From 0c2091869d28ee143325e7d8ac19b4620c86fbb2 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:47:16 -0400 Subject: [PATCH 1/8] docs(topic-docs): the self-ignore guard does not run where no checkout is detected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The convention already routes non-interactive runs to a destination outside every checkout by default (the no-project-root fallback, and non-interactive is the normal condition for forked subagents, dispatched workers, and headless runs), while the self-ignore-guard bullet still spoke unconditionally about that same destination. The document contradicted itself on its own default path, so this closes a gap rather than carving an exception. Two outcomes bind the guard: a memory-tier write is never picked up by a checkout governing the destination, and no plugin ever modifies content tracked in any checkout. The guard is the means to the first wherever a governing checkout is found; where none is detected it buys nothing toward it and its create-when-absent rule can violate the second. The rule is blanket by derivation rather than by generalizing from the measured case. A `.gitignore` absent from disk is either untracked in some undetected checkout, where creating it is harmless and even mitigating, or tracked there, where creating it overwrites committed content and cannot hide the change since a tracked file is exempt from its own pattern. Separating those requires querying a checkout, and the branch is defined by having found none — the index check that would decide it is the one check that cannot run. Guessing untracked and being wrong modifies content committed in a repository the producer cannot see; guessing tracked and being wrong forgoes a mitigation for a harm that is reachable rather than automatic. Undecidable test, asymmetric costs, so: do not write. "Not detected" is stated as a detection claim and never as a claim that none exists — the branch is entered precisely where detection can be wrong. Two dependents. detector-findings' consequence sentence was false in this case and stays a consequence, corrected rather than softened: where detection missed a checkout the findings file lands there untracked, reachable by a later `git add -A`, not committed by this producer's action. And mutation-testing's spoke drops its local derivation for a pointer, now that the rule has an owner. Co-Authored-By: Claude Opus 5 (1M context) --- docs/conventions/detector-findings/README.md | 10 +++-- docs/conventions/topic-docs/CHANGELOG.md | 37 +++++++++++++++++++ docs/conventions/topic-docs/README.md | 27 +++++++++++++- .../skills/audit/context/persist-findings.md | 13 ++----- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/docs/conventions/detector-findings/README.md b/docs/conventions/detector-findings/README.md index 4d304fe87..7075e08bf 100644 --- a/docs/conventions/detector-findings/README.md +++ b/docs/conventions/detector-findings/README.md @@ -54,9 +54,13 @@ What the binding leaves to a producer — consequences, not a second statement o answer to those rungs instead resolves to a directory the consumer never reaches. - **The directory never proves ownership.** What proves a file is this branch's is its own `branch:` frontmatter, never the directory it sits in — the binding's slug rule says why. -- **The self-ignore guard is owed, not re-derived** — including the convention's invalid-root rule, - which stops the guard from healing into a consumer's root `.gitignore`. Skipping it commits - findings that are meant to stay checkout-local. +- **The self-ignore guard is owed, not re-derived** — including the convention's invalid cases, which + stop the guard from healing into a consumer's root `.gitignore` and from writing at a root no + checkout is detected as governing. Skipping it **where a checkout governs the destination** commits + findings that are meant to stay checkout-local. Where none is detected the convention's own rule is + that the guard does not run, and the exposure is different in kind: the findings file lands as an + **untracked** path in any checkout the detection missed — reachable by a later `git add -A`, not + committed by this producer's action. Reachable, not automatic. ## Boundary diff --git a/docs/conventions/topic-docs/CHANGELOG.md b/docs/conventions/topic-docs/CHANGELOG.md index 89fdf4671..d3064108d 100644 --- a/docs/conventions/topic-docs/CHANGELOG.md +++ b/docs/conventions/topic-docs/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog — topic-docs convention +## 2.5.0 — 2026-08-15 + +**The self-ignore guard gains a second invalid case: a root no checkout is +detected as governing.** The guard does not run there. Two outcomes bind it — +(A) a memory-tier write is never picked up by a checkout that governs the +destination, and (B) no plugin ever modifies content tracked in any checkout. +The guard is the means to A wherever a governing checkout is found; where none +is detected it buys nothing toward A and can violate B. + +**The rule is blanket by derivation, not by generalizing from one case.** A +`.gitignore` absent from disk is either untracked in some undetected checkout — +where creating it is harmless and even mitigating — or tracked there, where +creating it overwrites committed content and cannot hide the change, since a +tracked file is exempt from its own pattern. Telling those apart requires +querying a checkout, and this branch is defined by having found none, so the +index check that would decide it is the one check that cannot run. The costs are +unequal: guessing "untracked" and being wrong modifies content committed in a +repository the producer cannot see; guessing "tracked" and being wrong forgoes a +mitigation for a harm that is reachable rather than automatic. An undecidable +test with asymmetric outcomes yields *do not write*. + +"Not detected" is stated as a detection claim and never as a claim that none +exists — the branch is entered precisely where detection can be wrong, which is +why the rule is *do not write* rather than *nothing is at risk*. The tracked-file +case was measured on the `core.worktree` topology, where a repository governs a +tree with no `.git` in the destination's path and nothing in the environment to +find; that demonstration is one route into the state, not its definition. + +This closes a self-contradiction rather than carving an exception: the +no-project-root fallback already routes non-interactive runs to +`${CLAUDE_PLUGIN_DATA}` by default — a destination outside every checkout — while +the guard bullet still spoke unconditionally about that same destination. +Non-interactive is the normal condition for forked subagents, dispatched +workers, and headless runs, so every consumer reaching that surface ran +create-when-absent against a root no checkout governs. +() + ## 2.4.2 — 2026-08-12 Docs-only: the contract-slice lifecycle now documents how to retrieve a diff --git a/docs/conventions/topic-docs/README.md b/docs/conventions/topic-docs/README.md index 38cb6dd06..4456bba36 100644 --- a/docs/conventions/topic-docs/README.md +++ b/docs/conventions/topic-docs/README.md @@ -389,7 +389,32 @@ cite it rather than redefining it. session, matching the committed-tier guard's scope. A root-equivalent `memory_dir` (`.`, empty, or resolving to the repo root) is **invalid** — stop and surface it; healing there would write `*` into the - consumer's root `.gitignore`, which the next rule forbids. + consumer's root `.gitignore`, which the next rule forbids. A root that + **no checkout is detected as governing** is the second invalid case: + the guard does **not** run there. Two outcomes bind it — (A) a + memory-tier write is never picked up by a checkout that governs the + destination, and (B) no plugin ever modifies content tracked in any + checkout. The guard is the *means* to A wherever a governing checkout + is found; where none is detected it buys nothing toward A, and its + create-when-absent rule can violate B. **"Not detected" is a detection + claim and never a claim that none exists** — the branch is entered + precisely where detection can be wrong. + The rule is blanket because the producer cannot make it conditional: + a `.gitignore` absent from disk is either untracked in some undetected + checkout (where creating it would be harmless, even mitigating) or + tracked there (where creating it overwrites committed content and + cannot hide the change, a tracked file being exempt from its own + pattern). **Telling those apart requires querying a checkout, and this + branch is defined by having found none** — so the index check that + would decide it is exactly the check that cannot run. The costs are + unequal: guessing "untracked" and being wrong modifies content + committed in a repository the producer cannot see, while guessing + "tracked" and being wrong forgoes a mitigation for a harm that is + reachable rather than automatic. An undecidable test with asymmetric + outcomes yields *do not write*. This is not a rare path: the + no-project-root fallback above routes non-interactive runs to + `${CLAUDE_PLUGIN_DATA}` by default, and non-interactive is the normal + condition for forked subagents, dispatched workers, and headless runs. - No plugin ever edits the consumer's root `.gitignore`. ## Slug and filename spec diff --git a/plugins/mutation-testing/skills/audit/context/persist-findings.md b/plugins/mutation-testing/skills/audit/context/persist-findings.md index c78f423e8..a2a4a3293 100644 --- a/plugins/mutation-testing/skills/audit/context/persist-findings.md +++ b/plugins/mutation-testing/skills/audit/context/persist-findings.md @@ -81,16 +81,9 @@ nothing left to ask there. The order is what makes the per-write form hold: nothing in the environment to find**. The designation lives in a config file that destination-side discovery never reaches, so both signals come back empty together. - **On the permissive branch, do not run the self-ignore guard at all.** Where no checkout governs - the path there is no repository to keep the write out of, so the guard has no work to do — and the - only circumstance in which its file would have mattered is the one where this discovery was wrong. - Skipping it is what keeps that case harmless. The guard's rule is *create when absent*, and a - `.gitignore` that is absent from disk but **tracked** in an undiscovered checkout — routine after - an `rm` or an interrupted checkout — would be created straight over the consumer's committed - content. A tracked file is exempt from its own pattern, so it could not even hide itself: the - result is a modified tracked file, which is precisely what "never in tracked source" forbids. Step - 3 is what refuses a root holding tracked files, and step 3 never ran here, so the guard would be - writing with its only protection absent. + **On the permissive branch the self-ignore guard does not run** — the + [topic-docs convention](../../../../../docs/conventions/topic-docs/README.md) "Runtime guards" + second invalid case, which owns both the rule and why. Nothing is re-derived here. What remains on that branch is the findings file alone, written into a directory nothing was found to govern. If discovery *was* wrong, it shows up in the undiscovered checkout as an untracked From d20945eb1ab11e72a1b24b3d99228229ec9402b3 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:58:05 -0400 Subject: [PATCH 2/8] chore(mutation-testing): bump to 0.2.1 for the guard-rule pointer The parity gate reads any change under a plugin's directory as requiring a version bump and a matching entry, and this change set edits the audit skill's spoke. 0.2.0 is published, so editing under it in place reuses a released number. Patch rather than minor: no behavior changes. The permissive-branch guard rule moved to the topic-docs convention that owns the guard, and the spoke now cites it instead of deriving it. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/mutation-testing/.claude-plugin/plugin.json | 2 +- plugins/mutation-testing/CHANGELOG.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/plugins/mutation-testing/.claude-plugin/plugin.json b/plugins/mutation-testing/.claude-plugin/plugin.json index 3885145a1..3f493d290 100644 --- a/plugins/mutation-testing/.claude-plugin/plugin.json +++ b/plugins/mutation-testing/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "mutation-testing", - "version": "0.2.0", + "version": "0.2.1", "description": "Measures whether a test suite can actually detect faults, not merely execute code: `/mutation-testing:principles` answers operator, mutant-state, and metric questions from the primary literature; `/mutation-testing:setup` verifies the ecosystem's mutation tool and writes the tracked config; `/mutation-testing:audit` runs diff-scoped mutation analysis and reports surviving mutants, verifying that tracked source was restored and failing the run when it cannot, delegating the productive-versus-arid judgment to a fresh-context reviewer and test authoring to the test lane, and optionally persisting survivors as a findings file the review fix pass consumes.", "author": { "name": "Melodic Software", diff --git a/plugins/mutation-testing/CHANGELOG.md b/plugins/mutation-testing/CHANGELOG.md index 01a728449..a0fa1e543 100644 --- a/plugins/mutation-testing/CHANGELOG.md +++ b/plugins/mutation-testing/CHANGELOG.md @@ -3,6 +3,16 @@ All notable changes to the `mutation-testing` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.2.1] + +### Changed + +- **The permissive-branch guard rule becomes a pointer.** `--persist-findings` skips the self-ignore + guard where no checkout is detected as governing the destination; that rule now belongs to the + [topic-docs convention](../../docs/conventions/topic-docs/README.md) "Runtime guards", which owns + the guard, so the spoke cites it instead of deriving it locally. Behavior is unchanged — the rule + moved to its owner, where it binds every consumer of that guard rather than this plugin alone. + ## [0.2.0] ### Added From 30f81bea63c1133e53e93a62cc35a7ba5b774930 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:23:47 -0400 Subject: [PATCH 3/8] fix(review): defer on the guard's invalid roots instead of reading unconditional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2737 added an owner table naming this binding as owning the self-ignore guard, so a reader now arrives here first. The bullet restated create-when-absent and ended "per the contract" but named none of the roots at which the contract says the guard does not run — so through that doorway it reads as unconditional. The omission predates this branch: root-equivalence has been in the convention for a long time and this bullet never carried it. The clause adds a second omission to an already-incomplete restatement rather than creating the defect. Deference, not a copy. The bullet now states that invalid roots exist and points at the convention's Runtime guards for them, enumerating none. Copying the list would put one rule in two places — the shape that left the aridity bar stated six ways — and would still omit root-equivalence, making the binding a differently incomplete copy. A pointer cannot drift. Not touched: fanout/SKILL.md and fix-pass-mode.md INVOKE the guard rather than defining it, so an invocation resolves to the repaired definition. Editing them would be a third and fourth copy of a rule that should exist once. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/review/.claude-plugin/plugin.json | 2 +- plugins/review/CHANGELOG.md | 12 ++++++++++++ plugins/review/reference/topic-docs.md | 5 ++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/plugins/review/.claude-plugin/plugin.json b/plugins/review/.claude-plugin/plugin.json index 2041202ce..44520184f 100644 --- a/plugins/review/.claude-plugin/plugin.json +++ b/plugins/review/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "review", - "version": "0.21.0", + "version": "0.21.1", "description": "Code-review toolkit: six read-only reviewer agents (code, security, architecture, doc drift, build/test/lint, CI-log audit) plus orchestration skills — quality gate, fan-out, and CI lane commands (/review:code-review, /review:security-review) for org reusable workflows.", "author": { "name": "Melodic Software", diff --git a/plugins/review/CHANGELOG.md b/plugins/review/CHANGELOG.md index e5679a5c3..86e201fcb 100644 --- a/plugins/review/CHANGELOG.md +++ b/plugins/review/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to the `review` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.21.1] + +### Changed + +- **The binding's self-ignore-guard bullet now defers on invalid roots as well as on cadence.** It + already restated the guard's create-when-absent behavior and ended "per the contract", but named + none of the roots at which the contract says the guard does **not** run — so a reader arriving + through the detector-findings owner table, which names this binding as the guard's owner, met text + reading as unconditional. The bullet now states that such roots exist and points at the + convention's "Runtime guards" for them, **enumerating none**: a second copy of the list is how a + rule ends up stated several ways, and the omission this repairs was itself an incomplete copy. + ## [0.21.0] ### Added diff --git a/plugins/review/reference/topic-docs.md b/plugins/review/reference/topic-docs.md index d415ee5f3..d1d7fc71d 100644 --- a/plugins/review/reference/topic-docs.md +++ b/plugins/review/reference/topic-docs.md @@ -67,5 +67,8 @@ before any write — the convention's no-project-root fallback surface never com - **Self-ignore guard:** the session's first memory-tier write verifies the **resolved memory root** (whatever `memory_dir` names — never a hardcoded `.work`) contains a `.gitignore` with `*`, creating it (announced) when absent — fresh clones heal on first write. Once per session, - per the contract. + per the contract. The contract also defines **invalid roots at which the guard does not run**; + they are enumerated in its + [Runtime guards](https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/topic-docs/README.md) + section and deliberately not listed here, so this binding cannot drift from them. - No skill in this plugin ever edits the consumer's root `.gitignore`. From bc8f1114040cbf29687a1e79658769ab61e1844c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:58:42 -0400 Subject: [PATCH 4/8] fix(architecture): defer on the guard's invalid roots in this binding too Second plugin binding restating the self-ignore guard unconditionally, same shape as review's before its deference clause. This plugin resolves through the same convention and so reaches the same no-project-root default, where its readers met text saying the guard always creates. Same form as review's: state that invalid roots exist, point at the convention's Runtime guards, enumerate none. Copying the list would put one rule in a third place. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/architecture/.claude-plugin/plugin.json | 2 +- plugins/architecture/CHANGELOG.md | 12 ++++++++++++ plugins/architecture/reference/topic-docs.md | 6 +++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/plugins/architecture/.claude-plugin/plugin.json b/plugins/architecture/.claude-plugin/plugin.json index 0b4b42f83..01fbb415a 100644 --- a/plugins/architecture/.claude-plugin/plugin.json +++ b/plugins/architecture/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "architecture", - "version": "0.5.2", + "version": "0.5.3", "description": "Scans an existing codebase for module-level architecture friction — shallow modules, seam leaks, and locality gaps — using Ousterhout's deep-module lens, presents candidates as a self-contained HTML report, and runs an interview loop on the selected candidate before handing off for planning.", "author": { "name": "Melodic Software", diff --git a/plugins/architecture/CHANGELOG.md b/plugins/architecture/CHANGELOG.md index 142724338..b9103a821 100644 --- a/plugins/architecture/CHANGELOG.md +++ b/plugins/architecture/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to the `architecture` plugin are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning. +## [0.5.3] + +### Changed + +- **The binding's Guards section now defers on invalid roots as well as stating the guard.** It said + the self-ignore guard "applies on first write (verify-or-create `.gitignore`)" and named none of + the roots at which the contract says the guard does **not** run — so a reader landed on text + reading as unconditional, including for the no-project-root default where this plugin's memory + writes go. The section now states that such roots exist and points at the convention's "Runtime + guards" for them, **enumerating none**: a second copy of the list is how one rule ends up stated + several ways. + ## [0.5.2] ### Fixed diff --git a/plugins/architecture/reference/topic-docs.md b/plugins/architecture/reference/topic-docs.md index ad7622244..8e4b48c3b 100644 --- a/plugins/architecture/reference/topic-docs.md +++ b/plugins/architecture/reference/topic-docs.md @@ -33,4 +33,8 @@ named module or path) → the current branch name. Form and collision rules are ## Guards The memory root's self-ignore guard applies on first write (verify-or-create `.gitignore` with -`*`, announced). Create the topic slice directory when absent. +`*`, announced). The contract also defines **invalid roots at which the guard does not run**; they +are enumerated in its +[Runtime guards](https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/topic-docs/README.md) +section and deliberately not listed here, so this binding cannot drift from them. Create the topic +slice directory when absent. From 2fcf1b90b7c859b90c9b9661b8e29f3bbf51141c Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:31:30 -0400 Subject: [PATCH 5/8] fix(conventions): withhold every write where no checkout can be shown to govern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A P2 on #2756 is right, and measurement confirms it: the undecidability that stops the self-ignore guard binds the findings file too. Reproduced — commit a findings-file path, delete it from disk, write it again: ` M `, a modified tracked file, not the untracked one the text claimed. So on the branch where neither signal finds a governing checkout, this producer now writes NOTHING and reports the resolved destination instead. "Read-only with respect to tracked source" admits no write that may land on a tracked deletion. The convention states the general form: the same undecidability binds every write on that branch, and a surface whose artifact must not modify tracked content refuses the artifact write too. A surface that can tolerate an untracked path being staged later may proceed — the two outcomes differ in what they protect, and only the first is absolute. detector-findings gains its missing version bump (2.0.0 -> 2.0.1, docs-only per its own Versioning section) and its consequence sentence drops the "untracked" claim the same measurement falsified. Both binding pointers move from raw.githubusercontent.com to the rendered blob URL with a #runtime-guards fragment: raw serves plain text with no anchor ids, so link text promising a section could not reach it. That is the repo's established cross-reference form. Swept for text the refusal invalidates: the "Where the file goes" obligation now names the second invalid case two sections before the rule, the at-most-two-writes preamble and step 1 say the empty-signals branch writes nothing, the bare-repo case is relabelled a conservative refusal it cannot distinguish from core.worktree, and SKILL.md, the plugin CHANGELOG and eval 13 follow. Co-Authored-By: Claude Opus 5 (1M context) --- .../detector-findings/CHANGELOG.md | 12 +++++ docs/conventions/detector-findings/README.md | 7 +-- docs/conventions/topic-docs/README.md | 9 ++++ plugins/architecture/reference/topic-docs.md | 2 +- plugins/mutation-testing/CHANGELOG.md | 9 ++-- .../mutation-testing/skills/audit/SKILL.md | 11 +++-- .../skills/audit/context/persist-findings.md | 45 ++++++++++++------- .../skills/audit/evals/evals.json | 2 +- plugins/review/reference/topic-docs.md | 2 +- 9 files changed, 69 insertions(+), 30 deletions(-) diff --git a/docs/conventions/detector-findings/CHANGELOG.md b/docs/conventions/detector-findings/CHANGELOG.md index 940ffa3b0..9ec7730c7 100644 --- a/docs/conventions/detector-findings/CHANGELOG.md +++ b/docs/conventions/detector-findings/CHANGELOG.md @@ -4,6 +4,18 @@ Notable changes to the detector-findings contract (SemVer). Changing a producer- the coexistence obligations, or an enforceability verdict is a major bump; additive guidance or a new adopter row is a minor bump; docs-only clarification is a patch. +## 2.0.1 — 2026-08-15 + +Docs-only: the self-ignore-guard bullet's consequence sentence was universally +true only where a checkout governs the destination. Where none is detected, the +[topic-docs convention](../topic-docs/README.md) "Runtime guards" now says the +guard does not run, and a producer bound to leave tracked content unmodified +withholds the findings file there too — that destination may be an index-tracked +deletion in the checkout the detection missed, where writing modifies tracked +content instead of creating an untracked path (measured). No producer-owned field +rule, coexistence obligation, or enforceability verdict changes, so this is a +patch. (#2680) + ## 2.0.0 — 2026-08-15 The crosswalk phase, written from what the pilot ran into. **Major** under this contract's own rule: diff --git a/docs/conventions/detector-findings/README.md b/docs/conventions/detector-findings/README.md index 9c6b879c5..e9cfc98b3 100644 --- a/docs/conventions/detector-findings/README.md +++ b/docs/conventions/detector-findings/README.md @@ -59,9 +59,10 @@ What the binding leaves to a producer — consequences, not a second statement o stop the guard from healing into a consumer's root `.gitignore` and from writing at a root no checkout is detected as governing. Skipping it **where a checkout governs the destination** commits findings that are meant to stay checkout-local. Where none is detected the convention's own rule is - that the guard does not run, and the exposure is different in kind: the findings file lands as an - **untracked** path in any checkout the detection missed — reachable by a later `git add -A`, not - committed by this producer's action. Reachable, not automatic. + that the guard does not run — and a producer bound to leave tracked content unmodified **withholds + the findings file there as well**, because that destination may itself be an index-tracked deletion + in the checkout the detection missed, where writing it modifies tracked content rather than + creating an untracked path. Report the resolved destination and persist nothing. ## Boundary diff --git a/docs/conventions/topic-docs/README.md b/docs/conventions/topic-docs/README.md index 1498c74a0..2906ee4dd 100644 --- a/docs/conventions/topic-docs/README.md +++ b/docs/conventions/topic-docs/README.md @@ -415,6 +415,15 @@ cite it rather than redefining it. no-project-root fallback above routes non-interactive runs to `${CLAUDE_PLUGIN_DATA}` by default, and non-interactive is the normal condition for forked subagents, dispatched workers, and headless runs. + **The same undecidability binds every other write on that branch**, not + only the guard's: any destination path may be an index-tracked + deletion in the undetected checkout, in which case writing it produces + a tracked modification rather than a new untracked file. A surface + whose artifact must not modify tracked content therefore refuses the + artifact write too, rather than skipping only the guard. A surface + that can tolerate an untracked path being staged by a later + `git add -A` may proceed — the two outcomes differ in what they + protect, and only the first is absolute. - No plugin ever edits the consumer's root `.gitignore`. ## Slug and filename spec diff --git a/plugins/architecture/reference/topic-docs.md b/plugins/architecture/reference/topic-docs.md index 8e4b48c3b..0a74c2bea 100644 --- a/plugins/architecture/reference/topic-docs.md +++ b/plugins/architecture/reference/topic-docs.md @@ -35,6 +35,6 @@ named module or path) → the current branch name. Form and collision rules are The memory root's self-ignore guard applies on first write (verify-or-create `.gitignore` with `*`, announced). The contract also defines **invalid roots at which the guard does not run**; they are enumerated in its -[Runtime guards](https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/topic-docs/README.md) +[Runtime guards](https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/topic-docs/README.md#runtime-guards) section and deliberately not listed here, so this binding cannot drift from them. Create the topic slice directory when absent. diff --git a/plugins/mutation-testing/CHANGELOG.md b/plugins/mutation-testing/CHANGELOG.md index f1339e04f..36351d43f 100644 --- a/plugins/mutation-testing/CHANGELOG.md +++ b/plugins/mutation-testing/CHANGELOG.md @@ -85,7 +85,7 @@ All notable changes to the `mutation-testing` plugin are documented here. Format contract rather than restated. Bare invocation is unchanged: it reports and stops. - **Each write this phase makes is proven outside tracked space before that write is made** — the findings file, and the self-ignore guard's own `.gitignore` where a governing checkout was found - (where none was, the guard does not run and the findings file is the only write). Per-write rather + (where none was, neither write happens — see below). Per-write rather than both up front, because on a fresh root the guard's file is what makes the findings file's probe pass. The guard's write is proven *before the guard heals*, by requiring the resolved root to hold no tracked files, because writing `*` into a @@ -102,8 +102,11 @@ All notable changes to the `mutation-testing` plugin are documented here. Format run at all.** There is no repository to keep the write out of, and its create-when-absent rule would otherwise write straight over a `.gitignore` that is absent from disk but tracked in the undiscovered checkout, modifying committed content it could not even hide, since a tracked file is - exempt from its own pattern. What remains on that branch is the findings file alone, which - overwrites nothing. With a governing checkout, `git check-ignore` decides, anchored there and never to the invoking + exempt from its own pattern. **The findings file is withheld on that branch too**: the same + undecidability applies to it, since its destination may be an index-tracked deletion in the + undetected checkout, where writing produces a modified tracked file rather than a new untracked one + (measured). The run reports the resolved destination and that nothing was persisted, rather than + writing where it cannot rule that out. With a governing checkout, `git check-ignore` decides, anchored there and never to the invoking worktree, where a memory root outside the worktree (a layout the `review:fanout` `fix` action supports explicitly) makes the probe fatal with exit 128 and every write a refusal. "The path is tracked space" and "the probe could not evaluate the path" are reported as the different states diff --git a/plugins/mutation-testing/skills/audit/SKILL.md b/plugins/mutation-testing/skills/audit/SKILL.md index 9f4a5a856..37b157ad4 100644 --- a/plugins/mutation-testing/skills/audit/SKILL.md +++ b/plugins/mutation-testing/skills/audit/SKILL.md @@ -45,7 +45,8 @@ Three properties, stated first because everything below depends on them: naming doctrine's verb contract, `audit` reports and stops — and bare invocation does exactly that. `--persist-findings` is the explicit user override that verb contract sanctions (the marketplace's `docs/PLUGIN-PHILOSOPHY.md` verb table). Its two writes — the findings file, - and the self-ignore guard's own `.gitignore` when it heals a root — are each **proven outside + and the self-ignore guard's own `.gitignore` when a governing checkout is found and the guard + heals that root — are each **proven outside tracked space before that write is made**, never in tracked source and never in a file another producer owns. 2. **No tests are written here.** Survivors are handed to the test-authoring lane. This skill never @@ -312,10 +313,12 @@ The mechanics are owned by [`context/persist-findings.md`](context/persist-findi the detector-findings producer contract for this plugin. Six things there are easy to get wrong and are not optional: the destination comes from the contract's **whole** rung order, taking its **non-interactive collapse** for the rungs that confirm or ask, never a hardcoded default; -**each** of the phase's two writes — the findings file and the self-ignore guard's `.gitignore` — is +**each** write this phase makes — the findings file and the self-ignore guard's `.gitignore` — is proven outside tracked space before **that** write is made, against the checkout that governs the -destination rather than the invoking worktree, and with the guard's own write proven before the guard -heals rather than reported afterwards (a memory root inside tracked space leaves `git status` +destination rather than the invoking worktree, with the guard's own write proven before the guard +heals rather than reported afterwards, and with **nothing written at all** where no governing +checkout can be found, since an undetected checkout may hold the destination as a tracked deletion +that a write would modify rather than create (a memory root inside tracked space leaves `git status` identical either way and so cannot detect itself, while a root outside the worktree is a layout the consumer supports and a worktree-anchored probe could only ever refuse); the Phase 4 **verdict class** selects the contract rule and the rule decides `Tier`, never the finding's prose, with diff --git a/plugins/mutation-testing/skills/audit/context/persist-findings.md b/plugins/mutation-testing/skills/audit/context/persist-findings.md index e7342a540..e0e8e0cee 100644 --- a/plugins/mutation-testing/skills/audit/context/persist-findings.md +++ b/plugins/mutation-testing/skills/audit/context/persist-findings.md @@ -21,7 +21,9 @@ obligations are the ones a mutation run is most likely to skip, so they are name here: run the **whole** rung order rather than its last rung; take the **non-interactive collapse** for the rungs that confirm or ask, since a headless detector cannot answer; and honor the **self-ignore guard**, including the invalid-root rule that keeps it out of a consumer's root -`.gitignore`. +`.gitignore` — and its second invalid case, named in "Prove the destination is outside tracked +space", where no checkout can be shown to govern the destination and this phase writes nothing at +all. File name: `${TS}-mutation-survivors.md`, with `TS="$(date -u +%Y%m%dT%H%M%SZ)"` — colon-free and Windows-safe, so lexical sort equals chronological sort. @@ -33,11 +35,12 @@ handed to the merge set — the same defect as writing into a file another produ ## Prove the destination is outside tracked space before writing to it -This phase makes **two** writes — the findings file and, when the self-ignore guard heals a root, that -root's `.gitignore` — and the property to prove is that git picks up neither. **Each is proven before -that write is made**, which is the strongest form available and not the same as proving both up -front: on a fresh root the guard's file is exactly what makes the findings file's probe pass, so that -probe cannot precede the guard. The order is what makes the per-write form hold: +This phase makes **at most two** writes — the findings file and, when the self-ignore guard heals a +root, that root's `.gitignore` — and the property to prove is that git picks up neither. **Each is +proven before that write is made**, which is the strongest form available and not the same as proving +both up front: on a fresh root the guard's file is exactly what makes the findings file's probe pass, +so that probe cannot precede the guard. "At most" is load-bearing — where no governing checkout is +found, neither write happens (step 1). The order is what makes the per-write form hold: 0. **Make the resolved root a physical path first.** `cd` to its nearest existing ancestor, take `pwd -P`, and re-append the components below it. A lexical walk over a path whose ancestor is a @@ -52,8 +55,9 @@ probe cannot precede the guard. The order is what makes the per-write form hold: run under the **ambient** environment. A toplevel it reports is a governing checkout even when the walk found none. - **No checkout governs the path only when both come back empty**; then, and only then, both writes - proceed. Either signal alone is fail-open in a state the other sees: the walk cannot see a working + **No checkout governs the path only when both come back empty** — and that is the branch on which + this producer writes **nothing**, not the branch on which it writes freely. Either signal alone is + fail-open in a state the other sees: the walk cannot see a working tree designated by `GIT_WORK_TREE`/`GIT_DIR`, where nothing in the path has a `.git` at all yet git reports tracked files there; `rev-parse` cannot tell "no repository" from a missing directory, a dangling `gitdir:`, or a discovery limit, all of which are exit 128. They do not fail on the same @@ -67,19 +71,26 @@ probe cannot precede the guard. The order is what makes the per-write form hold: nothing in the environment to find**. The designation lives in a config file that destination-side discovery never reaches, so both signals come back empty together. - **On the permissive branch the self-ignore guard does not run** — the + **On the permissive branch this producer writes nothing at all** — not the guard's `.gitignore`, + and not the findings file. The guard's half is the [topic-docs convention](../../../../../docs/conventions/topic-docs/README.md) "Runtime guards" - second invalid case, which owns both the rule and why. Nothing is re-derived here. + second invalid case, which owns the rule and why; nothing is re-derived here. The findings file + follows for the same undecidability applied to this skill's own contract: the destination may be + an **index-tracked deletion** in the checkout the detection missed, and writing it there produces + a *modified tracked file* rather than a new untracked one — measured. "Read-only with respect to + tracked source" admits no such write, so the branch that cannot rule it out cannot take it. - What remains on that branch is the findings file alone, written into a directory nothing was found - to govern. If discovery *was* wrong, it shows up in the undiscovered checkout as an untracked - path — visible, attributable, and having overwritten nothing. + Report the resolved destination, say that no checkout could be shown to govern it and the findings + were therefore not persisted, and stop. A refusal a human can act on is the correct end of this + branch; it is not a silent skip. Two cases the signals resolve rather than defer: a **bare** repository with no worktree puts no - `.git` in any ancestor and reports none, so it reaches the permissive branch — correct, since a - repository with no working tree picks nothing up. (A bare *layout* that names a worktree through - `core.worktree` is the topology above, not this one.) A destination **inside a checkout's own - `.git/`** is refused + `.git` in any ancestor and reports none, so it reaches this branch and the run refuses. That + refusal is conservative rather than necessary — a repository with no working tree picks nothing + up — but the signals cannot distinguish it from the `core.worktree` topology, which is the whole + reason the branch refuses; a rule that guessed which one it was facing would be the fail-open this + step exists to prevent. (A bare *layout* that names a worktree through `core.worktree` is that + topology, not this one.) A destination **inside a checkout's own `.git/`** is refused here, by name: `check-ignore` answers exit 1 for it, which step 5 would report as "tracked space", and `.git/` is not that. 2. **Reject a root-equivalent `memory_dir`** — the contract's invalid-root rule, judged against `T` diff --git a/plugins/mutation-testing/skills/audit/evals/evals.json b/plugins/mutation-testing/skills/audit/evals/evals.json index 2b09cd9ee..53cebb66d 100644 --- a/plugins/mutation-testing/skills/audit/evals/evals.json +++ b/plugins/mutation-testing/skills/audit/evals/evals.json @@ -76,7 +76,7 @@ { "id": 13, "prompt": "Our memory_dir points at a shared directory outside this worktree. Will --persist-findings write there?", - "expected_output": "Yes, once EACH of the phase's two writes -- the findings file and the self-ignore guard's `.gitignore` -- is proven outside tracked space before THAT write is made (per-write, not both up front, since on a fresh root the guard's file is what makes the findings file's probe pass). Makes the resolved root physical first via `pwd -P` from its nearest existing ancestor, so a symlinked ancestor cannot hide a checkout. Then resolves the governing checkout from TWO signals that must BOTH come back empty before the permissive branch: a walk of that physical path's ancestors for a `.git` entry using `test -e` (not `-d`, since a worktree's `.git` is a file), AND `git rev-parse --show-toplevel` run under the AMBIENT environment. Neither alone: rev-parse cannot distinguish no-repository from a missing directory, a dangling gitdir, or a discovery limit such as GIT_CEILING_DIRECTORIES -- all exit 128 -- while the walk cannot see a working tree designated by GIT_WORK_TREE or GIT_DIR. Names `core.worktree` as a topology defeating both, disclosed rather than claimed closed. With a governing checkout T: rejects a root-equivalent memory_dir judged against T; proves the guard's write BEFORE the guard makes it via `git -C T ls-files -- `, requiring exit 0 AND empty output -- a fatal ls-files prints nothing to stdout and exits 128, so reading output alone lets a failure pass for a clean root; only then runs the guard and `git -C T check-ignore -q -- `, writing only on exit 0. Reports exit 1 ('the destination is tracked space') distinctly from any other exit ('the probe did not evaluate this path'), refusing on both. Anchors the probes to a governing checkout, never the invoking worktree, because check-ignore against an outside path is fatal with exit 128 -- a worktree-anchored probe would refuse every write in a layout the fanout fix action supports explicitly -- and notes that `git -C` alone is not an anchor, since GIT_DIR and GIT_WORK_TREE override it. Not a path-prefix comparison, which would need BOTH paths canonicalized where realpath and readlink's canonicalizing flag are GNU-only.", + "expected_output": "Yes, once EACH of the phase's two writes -- the findings file and the self-ignore guard's `.gitignore` -- is proven outside tracked space before THAT write is made (per-write, not both up front, since on a fresh root the guard's file is what makes the findings file's probe pass). Makes the resolved root physical first via `pwd -P` from its nearest existing ancestor, so a symlinked ancestor cannot hide a checkout. Then resolves the governing checkout from TWO signals that must BOTH come back empty before the permissive branch: a walk of that physical path's ancestors for a `.git` entry using `test -e` (not `-d`, since a worktree's `.git` is a file), AND `git rev-parse --show-toplevel` run under the AMBIENT environment. Neither alone: rev-parse cannot distinguish no-repository from a missing directory, a dangling gitdir, or a discovery limit such as GIT_CEILING_DIRECTORIES -- all exit 128 -- while the walk cannot see a working tree designated by GIT_WORK_TREE or GIT_DIR. Names `core.worktree` as a topology defeating both, and says that where BOTH signals come back empty the run writes NOTHING -- not the guard's `.gitignore`, not the findings file -- reporting the resolved destination and stopping. Gives the reason the findings file is withheld too: its destination may be an index-tracked DELETION in the checkout the detection missed, where writing modifies a tracked file rather than creating an untracked one, which 'read-only with respect to tracked source' does not admit. Treats a bare repository reaching that branch as a conservative refusal indistinguishable from `core.worktree`, never as a case to write through. With a governing checkout T: rejects a root-equivalent memory_dir judged against T; proves the guard's write BEFORE the guard makes it via `git -C T ls-files -- `, requiring exit 0 AND empty output -- a fatal ls-files prints nothing to stdout and exits 128, so reading output alone lets a failure pass for a clean root; only then runs the guard and `git -C T check-ignore -q -- `, writing only on exit 0. Reports exit 1 ('the destination is tracked space') distinctly from any other exit ('the probe did not evaluate this path'), refusing on both. Anchors the probes to a governing checkout, never the invoking worktree, because check-ignore against an outside path is fatal with exit 128 -- a worktree-anchored probe would refuse every write in a layout the fanout fix action supports explicitly -- and notes that `git -C` alone is not an anchor, since GIT_DIR and GIT_WORK_TREE override it. Not a path-prefix comparison, which would need BOTH paths canonicalized where realpath and readlink's canonicalizing flag are GNU-only.", "files": [] }, { diff --git a/plugins/review/reference/topic-docs.md b/plugins/review/reference/topic-docs.md index d1d7fc71d..48dc00cfe 100644 --- a/plugins/review/reference/topic-docs.md +++ b/plugins/review/reference/topic-docs.md @@ -69,6 +69,6 @@ before any write — the convention's no-project-root fallback surface never com `*`, creating it (announced) when absent — fresh clones heal on first write. Once per session, per the contract. The contract also defines **invalid roots at which the guard does not run**; they are enumerated in its - [Runtime guards](https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/topic-docs/README.md) + [Runtime guards](https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/topic-docs/README.md#runtime-guards) section and deliberately not listed here, so this binding cannot drift from them. - No skill in this plugin ever edits the consumer's root `.gitignore`. From 676fef204e6cf376a095256ac57d3b09c9223b68 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 15 Aug 2026 21:46:00 +0000 Subject: [PATCH 6/8] chore(mutation-testing): fix MD012 double blank in CHANGELOG Co-authored-by: Kyle Sexton --- plugins/mutation-testing/CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/mutation-testing/CHANGELOG.md b/plugins/mutation-testing/CHANGELOG.md index 2edc2111f..df4d3944f 100644 --- a/plugins/mutation-testing/CHANGELOG.md +++ b/plugins/mutation-testing/CHANGELOG.md @@ -51,7 +51,6 @@ All notable changes to the `mutation-testing` plugin are documented here. Format and graded surfaces a reader meets first were wrong while the reference they consult last was right. - ## [0.3.1] ### Fixed From e6bcd8f17e5d18125b346438af053f9f583b3ce7 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:50:10 -0400 Subject: [PATCH 7/8] fix(mutation-testing): restate the step-1 proof for the write that survives the refusal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "at most two writes" framing and the carve-out pointer were restored earlier in this branch, but the argument under them was not: that the write taken on the no-checkout branch is PROVEN rather than merely permitted. It needed restating rather than restoring, because the rule changed under it. At a resolved root the branch now refuses both writes, so the surviving write is the ${CLAUDE_PLUGIN_DATA} fallback — and what proves that one safe is step 1 itself. Two independent signals agreeing that no checkout governs the path IS the proof that none can track it: there is no ignore rule to satisfy and nothing for check-ignore to answer. A proof, not an exemption, which is why step 1 needs both signals and why a single-signal version would be fail-open rather than weaker. Also corrects the carve-out pointer to say "a resolved root" — "writes nothing at all" was inexact once the plugin-data fallback became an explicit exception. Co-Authored-By: Claude Opus 5 (1M context) --- .../skills/audit/context/persist-findings.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/plugins/mutation-testing/skills/audit/context/persist-findings.md b/plugins/mutation-testing/skills/audit/context/persist-findings.md index b67438810..5d194dfde 100644 --- a/plugins/mutation-testing/skills/audit/context/persist-findings.md +++ b/plugins/mutation-testing/skills/audit/context/persist-findings.md @@ -22,8 +22,8 @@ here: run the **whole** rung order rather than its last rung; take the **non-int for the rungs that confirm or ask, since a headless detector cannot answer; and honor the **self-ignore guard**, including the invalid-root rule that keeps it out of a consumer's root `.gitignore` — and its second invalid case, named in "Prove the destination is outside tracked -space", where no checkout can be shown to govern the destination and this phase writes nothing at -all. +space", where no checkout can be shown to govern a resolved root and this phase writes nothing there +at all. File name: `${TS}-mutation-survivors.md`, with `TS="$(date -u +%Y%m%dT%H%M%SZ)"` — colon-free and Windows-safe, so lexical sort equals chronological sort. @@ -40,7 +40,15 @@ root, that root's `.gitignore` — and the property to prove is that git picks u proven before that write is made**, which is the strongest form available and not the same as proving both up front: on a fresh root the guard's file is exactly what makes the findings file's probe pass, so that probe cannot precede the guard. "At most" is load-bearing — where no governing checkout is -found, neither write happens (step 1). The order is what makes the per-write form hold: +found, neither write happens at a resolved root (step 1). + +**The one write that survives that branch is proven by step 1 itself, not by a probe.** The +`${CLAUDE_PLUGIN_DATA}` fallback is written there, and what proves it safe is the agreement of two +independent signals that no checkout governs the path: a destination outside every checkout cannot be +tracked by one, so there is no ignore rule to satisfy and nothing for `check-ignore` to answer. That +is a proof, not an exemption — which is why step 1 needs both signals and why a single-signal version +of it would be fail-open rather than merely weaker. The order below is what makes the per-write form +hold everywhere else: 0. **Make the resolved root a physical path first.** `cd` to its nearest existing ancestor, take `pwd -P`, and re-append the components below it. A lexical walk over a path whose ancestor is a From 2cf73d1a5fe1767c3e3e67a9e0fb23a45ba3e71e Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Sat, 15 Aug 2026 18:03:44 -0400 Subject: [PATCH 8/8] fix(mutation-testing): restore case 9's guard qualification, re-expressed for the new rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The P2 on #2756 is right about case 9. The pushed commit e6bcd8f1 states the self-ignore guard unconditionally, losing both qualifications #2759 published in 0.3.2: "where a checkout governs that root" and "does not run it at all where no checkout governs the destination". Verified by count against the commit, not the working tree: 1 on origin/main, 0 at e6bcd8f1. That is a genuine revert, not supersession — the destination discrimination makes the unqualified form MORE wrong, not less, since the run now refuses every write at an ungoverned resolved root. A graded case on this branch would have certified behavior this PR forbids. Restored from origin/main and then re-expressed, because main's text alone is still wrong under the new rule: it went on to assert "Writes exactly one file" unconditionally. Case 9 now carries the refusal and the ${CLAUDE_PLUGIN_DATA} exception. Case 13 is NOT a revert and is left as-is: main's "SHAPED around it / skipping the guard makes that topology harmless" grades the OLD rule the P2 falsified. Swept all 14 cases against origin/main programmatically rather than by inspection: 14/14 present, only 9 and 13 differ, both deliberate. No other case of main's work was dropped by the earlier rebuild. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/mutation-testing/skills/audit/evals/evals.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/mutation-testing/skills/audit/evals/evals.json b/plugins/mutation-testing/skills/audit/evals/evals.json index 5cae4cdad..60521f4b8 100644 --- a/plugins/mutation-testing/skills/audit/evals/evals.json +++ b/plugins/mutation-testing/skills/audit/evals/evals.json @@ -16,7 +16,7 @@ { "id": 3, "prompt": "Seven mutants survived. Which ones matter?", - "expected_output": "Delegates the productive / arid / equivalent classification to a fresh-context (non-fork) subagent rather than judging in the authoring context, naming this as the self-grade bias class. Hands over the artifact (mutated line, surrounding code, covering tests), not the reasoning that produced the mutants. Prefers a cross-vendor advisor for the equivalence call when one is installed, with the same-vendor fresh-context subagent as the stated fallback. Reports any WITHHOLDING claim that cannot cite its evidence as unclassified rather than as the class claimed — equivalence without a differential demonstration, and aridity without a complete proposed suppression entry whose reason names the behavior the suite deliberately does not assert on.", + "expected_output": "Delegates the productive / arid / equivalent classification to a fresh-context (non-fork) subagent rather than judging in the authoring context, naming this as the self-grade bias class. Hands over the artifact (mutated line, surrounding code, covering tests), not the reasoning that produced the mutants. Prefers a cross-vendor advisor for the equivalence call when one is installed, with the same-vendor fresh-context subagent as the stated fallback. Reports any WITHHOLDING claim that cannot cite its evidence as unclassified rather than as the class claimed — equivalence without a differential demonstration, and aridity without a complete proposed suppression entry whose `claim` binds a node kind from the closed enumerated vocabulary AND whose `reason` names the behavior the suite deliberately does not assert on. Free prose in `claim` fails the test even when the entry is otherwise complete and the reason is good: a survivor fitting no node kind is not arid.", "files": [] }, { @@ -52,7 +52,7 @@ { "id": 9, "prompt": "Run mutation testing on my changes and leave the findings somewhere the fix pass can pick them up.", - "expected_output": "Recognizes this as the --persist-findings path and runs Phase 6 after reporting. Reads the detector-findings producer contract at its raw-URL citation before the first write rather than inventing the destination. Resolves the findings location by running the whole rung order, not only the documented default, and runs the self-ignore guard on the RESOLVED memory root without ever editing the consumer's root .gitignore. Writes exactly one file named `-mutation-survivors.md`, and when that path already exists writes `-2`, then `-3`, taking the smallest free integer >= 2 rather than overwriting. Sets frontmatter `branch:` to `git branch --show-current` verbatim rather than the directory slug. Emits `Location` as a repo-relative `file:line` for the mutated line, `Surface(s)` as `mutation-testing:audit`, and escapes literal pipes in the `Finding` and `Action` cells because mutation fragments carry them. Names the covering test file inside `Action` and states that the remediation site is NOT the row's `Location` -- a mutation finding's fix lands in the test, not at the mutated node, and a consumer that fences each fix to `Location` would otherwise have to guess the target or breach its own fence. Does not restate the findings-file table anywhere.", + "expected_output": "Recognizes this as the --persist-findings path and runs Phase 6 after reporting. Reads the detector-findings producer contract at its raw-URL citation before the first write rather than inventing the destination. Resolves the findings location by running the whole rung order, not only the documented default, and runs the self-ignore guard on the RESOLVED memory root -- where a checkout governs that root -- without ever editing the consumer's root .gitignore, and does not run it at all where no checkout governs the destination -- where a RESOLVED ROOT has no governing checkout it writes nothing at all, neither the guard's `.gitignore` nor the findings file, reporting the resolved destination instead; the contract's `${CLAUDE_PLUGIN_DATA}` fallback is the one destination still written there, being outside every checkout by construction. Otherwise writes exactly one file named `-mutation-survivors.md`, and when that path already exists writes `-2`, then `-3`, taking the smallest free integer >= 2 rather than overwriting. Sets frontmatter `branch:` to `git branch --show-current` verbatim rather than the directory slug. Emits `Location` as a repo-relative `file:line` for the mutated line, `Surface(s)` as `mutation-testing:audit`, and escapes literal pipes in the `Finding` and `Action` cells because mutation fragments carry them. Names the covering test file inside `Action` and states that the remediation site is NOT the row's `Location` -- a mutation finding's fix lands in the test, not at the mutated node, and a consumer that fences each fix to `Location` would otherwise have to guess the target or breach its own fence. Does not restate the findings-file table anywhere.", "files": [] }, { @@ -63,8 +63,8 @@ }, { "id": 11, - "prompt": "Three mutants survived with --persist-findings on: one productive, one arid, one you called equivalent but could not demonstrate. What lands in the file?", - "expected_output": "Emits exactly two rows -- the productive survivor and the undemonstrated-equivalence survivor, both IMPORTANT, both `Confidence: high`. Names the class-keyed map as the source of the tier and does not re-derive severity from the finding's wording. Explains that productive is IMPORTANT rather than CRITICAL because a survivor proves the suite is blind, not that the source produces a wrong result, and that the unclassified survivor ranks WITH productive rather than below it so an undemonstrated equivalence claim cannot bury a real gap. Emits NO row for the DEMONSTRATED arid survivor, because its only remediation is a suppression entry the user must accept and handing that to an apply relay would launder the consent gate -- while still showing it to the human in the Phase 5 report; an aridity claim with no complete proposed suppression entry is unclassified instead and emits. Never emits `Confidence: low`, which ranks below omitting the field.", + "prompt": "Three mutants survived with --persist-findings on: one productive, one arid with a complete proposed suppression entry whose claim names a node kind from the vocabulary and whose reason names the specific behavior the suite leaves unasserted, one you called equivalent but could not demonstrate. What lands in the file?", + "expected_output": "Emits exactly two rows -- the productive survivor and the undemonstrated-equivalence survivor, both IMPORTANT, both `Confidence: high`. Names the class-keyed map as the source of the tier and does not re-derive severity from the finding's wording. Explains that productive is IMPORTANT rather than CRITICAL because a survivor proves the suite is blind, not that the source produces a wrong result, and that the unclassified survivor ranks WITH productive rather than below it so an undemonstrated equivalence claim cannot bury a real gap. Emits NO row for the DEMONSTRATED arid survivor, because its only remediation is a suppression entry the user must accept and handing that to an apply relay would launder the consent gate -- while still showing it to the human in the Phase 5 report; an aridity claim with no complete proposed suppression entry binding a node kind is unclassified instead and emits. Never emits `Confidence: low`, which ranks below omitting the field.", "files": [] }, { @@ -76,7 +76,7 @@ { "id": 13, "prompt": "Our memory_dir points at a shared directory outside this worktree. Will --persist-findings write there?", - "expected_output": "Yes, once EACH of the phase's two writes -- the findings file and the self-ignore guard's `.gitignore` -- is proven outside tracked space before THAT write is made (per-write, not both up front, since on a fresh root the guard's file is what makes the findings file's probe pass). Makes the resolved root physical first via `pwd -P` from its nearest existing ancestor, so a symlinked ancestor cannot hide a checkout. Then resolves the governing checkout from TWO signals that must BOTH come back empty before the permissive branch: a walk of that physical path's ancestors for a `.git` entry using `test -e` (not `-d`, since a worktree's `.git` is a file), AND `git rev-parse --show-toplevel` run under the AMBIENT environment. Neither alone: rev-parse cannot distinguish no-repository from a missing directory, a dangling gitdir, or a discovery limit such as GIT_CEILING_DIRECTORIES -- all exit 128 -- while the walk cannot see a working tree designated by GIT_WORK_TREE or GIT_DIR. Names `core.worktree` as a topology defeating both, and says that where BOTH signals come back empty the run writes NOTHING -- not the guard's `.gitignore`, not the findings file -- reporting the resolved destination and stopping. Gives the reason the findings file is withheld too: its destination may be an index-tracked DELETION in the checkout the detection missed, where writing modifies a tracked file rather than creating an untracked one, which 'read-only with respect to tracked source' does not admit. Treats a bare repository reaching that branch as a conservative refusal indistinguishable from `core.worktree`, never as a case to write through. Names the ONE exception: the contract's `${CLAUDE_PLUGIN_DATA}` fallback for a rootless directory is written normally, since it sits outside every checkout by construction and so cannot be a tracked deletion -- the rule discriminates between destinations rather than stopping every write. With a governing checkout T: rejects a root-equivalent memory_dir judged against T; proves the guard's write BEFORE the guard makes it via `git -C T ls-files -- `, requiring exit 0 AND empty output -- a fatal ls-files prints nothing to stdout and exits 128, so reading output alone lets a failure pass for a clean root; only then runs the guard and `git -C T check-ignore -q -- `, writing only on exit 0. Reports exit 1 ('the destination is tracked space') distinctly from any other exit ('the probe did not evaluate this path'), refusing on both. Anchors the probes to a governing checkout, never the invoking worktree, because check-ignore against an outside path is fatal with exit 128 -- a worktree-anchored probe would refuse every write in a layout the fanout fix action supports explicitly -- and notes that `git -C` alone is not an anchor, since GIT_DIR and GIT_WORK_TREE override it. Not a path-prefix comparison, which would need BOTH paths canonicalized where realpath and readlink's canonicalizing flag are GNU-only.", + "expected_output": "Yes, once EACH write the phase makes -- the findings file, and the self-ignore guard's `.gitignore` where a governing checkout was found -- is proven outside tracked space before THAT write is made (per-write, not both up front, since on a fresh root the guard's file is what makes the findings file's probe pass). Where NO governing checkout is found, the guard does not run at all and the findings file is the only write: there is no repository to keep it out of, and the guard's create-when-absent rule could otherwise land on a `.gitignore` absent from disk but tracked in an undiscovered checkout, modifying committed content with the tracked-file check never having run. Makes the resolved root physical first via `pwd -P` from its nearest existing ancestor, so a symlinked ancestor cannot hide a checkout. Then resolves the governing checkout from TWO signals that must BOTH come back empty before the permissive branch: a walk of that physical path's ancestors for a `.git` entry using `test -e` (not `-d`, since a worktree's `.git` is a file), AND `git rev-parse --show-toplevel` run under the AMBIENT environment. Neither alone: rev-parse cannot distinguish no-repository from a missing directory, a dangling gitdir, or a discovery limit such as GIT_CEILING_DIRECTORIES -- all exit 128 -- while the walk cannot see a working tree designated by GIT_WORK_TREE or GIT_DIR. Names `core.worktree` as a topology defeating both signals, and says that where BOTH signals come back empty the run writes NOTHING at a resolved root -- not the guard's `.gitignore`, not the findings file -- reporting the resolved destination and stopping. Gives the reason the findings file is withheld too: its destination may be an index-tracked DELETION in the checkout the detection missed, where writing modifies a tracked file rather than creating an untracked one, so 'it lands untracked' is not universally true. Names the ONE exception: the contract's `${CLAUDE_PLUGIN_DATA}` fallback for a rootless directory is written normally, since it sits outside every checkout by construction and cannot be a tracked deletion -- the rule discriminates between destinations rather than stopping every write. Treats a bare repository reaching that branch as a conservative refusal indistinguishable from `core.worktree`, never as a case to write through. With a governing checkout T: rejects a root-equivalent memory_dir judged against T; proves the guard's write BEFORE the guard makes it via `git -C T ls-files -- `, requiring exit 0 AND empty output -- a fatal ls-files prints nothing to stdout and exits 128, so reading output alone lets a failure pass for a clean root; only then runs the guard and `git -C T check-ignore -q -- `, writing only on exit 0. Reports exit 1 ('the destination is tracked space') distinctly from any other exit ('the probe did not evaluate this path'), refusing on both. Anchors the probes to a governing checkout, never the invoking worktree, because check-ignore against an outside path is fatal with exit 128 -- a worktree-anchored probe would refuse every write in a layout the fanout fix action supports explicitly -- and notes that `git -C` alone is not an anchor, since GIT_DIR and GIT_WORK_TREE override it. Not a path-prefix comparison, which would need BOTH paths canonicalized where realpath and readlink's canonicalizing flag are GNU-only.", "files": [] }, {