Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0c20918
docs(topic-docs): the self-ignore guard does not run where no checkou…
kyle-sexton Aug 15, 2026
d20945e
chore(mutation-testing): bump to 0.2.1 for the guard-rule pointer
kyle-sexton Aug 15, 2026
28b37cd
Merge remote-tracking branch 'origin/main' into fix/self-ignore-guard…
kyle-sexton Aug 15, 2026
30f81be
fix(review): defer on the guard's invalid roots instead of reading un…
kyle-sexton Aug 15, 2026
45aa5f9
Merge remote-tracking branch 'origin/main' into fix/self-ignore-guard…
kyle-sexton Aug 15, 2026
bc8f111
fix(architecture): defer on the guard's invalid roots in this binding…
kyle-sexton Aug 15, 2026
5987fee
Merge remote-tracking branch 'origin/main' into fix/self-ignore-guard…
kyle-sexton Aug 15, 2026
77afb1c
Merge origin/main into fix/self-ignore-guard-no-checkout
cursoragent Aug 15, 2026
2fcf1b9
fix(conventions): withhold every write where no checkout can be shown…
kyle-sexton Aug 15, 2026
ac19a7a
Merge origin/fix/self-ignore-guard-no-checkout: discriminate destinat…
kyle-sexton Aug 15, 2026
27f1da4
Merge origin/main into fix/self-ignore-guard-no-checkout
cursoragent Aug 15, 2026
dd46871
Merge origin/main: keep main's improvements, keep the destination dis…
kyle-sexton Aug 15, 2026
676fef2
chore(mutation-testing): fix MD012 double blank in CHANGELOG
cursoragent Aug 15, 2026
27349e3
Merge origin/fix/self-ignore-guard-no-checkout: reconcile the two mai…
kyle-sexton Aug 15, 2026
bd4a2e5
Merge remote-tracking branch 'origin/fix/self-ignore-guard-no-checkou…
kyle-sexton Aug 15, 2026
e6bcd8f
fix(mutation-testing): restate the step-1 proof for the write that su…
kyle-sexton Aug 15, 2026
2cf73d1
fix(mutation-testing): restore case 9's guard qualification, re-expre…
kyle-sexton Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/conventions/detector-findings/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.2 — 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, #2756 follow-through on #2715)

## 2.0.1 — 2026-08-15

Patch: no rule changes, one statement corrected to match the rule it was already describing.
Expand Down
15 changes: 12 additions & 3 deletions docs/conventions/detector-findings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,18 @@ 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
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
Comment thread
kyle-sexton marked this conversation as resolved.
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. **The artifact write is not automatically safe there either**:
recreating a path that is an *index-tracked deletion* in a missed checkout modifies tracked state
rather than creating an untracked one (measured), so "it lands untracked" is not universally true.
But a blanket refusal is the wrong correction — it would refuse the `${CLAUDE_PLUGIN_DATA}`
fallback the convention routes non-interactive runs to, which sits outside every checkout **by
construction** and cannot be a tracked deletion. The rule follows that distinction: write where the
destination is that plugin-data surface, and where it is a resolved root no checkout could be shown
to govern, report the resolved destination and persist nothing.

## Boundary

Expand Down
37 changes: 37 additions & 0 deletions docs/conventions/topic-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
(<https://github.com/melodic-software/claude-code-plugins/issues/2680>)

## 2.4.4 — 2026-08-15

Docs-only: the Memory, concern-scoped tier row now names `.work/running-retros/`
Expand Down
39 changes: 38 additions & 1 deletion docs/conventions/topic-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,44 @@ 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.
**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. **One
destination is exempt and it is the common one**: the
`${CLAUDE_PLUGIN_DATA}` fallback above is outside every checkout by
construction, so it cannot be a tracked deletion and needs no refusal —
which is why this rule is a discrimination between destinations rather
than a blanket stop. What it refuses is a *resolved root* no checkout
could be shown to govern.
- No plugin ever edits the consumer's root `.gitignore`.

## Slug and filename spec
Expand Down
2 changes: 1 addition & 1 deletion plugins/architecture/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
12 changes: 12 additions & 0 deletions plugins/architecture/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions plugins/architecture/reference/topic-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
How the `improve` skill resolves the destination for its durable per-topic artifact.

Implements the topic-docs convention:
<https://raw.githubusercontent.com/melodic-software/claude-code-plugins/main/docs/conventions/topic-docs/README.md>.
<https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/topic-docs/README.md#runtime-guards>.
The contract owns every general rule — tiers, schema, resolution order, slug spec, runtime guards,
no-project-root fallback, non-interactive/forked mode. This document records only this plugin's
deltas.
Expand Down Expand Up @@ -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://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.
2 changes: 1 addition & 1 deletion plugins/mutation-testing/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "mutation-testing",
"version": "0.3.2",
"version": "0.3.3",
"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",
Expand Down
22 changes: 19 additions & 3 deletions plugins/mutation-testing/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.3.3]

### 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.3.2]

### Fixed
Expand Down Expand Up @@ -113,7 +123,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
Expand All @@ -130,8 +140,14 @@ 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**, with one
exception: 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. The exception is the contract's
`${CLAUDE_PLUGIN_DATA}` fallback for a rootless directory — outside every checkout by
construction, so no tracked deletion can hide there and refusing it would strand the one
destination a headless run on such a directory is meant to use. 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
Expand Down
9 changes: 6 additions & 3 deletions plugins/mutation-testing/skills/audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,12 @@ are not optional: the destination comes from the contract's **whole** rung order
**each** write this 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, against
the checkout that governs the destination rather than the invoking worktree, with the guard's own
write proven before the guard heals rather than reported afterwards, and with the guard **not run at
all** where no governing checkout was found, since there its create-when-absent rule could land on a
tracked-but-deleted `.gitignore` with no check having been possible (a memory root inside tracked space leaves `git status`
write proven before the guard heals rather than reported afterwards, and with **nothing written at
all** where a resolved root has no governing checkout — the guard's create-when-absent rule could
land on a tracked-but-deleted `.gitignore` with no check having been possible, and the findings file
on a tracked deletion it would modify rather than create — while the contract's
`${CLAUDE_PLUGIN_DATA}` fallback is written normally, being outside every checkout by construction
(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
Expand Down
Loading
Loading