# 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` (plus `skipRestore` and `SkipGit`, which only toggle post-generation actions and replace no text). 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 (`sourceName` in `template.json`, currently this repo's own folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`. - The placeholder value of the `PluginAuthor` symbol (the string currently sitting in `EntryPoint.cs`'s `Author` property) → 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: ""`) instead of hardcoding a magic string. - `EntryPoint` and `Config` (the class names) are **not** substituted and must stay literally named that — LabAPI's `PluginLoader` discovers plugins by type inheritance, not by name, and the file names `EntryPoint.cs`/`Config.cs` are a fixed part of this template's contract. Only the *namespace* tracks the project name. ## Repo layout (only what affects decisions) - `.template.config/template.json` — template identity, the symbols above plus `SkipGit` (a display-only toggle, see below), `primaryOutputs` (points at the project file using its pre-substitution literal name — that's correct and expected, not a bug), and two `postActions`: the standard dotnet-templating "restore NuGet packages" action (id `210D431B-...`; skipped in Visual Studio, which restores on its own) and a "display manual instructions" action (id `AC1156F7-...`) that prints a reminder to run `git init` — see "Git on generation" below for why it's just a printed reminder and not an executed command. - `.template.config/ide.host.json` — Visual Studio-only UI hints for the New Project wizard. It does **not** rename any symbol (the CLI flags stay `PluginAuthor`/ `skipRestore`/`SkipGit` — a flag can't contain a space); it only maps each symbol's `id` to a human-readable `name.text` label (e.g. `SkipGit` → "Skip Git") shown in VS's UI, via `isVisible: "true"` per entry (VS hides a symbol from that UI unless explicitly marked visible here). Ignored entirely by `dotnet new` on the CLI. - The `.csproj` at the repo root — the project file every generated plugin ships with. - `EntryPoint.cs` / `Config.cs` — the scaffolded plugin (`Plugin`) and its YAML-backed settings class. - `.editorconfig` — formatting/naming only (indentation, brace style). It does **not** enforce the comment/language rules below — those aren't analyzer-checkable. - 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 (see `git log`) — that's unrelated to whether a *generated* project gets one, which is what "Git on generation" below covers. ## Git on generation — why there's no automated `git init` A generated project is **not** a git repository by default; a `postAction` only prints a reminder to run `git init && git add -A && git commit -m "Initial commit"`. This was a deliberate choice after testing the alternative: - `dotnet new`'s only postAction capable of running an arbitrary process is the "Run script" action (id `3A7C4B45-...`). Actually wiring it to run `git init`/ `git add`/`git commit` was tested and it **hangs forever** in any non-interactive shell: the CLI's script-execution security gate demands an explicit `Y` typed at a `Do you want to run this action [Y(yes)|N(no)]?` prompt, once per script action, with no flag on this SDK 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. - 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 this for free regardless. - CLI users see the printed reminder above (governed by the `SkipGit` symbol) and run the one-liner themselves. ## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed) `Plugin` (`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\\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 `TargetFramework` is `netstandard2.1` on purpose, even though LabAPI's own docs and the `Northwood.LabAPI` NuGet package only target `net48`/`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 to `net48`, don't add multi-targeting, unless the user asks. - `LangVersion` is pinned explicitly (currently `14.0`) — don't let it drift to `latest`. - The `.csproj` suppresses 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` ``, not a project-wide one) and `MSB3270` (via `ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch=None`). Don't broaden either into a blanket suppression and don't remove them without understanding why (comments sit next to both in the `.csproj`). - The `Northwood.LabAPI` `PackageReference` uses a floating `Version="*"` on purpose, so every `dotnet restore` always pulls whatever is the latest stable release on NuGet at that moment — there's no version to bump here. This is intentionally **not** wired to `EntryPoint.cs`'s `RequiredApiVersion`, which can't be a floating value (`System.Version` has no such concept and would throw at runtime if it tried). `RequiredApiVersion` is 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 `///` comments unless the user explicitly asks for comments/documentation in that request. No comments restating self-explanatory code. `EntryPoint.cs`/`Config.cs` are 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 `` below with this repo's actual folder name (the value you'd see in `sourceName`): ```bash 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.json` or the scaffolded files — the template cache keys off the installed path and stale content otherwise gets regenerated. - `dotnet build` also works directly inside `` (the placeholder project is itself valid and compilable), but that only proves the *unsubstituted* placeholder compiles — it does not exercise symbol substitution. Always additionally verify through an actual `dotnet new` generation before calling a change done. - On Windows, if cleaning up `../_verify` fails with "resource busy", run `dotnet build-server shutdown` first, then retry. - Delete the `../_verify` scratch output when done; it isn't part of the template.