14 KiB
CLAUDE.md
This repo is a dotnet new project template (identity SCPSL.LabApi.PluginTemplate,
shortName labapi-plugin), not a runnable plugin. It scaffolds SCP: Secret Laboratory
LabAPI plugins.
The next section only applies while you're editing this template repository
(i.e. a .template.config/ folder is present next to this file). It does not apply
if you're instead looking at a plugin someone already generated from this template —
that folder never contains .template.config/, and its EntryPoint.cs/Config.cs
are ordinary project files at that point, not template scaffolding.
Repo is template source — read before touching any file
.template.config/template.json sets this folder's name as the template's
sourceName and defines one text-replacement symbol, PluginAuthor. The dotnet
templating engine does a literal, repo-wide string replacement at generation
time — not semantic renaming — driven by that config:
- The folder/project name (
sourceNameintemplate.json, currently this repo's own folder name) → whatever-n/--namethe consumer passes todotnet new. - The placeholder value of the
PluginAuthorsymbol (the string currently sitting inEntryPoint.cs'sAuthorproperty) → the consumer's--PluginAuthor.
Open template.json to see the exact current literal for each — don't hardcode them
again here or anywhere else in prose, because this file itself ships inside every
generated project and any exact match gets rewritten too (self-referential text like
"the project is called X" would silently turn into nonsense after generation).
Consequences:
- Never introduce an exact match of one of those two literals elsewhere in the repo unless you intend it to be substituted too.
- If you add a new thing that should be configurable at generation time, add a symbol
to
template.json(type: parameter,replaces: "<literal>") instead of hardcoding a magic string. EntryPointandConfig(the class names) are not substituted and must stay literally named that — LabAPI'sPluginLoaderdiscovers plugins by type inheritance, not by name, and the file namesEntryPoint.cs/Config.csare a fixed part of this template's contract. Only the namespace tracks the project name.
Repo layout (only what affects decisions)
- Repo root holds only template-source/maintenance files that must never ship in
a generated project:
.git/,.gitignore,.editorconfig,CLAUDE.md, and.template.config/. None of these are copied to generated output — see "content/ vs. repo root" below. .template.config/template.json— template identity, thePluginAuthorsymbol, asourcesentry that maps./content→ the output root (this is what keeps the root-level files above out of every generated project),primaryOutputs(points at the project file using its pre-substitution literal name, relative tocontent/— that's correct and expected, not a bug), and onepostAction: the standard dotnet-templating "restore NuGet packages" action (id210D431B-...; skipped in Visual Studio, which restores on its own)..template.config/ide.host.json— Visual Studio-only UI hints for the New Project wizard. It does not rename thePluginAuthorsymbol (a flag can't contain a space); it only maps the symbol'sidto a human-readablename.textlabel shown in VS's UI, viaisVisible: "true"(VS hides a symbol from that UI unless explicitly marked visible here). Ignored entirely bydotnet newon the CLI.content/— the actual buildable project, and the only folder thesourcesmapping intemplate.jsoncopies into generated output:LabApiPluginTemplate.csproj,EntryPoint.cs,Config.cs, andcontent/CLAUDE.md. Add any new file that should ship with generated plugins in here, not at the repo root.content/CLAUDE.mdis a different file from this one (the repo-rootCLAUDE.md, which is template-editing guidance and never ships). It's the project memory a generated plugin ships with, so Claude Code has LabAPI/toolchain/style context automatically in every plugin generated from this template, without depending on any C#/.NET/LabAPI Skill existing in the catalog (none currently does — re-check withSearchSkills/ToolSearchbefore assuming that's still true). Keep its LabAPI-contract/toolchain/style sections in sync with this file's equivalent sections when either changes;content/CLAUDE.mdomits everything specific to editing this template repo (symbols,sourcesmapping,sourceNametraps, the verification workflow below) since none of that applies to a generated plugin.- No
.sln, no test project, no CI, no packaging (.nupkg) project, no GitHub Copilot instructions file currently exist in this folder. Don't assume any of them. This repo itself does have its own git history (seegit log) — that's unrelated to whether a generated project gets one, which is what "Git on generation" below covers.
content/ vs. repo root — why the project isn't at the repo root
Early versions of this template had the .csproj/.cs files sitting directly next
to .gitignore/.editorconfig/CLAUDE.md/.template.config/ at the repo root, with
no sources entry in template.json. Because dotnet new copies everything under
the mounted template root except .template.config/ by default, every one of those
maintenance files silently ended up inside every generated plugin too.
The fix: content/ holds only what should be copied, and template.json declares
"sources": [{ "source": "./content", "target": "./" }]. Files outside ./content
(root-level .gitignore, .editorconfig, CLAUDE.md) are never touched by
generation — no exclude-list to maintain.
Do not nest a folder named LabApiPluginTemplate inside content/ (e.g.
content/LabApiPluginTemplate/EntryPoint.cs). sourceName substitution renames any
folder whose name matches it, so an inner folder named after the project would get
renamed too, producing a doubled-up output path like
<output>/<ProjectName>/<ProjectName>/<ProjectName>.csproj instead of the flat
<output>/<ProjectName>/<ProjectName>.csproj consumers expect. Keep the .csproj/
.cs files directly under content/.
Git on generation — why there's no automated git init
A generated project is not a git repository, and there is no postAction that
tries to make it one. This was verified, not assumed — re-verified against the
dotnet 10.0.400 SDK on this machine while working on this exact question:
dotnet new's only postAction capable of running an arbitrary process is the "Run script" action (id3A7C4B45-...). Wiring it to rungit init/git add/git commithangs forever in any non-interactive shell: the CLI's script-execution security gate demands an explicitYtyped at aDo you want to run this action [Y(yes)|N(no)]?prompt, once per script action.dotnet new create --helpon this SDK exposes no--allow-scriptsor equivalent flag to pre-approve it (unlike the NuGet-restore postAction, which the CLI treats as trusted and never gates). Do not reintroduce "Run script" postActions for this without re-verifying that gate no longer applies on whatever SDK is current then.- A previous revision compensated with a
SkipGitsymbol plus a "display manual instructions" postAction that just printed thegit initone-liner. That has been removed — it wasn't automation, just a reminder to run a command any git user already knows, so it wasn't worth the extra symbol/postAction/ide.host.jsonentry. - Visual Studio's own "New Project" dialog already has a built-in "Create new Git
repository" checkbox that works for any template, including this one, with zero
template-side configuration — so VS users get real automatic git init for free.
CLI users just run
git initthemselves after generating.
LabAPI contract (verified against northwood-studios/LabAPI source, not guessed)
Plugin<TConfig> (LabApi.Loader.Features.Plugins) requires implementing:
Name, Description, Author, RequiredApiVersion (all abstract get-only) plus
Enable()/Disable(). Overridable but optional: Version (defaults to the
assembly's version), Priority (LoadPriority, defaults to Medium),
ConfigFileName (defaults to "config.yml"). Config is a plain class with a
public parameterless constructor and public get/set properties — the base class
(de)serializes it via YamlDotNet automatically; don't hand-roll config I/O.
Never invent a LabAPI class/namespace/method/property/attribute. Before using one
you haven't already seen in this repo, check the restored package (LabApi.dll +
LabApi.xml under %userprofile%\.nuget\packages\northwood.labapi\<version>\lib\net48\)
or https://github.com/northwood-studios/LabAPI. If you can't verify a member exists,
say so instead of guessing. No Skill/plugin specific to C#, .NET, MSBuild, NuGet, or
LabAPI exists in the Claude Code skill catalog as of this writing — check
ToolSearch/SearchSkills for one before editing in case that has changed, but don't
assume one exists.
Toolchain choices — don't "fix" these without being asked
- The project's
TargetFrameworkisnetstandard2.1on purpose, even though LabAPI's own docs and theNorthwood.LabAPINuGet package only targetnet48/net481. This is a deliberate compatibility choice (compiled output still loads under the game's Mono/.NET Framework 4.8 host) that trades away some netstandard2.1 BCL/language surface .NET Framework can't actually run (e.g. default interface members). Don't switch it tonet48, don't add multi-targeting, unless the user asks. LangVersionis pinned explicitly (currently14.0) — don't let it drift tolatest.- The
.csprojsuppresses exactly two warnings, both scoped as narrowly as MSBuild allows, because of the netstandard2.1/net48-only-package combination above:NU1701(via a per-PackageReference<NoWarn>, not a project-wide one) andMSB3270(viaResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None). Don't broaden either into a blanket suppression and don't remove them without understanding why (both are explained here, not in the.csproj— this repo's code style forbids comments, including in the.csproj, see "Code style" below). - The
Northwood.LabAPIPackageReferenceuses a floatingVersion="*"on purpose, so everydotnet restorealways pulls whatever is the latest stable release on NuGet at that moment — there's no version to bump here. This is intentionally not wired toEntryPoint.cs'sRequiredApiVersion, which can't be a floating value (System.Versionhas no such concept and would throw at runtime if it tried).RequiredApiVersionis a separate, manually-maintained literal representing the minimum LabAPI version the scaffolded code is known to work with — bump it by hand if you verify compatibility with a newer release, don't try to auto-sync it with the floating restore. Also be aware a floating version means a plugin generated today can restore a different (newer) LabAPI release than one generated last week, with no pinning — that's the deliberate trade-off of "always latest" over reproducibility.
Code style for anything you write here
- No
//,/* */,///, or XML (<!-- -->) comments unless the user explicitly asks for comments/documentation in that request. No comments restating self-explanatory code.EntryPoint.cs/Config.cs/the.csprojare already comment-free — keep them that way and follow the same standard for any new file. - All text embedded in code defaults to English: string/log/console messages,
exception messages,
[Description]/config text, any user-facing string. Example:Logger.Info("Plugin initialized successfully"), not a Spanish equivalent. - This never applies to identifiers the user picked — project name, namespace, class/method/property/variable names stay exactly as requested (standard C# casing rules aside). Honor an explicit request for a specific piece of text in another language.
How to verify a change (no test suite exists — this is the actual check)
There is no automated test project. Verify any template change by actually
generating a project from it and building that output. Replace <repo-name> below
with this repo's actual folder name (the value you'd see in sourceName):
dotnet new uninstall .
dotnet new install .
dotnet new labapi-plugin -n VerifyPlugin -o ../_verify/VerifyPlugin
cd ../_verify/VerifyPlugin
dotnet build
Expect Build succeeded. 0 Warning(s) 0 Error(s). If it isn't 0/0, the change broke
either the template's own warning suppressions or something in EntryPoint.cs/
Config.cs/the .csproj.
Notes:
- Re-run
dotnet new uninstall .+dotnet new install .after any edit to.template.config/template.jsonor the scaffolded files — the template cache keys off the installed path and stale content otherwise gets regenerated. dotnet buildalso works directly insidecontent/(the placeholder project is itself valid and compilable), but that only proves the unsubstituted placeholder compiles — it does not exercise symbol substitution or thesourcesmapping. Always additionally verify through an actualdotnet newgeneration before calling a change done.- If
dotnet new install .warns about another installed template sharing this identity (SCPSL.LabApi.PluginTemplate) from a different path — e.g. this repo's own parent folder having been installed by an earlier mistakendotnet new install .run one level up — that other install shadows this one and your edits won't be exercised. Rundotnet new uninstall <that other path>first. - On Windows, if cleaning up
../_verifyfails with "resource busy", rundotnet build-server shutdownfirst, then retry; if it still won't delete, fall back to PowerShell'sRemove-Item -Recurse -Force. - Delete the
../_verifyscratch output when done; it isn't part of the template.