diff --git a/.agents/skills/comment-and-doc-style/SKILL.md b/.agents/skills/comment-and-doc-style/SKILL.md index af574e92..806ce4ed 100644 --- a/.agents/skills/comment-and-doc-style/SKILL.md +++ b/.agents/skills/comment-and-doc-style/SKILL.md @@ -65,15 +65,15 @@ boundary before repository mounts begin. Each Docker command has a timeout and v Lint containers disable networking and mount the checkout read-only. Persist approval only when the executor constrains that whole shape. Never allow an unconstrained `docker run` prefix. PSScriptAnalyzer downloads its pinned module in a separate container that has network access and -no repository mount. `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)" -owns the exact invocation and full authorization model. +no repository mount. `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working +Invocations)" section owns the exact invocation and full authorization model. Agent-specific authorization stays in provider-labeled bullets so one agent's configuration does not read as a shared requirement: -- **Codex:** rules cannot safely cover changing worktree paths and digests. Smart Approvals can - prompt per task. No-prompt operation is supported only inside an external sandbox because it - removes command-wide protection. +- **Codex:** execution rules match exact argument prefixes, so they cannot safely cover changing + worktree paths and digests. Smart Approvals can prompt per task. No-prompt operation is + supported only inside an external sandbox because it removes command-wide protection. ## Markdown formatting @@ -230,7 +230,7 @@ hub-hosted tool the reader runs, are in `references/carried-doc-references.md`. - **Rules**: no vague titles (`update stuff`, `wip`). Dependabot's default `Bump X from Y to Z` titles are fine as-is. No `Co-Authored-By:` lines unless the developer explicitly asks. No release-bump magnitude in the title ("minor", "patch", "release v0.2.0"), Nerdbank.GitVersioning - computes the next version from `version.json` and git history, a dependency version in a + computes the next version from `version.json` and git history. A dependency version in a dependency-bump title is fine and expected. US English spelling, and title case with lowercase short bind words (a, an, the, and, but, or, of, in, on, at, to, by, for, from), a hyphenated compound capitalizes both parts unless the second is a short preposition (*Built-in*, @@ -241,7 +241,7 @@ Add Structured Logging Extensions to Library Pin softprops/action-gh-release to Commit SHA Drop net8.0 Multi-Targeting from Console Project Bump xunit.v3 from 3.2.2 to 3.3.0 -Clarify devcontainer Setup Steps in README +Clarify Devcontainer Setup Steps in README ``` ## Quantitative claims diff --git a/.agents/skills/dotnet-codestyle/SKILL.md b/.agents/skills/dotnet-codestyle/SKILL.md index f3565a5c..2cc81eef 100644 --- a/.agents/skills/dotnet-codestyle/SKILL.md +++ b/.agents/skills/dotnet-codestyle/SKILL.md @@ -49,9 +49,11 @@ All builds must complete without warnings, enforced three ways: surfaced as a warning fails the build and must be fixed or deliberately suppressed at the narrowest scope that fits (see Analyzer suppressions below), never left to accumulate. - **CI lint backstop.** CI runs the clean-compile checks on every PR as the authoritative gate. - A working local hook is strongly suggested, not optional: wire Husky.Net from the canonical - `catalog/snippets/husky/` config. See GOVERNANCE.md "Running the Linters Locally" for what the - hook must cover and what its absence means. + The .NET mechanism for a local hook is Husky.Net, wired from the canonical + `catalog/snippets/husky/` config in the hub, hub-local and not carried into every fleet repo. + GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section + carries the obligation itself, what the hook must cover, its audit treatment, and the + per-clone enablement steps. **A new port is not a license to silence diagnostics.** Brownfield or just-ported status never justifies relaxing analyzer severities or muting newly surfaced warnings. Fix them. (The only @@ -95,8 +97,9 @@ updates, dependency upgrades, benchmarks) on top: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed`. - **`dotnet-outdated-tool`** checks for dependency updates, and Nerdbank.GitVersioning owns version management. -- CI is the authoritative lint backstop. A local pre-commit hook is strongly suggested: wire - Husky.Net from `catalog/snippets/husky/` for local enforcement, including the shared doc gates. +- CI is the authoritative lint backstop. The .NET mechanism for a local pre-commit hook is + Husky.Net, wired from `catalog/snippets/husky/` in the hub, hub-local and not carried into every + fleet repo, and covering the shared doc gates alongside the language checks. - **Required VS Code extensions**: CSharpier, markdownlint, CSpell. Use the workspace settings without overrides. diff --git a/.agents/skills/python-codestyle/SKILL.md b/.agents/skills/python-codestyle/SKILL.md index f7a7451e..210a67b7 100644 --- a/.agents/skills/python-codestyle/SKILL.md +++ b/.agents/skills/python-codestyle/SKILL.md @@ -92,13 +92,16 @@ repo's type checker: `uv run pyright`, or `uv run mypy src` where mypy is the CI where the repo runs both (see Type checking above). Run it, plus `uv run pytest`, before committing. A **lint-only** profile's clean-compile substitutes its `uvx` and `unittest` equivalents, per Two Profiles above, and has no such command to run before committing beyond -those. These are documented commands, and an optional VS Code tasks mirror (all `type: process`, -no `&&` shell chaining, so it runs the same on any task shell) is in the hub -`vscode-tasks-python.json` snippet. CI runs the same clean-compile commands as the authoritative -backstop. A working local hook is strongly suggested, not opt-in: wire the Python `pre-commit` -framework from the canonical `catalog/snippets/pre-commit/.pre-commit-config.yaml`. See -GOVERNANCE.md "Running the Linters Locally" for what the hook must cover and what its absence -means. +those. These are documented commands, and the hub's `vscode-tasks-python.json` snippet carries the +VS Code tasks mirror that the fleet baseline expects. Every command-executing task in it is +`type: process`, and every aggregator is `dependsOn`-only. Neither chains with `&&`, so the mirror +runs the same on any task shell. CI runs the same clean-compile commands as the authoritative +backstop. The Python mechanism for a local hook is the `pre-commit` framework, wired from the +canonical `catalog/snippets/pre-commit/` directory, hub-local and not carried into every fleet +repo. That directory's own README names the second file to copy alongside the config. +GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section carries +the obligation itself, what the hook must cover, its audit treatment, and the per-clone enablement +steps. A restricted executor gives each task a cache directory under a writable temporary root. Point `UV_CACHE_DIR`, `RUFF_CACHE_DIR`, `MYPY_CACHE_DIR`, and `COVERAGE_FILE` into that directory before diff --git a/.agents/skills/repo-worktree/SKILL.md b/.agents/skills/repo-worktree/SKILL.md index 9e675f0a..4dda047f 100644 --- a/.agents/skills/repo-worktree/SKILL.md +++ b/.agents/skills/repo-worktree/SKILL.md @@ -65,13 +65,14 @@ The base clone is a fetch source, not a place to do task work. `fetch` and `work against it for that purpose, and outside "Listing and Cleanup"'s own terminal step below, nothing else does: never `checkout`, `pull`, `reset`, `commit`, or any other command that mutates its own working tree, index, or HEAD while a task is in progress. That distinction is the one a real -incident missed, reusing a primary checkout as the working directory itself rather than only as -the source a worktree is created from. On Claude Code this is also a mechanical stop for most -of that list. `merge --ff-only`/`pull --ff-only` and a `checkout `/`switch ` carrying no -force flag stay exempt even there, matching this skill's own cleanup step below, which needs -exactly those. -Prose remains the only enforcement for a non-Claude-Code agent, and for the shapes the hook itself -exempts. +incident missed, where an agent reused a primary checkout as the working directory itself rather +than only as the source a worktree is created from. On a machine carrying the hub's agent-safety +install, Claude Code also makes this a mechanical stop for most of that list. +`merge --ff-only`/`pull --ff-only` stay exempt even there, and so does a `checkout `/`switch ` +naming exactly one positional that resolves as a ref, with no force flag and no `--` separator, +which is the shape this skill's own cleanup step runs. A `checkout -- .` or a `switch -c ` is +denied, being neither. Prose remains the only enforcement for a non-Claude-Code agent, for a +machine without that install, and for the shapes the hook itself exempts. ## Creating a Worktree @@ -129,18 +130,26 @@ command. A declared sandbox boundary is the reason to request that approval, not reason to skip the registered worktree. Use the standard layout when both locations are writable or the executor approves the scoped -write. When approval is unavailable or denied, create a standalone clone under a writable -temporary root. Name it `/-`, fetch immediately, and create the -task branch from `origin/develop`. A standalone clone keeps its worktree and Git administrative -directory under the same writable root. It therefore supports edits, explicit-path staging, -commits, and branch updates without sharing the base clone's index. +write. When approval for the worktree path is unavailable or denied, create a standalone clone +under a writable temporary root. Name it `/-`, fetch immediately, +and create the task branch from `origin/develop`. On Claude Code that clone needs a grant of its +own before its git steps will run, per the paragraph below, so ask for both together rather than +meeting the second after the clone exists. A standalone clone keeps its worktree and Git +administrative directory under the same writable root. It therefore supports edits, explicit-path +staging, commits, and branch updates without sharing the base clone's index. On Claude Code, a standalone clone is structurally a primary checkout to `gh-write-guard.py`'s own rule 6 test (`--git-dir` equals `--git-common-dir` there too, since it is not a linked worktree of -anything), so the hook denies the very commits/edits this fallback exists to make. Set -`GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT` for the session before using one -- the same escape hatch -`host-setup/agent-safety/README.md`'s requirement 6 already documents -- since this fallback is -exactly the narrow, already-approval-gated case that grant exists for. +anything), so the hook denies the git commands this fallback exists to run. Its rules classify git +and gh commands only, so a plain file edit is never denied there and only the git steps need the +grant, though a compound command is judged whole, so an edit chained to a denied git write does +not run either. That grant is `GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT`, the same escape hatch +`host-setup/agent-safety/README.md` in the hub already documents as its requirement 6, not a +repo-relative link since that path is hub-local and not carried into every fleet repo. The +maintainer sets it before the session starts, and an agent cannot set it for itself: the guard +reads it from the environment the session was launched with, never from an inline `VAR=x` prefix +or an `export` the agent runs, so ask for it rather than trying to set it mid-session. This +fallback is exactly the narrow, already-approval-gated case that grant exists for. A temporary standalone clone is a degraded handoff, not an equivalent location. The base clone does not register it, `git worktree list` does not show it, and an IDE opened on the base clone @@ -168,13 +177,13 @@ git -C ~/repos/ fetch origin git -C ~/repos/ worktree add ~/repos/worktrees/- ``` -When the base clone holds only the remote-tracking ref, the same command creates the local -branch tracking `origin/` through git's ordinary checkout guessing, so a fresh -clone needs no separate branch setup. Git refuses to attach a branch that is already checked -out somewhere else, and that refusal is the mandate working, since the branch sitting checked -out in a shared tree is the hazard the continuation rule exists for. Return that checkout to -its own working branch first when its tree is clean, and stop when it is not, because a dirty -tree there may be another task's uncommitted work. +When the base clone holds only the remote-tracking ref, the same command creates the local branch +tracking `origin/` through git's ordinary checkout guessing, so a fresh clone needs +no separate branch setup. Git refuses to attach a branch that is already checked out somewhere +else, and that refusal is the mandate working, since the branch sitting checked out in a shared +tree is the hazard the continuation rule exists for. Return that checkout to its own working +branch first when its tree is clean, and stop when it is not, because a dirty tree there may be +another task's uncommitted work. A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine diff --git a/.agents/skills/resync-a-repo/SKILL.md b/.agents/skills/resync-a-repo/SKILL.md index 67918990..2bd17671 100644 --- a/.agents/skills/resync-a-repo/SKILL.md +++ b/.agents/skills/resync-a-repo/SKILL.md @@ -36,23 +36,26 @@ anything else. The finding kind names the procedure the repo is owed. ## Reach the hub and measure before changing anything -Fetch a hub checkout of your own immediately before reading it, per RESYNC.md section 1, since a -stale clone answers confidently instead of failing. Never operate against an existing checkout -already present at a known or shared path, the maintainer's own primary checkout included, even -one that looks current -- always fetch into a private worktree of your own, per `repo-worktree`. -On Claude Code this is now also a mechanical stop for most such commands (a `PreToolUse` hook -denies a mutating git operation run directly in a primary checkout), though this prose is still -the only enforcement for a non-Claude-Code agent, and for the narrow shapes the hook itself -exempts, so following it here is not optional even where no hook can catch a lapse. Verify the -host with `python3 scripts/host_gate.py --repo `. Then run the audit end -to end, -RESYNC.md section 2, against the target's `main` branch, never `develop`. A finding is a snapshot, -so quote the run stamp in anything derived from it and re-run before acting on a finding read -earlier in the session. -File any hub defect this work exposes against `ptr727/ProjectTemplate`. -Examples include bugs, conflicting sources, unclear or incomplete instructions, missing capabilities, and Copilot findings about any of them. -Search open and closed issues first, then update the matching issue or file a new one. -Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding only in chat, a review thread, the downstream repo, or agent memory. +Fetch a hub checkout of your own immediately before reading it, per the hub-only `RESYNC.md` +section 1, a file fetched with the hub rather than present in a carrier, since a stale clone +answers confidently instead of failing. Never do the resync's own work in a checkout another task +may be using, the maintainer's own primary checkout included, even one that looks current: for the +hub and for the target repo alike, create a worktree of your own off that base clone and work +there, per `repo-worktree`. On a machine carrying the hub's agent-safety install, Claude Code also +refuses a mutating git command run directly in a primary checkout, though its rules classify git +and gh commands only, so a plain file write is never denied and the re-vendoring this skill exists +to govern goes uncaught. A compound command is judged whole, so a copy chained to a denied git +write does not run either. Prose is the enforcement here, and following it is not optional. Verify +the host with `python3 scripts/host_gate.py --repo `, run from your hub +worktree, since `scripts/` is hub-hosted and no carrier holds it. Then run the audit end to end, +`RESYNC.md` section 2, against the target's `main` branch, never `develop`. A finding is a +snapshot, so quote the run stamp in anything derived from it and re-run before acting on a finding +read earlier in the session. File any hub defect this work exposes against +`ptr727/ProjectTemplate`. Examples include bugs, conflicting sources, unclear or incomplete +instructions, missing capabilities, and Copilot findings about any of them. Search open and closed +issues first, then update the matching issue or file a new one. Preserve the evidence `RESYNC.md` +section 2 requires, and do not leave the finding only in chat, a review thread, the downstream +repo, or agent memory. ## Apply, in this order diff --git a/.agents/skills/shell-codestyle/SKILL.md b/.agents/skills/shell-codestyle/SKILL.md index eaa4a583..46fbaec0 100644 --- a/.agents/skills/shell-codestyle/SKILL.md +++ b/.agents/skills/shell-codestyle/SKILL.md @@ -28,34 +28,35 @@ cover general scripting style. Bash, and only where a program cannot be Python: a bootstrap that installs the interpreter cannot be written in it, and a host tool that must run before a development toolchain exists cannot -depend on one. Everything else is Python, with a test under the scripts tree's `tests/` directory. +depend on Python either. Everything else is Python, with a test under its own scripts tree's +`tests/` directory. ## Rules - **`shellcheck` is the linter and `shfmt` the formatter.** The clean-compile is `shellcheck` clean at default severity plus `shfmt -d`, both reporting nothing before a commit. CI enforces - both, per `GOVERNANCE.md` "Running the Linters Locally", and `scripts/docker_lint.py` runs the - same pair headless. Neither is scoped to the `*.sh` glob alone: a tracked, extension-less - script whose shebang names bash or sh (the shape a script meant to run as a bare command takes) - joins the target list too. + both, per `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" + section, and the hub's `scripts/docker_lint.py` runs the same pair headless. Neither is scoped + to the `*.sh` glob alone: a tracked, extension-less script whose shebang names bash or sh (the + shape a script meant to run as a bare command takes) joins the target list too. - **`set -Eeuo pipefail`, before the first command the script runs.** A header comment sits above - it, as `repo-config/configure.sh` and the `host-setup/` scripts do, since what matters is that - nothing executes unguarded rather than which line number it lands on. Without `-e` a failed - command in the middle of a sequence lets the rest run against a state nobody checked, and + it, as the hub's own `repo-config/configure.sh` and `host-setup/` scripts do, since what matters + is that nothing executes unguarded rather than which line number it lands on. Without `-e` a + failed command in the middle of a sequence lets the rest run against a state nobody checked, and without `pipefail` a pipeline reports the exit of its last stage, so a fetch that failed reads as an answer when a parser downstream succeeds on an empty input. `-E` carries an `ERR` trap into functions and command substitutions, so a script that later adds one is not surprised by where it does not fire. - **A reader that stops early needs its producer read first.** Under `pipefail`, a producer - writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as - a failure whenever the match is found early enough. Capture the output, then search it. + writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as a + failure whenever the match is found early enough. Capture the output, then search it. - **Self-locating, never dependent on the caller's directory.** A script resolves its own directory from `BASH_SOURCE` and references its payloads through it, since the working directory at invocation is not a property of the script. - **`shellcheck` clean, and a deliberate exception carries its reason inline.** A `# shellcheck disable=SCxxxx` names why the rule does not apply here, so the next reader can - tell a considered exception from an unread warning. `repo-config/configure.sh` is the worked - example, carrying five `SC2016` disables where a single-quoted `jq` program must stay + tell a considered exception from an unread warning. The hub's own `repo-config/configure.sh` is + the worked example, carrying `SC2016` disables where a single-quoted `jq` program must stay unexpanded, each with its reason on the same line. - **Comments say why, never what.** The code states what it does. A comment restating it goes stale silently, where a comment carrying a reason fails visibly when the reason stops being diff --git a/.agents/skills/workflow-ci-contract/SKILL.md b/.agents/skills/workflow-ci-contract/SKILL.md index 54d97c14..9eaf908f 100644 --- a/.agents/skills/workflow-ci-contract/SKILL.md +++ b/.agents/skills/workflow-ci-contract/SKILL.md @@ -44,4 +44,4 @@ The full catalog, each guarantee with the failure mode it prevents, is in `refer ## After Any Workflow Edit -Workflow-only changes are not smoke-built, so run actionlint locally (the Docker invocation in `GOVERNANCE.md` "Running the Linters Locally", which bundles shellcheck for `run:` blocks) before pushing, and remember a workflow change is only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. +Workflow-only changes are not smoke-built, so run actionlint locally before pushing. Run it from the repository being checked, as `python3 /path/to/ProjectTemplate/scripts/docker_lint.py --root "$PWD" --linter actionlint`, using the hub-hosted wrapper documented in `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" section. actionlint includes `shellcheck` for `run:` blocks, so `--linter actionlint` already covers them. A workflow change is still only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. diff --git a/.claude-plugin/fleet-skills/.source-digest b/.claude-plugin/fleet-skills/.source-digest index dd9c5d32..008f19b6 100644 --- a/.claude-plugin/fleet-skills/.source-digest +++ b/.claude-plugin/fleet-skills/.source-digest @@ -1 +1 @@ -8d66cb3bd100d1b8 +fe834763b58313f7 diff --git a/.claude-plugin/fleet-skills/skills/comment-and-doc-style/SKILL.md b/.claude-plugin/fleet-skills/skills/comment-and-doc-style/SKILL.md index af574e92..806ce4ed 100644 --- a/.claude-plugin/fleet-skills/skills/comment-and-doc-style/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/comment-and-doc-style/SKILL.md @@ -65,15 +65,15 @@ boundary before repository mounts begin. Each Docker command has a timeout and v Lint containers disable networking and mount the checkout read-only. Persist approval only when the executor constrains that whole shape. Never allow an unconstrained `docker run` prefix. PSScriptAnalyzer downloads its pinned module in a separate container that has network access and -no repository mount. `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)" -owns the exact invocation and full authorization model. +no repository mount. `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working +Invocations)" section owns the exact invocation and full authorization model. Agent-specific authorization stays in provider-labeled bullets so one agent's configuration does not read as a shared requirement: -- **Codex:** rules cannot safely cover changing worktree paths and digests. Smart Approvals can - prompt per task. No-prompt operation is supported only inside an external sandbox because it - removes command-wide protection. +- **Codex:** execution rules match exact argument prefixes, so they cannot safely cover changing + worktree paths and digests. Smart Approvals can prompt per task. No-prompt operation is + supported only inside an external sandbox because it removes command-wide protection. ## Markdown formatting @@ -230,7 +230,7 @@ hub-hosted tool the reader runs, are in `references/carried-doc-references.md`. - **Rules**: no vague titles (`update stuff`, `wip`). Dependabot's default `Bump X from Y to Z` titles are fine as-is. No `Co-Authored-By:` lines unless the developer explicitly asks. No release-bump magnitude in the title ("minor", "patch", "release v0.2.0"), Nerdbank.GitVersioning - computes the next version from `version.json` and git history, a dependency version in a + computes the next version from `version.json` and git history. A dependency version in a dependency-bump title is fine and expected. US English spelling, and title case with lowercase short bind words (a, an, the, and, but, or, of, in, on, at, to, by, for, from), a hyphenated compound capitalizes both parts unless the second is a short preposition (*Built-in*, @@ -241,7 +241,7 @@ Add Structured Logging Extensions to Library Pin softprops/action-gh-release to Commit SHA Drop net8.0 Multi-Targeting from Console Project Bump xunit.v3 from 3.2.2 to 3.3.0 -Clarify devcontainer Setup Steps in README +Clarify Devcontainer Setup Steps in README ``` ## Quantitative claims diff --git a/.claude-plugin/fleet-skills/skills/dotnet-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/dotnet-codestyle/SKILL.md index f3565a5c..2cc81eef 100644 --- a/.claude-plugin/fleet-skills/skills/dotnet-codestyle/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/dotnet-codestyle/SKILL.md @@ -49,9 +49,11 @@ All builds must complete without warnings, enforced three ways: surfaced as a warning fails the build and must be fixed or deliberately suppressed at the narrowest scope that fits (see Analyzer suppressions below), never left to accumulate. - **CI lint backstop.** CI runs the clean-compile checks on every PR as the authoritative gate. - A working local hook is strongly suggested, not optional: wire Husky.Net from the canonical - `catalog/snippets/husky/` config. See GOVERNANCE.md "Running the Linters Locally" for what the - hook must cover and what its absence means. + The .NET mechanism for a local hook is Husky.Net, wired from the canonical + `catalog/snippets/husky/` config in the hub, hub-local and not carried into every fleet repo. + GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section + carries the obligation itself, what the hook must cover, its audit treatment, and the + per-clone enablement steps. **A new port is not a license to silence diagnostics.** Brownfield or just-ported status never justifies relaxing analyzer severities or muting newly surfaced warnings. Fix them. (The only @@ -95,8 +97,9 @@ updates, dependency upgrades, benchmarks) on top: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed`. - **`dotnet-outdated-tool`** checks for dependency updates, and Nerdbank.GitVersioning owns version management. -- CI is the authoritative lint backstop. A local pre-commit hook is strongly suggested: wire - Husky.Net from `catalog/snippets/husky/` for local enforcement, including the shared doc gates. +- CI is the authoritative lint backstop. The .NET mechanism for a local pre-commit hook is + Husky.Net, wired from `catalog/snippets/husky/` in the hub, hub-local and not carried into every + fleet repo, and covering the shared doc gates alongside the language checks. - **Required VS Code extensions**: CSharpier, markdownlint, CSpell. Use the workspace settings without overrides. diff --git a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md index f7a7451e..210a67b7 100644 --- a/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/python-codestyle/SKILL.md @@ -92,13 +92,16 @@ repo's type checker: `uv run pyright`, or `uv run mypy src` where mypy is the CI where the repo runs both (see Type checking above). Run it, plus `uv run pytest`, before committing. A **lint-only** profile's clean-compile substitutes its `uvx` and `unittest` equivalents, per Two Profiles above, and has no such command to run before committing beyond -those. These are documented commands, and an optional VS Code tasks mirror (all `type: process`, -no `&&` shell chaining, so it runs the same on any task shell) is in the hub -`vscode-tasks-python.json` snippet. CI runs the same clean-compile commands as the authoritative -backstop. A working local hook is strongly suggested, not opt-in: wire the Python `pre-commit` -framework from the canonical `catalog/snippets/pre-commit/.pre-commit-config.yaml`. See -GOVERNANCE.md "Running the Linters Locally" for what the hook must cover and what its absence -means. +those. These are documented commands, and the hub's `vscode-tasks-python.json` snippet carries the +VS Code tasks mirror that the fleet baseline expects. Every command-executing task in it is +`type: process`, and every aggregator is `dependsOn`-only. Neither chains with `&&`, so the mirror +runs the same on any task shell. CI runs the same clean-compile commands as the authoritative +backstop. The Python mechanism for a local hook is the `pre-commit` framework, wired from the +canonical `catalog/snippets/pre-commit/` directory, hub-local and not carried into every fleet +repo. That directory's own README names the second file to copy alongside the config. +GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section carries +the obligation itself, what the hook must cover, its audit treatment, and the per-clone enablement +steps. A restricted executor gives each task a cache directory under a writable temporary root. Point `UV_CACHE_DIR`, `RUFF_CACHE_DIR`, `MYPY_CACHE_DIR`, and `COVERAGE_FILE` into that directory before diff --git a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md index 9e675f0a..4dda047f 100644 --- a/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/repo-worktree/SKILL.md @@ -65,13 +65,14 @@ The base clone is a fetch source, not a place to do task work. `fetch` and `work against it for that purpose, and outside "Listing and Cleanup"'s own terminal step below, nothing else does: never `checkout`, `pull`, `reset`, `commit`, or any other command that mutates its own working tree, index, or HEAD while a task is in progress. That distinction is the one a real -incident missed, reusing a primary checkout as the working directory itself rather than only as -the source a worktree is created from. On Claude Code this is also a mechanical stop for most -of that list. `merge --ff-only`/`pull --ff-only` and a `checkout `/`switch ` carrying no -force flag stay exempt even there, matching this skill's own cleanup step below, which needs -exactly those. -Prose remains the only enforcement for a non-Claude-Code agent, and for the shapes the hook itself -exempts. +incident missed, where an agent reused a primary checkout as the working directory itself rather +than only as the source a worktree is created from. On a machine carrying the hub's agent-safety +install, Claude Code also makes this a mechanical stop for most of that list. +`merge --ff-only`/`pull --ff-only` stay exempt even there, and so does a `checkout `/`switch ` +naming exactly one positional that resolves as a ref, with no force flag and no `--` separator, +which is the shape this skill's own cleanup step runs. A `checkout -- .` or a `switch -c ` is +denied, being neither. Prose remains the only enforcement for a non-Claude-Code agent, for a +machine without that install, and for the shapes the hook itself exempts. ## Creating a Worktree @@ -129,18 +130,26 @@ command. A declared sandbox boundary is the reason to request that approval, not reason to skip the registered worktree. Use the standard layout when both locations are writable or the executor approves the scoped -write. When approval is unavailable or denied, create a standalone clone under a writable -temporary root. Name it `/-`, fetch immediately, and create the -task branch from `origin/develop`. A standalone clone keeps its worktree and Git administrative -directory under the same writable root. It therefore supports edits, explicit-path staging, -commits, and branch updates without sharing the base clone's index. +write. When approval for the worktree path is unavailable or denied, create a standalone clone +under a writable temporary root. Name it `/-`, fetch immediately, +and create the task branch from `origin/develop`. On Claude Code that clone needs a grant of its +own before its git steps will run, per the paragraph below, so ask for both together rather than +meeting the second after the clone exists. A standalone clone keeps its worktree and Git +administrative directory under the same writable root. It therefore supports edits, explicit-path +staging, commits, and branch updates without sharing the base clone's index. On Claude Code, a standalone clone is structurally a primary checkout to `gh-write-guard.py`'s own rule 6 test (`--git-dir` equals `--git-common-dir` there too, since it is not a linked worktree of -anything), so the hook denies the very commits/edits this fallback exists to make. Set -`GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT` for the session before using one -- the same escape hatch -`host-setup/agent-safety/README.md`'s requirement 6 already documents -- since this fallback is -exactly the narrow, already-approval-gated case that grant exists for. +anything), so the hook denies the git commands this fallback exists to run. Its rules classify git +and gh commands only, so a plain file edit is never denied there and only the git steps need the +grant, though a compound command is judged whole, so an edit chained to a denied git write does +not run either. That grant is `GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT`, the same escape hatch +`host-setup/agent-safety/README.md` in the hub already documents as its requirement 6, not a +repo-relative link since that path is hub-local and not carried into every fleet repo. The +maintainer sets it before the session starts, and an agent cannot set it for itself: the guard +reads it from the environment the session was launched with, never from an inline `VAR=x` prefix +or an `export` the agent runs, so ask for it rather than trying to set it mid-session. This +fallback is exactly the narrow, already-approval-gated case that grant exists for. A temporary standalone clone is a degraded handoff, not an equivalent location. The base clone does not register it, `git worktree list` does not show it, and an IDE opened on the base clone @@ -168,13 +177,13 @@ git -C ~/repos/ fetch origin git -C ~/repos/ worktree add ~/repos/worktrees/- ``` -When the base clone holds only the remote-tracking ref, the same command creates the local -branch tracking `origin/` through git's ordinary checkout guessing, so a fresh -clone needs no separate branch setup. Git refuses to attach a branch that is already checked -out somewhere else, and that refusal is the mandate working, since the branch sitting checked -out in a shared tree is the hazard the continuation rule exists for. Return that checkout to -its own working branch first when its tree is clean, and stop when it is not, because a dirty -tree there may be another task's uncommitted work. +When the base clone holds only the remote-tracking ref, the same command creates the local branch +tracking `origin/` through git's ordinary checkout guessing, so a fresh clone needs +no separate branch setup. Git refuses to attach a branch that is already checked out somewhere +else, and that refusal is the mandate working, since the branch sitting checked out in a shared +tree is the hazard the continuation rule exists for. Return that checkout to its own working +branch first when its tree is clean, and stop when it is not, because a dirty tree there may be +another task's uncommitted work. A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine diff --git a/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md b/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md index 67918990..2bd17671 100644 --- a/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/resync-a-repo/SKILL.md @@ -36,23 +36,26 @@ anything else. The finding kind names the procedure the repo is owed. ## Reach the hub and measure before changing anything -Fetch a hub checkout of your own immediately before reading it, per RESYNC.md section 1, since a -stale clone answers confidently instead of failing. Never operate against an existing checkout -already present at a known or shared path, the maintainer's own primary checkout included, even -one that looks current -- always fetch into a private worktree of your own, per `repo-worktree`. -On Claude Code this is now also a mechanical stop for most such commands (a `PreToolUse` hook -denies a mutating git operation run directly in a primary checkout), though this prose is still -the only enforcement for a non-Claude-Code agent, and for the narrow shapes the hook itself -exempts, so following it here is not optional even where no hook can catch a lapse. Verify the -host with `python3 scripts/host_gate.py --repo `. Then run the audit end -to end, -RESYNC.md section 2, against the target's `main` branch, never `develop`. A finding is a snapshot, -so quote the run stamp in anything derived from it and re-run before acting on a finding read -earlier in the session. -File any hub defect this work exposes against `ptr727/ProjectTemplate`. -Examples include bugs, conflicting sources, unclear or incomplete instructions, missing capabilities, and Copilot findings about any of them. -Search open and closed issues first, then update the matching issue or file a new one. -Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding only in chat, a review thread, the downstream repo, or agent memory. +Fetch a hub checkout of your own immediately before reading it, per the hub-only `RESYNC.md` +section 1, a file fetched with the hub rather than present in a carrier, since a stale clone +answers confidently instead of failing. Never do the resync's own work in a checkout another task +may be using, the maintainer's own primary checkout included, even one that looks current: for the +hub and for the target repo alike, create a worktree of your own off that base clone and work +there, per `repo-worktree`. On a machine carrying the hub's agent-safety install, Claude Code also +refuses a mutating git command run directly in a primary checkout, though its rules classify git +and gh commands only, so a plain file write is never denied and the re-vendoring this skill exists +to govern goes uncaught. A compound command is judged whole, so a copy chained to a denied git +write does not run either. Prose is the enforcement here, and following it is not optional. Verify +the host with `python3 scripts/host_gate.py --repo `, run from your hub +worktree, since `scripts/` is hub-hosted and no carrier holds it. Then run the audit end to end, +`RESYNC.md` section 2, against the target's `main` branch, never `develop`. A finding is a +snapshot, so quote the run stamp in anything derived from it and re-run before acting on a finding +read earlier in the session. File any hub defect this work exposes against +`ptr727/ProjectTemplate`. Examples include bugs, conflicting sources, unclear or incomplete +instructions, missing capabilities, and Copilot findings about any of them. Search open and closed +issues first, then update the matching issue or file a new one. Preserve the evidence `RESYNC.md` +section 2 requires, and do not leave the finding only in chat, a review thread, the downstream +repo, or agent memory. ## Apply, in this order diff --git a/.claude-plugin/fleet-skills/skills/shell-codestyle/SKILL.md b/.claude-plugin/fleet-skills/skills/shell-codestyle/SKILL.md index eaa4a583..46fbaec0 100644 --- a/.claude-plugin/fleet-skills/skills/shell-codestyle/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/shell-codestyle/SKILL.md @@ -28,34 +28,35 @@ cover general scripting style. Bash, and only where a program cannot be Python: a bootstrap that installs the interpreter cannot be written in it, and a host tool that must run before a development toolchain exists cannot -depend on one. Everything else is Python, with a test under the scripts tree's `tests/` directory. +depend on Python either. Everything else is Python, with a test under its own scripts tree's +`tests/` directory. ## Rules - **`shellcheck` is the linter and `shfmt` the formatter.** The clean-compile is `shellcheck` clean at default severity plus `shfmt -d`, both reporting nothing before a commit. CI enforces - both, per `GOVERNANCE.md` "Running the Linters Locally", and `scripts/docker_lint.py` runs the - same pair headless. Neither is scoped to the `*.sh` glob alone: a tracked, extension-less - script whose shebang names bash or sh (the shape a script meant to run as a bare command takes) - joins the target list too. + both, per `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" + section, and the hub's `scripts/docker_lint.py` runs the same pair headless. Neither is scoped + to the `*.sh` glob alone: a tracked, extension-less script whose shebang names bash or sh (the + shape a script meant to run as a bare command takes) joins the target list too. - **`set -Eeuo pipefail`, before the first command the script runs.** A header comment sits above - it, as `repo-config/configure.sh` and the `host-setup/` scripts do, since what matters is that - nothing executes unguarded rather than which line number it lands on. Without `-e` a failed - command in the middle of a sequence lets the rest run against a state nobody checked, and + it, as the hub's own `repo-config/configure.sh` and `host-setup/` scripts do, since what matters + is that nothing executes unguarded rather than which line number it lands on. Without `-e` a + failed command in the middle of a sequence lets the rest run against a state nobody checked, and without `pipefail` a pipeline reports the exit of its last stage, so a fetch that failed reads as an answer when a parser downstream succeeds on an empty input. `-E` carries an `ERR` trap into functions and command substitutions, so a script that later adds one is not surprised by where it does not fire. - **A reader that stops early needs its producer read first.** Under `pipefail`, a producer - writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as - a failure whenever the match is found early enough. Capture the output, then search it. + writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as a + failure whenever the match is found early enough. Capture the output, then search it. - **Self-locating, never dependent on the caller's directory.** A script resolves its own directory from `BASH_SOURCE` and references its payloads through it, since the working directory at invocation is not a property of the script. - **`shellcheck` clean, and a deliberate exception carries its reason inline.** A `# shellcheck disable=SCxxxx` names why the rule does not apply here, so the next reader can - tell a considered exception from an unread warning. `repo-config/configure.sh` is the worked - example, carrying five `SC2016` disables where a single-quoted `jq` program must stay + tell a considered exception from an unread warning. The hub's own `repo-config/configure.sh` is + the worked example, carrying `SC2016` disables where a single-quoted `jq` program must stay unexpanded, each with its reason on the same line. - **Comments say why, never what.** The code states what it does. A comment restating it goes stale silently, where a comment carrying a reason fails visibly when the reason stops being diff --git a/.claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md b/.claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md index 54d97c14..9eaf908f 100644 --- a/.claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md +++ b/.claude-plugin/fleet-skills/skills/workflow-ci-contract/SKILL.md @@ -44,4 +44,4 @@ The full catalog, each guarantee with the failure mode it prevents, is in `refer ## After Any Workflow Edit -Workflow-only changes are not smoke-built, so run actionlint locally (the Docker invocation in `GOVERNANCE.md` "Running the Linters Locally", which bundles shellcheck for `run:` blocks) before pushing, and remember a workflow change is only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. +Workflow-only changes are not smoke-built, so run actionlint locally before pushing. Run it from the repository being checked, as `python3 /path/to/ProjectTemplate/scripts/docker_lint.py --root "$PWD" --linter actionlint`, using the hub-hosted wrapper documented in `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" section. actionlint includes `shellcheck` for `run:` blocks, so `--linter actionlint` already covers them. A workflow change is still only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. diff --git a/.github/skills/comment-and-doc-style/SKILL.md b/.github/skills/comment-and-doc-style/SKILL.md index af574e92..806ce4ed 100644 --- a/.github/skills/comment-and-doc-style/SKILL.md +++ b/.github/skills/comment-and-doc-style/SKILL.md @@ -65,15 +65,15 @@ boundary before repository mounts begin. Each Docker command has a timeout and v Lint containers disable networking and mount the checkout read-only. Persist approval only when the executor constrains that whole shape. Never allow an unconstrained `docker run` prefix. PSScriptAnalyzer downloads its pinned module in a separate container that has network access and -no repository mount. `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)" -owns the exact invocation and full authorization model. +no repository mount. `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working +Invocations)" section owns the exact invocation and full authorization model. Agent-specific authorization stays in provider-labeled bullets so one agent's configuration does not read as a shared requirement: -- **Codex:** rules cannot safely cover changing worktree paths and digests. Smart Approvals can - prompt per task. No-prompt operation is supported only inside an external sandbox because it - removes command-wide protection. +- **Codex:** execution rules match exact argument prefixes, so they cannot safely cover changing + worktree paths and digests. Smart Approvals can prompt per task. No-prompt operation is + supported only inside an external sandbox because it removes command-wide protection. ## Markdown formatting @@ -230,7 +230,7 @@ hub-hosted tool the reader runs, are in `references/carried-doc-references.md`. - **Rules**: no vague titles (`update stuff`, `wip`). Dependabot's default `Bump X from Y to Z` titles are fine as-is. No `Co-Authored-By:` lines unless the developer explicitly asks. No release-bump magnitude in the title ("minor", "patch", "release v0.2.0"), Nerdbank.GitVersioning - computes the next version from `version.json` and git history, a dependency version in a + computes the next version from `version.json` and git history. A dependency version in a dependency-bump title is fine and expected. US English spelling, and title case with lowercase short bind words (a, an, the, and, but, or, of, in, on, at, to, by, for, from), a hyphenated compound capitalizes both parts unless the second is a short preposition (*Built-in*, @@ -241,7 +241,7 @@ Add Structured Logging Extensions to Library Pin softprops/action-gh-release to Commit SHA Drop net8.0 Multi-Targeting from Console Project Bump xunit.v3 from 3.2.2 to 3.3.0 -Clarify devcontainer Setup Steps in README +Clarify Devcontainer Setup Steps in README ``` ## Quantitative claims diff --git a/.github/skills/dotnet-codestyle/SKILL.md b/.github/skills/dotnet-codestyle/SKILL.md index f3565a5c..2cc81eef 100644 --- a/.github/skills/dotnet-codestyle/SKILL.md +++ b/.github/skills/dotnet-codestyle/SKILL.md @@ -49,9 +49,11 @@ All builds must complete without warnings, enforced three ways: surfaced as a warning fails the build and must be fixed or deliberately suppressed at the narrowest scope that fits (see Analyzer suppressions below), never left to accumulate. - **CI lint backstop.** CI runs the clean-compile checks on every PR as the authoritative gate. - A working local hook is strongly suggested, not optional: wire Husky.Net from the canonical - `catalog/snippets/husky/` config. See GOVERNANCE.md "Running the Linters Locally" for what the - hook must cover and what its absence means. + The .NET mechanism for a local hook is Husky.Net, wired from the canonical + `catalog/snippets/husky/` config in the hub, hub-local and not carried into every fleet repo. + GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section + carries the obligation itself, what the hook must cover, its audit treatment, and the + per-clone enablement steps. **A new port is not a license to silence diagnostics.** Brownfield or just-ported status never justifies relaxing analyzer severities or muting newly surfaced warnings. Fix them. (The only @@ -95,8 +97,9 @@ updates, dependency upgrades, benchmarks) on top: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed`. - **`dotnet-outdated-tool`** checks for dependency updates, and Nerdbank.GitVersioning owns version management. -- CI is the authoritative lint backstop. A local pre-commit hook is strongly suggested: wire - Husky.Net from `catalog/snippets/husky/` for local enforcement, including the shared doc gates. +- CI is the authoritative lint backstop. The .NET mechanism for a local pre-commit hook is + Husky.Net, wired from `catalog/snippets/husky/` in the hub, hub-local and not carried into every + fleet repo, and covering the shared doc gates alongside the language checks. - **Required VS Code extensions**: CSharpier, markdownlint, CSpell. Use the workspace settings without overrides. diff --git a/.github/skills/python-codestyle/SKILL.md b/.github/skills/python-codestyle/SKILL.md index f7a7451e..210a67b7 100644 --- a/.github/skills/python-codestyle/SKILL.md +++ b/.github/skills/python-codestyle/SKILL.md @@ -92,13 +92,16 @@ repo's type checker: `uv run pyright`, or `uv run mypy src` where mypy is the CI where the repo runs both (see Type checking above). Run it, plus `uv run pytest`, before committing. A **lint-only** profile's clean-compile substitutes its `uvx` and `unittest` equivalents, per Two Profiles above, and has no such command to run before committing beyond -those. These are documented commands, and an optional VS Code tasks mirror (all `type: process`, -no `&&` shell chaining, so it runs the same on any task shell) is in the hub -`vscode-tasks-python.json` snippet. CI runs the same clean-compile commands as the authoritative -backstop. A working local hook is strongly suggested, not opt-in: wire the Python `pre-commit` -framework from the canonical `catalog/snippets/pre-commit/.pre-commit-config.yaml`. See -GOVERNANCE.md "Running the Linters Locally" for what the hook must cover and what its absence -means. +those. These are documented commands, and the hub's `vscode-tasks-python.json` snippet carries the +VS Code tasks mirror that the fleet baseline expects. Every command-executing task in it is +`type: process`, and every aggregator is `dependsOn`-only. Neither chains with `&&`, so the mirror +runs the same on any task shell. CI runs the same clean-compile commands as the authoritative +backstop. The Python mechanism for a local hook is the `pre-commit` framework, wired from the +canonical `catalog/snippets/pre-commit/` directory, hub-local and not carried into every fleet +repo. That directory's own README names the second file to copy alongside the config. +GOVERNANCE.md's hub-only "Running the Linters Locally (Known-Working Invocations)" section carries +the obligation itself, what the hook must cover, its audit treatment, and the per-clone enablement +steps. A restricted executor gives each task a cache directory under a writable temporary root. Point `UV_CACHE_DIR`, `RUFF_CACHE_DIR`, `MYPY_CACHE_DIR`, and `COVERAGE_FILE` into that directory before diff --git a/.github/skills/repo-worktree/SKILL.md b/.github/skills/repo-worktree/SKILL.md index 9e675f0a..4dda047f 100644 --- a/.github/skills/repo-worktree/SKILL.md +++ b/.github/skills/repo-worktree/SKILL.md @@ -65,13 +65,14 @@ The base clone is a fetch source, not a place to do task work. `fetch` and `work against it for that purpose, and outside "Listing and Cleanup"'s own terminal step below, nothing else does: never `checkout`, `pull`, `reset`, `commit`, or any other command that mutates its own working tree, index, or HEAD while a task is in progress. That distinction is the one a real -incident missed, reusing a primary checkout as the working directory itself rather than only as -the source a worktree is created from. On Claude Code this is also a mechanical stop for most -of that list. `merge --ff-only`/`pull --ff-only` and a `checkout `/`switch ` carrying no -force flag stay exempt even there, matching this skill's own cleanup step below, which needs -exactly those. -Prose remains the only enforcement for a non-Claude-Code agent, and for the shapes the hook itself -exempts. +incident missed, where an agent reused a primary checkout as the working directory itself rather +than only as the source a worktree is created from. On a machine carrying the hub's agent-safety +install, Claude Code also makes this a mechanical stop for most of that list. +`merge --ff-only`/`pull --ff-only` stay exempt even there, and so does a `checkout `/`switch ` +naming exactly one positional that resolves as a ref, with no force flag and no `--` separator, +which is the shape this skill's own cleanup step runs. A `checkout -- .` or a `switch -c ` is +denied, being neither. Prose remains the only enforcement for a non-Claude-Code agent, for a +machine without that install, and for the shapes the hook itself exempts. ## Creating a Worktree @@ -129,18 +130,26 @@ command. A declared sandbox boundary is the reason to request that approval, not reason to skip the registered worktree. Use the standard layout when both locations are writable or the executor approves the scoped -write. When approval is unavailable or denied, create a standalone clone under a writable -temporary root. Name it `/-`, fetch immediately, and create the -task branch from `origin/develop`. A standalone clone keeps its worktree and Git administrative -directory under the same writable root. It therefore supports edits, explicit-path staging, -commits, and branch updates without sharing the base clone's index. +write. When approval for the worktree path is unavailable or denied, create a standalone clone +under a writable temporary root. Name it `/-`, fetch immediately, +and create the task branch from `origin/develop`. On Claude Code that clone needs a grant of its +own before its git steps will run, per the paragraph below, so ask for both together rather than +meeting the second after the clone exists. A standalone clone keeps its worktree and Git +administrative directory under the same writable root. It therefore supports edits, explicit-path +staging, commits, and branch updates without sharing the base clone's index. On Claude Code, a standalone clone is structurally a primary checkout to `gh-write-guard.py`'s own rule 6 test (`--git-dir` equals `--git-common-dir` there too, since it is not a linked worktree of -anything), so the hook denies the very commits/edits this fallback exists to make. Set -`GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT` for the session before using one -- the same escape hatch -`host-setup/agent-safety/README.md`'s requirement 6 already documents -- since this fallback is -exactly the narrow, already-approval-gated case that grant exists for. +anything), so the hook denies the git commands this fallback exists to run. Its rules classify git +and gh commands only, so a plain file edit is never denied there and only the git steps need the +grant, though a compound command is judged whole, so an edit chained to a denied git write does +not run either. That grant is `GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT`, the same escape hatch +`host-setup/agent-safety/README.md` in the hub already documents as its requirement 6, not a +repo-relative link since that path is hub-local and not carried into every fleet repo. The +maintainer sets it before the session starts, and an agent cannot set it for itself: the guard +reads it from the environment the session was launched with, never from an inline `VAR=x` prefix +or an `export` the agent runs, so ask for it rather than trying to set it mid-session. This +fallback is exactly the narrow, already-approval-gated case that grant exists for. A temporary standalone clone is a degraded handoff, not an equivalent location. The base clone does not register it, `git worktree list` does not show it, and an IDE opened on the base clone @@ -168,13 +177,13 @@ git -C ~/repos/ fetch origin git -C ~/repos/ worktree add ~/repos/worktrees/- ``` -When the base clone holds only the remote-tracking ref, the same command creates the local -branch tracking `origin/` through git's ordinary checkout guessing, so a fresh -clone needs no separate branch setup. Git refuses to attach a branch that is already checked -out somewhere else, and that refusal is the mandate working, since the branch sitting checked -out in a shared tree is the hazard the continuation rule exists for. Return that checkout to -its own working branch first when its tree is clean, and stop when it is not, because a dirty -tree there may be another task's uncommitted work. +When the base clone holds only the remote-tracking ref, the same command creates the local branch +tracking `origin/` through git's ordinary checkout guessing, so a fresh clone needs +no separate branch setup. Git refuses to attach a branch that is already checked out somewhere +else, and that refusal is the mandate working, since the branch sitting checked out in a shared +tree is the hazard the continuation rule exists for. Return that checkout to its own working +branch first when its tree is clean, and stop when it is not, because a dirty tree there may be +another task's uncommitted work. A machine not yet migrated to this layout still isolates exactly the same way, since the mandate is the isolation rather than the path: create the worktree beside whatever layout the machine diff --git a/.github/skills/resync-a-repo/SKILL.md b/.github/skills/resync-a-repo/SKILL.md index 67918990..2bd17671 100644 --- a/.github/skills/resync-a-repo/SKILL.md +++ b/.github/skills/resync-a-repo/SKILL.md @@ -36,23 +36,26 @@ anything else. The finding kind names the procedure the repo is owed. ## Reach the hub and measure before changing anything -Fetch a hub checkout of your own immediately before reading it, per RESYNC.md section 1, since a -stale clone answers confidently instead of failing. Never operate against an existing checkout -already present at a known or shared path, the maintainer's own primary checkout included, even -one that looks current -- always fetch into a private worktree of your own, per `repo-worktree`. -On Claude Code this is now also a mechanical stop for most such commands (a `PreToolUse` hook -denies a mutating git operation run directly in a primary checkout), though this prose is still -the only enforcement for a non-Claude-Code agent, and for the narrow shapes the hook itself -exempts, so following it here is not optional even where no hook can catch a lapse. Verify the -host with `python3 scripts/host_gate.py --repo `. Then run the audit end -to end, -RESYNC.md section 2, against the target's `main` branch, never `develop`. A finding is a snapshot, -so quote the run stamp in anything derived from it and re-run before acting on a finding read -earlier in the session. -File any hub defect this work exposes against `ptr727/ProjectTemplate`. -Examples include bugs, conflicting sources, unclear or incomplete instructions, missing capabilities, and Copilot findings about any of them. -Search open and closed issues first, then update the matching issue or file a new one. -Preserve the evidence RESYNC.md section 2 requires, and do not leave the finding only in chat, a review thread, the downstream repo, or agent memory. +Fetch a hub checkout of your own immediately before reading it, per the hub-only `RESYNC.md` +section 1, a file fetched with the hub rather than present in a carrier, since a stale clone +answers confidently instead of failing. Never do the resync's own work in a checkout another task +may be using, the maintainer's own primary checkout included, even one that looks current: for the +hub and for the target repo alike, create a worktree of your own off that base clone and work +there, per `repo-worktree`. On a machine carrying the hub's agent-safety install, Claude Code also +refuses a mutating git command run directly in a primary checkout, though its rules classify git +and gh commands only, so a plain file write is never denied and the re-vendoring this skill exists +to govern goes uncaught. A compound command is judged whole, so a copy chained to a denied git +write does not run either. Prose is the enforcement here, and following it is not optional. Verify +the host with `python3 scripts/host_gate.py --repo `, run from your hub +worktree, since `scripts/` is hub-hosted and no carrier holds it. Then run the audit end to end, +`RESYNC.md` section 2, against the target's `main` branch, never `develop`. A finding is a +snapshot, so quote the run stamp in anything derived from it and re-run before acting on a finding +read earlier in the session. File any hub defect this work exposes against +`ptr727/ProjectTemplate`. Examples include bugs, conflicting sources, unclear or incomplete +instructions, missing capabilities, and Copilot findings about any of them. Search open and closed +issues first, then update the matching issue or file a new one. Preserve the evidence `RESYNC.md` +section 2 requires, and do not leave the finding only in chat, a review thread, the downstream +repo, or agent memory. ## Apply, in this order diff --git a/.github/skills/shell-codestyle/SKILL.md b/.github/skills/shell-codestyle/SKILL.md index eaa4a583..46fbaec0 100644 --- a/.github/skills/shell-codestyle/SKILL.md +++ b/.github/skills/shell-codestyle/SKILL.md @@ -28,34 +28,35 @@ cover general scripting style. Bash, and only where a program cannot be Python: a bootstrap that installs the interpreter cannot be written in it, and a host tool that must run before a development toolchain exists cannot -depend on one. Everything else is Python, with a test under the scripts tree's `tests/` directory. +depend on Python either. Everything else is Python, with a test under its own scripts tree's +`tests/` directory. ## Rules - **`shellcheck` is the linter and `shfmt` the formatter.** The clean-compile is `shellcheck` clean at default severity plus `shfmt -d`, both reporting nothing before a commit. CI enforces - both, per `GOVERNANCE.md` "Running the Linters Locally", and `scripts/docker_lint.py` runs the - same pair headless. Neither is scoped to the `*.sh` glob alone: a tracked, extension-less - script whose shebang names bash or sh (the shape a script meant to run as a bare command takes) - joins the target list too. + both, per `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" + section, and the hub's `scripts/docker_lint.py` runs the same pair headless. Neither is scoped + to the `*.sh` glob alone: a tracked, extension-less script whose shebang names bash or sh (the + shape a script meant to run as a bare command takes) joins the target list too. - **`set -Eeuo pipefail`, before the first command the script runs.** A header comment sits above - it, as `repo-config/configure.sh` and the `host-setup/` scripts do, since what matters is that - nothing executes unguarded rather than which line number it lands on. Without `-e` a failed - command in the middle of a sequence lets the rest run against a state nobody checked, and + it, as the hub's own `repo-config/configure.sh` and `host-setup/` scripts do, since what matters + is that nothing executes unguarded rather than which line number it lands on. Without `-e` a + failed command in the middle of a sequence lets the rest run against a state nobody checked, and without `pipefail` a pipeline reports the exit of its last stage, so a fetch that failed reads as an answer when a parser downstream succeeds on an empty input. `-E` carries an `ERR` trap into functions and command substitutions, so a script that later adds one is not surprised by where it does not fire. - **A reader that stops early needs its producer read first.** Under `pipefail`, a producer - writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as - a failure whenever the match is found early enough. Capture the output, then search it. + writing to a closed pipe exits non-zero, so `curl ... | grep -q` reports a successful fetch as a + failure whenever the match is found early enough. Capture the output, then search it. - **Self-locating, never dependent on the caller's directory.** A script resolves its own directory from `BASH_SOURCE` and references its payloads through it, since the working directory at invocation is not a property of the script. - **`shellcheck` clean, and a deliberate exception carries its reason inline.** A `# shellcheck disable=SCxxxx` names why the rule does not apply here, so the next reader can - tell a considered exception from an unread warning. `repo-config/configure.sh` is the worked - example, carrying five `SC2016` disables where a single-quoted `jq` program must stay + tell a considered exception from an unread warning. The hub's own `repo-config/configure.sh` is + the worked example, carrying `SC2016` disables where a single-quoted `jq` program must stay unexpanded, each with its reason on the same line. - **Comments say why, never what.** The code states what it does. A comment restating it goes stale silently, where a comment carrying a reason fails visibly when the reason stops being diff --git a/.github/skills/workflow-ci-contract/SKILL.md b/.github/skills/workflow-ci-contract/SKILL.md index 54d97c14..9eaf908f 100644 --- a/.github/skills/workflow-ci-contract/SKILL.md +++ b/.github/skills/workflow-ci-contract/SKILL.md @@ -44,4 +44,4 @@ The full catalog, each guarantee with the failure mode it prevents, is in `refer ## After Any Workflow Edit -Workflow-only changes are not smoke-built, so run actionlint locally (the Docker invocation in `GOVERNANCE.md` "Running the Linters Locally", which bundles shellcheck for `run:` blocks) before pushing, and remember a workflow change is only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. +Workflow-only changes are not smoke-built, so run actionlint locally before pushing. Run it from the repository being checked, as `python3 /path/to/ProjectTemplate/scripts/docker_lint.py --root "$PWD" --linter actionlint`, using the hub-hosted wrapper documented in `GOVERNANCE.md`'s hub-only "Running the Linters Locally (Known-Working Invocations)" section. actionlint includes `shellcheck` for `run:` blocks, so `--linter actionlint` already covers them. A workflow change is still only fully exercised by CI, since `secrets: inherit`, `permissions:`, and `needs:` wiring resolve only in a real run. diff --git a/AGENTS.md b/AGENTS.md index 365845ed..2359cd0f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,7 +73,7 @@ If a rule you were given does not cover what you find, stop and report it. Do no ## Where the Rules Live -Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). Read the section the task needs. +Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md) unless its row says otherwise. Read the section the task needs. | Working on | Section | | --- | --- | @@ -87,14 +87,14 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R | A live config repo rather than a code repo | `Operational Repositories`, packaged as the `operational-vs-release-workflow` Skill | | Onboarding a repo or running a conformance sweep | `Repository Onboarding and Conformance` (hub only, not carried). Standing up a new repo from a hub checkout is packaged as the `standup-a-repo` Skill, resyncing one already stood up the same way is `resync-a-repo`, and measuring a named repo against the fleet ground truth per `AUDIT.md` is `audit-a-repo`, all hub-context only | | Running a fleet gate, the review digest, or the config script | `Hub-Hosted Tooling` | -| Running a lint or format check locally, or a lint tool missing from `command -v` | `Running the Linters Locally (Known-Working Invocations)` | +| Running a lint or format check locally, or a lint tool missing from `command -v` | `Running the Linters Locally (Known-Working Invocations)` (hub only, not carried) | | Running a test locally, or a test runner missing or failing to spawn | `Verification Discipline` | | Writing a commit message or pull request title | `Pull Request Title and Commit Message Conventions`, packaged as the `comment-and-doc-style` Skill | | Any prose, comment, doc, or line-ending change | `Documentation Style Conventions`, packaged as the `comment-and-doc-style` Skill | | Proving work actually happened | `Verification Discipline`, surfaced at its decision moment by the `agent-conduct` Skill, and the section keeps the full rules | -| Editing rule text, a Skill, or any other content other repos carry | `Verification Discipline`'s carried-content rule, whose pass the `local-strict-review` Skill runs and whose record `scripts/canonical_review.py` keeps | +| Editing rule text, a Skill, or any other content other repos carry | `Verification Discipline`'s carried-content rule, whose pass the `local-strict-review` Skill runs and whose record the hub-hosted `scripts/canonical_review.py` keeps | | Opening a pull request, or requesting, monitoring, answering, or closing a review | `PR Review Etiquette`, packaged as the `pr-review-conduct` Skill | -| Reviewing a pull request, patch, or change set | `code-review`, which routes to the applicable general, language, documentation, and workflow skills | +| Reviewing a pull request, patch, or change set | No section of its own: the `code-review` Skill, which routes to the applicable general, language, documentation, and workflow skills | | Reporting progress or asking the user something | `Communicating with the User`, surfaced at its decision moment by the `agent-conduct` Skill, and the section keeps the full rules | | Editing a workflow YAML file | `Workflow YAML Conventions`, surfaced with the full `WORKFLOW.md` contract by the `workflow-ci-contract` Skill, and this section and `WORKFLOW.md` keep the full rules | | Choosing an OS, runtime, or toolchain target | `Supported Development Platforms` | @@ -103,7 +103,7 @@ Every rule below is a level-two section of [`GOVERNANCE.md`](./GOVERNANCE.md). R | The About panel, description, or repo toggles | `Repository Details` | | Where a file belongs in the tree | `Repository Layout` | -A row above with no Skill annotation is doc-only by decision, not by omission. A Skill surfaces rules at a trigger moment, and each unannotated section either binds always or carries no moment narrower than reading it: `Foundational Principles` is rationale read once rather than a procedure, `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that must bind even when no Skill fires (the `gh-write-guard` hook and the host-wide instruction blocks the agent-safety installer maintains are their enforcement layer, and the one moment in the boundaries section narrow enough to surface, isolating into a worktree at task start, gets the `repo-worktree` Skill on top of that law rather than instead of it), and `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it. +A row above naming no Skill, or naming one only for part of its section, is doc-only by decision rather than by omission, and the reason differs by row. `Foundational Principles` is rationale read once rather than a procedure. `Repository Boundaries and Write Safety` and `Representative Data in Agent-Authored Text` are always-on law that binds whether or not a Skill fires, which is why the boundaries row names `repo-worktree` only for the one moment in it narrow enough to surface, isolating into a worktree at task start, on top of that law rather than instead of it. The `gh-write-guard` hook and the host-wide instruction blocks maintained by the hub's own agent-safety installer, hub-local at `host-setup/agent-safety/`, are the boundaries section's mechanical layer, while the data section has none, since no pattern decides it. `Running the Linters Locally (Known-Working Invocations)` is hub-only, so a carrier reaches it in a hub checkout rather than surfacing it. `Verification Discipline` carries its Skills on its other two rows. And `Hub-Hosted Tooling`, `Supported Development Platforms`, `Devcontainer`, `Editor and Tasks`, `Repository Details`, and `Repository Layout` are short reference sections a task reads at the moment it touches their subject, each already routed to by the procedures and Skills that need it. Some of the rules above are also packaged as Claude Code / opencode / Codex Skills, hand-authored at `.agents/skills/` in the hub (not a repo-relative link here, since that path is hub-local and not carried into every fleet repo), so they surface automatically instead of needing to be re-read every session. `scripts/` is hub-hosted and reached rather than carried, per "Hub-Hosted Tooling", so run the installer from a hub checkout: `python3 scripts/skills_install.py` (or the `.sh`/`.ps1` wrapper) once per machine, from `github.com/ptr727/ProjectTemplate`, installs them for every repo touched from that machine. `python3 scripts/skills_install.py --report`, also from a hub checkout, says whether this machine is current. A rule that keeps needing to be restated is a sign the install is missing or stale, not that the rule does not exist. Keeping a repo's own carried `.github/copilot-instructions.md` in sync with the hub, without losing that repo's own "Disproved Claims" ledger entries in the process, is `copilot-instructions-keeper`, a skill about maintaining that file rather than a rule extracted from it, since the file itself is read directly by the Copilot bot and stays fully intact everywhere it is carried. Checking, from inside this repo's own session with no operator watching, whether this repo and this machine are actually current against the hub is `fleet-conformance-check`, new content rather than a rule extracted from a section, the counterpart to `resync-a-repo` that needs no standing hub checkout or named target beyond the repo the session is already in, even though its own check fetches a hub checkout to reach `scripts/skills_install.py`. Opening a pull request against a repository outside this fleet, one the maintainer does not control, follows a different workflow entirely, new content rather than a rule extracted from a section, packaged as `upstream-contribution-workflow` and independent of the target repo's own type or workflow model. Isolating a task into its own worktree before its first file edit, with the base-branch choice, the layout convention, and the cleanup mechanics, is `repo-worktree`, the task-start surface of the `Repository Boundaries and Write Safety` law, which keeps the rule. Creating, changing, or retiring one of these skills is itself packaged as `skill-lifecycle`, hub-context only, since `.agents/skills/` exists only in the hub and the generated plugin tree is never hand-edited. @@ -111,4 +111,4 @@ Adding or changing a managed host tool is packaged as `add-host-tool`. It keeps Driving a pull request through its review loop, from a feature branch into `develop` and, when asked, on to a mergeable `develop -> main` promotion PR, disposing of every reviewer finding along the way per `pr-review-conduct`, is packaged as `drive-pr`, new content rather than a rule extracted from a section. Merging a ready promotion PR and dispatching the release it unblocks, refreshing this machine's installed Skills first when the repo is this hub, is `merge-and-release`, its own new-content package, invoked separately from `drive-pr` so the promotion merge and the release dispatch each keep their own explicit go-ahead. -Running one read-only, adversarial review pass against a branch's current diff against its target branch, full file context included, on the strongest model tier the session can reach, before a unit of PR-bound work is pushed toward a pull request or claimed done, is packaged as `local-strict-review`, new content rather than a rule extracted from a section. `drive-pr`, `pr-review-conduct`, and `agent-conduct` each reference it at the moment they already govern, rather than restating what it does. The rule itself lives in [`GOVERNANCE.md`](./GOVERNANCE.md) "Verification Discipline", `scripts/local_review.py` is the engine that records a pass so a capture point can check one, and a repository carrying a `.husky/pre-push` hook enforces it at the push itself, the skill staying the primary and agent-agnostic layer with the hook a bypassable backstop under it. That skill carries a second pass under the same rule, over canonical content this repository authors and others carry, read one whole unit at a time rather than as a diff, because a diff-scoped read leaves the first real review of a rule to whichever repository carries it next, which is the one repository that cannot act on what it finds. `scripts/canonical_review.py` is that pass's engine, and the backlog it has yet to reach is `reports/canonical-review.md` in the hub, not a repo-relative link here since that path is hub-local like the Skills tree above. +Running one read-only, adversarial review pass against a branch's current diff against its target branch, full file context included, on the strongest model tier the session can reach, before a unit of PR-bound work is pushed toward a pull request or claimed done, is packaged as `local-strict-review`, new content rather than a rule extracted from a section. `drive-pr`, `pr-review-conduct`, and `agent-conduct` each reference it at the moment they already govern, rather than restating what it does. The rule itself lives in [`GOVERNANCE.md`](./GOVERNANCE.md) "Verification Discipline", the hub-hosted `scripts/local_review.py` is the engine that records a pass so a capture point can check one, and a repository carrying a `.husky/pre-push` hook enforces it at the push itself, the skill staying the primary and agent-agnostic layer with the hook a bypassable backstop under it. That skill carries a second pass under the same rule, over canonical content this repository authors and others carry, read one whole unit at a time rather than as a diff, because a diff-scoped read leaves the first real review of a rule to whichever repository carries it next, which is the one repository that cannot act on what it finds. `scripts/canonical_review.py` is that pass's engine, and the backlog it has yet to reach is `reports/canonical-review.md` in the hub, not a repo-relative link here since that path is hub-local like the Skills tree above. diff --git a/CODESTYLE.md b/CODESTYLE.md index c0858897..b9b73cd8 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -16,9 +16,9 @@ Use each tool's official casing in task labels, docs, and prose, per the `commen Each language defines a **clean-compile** verification: the combination of build, formatter, linter, and code-analysis tools that must report clean before a commit. It is exposed as one or more **named** VS Code tasks (or, where a language ships no tasks, documented commands), and those definitions are the same across the fleet. The concrete names live in each language section below. -- **Run it after every code change, and it is not the whole gate.** The relevant language's clean-compile must pass before you commit. CI runs those same language checks as a backstop **plus everything else its validation workflow runs**, and all of it reports into the one required status, so a green clean-compile does not predict a green CI. That remainder is at least the doc-lint set (markdownlint, cspell, actionlint, `editorconfig-checker`) and whatever spec, config, and script gates the repo carries, so read the workflow for the full list rather than assuming this sentence enumerates it. What has to pass before a push is the repo's **whole** lint gate, per [GOVERNANCE.md "Verification Discipline"][governance-verification-discipline]. Each linter's known-working invocation is in [GOVERNANCE.md "Running the Linters Locally"][governance-running-the-linters-locally]. +- **Run it after every code change, and it is not the whole gate.** The relevant language's clean-compile must pass before you commit. CI runs those same language checks as a backstop **plus everything else its validation workflow runs**, and all of it reports into the one required status, so a green clean-compile does not predict a green CI. That remainder is at least the doc-lint set (markdownlint, cspell, actionlint, `editorconfig-checker`) and whatever spec, config, and script gates the repo carries, so read the workflow for the full list rather than assuming this sentence enumerates it. What has to pass before a push is the repo's **whole** lint gate, per [GOVERNANCE.md "Verification Discipline"][governance-verification-discipline]. Each linter's known-working invocation is in `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)", a hub-only section read in a hub checkout rather than carried into every fleet repo. - **The named task definition is the canonical spec** - its exact command sequence, arguments, and strictness. You may run it through the VS Code task **or** by invoking the equivalent native commands directly, and either is fine **only if the sequence, arguments, and strictness match exactly**. No shortcuts and no more-lenient options (for example, never drop `--verify-no-changes` or loosen a `--severity`). -- **A working local commit/pre-commit gate is strongly suggested, not the repo's free choice to skip.** No single hook runner fits every language (a `dotnet`-tool runner like Husky.Net suits .NET but not Python), so the *mechanism* stays the repo's choice: Husky.Net for .NET, `pre-commit` for Python, canonical configs for both in `catalog/snippets/`. What that gate must cover, and what its absence means for the audit, is [GOVERNANCE.md "Running the Linters Locally"][governance-running-the-linters-locally], not restated here. Keeping a working gate is not drift. +- **A working local commit/pre-commit gate is strongly suggested, not the repo's free choice to skip.** No single hook runner fits every language (a `dotnet`-tool runner like Husky.Net suits .NET but not Python), so the *mechanism* stays the repo's choice: Husky.Net for .NET, `pre-commit` for Python, canonical configs for both in `catalog/snippets/` in the hub, not a repo-relative path since it is hub-local and not carried into every fleet repo. What that gate must cover, and what its absence means for the audit, is `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)", the same hub-only section, not restated here. Keeping a working gate is not drift. ### Analyzer Diagnostics and Suppressions @@ -51,12 +51,11 @@ This is packaged as the `python-codestyle` Skill at `.agents/skills/python-codes ## Shell -Bash, and only where a program cannot be Python: a bootstrap that installs the interpreter cannot be written in it, and a host tool that must run before a development toolchain exists cannot depend on one. Everything else is Python, with a test under the scripts tree's `tests/` directory. The mandatory `set -Eeuo pipefail` header, the pipefail-versus-early-reader pitfall, self-locating scripts, the `shellcheck`-plus-`shfmt` clean-compile, and the why-not-what comment rule are packaged as the `shell-codestyle` Skill at `.agents/skills/shell-codestyle/SKILL.md` in the hub, not a repo-relative link since that path is hub-local and not carried into every fleet repo. Read the skill for the full rules. Run the clean-compile check itself per [GOVERNANCE.md "Running the Linters Locally"][governance-running-the-linters-locally], not by probing `command -v shellcheck`. +Bash, and only where a program cannot be Python: a bootstrap that installs the interpreter cannot be written in it, and a host tool that must run before a development toolchain exists cannot depend on Python either. Everything else is Python, with a test under its own scripts tree's `tests/` directory. The mandatory `set -Eeuo pipefail` header, the pipefail-versus-early-reader pitfall, self-locating scripts, the `shellcheck`-plus-`shfmt` clean-compile, and the why-not-what comment rule are packaged as the `shell-codestyle` Skill at `.agents/skills/shell-codestyle/SKILL.md` in the hub, not a repo-relative link since that path is hub-local and not carried into every fleet repo. Read the skill for the full rules. Run the clean-compile check itself per `GOVERNANCE.md` "Running the Linters Locally (Known-Working Invocations)", a hub-only section read in a hub checkout rather than carried into every fleet repo, not by probing `command -v shellcheck`. [governance]: ./GOVERNANCE.md -[governance-running-the-linters-locally]: ./GOVERNANCE.md#running-the-linters-locally-known-working-invocations [governance-verification-discipline]: ./GOVERNANCE.md#verification-discipline [readme]: ./README.md [root]: ./.editorconfig diff --git a/GOVERNANCE.md b/GOVERNANCE.md index c2d673c9..e6ce1b3a 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -17,7 +17,7 @@ The specific rules in this file implement a few governing principles. Read these - **Durable knowledge lives in the committed docs, not in agent memory.** Anything a future agent must honor (a rule, a contract, a hard-won gotcha, a pattern worth repeating or one to avoid) belongs in a committed governance file (`GOVERNANCE.md` for a cross-cutting rule, `AGENTS.md`, `CODESTYLE.md`, `WORKFLOW.md`, or a committed backlog the repository already keeps). Agent memory does not survive a new session, a new machine, or a new environment, so it holds only environment-specific nuance and in-flight session state, never anything whose loss on reset would matter. A durable lesson left only in memory is lost to the next agent. - **Keep the governance current as you work.** When work surfaces something durable (a rule worth enforcing, a recurring gotcha, a positive pattern to repeat, a negative one to design out), record it in the governance docs as part of that change, rather than leaving it in a local note or routing around it with a one-off workaround. Where the governing doc is carried from a template this repo cannot edit directly, propose the change upstream instead of only fixing it locally. Governance is not static: it improves by agents folding good patterns in and designing bad ones out. -- **A durable rule earns a mechanical hook only where a hook can actually decide it, otherwise it stays prose.** Three conditions together, not any one alone. The failure recurs even after the governing prose was demonstrably read and understood, so it is not a discovery or loading problem a structural fix (getting the rule into context at all) would already solve. The triggering shape is decidable from the tool call's own text, arguments, and working directory alone, with no semantic or contextual judgment required. And the failure is destructive or hard to reverse rather than a quality miss. A worktree-isolation lapse met all three (the ptr727/ProjectTemplate#1073 incident happened under prose the agent had already read, "is this command's target a primary checkout" is a plain directory comparison, and the harm is another task's swept or reverted work), so it was promoted to a `gh-write-guard` hook rule. A skill's own trigger going unread by the session at all, by contrast, is a loading problem, fixed by getting the rule into context (the `CLAUDE.md` importing `AGENTS.md`), not by a hook. And "was this review finding actually evidence-backed" fails the second condition outright: a hook sees only the command text, never the judgment call itself, so it can only ever nag, not decide, and that class of rule stays prose and a chained Skill trigger. Those three conditions gate promotion to a **host** hook, the involuntary layer that fires in every session under the maintainer's own credentials and cannot be opted out of, which is why the bar there is destructive harm. A **committed** hook in the repository's own tree is a third layer between prose and that one, and it is earned on weaker grounds: it is opt-in per clone, visible in the tree, bypassable by design, and it therefore fits a rule whose harm is a quality miss rather than a destruction. The second condition still binds it, since a hook that cannot decide its own trigger is a hook that nags, so what earns the layer is finding the decidable half of a rule whose other half is judgment. The local-review rule under "Verification Discipline" is the worked example: whether a review's findings were rightly disposed of is judgment no hook can decide and stays prose, while whether a review pass ran over exactly the content being pushed is a receipt comparison, which the hub's own `.husky/pre-push` decides. +- **A durable rule earns a mechanical hook only where a hook can actually decide it, otherwise it stays prose.** Three conditions together, not any one alone. The failure recurs even after the governing prose was demonstrably read and understood, so it is not a discovery or loading problem a structural fix (getting the rule into context at all) would already solve. The triggering shape is decidable from the tool call's own text, arguments, and working directory alone, with no semantic or contextual judgment required. And the failure is destructive or hard to reverse rather than a quality miss. A worktree-isolation lapse met all three (it recurred under prose the agent had already read, "is this command's target a primary checkout" is a plain directory comparison, and the harm is another task's swept or reverted work), so it was promoted to a `gh-write-guard` hook rule. A skill's own trigger going unread by the session at all, by contrast, is a loading problem, fixed by getting the rule into context (the `CLAUDE.md` importing `AGENTS.md`), not by a hook. And "was this review finding actually evidence-backed" fails the second condition outright: a hook sees only the command text, never the judgment call itself, so it can only ever nag, not decide, and that class of rule stays prose and a chained Skill trigger. Those three conditions gate promotion to a **host** hook, the involuntary layer that fires in every session under the maintainer's own credentials and that only the maintainer can grant an exemption from, which is why the bar there is destructive harm. A **committed** hook in the repository's own tree is a third layer between prose and that one, and it is earned on weaker grounds: it is opt-in per clone, visible in the tree, bypassable by design, and it therefore fits a rule whose harm is a quality miss rather than a destruction. The second condition still binds it, since a hook that cannot decide its own trigger is a hook that nags, so what earns the layer is finding the decidable half of a rule whose other half is judgment. The local-review rule under "Verification Discipline" is the worked example: whether a review's findings were rightly disposed of is judgment no hook can decide and stays prose, while whether a review pass ran over exactly the content being pushed is a receipt comparison, which the hub's own `.husky/pre-push` decides. This section keeps the full rules and is surfaced at its decision moment by the `agent-conduct` Skill at `.agents/skills/agent-conduct/SKILL.md` in the hub, not a repo-relative link since that path is hub-local and not carried into every fleet repo. @@ -306,13 +306,13 @@ Every repo's GitHub repository details (the About panel) follow a fixed conventi - [`AGENTS.md`](./AGENTS.md): the agent entry point, carrying context and delegation rules plus the map to the sections below. - [`CLAUDE.md`](./CLAUDE.md): imports `AGENTS.md`, since Claude Code reads `CLAUDE.md` and never `AGENTS.md` on its own. Carries no rule of its own. - [`GOVERNANCE.md`](./GOVERNANCE.md), [`CODESTYLE.md`](./CODESTYLE.md), [`WORKFLOW.md`](./WORKFLOW.md), [`AUDIT.md`](./AUDIT.md): the governance and audit docs. This file is the cross-cutting-rules authority. -- [`spec/`](./spec/): the machine-readable ground truth, covering project-type requirements, the file/section baseline, secrets, the host tool contract and its floors, and the preferred README structure. -- [`registry/repos.json`](./registry/repos.json): the fleet registry, holding every project, its type(s), and its status. +- [`spec/`](./spec/): hub-only, the machine-readable ground truth, covering project-type requirements, the file/section baseline, secrets, the host tool contract and its floors, and the preferred README structure. +- [`registry/repos.json`](./registry/repos.json): hub-only, the fleet registry, holding every project, its type(s), and its status. - [`repo-config/`](./repo-config/): hub-only branch rulesets, fleet settings, documentation, and the apply script every repo is configured by, kept out of `.github/` (which is Actions-owned). -- [`catalog/`](./catalog/): reusable reference snippets (workflows, configs, devcontainers) the audit compares implementations against. +- [`catalog/`](./catalog/): hub-only, reusable reference snippets (workflows, configs, devcontainers) the audit compares implementations against. - [`scripts/`](./scripts/): the fleet's lint and review tooling, with the deterministic checks also gating CI, for the documented rules no CI linter checks. Its unit tests sit apart under [`scripts/tests/`](./scripts/tests/). Hosted here and reached rather than carried, per "Hub-Hosted Tooling". - [`docs/`](./docs/): hub-only procedure and measurement docs (host setup, repository configuration, signing, agent token cost), none of them carried downstream. -- [`reports/`](./reports/): per-repo audit output. +- [`reports/`](./reports/): hub-only, holding every repo's audit output rather than each repo holding its own. - [`.github/workflows/`](./.github/workflows/): this repo's own lint-only CI and source-only release, the workflow contract it dogfoods. -After editing a doc, run the linters (see "Running the Linters Locally") before commit. The Comments, Character Set, and Line Endings rules above are the frequent regressions, so hold them exactly. +After editing a doc, run the linters (see "Running the Linters Locally (Known-Working Invocations)", a hub-only section read in a hub checkout rather than carried into every fleet repo) before commit. The Comments, Character Set, and Line Endings rules above are the frequent regressions, so hold them exactly. diff --git a/WORKFLOW.md b/WORKFLOW.md index 43f8535d..3494f9ec 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -150,11 +150,11 @@ The required behaviors, organized by domain. Each is a **MUST**, stated as input ### D1 - PR Fast-Feedback (Smoke) - **D1.1 Only changed targets build.** Input: a PR touching some targets. Output: the paths-filter marks exactly those targets and only their smoke builds run. Unchanged targets skip. A repo's own targets MUST each have a filter entry (so a touched target is never silently skipped). *Prevents: rebuilding everything, and a changed target slipping through unbuilt.* -- **D1.2 A validation job always runs.** Input: any PR. Output: a type-appropriate validation job runs unconditionally and the aggregator `needs:` it. In a .NET repo this is the `unit-test` job (format/style/test). A non-.NET repo **replaces** it (not deletes) with its own validator (lint, schema-check) and re-points **every** `needs:` on it (both the aggregator and `smoke-build`, which `needs:` the validation job by name) to the replacement. *Prevents: a PR merging with no validation, or a dangling `needs:` that fails the whole workflow to load.* +- **D1.2 A validation job always runs.** Input: any PR. Output: a type-appropriate validation job runs unconditionally and the aggregator `needs:` it. In a .NET repo this is the `unit-test` job (format/style/test). A non-.NET repo **replaces** it (not deletes) with its own validator (lint, schema-check) and re-points **every** `needs:` on it (both the aggregator and `smoke-build`, which `needs:` the validation job by name) to the replacement. *Prevents: a PR merging with no validation, or a dangling `needs:` that stops the whole workflow from loading.* - **D1.3 Smoke never publishes and never uploads.** Input: `smoke: true`. Output: full compile/lint/test, but no registry/image push, no release, and **no** artifact uploads (every `upload-artifact`, including any aggregation job, is gated `!smoke`). *Prevents: a PR publishing, and orphaned artifacts churning the storage quota.* - **D1.4 Workflow-file changes are not smoke-built.** Input: a PR changing only `.github/workflows/**`. Output: the paths-filter excludes workflow files, so smoke-build skips. *Implication: a workflow-only change is not smoke-built, but actionlint still validates it in CI.* - **D1.5 One required aggregator gates merge.** Input: any PR. Output: a single aggregator job must **succeed**, `needs:` the changes job and the validation job, treat a **skipped** smoke build as pass, and **block** on `failure`/`cancelled`. Its name is ruleset-bound: the job `name:` and the ruleset `context:` are the same string and MUST be renamed together, never independently. *Prevents: a paths-filter error letting a target-changing PR merge unbuilt.* -- **D1.6 Coverage is reported to Codecov (C# and Python).** Input: a C# or Python repo's validation/test job. Output: tests run with coverage collection (`dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage` or `pytest --cov-report=xml`) and a `codecov/codecov-action` step uploads it, **best-effort** (`continue-on-error` and/or `fail_ci_if_error: false`, so a Codecov outage or an absent token never reds the gate). `CODECOV_TOKEN` lives in the repo's **actions** secret store and reaches the reusable validator via `secrets: inherit`. Required for **every** C# and Python repo that has tests. That C# invocation runs under **Microsoft.Testing.Platform**, which an MTP-based test project on the .NET 10 SDK and later requires, since running one through the VSTest target fails outright. A repo whose test project is MTP-based, in practice any repo on xunit.v3 4.0.0 or later, therefore also ships a root **`global.json`** declaring `{"test": {"runner": "Microsoft.Testing.Platform"}}`, references **`Microsoft.Testing.Extensions.CodeCoverage`** at **18.9.0 or later** in place of `coverlet.collector`, whose VSTest data collector MTP ignores without failing, and drops `xunit.runner.visualstudio`, the VSTest adapter MTP replaces. A repo whose test project is not yet MTP-based keeps the VSTest collector and its existing pin on the reusable validator, and that lagging state is a migration still owed rather than drift, until its own bump makes the project MTP-based and forces the move. The version floor is load-bearing rather than cautionary. Below 18.1.0 the extension is built against Microsoft.Testing.Platform 1.x, and an 18.0.x resolution, which is what a `>= 18.0.0` range picks, throws a `TypeLoadException` against the 2.x platform xunit.v3 4.0.0 carries, runs zero tests, and still writes a well-formed Cobertura file reporting full coverage, so only the non-zero exit says the run reported nothing. 18.9.0 is the first release on Microsoft.Testing.Platform 2.3.x, where every test project writes into the one shared `--results-directory` the invocation names rather than resolving that relative path per project. Two details of the invocation are equally load-bearing, and neither failure reds the job on its own. `--coverage-output` stays unset, because pinning one filename gives every test project in the solution the same path and a repo with more than one then keeps only whichever ran last. The default name that produces, `.cobertura.xml`, is in turn one `codecov-cli`'s own file finder does not match, its patterns being `*coverage*.*` and an exact `cobertura.xml`, so the validator prefixes each report to `coverage-.cobertura.xml` before the upload step reads the directory. Where this guarantee does not apply (a `lint-only` profile for that type, per `registry/repos.json`), the hub's `spec/secrets.json` `typeMechanisms` mapping is not claimed for that repo, and the absence is not drift. The repo also ships a **`codecov.yml`** that sets the project and patch statuses to **`informational: true`** so a coverage delta never gates a PR (a distinct knob from `fail_ci_if_error`, which only guards the upload step), and excludes intentionally-untested, non-shipped code (an example/demo or benchmark project) from the coverage denominator via `ignore`. A repo may override this to enforce a coverage threshold where its quality bar requires it. Coverage output is a build artifact, so `.gitignore` excludes it (e.g. `coverage/` and `*.cobertura.xml`, with `.gitignore` the full source of truth) so a blanket `git add -A` won't stage the untracked output. *Prevents: coverage silently going unreported, a test project stranded on a runner the current SDK refuses, a stale and unused token, a coverage regression blocking an unrelated PR, and a coverage artifact committed by a blanket add.* +- **D1.6 Coverage is reported to Codecov (C# and Python).** Input: a C# or Python repo's validation/test job. Output: tests run with coverage collection (`dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage` or `pytest --cov-report=xml`) and a `codecov/codecov-action` step uploads it, **best-effort** (`continue-on-error` and/or `fail_ci_if_error: false`, so a Codecov outage or an absent token never reds the gate). `CODECOV_TOKEN` lives in the repo's **actions** and **dependabot** secret stores, the second because a run triggered by a Dependabot PR reads the Dependabot store and the upload would otherwise skip silently on every bot PR, and the caller maps it to the reusable validator by name (`secrets:` with `CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}`), the way every hub task's declared secrets are passed. Required for **every** C# and Python repo that has tests. That C# invocation runs under **Microsoft.Testing.Platform**, which an MTP-based test project on the .NET 10 SDK and later requires, since running one through the VSTest target fails outright. A repo whose test project is MTP-based, in practice any repo on xunit.v3 4.0.0 or later, therefore also ships a root **`global.json`** declaring `{"test": {"runner": "Microsoft.Testing.Platform"}}`, references **`Microsoft.Testing.Extensions.CodeCoverage`** at **18.9.0 or later** in place of `coverlet.collector`, whose VSTest data collector MTP ignores without failing, and drops `xunit.runner.visualstudio`, the VSTest adapter MTP replaces. A repo whose test project is not yet MTP-based keeps the VSTest collector and its existing pin on the reusable validator, and that lagging state is a migration still owed rather than drift, until its own bump makes the project MTP-based and forces the move. The version floor is load-bearing rather than cautionary. Below 18.1.0 the extension is built against Microsoft.Testing.Platform 1.x, and an 18.0.x resolution, which is what a `>= 18.0.0` range picks, throws a `TypeLoadException` against the 2.x platform xunit.v3 4.0.0 carries, runs zero tests, and still writes a well-formed Cobertura file reporting full coverage, so only the non-zero exit says the run reported nothing. 18.9.0 is the first release on Microsoft.Testing.Platform 2.3.x, where every test project writes into the one shared `--results-directory` the invocation names rather than resolving that relative path per project. Two details of the invocation are equally load-bearing, and neither failure reds the job on its own. `--coverage-output` stays unset, because pinning one filename gives every test project in the solution the same path and a repo with more than one then keeps only whichever ran last. Leaving it unset produces the default name `.cobertura.xml`, which `codecov-cli`'s own file finder does not match. The validator therefore prefixes each report to `coverage-.cobertura.xml` before the upload step reads the directory. Where this guarantee does not apply (a `lint-only` profile for that type, per the hub's `registry/repos.json`), the hub's `spec/secrets.json` `typeMechanisms` mapping is not claimed for that repo, and the absence is not drift. The repo also ships a **`codecov.yml`** that sets the project and patch statuses to **`informational: true`** so a coverage delta never gates a PR (a distinct knob from `fail_ci_if_error`, which only guards the upload step), and excludes intentionally-untested, non-shipped code (an example/demo or benchmark project) from the coverage denominator via `ignore`. A repo may override this to enforce a coverage threshold where its quality bar requires it. Coverage output is a build artifact, so `.gitignore` excludes it (e.g. `coverage/` and `*.cobertura.xml`, with `.gitignore` the full source of truth) so a blanket `git add -A` won't stage the untracked output. *Prevents: coverage silently going unreported, a test project stranded on a runner the current SDK refuses, a stale and unused token, a coverage regression blocking an unrelated PR, and a coverage artifact committed by a blanket add.* ### D2 - Input/State Validation at Entry diff --git a/repo-config/configure.sh b/repo-config/configure.sh index ea4dcdd4..ed45dd97 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -310,7 +310,7 @@ check_ruleset() { # payload-file - the live ruleset must match the committed pol [ -z "$t" ] && continue # shellcheck disable=SC2016 # $t is a jq --arg variable, not a shell expansion want="$(jq -S -c --arg t "$t" "[.rules[] | select(.type==\$t) | .parameters] | first | $norm" "$file")" - # shellcheck disable=SC2016 + # shellcheck disable=SC2016 # $t is a jq --arg variable, not a shell expansion got="$(jq -S -c --arg t "$t" "[.rules[] | select(.type==\$t) | .parameters] | first | $norm" <<<"$live")" assert "'$rname' rule '$t' parameters match the payload" test "$got" = "$want" done <<<"$ptypes" diff --git a/reports/canonical-review.json b/reports/canonical-review.json index b1410935..cb008295 100644 --- a/reports/canonical-review.json +++ b/reports/canonical-review.json @@ -9,6 +9,38 @@ "hubCommit": "0e6aacf45334149f6158aa23b8132d6aee1d1eb0", "stamp": "2026-09-01T16:37:57Z" }, + { + "unit": ".agents/skills/comment-and-doc-style/SKILL.md > Docker lint authorization", + "digest": "sha256:b61bea3f7010cafc475ce582e234e79f1e55e4bd0e5332dbe2b05f4fb5bb03ff", + "reviewer": "agent-skill", + "findings": 4, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:31Z" + }, + { + "unit": ".agents/skills/comment-and-doc-style/SKILL.md > PR titles and commit messages", + "digest": "sha256:5e23928ad9f6093b65d9a9fa1ecb68b8705770f3ce7937944416095c8186dd10", + "reviewer": "agent-skill", + "findings": 4, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:31Z" + }, + { + "unit": ".agents/skills/dotnet-codestyle/SKILL.md > Build requirements", + "digest": "sha256:c9a8e764ae6c8423420ba2cb7e8b65d5a0328a405be16a798b8aec521f01c459", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "ed329f4029c9b035662a96f00834152c27bf2d8b", + "stamp": "2026-09-01T06:18:30Z" + }, + { + "unit": ".agents/skills/dotnet-codestyle/SKILL.md > Tooling and editor", + "digest": "sha256:d00e3da0fde14b915ee959bcab0edb68d21cd0b22d954054058bfea21e7473f5", + "reviewer": "agent-skill", + "findings": 0, + "hubCommit": "a76dda7d67033f43c260d21e3ae6a291e1869c0a", + "stamp": "2026-09-01T06:46:39Z" + }, { "unit": ".agents/skills/drive-pr/SKILL.md > Disposing of Every Finding", "digest": "sha256:97b70e52bc5aebff405200e8f102b3b551d1bc28d93cbbe8aa6667207f95765b", @@ -129,6 +161,62 @@ "hubCommit": "b03a838ad24e3ff23ae6eeedb89571130a285ce6", "stamp": "2026-09-01T17:49:26Z" }, + { + "unit": ".agents/skills/python-codestyle/SKILL.md > Local development loop", + "digest": "sha256:6f1081d1c1cf0f719f9c154c0b5652a26c7c1f6d2478adc3717becb248a61458", + "reviewer": "agent-skill", + "findings": 1, + "hubCommit": "5664a954bf0c11acb6bab9014b0463697d18da4b", + "stamp": "2026-09-01T23:19:53Z" + }, + { + "unit": ".agents/skills/repo-worktree/SKILL.md > Creating a Worktree", + "digest": "sha256:5d8b1dff2f47416ac8aa52b612837e9836316a6a374522956df42124c07235c5", + "reviewer": "agent-skill", + "findings": 11, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:31Z" + }, + { + "unit": ".agents/skills/repo-worktree/SKILL.md > The Base Branch", + "digest": "sha256:025583508ebeda2bba30492e7e585965c6e52422ee956ab7e6266170ad2fc1e2", + "reviewer": "agent-skill", + "findings": 1, + "hubCommit": "6b3271c03c1a0dcdb71fd2ad0447ba2b4a1cd04c", + "stamp": "2026-09-01T15:41:23Z" + }, + { + "unit": ".agents/skills/resync-a-repo/SKILL.md > Reach the hub and measure before changing anything", + "digest": "sha256:6fed46dcc5ad75fb29b59f71ffd74eff7bb589887cab30d01a432596cc6c23f7", + "reviewer": "agent-skill", + "findings": 9, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:32Z" + }, + { + "unit": ".agents/skills/shell-codestyle/SKILL.md > Rules", + "digest": "sha256:11e0a976d220ac43ba707327c1212a74bd605c37d66b880c703ed20f51072117", + "reviewer": "agent-skill", + "findings": 9, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:58:02Z" + }, + { + "unit": ".agents/skills/shell-codestyle/SKILL.md > When shell, not Python", + "digest": "sha256:b29c744996123e32270bc884b6ff66def81646645be442cb3e848fe4dbebcf67", + "reviewer": "agent-skill", + "findings": 1, + "hubCommit": "a76dda7d67033f43c260d21e3ae6a291e1869c0a", + "stamp": "2026-09-01T06:46:39Z" + }, + { + "unit": ".agents/skills/workflow-ci-contract/SKILL.md > After Any Workflow Edit", + "digest": "sha256:7dc31b8f1e7e25effd4b85078f9099b89cf3663452ed0400cf255df019fbef0e", + "reviewer": "agent-skill", + "findings": 1, + "hubCommit": "f858a194ce29fa5702e2ce1c6ff8d1f3b221b2ee", + "stamp": "2026-09-01T23:24:57Z" + }, { "unit": ".agents/skills/workflow-ci-contract/SKILL.md > How the Contract Is Read", "digest": "sha256:89bd7614cb37469788ec836234482c1be2ebc6905bd786ad031504bfe3dd2239", @@ -187,11 +275,11 @@ }, { "unit": "AGENTS.md > Where the Rules Live", - "digest": "sha256:c339c7cca92d462dd3fab2adae3692c1ded67a75d3561b9e5457c368183f3027", + "digest": "sha256:2526a341afe0c6f1e2d2e1fe954855398742474a816e2bcba640672d1363766f", "reviewer": "agent-skill", - "findings": 1, - "hubCommit": "68117017a1483706765a8075ab8a29bb67bfe711", - "stamp": "2026-08-31T14:38:06Z" + "findings": 14, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:58:03Z" }, { "unit": "AUDIT.md > 10. Converge: Apply the Fixes", @@ -209,13 +297,37 @@ "hubCommit": "78898becaa2b1a62cb4c806d86273210c6390ac5", "stamp": "2026-09-01T14:48:23Z" }, + { + "unit": "CODESTYLE.md > General", + "digest": "sha256:ee34e84dab97a638e5011f7b8b5fc30893f82461cf6907ba35c96030eb2d4bb0", + "reviewer": "agent-skill", + "findings": 10, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:58:03Z" + }, + { + "unit": "CODESTYLE.md > Shell", + "digest": "sha256:7cf33c9cf26f21399d2de62d1219da67a16f28acfacf81cbe349948d502cb7ef", + "reviewer": "agent-skill", + "findings": 7, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:32Z" + }, { "unit": "GOVERNANCE.md > Durable Knowledge and Self-Improvement", - "digest": "sha256:4637c1bd6177bdd632f49957d252d56f276c6a6cb3e50b56c63cf8230279ba43", + "digest": "sha256:d2caaf6052fdf9838034bcd3dbf8a250d44b303b3bb73754ecb4c6fa0ead8bd0", "reviewer": "agent-skill", - "findings": 0, - "hubCommit": "fa065fcfb672eb88853c47796f96d8b0236c6811", - "stamp": "2026-09-01T16:49:07Z" + "findings": 2, + "hubCommit": "d0541523890324cbbdbb63631c8dd83e6003703a", + "stamp": "2026-09-01T19:25:11Z" + }, + { + "unit": "GOVERNANCE.md > Repository Layout", + "digest": "sha256:b02bb961b5a0ac3b595b75cd01f6a9be91b5d6690a7f7b24bfe1f9c5660196b7", + "reviewer": "agent-skill", + "findings": 6, + "hubCommit": "df5e2493b48aa45d80f586db68af0c21be97ea21", + "stamp": "2026-09-01T05:45:33Z" }, { "unit": "GOVERNANCE.md > Verification Discipline", @@ -251,11 +363,11 @@ }, { "unit": "WORKFLOW.md > 4. Behavioral Contract: Expected Outcomes", - "digest": "sha256:71023478e957e47a3a91187f1b856f611ca9bf021ace6f21ed4ea81b6b7cad30", + "digest": "sha256:c3ffe518edcfb301c3743d693a14fef3b566d059ff18b97747003bcb55cceffc", "reviewer": "agent-skill", - "findings": 0, - "hubCommit": "c3b2898feef97459d01ce7c0b63de25b6c5524bf", - "stamp": "2026-09-01T13:57:20Z" + "findings": 3, + "hubCommit": "d0541523890324cbbdbb63631c8dd83e6003703a", + "stamp": "2026-09-01T19:25:11Z" }, { "unit": "WORKFLOW.md > 5. Test Methodology", diff --git a/reports/canonical-review.md b/reports/canonical-review.md index dcdf9874..eee8949f 100644 --- a/reports/canonical-review.md +++ b/reports/canonical-review.md @@ -7,9 +7,9 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ## Coverage - units: 288 -- covered: 34 +- covered: 48 - stale: 0 -- never read here: 254 +- never read here: 240 ## Burn-down @@ -59,12 +59,10 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **Carried files reference no coordination machinery** - never - **Character set** - never - **Comments** - never -- **Docker lint authorization** - never - **Line endings** - never - **Markdown files: linting and spelling** - never - **Markdown formatting** - never - **Naming tools in prose** - never -- **PR titles and commit messages** - never - **Quantitative claims** - never - **Sentence structure** - never - **Why this exists** - never @@ -109,13 +107,11 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **(preamble)** - never - **Analyzer suppressions (.NET)** - never - **Best practices** - never -- **Build requirements** - never - **Code patterns** - never - **Coding standards and conventions** - never - **Error handling and logging** - never - **Project configuration** - never - **Testing conventions** - never -- **Tooling and editor** - never - **Why this exists** - never ### .agents/skills/dotnet-codestyle/references/conventions.md @@ -224,7 +220,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **Code style** - never - **Layout** - never - **Linter cleanliness** - never -- **Local development loop** - never - **Tests** - never - **Toolchain** - never - **Two profiles** - never @@ -257,10 +252,8 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **(preamble)** - never - **Agent-Specific Worktree Tools** - never -- **Creating a Worktree** - never - **Listing and Cleanup** - never - **Preparing Git Hooks** - never -- **The Base Branch** - never - **The Mandate** - never - **Why This Exists** - never @@ -269,15 +262,12 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **(preamble)** - never - **Apply, in this order** - never - **Confirm the procedure before starting** - never -- **Reach the hub and measure before changing anything** - never - **Ship it** - never - **Why this exists** - never ### .agents/skills/shell-codestyle/SKILL.md - **(preamble)** - never -- **Rules** - never -- **When shell, not Python** - never - **Why this exists** - never ### .agents/skills/skill-lifecycle/SKILL.md @@ -310,7 +300,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than ### .agents/skills/workflow-ci-contract/SKILL.md - **(preamble)** - never -- **After Any Workflow Edit** - never - **Why This Exists** - never ### .agents/skills/workflow-ci-contract/references/d-guarantees.md @@ -380,9 +369,7 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **(preamble)** - never - **.NET** - never -- **General** - never - **Python** - never -- **Shell** - never ### GOVERNANCE.md @@ -400,7 +387,6 @@ A unit is what a reviewer reads whole, decided by the carry manifest rather than - **Release Model** - never - **Repository Boundaries and Write Safety** - never - **Repository Details** - never -- **Repository Layout** - never - **Representative Data in Agent-Authored Text** - never - **Supported Development Platforms** - never diff --git a/scripts/pr_review.py b/scripts/pr_review.py index e8b7d6b8..1d098d11 100755 --- a/scripts/pr_review.py +++ b/scripts/pr_review.py @@ -270,6 +270,14 @@ # This change puts both spellings into the source and the runbook, so a review of it quotes them. # A quoted count read as this round's own is a coverage figure nobody stated. FENCE = re.compile(r"^ {0,3}```.*?^ {0,3}```[^\n]*", re.DOTALL | re.MULTILINE) +# An inline code span is a quotation for the same reason a fenced block is. +# A reviewer naming `` in prose was read as opening one. +# Bounded to a paragraph, and an escaped tick opens nothing, since either masks a section. +# Only the opener takes that guard, a backslash being literal inside a span rather than an escape. +# Replaced by a space, since deleting joins the sides into a tag the body never carried. +CODE_SPAN = re.compile(r"(?` around it. # One measured round carries the table with no ``, and two summary spellings wrap it. @@ -912,8 +920,13 @@ def is_coverage_line(line: str) -> bool: def coverage_statements(body: str) -> list[str]: - """The lines this round states its file coverage on, quotations excluded.""" - return [ln.strip() for ln in FENCE.sub("", body or "").splitlines() if is_coverage_line(ln)] + """The lines this round states its file coverage on, quotations excluded. + + An inline code span is a quotation the same as a fenced block, so a span carrying a coverage + line reads as one this round stated rather than one it quoted. + """ + plain = CODE_SPAN.sub(" ", FENCE.sub("", body or "")) + return [ln.strip() for ln in plain.splitlines() if is_coverage_line(ln)] def read_coverage(line: str) -> tuple[int, int] | None: @@ -1118,7 +1131,7 @@ def unrecognized_in(body: str) -> list[str]: # What is left of a drifted refusal is a body with no heading, which is the arm below. if refusal_of({"body": body}): return [] - plain = FENCE.sub("", body or "") + plain = CODE_SPAN.sub(" ", FENCE.sub("", body or "")) headings = [normal(ln) for ln in plain.splitlines() if MARKDOWN_HEADING.match(ln)] labels = [normal(m.group(1)) for m in map(LABEL_LINE.match, plain.splitlines()) if m] found = [f"heading: {h}" for h in dict.fromkeys(headings) if unvetted(h, VETTED_HEADINGS)] @@ -1574,7 +1587,7 @@ def qodo_open_findings(body: str) -> list[str]: return [] return [ s.strip() - for s in SUMMARY.findall(body) + for s in SUMMARY.findall(CODE_SPAN.sub(" ", FENCE.sub("", body))) if QODO_FINDING.match(s) and not QODO_BADGE.search(s) ] diff --git a/scripts/tests/test_pr_review.py b/scripts/tests/test_pr_review.py index 306cd39d..8b512ef9 100755 --- a/scripts/tests/test_pr_review.py +++ b/scripts/tests/test_pr_review.py @@ -1631,6 +1631,111 @@ def test_a_genuinely_unknown_marker_is_still_reported(self) -> None: any(f.startswith(shape) for f in found), f"{unknown!r} passed as vetted: {found}" ) + def test_a_tag_quoted_in_a_code_span_is_not_read_as_a_section(self) -> None: + """A reviewer naming `` in prose is quoting it, not opening a section. + + The reader consumed from the code span to the next real ``, swallowing the body + between them and reporting the wreckage as an unknown section, so a clean review read as + unclosable. It needs a real close tag later in the body to bite, which is why the vetted + section below is part of the case rather than scenery. A fenced block was already excluded + for the same reason a code span is: a quotation is not a statement. + """ + quoted = "- Add the missing ``/summary-arm case to the marker test." + self.assertEqual( + [], pr_review.unrecognized_in(collapsed().replace("\n\n", "\n" + quoted + "\n", 1)) + ) + + def test_a_real_summary_section_survives_the_code_span_strip(self) -> None: + """The strip must not cost the reader a genuine section, which is what it exists to find.""" + body = OVERVIEW + "\n`` in prose.\n
Reviewed Chances" + self.assertEqual(["summary: Reviewed Chances"], pr_review.unrecognized_in(body)) + + def test_masking_a_code_span_does_not_join_the_text_around_it(self) -> None: + """Deleting a span would fuse its two sides into a marker the body never carried. + + `Reviewed Chances
` are not a tag until the span between them + goes away, so the mask has to leave something behind rather than nothing. + """ + body = OVERVIEW + "\nReviewed Chances
" + self.assertEqual([], pr_review.unrecognized_in(body)) + + def test_a_stray_backtick_in_two_paragraphs_does_not_mask_between_them(self) -> None: + """A code span cannot cross a blank line, so two stray ticks are not a span. + + Unbounded, the pair swallowed every marker between them. That is the silent direction: + a real unknown section disappears and the digest closes the loop on a body it never read. + """ + body = ( + "## Pull request overview\n\nThe change is narrow.\n\n" + "The ` character is a delimiter.\n\n" + "
Bogus Section
\n\n" + "Also the ` character.\n" + ) + self.assertEqual(["summary: Bogus Section"], pr_review.unrecognized_in(body)) + + def test_a_stray_backtick_in_two_paragraphs_does_not_mask_the_headings(self) -> None: + """The same over-match in the blocking direction, which reads as a body with no heading.""" + body = ( + "Intro with a ` stray tick.\n\n## Pull request overview\n\nText.\n\n" + "Another ` stray tick.\n" + ) + self.assertEqual([], pr_review.unrecognized_in(body)) + + def test_a_masked_span_leaves_nothing_unprintable_in_a_reported_marker(self) -> None: + """The marker is quoted back to a reader, so the mask cannot be an unprintable byte.""" + body = OVERVIEW + "\n
Reviewed `x` Chances
" + self.assertEqual(["summary: Reviewed Chances"], pr_review.unrecognized_in(body)) + + def test_a_coverage_line_inside_a_code_span_is_a_quotation(self) -> None: + """A span carrying a coverage line is quoting one, not stating this round's own. + + The fence arm already excluded a quoted line, and a span is the same quotation, so a + review whose prose quotes a coverage line was reported as stating an invalid one. + """ + body = OVERVIEW + "\n\nQuoting `a\n- **Files reviewed:** invalid` here.\n" + self.assertEqual([], pr_review.coverage_statements(body)) + + def test_a_real_coverage_line_survives_the_code_span_mask(self) -> None: + """The mask must not cost the reader the statement it exists to find.""" + line = "- **Files reviewed:** 31/31 changed files" + self.assertEqual([line], pr_review.coverage_statements(OVERVIEW + "\n" + line + "\n")) + + def test_an_escaped_backtick_does_not_open_a_span(self) -> None: + """A backslash-escaped backtick is a literal, so it delimits nothing. + + Reading one as an opener pairs it with the next real tick and masks everything between, + which is the silent direction: a genuine unknown section disappears and the loop closes + on a body nobody read. + """ + body = ( + OVERVIEW + + "\nEscaped \\` then
Bogus
then ` a span `." + ) + self.assertEqual(["summary: Bogus"], pr_review.unrecognized_in(body)) + + def test_a_span_ending_in_a_backslash_still_closes(self) -> None: + """A backslash inside a code span is literal, so it does not escape the closing run. + + Guarding the closer the same way as the opener skips a valid close, so the span runs on + and masks the section after it, which is the failure the opener guard exists to prevent + rather than to cause. + """ + body = ( + OVERVIEW + + "\nA span `a\\` then
Hidden
and ` more." + ) + self.assertEqual(["summary: Hidden"], pr_review.unrecognized_in(body)) + + def test_a_quoted_marker_stays_masked_when_the_span_ends_in_a_backslash(self) -> None: + """The other side of the close guard: a quoted marker must not escape through it. + + Where the span both carries a marker and ends in a backslash, failing to recognize the + close reports the quoted marker as a real section, which is what the masking exists to + prevent. + """ + body = OVERVIEW + "\nQuoting `
Quoted
\\` here." + self.assertEqual([], pr_review.unrecognized_in(body)) + def test_every_vetted_marker_together_reads_as_recognized(self) -> None: """The corpus shape in one body, so the lists are held against what they were built from.""" self.assertEqual([], pr_review.unrecognized_in(nested())) diff --git a/spec/project-types.json b/spec/project-types.json index a16532ac..8c510cc3 100644 --- a/spec/project-types.json +++ b/spec/project-types.json @@ -11,7 +11,7 @@ { "id": "csharp.editorconfig.ruleblock", "verdict": "letter", "assert": ".editorconfig carries the shared [*.cs] plus ReSharper rule block.", "intentRef": "CODESTYLE.md" }, { "id": "csharp.analyzers.zerowarnings", "verdict": "intent", "assert": "Analyzer severities are enforced; warnings are not relaxed or suppressed wholesale.", "intentRef": "CODESTYLE.md" }, { "id": "csharp.centralconfig.props", "verdict": "letter", "assert": "Shared MSBuild configuration is centralized at the repo root: Directory.Build.props carries the common analyzer and warning properties (the Zero Warnings set), and Directory.Packages.props enables ManagePackageVersionsCentrally with every dependency version declared once - a csproj carries only project-specific properties and versionless PackageReference items.", "intentRef": "CODESTYLE.md" }, - { "id": "csharp.coverage.codecov", "verdict": "letter", "assert": "The unit-test job collects coverage (dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage, with the output filename left unset so each test project writes its own report rather than overwriting a shared one) and uploads it to Codecov via codecov/codecov-action, best-effort (fail_ci_if_error: false so a Codecov outage or an absent token never reds the gate). CODECOV_TOKEN is stored in the repo actions secrets and reaches the reusable validator via secrets: inherit. The validator prefixes each report to coverage-.cobertura.xml before uploading, since codecov-cli does not match the default name. Where the repo's test project is MTP-based (xunit.v3 4.0.0 or later), it also carries a root global.json declaring test.runner as Microsoft.Testing.Platform, references Microsoft.Testing.Extensions.CodeCoverage at 18.9.0 or later rather than coverlet.collector, and drops xunit.runner.visualstudio. A test project not yet MTP-based keeps the VSTest collector and its existing pin, which is a migration owed rather than drift. Required for every C# repo with tests.", "intentRef": "WORKFLOW.md", "minProfile": "build" } + { "id": "csharp.coverage.codecov", "verdict": "letter", "assert": "The unit-test job collects coverage (dotnet test --coverage --coverage-output-format cobertura --results-directory ./coverage, with the output filename left unset so each test project writes its own report rather than overwriting a shared one) and uploads it to Codecov via codecov/codecov-action, best-effort (fail_ci_if_error: false so a Codecov outage or an absent token never reds the gate). CODECOV_TOKEN is stored in both the repo actions and dependabot secrets stores, the second so the upload does not skip on a Dependabot PR, and the caller maps it to the reusable validator by name. The validator prefixes each report to coverage-.cobertura.xml before uploading, since codecov-cli does not match the default name. Where the repo's test project is MTP-based (xunit.v3 4.0.0 or later), it also carries a root global.json declaring test.runner as Microsoft.Testing.Platform, references Microsoft.Testing.Extensions.CodeCoverage at 18.9.0 or later rather than coverlet.collector, and drops xunit.runner.visualstudio. A test project not yet MTP-based keeps the VSTest collector and its existing pin, which is a migration owed rather than drift. Required for every C# repo with tests.", "intentRef": "WORKFLOW.md", "minProfile": "build" } ] }, "nuget": { @@ -42,7 +42,7 @@ { "id": "python.pyright.config", "verdict": "intent", "assert": "Build profile: pyright is configured and runs strict on first-party code (src or the integration package) - the strong typing baseline. Third-party strictness is relaxed only where a dependency has no usable types. N/A for the lint-only profile, whose type checker is mypy over stdlib-only code (python.mypy.allowed).", "intentRef": "CODESTYLE.md", "minProfile": "build" }, { "id": "python.config.placement", "verdict": "letter", "assert": "ruff and the type-checker config live in pyproject.toml (canonical); standalone .ruff.toml / pyrightconfig.json is a drift finding. A Home Assistant integration is the exception - it follows home-assistant/core standalone-config conventions and is scored by ha.python.conventions instead.", "intentRef": "CODESTYLE.md" }, { "id": "python.mypy.allowed", "verdict": "intent", "assert": "mypy is permitted as an additional type checker, not banned. It is required for a Home Assistant integration (platinum strict-typing) and is the lint-only profile's type checker. When used it runs in CI and the editor.", "intentRef": "CODESTYLE.md" }, - { "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). CODECOV_TOKEN is stored in the repo actions secrets. Required for every Python repo with tests. N/A for the lint-only profile (its unittest suite runs under coverage in CI, reported without a threshold and never uploaded to Codecov). In a mixed repo the codecov.yml file-presence is still required by any co-present type that has tests, e.g. csharp.", "intentRef": "WORKFLOW.md", "minProfile": "build" }, + { "id": "python.coverage.codecov", "verdict": "letter", "assert": "The test job collects coverage (pytest --cov-report=xml) and uploads it to Codecov via codecov/codecov-action, best-effort (continue-on-error and fail_ci_if_error: false). CODECOV_TOKEN is stored in both the repo actions and dependabot secrets stores, the second so the upload does not skip on a Dependabot PR, and the caller maps it to the reusable validator by name. Required for every Python repo with tests. N/A for the lint-only profile (its unittest suite runs under coverage in CI, reported without a threshold and never uploaded to Codecov). In a mixed repo the codecov.yml file-presence is still required by any co-present type that has tests, e.g. csharp.", "intentRef": "WORKFLOW.md", "minProfile": "build" }, { "id": "python.uvlock.pinned", "verdict": "letter", "assert": "Build profile: the committed uv.lock resolves to LF through the repository-wide .editorconfig and .gitattributes defaults. A CRLF-native operational repo adds a narrow uv.lock LF override only if it adopts the uv build profile. N/A for a non-uv Python repo (a Home Assistant integration on pip/requirements) and for the lint-only profile (no uv.lock by definition).", "intentRef": "GOVERNANCE.md#line-endings", "minProfile": "build" }, { "id": "python.scripts.uvx", "verdict": "letter", "assert": "Lint-only profile only: the tools run via uvx (no project install, no lockfile). A uvx @ pin in a run: step is not Dependabot-trackable, so CI runs uvx ruff@latest / uvx mypy@latest - the fleet rule pins only what Dependabot auto-updates and otherwise runs latest, never a manual pin that goes stale. VS Code tasks, README, and CI all run the unpinned latest. N/A for the build profile (which pins tool versions via uv.lock + uv sync --frozen instead).", "intentRef": "CODESTYLE.md" } ] diff --git a/spec/section-model.md b/spec/section-model.md index 8a03ac4d..b0c81377 100644 --- a/spec/section-model.md +++ b/spec/section-model.md @@ -49,7 +49,7 @@ A section is one of the following. Fidelity is declared in [files.json][files], **Devcontainer content.** A devcontainer is optional infrastructure, not required by any repo type. An operational (live config) repo is edited and deployed live and typically has none, so its Devcontainer section states that plainly. A repo that keeps one (a code repo's toolchain, or an offline-debugging aid for a config repo) describes it. The section is present in every carried `GOVERNANCE.md` so the development model is always answered, even when the answer is none. -**Not carried (hub-only).** `Repository Onboarding and Conformance` lives in the hub's `GOVERNANCE.md` as hub-audit context (reconciling the registry, the STANDUP cold-start, the conformance matrix) but is not a carried section, since a downstream agent never runs those. Its one universal rule, that a repo is done when it passes `AUDIT.md` for its type, is carried in `AUDIT.md` itself. Like the model docs, `STANDUP.md` and `RESYNC.md`, it is hub machinery, not fleet law. +**Not carried (hub-only).** `Repository Onboarding and Conformance` lives in the hub's `GOVERNANCE.md` as hub-audit context (reconciling the registry, the STANDUP cold-start, the conformance matrix) but is not a carried section, since a downstream agent never runs those. Its one universal rule, that a repo is done when it passes `AUDIT.md` for its type, is carried in `AUDIT.md` itself. Like the model docs, `STANDUP.md` and `RESYNC.md`, it is hub machinery, not fleet law. `Running the Linters Locally (Known-Working Invocations)` is hub-only for a different reason: a downstream agent does run what it documents, but the invocations themselves are hub-hosted rather than carried, `scripts/docker_lint.py` per `GOVERNANCE.md` "Hub-Hosted Tooling", and the section is written around surfaces only the hub holds, the `catalog/snippets/` hook shapes, `validate-task.yml`, and the `spec/project-types.json` checks that read it for rationale. A carrier reaches it in a hub checkout, the same way it reaches the tooling. Each carried surface routing to it names it as hub-only rather than as a section a carrier can open, which is `AGENTS.md`'s "Where the Rules Live" row, `CODESTYLE.md`, `GOVERNANCE.md`'s own "Repository Layout", and the `comment-and-doc-style`, `dotnet-codestyle`, `python-codestyle`, `shell-codestyle` and `workflow-ci-contract` Skills. ## Changing the structure carries review weight