Conversation
Carries the TODO.md cluster "Reaching the Hub in Your Own Checkout" whole. Repository Boundaries and Write Safety gains the checkout-isolation rule, scoped per task rather than per agent, and Verification Discipline gains the same rule for reading, since a clone is whatever it last fetched rather than the branch it names. Fleet Bootstrap names the hub as a defined term and adds the reach rule, README.md defines the six terms a request is phrased in for a repository that carries no instruction set, and the copilot instructions point a reviewer at the hub scripts and correct the byte-locked section count to match spec/files.json. The host-wide agent-safety blocks carry the same two rules. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Why Selecting the next work item meant re-deriving the grouping every session. Entries were grouped by the surface they change, which is #535's convention and answers what a change costs, while the working question is what fits one pull request. That analysis is what the file should carry forward and did not. Three defects made it unsafe to select from, each verified against `develop` at `3d1a0b1`: - **Three open issues appeared nowhere in it.** A pass reading only this file could not see #538, #550 or #557. - **Two open issues were already fixed.** Both are now closed with their evidence quoted, see below. - **Two research artifacts contradicted the tree.** The re-vendor entry called `repo-config/configure.sh` carried `verbatim` with `appliesTo: "*"`, while `spec/files.json` no longer declares it and `spec/divergences.json` dispositions it `retire`. `reports/divergences.md` still rendered it under `re-vendor` across six repos, so the committed report disagreed with its own ledger. ## What changed A `###` heading under `Work Clusters` is one pull request. 20 clusters, 28 entries, after the follow-up commit retired one. The #535 rationale is not discarded, it moves from an organizing axis to a per-cluster `Touches` and `Cost` field, which states more than the old heading implied: a cluster confined to one surface says so in a clause and a cluster spanning two states both. An entry carries `Blocked by`, `Issue` and `Checked` exactly once each, in that order, and never omits one, because an omitted field reads as unknown rather than as none. `Checked` is a per-entry freshness anchor naming the branch, commit and date a claim was last read against the tree, replacing a file-level anchor that already carried two commits for two passes and could not carry a third. `Open` states a decision a future session makes, `Settled` states a finding it does not re-derive, and a `Settled` bullet carries a number, a proper name, or a rejected alternative. Two structural slots are new: - **`Fleet Sweeps`** holds work landing on a downstream visit. It had no home, which left #353, #379 and #489 under a heading promising a close they explicitly could not take. Each item carries `Hub state`, `Outstanding` and `Rides with`, so six repetitions of naming the same visit become one manifest. It records the batching preference outright, that the fleet is caught up periodically rather than after every hub change, and names Blog as the pilot. - **`Recorded for the Maintainer`** holds issue actions that are the maintainer's to take, each carrying the evidence so it is one action rather than a re-derivation. `How to Select the Next Item` states the procedure. Step 1 is confirming every open issue has an entry, which is the honest maximum a hand-maintained file gives and the reason it comes first. ## Two overlaps folded in - **#577 and the registry-description entry are one decision.** Declaring the description in `registry/repos.json` makes every mirror read a field rather than parse a paragraph, so taking #577 first means writing an extraction rule the registry change then deletes. They are now one cluster with the sequencing stated. - **#365 and #483 are one host visit.** The rollout needs the tooling matrix to be repeatable and the matrix is only worth filling if the rollout uses it. ## A generated ledger was considered and rejected `spec/divergences.json` earns its generator by joining a curated ledger against live fleet reality and rendering `UNTRIAGED` for anything live the ledger missed. A backlog has no such reality to join against, and the only mechanizable join is issue open or closed state, which is step 1 of the procedure. What is given up is stated in the file rather than glossed: no enum enforcement, and no automatic surfacing of an open issue with no entry, which is exactly the #538, #550 and #557 failure. The bold-label template stays parseable, so a later checker asserting every `Checked` commit resolves is buildable without re-shaping the file. ## Sweep obligations `spec/readme-structure.md` said a `TODO.md` "opens with a `# TODO` heading and one line saying what it holds, then a flat bullet list". #535 already contradicted both clauses. It now states the minimum rather than the shape, and says this repo's file is a worked example rather than a template every repo adopts, since a five-line downstream backlog needs no selection procedure. That file is not in `spec/files.json`, so the edit costs no re-vendor. `spec/section-model.md`, `STANDUP.md` and `README.md` describe the file's purpose without asserting its shape, verified clean and recorded here so the sweep is done rather than skipped. `host-setup/agent-safety/README.md` ends the write-guard note with "Tracked in `TODO.md`", still true. ## Ledger regenerated `reports/divergences.md` now renders `repo-config/configure.sh` under `retire` rather than `re-vendor`, resolving the contradiction. Regenerating it also **disproved a claim written during this pass**: Blog is one of two repos carrying `AGENTS.md` "Fleet Bootstrap", not the only one. HomeAutomation-Config carries it too, and being `operational` it exercises the direct-to-`develop` path rather than the pull request one, which the pilot note now records as the second visit worth making rather than the first. A second correction, to #521's own claim: three merge-bot jobs carry both `contents: write` and `pull-requests: write` and the fourth carries `pull-requests: write` alone, so the issue's "all four" is one job wide. The entry records this rather than repeating the issue. ## Issues closed - **#552** closed. #572 landed `template_ref_outside_verbatim` in `spec/audit.py`, excising verbatim regions by position before the coordination-reference scan, with eight self-test cases. Positional excision rather than content removal is required by the fenced-copy case, since removing extracted text would delete a fenced copy along with the real region and the scan would fail open. - **#538** closed. Complete on all three points, checked one at a time: the whole `parameters` object of every parameterized rule is compared with keys and set-like arrays sorted on both sides, the rule-type set is compared in both directions, and the header states what check verifies and names what stays unaudited. ## Follow-up: the file's own procedure applied to itself `6bed2d9` is the first exercise of step 9, and it is also what gave this pull request the CI run it never had, since the whole thing opened during the Actions outage and a rollup does not re-fire on its own. `9d85941` merged "Reaching the Hub in Your Own Checkout" whole, so the cluster is deleted rather than annotated and nothing moves out of it. Each of its four entries was re-read against `develop` before the deletion rather than trusted to the merge: the checkout-isolation rule and the stale-clone read rule in `GOVERNANCE.md`, the hub definition in `AGENTS.md` "Fleet Bootstrap", the six terms in `README.md`, and the hub-script pointers in `.github/copilot-instructions.md`. #557 and #579 take its place under "Verified Complete, Awaiting Close" citing that commit, since both close on the `develop` to `main` promotion rather than on the merge that completed them. The two "Recorded for the Maintainer" comments they subsume go with them, because a recorded action saying an issue stays open until a cluster lands is a stale instruction once the cluster has landed. That section now holds two items rather than four. Two entries left the same section for the opposite reason. #509 and #490 were closed on 2026-08-03, three days before the clustering commit, so a section whose entire subject is what still awaits a close was carrying two entries that had already had one. Their link definitions stay, since other clusters still cite both issues. Four definitions the deletions orphaned go too, `issue-552`, `pr-572`, `pr-588` and `workflow`, along with the pull request block left holding none. MD053 is enabled by default, so an unused definition is a lint failure rather than a spare, and the reference-and-definition audit in the verification list above was re-run rather than assumed to still hold. ## Verification - `python3 scripts/prose_lint.py . --diff origin/develop` clean, and `TODO.md` holds 0 violations under the default rules as it did before. - `python3 spec/validate.py` reports 22 cataloged, 0 backlog. - markdownlint-cli2 and editorconfig-checker clean on all three files, CRLF preserved, and both re-run on `TODO.md` after `6bed2d9`. - Every link reference is defined and every definition is cited, with each block sorted by reference name. - All 28 cluster entries carry the three mandatory fields in order, checked mechanically. - The selection procedure was walked end to end against the file, and names a cluster from 5 KB of headings and `State` lines without opening any research. Note the file grew, 61 KB to 73 KB, rather than compressing. Three issues that had no entry were added along with the procedure and the per-entry anchors. The metric that moved is the one selection depends on, which is the 5 KB a re-select actually reads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Why Every verdict a `--diff` run reports rests on a scope the run never checked. A gate that finds nothing is indistinguishable from a gate with nothing to find, and the exit code is the same either way. Four routes to that false clean are already on record, each closed by a guard written after a reviewer noticed it: an unresolvable base widening to a whole-tree scan, a multi-line `paths` input read only to its first newline, a diff taken in one repository while scanning another, and a path under no repository at all. Per-route guards are the wrong shape for a fifth, because the fifth is found by a reviewer or not at all. `LEAST_PLAUSIBLE` is already the floor on a whole-tree sweep, so the shape exists in the file and the `--diff` path is what lacked it. Fresh evidence for the entry came from the session before this one, where `prose_lint --diff --summary` printed `0 violation(s) across 0 file(s)` on a real change and distinguishing that from a scoping failure took a hand-written call into `changed_lines()` to prove the scope was non-empty. ## What changed A run that resolves a non-empty diff and matches none of the files it names now exits 2 naming them, instead of exiting 0. Zero alone is not the test. A change touching only files the rules do not read, an image or a lock file, legitimately matches nothing, so the comparison is against the diff's own list of files this run could have read. `unread_diff_files()` builds that list, and `asked_about()` keeps a caller who narrowed the scan on purpose from being told the narrowing is a defect. The exit code is 2 rather than 1, matching the two guards beside it, so a caller can tell "the gate could not run" from "the gate ran and found something". ## The floor found the fifth route immediately `git diff` reports repository-relative keys while discovery keys off the directory the run started in. Run from a subdirectory, the intersection is empty and the run reports clean. No existing guard covers it, and it is why `unread_diff_files()` resolves keys against the repository top level: reading them against the working directory would leave the list empty in exactly the case it exists to catch. Measured on this branch's own diff, run from `scripts/`, with the previous revision extracted to a temporary path so both run against the same tree. The error text is verbatim, wrapped here for width: ```text $ git show HEAD~1:scripts/prose_lint.py > /tmp/prev_prose_lint.py $ cd scripts && python3 /tmp/prev_prose_lint.py . --diff develop --check dupword $ echo $? 0 $ cd scripts && python3 prose_lint.py . --diff develop --check dupword error: the diff against 'develop' names 2 readable file(s) this run was asked about, and the scan matched none of them: scripts/prose_lint.py, scripts/test_prose_lint.py. Refusing to report a clean run, since a gate that read nothing is indistinguishable from a gate with nothing to read. Check that the run starts at the repository top level and that the requested paths cover the change. $ echo $? 2 ``` ## Both directions are tested, and the second one is the larger half Ten cases. Three fail against the previous revision, which is what proves the floor bites. The other seven pass against both revisions on purpose: they hold the direction that matters more, that a legitimate zero stays a clean run. A gate that cries wolf on a commit adding a logo is how a safety check stops being read. The must-not-fire cases are a deliberate narrowing, an empty diff, an excluded file, a generated tree, a path the diff names that no longer exists, and a binary. Each was confirmed against a real run as well as a mocked one, the binary case with an actual PNG committed in a scratch repository. ## Verification - `python3 scripts/test_prose_lint.py` - 173 pass, up from 163. - `python3 spec/validate.py`, `python3 spec/audit.py --selftest`, `python3 scripts/repo_gate.py` - all clean. - `python3 scripts/prose_lint.py . --check charset --check dupword --check spelling` - clean, the blocking CI run. - `python3 scripts/prose_lint.py . --diff develop` - clean, and it caught two comment blocks in this change breaking the one-sentence-per-line rule, which are fixed. - Re-run after rebasing onto `develop` at `3f91868` rather than only on the base the work started from. ## Honest limits An absolute path argument produces an empty list rather than a finding, so the floor falls back to today's behavior there instead of over-blocking. Deletions never enter the scope map at all, since a deletion hunk yields an empty line range, so they cannot trigger it. The CI jobs in this repo call `prose_lint.py` without `--diff`, so nothing here changes what this repo's own pipeline reports. The consumer is `.github/actions/prose-gate`, which passes `--diff` and runs from the caller's repository root. ## Backlog Carries the `TODO.md` cluster "The Prose Gate Scope Floor" whole. It unblocks "The Representative-Data Path Check", which is worth having only once the gate can prove it read something. Per selection step 9 the cluster is deleted when this merges, which is a follow-up commit rather than part of this diff. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Why `GOVERNANCE.md` "Representative Data in Agent-Authored Text" says agent-authored text illustrates with constructed data rather than data observed in the maintainer's environment, and says in the same breath that no pattern closes it. This gates the half a pattern can reach. It is introduced as that half deliberately. The leak that motivated the rule was name-shaped and sat in a pull request comment, which no committed-file linter reads, so a check mistaken for the answer is what stops anyone looking at the exposure the rule actually exists for. Carries the `TODO.md` cluster "The Representative-Data Path Check", which #595 released from `blocked` once the scope floor shipped in #594. ## The shapes were measured, not assumed The backlog recorded four shapes, `/home/<name>`, `/Users/<name>`, `C:\Users\<name>`, and a bare drive letter. Run against this repository, **a bare drive letter matched 11 files and named a path in none of them.** An escaped newline after any word ending in a letter and a colon reads as a drive letter, so a YAML fixture is full of them: | Matched text | What it actually is | | --- | --- | | `e:\n needs` | `jobs:\n needs` inside a test fixture string | | `s:\n a` | `jobs:\n a:` inside a test fixture string | | `C:\tmp` (8 hits) | a temp path in fixtures for the string-literal rule | That shape is dropped. The one kept is a drive letter followed by `Users`, which matches nothing in the tree today. This is narrower than the committed prose, which says "an absolute home path or a drive letter", so the narrowing is stated here rather than left for a reader to discover. ## The exemption is built, not listed A wrong exemption hands out a work list that damages correct documents, and this rule has two self-referential problems that a file-name allowlist would solve badly and briefly. **The rule's own wording quotes the shapes it forbids.** Requiring a real user segment solves it by construction: the documentation placeholder is not a valid account name, so it describes the shape without matching it. No exemption names `GOVERNANCE.md`, and none goes stale when the text moves. | Probe | Verdict | | --- | --- | | `C:\Users\<name>` in documentation | no match | | `/home/<name>` in documentation | no match | | `/home/vscode/.ssh` | exempt, container account | | a real absolute home path | **flagged** | **Container accounts are exempt by name.** Every home path in this repository, all 15, is `/home/vscode` from the devcontainer snippets and the doc describing them. Without that exemption the rule would open with 15 findings and no true positive among them. **The gate reads its own source**, so the bait paths in the tests are assembled from parts and a case asserts that neither `prose_lint.py` nor its test file carries a literal. That is the approach the file already takes for non-ASCII, which it writes as escapes for exactly this reason. ## Operational repositories are exempt, and told so An operational repository's runbook carries the literal path an operator types. That is the repository's own content rather than an agent quoting an environment it observed, which is the distinction the rule is about. The model is read from what the repository carries rather than from the hub registry, which a downstream checkout cannot see: `spec/files.json` declares `repo-config/operational/develop.json` for the operational model and `repo-config/develop.json` for the release one. The hub carries both, being the template for each, so it reads as release rather than exempting itself from a rule it authors. **The skip is announced on stderr**, because a rule that silently stops running reads as a rule that passed, which is the same failure #594 closed on the other side. ## Scope note Unlike every other prose rule, this one reads fenced blocks and configuration values rather than stripped prose. A transcript pasted from a terminal and a bind mount naming a real home are the exposure in its most consequential forms, and neither is a sentence. ## Verification - `python3 scripts/test_prose_lint.py` - 188 pass, up from 173. Of the 14 new cases, 10 fail against `develop` and 4 pass against both, holding the must-not-fire direction. - `python3 scripts/prose_lint.py . --check home-path` - clean across the tracked tree. - `python3 scripts/prose_lint.py . --summary` - 557 violations across 45 files, the #519 backlog figure unchanged, with `home-path` contributing none. Adding a rule to `DEFAULT_RULES` did not move the number. - `python3 spec/validate.py`, `python3 spec/audit.py --selftest`, `python3 scripts/repo_gate.py` - clean. - The blocking CI prose run - clean. ## Correction: this branch carries the TODO bookkeeping too An earlier revision of this description said the `TODO.md` edits were held back to avoid conflicting with #595. **That was wrong.** This branch was cut from #595's branch rather than from `develop`, so it already contained that commit, and the description claimed an absence a reader could disprove from the diff. The review then caught the real consequence: the cluster entry still promised a bare drive letter after the implementation dropped that shape. Correcting the bullet was the smaller half of the answer, since this is the pull request that ships the cluster, so `c6fca9e` applies step 9 and deletes it rather than leaving a corrected description of finished work. The disproved claim is recorded here and in the commit message, alongside the measurement that disproved it. **This makes #595 redundant**, since every line of it is already in this branch. Closing it as superseded is the maintainer's call rather than something this branch does. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Answers #597, filed from a downstream repository after an agent skipped a mandatory pre-pull-request gate while following every carried rule correctly. ## The failure A repository's `OPERATIONS.md` held a verification step CI structurally cannot perform, under a heading of that repository's own invention. The routing sentence in its `AGENTS.md` named the file by its most visible function, which was a post-merge one. An agent deciding how to verify a change before opening a pull request therefore had no reason to open the file, and skipped the one check that could have caught the regression. Nothing in that chain broke a rule, which is why it produced no signal. ## Why the existing rules do not reach it "Verification Discipline" is the natural home and does not cover it. Every bullet there assumes the agent already knows which gate to run: a gate that stops gating, a filter that matches less, a green check that is not evidence. This failure is upstream of all of them, because the gate was never located, so there was no green to distrust. ## What lands **The locating half, not the wording half.** Fixing the one repository's routing sentence leaves the next repository to reinvent the same shape, so the fix is a carried rule that points at a fleet-declared location. - `GOVERNANCE.md` "Verification Discipline" gains a bullet, placed first because the failure is upstream of the rest: a change's checks are read from what the repository declares before any is run, CI's coverage is not that list, and green is the precise signal that the unreachable half was skipped. It names why a document's own description cannot be relied on to find the check. - `spec/section-model.md` and `STANDUP.md` declare `Local Verification` as a sixth `OPERATIONS.md` heading, leading the file as the only pre-merge one. Both restatements of "those five headings" move with it. - This repository's `OPERATIONS.md` becomes the worked example. The gates block moves under the new heading, and the section states the two verifications a pull request runner here cannot perform, the live fleet audit and `configure.sh check`, each of which reads another repository over the API. `spec/audit.py --selftest` is all CI runs of the audit, against fixtures. - `TODO.md` indexes #597 under "Fleet Sweeps" rather than as complete, since every repository still owes the heading, riding the pending re-vendor visit. ## The gap this leaves, stated rather than hidden The audit reports nothing here today, because `OPERATIONS.md` is presence-checked only, so a repository using none of the declared headings passes. The heading check is #523's cluster, and until it ships the sweep is verified by reading each file rather than by a run. `TODO.md` records that. ## Verification Every gate in the runbook: the four self-test suites, `repo_gate.py`, `spec/validate.py`, both `prose_lint.py` invocations, `editorconfig-checker`, and markdownlint over 44 files with 0 issues. The live audit runs clean at 0 defect findings, its drift output being the already-recorded re-vendor backlog. One note on the run itself, since this change is about false cleans. `prose_lint.py . --diff origin/develop` reported 0 violations across **0 files**, because nothing was committed at that point, so the warn set was re-run against the five paths explicitly. It reports 14 `semicolon` findings in `GOVERNANCE.md`, identical to the count on the same file with the change stashed, so this adds none. ## Review round: an unrunnable command, in the section about unrunnable checks Round one flagged that `configure.sh check` is not invokable from the repository root, since the script lives at `repo-config/configure.sh`, so a reader following the instruction gets `command not found`. Valid, and pointed at a section whose whole subject is a check CI cannot run. Fixed in `b9f9640`, and swept as a class rather than as an instance. This branch adds three command references, and a second carried the same defect: `spec/audit.py --selftest` was missing the `python3` prefix CI actually invokes it with. Both now carry the path and the prefix the runbooks below use, and the sentence points at those runbooks for the argument lists rather than restating them. Round two against `b9f9640` generated no new comments. Worth recording from the loop itself: the `gh-write-guard` hook blocked the resolve mutation because the thread id was hand-typed from the previous query's output rather than re-captured live. That is the rule catching a real instance rather than a hypothetical one, and the mutation succeeded once the id came from a query in the same invocation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements the decision recorded on #580, which closed with the reply-and-resolve helper carried forward as its own `TODO.md` cluster and the dependency it waited on already met. ## Why a helper rather than a firmer rule A reply-and-resolve run by hand keeps failing the same way: a GitHub node id typed into a mutation. Three instances are on record. The third happened while preparing this cluster, when `gh-write-guard` refused a `resolveReviewThread` mutation carrying an id hand-typed out of the previous query's output, roughly an hour after the same session quoted that exact rule in a pull request body. The rule is stated in `GOVERNANCE.md` and again in the host-level guidance, and the agents that broke it had read both. That says the shape of the operation fails rather than the reader's knowledge of the rule, so this changes the shape. ## What `reply` does ```sh python3 scripts/pr_review.py reply <N> --repo <owner>/<name> \ --match "<words from the finding>" --body "<answer>" --resolve ``` It queries the thread id itself and passes it straight to the mutation. There is deliberately no argument an id fits in, and a case asserts the id each mutation carries is the one that run just read. - **Selects on the finding's own words, not a line number.** A fix push moves the line, which is how three replies once posted against nothing while the resolves still succeeded, closing the threads carrying no answer. Matching is case-insensitive, since the text is quoted back out of a digest by a reader. - **Every failure stops rather than falls back**, because each alternative closes a finding while leaving it unanswered. No match exits `60` and prints the open threads, since a no-match and an already-answered thread read identically from here. More than one match exits `61` and prints both candidates rather than taking the first. A reply whose response carries no `url`, or whose body came back empty, exits `62` **without** resolving. A resolve that does not confirm exits `63`, with the reply already posted. - **`--resolve` is opt-in**, since a decline is resolved only once its evidence is in the thread. - **A target under another owner exits `64` before anything is read.** ## The trade this makes, stated rather than glossed The script carried `test_no_mutation_reaches_this_script`, locking it read-only so that mutations stayed visible to the `gh-write-guard` PreToolUse hook. This reverses that, so the test is **narrowed rather than deleted**: every other state-changing call is still banned tree-wide and the mutation-document count is pinned at exactly two, so a third arriving is a write nobody reviewed as one. The hook genuinely cannot see a write this script performs, since it sees `python3 pr_review.py reply` and no `gh` write. That is a real loss of a second pair of eyes. It is taken because what the hook guards against there is a fabricated id, and this removes that at the source instead of catching it afterwards. The guard's other rule is re-implemented rather than assumed. `in_scope` refuses a target under an owner other than this checkout's, and it takes **no `GH_WRITE_GUARD_ALLOW` escape**: a grant this process can be handed is one the caller sets on the command that runs it, and a grant the caller writes for itself is not a grant. The cross-owner case goes through the runbook's explicit `gh` path, where the hook reads the maintainer's grant from the session instead. It is honest in the docs that this stops a mistake rather than a determined caller. ## Corrections carried in this change - **The cluster's cost line was wrong.** `.github/copilot-instructions.md` is carried at `intent` fidelity per `spec/files.json`, so the runbook edit is not hub-only. A sixth `Detail` under the Fleet Sweeps re-vendor entry records it, noting a repo still on the old copy is not broken, since the mutations that copy documents still work. - **`OPERATIONS.md` scoped the `gh pr edit` breakage to `--base`**, narrower than this repo's own runbook, which states the same failure against `--title/--body`. The failure is in the mutation the command builds rather than the field asked for. Widened, with the runbook cross-referenced. Not exercised live here, since `gh pr edit` is a write. - **`TODO.md` carried a clause naming "The Prose Gate Scope Floor"**, a cluster that shipped and was deleted; the phrase appeared nowhere else in the tree. `GOVERNANCE.md` is deliberately untouched. It owns the review contract and routes mechanics to `.github/copilot-instructions.md`, so naming the helper there would add a verbatim re-vendor for nothing. ## Verification Run from this checkout on the branch head: - `python3 scripts/test_pr_review.py` — 103 tests, OK. The suite gains cases for selection without an id, cursor-followed pagination, ambiguity and no-match refusal, reply confirmation before resolve, owner scope, and argument validation. - `python3 scripts/test_prose_lint.py` (190), `python3 scripts/test_repo_gate.py` (23), `python3 spec/audit.py --selftest`, `python3 spec/validate.py`, `python3 scripts/repo_gate.py` — all pass. - `python3 scripts/prose_lint.py . --check charset --check dupword --check spelling` and `--diff origin/develop --summary` — both clean. - markdownlint and `editorconfig-checker` over the tree — clean. - `python3 scripts/pr_review.py status 588 --repo ptr727/ProjectTemplate` — a live read confirming the transport refactor, which routes `gql` through a shared `gh_graphql` that also checks the `errors` key rather than reading the null it leaves. **Not verified live:** every success path of `reply` posts a public comment, so it has not been run against a real pull request. The logic is covered by mocked cases only, and the two refusal paths that make no network call were exercised directly. This pull request's own review round is the natural first live exercise. Closes the `TODO.md` cluster "The Reply-and-Resolve Helper", deleted here per the file's own rule 9. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes part of #519: `catalog/snippets/` is now at zero prose findings. The whole tree goes from **557 across 45 files** to **373 across 26**. Snippets led because a non-conformant snippet seeds its violations into every repo that adopts it, and the downstream repo is then flagged for content it was handed. That is a seeding-risk ordering rather than a size one: the three biggest files in the tree are not snippets at all. ## The exemptions were fixed before the corpus The findings were read as a hypothesis, not a work list. `prose_lint`'s comment rules ban a construction rather than a detectable subset of it, so the exemptions carry the whole burden of encoding the legitimate uses, and a stale one hands out a work list that damages correct files. Three of the 184 snippet findings were exemption misses. **A comment body that is one token closing on a colon is a key or a heading.** `# ignore:` heading a commented-out block in `codecov.yml` is disabled configuration, so the capitalization `comment-case` asked for would corrupt the key a repo uncomments. Measured tree-wide, the shape matches exactly two bodies, both a key or a heading. The broader reading of "a body with no whitespace at all" covers 99 and sweeps in every reference URI and `v3.2.0` marker with them, which is why the narrow shape was taken. **A label opening a definition names the thing being defined.** `# publish - 'true' when ...` documents an output named `publish`, so capitalizing it renames what the workflow declares. This is the comment spelling of the `- **Label** - text` construct the dash rule already exempts. The first design here was wrong, and only the corpus said so. A bare `^word - ` reading matches six comment bodies, but two of them are wrapped continuations whose dash is parenthetical, which is precisely the construction the dash rule exists to catch. Both continuations follow a line that does not end a sentence, so scoping the exemption to a line that *opens* a definition excludes them structurally. The corpus chose the unit; a test locks that case. **A definition list keyed on a code span needed no checker change at all.** `- `latest` - the current stable release.` is flagged because `LABEL_DASH` requires a `**bold**` label. Following the finding's own message ("spaced hyphen -> a comma, two sentences, or parentheses") would have destroyed the list. `- **`latest`** - text` is *already* exempt and is the form `GOVERNANCE.md` and `docs/devcontainer.md` use, so bolding the label is the fix and the code span survives inside it. 17 such lines exist tree-wide; the 2 in snippets are fixed here. ## Verdicts diffed in both directions, before any prose was touched Whole-tree, old against new: - **Went silent:** exactly the three misses above. - **Newly reported:** nothing. Five tests lock the two new arms, including `test_a_continuation_dash_is_not_read_as_a_label`, which is the design that was rejected. Verified against the pre-change checker in an isolated copy: the new tests fail there and pass here. ## Cost, stated rather than left to be found The label exemption costs a detection: a comment that genuinely opens a sentence on one word followed by a spaced dash is no longer read as a lowercase opening. `scripts/README.md` records this alongside both new exemptions. `comment-case` still reports a comment opening on a lowercase identifier (`# isinstance guard first: ...`, `# consumerModel has no defaults fallback ...`). That is 23 of the remaining findings and the rule intends them restructured rather than exempted, so nothing here widens to cover them. ## Two open questions on #519, answered Neither answer was recorded on the issue, and both are now in `TODO.md`: 1. Is `reports/**` exempt? **Yes**, as a generated tree. 2. Are snippets higher priority? **Yes**, which is this pull request's whole ordering. The issue's own headline numbers (668 total, 119 hand-authored at `69688ec`) are stale, computed before `comment-wrap`/`comment-case` joined `DEFAULT_RULES` and before `reports/` became exempt. ## Verification Every gate in `OPERATIONS.md` "Run the gates the way CI runs them", from this checkout: - `test_prose_lint.py` 195 tests OK, `test_repo_gate.py` 23 OK, `test_pr_review.py` 103 OK - `spec/audit.py --selftest` SELFTEST PASS, `spec/validate.py` OK, `repo_gate.py` 0 issues - Gating prose run (`charset`/`dupword`/`spelling`) exits 0 - `editorconfig-checker` clean, `jq empty` clean over every JSON glob - `--diff origin/develop` exits 0, so nothing this branch touches carries a finding Line endings were checked explicitly rather than assumed: no file changed EOL style and none has mixed endings. The `git diff --check` trailing-whitespace warnings are pre-existing CR in CRLF files, which `text: unset` leaves alone. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The write-guard hook read a newline as whitespace, so it disappeared when tokenizing and every token on a later line became an argument of the first line's `git push`. A push and a `gh pr create` written as two lines resolved to seven push targets rather than one, `develop` among them, and the hook denied the push as a direct push to a protected branch while the push targeted an ordinary feature branch. The direction is over-blocking, so this was a usability defect rather than a safety hole. It is worth fixing because the denial states a reason that is not true of the command, and a safety hook that cries wolf is one that stops being read. A newline ends a command exactly as `;` and `&&` already did, so it is an operator character rather than whitespace. The operator set is stated once as a string, `shlex` takes it in that form, and the newline is removed from `shlex`'s whitespace. The degraded path, which only unbalanced quoting reaches, splits lines itself so it cannot lose the separator either. Backslash-newline continuations are folded to a space in `classify` before any of this runs, so a continued command still reads as one command. Six self-test cases lock it. Against the pre-fix code the incident case regresses and the others pass unchanged, which is what makes them guardrails: a push on a later line, a second push on the next line, a continuation, a newline inside a quoted body, and a push on the second line of a command whose quoting cannot be parsed. Run over 179 commands, being every existing case, each again with a trailing newline, and eleven realistic multi-line shapes, the two versions differ in five decisions, all deny to allow, all the false-positive shape, with no allow becoming deny. The self-test ran nowhere but at install time, where a regression surfaces as a broken machine rather than a failed check, so CI now runs it beside the audit self-test it matches: offline, standard library only. OPERATIONS.md carries the same line because that list mirrors what CI runs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gates of the same shape, each confirming that a reference in a
committed artifact still resolves. Both failures are silent, and both
are caught by a reviewer or not at all. This clears the `Two Checks That
Read What a Claim Points At` cluster from `TODO.md`, whose two `Checked`
anchors were re-verified against `develop` at `dbd1cdc` before anything
was written: `check_sha_pin` still matched `^[0-9a-f]{40}$` and never
read the ref, and no subcommand read a description at all.
## `sha-pin` resolves the pin, not just its shape
Forty hex characters is a format any fabricated string satisfies, and an
agent hand-writing a plausible SHA into a workflow is a failure this
repo has seen. Resolving also catches the neighboring case, a pin whose
commit was reachable only from a branch since squashed and deleted,
which breaks a downstream gate long after the change that caused it. The
`gh-write-guard` hook cannot cover either, since it watches Bash and an
editor tool writing the same string into a file never reaches it.
Resolution is scoped to the scanned repository's own owner, which is
where the fleet's actions live and where that decay comes from. A
third-party tag is stable by comparison, and reading one would make
every local run of the gate depend on a stranger's repository answering.
**The cost is real and is stated rather than hidden**: a fabricated pin
on a third-party action is still only shape-checked. So every run prints
what the pass actually covered, because a scope that resolves nothing
prints the same `0 issue(s)` a full sweep does. On the hub that is zero
today, since all 45 pinned refs here are under other owners, and the
`ptr727` pins live in the downstream repos that consume `prose-gate`,
reached by running this gate from a hub checkout with `--root`.
A pin is a finding only where GitHub **answered** absent, meaning a 404
or a 422. An offline host, a missing `gh`, a rate limit and a narrow
token all report as unread and leave the pin on its shape, so the gate
stays usable with no network instead of failing a correct tree. A 404 is
confirmed against the repository itself before it becomes a finding,
since an absent commit and an unreadable repository are the same answer
from here and a repository-scoped CI token is narrower than the fleet.
## `pr_review.py claims` reads a description against its own branch
Three stale descriptions in one session generated six review findings
between them.
**Its first design scanned the body for bare SHAs, and the corpus
rejected that outright.** Over the 25 most recent merged pull requests
it raised four findings and every one was correct prose: a `develop`
commit named as history, a SHA inside a pasted digest, and two commits
in another repository written without a URL. Nothing in the *shape* of a
bare SHA separates those from a real claim, and separating them by
meaning is the similarity heuristic `spec/section-model.md` already
rules out, which is the same reason the entry put prose claims out of
scope. A path arm measured on the same corpus is worse, flagging 54 of
215 backticked candidates, nearly all bare basenames, `origin/develop`,
and other repositories.
What survives is the verb. A commit counts only where the body says it
was fixed, landed, shipped, added, introduced, corrected, resolved,
carried or amended in it. That alternation raises exactly one reference
over the same 25, and that one is true. It is an **inclusion** list, so
a phrasing nobody thought of costs a detection rather than inventing a
finding, and a claimed SHA still carries a digit as a backstop on the
list growing later.
A commit passes on **ancestry**, so a description may cite one it
inherited from the base branch, and the two failing shapes are named
apart: a commit the repository does not carry at all, and one this head
does not descend from. The `uses:` refs are matched against the whole
tree at head, as one archive rather than a request per file, because
this repo carries `uses:` lines in snippets and docs as well as under
`.github/`. Undecided is a third answer here too, and where every
reference is undecided the run exits `71` rather than printing the
`stale=0` a clean read prints.
## Evidence
- Both new gates run clean locally, and the whole suite passes: `python3
-m unittest discover -s scripts` reports 367 tests OK, up from 340.
- The resolver was exercised live before the tests were written: a real
commit resolves, a fabricated 40-hex does not, and an unreadable
repository reads as unknown rather than as a finding.
- `claims` run over the 25 most recent merged pull requests reports
`stale=0` on every one, with one commit reference actually read and
checked.
- No case reaches the network. The GitHub answers are replaced by a
table keyed on the API path, so a case proves the reading rather than
the fleet's current state.
## TODO.md
The cluster is deleted per the file's own step 9. The one thing it
surfaced that this does not carry becomes a new `decision` cluster:
whether the hub should consume its own `prose-gate` action, so its pins
have something under this owner to resolve against. That changes what a
workflow runs rather than what a gate reports, so it is the maintainer's
call rather than this pull request's.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Clears the `A Home for a Disproved Finding` cluster from `TODO.md`. Its `Checked` anchor was re-verified against `develop` at `756a53e` before anything was written: the file still says the reviewer is sometimes factually wrong, still requires a decline to carry evidence, and still keeps its list of known non-working request paths, with nowhere to put the proof itself. ## The gap A decline that carries proof proves something about this tree, and [GOVERNANCE.md "Every Finding Ends in an Action"](https://github.com/ptr727/ProjectTemplate/blob/develop/GOVERNANCE.md#every-finding-ends-in-an-action) is right that the thread is where it belongs while the pull request is open. Afterwards the thread is the wrong place. The pull request merges, the next round starts with no memory of the last, and the second occurrence of the same claim reaches a maintainer with no way to tell it from a first. `Disproved Claims` is a new `###` under the runbook, so the three declared `##` sections in `spec/files.json` are unchanged. An entry names the claim, what was run or read to disprove it, the revision it was proved against, and what ends it. **It is deliberately not a list to append to**: an entry outliving the code it was proved against becomes a reason not to check, which is strictly worse than proving the claim again, so an entry whose subject moves is deleted by the change that moves it rather than edited to look current. Two guards ride with it. The record answers a repeated claim and never dismisses a new one, so a finding is judged on its merits first and matched second, and a reply carries the proof re-read rather than a pointer to a file the reviewer cannot open. And the entries are this repository's own, so a repository holding a copy carries the shape and deletes an entry whose subject it does not carry. ## The three worked examples **`keys_unsorted` requires jq 1.6.** A suppressed finding on #555 against the normalizer in `repo-config/configure.sh`, reasoning by analogy from the `walk/1` failure #553 fixed. Re-run in this session rather than quoted from the thread, on `jq-1.5-1-a5b5cbe` in `ubuntu:18.04`, the build that reproduces `walk/1`: ```console jq-1.5-1-a5b5cbe $ echo '{"b":1,"a":2}' | jq -c 'keys_unsorted' ["b","a"] $ echo '{"b":1}' | jq -c 'walk(.)' jq: error: walk/1 is not defined at <top-level>, line 1: jq: 1 compile error ``` **The write-guard's fallback parse, from #601.** Declined on the ground that the arm cannot execute, since `punctuation_chars` arrived in Python 3.6, the module uses f-strings throughout, and `install.py` refuses below 3.7. That is exactly the kind of disproof that expires, which is why the entry names the floor as what ends it. It also records that the finding earned a test case rather than a change, since only `ValueError` from unbalanced quoting reaches that path in practice and nothing covered it. **The bare-SHA design, from #602.** This one came from this repository's own backlog rather than from a reviewer, and it is here because a rejected method costs the same to re-propose as a declined finding costs to re-derive, while a backlog has a place for a claim the tree contradicts and none for a method a measurement rejects. Over the 25 most recent merged pull requests the bare-SHA arm raised four references and all four were correct prose, and a path arm flagged 54 of 215 backticked candidates. Both #601 and #602 are folded under disposition **Amends "Record what was tested and against which revision, and delete an entry whose subject changes."** They land as the second and third worked examples in the shipped section rather than as `Settled` bullets, because the entry ships in the same change. ## Verification - `python3 scripts/prose_lint.py . --diff develop` clean, `markdownlint-cli2` clean on both files. - `python3 -m unittest discover -s scripts` reports 372 tests OK and `python3 spec/audit.py --selftest` passes, neither touched by this change but both run because the file is carried. - Every factual claim in the new section was read against the tree at `756a53e`: the `keys_unsorted` call in `repo-config/configure.sh`, `_git_subcommand_arglists` and `_PUNCTUATION_CHARS` in `gh-write-guard.py`, the `(3, 7)` floor in `install.py`, and the verb alternation in `pr_review.py`. ## TODO.md The cluster is deleted per the file's own step 9. What this change does not carry becomes a new `ready` cluster: `GOVERNANCE.md` outcome 2 ends at the thread and nothing agent-agnostic points at the record, so an agent that never opens the provider runbook posts a decline and has nowhere to put the proof. That is a byte-locked carried section and a fleet re-vendor, which is why it is a separate change rather than a second surface in this one. ## Downstream `.github/copilot-instructions.md` is carried `whole` at `intent` with `appliesTo: "*"`, so this joins the re-vendor the ledger already lists. A repository taking it carries the section and its rules, and starts its own entries empty. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Takes the `The Prose Content Backlog` cluster from `TODO.md`. Its `Checked` anchor was re-verified against `develop` at `c64e3e0` before anything was written: `prose_lint.py --summary` reported the same 373 violations across 26 files, `catalog/snippets` the same 0, and the three largest files the same 99, 52 and 41. ## The exemption came first, and it was wrong `RUN_ON` read the closing dot of an ellipsis as a sentence terminator, so `scripts/repo_gate.py:204` was reported as two sentences: ```text # .editorconfig: [glob] ... end_of_line = lf ``` That is one schematic line, and the split the rule asked for would have broken the fragment the comment exists to show. Fixing the prose first would have damaged a correct comment on the checker's say-so, which is the failure this repository has now recorded six times. The guard is that a dot preceded by a dot never terminates, it carries two tests, and the verdict diff was run in both directions: exactly one finding disappeared and none appeared. ## The batch is the comment surface, all of it The backlog splits by surface rather than by file. `comment-wrap` and `comment-case` lived entirely in non-Markdown comments, `dash` and `semicolon` live entirely in Markdown prose, so the comment half is one reviewable change that owes no re-vendor: of the eleven files, four are workflows carried at `interface` fidelity, where the contract is job keys and tokens rather than comments, and the rest are hub-only. 241 findings are cleared by restructuring, never by widening an exemption. A sentence per comment line, and a comment that opened on a lowercase identifier reworded to open on a word, which is what the rule asks for and what the snippets sweep established. The tree goes from 373 across 26 files to 131 across 15, all of them `dash` and `semicolon` in Markdown. Six of those files are carried, so the next batch splits again at that line: the carried half rewrites byte-locked sections and owes a re-vendor, the hub-only half owes nothing. ## Both rules now gate A rule swept clean but left warn-only regresses on the next edit with nothing reporting it, so `comment-wrap` and `comment-case` move to the gating CI step and out of the warn-only one. Every claim about which rules gate moved with them, in `OPERATIONS.md`, `scripts/README.md` and the two workflow comments, including the one in `scripts/README.md` saying gating still needs `--diff` because the tree carries several hundred of them. ## One gap found while counting, not fixed here `home-path` is in `DEFAULT_RULES` and is named by neither CI list, so the pattern-detectable half of the representative-data rule runs on every bare local run and gates nothing in CI. It is clean tree-wide, so it is a hole rather than a backlog. It is recorded in `OPERATIONS.md` beside the two gaps already named there and in `TODO.md`, rather than closed here, because gating a third rule is its own decision. The write guard and both installer wrappers are among the eleven files, so every installed copy is now behind the hub by that much. The divergence is comment-only, the self-test passes unchanged, and `TODO.md` carries it under the host rollout as a re-run at the next visit. Verified with the full `OPERATIONS.md` local list: five self-tests, the repo gate, both prose invocations, `jq`, `spec/validate.py`, editorconfig-checker, markdownlint and actionlint. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) Takes the next batch of the `The Prose Content Backlog` cluster from [`TODO.md`](./TODO.md), following [#600](#600) (snippets) and [#604](#604) (comments). Part of [#519](#519). ## The anchor was re-verified first `develop` at `c9c92dd`, where `python3 scripts/prose_lint.py --summary` reports **131 violations across 15 files**. That is the figure the comment batch left, and merging that batch made it the anchor's own rather than a branch's, exactly as the cluster entry predicted. ## Scope, and a correction to it The batch is the **hub-only Markdown files**: `docs/`, `spec/*.md` and `catalog/README.md`. That is **90 findings across 9 files** (79 `dash`, 11 `semicolon`), not the ~104 the batch was scoped at. The carried six hold 41, and 90 + 41 = 131. The 6 carried files are left for a later batch, since they rewrite byte-locked sections and owe a fleet re-vendor while these owe nothing. Every `dash` finding in the tree turned out to be hub-only, so what the carried half now holds is **41 semicolons and no dash at all**. | File | Findings | | --- | ---: | | `docs/devcontainer.md` | 22 | | `docs/ssh-signing.md` | 17 | | `spec/type-model.md` | 17 | | `spec/fidelity-model.md` | 8 | | `docs/repo-config-carry.md` | 7 | | `spec/scope-model.md` | 7 | | `catalog/README.md` | 6 | | `docs/token-cost.md` | 5 | | `spec/readme-structure.md` | 1 | ## The exemption came first, and this time it was right `LABEL_DASH` exempts `- **Label** - text` as a definition separator that is structurally a colon. It reaches neither a code-span label nor a paragraph-leading one, and both shapes flagged here. Widening it was the obvious move, and the corpus rejected it twice. **A code-span definition list** is already spelled with a colon in five files, against four dash-spelled lines in one (`catalog/README.md`): ```text STANDUP.md:91 - `ARCHITECTURE.md`: how a code repo is built spec/section-model.md:68 - `ARCHITECTURE.md`: how a code repo is built scripts/README.md:81 - `sha-pin`: every workflow `uses:` naming repo-config/README.md:6 - `operational/develop.json`: the `develop` CODESTYLE.md:79 - `.NET Build`: Build with diagnostic ``` **A paragraph-leading label** is already spelled `**Label.** Sentence`, against nine dash-spelled lines confined to two troubleshooting sections. So both were a file that had not adopted the tree's own convention rather than a construct the rule fails to describe. **The checker is unchanged by this batch.** That is the same verdict the comment batch reached on a comment opening on a lowercase identifier. A bulleted list terminating its items with semicolons appeared in exactly one place tree-wide, `spec/type-model.md:24-25`, and its own last item already ended in a period. It was punctuation to make consistent rather than the list construction the semicolon exemption protects, which a line-based checker could not see across bullets in any case. ## One adjacent fix, flagged because it widens scope Rewriting `docs/devcontainer.md` and `docs/ssh-signing.md` with a script rather than an editor took both from CRLF to LF wholesale. Every prose and Markdown gate stayed green, and `editorconfig-checker` was the only one that reported it. `OPERATIONS.md` scoped that instruction to a *new* file, so it is widened here to a scripted rewrite of an existing one. Hub-only, `presence` fidelity, no re-vendor owed. Happy to split it out if it does not belong in this batch. ## Verification Run from the repository root: ```text prose_lint.py gating list exit 0 prose_lint.py backlog list 131/15 -> 41/6, the carried six exactly test_prose_lint.py 198 tests OK test_repo_gate.py, test_pr_review.py OK audit.py --selftest, gh-write-guard SELFTEST PASS repo_gate.py, validate.py, jq clean markdownlint-cli2 (44 files) 0 issues editorconfig-checker 0 errors ``` Every prose file changed line for line, with no line-count change in any of the 9, and the full word-diff was read for meaning drift. `TODO.md` carries the updated `Checked` anchor and four new `Settled` lines: the batch figures, the two rejected exemptions, the one-off bullet list, and the CRLF gap. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…606) Takes the last batch of the `The Prose Content Backlog` cluster from [`TODO.md`](./TODO.md), following [#600](#600) (snippets), [#604](#604) (comments) and [#605](#605) (hub-only Markdown). Closes [#519](#519) on the prose, subject to the sweep below. ## The anchor was re-verified first `develop` at `d791930`, where `python3 scripts/prose_lint.py --summary` reports **41 violations across 6 files**, all `semicolon`. That is the figure #605 predicted, and merging it made the figure develop's own rather than a branch's. | File | Findings | Fidelity | | --- | ---: | --- | | `GOVERNANCE.md` | 14 | `intent`, 5 sections `verbatim` | | `WORKFLOW.md` | 14 | `intent`, whole | | `CODESTYLE.md` | 6 | `intent`, whole | | `.github/copilot-instructions.md` | 4 | `intent`, whole | | `repo-config/README.md` | 2 | `intent`, whole | | `HISTORY.md` | 1 | `presence` | **The tree is now at 0 across 0**, from 557 across 45 when the backlog opened. ## The exemption came first, and left the checker alone again Two candidates, both rejected by the corpus. That is the third batch running where the pass ends in "do not touch the checker". **The `*Prevents: a; b.*` tail.** Flagged on 4 lines because the list exemption requires the items to carry commas. The tree already spells the same construct with a comma on **5** lines against **6** semicolon-spelled, so it is a convention half-adopted rather than a construct the rule fails to describe. Two of the six (`D1.6`, `D5.6`) keep their semicolons untouched, because their items *do* carry commas — the rule working, not an exception to it. **A two-example parenthetical**, `(A does X; B does Y)`. The tree already spells this with a comma where the items carry none, `GOVERNANCE.md` "No-op republish guarantee" being the case in point. ## Only 14 of the 41 are actually byte-locked The cluster entry said the carried half "rewrites byte-locked sections". That is true of `GOVERNANCE.md` and of nothing else, which changes what the sweep owes: - **`verbatim`, hash-detected.** All 14 `GOVERNANCE.md` findings sit in five `verbatim` sections: Branching Model, Release Model, Documentation Style Conventions, PR Review Etiquette, Workflow YAML Conventions. Every downstream copy is now byte-mismatched and `spec/audit.py` classifies it **stale** (matches a past hub revision), which is the correct disposition and reaches the fleet without anyone filing anything. - **`intent`, detected by nothing.** `WORKFLOW.md`, `CODESTYLE.md`, `.github/copilot-instructions.md` and `repo-config/README.md` are judged by meaning, so a punctuation-only edit produces no hash and therefore no audit finding at all. These are the half that would be silently missed, which is why they are written down rather than left to the run. - **`presence`.** `HISTORY.md` is each repo's own changelog. Its one fix owes nothing downstream. No rule changed meaning anywhere, so the re-vendor is a **hash refresh rather than a propagation**. A repo still holding the old copy is correct on the rule and wrong on the bytes. The Fleet Sweeps entry "Re-vendor the changed `verbatim` content" grows from **five files to seven** and carries both halves as `Detail` lines. It does not trigger a dedicated sweep — Blog remains the pilot. ## What #519 got wrong, and why it is worth recording The issue states that the governance files were clean and that this was "not a carry problem". That was true of the checker of the day and false of the tree: | Content | Checker | Result | | --- | --- | ---: | | the six files at `69688ec` | that commit's own `prose_lint.py` | **0** | | the six files at `69688ec` | today's `prose_lint.py` | **38** | `semicolon` was in `DEFAULT_RULES` the whole time. What changed is the **unit** the list exemption is judged over: scoping it to a sentence rather than a whole bullet accounts for **37 of the 38**, because a colon anywhere ahead of the first semicolon had been exempting every semicolon after it, however plainly one joined two clauses. So the carry problem was real from the start and invisible — the stale-exemption hazard running in the loose direction, on the most-carried files in the fleet. ## One fix beyond the 41 `GOVERNANCE.md` "Orchestration vs. build" carried a splice the exemption excused only because the **bold label** on that sentence happened to contain a comma (`...globs it; do not switch a single-target repo...`). Same line as a flagged finding, so it is fixed inline rather than stepped around. That is 42 semicolons removed, not 41. ## Verification Run from the repository root: ```text prose_lint.py --summary 41/6 -> 0/0 scripts/ unittest (375 tests) OK spec/audit.py --selftest SELFTEST PASS spec/validate.py 22 cataloged, 0 backlog scripts/repo_gate.py eol 0, sha-pin 0 markdownlint-cli2 (44 files) 0 issues cspell (README.md, HISTORY.md) 0 issues editorconfig-checker 0 on tracked files ``` Every edit is a **within-line** replacement, so all seven CRLF files stayed CRLF — verified by an `\n` versus `\r\n` count per file, not by `file`. The full word-diff was read for meaning drift; the change is 31 lines, 31 insertions and 31 deletions. `editorconfig-checker` reports 221 errors, every one of them under `.artifacts/` or `Tests/obj/` — untracked pre-repurpose build leftovers in the working tree, not tracked files. Pre-existing and untouched here. ## `TODO.md` - The `The Prose Content Backlog` cluster is deleted, per selection rule 9. - [#519](#519) moves to "Verified Complete, Awaiting Close" with both of its questions answered and the checker-versus-tree finding as closing evidence. - The Fleet Sweeps re-vendor entry grows to seven files and gains the two `Detail` lines above. - Four link reference definitions orphaned by the cluster deletion are dropped. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #607. ## What was wrong A Copilot review body states how many of the pull request's changed files it read, and nothing parsed that line. A round that read part of the diff carries the correct `commit.oid`, raises no inline threads, and reports "generated no comments", so it is the clean pass byte for byte in everything the loop checks, and `status` printed `review_on_head=yes` over it and exited `0`. Measured over **332 Copilot review bodies** on this repository, five rounds across three pull requests reported reading fewer files than were changed, and all three merged. #592 is the sharpest: three changed files, one never read, across *both* rounds, both reporting no comments. ## What changed, and why it is wider than the issue asked Partial coverage is the third instance of a shape this script already answers twice, and the generalization is the point. Every reader here keys on a structural marker, so a marker that changes spelling is a section the reader stops finding and reports as absent. All three failures on record have that shape: | Drift | Result | | --- | --- | | Suppressed heading reworded | `suppressed=0` over a body carrying findings | | Suppressed section moved inside another wrapper | `suppressed=0` again | | Coverage line never parsed | a partial round reported as a covered head | Each was found by the maintainer after it had landed, rather than by the gate. So the digest now vets the reviewer's output as a whole and **fails closed**: - `coverage=full` / `PARTIAL` / `unstated`, with exit **42** on a partial round. - `shapes=ok` / `UNRECOGNIZED`, with exit **43** on any heading, `<summary>`, metadata label, coverage wording or reviewer login the script has no vetted spelling for. It outranks 42, because a reader that does not understand the output cannot be believed about what it read of the diff. The `43` message states the remedy in two parts: **file an issue on the repository hosting the reader**, quoting the body the shape came from, and **the merge decision is the maintainer's**. An unrecognized shape does not say the pull request is bad, only that nothing here can vouch for the review of it. ## The inventory is measured, not imagined With fenced blocks dropped and text reduced to ASCII, all 332 bodies reduce to **7 headings, 6 `<summary>` texts and 3 metadata labels**, and every body carries at least one. Counts normalize to `(N)` and the verdict headings' colored circle is dropped before comparing, since both change on every review without the section changing, and dropping the emoji is also what keeps the source inside the charset rule. Two exemptions, both required by the corpus: - **A body stating no coverage** reads as `unstated`, never as pass or failure. 28 of the 332 are an overview and a change list, that shape is current and interleaves with the counted one, and one pull request carries both across its two rounds. Failing on it would cry wolf on about one review in twelve. - **A refusal** is exempt, being a bare paragraph by design and already classified. The exemption is the pattern rather than a carve-out, so a refusal reworded stops being exempt and blocks, which is the refusal check's own failure mode caught one rewording later. The quietest reading is the reviewer **login**: a rename leaves every filter here matching nothing, so a review that landed reads as `rounds=0` and a wait polls out its timeout against it. ## Evidence Both readers were swept over the full corpus before this was written, and over it again after: - Shape inventory: **0** items raised across all 332 bodies, and **0** across every review and comment author on those 120 pull requests (`copilot-pull-request-reviewer` and `ptr727`). - Coverage: `full 299 / unstated 28 / partial 5`, the five being exactly #476 (x2), #479 and #592 (x2). - Live: #592 reports `coverage=PARTIAL shapes=ok` and exits `42`. #595, #604 and #606 report `shapes=ok` and exit `0`. 166 tests pass, up from 132. The two fixtures the issue named as unasserted filler are promoted to assertions, and a case reads the vetted coverage spellings out of the runbook and hands them to the script's own parser, so the pair fails in both directions on drift. The old fixtures crafted review bodies with no heading, which no real body has, so they were made realistic rather than the check loosened. ## Also carried - `.github/copilot-instructions.md` - the verify step checked `commit.oid` only, which is what this shows to be insufficient, plus a new section stating that an unrecognized shape blocks and earns an issue. - `GOVERNANCE.md` - merge-gate precondition 4, and the "all four preconditions" restatement swept to five. - `scripts/README.md` - both readings. - `TODO.md` - a `measure` cluster to survey whether GitHub publishes anything structured about a Copilot review, since this whole design rests on prose being the only surface. Introspection says it is: `PullRequestReview` exposes `body`, `bodyText` and `bodyHTML` and no field naming a finding or a file count, and the only Copilot-named GraphQL types configure review-on-push in a ruleset. ## The accepted cost The shape scan reads every round rather than the head's, so once Copilot changes format, every open pull request blocks at once until the inventory is updated. That is deliberate and the maintainer's call: it is well balanced against a reviewer silently missing a raised finding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Promotes the current develop branch state to main, carrying forward the completed prose/backlog cleanups and the tightened “fail-closed” review-loop and gating mechanics so main becomes the new baseline for downstream repos and the fleet audit tooling.
Changes:
- Updates governance/spec/docs to incorporate “Local Verification” as the pre-merge operations entry point and to reflect the latest review-loop/gating contract.
- Enhances
scripts/repo_gate.pySHA-pin checking by optionally resolving pins against GitHub (scoped to the scanned repo’s owner), with accompanying unit tests. - Refreshes catalog snippets and workflow/doc comments to align with current prose/style rules and clarified operational guidance.
Reviewed changes
Copilot reviewed 61 out of 62 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| STANDUP.md | Updates baseline repo standup guidance; adds/clarifies OPERATIONS.md heading set and rationale. |
| spec/type-model.md | Prose clarification and consistency updates in the type model doc. |
| spec/section-model.md | Updates destinations/requirements text, including OPERATIONS.md headings and “Local Verification” rationale. |
| spec/scope-model.md | Prose clarifications for rule scoping and selector semantics. |
| spec/readme-structure.md | Adjusts TODO.md minimum-shape guidance to match grouped-form reality. |
| spec/fidelity-model.md | Prose edits clarifying fidelity/normalization semantics. |
| spec/fidelity_honesty.py | Comment/documentation clarifications; no functional change indicated in the diff. |
| scripts/test_repo_gate.py | Adds comprehensive tests for SHA-pin resolution behavior and gh error classification. |
| scripts/repo_gate.py | Adds owner detection and GitHub-backed pin resolvability checks plus note-reporting plumbing. |
| reports/divergences.md | Regenerates/updates divergences report content to reflect current dispositions and gaps. |
| repo-config/README.md | Prose clarification about secrets/publishing mechanisms. |
| README.md | Adds “term definitions” section and missing link references for hub/fleet concepts. |
| OPERATIONS.md | Adds “Local Verification” section and updates local gate invocations and known gh limitations. |
| host-setup/agent-safety/README.md | Updates host safety block descriptions and clarifies what is hook-enforced vs prose-only. |
| host-setup/agent-safety/install.sh | Refines wrapper comments; no logic changes. |
| host-setup/agent-safety/install.ps1 | Refines wrapper comments; no logic changes. |
| host-setup/agent-safety/claude-md-safety.md | Renames/expands safety heading and adds checkout-boundary guidance. |
| host-setup/agent-safety/claude-md-fleet.md | Clarifies “hub” definition and adds explicit “own checkout” reach rule. |
| HISTORY.md | Minor prose edit for clarity in release history. |
| docs/token-cost.md | Prose edits for clarity/structure. |
| docs/ssh-signing.md | Prose edits for clarity/structure. |
| docs/repo-config-carry.md | Prose edits and clarifications about carry/apply/regen procedure. |
| docs/devcontainer.md | Prose edits for clarity/structure. |
| CODESTYLE.md | Prose edits for clarity/structure in style guidance. |
| catalog/snippets/workflows/run-periodic-codegen-pull-request.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/README.md | Prose edits in snippet overview/mapping table text. |
| catalog/snippets/workflows/publish-plan-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/publish-docker-readme-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/get-version-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/check-upstream-version-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-release-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-pypilibrary-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-nugetlibrary-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-executable-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-docker-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/workflows/build-datebadge-task.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/vscode/README.md | Prose edits; emphasizes extension list formatting and punctuation. |
| catalog/snippets/husky/README.md | Prose edits for clarity/structure. |
| catalog/snippets/husky/pre-commit | Comment restructuring for prose rule compliance. |
| catalog/snippets/devcontainer/python/post-create.sh | Comment restructuring for prose rule compliance. |
| catalog/snippets/configs/docker-hub-readme.md | Adjusts definition-list formatting for prose rule compliance. |
| catalog/snippets/configs/dependabot.yml | Comment restructuring for prose rule compliance. |
| catalog/snippets/configs/codecov.yml | Comment restructuring for prose rule compliance. |
| catalog/README.md | Prose edits for clarity/structure and consistent list punctuation. |
| AGENTS.md | Clarifies “hub” definition and adds explicit “own checkout” reach rule. |
| .github/workflows/validate-task.yml | Adds gh-write-guard selftest; updates which prose rules gate vs warn-only. |
| .github/workflows/test-pull-request.yml | Comment restructuring for prose rule compliance. |
| .github/workflows/publish-release.yml | Comment restructuring for prose rule compliance. |
| .github/workflows/merge-bot-pull-request.yml | Comment restructuring for prose rule compliance. |
| .github/dependabot.yml | Comment restructuring for prose rule compliance. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…610) Raised by Copilot on the promotion pull request #609, against code that promotion carries. ## The defect `check_sha_pin`'s docstring states the reason the note exists: > The counts below print on every run so that narrowness is visible rather than inferred from a clean line. The code guarded it on a non-zero counter, so it printed on every run **except** the one it was written for. A repository carrying no workflow file at all resolves nothing, every counter is zero, and the note vanished, leaving precisely the clean line the sentence says nobody should have to infer the check's narrowness from. The comment inside the guard made the contradiction plain, reading "One fixed shape every run, so a zero in any position is as visible as a count" while sitting behind the condition that made it conditional. Reproduced before the fix, against a directory with no workflows: ``` bad: [] NOTES: [] ``` ## The fix The note is unconditional. After it, the same call reports what it did rather than nothing: ``` resolved 0 pin(s) against GitHub. Read for shape only: 0 under another owner, 0 whose owner could not be compared because this checkout's origin is unreadable, 0 GitHub did not answer for. ``` The existing case, `test_the_note_carries_every_count_including_the_zeroes`, asserted the zeroes only where a count sat beside them, which is why the all-zero run went uncovered. A case now drives that run directly. `repo_gate.py` on this repository is unchanged in substance and still reports `45 under another owner`, which is the number that made the hub's own zero worth surfacing in the first place. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
ptr727
added a commit
that referenced
this pull request
Aug 8, 2026
Follows the #609 promotion, which went partial at 61 of 62 files and stayed there through two re-requests. That prompted measuring whether the remedy the gate recommends has ever worked. It has not. ## The measurement Over the **332 Copilot review bodies** on the newest 120 pull requests, read beside `gh pr list --json number,changedFiles,additions,deletions`: | Question | Answer | | --- | --- | | Pull requests that went partial | **4** - #476, #479, #592, and the #609 promotion | | Rounds those four produced | **7** | | Rounds that recovered | **0** - every later round repeated the identical ratio | | Files skipped, each time | exactly **1** | ``` PR 592: round 1 = 2/3 round 2 = 2/3 PR 476: round 1 = 10/11 round 2 = 10/11 PR 479: round 1 = 16/17 PR 609: round 1 = 61/62 round 2 = 61/62 (after a re-request on a new head) ``` So the digest was telling a reader to spend a round on a retry that nothing has ever retried out of. ## Two hypotheses tested and rejected, so they are not re-derived **Size does not predict it.** The partials changed 502, 629 and 961 lines. Fully covered pull requests on this repository reach **33 files and 2,219 changed lines**, well past all three. **The denominator is not itself lossy.** I suspected the reviewer might drop a file before counting, which would make `N out of N` a lie. It does not: the stated denominator equals the API's own `changedFiles` on **103 of 104** pull requests, and the single exception is one whose branch shrank between rounds. The reviewer counts the file and does not read it. ## What changed The three places that stated the remedy now state what the record supports, swept by grepping the phrasing rather than by fixing the one the finding pointed at: - `scripts/pr_review.py` - the `status=COVERAGE_IS_PARTIAL` line, the digest's `COVERAGE IS PARTIAL` block, and the `status` exit-code docstring. - `.github/copilot-instructions.md` - the "Counts unequal" bucket. - `scripts/README.md` - the coverage paragraph, carrying the measurements. The new wording says a re-request has never cleared one, that splitting is real for a feature branch and unavailable for a promotion whose head is `develop`, and that the merge decision is the maintainer's, taken knowing one file has no review. **No exit code or verdict changes**, since the gate was reporting the state correctly. Only the advice attached to it was wrong. ## What is left open `TODO.md` gains an entry under the programmatic-reading cluster, with the measurements attached: **which** file is skipped, and why. The reviewer names no file list in these rounds, so it cannot be recovered from the API, and the pull request page may be the only place it appears. Whether it is worth escalating to GitHub needs the file first. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 8, 2026
ptr727
added a commit
that referenced
this pull request
Aug 8, 2026
) Closes #623. **Disposition** `Amends "Find out which file a partial round skips, and why re-requesting never clears it."` in the "A Programmatic Reading of a Copilot Review" cluster. #623 was filed after this repository's last `TODO.md` edit and appeared nowhere in the file, so selection step 1 was false until this change. ## What was wrong The `COVERAGE_IS_PARTIAL` caveat, `scripts/README.md` and the runbook all said the reviewer names no file list in these rounds, so which file went unread cannot be read from the API. Measured over 348 Copilot review bodies on the newest 120 pull requests here and 121 on Blog, that is false. 91 bodies carry a `| File | Description |` table, and every table row in the corpus belongs to one of those tables. ## What the measurement says the table is worth The issue proposed reading a table that names every changed file as evidence that the count is a reporting artifact. It cannot carry that, and the corpus is what says so. | round | states | names | reading | | --- | --- | --- | --- | | #476 | 10 of 11 | all 11 | table contradicts the count | | #592 | 2 of 3 | all 3 | table contradicts the count | | #479 | 16 of 17 | 16, omitting `GOVERNANCE.md` | table corroborates the count and names the file | | #609 | 61 of 62 | 50 of 62 | short by 12 where the count is short by 1 | | #574 | 33 of 33, full | 32, omitting `TODO.md` | a short table on a fully covered round | | #606 | 7 of 7 | names `GOVENANCE.md` | a path no diff carries | On Blog every table names exactly the changed set, on all seven partial rounds and on every full round alike. A reading identical under both outcomes discriminates neither, so a full table is reported as corroborating nothing rather than as a miscount. #574 kills the converse, since a short table sits on a round that read everything. ## What ships - `Q_FULL` selects the pull request's own `files`, windowed at 100 with `hasNextPage` carried, since a path outside a short window reads exactly like a path the reviewer left out and the record holds a pull request of 301 changed files. - `file_table` reads the table, quotations dropped for the reason the coverage line's are. - `head_table` takes it from any round covering the current head and never from a round before a push. Three of the four partials here carry their table on the pre-push round, describing a diff that push replaced, and comparing that against the current changed files would name a file unreviewed on a stale list. Thirteen commits here carry more than one round, and on one of them a round with a table sits beside a round without, so which of the two the verdict reads must not decide whether a table is found. - `table_against_diff` prints one sentence and decides nothing. It names an omitted file only where the table is short by exactly what the counts leave unread **and** names nothing outside the diff, that second guard existing because #606's typo would otherwise push the real `GOVERNANCE.md` into the omissions and report it as the file nobody reviewed. - Exit `42` is unchanged in every arm, and the caveat keeps its maintainer-decision framing, which is the issue's third suggestion. ## Verified against live data #479 the reviewer's own file table omits exactly the 1 file the counts leave unread, naming GOVERNANCE.md Blog #60 the reviewer's own file table names all 6 changed files, ... so it corroborates nothing #476/#592/#609 no round covering this head carries a file table ## Gates 228 `test_pr_review` cases, plus `test_prose_lint`, `test_repo_gate`, `spec/audit.py --selftest`, `spec/validate.py`, both `prose_lint.py` invocations, `markdownlint-cli2` and `editorconfig-checker`, all green locally. Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes 14 commits from
develop. Merge with a merge commit (gh pr merge --merge), never a squash, and without--delete-branch, since this pull request's head isdevelopitself.Closes #607 through the closing keyword already carried in
530cf71, which is why it is not repeated here.The prose backlog, cleared end to end
#600,#604,#605,#606took the tree from 557 findings across 45 files to 0 across 0, in four batches ordered by surface: snippets, comments, hub-only Markdown, then the carried files. Each batch measured the checker's own exemption against the live corpus before sweeping, and twice the measured answer was "do not change the checker", which is a result of that pass rather than a skipped one.#594added the floor that makes those numbers trustworthy: a diff-scoped run now asserts what it actually scanned, since a check whose scan matches nothing reports zero findings and reads exactly like a pass.One finding from that work is worth carrying up: an exemption that is too loose produces silence rather than false positives. #519 recorded the governance files as clean; today's checker reports 38 findings against those same files as they stood at the commit that measured them.
A review loop that fails closed
#599,#601,#602,#603and#608are one arc onscripts/pr_review.py, each removing a shape in which the loop reported a clean pass over a review it had misread:#599removed the shape a reply kept failing in, by taking the thread's words rather than an id, so there is no argument a hand-typedPRRT_...fits in.#602madeclaimsresolve what a description points at rather than what it looks like.#603gave a disproved claim a home the next round reads.#608reads the file-coverage line, and then generalizes: every reader keys on a structural marker, so a marker that changes spelling is a section the reader stops finding and reports as absent. The digest now vets headings,<summary>texts, metadata labels, coverage wordings and the reviewer login against an inventory measured from 332 review bodies, and blocks on anything outside it, exit43, with the remedy stated as filing an issue on the hub. Whether to merge regardless is the maintainer's decision.GOVERNANCE.mdmerge gate went from four preconditions to five accordingly.Governance and tooling
#593states which checkout an agent works in and what the hub is, which is the host-wide routing the repositories that most need it cannot carry.#596gates the pattern-detectable half of the representative-data rule, honest that no pattern closes the name-shaped case.#598declares where a repository states what CI cannot verify.#592regroupedTODO.mdby what ships rather than by what it touches, so a###heading is one pull request.#601ended agh pushargument list at a newline rather than only at&&, fixing a write-guard over-block.Verification
Run on
developat530cf71immediately before opening this:test_pr_review.py(174),test_prose_lint.py,test_repo_gate.py,spec/audit.py --selftest,gh-write-guard.py --selftest,spec/validate.py,repo_gate.py, the prose gate in both CI invocations, markdownlint and editorconfig-checker. All clean.Not carried by this promotion
TODO.mdholds its closing evidence under "Verified Complete, Awaiting Close". Closing it is the maintainer's call, so no keyword for it appears here.#606queued seven, and#608changedGOVERNANCE.md"PR Review Etiquette" (verbatim) and.github/copilot-instructions.md(intent) on top. Theintenthalf produces no hash and therefore no audit finding, which is why the Fleet Sweeps entry names those files by hand.🤖 Generated with Claude Code