diff --git a/.template.config/ide.host.json b/.template.config/ide.host.json index 78f3963..2860180 100644 --- a/.template.config/ide.host.json +++ b/.template.config/ide.host.json @@ -7,8 +7,8 @@ "isVisible": "true" }, { - "id": "SkipGit", - "name": { "text": "Skip Git" }, + "id": "PluginDescription", + "name": { "text": "Plugin Description" }, "isVisible": "true" } ] diff --git a/.template.config/template.json b/.template.config/template.json index 4f1192e..3764bb3 100644 --- a/.template.config/template.json +++ b/.template.config/template.json @@ -10,19 +10,26 @@ }, "sourceName": "LabApiPluginTemplate", "preferNameDirectory": true, + "sources": [ + { + "source": "./content", + "target": "./" + } + ], "symbols": { "PluginAuthor": { "type": "parameter", "datatype": "string", - "defaultValue": "YourName", + "defaultValue": "Your Name", "replaces": "YourName", "description": "Name used as the plugin's author." }, - "SkipGit": { + "PluginDescription": { "type": "parameter", - "datatype": "bool", - "description": "If set, skips printing the reminder to initialize a git repository.", - "defaultValue": "false" + "datatype": "string", + "defaultValue": "Your Description", + "replaces": "TODO: describe what this plugin does.", + "description": "Short description of what the plugin does." } }, "primaryOutputs": [ @@ -36,15 +43,6 @@ ], "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", "continueOnError": true - }, - { - "condition": "(!SkipGit)", - "description": "This project is not a git repository yet.", - "manualInstructions": [ - { "text": "Run 'git init && git add -A && git commit -m \"Initial commit\"'" } - ], - "actionId": "AC1156F7-BB77-4DB8-B28F-24EEBCCA1E5C", - "continueOnError": true } ] } diff --git a/CLAUDE.md b/CLAUDE.md index 74df3a4..331ca7a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,10 +13,9 @@ 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 -`SkipGit`, which only toggles the git-reminder post-generation action and replaces no -text). The dotnet templating engine does a literal, repo-wide **string replacement** -at generation time — not semantic renaming — driven by that config: +`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 (`sourceName` in `template.json`, currently this repo's own folder name) → whatever `-n`/`--name` the consumer passes to `dotnet new`. @@ -41,50 +40,86 @@ Consequences: ## 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. +- 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, the `PluginAuthor` symbol, + a `sources` entry 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 to `content/` — + that's correct and expected, not a bug), and one `postAction`: the standard + dotnet-templating "restore NuGet packages" action (id `210D431B-...`; 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 any symbol (the CLI flags stay `PluginAuthor`/ - `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. + wizard. It does **not** rename the `PluginAuthor` symbol (a flag can't contain a + space); it only maps the symbol's `id` to a human-readable `name.text` label shown + in VS's UI, via `isVisible: "true"` (VS hides a symbol from that UI unless + explicitly marked visible here). Ignored entirely by `dotnet new` on the CLI. +- `content/` — the actual buildable project, and the only folder the `sources` + mapping in `template.json` copies into generated output: `LabApiPluginTemplate.csproj`, + `EntryPoint.cs`, `Config.cs`, and `content/CLAUDE.md`. Add any new file that should + ship with generated plugins in here, not at the repo root. +- `content/CLAUDE.md` is a **different file** from this one (the repo-root + `CLAUDE.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 with `SearchSkills`/`ToolSearch` before 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.md` omits everything specific to + editing *this* template repo (symbols, `sources` mapping, `sourceName` traps, 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 (see `git 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 +`///.csproj` instead of the flat +`//.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 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: +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 (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. + "Run script" action (id `3A7C4B45-...`). Wiring it to run `git init`/`git add`/ + `git commit` 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. + `dotnet new create --help` on this SDK exposes no `--allow-scripts` or 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 `SkipGit` symbol plus a "display manual + instructions" postAction that just printed the `git init` one-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.json` entry. - 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. + template-side configuration — so VS users get real automatic git init for free. + CLI users just run `git init` themselves after generating. ## LabAPI contract (verified against northwood-studios/LabAPI source, not guessed) @@ -121,7 +156,8 @@ assume one exists. `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`). + 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.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 @@ -136,10 +172,10 @@ assume one exists. ## 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. +- 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 `.csproj` 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. @@ -170,10 +206,17 @@ 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. +- `dotnet build` also works directly inside `content/` (the placeholder project is + itself valid and compilable), but that only proves the *unsubstituted* placeholder + compiles — it does not exercise symbol substitution or the `sources` mapping. + Always additionally verify through an actual `dotnet new` generation 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 mistaken + `dotnet new install .` run one level up — that other install shadows this one and + your edits won't be exercised. Run `dotnet new uninstall ` first. - On Windows, if cleaning up `../_verify` fails with "resource busy", run - `dotnet build-server shutdown` first, then retry. + `dotnet build-server shutdown` first, then retry; if it still won't delete, fall + back to PowerShell's `Remove-Item -Recurse -Force`. - Delete the `../_verify` scratch output when done; it isn't part of the template. diff --git a/LabApiPluginTemplate.csproj b/LabApiPluginTemplate.csproj deleted file mode 100644 index b92a9f3..0000000 --- a/LabApiPluginTemplate.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - - netstandard2.1 - - - 14.0 - - enable - disable - LabApiPluginTemplate - LabApiPluginTemplate - false - false - - - $(NoWarn);NU1701 - - - None - - - - - - NU1701 - - - - diff --git a/content/CLAUDE.md b/content/CLAUDE.md new file mode 100644 index 0000000..8723b55 --- /dev/null +++ b/content/CLAUDE.md @@ -0,0 +1,63 @@ +# CLAUDE.md + +This is an SCP: Secret Laboratory LabAPI plugin, generated from the +`labapi-plugin` `dotnet new` template. `EntryPoint.cs` is the plugin's entry +point (`Plugin`); `Config.cs` is its YAML-backed settings class. + +## 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 project, 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. + +## Toolchain choices — don't "fix" these without being asked + +- `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 asked. +- `LangVersion` is pinned explicitly in the `.csproj` — 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 (both are explained here, not in the `.csproj` — this project's + code style forbids comments, including in the `.csproj`, see "Code style" below). +- `Northwood.LabAPI` uses a floating `Version="*"` on purpose, so every + `dotnet restore` pulls whatever is latest on NuGet at that moment. This is + intentionally **not** wired to `EntryPoint.cs`'s `RequiredApiVersion`, which is a + separate, manually-maintained literal for the minimum LabAPI version this 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. + +## Code style + +- No `//`, `/* */`, `///`, or XML (``) comments unless explicitly asked for. + No comments restating self-explanatory code. +- All text embedded in code defaults to **English**: string/log/console messages, + exception messages, `[Description]`/config text, any user-facing string. +- This never applies to identifiers picked deliberately (project name, namespace, + class/method/property/variable names) — keep those as requested. + +## How to verify a change + +There is no test project scaffolded. Verify with: + +```bash +dotnet build +``` + +Expect `Build succeeded. 0 Warning(s) 0 Error(s)`. diff --git a/Config.cs b/content/Config.cs similarity index 100% rename from Config.cs rename to content/Config.cs diff --git a/EntryPoint.cs b/content/EntryPoint.cs similarity index 85% rename from EntryPoint.cs rename to content/EntryPoint.cs index 093393a..d894295 100644 --- a/EntryPoint.cs +++ b/content/EntryPoint.cs @@ -1,7 +1,6 @@ using System; using LabApi.Features.Console; using LabApi.Loader.Features.Plugins; -using LabApi.Loader.Features.Plugins.Enums; namespace LabApiPluginTemplate { @@ -15,8 +14,6 @@ namespace LabApiPluginTemplate public override Version RequiredApiVersion { get; } = new Version("1.1.7"); - public override LoadPriority Priority => LoadPriority.Medium; - public override void Enable() { Logger.Info($"{Name} has been enabled."); diff --git a/content/LabApiPluginTemplate.csproj b/content/LabApiPluginTemplate.csproj new file mode 100644 index 0000000..3acd28a --- /dev/null +++ b/content/LabApiPluginTemplate.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.1 + 14.0 + enable + disable + LabApiPluginTemplate + LabApiPluginTemplate + false + false + $(NoWarn);NU1701 + None + + + + + NU1701 + + + +