AGENTS.md · git:20261102.ba5cd7f · 2026-11-02 · sha256 ea716f2bf3589f75
AGENTS.md git:20261102.ba5cd7fA
Immutable. This exact content never changes and is served at /api/v1/blob/ea716f2bf3589f75.
<!-- Version: 1.0.0 --> # AGENTS.md ## Project overview `agents-plugins` is the CS Modding marketplace (`csmodding`) of agent plugins for Claude Code and OpenAI Codex CLI. - `plugins/coherent-gameface/`: flagship plugin. Drives a running Coherent Gameface UI (Cohtml) over a direct CDP WebSocket; TypeScript MCP server (npm `@csmodding/gameface-devtools-mcp`) + skills. - `plugins/unity-devtools/`: drives a running Unity Mono development build over the Mono Soft Debugger protocol (SDB); C# MCP server + SDB class library. - `plugins/cs2-modding/`: teaches an agent to write **Cities: Skylines II** code mods; skills + the TypeScript `cs2-dev` MCP server over the game's lifecycle, bundled and never published. The first two are generic toolkits, developed and verified against that game as the reference target. Each plugin documents its own architecture and gotchas in its `AGENTS.md` (with a `CLAUDE.md` symlink). Everything a plugin ships MUST live inside its `plugins/<name>/` directory: marketplace installs copy only that subtree. New plugins get a sibling directory and an entry in both marketplace files. ## Tech stack - [mise-en-place](https://mise.jdx.dev): dev tools, env vars and tasks. - Bun workspaces: the root `package.json` carries lint/format tooling (`oxfmt.config.ts`, `oxlint.config.ts`), lefthook, and what `scripts/` and `bench/` import; the gameface and `cs2-modding` `mcp/` folders are the workspace packages, and `bun.lock` lives at the root. - .NET 10 SDK: the unity-devtools C# projects, grouped by `agents-plugins.slnx` at the repo root. ## Repository structure - `.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`: the Claude Code and Codex CLI marketplace files. Both list every plugin. - `.mcp.json` (root): LOCAL DEV ONLY, wiring the MCP servers for sessions in this repo (gameface and `cs2-dev` from their committed bundles, unity from sources via `dotnet run`). Installed users get each plugin's own `.mcp.json`; keep them in sync when changing server wiring. - `scripts/`: `check-plugin-sync.ts` (manifest consistency) and `check-skill-content.ts` (the `cs2-modding` shipped-prose rules, plus the marker spelling and the baseline lines of the gameface skills), both part of `mise check`; `check-skill-changelog.ts` (`mise skills:check-changelog`, network-dependent, not in CI); `research-cite-audit.ts` (`mise research:cite-audit`, the `cs2-game-update` sweep's cite step, reads a local decompile so not in CI). - `.agents/hooks/check-line-length.ts`: PostToolUse hook reporting `.ts`/`.cs` lines over 100 characters. Synced verbatim from the `scrolls` repo, which is why oxlint ignores `.agents/hooks` and oxfmt all of `.agents`. Markdown is deliberately out of scope: these docs are agent-facing and unwrapped by design. - `.agents/skills/`: repo-only maintenance skills, each symlinked from `.claude/skills/` and shipped by no plugin. `cs2-game-update` is the `cs2-modding` version sweep: the `VOLATILE:` markers and the baseline lines are its checklist, and it edits `docs/research/` and `docs/SOURCES.md`, which is why it cannot ship. `gameface-update` sweeps the gameface plugin after the reference target's engine moved, and keeps the probe catalogue beside it. `scan-mcp-failures` mines this machine's transcripts for failed gameface, unity and `cs2-dev` calls, and feeds the **Hits** of `docs/ROADMAP.md` entries. `terra-nil-debuggee` launches a release Mono game from this machine's Steam library as a second debuggee for the unity server, the shape of a BepInEx-community report. - `docs/ROADMAP.md`: planned facets. `docs/solutions/`: one file per hard-won problem, linked from where it bites. `docs/adr/`: numbered decision records. - `docs/authoring/`: standing authoring contracts. The ticket template any spec's tickets take, pointed at from `docs/agents/cantrips-loop.md`'s publish-tickets verb; and the `cs2-modding` reference pipeline's own — one shape doc per reference family and the reference-ticket protocol — disclosed out of the plugin's `AGENTS.md` because only an authoring pass reaches them. `check-skill-content.ts` enforces both families' prose-line budgets. - `docs/SOURCES.md`: every source the `cs2-modding` pipeline may read, what each settles, and how to locate it. Other files point at it; keep it pointing at as few as possible. - `bench/`: the skill-efficacy benchmark, measuring what the four `cs2-*` skills are worth to an agent writing mod code against the same model without them. Its `README.md` holds the invocation contract, the task format, and how a run is resumed, calibrated and promoted; `core.ts` holds every decision and `run.ts` is a thin, deliberately untested shell. `results.md` is the tracked baseline, written only by `mise bench:promote` from an Opus run, never by hand. - `docs/research/`: the `cs2-modding` pipeline's cited stage, sitting outside `plugins/` so none of it ships. Its `README.md` holds the conventions a research file satisfies; nothing under `plugins/` may reference it or `SOURCES.md`, and `check-skill-content.ts` fails any shipped link resolving outside the plugin directory, since existence alone passes one that works here and dead-ends for every installed user. ## Commands - `mise check:agents`: read-only type check, lint, format and plugin-sync, output tuned for agents. `mise fix` applies auto-fixes; C# formatting is `mise fix:cs` (write-only, no read-only counterpart). - `mise test`: the .NET test suite. `mise test:gameface` and `mise test:cs2-dev` are the TypeScript MCP servers' and `mise bench:test` the benchmark core's, the three TypeScript suites; no task runs another, and the benchmark's is the one left out of CI. - `mise build:gameface`, `mise build:cs2-dev`: rebuild a shipped bundle (commit the result). Run `mise tasks` to see the full shortcut list; append arguments freely, mise passes them through (ex. `mise some:task --some-arg`). Do NOT use npx to run commands; prefer mise shortcuts, or bun/bunx when no shortcut exists. Always run the appropriate check/test commands after changes, at the end of the editing session rather than mid-flight. ## Dual-harness plugin architecture Every plugin ships two manifests, one per harness: `.claude-plugin/plugin.json` + `.mcp.json` for Claude Code, `.codex-plugin/plugin.json` + `.codex-plugin/mcp.json` for Codex CLI. The two MCP configs cannot be merged, and a plugin server must work with no env passed at all — read [`docs/solutions/dual-harness-mcp-config.md`](docs/solutions/dual-harness-mcp-config.md) before touching any of these files. SYNC RULE: the shared fields of a plugin's two plugin.json files (name, version, description, author, homepage, repository, license, keywords) must stay identical. Edit BOTH; `mise check:plugin-sync` enforces it in `mise check`, CI and the pre-commit. ## Versioning and releases release-please maintains a rolling release PR on `main` from Conventional Commits. Each toolkit plugin has two release units (the plugin and its mcp) joined by `linked-versions`, so they always share a version; `cs2-modding` is one unit, its server carrying the plugin's version and publishing nowhere. The plugins version independently and there is no root unit (root-only changes never release). Never hand-edit a version: each unit's number lives in a private `package.json` anchor, and release-please syncs it into the plugin manifests, the unity csproj `<Version>`, and the `dotnet dnx` version pins. - Any releasable commit under a plugin's directory bumps ALL of that plugin's units. - Keep a commit inside one release unit: each unit's changelog is written from the commits touching it, so a `feat` spanning a plugin and the root files root tooling under that plugin's release notes. - `feat` bumps minor, `fix` patch; a `!` or `BREAKING CHANGE:` commit bumps major once a unit has reached 1.0.0, and only minor while it is below. Any other version comes from a deliberate `Release-As:` footer. - An mcp release publishes itself from `release-please.yml` through trusted publishing, the gameface one to npm and the unity one to NuGet. There is no local publish path: a failed or missed publish is retried by running the workflow by hand with the release tag. - CI runs `mise check:agents` + both bundle builds with `git diff --exit-code` (catching a stale committed bundle) + `mise test:gameface` + `mise test:cs2-dev`, then builds the .NET solution and runs the tests. The pre-commit rebuilds and stages the bundle of each server with staged sources, and runs `dotnet test` on staged C# changes. ## Boundaries Never: - Create a git branch, stage files, or commit work yourself unless the user expressly told you so. - Commit secrets, tokens, `.env` files, dumps or credentials. - Modify generated files unless the generation command was run. - Change public API behavior without calling it out. - Reference Cities: Skylines 2 in `coherent-gameface` or `unity-devtools` — code, documentation and skills alike, those two stay application-agnostic. `cs2-modding` is the carve-out: a plugin about the game that names it throughout, with the reasoning in its own `AGENTS.md`. - Reference the coherent-gameface project in the unity-devtools one, and vice versa. Ask first before: - Adding a dependency. - Reworking architecture. - Performing destructive file or data operations. - Acting on the user's running game — launching it, quitting it, loading or reloading a save, changing a setting in it. Say what you need done and why, then wait: only they can see what the game is actually on, and a launch costs minutes of their machine. Building, deploying and reading logs need no such permission. **The ask ends the turn.** Put it last, in bold at the top level of your reply, and stop there rather than carrying on with the rest of the work — an ask buried mid-message under paragraphs of what you did instead goes unread, and the session then ships the workaround it was asking permission to avoid. Check `mcp__unity__status` or `game_status` before asking: the beacon answers in one call and the game is often already up. ## Where knowledge goes Five stores, checked in this order when writing something down: 1. **Code comments**: anything one file owns. If `Invoker.Retrying` implements the retry, the explanation lives there. 2. **A plugin's `skills/`**: how to drive the tools at runtime. Shipped, agent-facing, generic. 3. **`docs/solutions/`**: expensive investigations with dead ends, one file per problem, loaded on demand through a pointer placed where the problem bites. 4. **`docs/adr/`**: why a choice was made, one record per decision. 5. **`AGENTS.md`**: what no single site owns — the map, the conventions, the invariants spanning files. Plugin-specific facts go in that plugin's file, repo-wide facts at the root. An `AGENTS.md` may disclose a contract only one branch of work reaches into a `docs/` file of its own, pointed at from the rule it elaborates — the `cs2-modding` contracts under `docs/authoring/` are the ones that do, and the ticket template there is the loop config's parallel case, disclosed from its publish-tickets verb. An `AGENTS.md` line that restates a comment, a tool description, or plainly readable code is dead weight: delete it. Propose updates whenever you detect drift. `.scratch/` is working material and gitignored, so nothing tracked may cite a path inside it — a pointer from `docs/` or from a plugin into a scratch file dangles the moment the feature closes and its folder goes. Move the fact into one of the five stores instead. The exception is machine-local state a tracked script writes there and recreates when missing, such as the `scan-mcp-failures` ledger: that path cannot dangle. Because it is gitignored, a repo-wide sweep — renaming a term, retiring a rule — has to name `.scratch/` explicitly, since the search tools honour `.gitignore` and skip it by default. Such a sweep edits what it finds there: a live spec and its tickets are the instructions the next authoring pass runs on, so one still teaching a rule a decision has retired is a defect like any other, and a review that parks it as out of scope leaves the sweep half done. A sweep correcting shipped `cs2-modding` prose covers `docs/research/` too: those files are the next authoring pass's inputs, and a retired teaching surviving there walks straight back into the reference. Any edit to shipped `cs2-modding` prose also re-points the research files' line cites into it, since a line added or removed shifts every cite below it. **Sweep by subject rather than by the sentence being retired** — the same wrong claim is stated in other words elsewhere, and a grep for the old phrasing returns none of those files. It is also stated in places that are not prose and that a subject sweep misses: [the carriers a correction leaves behind](docs/solutions/a-correction-that-landed-on-one-carrier.md) enumerates them. Rescoping a rule takes the same sweep: enumerate every copy first and land them all in one batch, since each copy left behind becomes the file's counterexample. A file carrying a dated correction can still carry the pre-correction claim further down, since appending the note is not editing the original. Agent-facing prose is a deliverable here, not documentation of one: a plugin's `skills/`, every `AGENTS.md`, the rules files, and an MCP tool's or parameter's `[Description]` (agent-facing despite living in `.cs`). Load the `writing-for-agents` skill and hold the edit to it before writing any of them. In the toolkit plugins, its examples name placeholder types (`MyGame.Movement.Speed`) rather than the reference target's own, and claim verification against no named game: an example teaches a shape, while a real name invites an agent to try it on a game that never had it. Cite a checklist item by quoting its phrase, never its ordinal — an insertion silently renumbers every positional cite. A tool description may shed a trap onto another tier, never a trigger: a trap reaches the caller already using that tool, while a fact deciding whether to reach for it at all goes unread on the tool nobody reached — that one stays, as a pointer, on the tools whose callers need it. User-facing prose is the other deliverable: a README, `WHY.md`, anything a person reads to decide whether to install. It takes the opposite register to the above — contractions, one idea per sentence, em dashes rare — and the same standard of evidence, since a claim about someone else's project is checkable by the reader in a way an internal one is not. ## Preferred agent behavior - Start by inspecting existing patterns. - Prefer LSP over Grep/Glob/Read for code navigation. - Make the smallest safe change, but speak up when a refactor is overdue. - Prefer editing existing files over creating parallel abstractions. - When uncertain, state the assumption and proceed conservatively. - When correcting prose, quote the owning source or delete; a paraphrase or an added rationale is what the next review indicts. The `cs2-modding` `AGENTS.md` states the full rule for reference corrections. - Resolve a ticket only after its review gate passes. - A test written to hold a fix earns belief once you have watched it fail with that fix backed out; reading cannot settle whether it is coupled ([the shapes that fool a reading](docs/solutions/a-regression-guard-that-passes-without-its-behavior.md)). - Actively propose updates to `AGENTS.md`, comments, or other docs when you detect drift. - The gameface, unity and `cs2-dev` tools show their gaps only in use, so a session that drove any of those MCP servers owes the repo what it learned: the tool you wished existed, the mode missing from one that does, the parameter or output shape that cost you a round trip, the workaround you wrote instead. Collect them as you hit them and propose each as an entry in [`docs/ROADMAP.md`](docs/ROADMAP.md), naming the moment in the session that produced it as its evidence — the call you made, what came back, what you did instead. Surface the list at `/compound` time with the rest of the session's learnings, and let the user approve, redirect or drop each one; a proposal lands in `ROADMAP.md` only on an approval. - Formatters reformat the whole project, not your diff. Never revert what they touch: fold drift in a file your change already edits into that commit, and land drift in files it does not into a commit of its own.