From bdc81b5aa425b4d448adebc337e4f930d8a4dcb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:41:31 +0200 Subject: [PATCH 01/11] docs(intake): add hardening-round-0-8-0-and-pr-21 story --- ...3-hardening-round-0-8-0-and-pr-21-story.md | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md diff --git a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md new file mode 100644 index 0000000..c5a48c1 --- /dev/null +++ b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md @@ -0,0 +1,109 @@ +# Hardening round — the 0.8.0 cycle and PR #21 — Story + +**Date:** 2026-08-03 · **Size:** story +**Risk:** standard · **Security:** none · **Validation:** battery+check + +**Profile rationale:** not an entry in the profile log — no value has moved, and the log's +three kinds (axis change, mode override, adoption) all record a change. Recorded here +because the delimitation is load-bearing: the classifier story that produced these findings +was `high`, this round is `standard`, and the line between them is executable enforcement +core vs. prompt discipline. Shipping downstream is true of both and must not by itself +raise risk — an axis that fires on every artifact this repo ships stops discriminating and +dies. + +## 1. Problem statement + +The 0.8.0 cycle and PR #21 produced findings that were fixed but never hardened. Commit +23b842d says so in its own body: "Not done in this cycle, and owed: harden-finding on +these five." Two ledger classes — `docs-drift` and `unverified-enforcement-claim` — now +sit at six-plus occurrences each, and the ledger's own escalation reflex reads a sixth +occurrence as "escalate a rung." + +That reflex is the second problem. `harden-finding` compares fingerprints only; rungs +guard *scopes*. The 2026-07-26 rows warn by name that a later in-class defect **outside** +the guarded spelling will be proposed for a stronger rung than anything justifies, and the +workaround for it lives in ledger prose, which agents do not read — they read the skill. +Daniel has now had to issue that precheck as a manual instruction, which is the parked +row's own diagnosis demonstrating itself. + +Two further conditions have come due: several lessons from the cycle (single-shell test +blindness, mechanical sweep before read-pass, fixture-per-branch perf oracles, incomplete +negative bounds statements) have no ledger record at all; and one defect shape — a check +that supplies the thing it tests — has no home in the taxonomy. + +A third, the ledger's missing supersession convention, is **deliberately out of this +round's scope.** Its trigger has fired — the 2026-07-20 row teaches pre-0.8.0 counting +behaviour as current, and the parked row's trigger reads "the next falsified row — this is +the second." A fired trigger that nobody records evaporates, so this round's deliverables +include opening the intake for it as its own story rather than folding a ledger-format +change into a round about rungs. + +## 2. Desired outcome + +Every finding from the 0.8.0 cycle and PR #21 carries a recorded disposition: a ledger row +at a rung that fits, or a parked backlog row with a stated trigger. Escalation decisions +are made against what the prior rung *claimed to guard*, not against an occurrence count — +and that reading is something the skill instructs, so the next round does it without +Daniel saying so. The ledger-supersession work leaves this round as a story of its own, +with its trigger recorded rather than remembered. And the round leaves the repository +smaller than the findings would suggest, because items that turn out to need design are +parked rather than squeezed in. + +## 3. Acceptance criteria + +- [ ] Each of the seven findings in this round's scope has exactly one recorded disposition + — a `docs/hardening-log.md` row at a named rung, or a `todos.md` row with a stated + trigger. None is left without one. +- [ ] Every escalation decision in the round records which prior row's guard was read and + whether the finding falls inside or outside it. Outside → the fitting rung, no + escalation. Inside → recorded as a regression of that mechanism, repaired or strengthened. +- [ ] Neither six-plus-occurrence class escalates on count alone; each row states the guard + it was measured against and why the chosen rung fits. +- [ ] `harden-finding` instructs the guard-scope precheck, with its two branches pointing the + direction the parked row states — the known inverted-branches hazard is named in the + Gate-A prompt for this round and the reviewer confirms the direction. +- [ ] The parked scope-blind row records this round as its third evidence case. +- [ ] The check-supplies-its-own-input defect lands as exactly one of: an extension of the + existing counterfactual prose, or a new class in `docs/hardening-taxonomy.md` — with + the choice justified against that file's "before minting, grep for a near match" rule. +- [ ] A story for the ledger-supersession convention exists on disk, produced by `intake`, + carrying that work's fired trigger and its central design question: whether the + convention must also reach `/workflow-init`'s inline ledger-header template, since a + repo-only fix would ship a ledger rule this kit's own ledger obeys and every + scaffolded one does not. +- [ ] Each of the three parked rows whose trigger names the §5 template (Finding A, + Finding B, and the cycle-record-destruction row) states that the trigger reads a + change to §5's substance, not any touch of it. +- [ ] If the `harden-finding` edit requires anything beyond a rule paragraph — row metadata, + fingerprint-scope fields, ledger format changes — the round stops, records evidence + case 3 only, and the skill change becomes its own story. +- [ ] If two findings pull the same artifact in opposite directions, the round stops and + surfaces rather than choosing. +- [ ] Validation mode `battery+check` is satisfied: the full quality battery green, plus a + check that fails without the change, with its counterfactual stated. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "10. **The base taxonomy stays + stack-neutral.** Project vocabulary … goes only in that project's + `docs/hardening-taxonomy.md`, never into the `harden-finding` skill." +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." +- `## Don'ts` — "**Never replace a decision procedure without accounting for its old + conditions.** List what the previous prose required, then mark each one kept, moved, or + deliberately dropped." +- `## Don'ts` — "**Never describe what a gate proves without checking what it actually + compares.**" +- `## Don'ts` — "**Never rename or delete a doc section without grepping for references + first.**" + +## 5. Open questions + +- None. + +## 6. Suggested size + +`story` — one coherent round with one spec → plan → PR, as #16 was. Seven findings is above +that precedent's three, and the parking discipline plus the stop-on-machinery rider are what +keep it from being an epic; if either fires, the split point is already named. From 9f1c092c3ba9ca142a1e46118feb1758708f4e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Mon, 3 Aug 2026 19:50:54 +0200 Subject: [PATCH 02/11] docs(spec): hardening round over the 0.8.0 cycle and PR #21 --- ...-hardening-round-0-8-0-and-pr-21-design.md | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md diff --git a/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md new file mode 100644 index 0000000..108bec2 --- /dev/null +++ b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md @@ -0,0 +1,216 @@ +# Hardening round — the 0.8.0 cycle and PR #21 — Design + +**Story:** `docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md` +— the profile lives in that story's header and is read fresh at every pass. No value from +it is copied here. + +**Scope:** findings 1–6 and 8 of the story. Finding 7 (the ledger's missing supersession +convention) is split out; opening its intake is a deliverable of this round (§8). + +## 1. What this round is + +Seven findings from the 0.8.0 cycle and PR #21, each processed through +`dev-workflow:harden-finding` with one standing precheck: **before proposing any +escalation, read the prior row's stated guard.** Outside that guard → the fitting rung, no +escalation. Inside → the mechanism was meant to catch this and did not, which is a +regression to repair. + +The round also lands that precheck in the skill itself, because a rule that only exists as +a human instruction is the defect the parked row names. + +## 2. Decision record + +| # | Decision | Why | Rejected alternative | +|---|---|---|---| +| D1 | The round **edits `CLAUDE.md` §5** and its shipped mirror | Three findings' homes are in §5. Relocating them to a non-shipping file to honour a scope sentence optimizes the artifact instead of the defect | Land them in `AGENTS.md` Don'ts — repo-local, so the shipped lens could not be strengthened at all and downstream projects inherit nothing | +| D2 | The three parked §5 triggers **fire on a change to the mechanism each row names**, not on adding or sharpening a rule inside §5 | Each row is about a specific mechanism — the file/slot protocol, the template sync contract, the ledger route. None is touched here | A literal touch reading, which fires two triggers whose items each need their own design, inflating the round | +| D3 | The story's "doesn't touch §5 at all post-split" is **corrected inline as an amendment** naming what it replaced | It was a prediction made before the homes were traced. Silently deleting it would be `rewrite-drops-prior-condition` | Honouring the sentence by relocating the edits (see D1) | +| D4 | F2's evidence half, F3, F4 and F6 all fingerprint **`verification-masks-failure`** and resolve at one site | Same failure: a check reports success because of how it was wired. The taxonomy's own rule prefers a reused near-miss over a precise class nobody greps | Minting `single-environment-test-blindness` and `perf-oracle-fixture-gap`, splitting a four-occurrence signal into three that never recur | +| D5 | Each §5 edit is **one clause at the exact existing site** | §5 is the repo's most load-bearing prompt and draws concentrated Gate-A fire. A finding needing a full paragraph is a finding whose home is wrong | Rewriting the surrounding paragraphs, which is how conditions get dropped | +| D6 | `prompt-standards.md` item 11 gains a **sub-clause, not a 13th item** | A new numbered item ripples the count through three files pinned by check 4b — the escalation the 2026-07-27 row refused by name | Item 13 | +| D7 | The skill edit is **all prose**: step 3 gains three branches, step 7 one sentence | No new column, no metadata, no format change. The step-7 sentence is guidance about the existing free-text `ref` | A scope field on the log row, which is the machinery the story exits on | +| D8 | Version **0.9.0** | `#16` (rules added to shipped prompts) took a patch; `#15` (a skill's procedure changed) took a minor. This round does both | 0.8.1, which understates a decision-procedure change | + +## 3. The precheck, in `harden-finding` + +**Step 3 (Recurrence check).** The bullet reading "latest matching row is a real rung +(1–4/P) → the prior rung didn't hold; propose **one rung stronger**, with reasoning" is +replaced by: + +> - latest matching row is a real rung (1–4/P) → **read that row's stated guard before +> proposing anything.** Rungs guard scopes; a fingerprint match is not a scope match. +> - **outside** the guard → that mechanism never claimed this shape, so it did not fail: +> pick the rung that fits this finding, and do not escalate. +> - **inside** the guard → the mechanism was meant to catch this and did not; that is a +> regression. Repair or strengthen it, and say what changed and why the old form missed +> this case. +> - **no determinable guard** (older rows often state none) → quote the `ref` text you +> read, say what it leaves undetermined, then pick the fitting rung. Never escalate on +> the occurrence count alone. The quote is what keeps this branch from becoming the +> default: "nothing determinable there" is a conclusion reached after reading, and +> quoting makes that claim checkable. + +**Step 7 (Log).** Gains one sentence: + +> State in the `ref` what the rung you land actually guards — the exact spelling, path, or +> rule it covers — so the next recurrence can be judged against a scope rather than a count. + +**Direction check, for the reviewer.** An earlier draft of this rule had the two branches +inverted, which would have entrenched the bug it was filed against. Outside the guard means +*no escalation*; inside means *regression*. Read them in that direction. + +## 4. The four clause edits + +Each lands at the site named, mirrored into +`plugins/dev-workflow/commands/workflow-init.md` in the same commit. + +### 4.1 §5 Gate B standing lens — finding 1 + +Site: the `CLAUDE.md` §5 Gate B paragraph opening **"Standing lens, every Gate-B call"**. +Cited by anchor rather than line number throughout this spec, because a moved line citation +is finding 1's own C3. + +Appended to the existing lens paragraph: + +> **Name the sets this diff changes the size or position of** — a list that gained a +> member, a count, a cited line or path, a version string — and grep for where each is +> described elsewhere. Asked as an open question alone, this lens missed three such +> statements in one cycle while being carried with unusual force; recall does not enumerate +> a set, a search does. The search is still something you run, not something that runs. + +**Diagnosis this rests on.** The lens *was* carried on the 0.8.0 cycle, and the plan's +execution notes record it being carried "with unusual force". It still missed C1–C3, and +all three are one shape: the diff changed the membership of a set described elsewhere — a +prerequisite list gained `dash`, a run count went four to five, a cited line moved. The +open question asks for recall; enumerating a set needs a search. + +**What this does not do.** It adds no mechanical component. The grep is greppable, and +nothing runs it or validates the answer. A lens that failed under force needs a sharper +question, and this is one; it is not a check, and the ledger row says so. + +### 4.2 §5 Profiles counterfactual — findings 2 (evidence half), 3, 4, 6 + +Site: the `CLAUDE.md` §5 Profiles paragraph containing **"Either route owes the +counterfactual"**. + +Appended to the counterfactual paragraph: + +> The counterfactual has two halves, and the second is the teeth: **name the observation +> that would exist if the claim were false, and confirm the wiring could have produced that +> observation.** A check that supplies its own input, runs where the defect cannot appear, +> or uses a fixture that never reaches the branch it covers has a falsifying observation in +> principle and no way to produce it — it reports success because of how it was wired, not +> because the thing it checks succeeded. + +### 4.3 §5 Gate A pass procedure — finding 5 + +One sentence, after "Each pass: validate, revise, re-run": + +> Before each read pass, run what a machine can check on the artifact's own assertions — +> fenced blocks through a parser, cited paths and line numbers, counts, referenced commands +> — and fix what it reports first, so the read pass spends its attention on what no parser +> decides: on this repo's largest plan a `sh -n` sweep found in seconds defects that eight +> read passes had missed. + +### 4.4 `prompt-standards.md` item 11 sub-clause — findings 2 (claim half), 8 + +> **State the claim's scope, and never let a negative imply the rest is covered.** "It +> bounds the scan, not memory" named one thing the mechanism does not do and left the +> reader to infer it does the others — the bound was a size bound, the work was quadratic, +> and time went unbounded and unnoticed for three drafts. When you name what a mechanism +> does not cover, name the axes you checked, or say you enumerated one and did not check +> the rest. + +The checklist stays at 12 items, so check 4b does not ripple. + +## 5. The eight ledger rows + +Seven findings, eight rows: finding 2 splits, because its two halves are different classes. + +| Row | Story finding | Class | Occ | Precheck verdict | Rung | +|---|---|---|---|---|---| +| 1 | F1 — C1–C3, PR #21 | `docs-drift` | 5 | **inside** the 2026-07-27 lens — it was carried and missed all three | `P std` | +| 2 | F2, claim half — C4–C5, PR #21 | `unverified-enforcement-claim` | 5 | **outside** item 11 — a README and a story are not prompt artifacts, which is the scope item 11 claims | `P std` | +| 3 | F8 — "bounds not memory" | `unverified-enforcement-claim` | 6 | **outside**, same guard, same reason | `P std` | +| 4 | F3 — `$EVIDENCE` dry run | `verification-masks-failure` | 2 | **outside** the 2026-07-20 row, whose `ref` states its own scope: "nothing checks new plans for the same shape" | `P std` | +| 5 | F4 — single-shell regression test | `verification-masks-failure` | 3 | **outside**, same row, same reason | `P std` | +| 6 | F6 — timed row, unreached branch | `verification-masks-failure` | 4 | **outside**, same row, same reason | `P std` | +| 7 | F2, evidence half — dash coverage claimed in release evidence | `verification-masks-failure` | 5 | **outside**, same row, same reason | `P std` | +| 8 | F5 — read pass before the sweep | `mechanical-check-skipped-before-review` | new | — | `P std` | + +Finding 2 occupies rows 2 and 7: its C4–C5 half is a claim nothing verified, its evidence +half is a check that could not fail. Different classes, different fixes, so different rows. + +**Rows 4–7 move `1 prose` → `P std`, and that is not an escalation.** The ladder puts +prompt artifacts on rung P; the counterfactual rule lives in `CLAUDE.md` §5, which §5 itself +classifies as product rather than prose. The rung follows the artifact, not the count. Each +row states this, because a reader comparing rung names alone will read it as a stealth +escalation. + +**Each of rows 4–7 answers the clause's two questions for its own case**, so a future grep +lands on worked examples rather than an abstraction. + +**Occurrence counts are derived from the ledger, and two differ from the story's estimate.** +The story says `docs-drift` and `unverified-enforcement-claim` are both at "occurrences 6+". +Counting the rows: `docs-drift` reaches 5 here (2026-07-18 twice, 2026-07-25 as `pending`, +2026-07-26 resolving that row rather than recording a new defect, 2026-07-27 as the fourth), +and `unverified-enforcement-claim` reaches 5 and 6. The correction is stated rather than +quietly applied, because an unchecked count is the class this round hardens. + +**Row 8 mints one class**, in `docs/hardening-taxonomy.md`: + +> - `mechanical-check-skipped-before-review` — an artifact carrying machine-checkable +> assertions goes to an expensive read pass before anything parses it, so attention is +> spent on what a tool decides in seconds. Aliases: `sh -n` after the fact, the parser +> would have caught it, read pass before the sweep, manual review of machine-decidable +> claims. + +Its boundary against `verification-masks-failure`: there, a check ran and could not fail; +here, the cheap check never ran at all. + +## 6. Everything else the round ships + +- `todos.md` — the three parked §5-trigger rows each gain the D2 half-sentence; the + scope-blind row records this round as evidence case 3 and is marked done with what shipped. +- `docs/hardening-taxonomy.md` — the row-8 class. +- `plugins/dev-workflow/CHANGELOG.md` and `.claude-plugin/plugin.json` — 0.9.0. +- The story file — the D3 amendment. + +## 7. Validation evidence + +Mode is read from the story header at each pass. As of this writing it derives +`battery+check`, which owes: + +- **battery** — the full quality command from `AGENTS.md § Commands`, green. +- **check, with its counterfactual** — apply §4.2's clause as worded to the four cases in + rows 4–7. **Each must fail it.** The counterfactual is that each of the four passed + before: all four shipped, and three of them shipped a defect behind a green check. + +## 8. The follow-on intake + +Finding 7 gets its own story, produced by `dev-workflow:intake` as a deliverable of this +round, carrying its fired trigger (`docs/hardening-log.md`'s 2026-07-20 row now teaches +pre-0.8.0 counting behaviour as current) and its central design question: whether the +convention must also reach `/workflow-init`'s inline ledger-header template, since a +repo-only fix ships a rule this kit's ledger obeys and every scaffolded one does not. + +## 9. Gate-A riders, verbatim in every pass prompt + +1. **Direction check.** Confirm the §3 precheck's branches point the direction the parked + row states: outside the guard → fitting rung, no escalation; inside → regression to + repair. An earlier draft had them inverted. +2. **Sweep before reading.** Before the read pass, mechanically validate whatever this spec + asserts that a machine can check — line citations, counts, quoted file contents, grep + claims — and report what the sweep found separately from what the read found. +3. **Four-case self-test.** Apply §4.2's clause as worded to the four cases in rows 4–7. + Each must fail it. A clause all four would have passed is miswired. + +## 10. What this round does not do + +- It adds no mechanical check. Every rung here is `P std`; a reader is the detection. +- It does not claim the strengthened lens catches the class. It sharpens one question and + names a search; nothing runs the search. +- It does not resolve the ledger's supersession gap — §8 opens that, and until it lands the + 2026-07-20 row still reads as current. +- The precheck depends on rows stating their guard. Older rows do not, which is why §3 has a + third branch rather than a promise. From b21f468b1da0e7800c7365aa943b85359032dbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:11:16 +0200 Subject: [PATCH 03/11] docs(spec,story): re-scope the hardening round after eight Gate-A passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The round drops from seven findings landing in one cycle to four hardenings, four ledger rows, one parked row and four split stories. Two exits fired on evidence rather than judgement: - The harden-finding guard-scope precheck left the round. Four Gate-A findings independently demanded row metadata, a ledger format change and a collision state machine, which is the machinery exit the story names. It becomes its own story carrying six observed failures and their open questions. - The §5 additions were cut back to one sentence each after review found they had grown into paragraphs, one of them a whole procedure. C4 and C5 leave the ledger for a parked row. Both fall inside the 2026-07-19 Don't, whose operative instruction already requires what they omitted, so no textual repair exists to log — a compliance recurrence, not a rule defect. Their placement had moved four times across four passes, which is the count prompt-standards item 11 names for stopping rather than refining again. Version is 0.8.1, not 0.9.0: with the skill change split out the round is #16's shape, and #16 took a patch. The #15 minor precedent applied to a skill's procedure changing, which this round no longer does. Gate A: eight passes, all valid under the file-first protocol, every finding dispositioned in writing under .context/codex-reviews/. Pass 8 was the first where all riders passed — both self-tests (§5.2 rejects its four cases, §5.4 rejects F8) and the per-case precheck audit. The final pass was not clean; its seven findings are fixed and re-verified, and pass 9 runs against these bytes. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...-hardening-round-0-8-0-and-pr-21-design.md | 695 +++++++++++++----- ...3-hardening-round-0-8-0-and-pr-21-story.md | 163 +++- 2 files changed, 617 insertions(+), 241 deletions(-) diff --git a/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md index 108bec2..9ef8284 100644 --- a/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md +++ b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md @@ -1,216 +1,507 @@ # Hardening round — the 0.8.0 cycle and PR #21 — Design **Story:** `docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md` -— the profile lives in that story's header and is read fresh at every pass. No value from -it is copied here. - -**Scope:** findings 1–6 and 8 of the story. Finding 7 (the ledger's missing supersession -convention) is split out; opening its intake is a deliverable of this round (§8). - -## 1. What this round is - -Seven findings from the 0.8.0 cycle and PR #21, each processed through -`dev-workflow:harden-finding` with one standing precheck: **before proposing any -escalation, read the prior row's stated guard.** Outside that guard → the fitting rung, no -escalation. Inside → the mechanism was meant to catch this and did not, which is a -regression to repair. - -The round also lands that precheck in the skill itself, because a rule that only exists as -a human instruction is the defect the parked row names. - -## 2. Decision record +— the profile lives in that story's header and is read fresh at every pass. No value from it +is copied here. + +**Scope:** F1–F6 and F8 of the enumeration in §1. F7 and the `harden-finding` change are split +out (§4, §8). + +**How to read this document.** It states decisions and their reasons. It does not record how +those decisions were revised — that history lives in `.context/codex-reviews/gate-a-spec-pass-*-dispositions.md`, +one file per review pass. Keeping it there is deliberate: a correction narrative inside the spec +is itself a claim that can be wrong, and this one accumulated faster than the design changed. + +## 1. The findings, enumerated + +The story describes these in prose and does not number them. This table is the authoritative +enumeration for the round. + +**C1–C5** are this spec's labels for the five unlabelled corrections in commit `23b842d`, in +the order that commit body lists them: **C1** the `dash` prerequisite undocumented in +`AGENTS.md`; **C2** the plan's "four script runs" for a block holding five; **C3** the spec's +`codex-gate.sh:361` citation, moved by the 0.8.0 rewrite; **C4** the story's unqualified +jq-parity criterion; **C5** the README's "a typo can't quietly unhook a gate". + +| # | Finding | Evidence | +|---|---|---| +| F1 | Three statements the 0.8.0 diff falsified, found by a PR bot rather than by the standing Gate-B lens | C1–C3 of commit `23b842d` | +| F2 | Two unverified claims, plus a release-evidence claim of `dash` coverage the runner never produced | C4–C5 of commit `23b842d`; `docs/superpowers/plans/2026-08-01-gate-pass-result-classification.md`, the paragraph beginning "**The `dash` run needs `HOOK_SH=dash`**" | +| F3 | A dry run that defined `$EVIDENCE` itself, so it proved the `git` mechanics and never tested that the plan defines the variable | `docs/superpowers/plans/2026-08-01-gate-pass-result-classification-execution-notes.md`, "A check that supplies the thing under test is not a check" | +| F4 | A regression test that only ever ran under macOS `sh`, while the defect it guards ships on `dash` | `plugins/dev-workflow/CHANGELOG.md`, 0.8.0, "regression test never caught it because it only ever ran under macOS" | +| F5 | Eight read-only Gate-A passes missed what thirteen machine checks then found in one sweep | the execution notes, paragraph "**The 2026-08-02 sweep was the right response and was not sufficient.**" | +| F6 | Timed regression rows whose fixtures never reach the second quadratic path they are read as bounding | the plan, "the timed rows all use large single-record payloads and never reach (b)" | +| F7 | The ledger has no supersession convention — **split out, see §8** | `todos.md`, the parked row "**The hardening ledger has no supersession convention.**" | +| F8 | A negative bounds statement that named one uncovered axis and left another uncovered and unnoticed | the plan, "It bounds the **scan**, not memory" | + +## 2. What this round is + +Seven findings processed through `dev-workflow:harden-finding`, each disposed at a rung chosen +by reading what the prior rung *claimed to guard* rather than by counting occurrences. **Four +hardenings** land as text — three sentences in `CLAUDE.md` §5 and one clause appended to an +existing `AGENTS.md` Don't — **four ledger rows** record them, one taxonomy class is minted, and +four follow-on stories are written. + +The round does not change `harden-finding` itself. Landing the guard-scope precheck in the +skill was this round's centrepiece until it became clear the precheck cannot work without +settling what a durable record holds, how a guard citation survives the ledger's format, and +what happens when a row appears mid-run — questions a rule paragraph cannot answer (§4). + +## 3. Decision record | # | Decision | Why | Rejected alternative | |---|---|---|---| -| D1 | The round **edits `CLAUDE.md` §5** and its shipped mirror | Three findings' homes are in §5. Relocating them to a non-shipping file to honour a scope sentence optimizes the artifact instead of the defect | Land them in `AGENTS.md` Don'ts — repo-local, so the shipped lens could not be strengthened at all and downstream projects inherit nothing | -| D2 | The three parked §5 triggers **fire on a change to the mechanism each row names**, not on adding or sharpening a rule inside §5 | Each row is about a specific mechanism — the file/slot protocol, the template sync contract, the ledger route. None is touched here | A literal touch reading, which fires two triggers whose items each need their own design, inflating the round | -| D3 | The story's "doesn't touch §5 at all post-split" is **corrected inline as an amendment** naming what it replaced | It was a prediction made before the homes were traced. Silently deleting it would be `rewrite-drops-prior-condition` | Honouring the sentence by relocating the edits (see D1) | -| D4 | F2's evidence half, F3, F4 and F6 all fingerprint **`verification-masks-failure`** and resolve at one site | Same failure: a check reports success because of how it was wired. The taxonomy's own rule prefers a reused near-miss over a precise class nobody greps | Minting `single-environment-test-blindness` and `perf-oracle-fixture-gap`, splitting a four-occurrence signal into three that never recur | -| D5 | Each §5 edit is **one clause at the exact existing site** | §5 is the repo's most load-bearing prompt and draws concentrated Gate-A fire. A finding needing a full paragraph is a finding whose home is wrong | Rewriting the surrounding paragraphs, which is how conditions get dropped | -| D6 | `prompt-standards.md` item 11 gains a **sub-clause, not a 13th item** | A new numbered item ripples the count through three files pinned by check 4b — the escalation the 2026-07-27 row refused by name | Item 13 | -| D7 | The skill edit is **all prose**: step 3 gains three branches, step 7 one sentence | No new column, no metadata, no format change. The step-7 sentence is guidance about the existing free-text `ref` | A scope field on the log row, which is the machinery the story exits on | -| D8 | Version **0.9.0** | `#16` (rules added to shipped prompts) took a patch; `#15` (a skill's procedure changed) took a minor. This round does both | 0.8.1, which understates a decision-procedure change | +| D1 | The round **edits `CLAUDE.md` §5** and its shipped mirror | Three of the four hardening sites are in §5, fed by six of the seven findings. Relocating them to honour a scope preference optimizes the artifact instead of the defect | `AGENTS.md` only — the shipped lens could not be strengthened at all | +| D2 | The three parked §5 triggers are **read literally, each against its own text.** Finding A ("the next round that touches §5") and Finding B ("the next round that touches the §5 template") **fire**; the slot-collision row ("**Each Gate cycle destroys the previous cycle's review record.**", whose trigger names the §5 file protocol) **does not**, because this round never touches the file/slot protocol | They are opportunity triggers — "do this while you are already in §5" — not regression triggers. Narrowing them changes their kind, and for Finding A, whose subject is a route that does not exist yet, the narrowed form is close to unreachable | Narrowing them, which is `rewrite-drops-prior-condition` committed by a round that hardens it | +| D3 | Every trigger **this round fires** is recorded as fired and its story written as a round deliverable | A fired trigger nobody records evaporates | Taking on every trigger already fired in the backlog, which would make any round that touches §5 responsible for unrelated due work | +| D4 | F2's evidence half, F3, F4 and F6 all fingerprint **`verification-masks-failure`** and resolve at one site | Same failure: a check reports success because of how it was wired. The taxonomy prefers a reused near-miss over a precise class nobody greps | Minting two classes that then never recur | +| D5 | Each §5 edit is **one sentence at the exact existing site** | §5 is the repo's most load-bearing prompt, and a finding needing a paragraph is a finding whose home is wrong | Paragraph-sized insertions, which is how §5 accretes and how conditions get dropped | +| D6 | F2's claim half and F8 are repaired by **amending the existing 2026-07-19 `AGENTS.md` Don't**, not by adding a sibling | All three of their cases fall inside that Don't's operative instruction (§6.1), so the fitting response to a guard that did not hold is to strengthen it. A second Don't covering adjacent ground would leave two rules a reader must reconcile | A new Don't, which would record a fresh-scope hardening where a regression actually occurred | +| D7 | The `harden-finding` change is **split into its own story** and this round edits no skill file | The precheck cannot be stated without deciding what a durable record holds, how a guard citation survives the ledger format, and what happens on a mid-run collision. Those are design questions, and answering them inside a hardening round is how a round becomes a redesign | Designing it here | +| D8 | **One ledger row per landed hardening**, each carrying its motivating cases as worked examples | The ledger header says one row per hardening, and both aggregation precedents agree (2026-07-18 logged six claims as one row; 2026-07-27 logged ten instances as one row). A row per case would inflate the recurrence counts that drive escalation, in a round about escalation | One row per case | +| D9 | Version **0.8.1** | With the skill change split out, the round is `#16`'s shape — rules added to a shipped prompt and its template mirror — and `#16` took 0.7.0 → 0.7.1. The `#15` minor precedent applied to a skill's *procedure* changing, which this round does not do | 0.9.0, which follows a precedent this round does not match | + +## 4. What the split story inherits + +The `harden-finding` guard-scope precheck leaves this round. Its story carries these as opening +evidence, each stated as an observed failure and the question it leaves open. `dev-workflow:intake` +and `superpowers:brainstorming` divide capture from design; naming record structures, fields, +formats or state machines here would hand brainstorming a solution to ratify. + +- **Observed:** a recurrence verdict was reached by reading one prior row's guard and stopping, + when an older row's guard covered the case. **Open:** which prior rows a recurrence must be + judged against before "outside" is a safe conclusion. +- **Observed:** a reader cannot tell whether a past rung choice followed from a guard reading or + from an occurrence count, because nothing durable records the reading. **Open:** what a later + reader needs in order to check a scope decision, and where it belongs. +- **Observed:** guards are frequently exact spellings containing regex alternation, and the + ledger's stated escaping rule covers the `finding` column. **Open:** how a guard citation + survives a Markdown table without changing what the columns mean. +- **Observed:** step 7 re-reads the ledger and then appends, so a row landing after that read is + not seen at all, and a row seen at the read is treated as a duplicate without anyone + consulting what it guards. **Open:** what should happen when a row appears mid-run, including + when it is `pending` or its guard cannot be placed. +- **Observed:** widening the branch condition from "latest matching row is a real rung" to "a + matching row exists at a real rung" makes it fire when the latest row is `pending`, bypassing + the prerequisite rule. **Open:** how a guard scan and a prerequisite block compose. +- **Observed:** the old step-3 text required proposing one rung stronger on recurrence, and a + replacement that chooses "the rung that fits the repair" does not carry it. **Open:** whether + automatic escalation on recurrence is kept, narrowed, or deliberately dropped, with a stated + reason either way. + +**Evidence case 3 is recorded in this round** (§9). The parked scope-blind row earns it because +the round demonstrated the defect twice: the precheck had to be issued as a manual instruction, +and applying it by hand still produced a wrong verdict from reading a single guard. + +## 5. The clause edits + +Sites are cited by anchor rather than line number, because a moved line citation is F1's own C3. +Each §5 edit is one sentence and mirrors into `plugins/dev-workflow/commands/workflow-init.md` +in the same commit. + +**Stated once, for all of them: each is an instruction, and nothing checks it mechanically.** +A whitespace-normalization rule for wrapped prose, a required sweep record, parser selection, +terminal states — those belong to an enforcement design, which is the split story's subject. A +sentence that grew a procedure to answer such a question would be the accretion D5 exists to +stop. + +### 5.1 §5 Gate B standing lens — F1 + +Site: the `CLAUDE.md` §5 Gate B paragraph opening **"Standing lens, every Gate-B call:"**. +Appended: + +> **Name what this diff changes the size, value or position of** — a list, a count, a version, +> an identifier, a cited line — and grep for where each is described elsewhere, because asked +> as an open question alone this lens missed three such statements in one cycle while being +> carried with unusual force. + +**Diagnosis this rests on.** The lens was carried on the 0.8.0 cycle — the execution notes +record it under the heading "Standing lens, with unusual force" — and still missed C1–C3. The +three share one shape, a fact recorded elsewhere that the diff changed, and differ in which +axis moved: C1 a list's **membership**, C2 a **count**, C3 a **position**. The open question +asks for recall; enumerating needs a search. + +**What this does not do.** It adds no mechanical component. Nothing runs the grep or validates +the answer. + +### 5.2 §5 Profiles counterfactual — F2 (evidence half), F3, F4, F6 + +Site: the `CLAUDE.md` §5 Profiles paragraph containing the sentence `Either route owes the +**counterfactual**: the observation against the prior state.` Appended: + +> **Name the observation that would exist if the claim were false, and confirm the wiring could +> have produced it** — a check that supplies its own input, runs where the defect cannot appear, +> or uses a fixture that never reaches the branch it covers reports success because of how it +> was wired, not because the thing it checks succeeded. + +### 5.3 §5 Gate A pass procedure — F5 + +Site: the `CLAUDE.md` §5 Gate A bullet, after the sentence "Each pass: validate, revise, +re-run." Added: + +> Before each read pass, settle mechanically what the artifact asserts and a machine can decide +> without side effects — cited paths, quoted passages, stated counts, the syntax of standalone +> fenced blocks — inspecting quoted commands rather than running them, since a command quoted +> in a spec may be destructive or an intentional failure. + +### 5.4 The 2026-07-19 `AGENTS.md` Don't, amended — F2 (claim half), F8 + +Site: the Don't opening **"Never describe what a gate proves without checking what it actually +compares."** Its closing rule currently reads "for every sentence about a gate, name the exact +comparison the code performs, and delete any part of the sentence that outruns it." Appended to +that sentence: + +> — and where the sentence says what the mechanism does *not* cover, name the axes it was +> checked against and state whether that list is exhaustive, because an enumeration read as +> complete guarantees the axes it omits. + +**Why this sentence and not a looser one.** It has to reject its own motivating case. "It bounds +the **scan**, not memory" names two axes and gives each a coverage verdict, so any rule asking +only for the axes checked would approve it. What that sentence never does is declare its list +exhaustive or not — so the exhaustiveness statement is the operative requirement, and F8 fails +it. + +**This does not ship downstream.** `AGENTS.md` is repo-local; `/workflow-init` scaffolds a +project's own. + +## 6. The ledger rows + +One row per landed hardening: four rows for four hardenings. Each row's `finding` carries its +motivating cases as worked examples, and for row C each case answers both halves of the §5.2 +sentence. + +| Row | Hardening | Class | Occ | Cases | `source` | `severity` | Rung | +|---|---|---|---|---|---|---|---| +| A | §5.1 lens strengthening | `docs-drift` | 5 | C1, C2, C3 | `bot` | `major` | `P std` | +| B | §5.4 Don't amendment | `unverified-enforcement-claim` | 5 | F8 | `gate-a` | `major` | `1 prose` | +| C | §5.2 counterfactual sentence | `verification-masks-failure` | 2 | `$EVIDENCE`, single-shell test, timed row, `dash` release evidence | `gate-b` | `major` | `P std` | +| D | §5.3 Gate-A sweep sentence | `mechanical-check-skipped-before-review` | new | the eight-pass/thirteen-check sweep | `manual` | `major` | `P std` | + +Row D's class is minted in §6.5. + +**C4 and C5 are dispositioned in `todos.md`, not in a ledger row**, because no hardening is +available for them. Both fall inside the 2026-07-19 Don't, whose operative instruction already +requires exactly what they failed to do — name the exact comparison the code performs, and +delete any part of the sentence that outruns it. The rule was correct and was not followed, +twice. That is a compliance recurrence, and a ledger row would have to name a repair that does +not exist: §5.4's clause governs sentences about what a mechanism does *not* cover, so it +strengthens F8's shape and reaches neither a positive parity claim nor a positive prevention +claim. The parked row records the signal instead, with its trigger: **a third compliance miss +against that Don't, or a feasible mechanical rung emerging from the split skill story.** + +### 6.1 Precheck verdicts, recorded per case -## 3. The precheck, in `harden-finding` +**Row A is inside** the 2026-07-27 lens: it was carried and missed all three cases. The row +records a regression repaired at the same rung. -**Step 3 (Recurrence check).** The bullet reading "latest matching row is a real rung -(1–4/P) → the prior rung didn't hold; propose **one rung stronger**, with reasoning" is -replaced by: - -> - latest matching row is a real rung (1–4/P) → **read that row's stated guard before -> proposing anything.** Rungs guard scopes; a fingerprint match is not a scope match. -> - **outside** the guard → that mechanism never claimed this shape, so it did not fail: -> pick the rung that fits this finding, and do not escalate. -> - **inside** the guard → the mechanism was meant to catch this and did not; that is a -> regression. Repair or strengthen it, and say what changed and why the old form missed -> this case. -> - **no determinable guard** (older rows often state none) → quote the `ref` text you -> read, say what it leaves undetermined, then pick the fitting rung. Never escalate on -> the occurrence count alone. The quote is what keeps this branch from becoming the -> default: "nothing determinable there" is a conclusion reached after reading, and -> quoting makes that claim checkable. - -**Step 7 (Log).** Gains one sentence: - -> State in the `ref` what the rung you land actually guards — the exact spelling, path, or -> rule it covers — so the next recurrence can be judged against a scope rather than a count. - -**Direction check, for the reviewer.** An earlier draft of this rule had the two branches -inverted, which would have entrenched the bug it was filed against. Outside the guard means -*no escalation*; inside means *regression*. Read them in that direction. - -## 4. The four clause edits - -Each lands at the site named, mirrored into -`plugins/dev-workflow/commands/workflow-init.md` in the same commit. - -### 4.1 §5 Gate B standing lens — finding 1 - -Site: the `CLAUDE.md` §5 Gate B paragraph opening **"Standing lens, every Gate-B call"**. -Cited by anchor rather than line number throughout this spec, because a moved line citation -is finding 1's own C3. - -Appended to the existing lens paragraph: - -> **Name the sets this diff changes the size or position of** — a list that gained a -> member, a count, a cited line or path, a version string — and grep for where each is -> described elsewhere. Asked as an open question alone, this lens missed three such -> statements in one cycle while being carried with unusual force; recall does not enumerate -> a set, a search does. The search is still something you run, not something that runs. - -**Diagnosis this rests on.** The lens *was* carried on the 0.8.0 cycle, and the plan's -execution notes record it being carried "with unusual force". It still missed C1–C3, and -all three are one shape: the diff changed the membership of a set described elsewhere — a -prerequisite list gained `dash`, a run count went four to five, a cited line moved. The -open question asks for recall; enumerating a set needs a search. - -**What this does not do.** It adds no mechanical component. The grep is greppable, and -nothing runs it or validates the answer. A lens that failed under force needs a sharper -question, and this is one; it is not a check, and the ledger row says so. +**F8, C4 and C5 are all inside the 2026-07-19 Don't.** Its operative instruction is *"for every +sentence about a gate, name the exact comparison the code performs, and delete any part of the +sentence that outruns it"* — scoped to **every sentence about a gate**, not to comparison claims +alone. -### 4.2 §5 Profiles counterfactual — findings 2 (evidence half), 3, 4, 6 - -Site: the `CLAUDE.md` §5 Profiles paragraph containing **"Either route owes the -counterfactual"**. - -Appended to the counterfactual paragraph: - -> The counterfactual has two halves, and the second is the teeth: **name the observation -> that would exist if the claim were false, and confirm the wiring could have produced that -> observation.** A check that supplies its own input, runs where the defect cannot appear, -> or uses a fixture that never reaches the branch it covers has a falsifying observation in -> principle and no way to produce it — it reports success because of how it was wired, not -> because the thing it checks succeeded. - -### 4.3 §5 Gate A pass procedure — finding 5 - -One sentence, after "Each pass: validate, revise, re-run": - -> Before each read pass, run what a machine can check on the artifact's own assertions — -> fenced blocks through a parser, cited paths and line numbers, counts, referenced commands -> — and fix what it reports first, so the read pass spends its attention on what no parser -> decides: on this repo's largest plan a `sh -n` sweep found in seconds defects that eight -> read passes had missed. - -### 4.4 `prompt-standards.md` item 11 sub-clause — findings 2 (claim half), 8 - -> **State the claim's scope, and never let a negative imply the rest is covered.** "It -> bounds the scan, not memory" named one thing the mechanism does not do and left the -> reader to infer it does the others — the bound was a size bound, the work was quadratic, -> and time went unbounded and unnoticed for three drafts. When you name what a mechanism -> does not cover, name the axes you checked, or say you enumerated one and did not check -> the rest. - -The checklist stays at 12 items, so check 4b does not ripple. - -## 5. The eight ledger rows - -Seven findings, eight rows: finding 2 splits, because its two halves are different classes. - -| Row | Story finding | Class | Occ | Precheck verdict | Rung | -|---|---|---|---|---|---| -| 1 | F1 — C1–C3, PR #21 | `docs-drift` | 5 | **inside** the 2026-07-27 lens — it was carried and missed all three | `P std` | -| 2 | F2, claim half — C4–C5, PR #21 | `unverified-enforcement-claim` | 5 | **outside** item 11 — a README and a story are not prompt artifacts, which is the scope item 11 claims | `P std` | -| 3 | F8 — "bounds not memory" | `unverified-enforcement-claim` | 6 | **outside**, same guard, same reason | `P std` | -| 4 | F3 — `$EVIDENCE` dry run | `verification-masks-failure` | 2 | **outside** the 2026-07-20 row, whose `ref` states its own scope: "nothing checks new plans for the same shape" | `P std` | -| 5 | F4 — single-shell regression test | `verification-masks-failure` | 3 | **outside**, same row, same reason | `P std` | -| 6 | F6 — timed row, unreached branch | `verification-masks-failure` | 4 | **outside**, same row, same reason | `P std` | -| 7 | F2, evidence half — dash coverage claimed in release evidence | `verification-masks-failure` | 5 | **outside**, same row, same reason | `P std` | -| 8 | F5 — read pass before the sweep | `mechanical-check-skipped-before-review` | new | — | `P std` | - -Finding 2 occupies rows 2 and 7: its C4–C5 half is a claim nothing verified, its evidence -half is a check that could not fail. Different classes, different fixes, so different rows. - -**Rows 4–7 move `1 prose` → `P std`, and that is not an escalation.** The ladder puts -prompt artifacts on rung P; the counterfactual rule lives in `CLAUDE.md` §5, which §5 itself -classifies as product rather than prose. The rung follows the artifact, not the count. Each -row states this, because a reader comparing rung names alone will read it as a stealth -escalation. +- **F8** — "It bounds the **scan**, not memory" is a sentence about the gate's scanner whose + implication outran the length comparison the code performs. Its literal content is true; the + defect is what the enumeration implies about the axis it omits. **The scope of a claim is part + of the claim**, so an implied guarantee is still one the mechanism does not deliver. +- **C5** — the README claimed a typo cannot quietly unhook a gate, a claim about what the gate's + matching prevents, made without checking what the parser compares. +- **C4** — the jq-parity criterion claimed unqualified parity between the routing paths, + outrunning what `field()` compares for a malformed outer document. -**Each of rows 4–7 answers the clause's two questions for its own case**, so a future grep -lands on worked examples rather than an abstraction. +**They divide on whether a repair exists.** F8's shape — an enumeration read as complete — is +one the Don't did not spell out, so amending it closes a real gap, and D6 follows: strengthen +that Don't rather than add a sibling. C4 and C5 needed nothing the Don't does not already say; +they are compliance misses, and §5.4's clause does not reach a positive claim. Row B therefore +carries F8 alone, and C4/C5 are parked (§6 above, §9). -**Occurrence counts are derived from the ledger, and two differ from the story's estimate.** -The story says `docs-drift` and `unverified-enforcement-claim` are both at "occurrences 6+". -Counting the rows: `docs-drift` reaches 5 here (2026-07-18 twice, 2026-07-25 as `pending`, -2026-07-26 resolving that row rather than recording a new defect, 2026-07-27 as the fourth), -and `unverified-enforcement-claim` reaches 5 and 6. The correction is stated rather than -quietly applied, because an unchecked count is the class this round hardens. +**Row C is outside** every prior guard. The only prior `verification-masks-failure` row is +2026-07-20, whose `ref` states its own scope — "nothing checks new plans for the same shape". -**Row 8 mints one class**, in `docs/hardening-taxonomy.md`: +**Row D** has no prior matching row. -> - `mechanical-check-skipped-before-review` — an artifact carrying machine-checkable -> assertions goes to an expensive read pass before anything parses it, so attention is -> spent on what a tool decides in seconds. Aliases: `sh -n` after the fact, the parser -> would have caught it, read pass before the sweep, manual review of machine-decidable -> claims. - -Its boundary against `verification-masks-failure`: there, a check ran and could not fail; -here, the cheap check never ran at all. - -## 6. Everything else the round ships - -- `todos.md` — the three parked §5-trigger rows each gain the D2 half-sentence; the - scope-blind row records this round as evidence case 3 and is marked done with what shipped. -- `docs/hardening-taxonomy.md` — the row-8 class. -- `plugins/dev-workflow/CHANGELOG.md` and `.claude-plugin/plugin.json` — 0.9.0. -- The story file — the D3 amendment. - -## 7. Validation evidence - -Mode is read from the story header at each pass. As of this writing it derives -`battery+check`, which owes: - -- **battery** — the full quality command from `AGENTS.md § Commands`, green. -- **check, with its counterfactual** — apply §4.2's clause as worded to the four cases in - rows 4–7. **Each must fail it.** The counterfactual is that each of the four passed - before: all four shipped, and three of them shipped a defect behind a green check. - -## 8. The follow-on intake +### 6.2 `source` -Finding 7 gets its own story, produced by `dev-workflow:intake` as a deliverable of this -round, carrying its fired trigger (`docs/hardening-log.md`'s 2026-07-20 row now teaches -pre-0.8.0 counting behaviour as current) and its central design question: whether the -convention must also reach `/workflow-init`'s inline ledger-header template, since a -repo-only fix ships a rule this kit's ledger obeys and every scaffolded one does not. - -## 9. Gate-A riders, verbatim in every pass prompt - -1. **Direction check.** Confirm the §3 precheck's branches point the direction the parked - row states: outside the guard → fitting rung, no escalation; inside → regression to - repair. An earlier draft had them inverted. -2. **Sweep before reading.** Before the read pass, mechanically validate whatever this spec - asserts that a machine can check — line citations, counts, quoted file contents, grep - claims — and report what the sweep found separately from what the read found. -3. **Four-case self-test.** Apply §4.2's clause as worded to the four cases in rows 4–7. - Each must fail it. A clause all four would have passed is miswired. - -## 10. What this round does not do - -- It adds no mechanical check. Every rung here is `P std`; a reader is the detection. -- It does not claim the strengthened lens catches the class. It sharpens one question and - names a search; nothing runs the search. -- It does not resolve the ledger's supersession gap — §8 opens that, and until it lands the - 2026-07-20 row still reads as current. -- The precheck depends on rows stating their guard. Older rows do not, which is why §3 has a - third branch rather than a promise. +Row A is `bot`: C1–C3 all came from the PR bot on #21. + +Row B is `bot`: C4 and C5 from the PR bot, F8 from Gate A on the plan. + +Row C's four cases split evenly: `$EVIDENCE` from Gate A on the plan (pass 9) and the +single-shell test from Gate A on the plan (recorded in +`.context/codex-reviews/gate-a-plan-pass-5-dispositions.md`); the timed row from Gate B +(`.context/codex-reviews/gate-b-quality-pass-3.md`) and the `dash` release-evidence claim from +Gate B pass 1. Two and two, so the tie-break applies: the **`dash` release-evidence case is what +triggered this hardening**, and it is Gate B. Row C is **`gate-b`**. + +Row D is `manual`: the sweep-versus-passes comparison was drawn while closing the cycle, not +raised by a gate. + +Where a row's cases differ, `source` takes the majority and the row states the mixed +provenance, following the 2026-07-18 precedent that records "Gate A as the majority and the +trigger". Where no value holds a majority, take the plurality; where the plurality is tied, +take the case that triggered the hardening. + +`severity` is `major` for all four: each let a defect through a check believed to cover it. + +### 6.3 Occurrence counting + +**An occurrence is one ledger event — a defect reaching the ledger and being hardened — not a +distinct defect and not a physical row.** A row that resolves a `pending` predecessor hardens a +defect already counted, so it adds a row without adding an occurrence; and a row may aggregate +several defects under one hardening and count once, because the ladder escalates on how many +times a class has been hardened and come back, not on how many sentences were wrong. + +By that unit `docs-drift` reaches 5 here (2026-07-18 twice, 2026-07-25 as `pending`, 2026-07-26 +resolving it, 2026-07-27 as the fourth) and `unverified-enforcement-claim` reaches 5 +(2026-07-18, 2026-07-19, 2026-07-25 as `pending`, 2026-07-26 resolving it, 2026-07-27 as the +fourth, which is the count that row states for itself). + +### 6.4 Where the audit text goes + +The precheck reading is acceptance-critical (story AC 2) and the structured format for it is +split out, so this round records it in the columns the ledger already has — no schema change. +Each of rows A–C appends, inside `ref`: the **controlling prior row** by date and fingerprint; +its **guard**, quoted or cited precisely enough to re-find; the **verdict per motivating case** +where they differ; and **why the chosen rung fits**, in one clause. Row D records that it has no +prior matching row, plus its rung rationale. + +A literal `|` inside quoted guard text is escaped `\|`, as the ledger header requires for +`finding`. Extending that rule to `ref` formally is one of the split story's open questions +(§4), so this round avoids guard quotations containing pipes rather than relying on an unstated +rule. + +**Concurrency, stated as the gap it is.** This round appends after step 7's re-read. A +same-fingerprint row visible at that read is surfaced and the round stops rather than guessing +whether the two hardenings share a scope. A row landing *after* the read is not detected — step +7 offers no observation point past it, and supplying one is the split story's subject. This +round runs single-writer and claims nothing stronger. + +### 6.5 The minted class + +`docs/hardening-taxonomy.md` gains: + +> - `mechanical-check-skipped-before-review` — an artifact carrying machine-checkable assertions +> goes to an expensive read pass before anything parses it, so attention is spent on what a +> tool decides in seconds. Aliases: `sh -n` after the fact, the parser would have caught it, +> read pass before the sweep, manual review of machine-decidable claims. + +Its boundary against `verification-masks-failure`: there, a check ran and could not fail; here, +the cheap check never ran at all. + +The class is stack-neutral rather than project vocabulary. It lands in the project file anyway, +because the skill instructs minting there and that file's closing note records the same of +several classes already in it, flagging them for promotion when the base taxonomy is next +revised. Moving it into the base list would be a skill edit, which D7 excludes. + +## 7. Story amendments + +The story carries an `**Amendment log:**` block beneath its profile header, one entry per +amendment. An entry that replaces text names the text it replaced; one that extends a section +names what was added. The story is the writable copy, and this section does not restate the +entries. + +## 8. The four split stories + +Four story files at `docs/superpowers/stories/YYYY-MM-DD--story.md`. A note or a +commit-body summary does not satisfy this: the point is an artifact that outlives the session. + +**They are written directly rather than produced by `dev-workflow:intake`, and each file says +so.** Intake excludes "items that already have an approved story/spec or have moved into +solution design", and all four are that: three carry cut-short design analysis from earlier +rounds, and the fourth (F7) is the ledger-format work this round declined to fold in and has +already scoped. Making an intake-produced artifact acceptance-critical would deadlock the round +against a skill that declines the input. + +**Each file uses the story template's six sections and its `**Date:**`/`**Size:**` header line, +and deliberately omits the profile line** — that is the only departure, and each file names it. +A profile is proposed and human-confirmed at intake time; written now it would be a +confirmed-looking value nobody confirmed, which §5 classifies as unresolvable and stops on. + +**The debt is carried as each story's first acceptance criterion**, not as a claim about a later +process: *"the story's profile — both axes and the derived mode — is proposed and confirmed by +Daniel before design begins."* Nothing else would pick it up: `intake` declines work already in +solution design, `brainstorming` does not assess profiles, and CLAUDE.md §5 proceeds when a +cited story is unprofiled. A sentence saying the profile is "owed at pickup" would name no owner +and no step, which is the unbacked-claim shape this round hardens; a criterion inside the +artifact is checkable by whoever picks it up. + +**Each story inherits its source row's conditions, accounted for.** Three of the four replace a +parked `todos.md` row that carries settled analysis, and the `AGENTS.md` Don't "Never replace a +decision procedure without accounting for its old conditions" applies to that replacement as +much as to any rule: each story lists every condition its source row states and marks it kept, +moved, or deliberately dropped. A thinner brief that quietly discards a matured constraint is +the failure mode, and it is acceptance-critical, not advisory. + +**Paths are fixed, and collisions stop the round.** Each story's path is named below. Missing → +write it; byte-identical → reuse it and say so; present and different → stop and surface rather +than overwrite, per invariant 9's rule for scaffolded files. + +**The round is incomplete until all four exist.** If one cannot be grounded — no statable +problem, outcome, or three checkable criteria — that is a stop-and-surface: say which and why, +rather than padding a story into existence. + +1. **The `harden-finding` guard-scope precheck** → + `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`. + Opening evidence: the six items in §4. No source row is replaced; the parked scope-blind row + remains open and points here (§9). +2. **The ledger's supersession convention (F7)** → + `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`. Trigger fired: + the 2026-07-20 row now teaches pre-0.8.0 counting behaviour as current, the second falsified + row its parked trigger names. Conditions to account for, from that row: the append-only rule, + the one-row-per-hardening rule, and the version-qualified supersession note that already + worked once in the 2026-07-20 spec. Open question: which artifacts a supersession rule has to + reach before a reader can trust any row — this repo's ledger alone, or every ledger + `/workflow-init` scaffolds. +3. **A route from a fixed finding to the ledger, for projects that never open PRs (Finding A)** → + `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`. Trigger + fired: "the next round that touches §5". Evidence: the only mandated ledger check lives in + `process-pr-review` step 5, and one project has 51 Gate-A pass files and zero ledger rows. + Conditions to account for: the scope must match that step exactly — every accepted actionable + fixed finding checked, `harden-finding` invoked only when a class matches or a new one is + clearly warranted — and it cannot rest on same-session memory. Open question: what has to be + true for a fixed finding to reach the ledger without a pull request. +4. **A §5 version stamp (Finding B)** → + `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`. Trigger fired: "the + next round that touches the §5 template". Conditions to account for: the semantic §5 locator + problem, per-state merge semantics under invariant 9, stamp cardinality (absent, duplicate, + malformed), and that the binding must hold on every push path — the version-bump coupling is + false, since invariant 12's checker is `pull_request`-only. Open question: how a scaffolded + `CLAUDE.md` can tell its reader that it lags the installed plugin. + +**Not fired:** the slot-collision row, whose trigger names the §5 **file protocol**. This round +changes no part of it — not the slot names, not the pre-call delete, not the terminator or +acceptance rules. + +## 9. Other deliverables + +- **`todos.md`, the scope-blind row** — records this round as its **third evidence case**, and + points at the split story (§8 item 1) rather than being marked resolved, because the fix it + sketched has not landed. +- **`todos.md`, the three §5-trigger rows** — Finding A and Finding B marked **fired**, each + pointing at its story path from §8; the slot-collision row records that it did **not** fire, + and why. +- **`todos.md`, the ledger-supersession row** — marked **fired** and pointed at §8 item 2's + story path. Its conditions stay in the row; only its status changes. Left unmarked, the row + reads as live work and a later session can open a second story for it. +- **`todos.md`, the `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS` row** — **stays open; an observation is + appended.** With the variable set to `0`, Gate-A passes 2–6 of this cycle ran 458 s, 550 s, + 757 s, 663 s and 780 s, all staying in the foreground and returning ordinary `success: true` + envelopes the hook could read; pass 1 was not timed. Start and end stamps are tabulated in + `.context/codex-reviews/gate-a-spec-resume.md`. **No control run was made** with the variable + unset, so this is a correlation under one setting, not a demonstration that the variable held + those calls in the foreground; what it establishes is that calls well past 120 s can return as + ordinary foreground results here. The row stays open because its deliverable is a + `/workflow-init` preflight check, which is unbuilt. +- **`todos.md`, a new parked row for C4 and C5** — the 2026-07-19 Don't is correct and was not + followed twice; no textual repair is available, because its operative instruction already + requires what both cases omitted. *Trigger: a third compliance miss against that Don't, or a + feasible mechanical rung emerging from the split skill story (§8 item 1).* +- **`docs/hardening-taxonomy.md`** — the §6.5 class. +- **`plugins/dev-workflow/.claude-plugin/plugin.json`** — `version` to `0.8.1`, required by + invariant 12. +- **`plugins/dev-workflow/CHANGELOG.md`** — a `## 0.8.1` entry, newest first. + +## 10. Validation evidence + +The mode is read from the story header at each pass. As of this writing it derives +`battery+check`. + +**battery** — the full quality command from `AGENTS.md § Commands`, green. + +**check — a named verification, with its counterfactual.** Automation is not available: the +thing under test is whether a prompt sentence rejects a case, which is a reading. + +**The four inputs, fixed here so the verification is reproducible.** + +1. **`$EVIDENCE` dry run.** Claim: the plan defines `$EVIDENCE` before three commit commands + read it. Wiring: a scratch-clone dry run that assigned `$EVIDENCE` itself before invoking the + commands. Falsifying observation: the commands failing on an unset variable — which this + wiring cannot produce, because it sets the variable the plan was supposed to set. +2. **Single-shell regression test.** Claim: the hook survives a directory at a marker path. + Wiring: a regression test invoking the hook through macOS `sh`. Falsifying observation: a + non-zero exit from the `:` special builtin, which appears under `dash` and not under macOS + `sh`, so this wiring cannot produce it. +3. **Timed regression row.** Claim: the timed rows establish the scan's runtime across the + payloads the ceiling admits. Wiring: timed fixtures built from large **single-record** + payloads. Falsifying observation: a newline-rich payload *under* the ceiling costing far more + than the measured shapes — the record accumulator is quadratic in line count, which no + single-record fixture reaches. The ceiling keeps the work finite, so the falsifying + observation is a runtime far outside the measured range, not an unbounded one. +4. **`dash` coverage in release evidence.** Claim: the suite ran under both shells. Wiring: + `dash codex-gate.test.sh` without `HOOK_SH=dash`, which runs the harness under `dash` and the + hook under `/bin/sh` — bash, on macOS. Falsifying observation: a hook-level `dash` failure, + which this wiring cannot produce because the hook never ran under `dash`. + +**The verification.** Apply §5.2's sentence, as worded, to all four. **Each must fail**, on its +second half. Separately, apply §5.4's appended clause to "It bounds the **scan**, not memory" — +**it must fail too**, on the exhaustiveness requirement. If any case passes the sentence written +to reject it, that sentence is miswired and the verification fails. + +**The counterfactual.** All four were reviewed during the 0.8.0 cycle under the §5 text as it +reads today, and each was accepted by at least one review looking for exactly this — the +`$EVIDENCE` case survived eight read passes. Of the four, one reached the released artifact: the +single-shell test, whose failure the 0.8.0 changelog records as why an invariant-1 violation +shipped. That test also predates the counterfactual text, which landed 2026-07-27 in `27d019b`, +so the current wording had no opportunity to prevent its authoring — only to catch it later, +which it did not. + +**What is deliberately not claimed.** The old text is not re-applied as a pass/fail oracle. It +asks for "the observation against the prior state" and says nothing about wiring, so applying it +yields no determinate verdict on the question the four cases turn on — which is the gap §5.2 +closes. + +**Prompt conformance.** Invariant 11 requires all 12 items of `docs/prompt-standards.md` for +"any skill, command, agent definition, hook message, or scaffolded template". Of everything this +round changes, exactly one file is inside that surface: +`plugins/dev-workflow/commands/workflow-init.md`, both a command and the carrier of the +scaffolded §5 template. It gets a recorded 12-item review in which **all 12 must pass** — an +exception only where the checklist item itself authorizes one (items 9 and 12 do), recorded with +that item's stated reason. + +`CLAUDE.md` and `AGENTS.md` are not on that list, and demanding all 12 of them is unsatisfiable +rather than strict: neither carries a `Target model:` line or an output-format example, because +neither is executed against a named model. Their edits are reviewed against the items that apply +to a rule statement — 6, 7, 8, 9, 11 and 12 — with the result recorded. §5.4's appended clause is +phrased as a continuation of an existing prohibition; item 9's own text exempts rules whose +subject is the prohibition, and that exemption is cited in the record rather than assumed. + +The four split stories are agent inputs, and `docs/prompt-standards.md` holds briefs to the +checklist in spirit. Each gets a recorded in-spirit review — success criteria, stop conditions, +verified claims — without asserting the mandatory 12 apply. + +**Mirror parity.** For each of the three §5 sentences, confirm the inserted text is identical in +`CLAUDE.md` and `workflow-init.md` and sits under the same heading in both. The battery does not +compare them, and a sentence landing in one copy only is the shipped-template drift this repo +treats as load-bearing. + +**Cross-finding conflict check** (story AC 10). Before applying any edit, check whether two +findings pull one artifact in opposite directions; if so, stop and surface rather than choosing. +Current verdict: no conflict. The four hardenings touch four distinct sites — the Gate-B lens +paragraph, the Profiles counterfactual paragraph, the Gate-A pass bullet, and the 2026-07-19 +Don't — and none rewrites text another needs. + +## 11. Gate-A riders, verbatim in every pass prompt + +1. **Sweep before reading.** Mechanically settle whatever this spec asserts that a machine can + decide without side effects — cited paths, quoted passages, counts, durations, section + numbering and internal cross-references. Inspect quoted commands rather than running them. + Several source files are hard-wrapped, so compare code byte-for-byte and prose with + whitespace normalized, and report a wrap artifact as a wrap artifact. Report what the sweep + found separately from what the read found. +2. **Two self-tests.** Apply §5.2's sentence to the four cases in §10 — each must fail. Apply + §5.4's appended clause to "It bounds the scan, not memory" — it must fail. Report both + explicitly either way. +3. **Per-case precheck audit.** §6.1 records a verdict per motivating case against the prior + rows it names. Check each verdict against the guard text it cites. + +## 12. What this round does not do + +- It adds no mechanical check. Every rung is `P std` or `1 prose`; a reader is the detection. +- It does not claim the strengthened lens catches the class. It sharpens one question and names + a search; nothing runs the search. +- It changes no skill file. The guard-scope precheck, and every determinism question raised + about it, belong to the split story (§4). +- It does not resolve the ledger's supersession gap, the no-PR ledger route, or the §5 version + stamp. §8 writes the stories; until they land, the 2026-07-20 row still reads as current. +- It does not detect a same-fingerprint row landing after step 7's re-read (§6.4). +- §5.4's amendment reaches this repository only. Downstream projects inherit the three §5 + sentences, not the `AGENTS.md` Don't. diff --git a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md index c5a48c1..486bc44 100644 --- a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md +++ b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md @@ -3,6 +3,69 @@ **Date:** 2026-08-03 · **Size:** story **Risk:** standard · **Security:** none · **Validation:** battery+check +**Amendment log:** +- 2026-08-03 · AC 1 amended · replaced "has exactly one recorded disposition" with the + current wording · a finding whose halves fall in different fingerprint classes needs a + disposition in each; F2's two halves are `unverified-enforcement-claim` and + `verification-masks-failure`, and forcing one row would file one of them under a class it + does not belong to. Raised at Gate-A spec pass 1. +- 2026-08-03 · AC 8 replaced · it read "Each of the three parked rows whose trigger names the + §5 template (Finding A, Finding B, and the cycle-record-destruction row) states that the + trigger reads a change to §5's substance, not any touch of it." · it now reads "Each of the + three parked rows whose trigger names §5 is read literally against its own text, and the + round records which fired and which did not, with the reason." · the old wording encoded a + misreading of those rows as regression triggers. They are opportunity triggers — "do this + while you are already in §5" — and narrowing them would be `rewrite-drops-prior-condition` + committed by a round that hardens it. The round now honours them as written: Finding A and + Finding B fire and their intakes are opened; the cycle-record row does not fire. Raised at + Gate-A spec pass 1; the entry said "removed" until Gate-A spec pass 3 caught that the story + carries a replacement. +- 2026-08-04 · AC 1 re-amended · the pass-1 amendment replaced "has exactly one recorded + disposition" with "is dispositioned", which solved F2's split by deleting uniqueness for + every finding · uniqueness is restored per class: exactly one disposition per finding, and + for a finding whose halves fall in different fingerprint classes, exactly one per half. + Raised at Gate-A spec pass 3. +- 2026-08-04 · AC 4 replaced · it required `harden-finding` to instruct the guard-scope + precheck · the skill change is split into its own story, and the criterion now requires that + story to exist carrying its named opening evidence · Gate-A spec pass 3 produced four + findings independently demanding row metadata, a ledger format change and a collision state + machine. That is the machinery exit AC 9 names, and it fired on evidence rather than on + judgement. +- 2026-08-04 · AC 7 replaced · it named one intake and specified its "central design question" + · three intakes are now required, and each criterion states problem, evidence and open + question rather than a solution · `dev-workflow:intake` forbids HOW outside its invariant + quotations, so the old wording asked for an artifact intake cannot conformantly produce. + Raised at Gate-A spec pass 3. +- 2026-08-04 · AC 9 amended · it stated the machinery exit as a conditional · it now records + that the condition fired, so the criterion is a completed action rather than a standing test. +- 2026-08-04 · §1 corrected · it said the check-supplies-its-own-input shape "has no home in + the taxonomy" · `verification-masks-failure` already covers it; what the shape lacked was a + recorded application, not a class. Raised at Gate-A spec pass 3. +- 2026-08-04 · §4 extended · invariant 12 added · the round changes paths under + `plugins/dev-workflow/`, so the manifest version gates it. Raised at Gate-A spec pass 3. +- 2026-08-04 · §1 and AC 3 corrected · both said the two classes sit at "six-plus + occurrences" · each lineage calls its 2026-07-27 row the fourth, so four is the prior count + and this round makes five. An unchecked count in the premise of a round about count-blind + escalation. Raised at Gate-A spec pass 4. +- 2026-08-04 · §1 diagnosis corrected · it said the scope-blind workaround "lives in ledger + prose, which agents do not read — they read the skill" · `harden-finding` step 3 does say to + re-read the log, so the premise was false; the defect is that the decision branch keys on + fingerprint and latest rung without letting the row's guard control the verdict. A round + about unverified claims resting on one. Raised at Gate-A spec pass 7. +- 2026-08-04 · AC 4 and AC 7 re-amended · both required the four stories to be "produced by + `intake`" · they now require the files written directly in the story template's shape and + unprofiled · `intake` excludes items that have moved into solution design, which three of the + four are, so making an intake-produced artifact acceptance-critical would deadlock the round + against its own tooling. Unprofiled because a profile is proposed and human-confirmed at + intake time; written now it would be a confirmed-looking value nobody confirmed. Raised at + Gate-A spec pass 6. +- 2026-08-04 · AC 4, AC 9 and §2 rewritten · they named the split work as solution forms + ("multi-row guard scan", "per-row audit record", "row metadata", "ledger format change", + "collision state machine") · they now name the observed failures and the questions left open, + because `intake` forbids HOW and a criterion demanding named machinery would make a + conformant story fail acceptance. §2's "each row records that reading" also became "each row + with a prior matching row", since the new-class row has none. Raised at Gate-A spec pass 5. + **Profile rationale:** not an entry in the profile log — no value has moved, and the log's three kinds (axis change, mode override, adoption) all record a change. Recorded here because the delimitation is load-bearing: the classifier story that produced these findings @@ -15,21 +78,25 @@ dies. The 0.8.0 cycle and PR #21 produced findings that were fixed but never hardened. Commit 23b842d says so in its own body: "Not done in this cycle, and owed: harden-finding on -these five." Two ledger classes — `docs-drift` and `unverified-enforcement-claim` — now -sit at six-plus occurrences each, and the ledger's own escalation reflex reads a sixth -occurrence as "escalate a rung." - -That reflex is the second problem. `harden-finding` compares fingerprints only; rungs -guard *scopes*. The 2026-07-26 rows warn by name that a later in-class defect **outside** -the guarded spelling will be proposed for a stronger rung than anything justifies, and the -workaround for it lives in ledger prose, which agents do not read — they read the skill. -Daniel has now had to issue that precheck as a manual instruction, which is the parked -row's own diagnosis demonstrating itself. +these five." Two ledger classes — `docs-drift` and `unverified-enforcement-claim` — each +stand at **four** prior occurrences, both lineages calling their 2026-07-27 row the fourth, +and this round makes each the fifth. The ledger's own escalation reflex reads a recurrence as +"escalate a rung" regardless of what the prior rung guarded. + +That reflex is the second problem, and it sits in the decision rather than in the reading. +The skill's recurrence step does have you re-read the ledger. What it does not do is let the +row's stated guard control the verdict: the branch keys on a fingerprint match and the latest +row's rung, so a later in-class defect **outside** the guarded spelling is proposed for a +stronger rung than anything justifies — exactly what the 2026-07-26 rows warn about by name. +That warning lives in ledger prose the branch never consults. Daniel has now had to issue the +precheck as a manual instruction, which is the parked row's own diagnosis demonstrating itself. Two further conditions have come due: several lessons from the cycle (single-shell test blindness, mechanical sweep before read-pass, fixture-per-branch perf oracles, incomplete negative bounds statements) have no ledger record at all; and one defect shape — a check -that supplies the thing it tests — has no home in the taxonomy. +that supplies the thing it tests — has a taxonomy home in +`verification-masks-failure` but no recorded application of it, so the class reads as a +one-off rather than as the recurring shape it is. A third, the ledger's missing supersession convention, is **deliberately out of this round's scope.** Its trigger has fired — the 2026-07-20 row teaches pre-0.8.0 counting @@ -41,42 +108,55 @@ change into a round about rungs. ## 2. Desired outcome Every finding from the 0.8.0 cycle and PR #21 carries a recorded disposition: a ledger row -at a rung that fits, or a parked backlog row with a stated trigger. Escalation decisions -are made against what the prior rung *claimed to guard*, not against an occurrence count — -and that reading is something the skill instructs, so the next round does it without -Daniel saying so. The ledger-supersession work leaves this round as a story of its own, -with its trigger recorded rather than remembered. And the round leaves the repository -smaller than the findings would suggest, because items that turn out to need design are -parked rather than squeezed in. +at a rung that fits, or a parked backlog row with a stated trigger. Escalation decisions in +this round are made against what the prior rung *claimed to guard*, not against an occurrence +count, and each row with a prior matching row records that reading so a later reader can check +it. Making the skill +instruct that reading turned out to be a design rather than a paragraph, so it leaves as its +own story with its evidence named. The ledger-supersession work leaves the same way, and so +do the two §5 triggers this round fires. The round leaves the repository smaller than the +findings would suggest, because items that turn out to need design are split rather than +squeezed in. ## 3. Acceptance criteria -- [ ] Each of the seven findings in this round's scope has exactly one recorded disposition - — a `docs/hardening-log.md` row at a named rung, or a `todos.md` row with a stated - trigger. None is left without one. +- [ ] Each of the seven findings in this round's scope carries exactly one disposition — a + `docs/hardening-log.md` row at a named rung, or a `todos.md` row with a stated trigger. + A finding whose halves fall in different fingerprint classes carries exactly one per + half, and the round names which halves those are. None is left without one, and none + carries two dispositions in the same class. - [ ] Every escalation decision in the round records which prior row's guard was read and whether the finding falls inside or outside it. Outside → the fitting rung, no escalation. Inside → recorded as a regression of that mechanism, repaired or strengthened. -- [ ] Neither six-plus-occurrence class escalates on count alone; each row states the guard - it was measured against and why the chosen rung fits. -- [ ] `harden-finding` instructs the guard-scope precheck, with its two branches pointing the - direction the parked row states — the known inverted-branches hazard is named in the - Gate-A prompt for this round and the reviewer confirms the direction. +- [ ] Neither recurring class escalates on count alone; each row states the guard it was + measured against and why the chosen rung fits. +- [ ] A story for the `harden-finding` guard-scope precheck exists on disk, written directly in + the story template's shape and unprofiled, carrying as opening evidence the failures this + round observed — a verdict + reached by reading one prior row and stopping, a rung choice no later reader can check, + a guard citation the ledger's escaping rule does not cover, a same-fingerprint row + treated as a duplicate without reading what it guards, a branch condition that bypasses + an unresolved prerequisite, and an escalation instruction recorded as moved while + nothing carried it — each with the question it leaves open, and none with a solution, + which `intake` forbids. - [ ] The parked scope-blind row records this round as its third evidence case. - [ ] The check-supplies-its-own-input defect lands as exactly one of: an extension of the existing counterfactual prose, or a new class in `docs/hardening-taxonomy.md` — with the choice justified against that file's "before minting, grep for a near match" rule. -- [ ] A story for the ledger-supersession convention exists on disk, produced by `intake`, - carrying that work's fired trigger and its central design question: whether the - convention must also reach `/workflow-init`'s inline ledger-header template, since a - repo-only fix would ship a ledger rule this kit's own ledger obeys and every - scaffolded one does not. -- [ ] Each of the three parked rows whose trigger names the §5 template (Finding A, - Finding B, and the cycle-record-destruction row) states that the trigger reads a - change to §5's substance, not any touch of it. -- [ ] If the `harden-finding` edit requires anything beyond a rule paragraph — row metadata, - fingerprint-scope fields, ledger format changes — the round stops, records evidence - case 3 only, and the skill change becomes its own story. +- [ ] Three stories exist on disk, each written directly in the story template's shape and + unprofiled, each carrying the trigger that fired and the evidence it fired on — the + ledger-supersession convention, the no-PR route from a fixed finding to the ledger, and + the §5 version stamp. Each states its problem and its open question, and each says it is + a split from a designed round, and each carries as its first acceptance criterion that its + profile — both axes and the derived mode — is proposed and confirmed by Daniel before + design begins. +- [ ] Each of the three parked rows whose trigger names §5 is read literally against its own + text, and the round records which fired and which did not, with the reason. +- [ ] **Fired at Gate-A spec pass 3.** Making the precheck work turned out to require decisions + about what a durable record holds, how a guard citation survives the ledger's format, and + what happens when a row appears mid-run — more than the rule paragraph this criterion + budgeted. The round therefore records evidence case 3, carries no edit to + `harden-finding`, and the skill change became its own story. - [ ] If two findings pull the same artifact in opposite directions, the round stops and surfaces rather than choosing. - [ ] Validation mode `battery+check` is satisfied: the full quality battery green, plus a @@ -85,8 +165,9 @@ parked rather than squeezed in. ## 4. Affected AGENTS.md invariants - `## Key invariants` → `### Prompts and scaffolding` — "10. **The base taxonomy stays - stack-neutral.** Project vocabulary … goes only in that project's - `docs/hardening-taxonomy.md`, never into the `harden-finding` skill." + stack-neutral.** Project vocabulary — tables, auth helpers, framework APIs — goes only in + that project's `docs/hardening-taxonomy.md`, never into the `harden-finding` skill. Otherwise + one project leaks into every other." - `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent definition, hook message, or scaffolded template." @@ -97,6 +178,10 @@ parked rather than squeezed in. compares.**" - `## Don'ts` — "**Never rename or delete a doc section without grepping for references first.**" +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.** A + pull request that changes any path under a `plugins//` directory **that still exists + at HEAD** — `examples/` included, and no exemptions among the paths inside such a directory + — must also change that plugin manifest's `version`, or CI fails." ## 5. Open questions From b9111a2a55ab3dbec05642999c5744745412543b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:20:25 +0200 Subject: [PATCH 04/11] docs(spec,story): apply Gate-A pass 9 and close the spec gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five findings, none needing a new decision: - The pass-8 C4/C5 split had reached the ledger-row table and §6.1 but not §2, D6 or §6.2, which still described row B as carrying all three cases with a bot source. Row B is F8 alone, source gate-a; C4/C5 are parked. - §8's inheritance inventories were incomplete in the section that makes completeness acceptance-critical. Finding A gains the durable-handoff constraint and the deferred-minting rule; Finding B gains the wire-format point and the no-schedule-pressure note. - The profile criterion read "before design begins", which these stories cannot satisfy — they exist because design on them was cut short. Now "before design resumes", naming who proposes, who confirms and when the header is written. - The AC 7 amendment had no log entry; added. - The story-path collision rule was check-then-write; classification now happens immediately before each write, failing closed if the path appeared meanwhile. Gate A closes at nine passes, all valid under the file-first protocol, every finding dispositioned in writing under .context/codex-reviews/. Pass 9 returned no finding requiring a decision, which is the pinned exit. All riders passed: §5.2 rejects its four cases, §5.4 rejects F8, and the disposition audit confirms F1→A, F2 evidence→C, F2 claim→parked, F3→C, F4→C, F5→D, F6→C, F8→B with none missing or doubled. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...-hardening-round-0-8-0-and-pr-21-design.md | 53 +++++++++++++------ ...3-hardening-round-0-8-0-and-pr-21-story.md | 12 ++++- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md index 9ef8284..929ee40 100644 --- a/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md +++ b/docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md @@ -36,8 +36,9 @@ jq-parity criterion; **C5** the README's "a typo can't quietly unhook a gate". ## 2. What this round is -Seven findings processed through `dev-workflow:harden-finding`, each disposed at a rung chosen -by reading what the prior rung *claimed to guard* rather than by counting occurrences. **Four +Seven findings processed through `dev-workflow:harden-finding`, each disposed by reading what +the prior rung *claimed to guard* rather than by counting occurrences — **at a rung where a +hardening exists, and in a parked backlog row where none does.** **Four hardenings** land as text — three sentences in `CLAUDE.md` §5 and one clause appended to an existing `AGENTS.md` Don't — **four ledger rows** record them, one taxonomy class is minted, and four follow-on stories are written. @@ -56,7 +57,7 @@ what happens when a row appears mid-run — questions a rule paragraph cannot an | D3 | Every trigger **this round fires** is recorded as fired and its story written as a round deliverable | A fired trigger nobody records evaporates | Taking on every trigger already fired in the backlog, which would make any round that touches §5 responsible for unrelated due work | | D4 | F2's evidence half, F3, F4 and F6 all fingerprint **`verification-masks-failure`** and resolve at one site | Same failure: a check reports success because of how it was wired. The taxonomy prefers a reused near-miss over a precise class nobody greps | Minting two classes that then never recur | | D5 | Each §5 edit is **one sentence at the exact existing site** | §5 is the repo's most load-bearing prompt, and a finding needing a paragraph is a finding whose home is wrong | Paragraph-sized insertions, which is how §5 accretes and how conditions get dropped | -| D6 | F2's claim half and F8 are repaired by **amending the existing 2026-07-19 `AGENTS.md` Don't**, not by adding a sibling | All three of their cases fall inside that Don't's operative instruction (§6.1), so the fitting response to a guard that did not hold is to strengthen it. A second Don't covering adjacent ground would leave two rules a reader must reconcile | A new Don't, which would record a fresh-scope hardening where a regression actually occurred | +| D6 | **F8** is repaired by **amending the existing 2026-07-19 `AGENTS.md` Don't**, not by adding a sibling. **F2's claim half (C4, C5) is not repaired at all** — it is parked as an unrepaired compliance recurrence | All three cases fall inside that Don't (§6.1), but only F8's shape — an enumeration read as complete — is one the Don't did not spell out, so amending it closes a real gap. C4 and C5 omitted what the Don't already requires, so no amendment reaches them and a ledger row would have to name a repair that does not exist. A second Don't covering adjacent ground would also leave two rules a reader must reconcile | A new Don't; or logging C4/C5 against §5.4, which does not govern a positive claim | | D7 | The `harden-finding` change is **split into its own story** and this round edits no skill file | The precheck cannot be stated without deciding what a durable record holds, how a guard citation survives the ledger format, and what happens on a mid-run collision. Those are design questions, and answering them inside a hardening round is how a round becomes a redesign | Designing it here | | D8 | **One ledger row per landed hardening**, each carrying its motivating cases as worked examples | The ledger header says one row per hardening, and both aggregation precedents agree (2026-07-18 logged six claims as one row; 2026-07-27 logged ten instances as one row). A row per case would inflate the recurrence counts that drive escalation, in a round about escalation | One row per case | | D9 | Version **0.8.1** | With the skill change split out, the round is `#16`'s shape — rules added to a shipped prompt and its template mirror — and `#16` took 0.7.0 → 0.7.1. The `#15` minor precedent applied to a skill's *procedure* changing, which this round does not do | 0.9.0, which follows a precedent this round does not match | @@ -223,7 +224,8 @@ carries F8 alone, and C4/C5 are parked (§6 above, §9). Row A is `bot`: C1–C3 all came from the PR bot on #21. -Row B is `bot`: C4 and C5 from the PR bot, F8 from Gate A on the plan. +Row B is `gate-a`: its single case is F8, which came from Gate A on the plan. C4 and C5 are not +row B's cases — they are parked (§9) — so they contribute no provenance to it. Row C's four cases split evenly: `$EVIDENCE` from Gate A on the plan (pass 9) and the single-shell test from Gate A on the plan (recorded in @@ -316,13 +318,19 @@ and deliberately omits the profile line** — that is the only departure, and ea A profile is proposed and human-confirmed at intake time; written now it would be a confirmed-looking value nobody confirmed, which §5 classifies as unresolvable and stops on. -**The debt is carried as each story's first acceptance criterion**, not as a claim about a later -process: *"the story's profile — both axes and the derived mode — is proposed and confirmed by -Daniel before design begins."* Nothing else would pick it up: `intake` declines work already in -solution design, `brainstorming` does not assess profiles, and CLAUDE.md §5 proceeds when a -cited story is unprofiled. A sentence saying the profile is "owed at pickup" would name no owner -and no step, which is the unbacked-claim shape this round hardens; a criterion inside the -artifact is checkable by whoever picks it up. +**The debt is carried as each story's first acceptance criterion**, and the criterion names the +step rather than an owner-less obligation: + +> Before design resumes on this story, whoever picks it up proposes both axes and the mode +> derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into +> this header. Design continues only after that. + +"Before design *resumes*", not "begins": these stories exist because design on them was cut +short, so a criterion gated on design not having started would be unsatisfiable by construction. +Nothing else would pick the debt up — `intake` declines work already in solution design, +`brainstorming` does not assess profiles, and CLAUDE.md §5 proceeds when a cited story is +unprofiled — so a sentence merely saying the profile is owed would name no step at all, which is +the unbacked-claim shape this round hardens. **Each story inherits its source row's conditions, accounted for.** Three of the four replace a parked `todos.md` row that carries settled analysis, and the `AGENTS.md` Don't "Never replace a @@ -333,7 +341,11 @@ the failure mode, and it is acceptance-critical, not advisory. **Paths are fixed, and collisions stop the round.** Each story's path is named below. Missing → write it; byte-identical → reuse it and say so; present and different → stop and surface rather -than overwrite, per invariant 9's rule for scaffolded files. +than overwrite, per invariant 9's rule for scaffolded files. **Classify each path immediately +before writing it, not once up front**, and create with an operation that fails if the path +appeared meanwhile rather than one that truncates: a classification taken early and acted on +later is check-then-write, and another session can create or change the file in the gap. On any +such failure, stop and surface — the same answer as a collision seen at classification time. **The round is incomplete until all four exist.** If one cannot be grounded — no statable problem, outcome, or three checkable criteria — that is a stop-and-surface: say which and why, @@ -357,15 +369,22 @@ rather than padding a story into existence. `process-pr-review` step 5, and one project has 51 Gate-A pass files and zero ledger rows. Conditions to account for: the scope must match that step exactly — every accepted actionable fixed finding checked, `harden-finding` invoked only when a class matches or a new one is - clearly warranted — and it cannot rest on same-session memory. Open question: what has to be - true for a fixed finding to reach the ledger without a pull request. + clearly warranted; it cannot rest on same-session memory, because a compaction, interruption + or handoff loses the fixed-finding set and nothing detects the loss; a durable handoff needs + real design for identity, deduplication and consumption semantics, which is why it was + refused as a mid-round addition; and `mandatory-step-anchored-to-optional-path` is minted + only when the hardening lands, since minting it earlier leaves a class no row uses. Open + question: what has to be true for a fixed finding to reach the ledger without a pull request. 4. **A §5 version stamp (Finding B)** → `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`. Trigger fired: "the next round that touches the §5 template". Conditions to account for: the semantic §5 locator problem, per-state merge semantics under invariant 9, stamp cardinality (absent, duplicate, - malformed), and that the binding must hold on every push path — the version-bump coupling is - false, since invariant 12's checker is `pull_request`-only. Open question: how a scaffolded - `CLAUDE.md` can tell its reader that it lags the installed plugin. + malformed), that the binding must hold on every push path — the version-bump coupling is + false, since invariant 12's checker is `pull_request`-only — that a stamp is a **wire + format**, so shipping a provisional one writes legacy into every scaffolded file, and that + the one known-stale instance is being re-synced by hand, so this carries no schedule + pressure. Open question: how a scaffolded `CLAUDE.md` can tell its reader that it lags the + installed plugin. **Not fired:** the slot-collision row, whose trigger names the §5 **file protocol**. This round changes no part of it — not the slot names, not the pre-call delete, not the terminator or diff --git a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md index 486bc44..f7a7596 100644 --- a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md +++ b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md @@ -47,6 +47,12 @@ occurrences" · each lineage calls its 2026-07-27 row the fourth, so four is the prior count and this round makes five. An unchecked count in the premise of a round about count-blind escalation. Raised at Gate-A spec pass 4. +- 2026-08-04 · AC 7 amended · it required each story to say it "owes a profile at pickup" · it + now requires each story to carry, as its first acceptance criterion, that its profile — both + axes and the derived mode — is proposed and confirmed by Daniel before design resumes · the + old wording named no owner and no step, and nothing would have picked the debt up: `intake` + declines work already in solution design, `brainstorming` does not assess profiles, and + CLAUDE.md §5 proceeds when a cited story is unprofiled. Raised at Gate-A spec pass 8. - 2026-08-04 · §1 diagnosis corrected · it said the scope-blind workaround "lives in ledger prose, which agents do not read — they read the skill" · `harden-finding` step 3 does say to re-read the log, so the premise was false; the defect is that the decision branch keys on @@ -137,8 +143,10 @@ squeezed in. a guard citation the ledger's escaping rule does not cover, a same-fingerprint row treated as a duplicate without reading what it guards, a branch condition that bypasses an unresolved prerequisite, and an escalation instruction recorded as moved while - nothing carried it — each with the question it leaves open, and none with a solution, - which `intake` forbids. + nothing carried it — each with the question it leaves open and none with a solution, since + the design is brainstorming's to do. Like the other three, it carries as its first + acceptance criterion that its profile is proposed and confirmed by Daniel before design + resumes. - [ ] The parked scope-blind row records this round as its third evidence case. - [ ] The check-supplies-its-own-input defect lands as exactly one of: an extension of the existing counterfactual prose, or a new class in `docs/hardening-taxonomy.md` — with From 8fc145df859fd415ae132e2977389508f0898f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:31:16 +0200 Subject: [PATCH 05/11] docs(plan): implementation plan for the hardening round MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine tasks over the spec closed at b9111a2. Opens with the deliverables checklist, states decisions without their revision history, and carries the exact text of every artifact it asks for: four ledger rows, four split stories, six todos updates, the CHANGELOG entry. Task ordering follows the dependencies rather than the spec's section order: hardenings first, then the stories, then todos (which cite the story paths), then the ledger rows (which cite the hardenings and the parked row), then the version bump, then validation and Gate B. Two defects found by the plan's own pre-commit sweep, both instances of classes this round hardens: - A mirror-parity check used process substitution, a bashism, in a repo whose invariant 4 is POSIX sh and whose CI runs sh. Replaced with a POSIX loop that also guards against two empty extractions comparing equal — a check that cannot fail is the verification-masks-failure shape. - The sweep itself reported one failure flag for two independent checks, so a shell-syntax failure masked whether the path check had passed. Split. All 23 fenced blocks parse under sh -n. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption; Gate A on the plan runs next. --- ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 1018 +++++++++++++++++ 1 file changed, 1018 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md new file mode 100644 index 0000000..2f6d43a --- /dev/null +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -0,0 +1,1018 @@ +# Hardening round — the 0.8.0 cycle and PR #21 — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Land four hardenings from the 0.8.0 cycle and PR #21 as text, record them in four ledger rows, park what has no repair, and open four split stories — without changing any skill file. + +**Architecture:** Three one-sentence additions to `CLAUDE.md` §5, each mirrored into the inline template in `plugins/dev-workflow/commands/workflow-init.md`; one clause appended to an existing `AGENTS.md` Don't; one new taxonomy class; four appended ledger rows; six `todos.md` row updates; four new story files. No executable code changes. + +**Tech Stack:** Markdown prompts and POSIX shell checks. The quality battery in `AGENTS.md § Commands` is the test cycle — there is no unit test for prose. + +**Spec:** `docs/superpowers/specs/2026-08-03-hardening-round-0-8-0-and-pr-21-design.md` (Gate A closed at nine passes, `b9111a2`). + +**Story:** `docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md` — read its profile header fresh at every Gate-B pass; no value from it is copied here. + +## Deliverables checklist + +Everything this round ships. A task is not done until its rows here are true. + +- [ ] **Three §5 sentences** — Gate-B lens, Profiles counterfactual, Gate-A pass procedure (Task 1) +- [ ] **Three template mirrors** of those sentences in `workflow-init.md`, byte-identical, same headings (Task 1) +- [ ] **One `AGENTS.md` Don't amendment** — the 2026-07-19 gate-claims Don't (Task 2) +- [ ] **One taxonomy class** — `mechanical-check-skipped-before-review` (Task 3) +- [ ] **Four split stories**, unprofiled, each with a profile-confirmation first criterion and an inheritance inventory (Tasks 4–5) +- [ ] **Six `todos.md` updates** — one new parked row (C4/C5), evidence case 3 on the scope-blind row, Finding A and Finding B marked fired, the F7 row marked fired, the slot-collision row marked not-fired, and the env-var row's observation appended (Task 6) +- [ ] **Four ledger rows** — A, B, C, D (Task 7) +- [ ] **Version 0.8.1** in the manifest, and a `## 0.8.1` CHANGELOG entry (Task 8) +- [ ] **Validation evidence** — battery green, the named verification with its counterfactual, prompt conformance, mirror parity, cross-finding conflict check (Task 9) + +## Global Constraints + +Copied verbatim from the spec and `AGENTS.md`. Every task's requirements implicitly include this section. + +- **Each §5 edit is one sentence at the exact existing site.** A finding needing a paragraph is a finding whose home is wrong (spec D5). +- **`CLAUDE.md` and `workflow-init.md` change in the same commit.** A sentence landing in one copy only is shipped-template drift. +- **No skill file is edited.** `plugins/dev-workflow/skills/**` is out of scope (spec D7). If a step seems to need a skill edit, stop and surface. +- **Version is `0.8.1`.** Required by invariant 12, since the round changes a path under `plugins/dev-workflow/`. +- **The ledger is append-only.** Never edit an existing row; resolve a `pending` row by appending a new one. +- **Escape a literal `|` inside a ledger field as `\|`.** This round avoids guard quotations containing pipes rather than relying on an unstated rule for the `ref` column. +- **The four stories are unprofiled** — no `**Risk:**`/`**Security:**`/`**Validation:**` line — and each carries the profile-confirmation criterion as its first acceptance criterion. +- **No `Co-Authored-By: Claude` or `Generated with` trailers** on any commit. +- **Quality command** (the battery, what CI runs), from `AGENTS.md § Commands`: + +``` +shellcheck --shell=sh plugins/dev-workflow/hooks/codex-gate.sh && shellcheck --shell=sh --exclude=SC2015 plugins/dev-workflow/hooks/codex-gate.test.sh && shellcheck --shell=sh scripts/check-invariants.sh && shellcheck --shell=sh --exclude=SC2015 scripts/check-invariants.test.sh && shellcheck --shell=sh scripts/check-version-bump.sh && shellcheck --shell=sh scripts/check-version-bump.test.sh && HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh && HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh && sh scripts/check-invariants.test.sh && sh scripts/check-invariants.sh && sh scripts/check-version-bump.test.sh && sh scripts/check-version-bump.sh main && claude plugin validate . --strict +``` + +- **`check-invariants.sh` scans the working tree, not the tracked set.** Move any untracked scratch directory aside before running the battery, or it can fail on text it merely quotes. + +--- + +## File Structure + +| File | Responsibility in this round | +|---|---| +| `CLAUDE.md` | three §5 sentences (Gate-B lens, Profiles counterfactual, Gate-A pass procedure) | +| `plugins/dev-workflow/commands/workflow-init.md` | the inline-template mirror of those three sentences | +| `AGENTS.md` | one clause appended to the 2026-07-19 Don't | +| `docs/hardening-taxonomy.md` | one new class | +| `docs/hardening-log.md` | four appended rows | +| `todos.md` | one new parked row, five existing rows updated | +| `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md` | split story: the precheck | +| `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` | split story: F7 | +| `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` | split story: Finding A | +| `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` | split story: Finding B | +| `plugins/dev-workflow/.claude-plugin/plugin.json` | version 0.8.0 → 0.8.1 | +| `plugins/dev-workflow/CHANGELOG.md` | `## 0.8.1` entry | + +--- + +### Task 1: The three §5 sentences and their template mirrors + +**Files:** +- Modify: `CLAUDE.md` (three sites) +- Modify: `plugins/dev-workflow/commands/workflow-init.md` (the same three sites in the inline template) + +**Interfaces:** +- Consumes: nothing. +- Produces: the three sentences that ledger rows A, C and D cite as their `ref` (Task 7). Their exact wording is fixed here; Task 7 quotes it. + +**Anchors, verified at `b9111a2`:** + +| Sentence | `CLAUDE.md` | `workflow-init.md` | +|---|---|---| +| Gate-B lens | line 247, paragraph opening `**Standing lens, every Gate-B call:` | line 441, same opening | +| Profiles counterfactual | line 342, the sentence beginning `satisfies it and the entry says which route was taken and why. Either route owes the` | line 532, same | +| Gate-A pass procedure | line 224, `Each pass: validate, revise, re-run.` | line 419, same | + +Line numbers locate the paragraph; match on the quoted text, since a citation by line is the C3 defect this round hardens. + +- [ ] **Step 1: Append the lens sentence in `CLAUDE.md`** + +Find the paragraph opening `**Standing lens, every Gate-B call: "which existing statements does this diff falsify?"**`. Append to the end of that paragraph, after the sentence ending `plus two user-facing docs teaching a rule the same change had just narrowed.`: + +``` + **Name what this diff changes the size, value or position of** — a list, a count, a + version, an identifier, a cited line — and grep for where each is described elsewhere, + because asked as an open question alone this lens missed three such statements in one + cycle while being carried with unusual force. +``` + +Match the surrounding indentation (this paragraph is indented two spaces as a sub-bullet of the Gate-B item). + +- [ ] **Step 2: Append the counterfactual sentence in `CLAUDE.md`** + +Find the paragraph containing `Either route owes the **counterfactual**: the observation against the prior state.` Append after the sentence ending `A fabricated test satisfies nothing.`: + +``` +**Name the observation that would exist if the claim were false, and confirm the wiring could +have produced it** — a check that supplies its own input, runs where the defect cannot appear, +or uses a fixture that never reaches the branch it covers reports success because of how it was +wired, not because the thing it checks succeeded. +``` + +- [ ] **Step 3: Add the Gate-A sweep sentence in `CLAUDE.md`** + +Find `Each pass: validate, revise, re-run. (Large/high-risk artifact: optional focused` and insert immediately after the sentence `Each pass: validate, revise, re-run.`, before the parenthetical: + +``` +Before each read pass, settle mechanically what the artifact asserts and a machine can decide +without side effects — cited paths, quoted passages, stated counts, the syntax of standalone +fenced blocks — inspecting quoted commands rather than running them, since a command quoted in +a spec may be destructive or an intentional failure. +``` + +- [ ] **Step 4: Mirror all three into `workflow-init.md`** + +Apply Steps 1–3 verbatim at the three corresponding sites in the inline template (lines 441, 532, 419 area). The text must be byte-identical to what landed in `CLAUDE.md`; only surrounding indentation may differ if the template's own indentation differs. + +- [ ] **Step 5: Verify mirror parity** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +for lit in \ + "Name what this diff changes the size, value or position of" \ + "Name the observation that would exist if the claim were false" \ + "Before each read pass, settle mechanically what the artifact asserts" ; do + a=$(tr '\n' ' ' < CLAUDE.md | tr -s ' ' | grep -cF -- "$lit") + b=$(tr '\n' ' ' < plugins/dev-workflow/commands/workflow-init.md | tr -s ' ' | grep -cF -- "$lit") + printf '%-62s CLAUDE=%s TEMPLATE=%s\n' "${lit:0:60}" "$a" "$b" +done +``` + +Expected: every row `CLAUDE=1 TEMPLATE=1`. Whitespace is normalized because both files are hard-wrapped and an accurate quotation can span a line break. + +- [ ] **Step 6: Verify the full sentences match, not just their openings** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +norm() { tr '\n' ' ' < "$1" | tr -s ' '; } +for pat in 'Name what this diff changes[^.]*\.' \ + 'Name the observation that would exist[^.]*\.' \ + 'Before each read pass[^.]*\.' ; do + a=$(norm CLAUDE.md | grep -o "$pat") + b=$(norm plugins/dev-workflow/commands/workflow-init.md | grep -o "$pat") + if [ -n "$a" ] && [ "$a" = "$b" ]; then + echo "IDENTICAL: ${a%% *}..." + else + echo "DIFFERS or EMPTY:"; printf 'CLAUDE: %s\nTEMPLATE: %s\n' "$a" "$b" + fi +done +``` + +Expected: three `IDENTICAL:` lines. The `-n "$a"` guard matters — without it two empty extractions compare equal and report a match that proves nothing, which is the `verification-masks-failure` shape this round hardens. + +No process substitution: `<(...)` is a bashism, and this repo's invariant 4 is POSIX `sh`. Every fenced block in this plan parses under `sh -n`. + +- [ ] **Step 7: Run the battery** + +Run the quality command from Global Constraints. Expected: exit 0. + +- [ ] **Step 8: Commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md +git commit -m "WIP: three §5 sentences and their template mirrors" +``` + +Named `WIP:` deliberately — the hook treats a `wip`-prefixed message as cycle-internal, so it neither fires a Gate-B STOP nor resets the pass counters. Task 9 replaces it by amend. + +--- + +### Task 2: The `AGENTS.md` Don't amendment + +**Files:** +- Modify: `AGENTS.md` (the 2026-07-19 Don't, closing sentence at line 218) + +**Interfaces:** +- Consumes: nothing. +- Produces: the rule ledger row B cites as its `ref` (Task 7). + +- [ ] **Step 1: Extend the Don't's closing sentence** + +Find in `AGENTS.md § Don'ts`, inside `**Never describe what a gate proves without checking what it actually compares.**`, the closing sentence: + +``` + underlying rule is the check itself: for every sentence about a gate, name the exact + comparison the code performs, and delete any part of the sentence that outruns it. +``` + +Replace the trailing `outruns it.` so the sentence continues: + +``` + underlying rule is the check itself: for every sentence about a gate, name the exact + comparison the code performs, and delete any part of the sentence that outruns it — and + where the sentence says what the mechanism does *not* cover, name the axes it was checked + against and state whether that list is exhaustive, because an enumeration read as complete + guarantees the axes it omits. +``` + +- [ ] **Step 2: Verify the amendment rejects its own motivating case** + +This is the operative check. Read the amended sentence and apply it to `It bounds the **scan**, not memory`: + +- Does that sentence name the axes it was checked against? Yes — scan and memory. +- Does it state whether that list is exhaustive? **No.** +- Therefore it fails the amended rule. + +Expected: fails on the exhaustiveness requirement. If it passes, the wording is wrong — a rule asking only for the axes checked would approve the very sentence this amendment exists to reject. Stop and surface rather than proceeding. + +- [ ] **Step 3: Confirm no other site restates this Don't** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +grep -rn "delete any part of the sentence that outruns it" --include='*.md' . | grep -v source-files/ +``` + +Expected: only `AGENTS.md`. The Don't is repo-local and is not mirrored into the scaffolded template; if another site appears, stop and surface — a second copy would need the same amendment and this plan does not budget one. + +- [ ] **Step 4: Run the battery** + +Run the quality command. Expected: exit 0. + +- [ ] **Step 5: Commit** + +```bash +git add AGENTS.md +git commit -m "WIP: extend the gate-claims Don't to coverage enumerations" +``` + +--- + +### Task 3: The taxonomy class + +**Files:** +- Modify: `docs/hardening-taxonomy.md` (append to `## Classes`, before the `**Promotion candidate.**` paragraph) + +**Interfaces:** +- Consumes: nothing. +- Produces: the fingerprint string `mechanical-check-skipped-before-review`, used by ledger row D (Task 7). + +- [ ] **Step 1: Add the class** + +Insert as the last entry under `## Classes`, immediately before the `**Promotion candidate.**` paragraph: + +```markdown +- `mechanical-check-skipped-before-review` — an artifact carrying machine-checkable assertions + goes to an expensive read pass before anything parses it, so attention is spent on what a + tool decides in seconds. Aliases: `sh -n` after the fact, the parser would have caught it, + read pass before the sweep, manual review of machine-decidable claims. + + **Not `verification-masks-failure`.** There a check ran and could not fail; here the cheap + check never ran at all. Grep this one when the sentence is "a parser would have found it + immediately"; grep the other when it is "the check passed and proved nothing". +``` + +- [ ] **Step 2: Confirm the class is not being added to the skill** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +grep -c "mechanical-check-skipped-before-review" plugins/dev-workflow/skills/harden-finding/SKILL.md +``` + +Expected: `0`. Invariant 10 keeps project classes out of the shipped skill. The class is stack-neutral, and it lands in the project file anyway because the skill instructs minting there — the file's own `**Promotion candidate.**` note records the same of several existing classes. + +- [ ] **Step 3: Run the battery** + +Expected: exit 0. + +- [ ] **Step 4: Commit** + +```bash +git add docs/hardening-taxonomy.md +git commit -m "WIP: mint mechanical-check-skipped-before-review" +``` + +--- + +### Task 4: The `harden-finding` precheck split story + +**Files:** +- Create: `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: the story path that `todos.md`'s scope-blind row points at (Task 6). + +- [ ] **Step 1: Check the target path immediately before writing** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +[ -e "$p" ] && echo "EXISTS — compare content, do not overwrite" || echo "ABSENT — safe to create" +``` + +If it exists and its content is byte-identical to Step 2's text, reuse it and say so. If it exists and differs, **stop and surface** — do not overwrite (invariant 9's rule for scaffolded files). + +- [ ] **Step 2: Write the story** + +```markdown +# `harden-finding`'s recurrence rule is scope-blind — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately.** This story is a split from a designed round, not a raw idea, so +it did not pass through `dev-workflow:intake` — intake excludes items that have moved into +solution design. A profile is proposed and human-confirmed at intake time; writing one here +would be a confirmed-looking value nobody confirmed, which CLAUDE.md §5 classifies as an +unresolvable profile and stops on. Acceptance criterion 1 carries that debt instead. + +## 1. Problem statement + +`harden-finding`'s recurrence step tells you to re-read the ledger, and then decides from the +fingerprint and the latest matching row's rung. It never lets that row's **stated guard** +control the verdict. So a later in-class defect outside the guarded spelling is proposed for a +stronger rung than anything justifies — which the 2026-07-26 rows warn about by name, in ledger +prose the decision branch does not consult. + +The 2026-08-03 hardening round applied the missing precheck by hand, on a standing instruction +from Daniel, and still reached a wrong verdict twice by reading one prior row's guard and +stopping. Landing the rule in the skill turned out to require decisions a rule paragraph cannot +carry, which is why it is here rather than in that round. + +## 2. Desired outcome + +A recurrence proposal states which prior rows it read, what each guards, and whether this +finding falls inside or outside — and a later reader can check that reasoning against the +ledger. Escalation follows from a guard that failed, never from a count. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A recurrence decision names every prior matching row it considered, not only the latest, + and records what each guards. +- [ ] The record a decision leaves behind is sufficient for a later reader to reach the same + inside/outside verdict without re-deriving it. +- [ ] The rule states what happens when a same-fingerprint row appears mid-run, including when + that row is `pending` or its guard cannot be determined. +- [ ] Whether automatic escalation on recurrence is kept, narrowed, or dropped is settled + explicitly, with a stated reason. +- [ ] The `AGENTS.md` Don't "Never replace a decision procedure without accounting for its old + conditions" is satisfied for every condition the current step 3 and step 7 carry. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" +- `## Don'ts` — "**Never replace a decision procedure without accounting for its old + conditions.** List what the previous prose required, then mark each one kept, moved, or + deliberately dropped." + +## 5. Open questions + +Six, each paired with the failure that raised it: + +- A verdict was reached by reading one prior row's guard and stopping, when an older row's guard + covered the case. **Which prior rows must a recurrence be judged against before "outside" is + safe?** +- A reader cannot tell whether a past rung choice followed from a guard reading or from a count, + because nothing durable records the reading. **What does a later reader need, and where does + it belong?** +- Guards are frequently exact spellings containing regex alternation, and the ledger's stated + escaping rule covers the `finding` column. **How does a guard citation survive a Markdown + table without changing what the columns mean?** +- Step 7 re-reads the ledger and then appends, so a row landing after that read is not seen, and + a row seen at the read is treated as a duplicate without anyone consulting what it guards. + **What should happen when a row appears mid-run?** +- Widening the branch condition from "latest matching row is a real rung" to "a matching row + exists at a real rung" makes it fire when the latest row is `pending`, bypassing the + prerequisite rule. **How do a guard scan and a prerequisite block compose?** +- The old step-3 text required proposing one rung stronger on recurrence, and a replacement + choosing "the rung that fits the repair" does not carry it. **Is automatic escalation kept, + narrowed, or deliberately dropped?** + +## 6. Suggested size + +`story` — one skill file, one decision procedure, one spec → plan → PR. It is above a chore +because the six questions above are real design, and below an epic because they all concern one +procedure in one file. +``` + +- [ ] **Step 3: Confirm the story is unprofiled and carries the criterion** + +Run: + +```bash +cd "$(git rev-parse --show-toplevel)" +p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" +tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' +grep -c '^## ' "$p" +``` + +Expected: `0` profile lines, `1` profile criterion, `6` sections. + +- [ ] **Step 4: Commit** + +```bash +git add docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +git commit -m "WIP: split story for the harden-finding guard-scope precheck" +``` + +--- + +### Task 5: The three trigger stories + +**Files:** +- Create: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` +- Create: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` +- Create: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` + +**Interfaces:** +- Consumes: nothing. +- Produces: three story paths that `todos.md` rows point at (Task 6). + +Each story replaces a parked `todos.md` row that carries settled analysis, so each carries an inheritance inventory marking every condition of its source row **kept, moved, or deliberately dropped**. A thinner brief that quietly discards a matured constraint is the failure mode, and `AGENTS.md`'s "Never replace a decision procedure without accounting for its old conditions" applies to that replacement. + +- [ ] **Step 1: Check all three paths immediately before writing** + +```bash +cd "$(git rev-parse --show-toplevel)" +for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ + docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ + docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md ; do + [ -e "$p" ] && echo "EXISTS $p" || echo "ABSENT $p" +done +``` + +Absent → create. Byte-identical → reuse and say so. Present and different → **stop and surface**. + +- [ ] **Step 2: Write the ledger-supersession story** + +Path: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` + +```markdown +# The hardening ledger has no supersession convention — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`, which excludes work already in solution design. Acceptance criterion 1 +carries the profile debt. + +## 1. Problem statement + +`docs/hardening-log.md`'s header says never edit a row, and one row per hardening. When a row's +"what this does NOT do" narration is later falsified by a feature change, neither move is +sanctioned: editing breaks the first rule, appending breaks the second. + +This is live. The 2026-07-20 row describes pre-0.8.0 counting behaviour as current, and a reader +who trusts it is misled about how the gate hook counts today. That is the second falsified row, +which is the condition the parked backlog row named as its trigger. + +## 2. Desired outcome + +A reader who opens any ledger row can tell whether it still describes current behaviour, and a +row falsified by a later change can be marked as such without breaking either standing rule. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] The 2026-07-20 row no longer reads as current behaviour, and it was not edited. +- [ ] The append-only rule and the one-row-per-hardening rule both still hold after the change. +- [ ] A reader can determine, from the ledger alone, which row to trust for current behaviour. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites + silently.** Idempotent: missing → write; identical → report unchanged; present and different → + show the diff and ask; additive files (`.gitattributes`, `.mcp.json`, …) → merge." +- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay + inline** in the command body." + +## 5. Inherited conditions + +From the parked `todos.md` row "**The hardening ledger has no supersession convention.**": + +| Condition | Disposition | +|---|---| +| Never edit a row | **kept** — any solution must preserve it | +| One row per hardening | **kept** — any solution must preserve it | +| The 2026-07-20 row now describes pre-0.8.0 behaviour as current | **kept** as the motivating instance | +| The 2026-07-20 *spec* took a version-qualified supersession note and it worked | **kept** as prior art the design should evaluate first | +| Alternative: an explicit "rows are historical, read the newest row for current behaviour" header statement | **kept** as a candidate | +| Trigger: the next row falsified by a later change — this is the second | **moved** — the trigger has fired and is recorded here | + +## 6. Open questions + +- Which artifacts must the convention reach before a reader can trust any row — this repo's + ledger alone, or every ledger `/workflow-init` scaffolds? A repo-only fix ships a rule this + kit's ledger obeys and every scaffolded one does not. + +## 7. Suggested size + +`story` — one file's header convention plus possibly one inline template, one spec → plan → PR. +``` + +- [ ] **Step 3: Write the no-PR ledger route story** + +Path: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` + +```markdown +# A route from a fixed finding to the ledger, for projects that never open PRs — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`. Acceptance criterion 1 carries the profile debt. + +## 1. Problem statement + +The only mandated ledger check lives in `/dev-workflow:process-pr-review` step 5, so a project +that never opens a pull request never reaches it. One project has 51 Gate-A pass files and +**zero** ledger rows: findings were raised, validated and fixed, and none was ever considered +for hardening. + +## 2. Desired outcome + +A project that fixes review findings without opening a pull request still reaches the ledger +check, and a fixed finding that warrants hardening is not lost to a compaction, an interruption +or a handoff. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A project with no pull requests reaches a ledger check on the findings it fixed. +- [ ] The route does not depend on same-session memory. +- [ ] The check's scope matches `process-pr-review` step 5 exactly — no wider, no narrower. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" + +## 5. Inherited conditions + +From the parked `todos.md` row "**Finding A — a route from a fixed finding to the ledger for +projects that never open PRs.**": + +| Condition | Disposition | +|---|---| +| Scope must match `process-pr-review` step 5 **exactly** — check every accepted actionable fixed finding, but invoke `harden-finding` only when a class matches or a new one is clearly warranted | **kept** — every approximating draft got this wrong | +| Cannot rest on same-session memory: a compaction, interruption or handoff loses the fixed-finding set and nothing detects the loss | **kept** | +| A durable handoff needs real design — identity, deduplication, consumption semantics | **kept**, and it is why this is a story rather than a mid-round addition | +| It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it, not before | +| Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | +| Trigger: the next round that touches §5, or a project reporting an empty ledger across cycles | **moved** — fired by the 2026-08-03 round, recorded here | + +## 6. Open questions + +- What has to be true for a fixed finding to reach the ledger in a project that never opens a + pull request? + +## 7. Suggested size + +`story` — one route, one spec → plan → PR. +``` + +- [ ] **Step 4: Write the §5 version stamp story** + +Path: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` + +```markdown +# A §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the plugin — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`. Acceptance criterion 1 carries the profile debt. + +## 1. Problem statement + +`/workflow-init` scaffolds CLAUDE.md §5 from an inline template. When the plugin's §5 changes, +every previously scaffolded copy silently lags, and nothing in the scaffolded file tells its +reader so. One known-stale instance exists and is being re-synced by hand. + +## 2. Desired outcome + +A reader of a scaffolded `CLAUDE.md` can tell whether its §5 matches the installed plugin's, +without comparing the two by hand. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A scaffolded `CLAUDE.md` whose §5 lags the installed plugin is detectable as such. +- [ ] Re-running `/workflow-init` on a stale file behaves per invariant 9 — it does not silently + overwrite accumulated content. +- [ ] Whatever binding the design chooses holds on every push path, not only on pull requests. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay + inline** in the command body. Claude Code does not expand `${CLAUDE_PLUGIN_ROOT}` inside + command markdown (verified), and the cache path is not an API." +- `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites + silently.**" +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" + +## 5. Inherited conditions + +From the parked `todos.md` row "**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can +tell it lags the installed plugin.**": + +| Condition | Disposition | +|---|---| +| A semantic §5 locator is needed — `/workflow-init` may append the section renumbered, so "no §5 heading" can misread a valid section and append a duplicate | **kept** | +| Per-state merge semantics: invariant 9 forbids a silent overwrite, and "re-run init to sync" promises what the command cannot give | **kept** | +| Stamp cardinality: absent, duplicate, malformed | **kept** | +| The binding must be real on **every** push path; the version-bump coupling first proposed was false, since invariant 12's checker is `pull_request`-only | **kept** — the false coupling is recorded so it is not re-proposed | +| A stamp is a **wire format**: shipping a provisional one writes legacy into every scaffolded file | **kept** — it is why a provisional stamp is not acceptable | +| The one known-stale instance is being re-synced by hand, so this carries no schedule pressure | **kept** — the work is not urgent | +| Trigger: the next round that touches the §5 template | **moved** — fired by the 2026-08-03 round, recorded here | + +## 6. Open questions + +- How can a scaffolded `CLAUDE.md` tell its reader that it lags the installed plugin? + +## 7. Suggested size + +`story` — one stamp format and its detection, one spec → plan → PR. It is not a chore: a wire +format shipped provisionally cannot be taken back. +``` + +- [ ] **Step 5: Verify all three are unprofiled and carry their criterion and inventory** + +```bash +cd "$(git rev-parse --show-toplevel)" +for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ + docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ + docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md ; do + prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p") + crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them') + inv=$(grep -c '^## 5. Inherited conditions' "$p") + printf '%-70s profile=%s criterion=%s inventory=%s\n' "$(basename "$p")" "$prof" "$crit" "$inv" +done +``` + +Expected: every row `profile=0 criterion=1 inventory=1`. + +- [ ] **Step 6: Commit** + +```bash +git add docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ + docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ + docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md +git commit -m "WIP: three split stories for the fired triggers" +``` + +--- + +### Task 6: `todos.md` updates + +**Files:** +- Modify: `todos.md` (one new row, five existing rows) + +**Interfaces:** +- Consumes: the four story paths from Tasks 4–5. +- Produces: the parked C4/C5 row that ledger row B's `ref` cross-references (Task 7). + +- [ ] **Step 1: Add the parked C4/C5 compliance row** + +Add under `### Parked (trigger-gated)`: + +```markdown +- [ ] **The gate-claims Don't is correct and was not followed, twice.** PR #21's C4 (an + unqualified jq-parity criterion that outran what `field()` compares for a malformed outer + document) and C5 (a README claim that a typo cannot quietly unhook a gate) both fall + inside the 2026-07-19 `AGENTS.md` Don't, whose operative instruction already requires + exactly what they omitted — name the exact comparison the code performs, and delete any + part of the sentence that outruns it. **No textual repair exists**, which is why these are + parked rather than logged: a ledger row would have to name a hardening, and the rule + needing no change means the failure was compliance, not wording. The 2026-08-04 amendment + covers coverage enumerations (F8's shape) and reaches neither a positive parity claim nor + a positive prevention claim. *Trigger: a third compliance miss against that Don't, or a + feasible mechanical rung emerging from + `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`.* +``` + +- [ ] **Step 2: Record evidence case 3 on the scope-blind row** + +In the row `**`harden-finding`'s recurrence rule is scope-blind.**`, append before its `*Trigger:*` sentence: + +``` + **Evidence case 3 (2026-08-04):** the 2026-08-03 hardening round ran the precheck as a + standing manual instruction from Daniel — which is this row's own diagnosis, since a rule + that exists only in chat is not one the skill carries — and still reached a wrong verdict + twice by reading a single prior row's guard and stopping. Split to + `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`; this + row stays open because the fix it sketches has not landed. +``` + +- [ ] **Step 3: Mark Finding A's row fired** + +In the row `**Finding A — a route from a fixed finding to the ledger for projects that never open PRs.**`, append: + +``` + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5. Story: + `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, which + carries this row's conditions with each marked kept, moved or dropped. +``` + +- [ ] **Step 4: Mark Finding B's row fired** + +In the row `**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the installed plugin.**`, append: + +``` + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits the §5 inline + template. Story: + `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, which carries this + row's conditions with each marked kept, moved or dropped. +``` + +- [ ] **Step 5: Mark the ledger-supersession row fired** + +In the row `**The hardening ledger has no supersession convention.**`, append: + +``` + **TRIGGER FIRED (2026-08-04):** the 2026-07-20 row now teaches pre-0.8.0 counting + behaviour as current — the second falsified row this trigger names. Story: + `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`. +``` + +- [ ] **Step 6: Record that the slot-collision row did NOT fire** + +In the row `**Each Gate cycle destroys the previous cycle's review record.**`, append: + +``` + **NOT FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5 prose and its template + mirror, and changes no part of the §5 **file protocol** this row's trigger names — not the + slot names, not the pre-call delete, not the terminator or acceptance rules. Recorded so a + later reader can check the reading rather than re-derive it. +``` + +- [ ] **Step 7: Append the env-var observation, leaving the row open** + +In the row `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**`, append: + +``` + **OBSERVATION (2026-08-04), and the row stays open.** With the variable set to `0`, five + Gate-A passes of the 2026-08-03 round ran 458 s, 550 s, 757 s, 663 s and 780 s; all five + stayed in the foreground and returned ordinary `success: true` envelopes the hook could + read. **No control run was made** with the variable unset, so this is a correlation + observed under one setting, not a demonstration that the variable held those calls in the + foreground. What it does establish is that calls well past 120 s can return as ordinary + foreground results here. The row's deliverable — a `/workflow-init` preflight check — is + unbuilt, so the row is not discharged by this. +``` + +- [ ] **Step 8: Verify every story path referenced exists** + +```bash +cd "$(git rev-parse --show-toplevel)" +grep -o 'docs/superpowers/stories/2026-08-04-[a-z0-9-]*\.md' todos.md | sort -u | while read -r p; do + [ -e "$p" ] && echo "OK $p" || echo "MISS $p" +done +``` + +Expected: four `OK` lines, no `MISS`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens. + +- [ ] **Step 9: Run the battery** + +Expected: exit 0. + +- [ ] **Step 10: Commit** + +```bash +git add todos.md +git commit -m "WIP: park the compliance recurrence and record the fired triggers" +``` + +--- + +### Task 7: The four ledger rows + +**Files:** +- Modify: `docs/hardening-log.md` (append four rows) + +**Interfaces:** +- Consumes: the three §5 sentences (Task 1), the `AGENTS.md` amendment (Task 2), the taxonomy class (Task 3), the parked row (Task 6). +- Produces: nothing downstream. + +Append in order A, B, C, D at the end of the table. Each row is one line — the newlines below are for readability in this plan only; **write each as a single line.** + +- [ ] **Step 1: Re-read the log and check for a same-fingerprint row appearing meanwhile** + +```bash +cd "$(git rev-parse --show-toplevel)" +for fp in docs-drift unverified-enforcement-claim verification-masks-failure mechanical-check-skipped-before-review; do + printf '%-42s ' "$fp" + grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md +done +``` + +Expected: `docs-drift 5`, `unverified-enforcement-claim 5`, `verification-masks-failure 1`, `mechanical-check-skipped-before-review 0`. If any count is higher, a row appeared since this plan was written: **stop and surface**, naming the row. Do not append. Resolving a mid-run collision is the split story's subject (Task 4), and inventing the procedure here would re-import what was split out. + +- [ ] **Step 2: Append row A** + +``` +| 2026-08-04 | docs-drift | fifth occurrence: three statements the 0.8.0 diff falsified — AGENTS.md's prerequisites list omitting `dash` while the battery invokes it twice, a plan's "four script runs" for a block holding five, and a spec citing a hook line the same rewrite had moved — all found by a PR bot, none by the standing Gate-B lens | bot | major | P std | CLAUDE.md §5 Gate-B lens + the same block in the workflow-init inline template: the lens now asks what the diff changes the size, value or position of, and to grep for where each is described elsewhere. PRIOR ROW: 2026-07-27 docs-drift (P std), guard "which existing statements does this diff falsify?" — this finding is INSIDE that guard, so it is a regression repaired at the same rung, not an escalation: the lens was carried on the 0.8.0 cycle (the execution notes record it under "Standing lens, with unusual force") and missed all three. WHAT CHANGED AND WHY THE OLD FORM MISSED IT: the open question asks for recall; all three cases are a fact recorded elsewhere that the diff changed — a list's membership, a count, a position — and enumerating those needs a search. STILL INSTRUCTION-BACKED: nothing runs the grep or validates the answer, so this raises the floor and does not close the class | +``` + +- [ ] **Step 3: Append row B** + +``` +| 2026-08-04 | unverified-enforcement-claim | fifth occurrence: "It bounds the **scan**, not memory" named one uncovered axis and left the reader to infer the others were covered — the bound was a size bound, the work was quadratic in size, and the runtime it was read as bounding held only for the shapes that had been measured | gate-a | major | 1 prose | AGENTS.md Don't "Never describe what a gate proves without checking what it actually compares", closing rule extended: where a sentence says what a mechanism does NOT cover, name the axes it was checked against and state whether that list is exhaustive. PRIOR ROW: 2026-07-19 (1 prose), the same Don't, whose operative instruction covers "every sentence about a gate" — INSIDE it, so this is a regression, and the amendment closes a gap the comparison-focused wording left open. GUARD, exactly: the exhaustiveness statement. The motivating sentence names two axes and gives each a verdict, so a rule asking only for the axes checked would approve it; declaring the list exhaustive or not is what it never does. NOT LOGGED HERE: PR #21's C4 and C5 fall inside the same Don't but need nothing it does not already say, so no repair exists to name — they are parked in todos.md as a compliance recurrence with their own trigger. STILL INSTRUCTION-BACKED: no checker reads a coverage claim | +``` + +- [ ] **Step 4: Append row C** + +``` +| 2026-08-04 | verification-masks-failure | second occurrence, four cases in one cycle: a dry run that defined `$EVIDENCE` itself, proving the git mechanics and never that the plan defines the variable; a regression test that only ever ran under macOS `sh` while the defect it guards appears under `dash`; timed rows whose single-record fixtures never reach the record accumulator's quadratic path; and release evidence claiming `dash` coverage from a run that executed the harness under `dash` and the hook under `/bin/sh` | gate-b | major | P std | CLAUDE.md §5 Profiles counterfactual + the same block in the workflow-init inline template: name the observation that would exist if the claim were false, and confirm the wiring could have produced it. GUARD, exactly: the second half — a check that supplies its own input, runs where the defect cannot appear, or uses a fixture that never reaches the branch it covers. Each of the four cases fails on it. PRIOR ROW: 2026-07-20 (1 prose), whose ref states its own scope — "nothing checks new plans for the same shape" — so all four are OUTSIDE it and this is the fitting rung rather than an escalation on the count. RUNG P NOT 1: the artifact is a prompt; the rung follows the artifact, not the count. SOURCE: two gate-a cases and two gate-b, so the tie-break applies — the triggering case is the `dash` release evidence, which is gate-b. STILL INSTRUCTION-BACKED: nothing tests whether a check could have failed | +``` + +- [ ] **Step 5: Append row D** + +``` +| 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | +``` + +- [ ] **Step 6: Verify the rows are well-formed** + +```bash +cd "$(git rev-parse --show-toplevel)" +echo "--- new rows are single lines with 7 fields ---" +tail -4 docs/hardening-log.md | awk -F'|' '{print NR": fields="NF-2}' +echo "--- fingerprint counts after append ---" +for fp in docs-drift unverified-enforcement-claim verification-masks-failure mechanical-check-skipped-before-review; do + printf '%-42s ' "$fp"; grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md +done +echo "--- no unescaped pipes inside fields (each row must have exactly 8 pipes) ---" +tail -4 docs/hardening-log.md | awk '{n=gsub(/\|/,"|"); print NR": pipes="n}' +``` + +Expected: `fields=7` for each of the four; counts `6 / 6 / 2 / 1`; `pipes=8` for each row. + +- [ ] **Step 7: Run the battery** + +Expected: exit 0. Note `check-invariants.sh` excludes `docs/hardening-log.md` from checks 4a/4b — a ledger that quotes defects would otherwise self-reject. + +- [ ] **Step 8: Commit** + +```bash +git add docs/hardening-log.md +git commit -m "WIP: four ledger rows for the 0.8.0 and PR #21 hardenings" +``` + +--- + +### Task 8: Version bump and CHANGELOG + +**Files:** +- Modify: `plugins/dev-workflow/.claude-plugin/plugin.json:4` +- Modify: `plugins/dev-workflow/CHANGELOG.md` (new `## 0.8.1` section, directly under the intro, above `## 0.8.0`) + +**Interfaces:** +- Consumes: everything above (the CHANGELOG describes it). +- Produces: nothing downstream. + +- [ ] **Step 1: Bump the manifest** + +In `plugins/dev-workflow/.claude-plugin/plugin.json`, change `"version": "0.8.0",` to `"version": "0.8.1",`. + +- [ ] **Step 2: Add the CHANGELOG entry** + +Insert immediately above `## 0.8.0`: + +```markdown +## 0.8.1 + +- **Three sentences added to the §5 gate protocol, and to the template `/workflow-init` + scaffolds.** The Gate-B standing lens now asks what a diff changes the size, value or position + of, and to grep for where each is described elsewhere — asked as an open question alone it + missed three such statements in one cycle while being carried with unusual force. The Profiles + counterfactual now asks for both halves: name the observation that would exist if the claim + were false, and confirm the wiring could have produced it. The Gate-A pass procedure now asks + for a mechanical sweep before each read pass, inspecting quoted commands rather than running + them, since a command quoted in a spec may be destructive. +- **None of the three is a check.** Nothing runs the grep, tests whether a check could have + failed, or records that a sweep happened. They sharpen questions a reader asks; the ledger + rows say so rather than implying otherwise. +- **No skill changed.** The `harden-finding` guard-scope precheck — the change this round set out + to make — turned out to need decisions about durable records, ledger format and mid-run + collisions that a rule paragraph cannot carry. It is split to its own story. +``` + +- [ ] **Step 3: Verify the bump is detected** + +```bash +cd "$(git rev-parse --show-toplevel)" +grep -n '"version"' plugins/dev-workflow/.claude-plugin/plugin.json +grep -n '^## 0\.8\.1' plugins/dev-workflow/CHANGELOG.md +``` + +Expected: version `0.8.1`, and one `## 0.8.1` heading. + +- [ ] **Step 4: Run the battery** + +Expected: exit 0. `check-version-bump.sh main` compares commits, so run it once this task is committed — on `main` the merge-base is HEAD and it passes trivially; CI runs it against the PR's base. + +- [ ] **Step 5: Commit** + +```bash +git add plugins/dev-workflow/.claude-plugin/plugin.json plugins/dev-workflow/CHANGELOG.md +git commit -m "WIP: bump to 0.8.1 with its changelog entry" +``` + +--- + +### Task 9: Validation evidence and the Gate-B cycle + +**Files:** +- No new files. This task produces the evidence entry that the cycle-closing commit body carries. + +**Interfaces:** +- Consumes: every prior task. +- Produces: the final commit. + +- [ ] **Step 1: Squash the WIP commits into one snapshot** + +```bash +cd "$(git rev-parse --show-toplevel)" +FIRST_WIP=$(git log --format='%H %s' | awk '/WIP: three §5 sentences/{print $1}') +git reset --soft "$FIRST_WIP"^ +git commit -m "WIP: hardening round — the 0.8.0 cycle and PR #21" +git log --oneline -3 +``` + +`git commit` and `git add` must be **separate** calls throughout: the hook derives its docs-only file list at `PreToolUse`, so a compound `git add && git commit` presents an empty index and fires a spurious Gate-B STOP. + +- [ ] **Step 2: Run the full battery and capture the real numbers** + +Run the quality command from Global Constraints. Record the actual counts it prints — shellcheck files, hook suite assertions under both `sh` and `dash`, invariant assertions, version-bump assertions, `claude plugin validate` result. Do not write a number you did not read from output. + +- [ ] **Step 3: Run the named verification** + +Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10. Each must **fail** on the second half. Then apply §5.4's appended clause to `It bounds the **scan**, not memory` — it must fail on the exhaustiveness requirement. + +Record the result per case. If any case passes the sentence written to reject it, that sentence is miswired: **stop and surface**, do not proceed to Gate B. + +- [ ] **Step 4: Run the prompt-conformance reviews** + +`plugins/dev-workflow/commands/workflow-init.md` is the only changed file inside invariant 11's enumerated surface. Review it against all 12 items of `docs/prompt-standards.md`; all 12 must pass, with an exception only where the checklist item itself authorizes one (items 9 and 12 do), recorded with that item's stated reason. + +`CLAUDE.md` and `AGENTS.md` are not on that list — neither carries a `Target model:` line, because neither is executed against a named model. Review their edits against items 6, 7, 8, 9, 11 and 12 and record the result. §5.4's clause is a continuation of an existing prohibition; item 9's own text exempts rules whose subject is the prohibition, so cite that exemption rather than assuming it. + +Give each of the four split stories a recorded in-spirit brief review — success criteria, stop conditions, verified claims — without asserting the mandatory 12 apply. + +- [ ] **Step 5: Run the cross-finding conflict check** + +Confirm no two findings pull one artifact in opposite directions. Expected verdict: none — the four hardenings touch four distinct sites (the Gate-B lens paragraph, the Profiles counterfactual paragraph, the Gate-A pass bullet, and the 2026-07-19 Don't), and none rewrites text another needs. If a conflict appears, **stop and surface** rather than choosing. + +- [ ] **Step 6: Run Gate B** + +Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the WIP commit. Delete both branch files first and confirm they are gone. Carry the story path and the evidence entry verbatim in `additionalContext`, plus the standing lens. + +Minimum three passes, final pass clean. Re-review after every fix — a fix changes the diff. Every pass writes to `.context/codex-reviews/gate-b--pass-

.md` and is validated before it is read: terminator exact, count matching, nothing but finding lines, both branch files present. + +- [ ] **Step 7: Close the cycle by amend** + +```bash +cd "$(git rev-parse --show-toplevel)" +git commit --amend -F /path/to/final-message.txt +git log --oneline -1 +``` + +The closing message carries the validated evidence entry — battery result, the check and its counterfactual — and the story path. The amend replaces the WIP message wholesale, so an entry written only into the WIP body is destroyed exactly when the cycle closes. + +- [ ] **Step 8: Open the PR** + +```bash +git push -u origin HEAD +gh pr create --title "Harden four classes from the 0.8.0 cycle and PR #21" --body-file /path/to/pr-body.md +``` + +Then run `/dev-workflow:process-pr-review` once the bots report. + +--- + +## Self-Review + +**Spec coverage.** §5.1 → Task 1 Step 1. §5.2 → Task 1 Step 2. §5.3 → Task 1 Step 3. §5.4 → Task 2. §6 rows A–D → Task 7. §6.4 collision behaviour → Task 7 Step 1. §6.5 class → Task 3. §8 four stories → Tasks 4–5, with paths and the pre-write check. §9 deliverables → Task 6 (todos), Task 8 (version, CHANGELOG), Task 3 (taxonomy). §10 validation → Task 9. Decision D7 (no skill file) → Global Constraints and Task 3 Step 2. + +**Placeholder scan.** No `TBD`, no "add appropriate handling", no "similar to Task N". Every ledger row, story and CHANGELOG entry is written out in full. + +**Type consistency.** The fingerprint string `mechanical-check-skipped-before-review` is identical in Task 3 (minting), Task 7 Steps 1, 5 and 6 (rows and verification). The four story paths are identical in Tasks 4, 5, 6 and their verification steps. The three §5 sentence openings used in Task 1's parity checks match the sentences inserted in Steps 1–3 verbatim. + +**One gap found and closed during review:** Task 7's collision check originally ran after appending. It now runs first — appending and then discovering a collision would mean editing the ledger, which the append-only rule forbids. From e21948eaf8b8252f6d8460a8a4e157bd6d6b64ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:49:59 +0200 Subject: [PATCH 06/11] docs(plan,story): apply Gate-A plan pass 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nineteen findings, all accepted. The one that mattered most: `${lit:0:60}` is bash-only substring expansion, and the pre-commit sweep passed it because `sh -n` is a parse check while the failure is at runtime — `dash -c` gives `Bad substitution`. Every block now parses under both `sh -n` and `dash -n`, and the sweep greps for the constructs neither parse check catches. Mechanical correctness: - `grep -c` exits 1 on a zero count, so every expected-zero check would have aborted on the correct result. All captured and compared numerically. - The battery includes `check-version-bump.sh main`, which fails on a committed plugins/ change with no bump. The manifest bump moved to Task 1, so Tasks 2-7 no longer assert an exit status the battery cannot produce. - The "only AGENTS.md" grep could never succeed, since the spec and plan both quote the phrase; narrowed to the one operative copy. Structure: the three trigger stories had seven sections against a six-section template — the inheritance inventory is now a subsection. Path collisions are classified immediately before each write, with set -C and a symlink test. The cross-finding conflict check moved ahead of every edit, per the spec. Claims the plan could not support: two `/path/to/` placeholders in a plan asserting it had none, and validation results with no destination — both now `.context/evidence-0.8.1.md`, defined before it is read, with a guard that stops the amend if the template ships unfilled. A split story also claimed §5 would stop on a fabricated profile; it would not, and the wording is corrected in all four. The story's AC 7 still read "before design begins" while the spec and the amendment log read "resumes" — drift from the pass-9 spec fix, corrected here. My first grep for it found nothing, being line-oriented against text wrapped across two lines; the normalized search found it. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 692 +++++++++++------- ...3-hardening-round-0-8-0-and-pr-21-story.md | 2 +- 2 files changed, 422 insertions(+), 272 deletions(-) diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md index 2f6d43a..d3a3bf9 100644 --- a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -4,7 +4,7 @@ **Goal:** Land four hardenings from the 0.8.0 cycle and PR #21 as text, record them in four ledger rows, park what has no repair, and open four split stories — without changing any skill file. -**Architecture:** Three one-sentence additions to `CLAUDE.md` §5, each mirrored into the inline template in `plugins/dev-workflow/commands/workflow-init.md`; one clause appended to an existing `AGENTS.md` Don't; one new taxonomy class; four appended ledger rows; six `todos.md` row updates; four new story files. No executable code changes. +**Architecture:** Three one-sentence additions to `CLAUDE.md` §5, each mirrored into the inline template in `plugins/dev-workflow/commands/workflow-init.md`; one clause appended to an existing `AGENTS.md` Don't; one new taxonomy class; four appended ledger rows; seven `todos.md` row changes; four new story files. No executable code changes. **Tech Stack:** Markdown prompts and POSIX shell checks. The quality battery in `AGENTS.md § Commands` is the test cycle — there is no unit test for prose. @@ -17,28 +17,32 @@ Everything this round ships. A task is not done until its rows here are true. - [ ] **Three §5 sentences** — Gate-B lens, Profiles counterfactual, Gate-A pass procedure (Task 1) -- [ ] **Three template mirrors** of those sentences in `workflow-init.md`, byte-identical, same headings (Task 1) +- [ ] **Three template mirrors** of those sentences in `workflow-init.md` (Task 1) +- [ ] **Version 0.8.1** in the manifest — committed with the first plugin change, not later (Task 1) - [ ] **One `AGENTS.md` Don't amendment** — the 2026-07-19 gate-claims Don't (Task 2) - [ ] **One taxonomy class** — `mechanical-check-skipped-before-review` (Task 3) -- [ ] **Four split stories**, unprofiled, each with a profile-confirmation first criterion and an inheritance inventory (Tasks 4–5) -- [ ] **Six `todos.md` updates** — one new parked row (C4/C5), evidence case 3 on the scope-blind row, Finding A and Finding B marked fired, the F7 row marked fired, the slot-collision row marked not-fired, and the env-var row's observation appended (Task 6) +- [ ] **Four split stories**, unprofiled, six `##` sections each, with a profile-confirmation first criterion and (for the three that replace a backlog row) an inheritance inventory (Tasks 4–5) +- [ ] **Seven `todos.md` row changes** — one new parked row plus six existing rows updated (Task 6) - [ ] **Four ledger rows** — A, B, C, D (Task 7) -- [ ] **Version 0.8.1** in the manifest, and a `## 0.8.1` CHANGELOG entry (Task 8) -- [ ] **Validation evidence** — battery green, the named verification with its counterfactual, prompt conformance, mirror parity, cross-finding conflict check (Task 9) +- [ ] **A `## 0.8.1` CHANGELOG entry** (Task 8) +- [ ] **Validation evidence** written to a named file, then folded into the closing commit body (Task 9) ## Global Constraints Copied verbatim from the spec and `AGENTS.md`. Every task's requirements implicitly include this section. - **Each §5 edit is one sentence at the exact existing site.** A finding needing a paragraph is a finding whose home is wrong (spec D5). -- **`CLAUDE.md` and `workflow-init.md` change in the same commit.** A sentence landing in one copy only is shipped-template drift. +- **`CLAUDE.md` and `workflow-init.md` change in the same commit**, and their inserted sentences must be **equivalent under whitespace normalization** — identical word sequences, with only line wrapping and leading indentation permitted to differ, because the two files wrap at different widths and nest at different depths. Byte identity is not the requirement and no check in this plan asserts it. - **No skill file is edited.** `plugins/dev-workflow/skills/**` is out of scope (spec D7). If a step seems to need a skill edit, stop and surface. -- **Version is `0.8.1`.** Required by invariant 12, since the round changes a path under `plugins/dev-workflow/`. -- **The ledger is append-only.** Never edit an existing row; resolve a `pending` row by appending a new one. +- **POSIX `sh` only** (invariant 4). No `<(...)`, no `[[ ]]`, no arrays, no `${var:offset:length}`, no `local`. `sh -n` is a *parse* check and will not catch `${var:0:3}` — it fails at runtime under `dash` with `Bad substitution`. Test any new snippet with `dash -c`, not only `sh -n`. +- **`grep -c` exits 1 when the count is zero.** Every expected-zero check in this plan captures the count and asserts the number; none relies on grep's exit status. +- **Version is `0.8.1`**, committed in Task 1 alongside the first `plugins/` change. The battery includes `check-version-bump.sh main`, which fails on any committed `plugins/` change without a bump — so deferring the bump would make every intervening battery run fail by construction. +- **The ledger is append-only.** Never edit an existing row. - **Escape a literal `|` inside a ledger field as `\|`.** This round avoids guard quotations containing pipes rather than relying on an unstated rule for the `ref` column. -- **The four stories are unprofiled** — no `**Risk:**`/`**Security:**`/`**Validation:**` line — and each carries the profile-confirmation criterion as its first acceptance criterion. +- **The four stories are unprofiled** — no `**Risk:**`/`**Security:**`/`**Validation:**` line — each has exactly six `##` sections, and each carries the profile-confirmation criterion first. - **No `Co-Authored-By: Claude` or `Generated with` trailers** on any commit. -- **Quality command** (the battery, what CI runs), from `AGENTS.md § Commands`: +- **`git add` and `git commit` are always separate calls.** The gate hook derives its docs-only file list at `PreToolUse`; a compound `git add && git commit` presents an empty index and fires a spurious Gate-B STOP. +- **Quality battery** (what CI runs), from `AGENTS.md § Commands`: ``` shellcheck --shell=sh plugins/dev-workflow/hooks/codex-gate.sh && shellcheck --shell=sh --exclude=SC2015 plugins/dev-workflow/hooks/codex-gate.test.sh && shellcheck --shell=sh scripts/check-invariants.sh && shellcheck --shell=sh --exclude=SC2015 scripts/check-invariants.test.sh && shellcheck --shell=sh scripts/check-version-bump.sh && shellcheck --shell=sh scripts/check-version-bump.test.sh && HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh && HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh && sh scripts/check-invariants.test.sh && sh scripts/check-invariants.sh && sh scripts/check-version-bump.test.sh && sh scripts/check-version-bump.sh main && claude plugin validate . --strict @@ -48,48 +52,47 @@ shellcheck --shell=sh plugins/dev-workflow/hooks/codex-gate.sh && shellcheck --s --- -## File Structure +## Task 0: Cross-finding conflict check -| File | Responsibility in this round | +The spec requires this **before applying any edit**, so it runs first rather than at validation time. + +- [ ] **Step 1: Confirm no two hardenings contend for one site** + +List the four sites and confirm each hardening writes to a distinct one: + +| Hardening | Site | |---|---| -| `CLAUDE.md` | three §5 sentences (Gate-B lens, Profiles counterfactual, Gate-A pass procedure) | -| `plugins/dev-workflow/commands/workflow-init.md` | the inline-template mirror of those three sentences | -| `AGENTS.md` | one clause appended to the 2026-07-19 Don't | -| `docs/hardening-taxonomy.md` | one new class | -| `docs/hardening-log.md` | four appended rows | -| `todos.md` | one new parked row, five existing rows updated | -| `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md` | split story: the precheck | -| `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` | split story: F7 | -| `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` | split story: Finding A | -| `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` | split story: Finding B | -| `plugins/dev-workflow/.claude-plugin/plugin.json` | version 0.8.0 → 0.8.1 | -| `plugins/dev-workflow/CHANGELOG.md` | `## 0.8.1` entry | +| §5.1 lens sentence | `CLAUDE.md` §5 Gate-B paragraph opening `**Standing lens, every Gate-B call:` | +| §5.2 counterfactual sentence | `CLAUDE.md` §5 Profiles paragraph containing `Either route owes the` | +| §5.3 sweep sentence | `CLAUDE.md` §5 Gate-A bullet at `Each pass: validate, revise, re-run.` | +| §5.4 Don't clause | `AGENTS.md` Don't closing sentence `delete any part of the sentence that outruns it` | + +Expected verdict: **no conflict** — four distinct sites, and none rewrites text another needs. If two findings pull one artifact in opposite directions, **stop and surface** rather than choosing. Repeat this check if a Gate-B fix changes any hardening's site or direction. --- -### Task 1: The three §5 sentences and their template mirrors +### Task 1: The three §5 sentences, their mirrors, and the version bump **Files:** - Modify: `CLAUDE.md` (three sites) - Modify: `plugins/dev-workflow/commands/workflow-init.md` (the same three sites in the inline template) +- Modify: `plugins/dev-workflow/.claude-plugin/plugin.json` (version 0.8.0 → 0.8.1) **Interfaces:** - Consumes: nothing. -- Produces: the three sentences that ledger rows A, C and D cite as their `ref` (Task 7). Their exact wording is fixed here; Task 7 quotes it. +- Produces: the three sentences that ledger rows A, C and D cite as their `ref` (Task 7). Their wording is fixed here; Task 7 quotes it. -**Anchors, verified at `b9111a2`:** +**Anchors, verified at `8fc145d`.** Line numbers locate the paragraph; **match on the quoted text**, since a citation by line is the C3 defect this round hardens. | Sentence | `CLAUDE.md` | `workflow-init.md` | |---|---|---| -| Gate-B lens | line 247, paragraph opening `**Standing lens, every Gate-B call:` | line 441, same opening | -| Profiles counterfactual | line 342, the sentence beginning `satisfies it and the entry says which route was taken and why. Either route owes the` | line 532, same | -| Gate-A pass procedure | line 224, `Each pass: validate, revise, re-run.` | line 419, same | - -Line numbers locate the paragraph; match on the quoted text, since a citation by line is the C3 defect this round hardens. +| Gate-B lens | ~247, paragraph opening `**Standing lens, every Gate-B call:` | ~441, same opening | +| Profiles counterfactual | ~342, sentence `Either route owes the` | ~532, same | +| Gate-A pass procedure | ~224, `Each pass: validate, revise, re-run.` | ~419, same | - [ ] **Step 1: Append the lens sentence in `CLAUDE.md`** -Find the paragraph opening `**Standing lens, every Gate-B call: "which existing statements does this diff falsify?"**`. Append to the end of that paragraph, after the sentence ending `plus two user-facing docs teaching a rule the same change had just narrowed.`: +Find the paragraph opening `**Standing lens, every Gate-B call: "which existing statements does this diff falsify?"**`. Append at the end of that paragraph, after the sentence ending `plus two user-facing docs teaching a rule the same change had just narrowed.`: ``` **Name what this diff changes the size, value or position of** — a list, a count, a @@ -98,7 +101,7 @@ Find the paragraph opening `**Standing lens, every Gate-B call: "which existing cycle while being carried with unusual force. ``` -Match the surrounding indentation (this paragraph is indented two spaces as a sub-bullet of the Gate-B item). +Match the surrounding indentation — this paragraph is a two-space-indented sub-bullet of the Gate-B item. - [ ] **Step 2: Append the counterfactual sentence in `CLAUDE.md`** @@ -113,7 +116,7 @@ wired, not because the thing it checks succeeded. - [ ] **Step 3: Add the Gate-A sweep sentence in `CLAUDE.md`** -Find `Each pass: validate, revise, re-run. (Large/high-risk artifact: optional focused` and insert immediately after the sentence `Each pass: validate, revise, re-run.`, before the parenthetical: +Find `Each pass: validate, revise, re-run. (Large/high-risk artifact: optional focused` and insert immediately after `Each pass: validate, revise, re-run.`, before the parenthetical: ``` Before each read pass, settle mechanically what the artifact asserts and a machine can decide @@ -124,69 +127,68 @@ a spec may be destructive or an intentional failure. - [ ] **Step 4: Mirror all three into `workflow-init.md`** -Apply Steps 1–3 verbatim at the three corresponding sites in the inline template (lines 441, 532, 419 area). The text must be byte-identical to what landed in `CLAUDE.md`; only surrounding indentation may differ if the template's own indentation differs. - -- [ ] **Step 5: Verify mirror parity** - -Run: - -```bash -cd "$(git rev-parse --show-toplevel)" -for lit in \ - "Name what this diff changes the size, value or position of" \ - "Name the observation that would exist if the claim were false" \ - "Before each read pass, settle mechanically what the artifact asserts" ; do - a=$(tr '\n' ' ' < CLAUDE.md | tr -s ' ' | grep -cF -- "$lit") - b=$(tr '\n' ' ' < plugins/dev-workflow/commands/workflow-init.md | tr -s ' ' | grep -cF -- "$lit") - printf '%-62s CLAUDE=%s TEMPLATE=%s\n' "${lit:0:60}" "$a" "$b" -done -``` +Apply Steps 1–3 at the three corresponding sites in the inline template. The word sequence must match what landed in `CLAUDE.md`; line wrapping and leading indentation may differ, per Global Constraints. -Expected: every row `CLAUDE=1 TEMPLATE=1`. Whitespace is normalized because both files are hard-wrapped and an accurate quotation can span a line break. +- [ ] **Step 5: Bump the manifest** -- [ ] **Step 6: Verify the full sentences match, not just their openings** +In `plugins/dev-workflow/.claude-plugin/plugin.json`, change `"version": "0.8.0",` to `"version": "0.8.1",`. This lands here rather than at the end because `check-version-bump.sh main` — part of the battery — fails on any committed `plugins/` change without it. -Run: +- [ ] **Step 6: Verify mirror equivalence** ```bash cd "$(git rev-parse --show-toplevel)" norm() { tr '\n' ' ' < "$1" | tr -s ' '; } +rc=0 for pat in 'Name what this diff changes[^.]*\.' \ 'Name the observation that would exist[^.]*\.' \ 'Before each read pass[^.]*\.' ; do - a=$(norm CLAUDE.md | grep -o "$pat") - b=$(norm plugins/dev-workflow/commands/workflow-init.md | grep -o "$pat") + a=$(norm CLAUDE.md | grep -o "$pat" || true) + b=$(norm plugins/dev-workflow/commands/workflow-init.md | grep -o "$pat" || true) if [ -n "$a" ] && [ "$a" = "$b" ]; then - echo "IDENTICAL: ${a%% *}..." + printf 'EQUIVALENT: %.55s...\n' "$a" else - echo "DIFFERS or EMPTY:"; printf 'CLAUDE: %s\nTEMPLATE: %s\n' "$a" "$b" + rc=1 + printf 'MISMATCH OR EMPTY\n CLAUDE : %s\n TEMPLATE: %s\n' "$a" "$b" fi done +echo "parity exit=$rc" ``` -Expected: three `IDENTICAL:` lines. The `-n "$a"` guard matters — without it two empty extractions compare equal and report a match that proves nothing, which is the `verification-masks-failure` shape this round hardens. +Expected: three `EQUIVALENT:` lines and `parity exit=0`. The `-n "$a"` guard is load-bearing — without it two empty extractions compare equal and report a match that proves nothing, which is the `verification-masks-failure` shape this round hardens. `printf '%.55s'` is used rather than `${a:0:55}`, which is a bash-ism that fails under `dash`. -No process substitution: `<(...)` is a bashism, and this repo's invariant 4 is POSIX `sh`. Every fenced block in this plan parses under `sh -n`. +- [ ] **Step 7: Verify the manifest** -- [ ] **Step 7: Run the battery** +```bash +cd "$(git rev-parse --show-toplevel)" +v=$(grep -o '"version": *"[^"]*"' plugins/dev-workflow/.claude-plugin/plugin.json | head -1) +echo "$v" +[ "$v" = '"version": "0.8.1"' ] && echo "version OK" || echo "version WRONG" +``` -Run the quality command from Global Constraints. Expected: exit 0. +Expected: `version OK`. -- [ ] **Step 8: Commit** +- [ ] **Step 8: Run the battery** + +Run the quality command from Global Constraints. Expected: exit 0. It passes because the bump landed in Step 5 — `check-version-bump.sh main` compares committed state, and on `main` the merge-base is HEAD, so it passes trivially; CI runs it against the PR's base ref. + +- [ ] **Step 9: Commit** + +```bash +git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md plugins/dev-workflow/.claude-plugin/plugin.json +``` ```bash -git add CLAUDE.md plugins/dev-workflow/commands/workflow-init.md -git commit -m "WIP: three §5 sentences and their template mirrors" +git commit -m "WIP: three §5 sentences, their template mirrors, and the 0.8.1 bump" ``` -Named `WIP:` deliberately — the hook treats a `wip`-prefixed message as cycle-internal, so it neither fires a Gate-B STOP nor resets the pass counters. Task 9 replaces it by amend. +Two separate calls, per Global Constraints. Named `WIP:` deliberately — the hook treats a `wip`-prefixed message as cycle-internal, so it neither fires a Gate-B STOP nor resets the pass counters. Task 9 replaces it by amend. --- ### Task 2: The `AGENTS.md` Don't amendment **Files:** -- Modify: `AGENTS.md` (the 2026-07-19 Don't, closing sentence at line 218) +- Modify: `AGENTS.md` (the 2026-07-19 Don't, closing sentence around line 218) **Interfaces:** - Consumes: nothing. @@ -194,7 +196,7 @@ Named `WIP:` deliberately — the hook treats a `wip`-prefixed message as cycle- - [ ] **Step 1: Extend the Don't's closing sentence** -Find in `AGENTS.md § Don'ts`, inside `**Never describe what a gate proves without checking what it actually compares.**`, the closing sentence: +In `AGENTS.md § Don'ts`, inside `**Never describe what a gate proves without checking what it actually compares.**`, find: ``` underlying rule is the check itself: for every sentence about a gate, name the exact @@ -213,33 +215,36 @@ Replace the trailing `outruns it.` so the sentence continues: - [ ] **Step 2: Verify the amendment rejects its own motivating case** -This is the operative check. Read the amended sentence and apply it to `It bounds the **scan**, not memory`: +Read the amended sentence and apply it to `It bounds the **scan**, not memory`: -- Does that sentence name the axes it was checked against? Yes — scan and memory. +- Does it name the axes it was checked against? Yes — scan and memory. - Does it state whether that list is exhaustive? **No.** -- Therefore it fails the amended rule. +- Therefore it fails the amended rule. ✔ -Expected: fails on the exhaustiveness requirement. If it passes, the wording is wrong — a rule asking only for the axes checked would approve the very sentence this amendment exists to reject. Stop and surface rather than proceeding. +Expected: **fails on the exhaustiveness requirement.** If it passes, the wording is wrong — a rule asking only for the axes checked would approve the very sentence this amendment exists to reject. **Stop and surface** rather than proceeding. -- [ ] **Step 3: Confirm no other site restates this Don't** - -Run: +- [ ] **Step 3: Confirm `AGENTS.md` holds exactly one operative copy** ```bash cd "$(git rev-parse --show-toplevel)" -grep -rn "delete any part of the sentence that outruns it" --include='*.md' . | grep -v source-files/ +n=$(tr '\n' ' ' < AGENTS.md | tr -s ' ' | grep -c 'delete any part of the sentence that outruns it' || true) +echo "AGENTS.md operative copies: $n" +[ "$n" = "1" ] && echo "OK" || echo "UNEXPECTED — stop and surface" ``` -Expected: only `AGENTS.md`. The Don't is repo-local and is not mirrored into the scaffolded template; if another site appears, stop and surface — a second copy would need the same amendment and this plan does not budget one. +Expected: `1` and `OK`. The rule is repo-local and is **not** mirrored into the scaffolded template, so no second operative copy needs the amendment. The spec and this plan both quote the phrase; those are quotations of the rule, not copies of it, and are deliberately not searched here — a repository-wide grep would report them and send the executor to a stop branch with nothing wrong. - [ ] **Step 4: Run the battery** -Run the quality command. Expected: exit 0. +Expected: exit 0. - [ ] **Step 5: Commit** ```bash git add AGENTS.md +``` + +```bash git commit -m "WIP: extend the gate-claims Don't to coverage enumerations" ``` @@ -248,7 +253,7 @@ git commit -m "WIP: extend the gate-claims Don't to coverage enumerations" ### Task 3: The taxonomy class **Files:** -- Modify: `docs/hardening-taxonomy.md` (append to `## Classes`, before the `**Promotion candidate.**` paragraph) +- Modify: `docs/hardening-taxonomy.md` (append under `## Classes`, before the `**Promotion candidate.**` paragraph) **Interfaces:** - Consumes: nothing. @@ -269,16 +274,18 @@ Insert as the last entry under `## Classes`, immediately before the `**Promotion immediately"; grep the other when it is "the check passed and proved nothing". ``` -- [ ] **Step 2: Confirm the class is not being added to the skill** - -Run: +- [ ] **Step 2: Confirm the class did not land in the shipped skill** ```bash cd "$(git rev-parse --show-toplevel)" -grep -c "mechanical-check-skipped-before-review" plugins/dev-workflow/skills/harden-finding/SKILL.md +n=$(grep -c "mechanical-check-skipped-before-review" plugins/dev-workflow/skills/harden-finding/SKILL.md || true) +echo "occurrences in SKILL.md: $n" +[ "$n" = "0" ] && echo "OK — invariant 10 holds" || echo "VIOLATION — stop and surface" ``` -Expected: `0`. Invariant 10 keeps project classes out of the shipped skill. The class is stack-neutral, and it lands in the project file anyway because the skill instructs minting there — the file's own `**Promotion candidate.**` note records the same of several existing classes. +Expected: `0` and `OK`. The count is captured and compared numerically because `grep -c` exits 1 on zero, which would otherwise abort the step on the correct result. + +Invariant 10 keeps project classes out of the shipped skill. This class is stack-neutral and lands in the project file anyway, because the skill instructs minting there — the file's own `**Promotion candidate.**` note records the same of several existing classes. - [ ] **Step 3: Run the battery** @@ -288,6 +295,9 @@ Expected: exit 0. ```bash git add docs/hardening-taxonomy.md +``` + +```bash git commit -m "WIP: mint mechanical-check-skipped-before-review" ``` @@ -300,19 +310,25 @@ git commit -m "WIP: mint mechanical-check-skipped-before-review" **Interfaces:** - Consumes: nothing. -- Produces: the story path that `todos.md`'s scope-blind row points at (Task 6). - -- [ ] **Step 1: Check the target path immediately before writing** +- Produces: the story path `todos.md`'s scope-blind row points at (Task 6). -Run: +- [ ] **Step 1: Classify and create the path atomically** ```bash cd "$(git rev-parse --show-toplevel)" p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md -[ -e "$p" ] && echo "EXISTS — compare content, do not overwrite" || echo "ABSENT — safe to create" +if [ -e "$p" ] || [ -L "$p" ]; then + echo "EXISTS (or is a symlink) — compare bytes against Step 2 before doing anything" +elif (set -C; : > "$p") 2>/dev/null; then + echo "CREATED empty placeholder — safe to write" +else + echo "APPEARED between the test and the create — stop and surface" +fi ``` -If it exists and its content is byte-identical to Step 2's text, reuse it and say so. If it exists and differs, **stop and surface** — do not overwrite (invariant 9's rule for scaffolded files). +`set -C` (noclobber) makes the creation fail if any directory entry now exists, so a file appearing between the test and the write is caught rather than overwritten. `[ -L ]` is tested separately because `[ -e ]` is false for a dangling symlink, which would otherwise be silently replaced. + +If it exists and its content is byte-identical to Step 2's text, reuse it and say so. If it exists and differs, **stop and surface** — do not overwrite (invariant 9). - [ ] **Step 2: Write the story** @@ -323,9 +339,11 @@ If it exists and its content is byte-identical to Step 2's text, reuse it and sa **Unprofiled, deliberately.** This story is a split from a designed round, not a raw idea, so it did not pass through `dev-workflow:intake` — intake excludes items that have moved into -solution design. A profile is proposed and human-confirmed at intake time; writing one here -would be a confirmed-looking value nobody confirmed, which CLAUDE.md §5 classifies as an -unresolvable profile and stops on. Acceptance criterion 1 carries that debt instead. +solution design. A profile is proposed and human-confirmed at intake time. Writing one here +would produce a header that **looks** confirmed and is not, and nothing would reveal that: +`CLAUDE.md` §5 stops on a profile that is malformed or internally inconsistent, not on one +whose values are well-formed but unconfirmed. That is exactly why the debt is carried as +acceptance criterion 1 rather than by fabricating a header. ## 1. Problem statement @@ -364,6 +382,10 @@ ledger. Escalation follows from a guard that failed, never from a count. ## 4. Affected AGENTS.md invariants +- `## Key invariants` → `### Prompts and scaffolding` — "10. **The base taxonomy stays + stack-neutral.** Project vocabulary — tables, auth helpers, framework APIs — goes only in + that project's `docs/hardening-taxonomy.md`, never into the `harden-finding` skill. Otherwise + one project leaks into every other." - `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent definition, hook message, or scaffolded template." @@ -397,29 +419,32 @@ Six, each paired with the failure that raised it: ## 6. Suggested size -`story` — one skill file, one decision procedure, one spec → plan → PR. It is above a chore -because the six questions above are real design, and below an epic because they all concern one -procedure in one file. +`story` — one skill file, one decision procedure, one spec → plan → PR. Above a chore because +the six questions above are real design; below an epic because they all concern one procedure +in one file. ``` -- [ ] **Step 3: Confirm the story is unprofiled and carries the criterion** - -Run: +- [ ] **Step 3: Verify shape** ```bash cd "$(git rev-parse --show-toplevel)" p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md -grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" -tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' -grep -c '^## ' "$p" +prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" || true) +crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' || true) +sect=$(grep -c '^## ' "$p" || true) +printf 'profile lines=%s (want 0) criterion=%s (want 1) sections=%s (want 6)\n' "$prof" "$crit" "$sect" +[ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ] && echo OK || echo "WRONG — stop and surface" ``` -Expected: `0` profile lines, `1` profile criterion, `6` sections. +Expected: `OK`. - [ ] **Step 4: Commit** ```bash git add docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +``` + +```bash git commit -m "WIP: split story for the harden-finding guard-scope precheck" ``` @@ -434,26 +459,15 @@ git commit -m "WIP: split story for the harden-finding guard-scope precheck" **Interfaces:** - Consumes: nothing. -- Produces: three story paths that `todos.md` rows point at (Task 6). +- Produces: three story paths `todos.md` rows point at (Task 6). -Each story replaces a parked `todos.md` row that carries settled analysis, so each carries an inheritance inventory marking every condition of its source row **kept, moved, or deliberately dropped**. A thinner brief that quietly discards a matured constraint is the failure mode, and `AGENTS.md`'s "Never replace a decision procedure without accounting for its old conditions" applies to that replacement. +Each replaces a parked `todos.md` row carrying settled analysis, so each holds an inheritance inventory marking every condition of its source row **kept, moved, or deliberately dropped** — `AGENTS.md`'s "Never replace a decision procedure without accounting for its old conditions" applies to that replacement. The inventory is a `###` subsection of §1 so the story keeps exactly six `##` sections. -- [ ] **Step 1: Check all three paths immediately before writing** +**Classify each path immediately before its own write**, using the Task 4 Step 1 procedure verbatim with that story's path substituted. A single up-front classification of all three is stale by construction for the second and third. -```bash -cd "$(git rev-parse --show-toplevel)" -for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ - docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ - docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md ; do - [ -e "$p" ] && echo "EXISTS $p" || echo "ABSENT $p" -done -``` - -Absent → create. Byte-identical → reuse and say so. Present and different → **stop and surface**. - -- [ ] **Step 2: Write the ledger-supersession story** +- [ ] **Step 1: Classify, then write the ledger-supersession story** -Path: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` +Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`, then write: ```markdown # The hardening ledger has no supersession convention — Story @@ -461,8 +475,9 @@ Path: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.m **Date:** 2026-08-04 · **Size:** story **Unprofiled, deliberately** — a split from a designed round, so it bypassed -`dev-workflow:intake`, which excludes work already in solution design. Acceptance criterion 1 -carries the profile debt. +`dev-workflow:intake`, which excludes work already in solution design. A profile written now +would look confirmed without being confirmed, and nothing would reveal that; acceptance +criterion 1 carries the debt instead. ## 1. Problem statement @@ -474,6 +489,19 @@ This is live. The 2026-07-20 row describes pre-0.8.0 counting behaviour as curre who trusts it is misled about how the gate hook counts today. That is the second falsified row, which is the condition the parked backlog row named as its trigger. +### Conditions inherited from the source row + +From `todos.md`, "**The hardening ledger has no supersession convention.**": + +| Condition | Disposition | +|---|---| +| Never edit a row | **kept** — any solution must preserve it | +| One row per hardening | **kept** — any solution must preserve it | +| The 2026-07-20 row now describes pre-0.8.0 behaviour as current | **kept** as the motivating instance | +| The 2026-07-20 *spec* took a version-qualified supersession note and it worked | **kept** as prior art the design should evaluate first | +| Alternative: an explicit "rows are historical, read the newest row for current behaviour" header statement | **kept** as a candidate | +| Trigger: the next row falsified by a later change — this is the second | **moved** — fired, and recorded here | + ## 2. Desired outcome A reader who opens any ledger row can tell whether it still describes current behaviour, and a @@ -490,39 +518,32 @@ row falsified by a later change can be marked as such without breaking either st ## 4. Affected AGENTS.md invariants +- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay + inline** in the command body." - `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites silently.** Idempotent: missing → write; identical → report unchanged; present and different → show the diff and ask; additive files (`.gitattributes`, `.mcp.json`, …) → merge." -- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay - inline** in the command body." +- **Conditional, if the design reaches the scaffolded template** — `## Key invariants` → + `### Prompts and scaffolding` — "11. **Prompt changes pass `docs/prompt-standards.md`** — all + 12 checklist items, for any skill, command, agent definition, hook message, or scaffolded + template." — and `### Packaging` — "12. **A plugin change requires a version bump.**" Both + bind only if the convention must reach `/workflow-init`'s inline ledger header, which is §5's + open question. -## 5. Inherited conditions - -From the parked `todos.md` row "**The hardening ledger has no supersession convention.**": - -| Condition | Disposition | -|---|---| -| Never edit a row | **kept** — any solution must preserve it | -| One row per hardening | **kept** — any solution must preserve it | -| The 2026-07-20 row now describes pre-0.8.0 behaviour as current | **kept** as the motivating instance | -| The 2026-07-20 *spec* took a version-qualified supersession note and it worked | **kept** as prior art the design should evaluate first | -| Alternative: an explicit "rows are historical, read the newest row for current behaviour" header statement | **kept** as a candidate | -| Trigger: the next row falsified by a later change — this is the second | **moved** — the trigger has fired and is recorded here | - -## 6. Open questions +## 5. Open questions - Which artifacts must the convention reach before a reader can trust any row — this repo's ledger alone, or every ledger `/workflow-init` scaffolds? A repo-only fix ships a rule this kit's ledger obeys and every scaffolded one does not. -## 7. Suggested size +## 6. Suggested size `story` — one file's header convention plus possibly one inline template, one spec → plan → PR. ``` -- [ ] **Step 3: Write the no-PR ledger route story** +- [ ] **Step 2: Classify, then write the no-PR ledger route story** -Path: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` +Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, then write: ```markdown # A route from a fixed finding to the ledger, for projects that never open PRs — Story @@ -530,7 +551,8 @@ Path: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-st **Date:** 2026-08-04 · **Size:** story **Unprofiled, deliberately** — a split from a designed round, so it bypassed -`dev-workflow:intake`. Acceptance criterion 1 carries the profile debt. +`dev-workflow:intake`. A profile written now would look confirmed without being confirmed; +acceptance criterion 1 carries the debt instead. ## 1. Problem statement @@ -539,6 +561,20 @@ that never opens a pull request never reaches it. One project has 51 Gate-A pass **zero** ledger rows: findings were raised, validated and fixed, and none was ever considered for hardening. +### Conditions inherited from the source row + +From `todos.md`, "**Finding A — a route from a fixed finding to the ledger for projects that +never open PRs.**": + +| Condition | Disposition | +|---|---| +| Scope must match `process-pr-review` step 5 **exactly** — check every accepted actionable fixed finding, but invoke `harden-finding` only when a class matches or a new one is clearly warranted | **kept** — every approximating draft got this wrong | +| Cannot rest on same-session memory: a compaction, interruption or handoff loses the fixed-finding set and nothing detects the loss | **kept** | +| A durable handoff needs real design — identity, deduplication, consumption semantics | **kept**, and it is why this is a story rather than a mid-round addition | +| It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it | +| Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | +| Trigger: the next round that touches §5, or a project reporting an empty ledger across cycles | **moved** — fired by the 2026-08-03 round, recorded here | + ## 2. Desired outcome A project that fixes review findings without opening a pull request still reaches the ledger @@ -561,33 +597,19 @@ or a handoff. definition, hook message, or scaffolded template." - `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" -## 5. Inherited conditions - -From the parked `todos.md` row "**Finding A — a route from a fixed finding to the ledger for -projects that never open PRs.**": - -| Condition | Disposition | -|---|---| -| Scope must match `process-pr-review` step 5 **exactly** — check every accepted actionable fixed finding, but invoke `harden-finding` only when a class matches or a new one is clearly warranted | **kept** — every approximating draft got this wrong | -| Cannot rest on same-session memory: a compaction, interruption or handoff loses the fixed-finding set and nothing detects the loss | **kept** | -| A durable handoff needs real design — identity, deduplication, consumption semantics | **kept**, and it is why this is a story rather than a mid-round addition | -| It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it, not before | -| Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | -| Trigger: the next round that touches §5, or a project reporting an empty ledger across cycles | **moved** — fired by the 2026-08-03 round, recorded here | - -## 6. Open questions +## 5. Open questions - What has to be true for a fixed finding to reach the ledger in a project that never opens a pull request? -## 7. Suggested size +## 6. Suggested size `story` — one route, one spec → plan → PR. ``` -- [ ] **Step 4: Write the §5 version stamp story** +- [ ] **Step 3: Classify, then write the §5 version stamp story** -Path: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` +Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, then write: ```markdown # A §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the plugin — Story @@ -595,14 +617,30 @@ Path: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` **Date:** 2026-08-04 · **Size:** story **Unprofiled, deliberately** — a split from a designed round, so it bypassed -`dev-workflow:intake`. Acceptance criterion 1 carries the profile debt. +`dev-workflow:intake`. A profile written now would look confirmed without being confirmed; +acceptance criterion 1 carries the debt instead. ## 1. Problem statement -`/workflow-init` scaffolds CLAUDE.md §5 from an inline template. When the plugin's §5 changes, +`/workflow-init` scaffolds `CLAUDE.md` §5 from an inline template. When the plugin's §5 changes, every previously scaffolded copy silently lags, and nothing in the scaffolded file tells its reader so. One known-stale instance exists and is being re-synced by hand. +### Conditions inherited from the source row + +From `todos.md`, "**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags +the installed plugin.**": + +| Condition | Disposition | +|---|---| +| A semantic §5 locator is needed — `/workflow-init` may append the section renumbered, so "no §5 heading" can misread a valid section and append a duplicate | **kept** | +| Per-state merge semantics: invariant 9 forbids a silent overwrite, and "re-run init to sync" promises what the command cannot give | **kept** | +| Stamp cardinality: absent, duplicate, malformed | **kept** | +| The binding must be real on **every** push path; the version-bump coupling first proposed was false, since invariant 12's checker is `pull_request`-only | **kept** — the false coupling is recorded so it is not re-proposed | +| A stamp is a **wire format**: shipping a provisional one writes legacy into every scaffolded file | **kept** — it is why a provisional stamp is unacceptable | +| The one known-stale instance is being re-synced by hand, so this carries no schedule pressure | **kept** — the work is not urgent | +| Trigger: the next round that touches the §5 template | **moved** — fired by the 2026-08-03 round, recorded here | + ## 2. Desired outcome A reader of a scaffolded `CLAUDE.md` can tell whether its §5 matches the installed plugin's, @@ -625,72 +663,71 @@ without comparing the two by hand. command markdown (verified), and the cache path is not an API." - `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites silently.**" +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items." - `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" -## 5. Inherited conditions - -From the parked `todos.md` row "**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can -tell it lags the installed plugin.**": - -| Condition | Disposition | -|---|---| -| A semantic §5 locator is needed — `/workflow-init` may append the section renumbered, so "no §5 heading" can misread a valid section and append a duplicate | **kept** | -| Per-state merge semantics: invariant 9 forbids a silent overwrite, and "re-run init to sync" promises what the command cannot give | **kept** | -| Stamp cardinality: absent, duplicate, malformed | **kept** | -| The binding must be real on **every** push path; the version-bump coupling first proposed was false, since invariant 12's checker is `pull_request`-only | **kept** — the false coupling is recorded so it is not re-proposed | -| A stamp is a **wire format**: shipping a provisional one writes legacy into every scaffolded file | **kept** — it is why a provisional stamp is not acceptable | -| The one known-stale instance is being re-synced by hand, so this carries no schedule pressure | **kept** — the work is not urgent | -| Trigger: the next round that touches the §5 template | **moved** — fired by the 2026-08-03 round, recorded here | - -## 6. Open questions +## 5. Open questions - How can a scaffolded `CLAUDE.md` tell its reader that it lags the installed plugin? -## 7. Suggested size +## 6. Suggested size -`story` — one stamp format and its detection, one spec → plan → PR. It is not a chore: a wire -format shipped provisionally cannot be taken back. +`story` — one stamp format and its detection, one spec → plan → PR. Not a chore: a wire format +shipped provisionally cannot be taken back. ``` -- [ ] **Step 5: Verify all three are unprofiled and carry their criterion and inventory** +- [ ] **Step 4: Verify all three** ```bash cd "$(git rev-parse --show-toplevel)" +rc=0 for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md ; do - prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p") - crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them') - inv=$(grep -c '^## 5. Inherited conditions' "$p") - printf '%-70s profile=%s criterion=%s inventory=%s\n' "$(basename "$p")" "$prof" "$crit" "$inv" + prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" || true) + crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' || true) + sect=$(grep -c '^## ' "$p" || true) + inv=$(grep -c '^### Conditions inherited from the source row' "$p" || true) + printf '%-58s profile=%s crit=%s sections=%s inventory=%s\n' "$(basename "$p")" "$prof" "$crit" "$sect" "$inv" + [ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ] && [ "$inv" = 1 ] || rc=1 done +echo "stories exit=$rc" ``` -Expected: every row `profile=0 criterion=1 inventory=1`. +Expected: three rows reading `profile=0 crit=1 sections=6 inventory=1`, and `stories exit=0`. -- [ ] **Step 6: Commit** +- [ ] **Step 5: Commit** + +```bash +git add docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md +``` ```bash -git add docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ - docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ - docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md git commit -m "WIP: three split stories for the fired triggers" ``` --- -### Task 6: `todos.md` updates +### Task 6: `todos.md` — one new row, six existing rows **Files:** -- Modify: `todos.md` (one new row, five existing rows) +- Modify: `todos.md` **Interfaces:** - Consumes: the four story paths from Tasks 4–5. -- Produces: the parked C4/C5 row that ledger row B's `ref` cross-references (Task 7). +- Produces: the parked C4/C5 row ledger row B cross-references (Task 7). + +**Every mutation is idempotent.** Before each, check whether the marker text is already present: absent → apply; already present and identical → skip and say so; present and different → **stop and surface**. An interrupted task retried from the top must not duplicate a status block. + +```bash +cd "$(git rev-parse --show-toplevel)" +applied() { n=$(tr '\n' ' ' < todos.md | tr -s ' ' | grep -c "$1" || true); echo "$n"; } +``` - [ ] **Step 1: Add the parked C4/C5 compliance row** -Add under `### Parked (trigger-gated)`: +Guard: `applied 'The gate-claims Don.t is correct and was not followed, twice'` must be `0`. Add under `### Parked (trigger-gated)`: ```markdown - [ ] **The gate-claims Don't is correct and was not followed, twice.** PR #21's C4 (an @@ -699,17 +736,17 @@ Add under `### Parked (trigger-gated)`: inside the 2026-07-19 `AGENTS.md` Don't, whose operative instruction already requires exactly what they omitted — name the exact comparison the code performs, and delete any part of the sentence that outruns it. **No textual repair exists**, which is why these are - parked rather than logged: a ledger row would have to name a hardening, and the rule - needing no change means the failure was compliance, not wording. The 2026-08-04 amendment - covers coverage enumerations (F8's shape) and reaches neither a positive parity claim nor - a positive prevention claim. *Trigger: a third compliance miss against that Don't, or a + parked rather than logged: a ledger row would have to name a hardening, and a rule needing + no change means the failure was compliance, not wording. The 2026-08-04 amendment covers + coverage enumerations (F8's shape) and reaches neither a positive parity claim nor a + positive prevention claim. *Trigger: a third compliance miss against that Don't, or a feasible mechanical rung emerging from `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`.* ``` - [ ] **Step 2: Record evidence case 3 on the scope-blind row** -In the row `**`harden-finding`'s recurrence rule is scope-blind.**`, append before its `*Trigger:*` sentence: +Guard: `applied 'Evidence case 3 \(2026-08-04\)'` must be `0`. In `**`harden-finding`'s recurrence rule is scope-blind.**`, insert before its `*Trigger:*` sentence: ``` **Evidence case 3 (2026-08-04):** the 2026-08-03 hardening round ran the precheck as a @@ -722,7 +759,7 @@ In the row `**`harden-finding`'s recurrence rule is scope-blind.**`, append befo - [ ] **Step 3: Mark Finding A's row fired** -In the row `**Finding A — a route from a fixed finding to the ledger for projects that never open PRs.**`, append: +Guard: `applied 'TRIGGER FIRED \(2026-08-04\): the 2026-08-03 hardening round edits §5\.'` must be `0`. In `**Finding A — a route from a fixed finding to the ledger for projects that never open PRs.**`, append: ``` **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5. Story: @@ -732,7 +769,7 @@ In the row `**Finding A — a route from a fixed finding to the ledger for proje - [ ] **Step 4: Mark Finding B's row fired** -In the row `**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the installed plugin.**`, append: +Guard: `applied 'edits the §5 inline template'` must be `0`. In `**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the installed plugin.**`, append: ``` **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits the §5 inline @@ -743,7 +780,7 @@ In the row `**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can t - [ ] **Step 5: Mark the ledger-supersession row fired** -In the row `**The hardening ledger has no supersession convention.**`, append: +Guard: `applied 'the second falsified row this trigger names'` must be `0`. In `**The hardening ledger has no supersession convention.**`, append: ``` **TRIGGER FIRED (2026-08-04):** the 2026-07-20 row now teaches pre-0.8.0 counting @@ -753,7 +790,7 @@ In the row `**The hardening ledger has no supersession convention.**`, append: - [ ] **Step 6: Record that the slot-collision row did NOT fire** -In the row `**Each Gate cycle destroys the previous cycle's review record.**`, append: +Guard: `applied 'NOT FIRED \(2026-08-04\)'` must be `0`. In `**Each Gate cycle destroys the previous cycle's review record.**`, append: ``` **NOT FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5 prose and its template @@ -764,7 +801,7 @@ In the row `**Each Gate cycle destroys the previous cycle's review record.**`, a - [ ] **Step 7: Append the env-var observation, leaving the row open** -In the row `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**`, append: +Guard: `applied 'OBSERVATION \(2026-08-04\)'` must be `0`. In `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**`, append: ``` **OBSERVATION (2026-08-04), and the row stays open.** With the variable set to `0`, five @@ -777,16 +814,28 @@ In the row `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND unbuilt, so the row is not discharged by this. ``` -- [ ] **Step 8: Verify every story path referenced exists** +- [ ] **Step 8: Verify all seven changes landed exactly once, and every cited story exists** ```bash cd "$(git rev-parse --show-toplevel)" +n=$(tr '\n' ' ' < todos.md | tr -s ' ') +rc=0 +check() { c=$(printf '%s' "$n" | grep -c "$1" || true); printf '%-46s %s (want 1)\n' "$2" "$c"; [ "$c" = 1 ] || rc=1; } +check 'The gate-claims Don.t is correct and was not followed, twice' 'parked C4/C5 row' +check 'Evidence case 3 \(2026-08-04\)' 'scope-blind evidence case 3' +check 'hardening round edits §5\. Story' 'Finding A fired' +check 'edits the §5 inline template' 'Finding B fired' +check 'the second falsified row this trigger names' 'ledger-supersession fired' +check 'NOT FIRED \(2026-08-04\)' 'slot-collision not fired' +check 'OBSERVATION \(2026-08-04\)' 'env-var observation' +echo "--- every cited story path exists ---" grep -o 'docs/superpowers/stories/2026-08-04-[a-z0-9-]*\.md' todos.md | sort -u | while read -r p; do - [ -e "$p" ] && echo "OK $p" || echo "MISS $p" + if [ -e "$p" ]; then echo "OK $p"; else echo "MISS $p"; fi done +echo "todos exit=$rc" ``` -Expected: four `OK` lines, no `MISS`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens. +Expected: seven `1 (want 1)` lines, four `OK` story paths and no `MISS`, `todos exit=0`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens. - [ ] **Step 9: Run the battery** @@ -796,6 +845,9 @@ Expected: exit 0. ```bash git add todos.md +``` + +```bash git commit -m "WIP: park the compliance recurrence and record the fired triggers" ``` @@ -810,19 +862,33 @@ git commit -m "WIP: park the compliance recurrence and record the fired triggers - Consumes: the three §5 sentences (Task 1), the `AGENTS.md` amendment (Task 2), the taxonomy class (Task 3), the parked row (Task 6). - Produces: nothing downstream. -Append in order A, B, C, D at the end of the table. Each row is one line — the newlines below are for readability in this plan only; **write each as a single line.** +Append in order A, B, C, D at the end of the table. Each row is **one line** — the blocks below are single lines that this document wraps for display; write each as one line with no internal newline. -- [ ] **Step 1: Re-read the log and check for a same-fingerprint row appearing meanwhile** +- [ ] **Step 1: Re-read the log and require exact fingerprint counts** ```bash cd "$(git rev-parse --show-toplevel)" -for fp in docs-drift unverified-enforcement-claim verification-masks-failure mechanical-check-skipped-before-review; do - printf '%-42s ' "$fp" - grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md -done +rc=0 +expect() { + fp=$1; want=$2 + got=$(grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md || true) + printf '%-42s got=%s want=%s\n' "$fp" "$got" "$want" + if [ "$got" != "$want" ]; then + rc=1 + echo " --- rows found, for the stop report ---" + grep -nE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md | cut -c1-160 + fi +} +expect docs-drift 5 +expect unverified-enforcement-claim 5 +expect verification-masks-failure 1 +expect mechanical-check-skipped-before-review 0 +echo "ledger precondition exit=$rc" ``` -Expected: `docs-drift 5`, `unverified-enforcement-claim 5`, `verification-masks-failure 1`, `mechanical-check-skipped-before-review 0`. If any count is higher, a row appeared since this plan was written: **stop and surface**, naming the row. Do not append. Resolving a mid-run collision is the split story's subject (Task 4), and inventing the procedure here would re-import what was split out. +Expected: `ledger precondition exit=0`. + +**Any inequality — higher or lower — stops the task.** Higher means a row appeared since this plan was written; lower means a row this plan's premise depends on was removed or rewritten. Either invalidates the reviewed precondition. **Stop and surface, naming the rows the command printed.** Do not append. Resolving a mid-run collision is the split story's subject (Task 4), and inventing the procedure here would re-import what was split out. - [ ] **Step 2: Append row A** @@ -848,50 +914,53 @@ Expected: `docs-drift 5`, `unverified-enforcement-claim 5`, `verification-masks- | 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | ``` -- [ ] **Step 6: Verify the rows are well-formed** +- [ ] **Step 6: Verify the appended rows** ```bash cd "$(git rev-parse --show-toplevel)" -echo "--- new rows are single lines with 7 fields ---" -tail -4 docs/hardening-log.md | awk -F'|' '{print NR": fields="NF-2}' +rc=0 +echo "--- the four new rows: 7 fields, 8 pipes, one line each ---" +tail -4 docs/hardening-log.md | awk -F'|' '{ + pipes = gsub(/\|/,"|") + printf "row %d: fields=%d pipes=%d\n", NR, NF-2, pipes + if (NF-2 != 7 || pipes != 8) exit_code=1 +} END { exit exit_code+0 }' || rc=1 echo "--- fingerprint counts after append ---" -for fp in docs-drift unverified-enforcement-claim verification-masks-failure mechanical-check-skipped-before-review; do - printf '%-42s ' "$fp"; grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md +for pair in "docs-drift 6" "unverified-enforcement-claim 6" "verification-masks-failure 2" "mechanical-check-skipped-before-review 1"; do + fp=${pair% *}; want=${pair#* } + got=$(grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md || true) + printf '%-42s got=%s want=%s\n' "$fp" "$got" "$want" + [ "$got" = "$want" ] || rc=1 done -echo "--- no unescaped pipes inside fields (each row must have exactly 8 pipes) ---" -tail -4 docs/hardening-log.md | awk '{n=gsub(/\|/,"|"); print NR": pipes="n}' +echo "ledger exit=$rc" ``` -Expected: `fields=7` for each of the four; counts `6 / 6 / 2 / 1`; `pipes=8` for each row. +Expected: four rows each `fields=7 pipes=8`; counts `6 / 6 / 2 / 1`; `ledger exit=0`. - [ ] **Step 7: Run the battery** -Expected: exit 0. Note `check-invariants.sh` excludes `docs/hardening-log.md` from checks 4a/4b — a ledger that quotes defects would otherwise self-reject. +Expected: exit 0. `check-invariants.sh` excludes `docs/hardening-log.md` from checks 4a/4b — a ledger that quotes defects would otherwise self-reject. - [ ] **Step 8: Commit** ```bash git add docs/hardening-log.md +``` + +```bash git commit -m "WIP: four ledger rows for the 0.8.0 and PR #21 hardenings" ``` --- -### Task 8: Version bump and CHANGELOG +### Task 8: The CHANGELOG entry **Files:** -- Modify: `plugins/dev-workflow/.claude-plugin/plugin.json:4` -- Modify: `plugins/dev-workflow/CHANGELOG.md` (new `## 0.8.1` section, directly under the intro, above `## 0.8.0`) - -**Interfaces:** -- Consumes: everything above (the CHANGELOG describes it). -- Produces: nothing downstream. - -- [ ] **Step 1: Bump the manifest** +- Modify: `plugins/dev-workflow/CHANGELOG.md` (new `## 0.8.1` section directly above `## 0.8.0`) -In `plugins/dev-workflow/.claude-plugin/plugin.json`, change `"version": "0.8.0",` to `"version": "0.8.1",`. +The manifest bump already landed in Task 1. This task adds the entry describing what shipped, which is only writable once the content exists. -- [ ] **Step 2: Add the CHANGELOG entry** +- [ ] **Step 1: Add the entry** Insert immediately above `## 0.8.0`: @@ -914,25 +983,30 @@ Insert immediately above `## 0.8.0`: collisions that a rule paragraph cannot carry. It is split to its own story. ``` -- [ ] **Step 3: Verify the bump is detected** +- [ ] **Step 2: Verify** ```bash cd "$(git rev-parse --show-toplevel)" -grep -n '"version"' plugins/dev-workflow/.claude-plugin/plugin.json -grep -n '^## 0\.8\.1' plugins/dev-workflow/CHANGELOG.md +h=$(grep -c '^## 0\.8\.1' plugins/dev-workflow/CHANGELOG.md || true) +v=$(grep -o '"version": *"0\.8\.1"' plugins/dev-workflow/.claude-plugin/plugin.json | head -1) +printf 'changelog headings=%s (want 1) manifest=%s\n' "$h" "$v" +[ "$h" = 1 ] && [ -n "$v" ] && echo OK || echo "WRONG — stop and surface" ``` -Expected: version `0.8.1`, and one `## 0.8.1` heading. +Expected: `OK`. -- [ ] **Step 4: Run the battery** +- [ ] **Step 3: Run the battery** -Expected: exit 0. `check-version-bump.sh main` compares commits, so run it once this task is committed — on `main` the merge-base is HEAD and it passes trivially; CI runs it against the PR's base. +Expected: exit 0. -- [ ] **Step 5: Commit** +- [ ] **Step 4: Commit** ```bash -git add plugins/dev-workflow/.claude-plugin/plugin.json plugins/dev-workflow/CHANGELOG.md -git commit -m "WIP: bump to 0.8.1 with its changelog entry" +git add plugins/dev-workflow/CHANGELOG.md +``` + +```bash +git commit -m "WIP: 0.8.1 changelog entry" ``` --- @@ -940,67 +1014,141 @@ git commit -m "WIP: bump to 0.8.1 with its changelog entry" ### Task 9: Validation evidence and the Gate-B cycle **Files:** -- No new files. This task produces the evidence entry that the cycle-closing commit body carries. +- Create: `.context/evidence-0.8.1.md` — the durable evidence record. `.context/` is git-ignored, so it never enters the diff; its content is folded into the closing commit body at Step 8. **Interfaces:** - Consumes: every prior task. - Produces: the final commit. -- [ ] **Step 1: Squash the WIP commits into one snapshot** +- [ ] **Step 1: Create the evidence file with its full structure** ```bash cd "$(git rev-parse --show-toplevel)" -FIRST_WIP=$(git log --format='%H %s' | awk '/WIP: three §5 sentences/{print $1}') +EVIDENCE="$(git rev-parse --show-toplevel)/.context/evidence-0.8.1.md" +mkdir -p "$(dirname "$EVIDENCE")" +cat > "$EVIDENCE" <<'EOF' +# Validation evidence — hardening round 0.8.1 + +Story: docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md + +## battery +command: (the quality command from AGENTS.md § Commands) +result: + +## check (named verification), with its counterfactual +§5.2 applied to the four cases in spec §10 — each must FAIL on the wiring half: + 1. $EVIDENCE dry run -> + 2. single-shell regression -> + 3. timed regression row -> + 4. dash release evidence -> +§5.4 applied to "It bounds the scan, not memory" — must FAIL on exhaustiveness: + +counterfactual: all four were reviewed during the 0.8.0 cycle under the §5 text as it +read before this change and each was accepted by at least one review looking for exactly +this; one (the single-shell test) reached the released artifact. + +## prompt conformance +workflow-init.md (invariant 11 surface) — all 12 items: +CLAUDE.md, AGENTS.md (not on invariant 11's list) — items 6,7,8,9,11,12: +four split stories — in-spirit brief review: + +## mirror parity +Task 1 Step 6 re-run result: + +## cross-finding conflict check +Task 0 verdict: +EOF +echo "created: $EVIDENCE" +[ -s "$EVIDENCE" ] && echo "non-empty OK" || echo "EMPTY — stop and surface" +``` + +`$EVIDENCE` is defined here, in this step, before any later step reads it. A dry run that assigns the variable itself would prove the shell mechanics and never that this plan defines it — the `verification-masks-failure` case row C records. + +- [ ] **Step 2: Squash the WIP commits into one snapshot** + +```bash +cd "$(git rev-parse --show-toplevel)" +n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) +echo "matching WIP anchors: $n (want exactly 1)" +if [ "$n" != "1" ]; then echo "AMBIGUOUS OR MISSING ANCHOR — stop and surface"; else + FIRST_WIP=$(git log --format='%H %s' | awk '/^[0-9a-f]+ WIP: three §5 sentences/{print $1}') + echo "first WIP: $FIRST_WIP" + git log --oneline "$FIRST_WIP"^..HEAD +fi +``` + +Expected: `matching WIP anchors: 1`, then a log listing exactly the WIP commits from Tasks 1–8 and nothing else. If a resumed or repeated task produced two matching subjects, **stop and surface** — a reset against an ambiguous anchor rewrites history at the wrong point. + +Then: + +```bash git reset --soft "$FIRST_WIP"^ +``` + +```bash git commit -m "WIP: hardening round — the 0.8.0 cycle and PR #21" -git log --oneline -3 ``` -`git commit` and `git add` must be **separate** calls throughout: the hook derives its docs-only file list at `PreToolUse`, so a compound `git add && git commit` presents an empty index and fires a spurious Gate-B STOP. +Expected afterwards: `git log --oneline -2` shows the single WIP snapshot on top of the pre-round HEAD. -- [ ] **Step 2: Run the full battery and capture the real numbers** +- [ ] **Step 3: Run the full battery and record the real numbers** -Run the quality command from Global Constraints. Record the actual counts it prints — shellcheck files, hook suite assertions under both `sh` and `dash`, invariant assertions, version-bump assertions, `claude plugin validate` result. Do not write a number you did not read from output. +Run the quality command. Write the actual counts it prints into `$EVIDENCE` under `## battery` — shellcheck files, hook suite assertions under both `sh` and `dash`, invariant assertions, version-bump assertions, `claude plugin validate` result. **Do not write a number you did not read from output.** -- [ ] **Step 3: Run the named verification** +- [ ] **Step 4: Run the named verification and record it** -Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10. Each must **fail** on the second half. Then apply §5.4's appended clause to `It bounds the **scan**, not memory` — it must fail on the exhaustiveness requirement. +Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10. Each must **fail** on the second half. Then apply §5.4's appended clause to `It bounds the **scan**, not memory` — it must fail on exhaustiveness. Write all five verdicts into `$EVIDENCE`. -Record the result per case. If any case passes the sentence written to reject it, that sentence is miswired: **stop and surface**, do not proceed to Gate B. +If any case passes the sentence written to reject it, that sentence is miswired: **stop and surface**, do not proceed to Gate B. -- [ ] **Step 4: Run the prompt-conformance reviews** +- [ ] **Step 5: Run the prompt-conformance reviews and record them** `plugins/dev-workflow/commands/workflow-init.md` is the only changed file inside invariant 11's enumerated surface. Review it against all 12 items of `docs/prompt-standards.md`; all 12 must pass, with an exception only where the checklist item itself authorizes one (items 9 and 12 do), recorded with that item's stated reason. -`CLAUDE.md` and `AGENTS.md` are not on that list — neither carries a `Target model:` line, because neither is executed against a named model. Review their edits against items 6, 7, 8, 9, 11 and 12 and record the result. §5.4's clause is a continuation of an existing prohibition; item 9's own text exempts rules whose subject is the prohibition, so cite that exemption rather than assuming it. +`CLAUDE.md` and `AGENTS.md` are not on that list — neither carries a `Target model:` line, because neither is executed against a named model. Review their edits against items 6, 7, 8, 9, 11 and 12. §5.4's clause continues an existing prohibition; item 9's own text exempts rules whose subject is the prohibition, so cite that exemption rather than assuming it. Give each of the four split stories a recorded in-spirit brief review — success criteria, stop conditions, verified claims — without asserting the mandatory 12 apply. -- [ ] **Step 5: Run the cross-finding conflict check** +Write all results into `$EVIDENCE`. -Confirm no two findings pull one artifact in opposite directions. Expected verdict: none — the four hardenings touch four distinct sites (the Gate-B lens paragraph, the Profiles counterfactual paragraph, the Gate-A pass bullet, and the 2026-07-19 Don't), and none rewrites text another needs. If a conflict appears, **stop and surface** rather than choosing. +- [ ] **Step 6: Re-run mirror parity and record it** -- [ ] **Step 6: Run Gate B** +Re-run Task 1 Step 6's block and write the result into `$EVIDENCE`. Parity is checked here, again after every Gate-B fix, and once more immediately before Step 8 — a review fix can touch only `CLAUDE.md` or only the template and otherwise reach the final commit unnoticed. -Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the WIP commit. Delete both branch files first and confirm they are gone. Carry the story path and the evidence entry verbatim in `additionalContext`, plus the standing lens. +- [ ] **Step 7: Run Gate B** + +Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the WIP commit. Delete both branch target files first and confirm they are gone. Carry the story path and `$EVIDENCE`'s content verbatim in `additionalContext`, plus the standing lens. Minimum three passes, final pass clean. Re-review after every fix — a fix changes the diff. Every pass writes to `.context/codex-reviews/gate-b--pass-

.md` and is validated before it is read: terminator exact, count matching, nothing but finding lines, both branch files present. -- [ ] **Step 7: Close the cycle by amend** +After each fix: re-run the battery, re-run mirror parity, and update `$EVIDENCE` before the next call. + +- [ ] **Step 8: Close the cycle by amend** ```bash cd "$(git rev-parse --show-toplevel)" -git commit --amend -F /path/to/final-message.txt -git log --oneline -1 +EVIDENCE="$(git rev-parse --show-toplevel)/.context/evidence-0.8.1.md" +[ -r "$EVIDENCE" ] && [ -s "$EVIDENCE" ] || { echo "EVIDENCE missing or empty — stop"; exit 1; } +grep -q ' Date: Tue, 4 Aug 2026 13:13:45 +0200 Subject: [PATCH 07/11] docs(plan): apply Gate-A plan pass 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eleven findings, all accepted. Ten are one class: a verification block that reports a failure and then exits 0, or that cannot see the failure at all — in a plan whose deliverable includes the verification-masks-failure hardening. Found by the reviewer, not by the sweep run before the previous commit. - Nine blocks printed their verdict and ended on an echo, so shell status was 0 on every mismatch. All now accumulate into rc and exit with it. - The cited-story loop ran in a pipeline subshell, discarding rc=1; it could print MISS and still report success. Reads from a redirected file now, and asserts exactly four distinct paths while rejecting symlinks. - applied() and FIRST_WIP were defined in one fenced block and used in later ones. Agentic workers run each block in a fresh shell, so neither existed. - Marker counts cannot tell "already applied" from "partially applied"; the guard now compares the whole intended block with three explicit branches. - Mirror parity checked equality without cardinality, so duplicates or two empty extractions would have reported equivalence. Path handling: a symlink whose target held identical text was eligible for reuse, which would stage a link rather than the story bytes — now an unconditional stop, tested before -e. Placeholder reservation closed the check-to-create race and opened a create-to-write one; content is now installed atomically with ln. Creation failures no longer all blame a concurrent writer. The plan asked for counts the battery does not print. Established by running it: the hook suite prints "all passed" with no total, the invariants and version-bump suites print their assertion counts, shellcheck prints nothing. The evidence template now asks for terminal lines verbatim and labels the hook-suite count as derived, with the deriving command — itself run before being written down. The no-PR story's inheritance inventory had dropped one of its source row's two trigger alternatives, in the inventory whose purpose is to drop none. Sweep after fixing: 38 blocks, all parse under sh and dash, no bash-isms, no pipeline-subshell accumulation, every failure path exits nonzero. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 208 ++++++++++++++---- 1 file changed, 162 insertions(+), 46 deletions(-) diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md index d3a3bf9..b7135f3 100644 --- a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -36,6 +36,10 @@ Copied verbatim from the spec and `AGENTS.md`. Every task's requirements implici - **No skill file is edited.** `plugins/dev-workflow/skills/**` is out of scope (spec D7). If a step seems to need a skill edit, stop and surface. - **POSIX `sh` only** (invariant 4). No `<(...)`, no `[[ ]]`, no arrays, no `${var:offset:length}`, no `local`. `sh -n` is a *parse* check and will not catch `${var:0:3}` — it fails at runtime under `dash` with `Bad substitution`. Test any new snippet with `dash -c`, not only `sh -n`. - **`grep -c` exits 1 when the count is zero.** Every expected-zero check in this plan captures the count and asserts the number; none relies on grep's exit status. +- **Every verification block is self-contained and exits nonzero when it fails.** Two rules, both learned the hard way: + - **Self-contained:** a block defines every function and variable it uses. Agentic workers run each fenced block in a fresh shell, so a helper defined in one block and called in another simply is not there. + - **Fails loudly:** a block that prints `MISMATCH` and then ends on an `echo` exits 0, and any wrapper trusting exit status walks straight through the stop path. Every check accumulates into `rc` and ends with `exit "$rc"`. + - **No pipeline subshells for accumulation:** `... | while read x; do rc=1; done` sets `rc` in a subshell and discards it. Redirect from a file or use a here-document instead. - **Version is `0.8.1`**, committed in Task 1 alongside the first `plugins/` change. The battery includes `check-version-bump.sh main`, which fails on any committed `plugins/` change without a bump — so deferring the bump would make every intervening battery run fail by construction. - **The ledger is append-only.** Never edit an existing row. - **Escape a literal `|` inside a ledger field as `\|`.** This round avoids guard quotations containing pipes rather than relying on an unstated rule for the `ref` column. @@ -137,24 +141,33 @@ In `plugins/dev-workflow/.claude-plugin/plugin.json`, change `"version": "0.8.0" ```bash cd "$(git rev-parse --show-toplevel)" -norm() { tr '\n' ' ' < "$1" | tr -s ' '; } rc=0 for pat in 'Name what this diff changes[^.]*\.' \ 'Name the observation that would exist[^.]*\.' \ 'Before each read pass[^.]*\.' ; do - a=$(norm CLAUDE.md | grep -o "$pat" || true) - b=$(norm plugins/dev-workflow/commands/workflow-init.md | grep -o "$pat" || true) - if [ -n "$a" ] && [ "$a" = "$b" ]; then + a=$(tr '\n' ' ' < CLAUDE.md | tr -s ' ' | grep -o "$pat" || true) + b=$(tr '\n' ' ' < plugins/dev-workflow/commands/workflow-init.md | tr -s ' ' | grep -o "$pat" || true) + na=$(printf '%s' "$a" | grep -c . || true) + nb=$(printf '%s' "$b" | grep -c . || true) + if [ "$na" != "1" ] || [ "$nb" != "1" ]; then + rc=1 + printf 'CARDINALITY: expected exactly one match per file, got CLAUDE=%s TEMPLATE=%s for %s\n' "$na" "$nb" "$pat" + elif [ "$a" = "$b" ]; then printf 'EQUIVALENT: %.55s...\n' "$a" else rc=1 - printf 'MISMATCH OR EMPTY\n CLAUDE : %s\n TEMPLATE: %s\n' "$a" "$b" + printf 'MISMATCH\n CLAUDE : %s\n TEMPLATE: %s\n' "$a" "$b" fi done echo "parity exit=$rc" +exit "$rc" ``` -Expected: three `EQUIVALENT:` lines and `parity exit=0`. The `-n "$a"` guard is load-bearing — without it two empty extractions compare equal and report a match that proves nothing, which is the `verification-masks-failure` shape this round hardens. `printf '%.55s'` is used rather than `${a:0:55}`, which is a bash-ism that fails under `dash`. +Expected: three `EQUIVALENT:` lines, `parity exit=0`, and **shell status 0**. + +Three things in that block are load-bearing. The **cardinality check** comes before the comparison: two empty extractions compare equal, and two duplicated copies compare equal, so equality alone reports a match that proves nothing — the `verification-masks-failure` shape this round hardens. `printf '%.55s'` is used rather than `${a:0:55}`, a bash-ism that fails under `dash` at runtime while passing `sh -n`. And the block **ends with `exit "$rc"`**, because a block that prints `MISMATCH` and then ends on an `echo` exits 0. + +**Placement is not checked mechanically here.** Cardinality proves each sentence appears once per file; that it sits under the corresponding heading is confirmed by reading, at Steps 1–4. A grep cannot cheaply establish "under the same heading" in two files that nest at different depths, and claiming otherwise would overstate what the check compares. - [ ] **Step 7: Verify the manifest** @@ -162,7 +175,7 @@ Expected: three `EQUIVALENT:` lines and `parity exit=0`. The `-n "$a"` guard is cd "$(git rev-parse --show-toplevel)" v=$(grep -o '"version": *"[^"]*"' plugins/dev-workflow/.claude-plugin/plugin.json | head -1) echo "$v" -[ "$v" = '"version": "0.8.1"' ] && echo "version OK" || echo "version WRONG" +if [ "$v" = '"version": "0.8.1"' ]; then echo "version OK"; exit 0; else echo "version WRONG — stop and surface"; exit 1; fi ``` Expected: `version OK`. @@ -229,7 +242,7 @@ Expected: **fails on the exhaustiveness requirement.** If it passes, the wording cd "$(git rev-parse --show-toplevel)" n=$(tr '\n' ' ' < AGENTS.md | tr -s ' ' | grep -c 'delete any part of the sentence that outruns it' || true) echo "AGENTS.md operative copies: $n" -[ "$n" = "1" ] && echo "OK" || echo "UNEXPECTED — stop and surface" +if [ "$n" = "1" ]; then echo "OK"; exit 0; else echo "UNEXPECTED — stop and surface"; exit 1; fi ``` Expected: `1` and `OK`. The rule is repo-local and is **not** mirrored into the scaffolded template, so no second operative copy needs the amendment. The spec and this plan both quote the phrase; those are quotations of the rule, not copies of it, and are deliberately not searched here — a repository-wide grep would report them and send the executor to a stop branch with nothing wrong. @@ -280,7 +293,7 @@ Insert as the last entry under `## Classes`, immediately before the `**Promotion cd "$(git rev-parse --show-toplevel)" n=$(grep -c "mechanical-check-skipped-before-review" plugins/dev-workflow/skills/harden-finding/SKILL.md || true) echo "occurrences in SKILL.md: $n" -[ "$n" = "0" ] && echo "OK — invariant 10 holds" || echo "VIOLATION — stop and surface" +if [ "$n" = "0" ]; then echo "OK — invariant 10 holds"; exit 0; else echo "VIOLATION — stop and surface"; exit 1; fi ``` Expected: `0` and `OK`. The count is captured and compared numerically because `grep -c` exits 1 on zero, which would otherwise abort the step on the correct result. @@ -312,23 +325,52 @@ git commit -m "WIP: mint mechanical-check-skipped-before-review" - Consumes: nothing. - Produces: the story path `todos.md`'s scope-blind row points at (Task 6). -- [ ] **Step 1: Classify and create the path atomically** +- [ ] **Step 1: Classify the path (read-only)** ```bash cd "$(git rev-parse --show-toplevel)" p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +if [ -L "$p" ]; then + echo "SYMLINK at target — stop and surface"; exit 1 +elif [ -d "$p" ]; then + echo "DIRECTORY at target — stop and surface"; exit 1 +elif [ -e "$p" ]; then + echo "REGULAR FILE exists — compare bytes against Step 2; identical means reuse, different means stop"; exit 0 +else + echo "ABSENT — Step 2 will create it"; exit 0 +fi +``` + +**A symlink is an unconditional stop, never a reuse candidate**, even if its target currently holds byte-identical text: `git add` would stage a link rather than the durable story bytes, and every shape check in Step 3 would follow the target and pass while another checkout receives a dangling or retargeted path. `[ -L ]` is tested first because `[ -e ]` is false for a dangling symlink and true for a live one, so `-e` alone cannot tell either case from a regular file. + +- [ ] **Step 2: Write the story, installing it atomically** + +Write the text below to a temporary file in the same directory, then link it into place. `ln` fails if the target exists, so creation is atomic against another writer — unlike reserving an empty placeholder and filling it later, which leaves a window in which the placeholder can be modified and then silently overwritten. + +```bash +cd "$(git rev-parse --show-toplevel)" +p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md +tmp="$(dirname "$p")/.tmp-$$-story.md" +# (write the full story text of this step into "$tmp" here) if [ -e "$p" ] || [ -L "$p" ]; then - echo "EXISTS (or is a symlink) — compare bytes against Step 2 before doing anything" -elif (set -C; : > "$p") 2>/dev/null; then - echo "CREATED empty placeholder — safe to write" + if cmp -s "$tmp" "$p"; then echo "IDENTICAL — reusing existing file"; rm -f "$tmp"; exit 0 + else echo "DIFFERENT content at target — stop and surface"; rm -f "$tmp"; exit 1; fi +fi +if ln "$tmp" "$p" 2>/dev/null; then + rm -f "$tmp"; echo "INSTALLED $p"; exit 0 +fi +rm -f "$tmp" +if [ -e "$p" ] || [ -L "$p" ]; then + echo "COLLISION: an entry appeared at $p between the check and the install — stop and surface" else - echo "APPEARED between the test and the create — stop and surface" + echo "CREATE FAILED and no entry exists — investigate permissions, a missing parent directory, or a full or read-only filesystem" fi +exit 1 ``` -`set -C` (noclobber) makes the creation fail if any directory entry now exists, so a file appearing between the test and the write is caught rather than overwritten. `[ -L ]` is tested separately because `[ -e ]` is false for a dangling symlink, which would otherwise be silently replaced. +The failure branch **re-stats the path before naming a cause**. Reporting every install failure as a concurrent writer would send the engineer hunting for another session when the real cause is a permission or a missing directory — the `prompt-diagnostic-cause-unnamed` class in this repo's own taxonomy. -If it exists and its content is byte-identical to Step 2's text, reuse it and say so. If it exists and differs, **stop and surface** — do not overwrite (invariant 9). +The story text: - [ ] **Step 2: Write the story** @@ -433,7 +475,7 @@ prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" | crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' || true) sect=$(grep -c '^## ' "$p" || true) printf 'profile lines=%s (want 0) criterion=%s (want 1) sections=%s (want 6)\n' "$prof" "$crit" "$sect" -[ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ] && echo OK || echo "WRONG — stop and surface" +if [ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ]; then echo OK; exit 0; else echo "WRONG — stop and surface"; exit 1; fi ``` Expected: `OK`. @@ -467,7 +509,7 @@ Each replaces a parked `todos.md` row carrying settled analysis, so each holds a - [ ] **Step 1: Classify, then write the ledger-supersession story** -Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`, then write: +Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`, then write: ```markdown # The hardening ledger has no supersession convention — Story @@ -543,7 +585,7 @@ row falsified by a later change can be marked as such without breaking either st - [ ] **Step 2: Classify, then write the no-PR ledger route story** -Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, then write: +Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, then write: ```markdown # A route from a fixed finding to the ledger, for projects that never open PRs — Story @@ -573,7 +615,8 @@ never open PRs.**": | A durable handoff needs real design — identity, deduplication, consumption semantics | **kept**, and it is why this is a story rather than a mid-round addition | | It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it | | Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | -| Trigger: the next round that touches §5, or a project reporting an empty ledger across cycles | **moved** — fired by the 2026-08-03 round, recorded here | +| Trigger, first alternative: the next round that touches §5 | **moved** — fired by the 2026-08-03 round, recorded here | +| Trigger, second alternative: a project reporting an empty ledger across cycles that fixed findings | **kept** — it did not fire, and it remains the condition that would raise this independently if the first had not | ## 2. Desired outcome @@ -609,7 +652,7 @@ or a handoff. - [ ] **Step 3: Classify, then write the §5 version stamp story** -Run Task 4 Step 1's block with `p=docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, then write: +Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, then write: ```markdown # A §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the plugin — Story @@ -693,6 +736,7 @@ for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story [ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ] && [ "$inv" = 1 ] || rc=1 done echo "stories exit=$rc" +exit "$rc" ``` Expected: three rows reading `profile=0 crit=1 sections=6 inventory=1`, and `stories exit=0`. @@ -718,11 +762,28 @@ git commit -m "WIP: three split stories for the fired triggers" - Consumes: the four story paths from Tasks 4–5. - Produces: the parked C4/C5 row ledger row B cross-references (Task 7). -**Every mutation is idempotent.** Before each, check whether the marker text is already present: absent → apply; already present and identical → skip and say so; present and different → **stop and surface**. An interrupted task retried from the top must not duplicate a status block. +**Every mutation is idempotent, and idempotence is decided on the whole block, not on a marker.** Before each mutation, run the guard below with that step's marker and its full intended text. A marker count alone cannot tell an identical completed mutation from a partial or independently edited one, and skipping on a marker hit would leave `todos.md` semantically wrong while the final count check still passed. + +The guard is written out in each step rather than defined once, because **agentic workers run each fenced block in a fresh shell** — a helper defined in one block is not in scope in the next. ```bash +# Guard template. MARKER is a regex identifying the block; INTENDED is a file +# holding the exact text this step would add. cd "$(git rev-parse --show-toplevel)" -applied() { n=$(tr '\n' ' ' < todos.md | tr -s ' ' | grep -c "$1" || true); echo "$n"; } +n=$(tr '\n' ' ' < todos.md | tr -s ' ' | grep -c 'MARKER' || true) +if [ "$n" = "0" ]; then + echo "ABSENT — apply this step"; exit 0 +elif [ "$n" != "1" ]; then + echo "MARKER APPEARS $n TIMES — stop and surface"; exit 1 +fi +# present exactly once: compare the whole intended block, whitespace-normalized +have=$(tr '\n' ' ' < todos.md | tr -s ' ') +want=$(tr '\n' ' ' < INTENDED | tr -s ' ') +if printf '%s' "$have" | grep -qF "$want"; then + echo "ALREADY APPLIED, identical — skip this step"; exit 0 +else + echo "PRESENT BUT DIFFERENT — stop and surface"; exit 1 +fi ``` - [ ] **Step 1: Add the parked C4/C5 compliance row** @@ -820,22 +881,37 @@ Guard: `applied 'OBSERVATION \(2026-08-04\)'` must be `0`. In `**`/workflow-init cd "$(git rev-parse --show-toplevel)" n=$(tr '\n' ' ' < todos.md | tr -s ' ') rc=0 -check() { c=$(printf '%s' "$n" | grep -c "$1" || true); printf '%-46s %s (want 1)\n' "$2" "$c"; [ "$c" = 1 ] || rc=1; } -check 'The gate-claims Don.t is correct and was not followed, twice' 'parked C4/C5 row' -check 'Evidence case 3 \(2026-08-04\)' 'scope-blind evidence case 3' -check 'hardening round edits §5\. Story' 'Finding A fired' -check 'edits the §5 inline template' 'Finding B fired' -check 'the second falsified row this trigger names' 'ledger-supersession fired' -check 'NOT FIRED \(2026-08-04\)' 'slot-collision not fired' -check 'OBSERVATION \(2026-08-04\)' 'env-var observation' -echo "--- every cited story path exists ---" -grep -o 'docs/superpowers/stories/2026-08-04-[a-z0-9-]*\.md' todos.md | sort -u | while read -r p; do - if [ -e "$p" ]; then echo "OK $p"; else echo "MISS $p"; fi +for spec in \ + 'parked C4/C5 row=The gate-claims Don.t is correct and was not followed, twice' \ + 'scope-blind evidence case 3=Evidence case 3 \(2026-08-04\)' \ + 'Finding A fired=hardening round edits §5\. Story' \ + 'Finding B fired=edits the §5 inline template' \ + 'ledger-supersession fired=the second falsified row this trigger names' \ + 'slot-collision not fired=NOT FIRED \(2026-08-04\)' \ + 'env-var observation=OBSERVATION \(2026-08-04\)' ; do + label=${spec%%=*}; pat=${spec#*=} + c=$(printf '%s' "$n" | grep -c "$pat" || true) + printf '%-30s %s (want 1)\n' "$label" "$c" + [ "$c" = "1" ] || rc=1 done + +echo "--- every cited story path exists, and there are exactly four ---" +grep -o 'docs/superpowers/stories/2026-08-04-[a-z0-9-]*\.md' todos.md | sort -u > /tmp/cited-stories.txt +cited=$(grep -c . /tmp/cited-stories.txt || true) +printf 'distinct cited story paths=%s (want 4)\n' "$cited" +[ "$cited" = "4" ] || rc=1 +while IFS= read -r sp; do + if [ -f "$sp" ] && [ ! -L "$sp" ]; then echo "OK $sp"; else echo "MISS or NOT A REGULAR FILE: $sp"; rc=1; fi +done < /tmp/cited-stories.txt +rm -f /tmp/cited-stories.txt + echo "todos exit=$rc" +exit "$rc" ``` -Expected: seven `1 (want 1)` lines, four `OK` story paths and no `MISS`, `todos exit=0`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens. +Expected: seven `1 (want 1)` lines, `distinct cited story paths=4`, four `OK` lines, `todos exit=0`, and **shell status 0**. + +The loop reads from a redirected file rather than a pipeline, because `... | while read` runs the loop body in a subshell and discards every `rc=1` set inside it — the check would print `MISS` and still report `todos exit=0`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens, so the check has to be able to fail. - [ ] **Step 9: Run the battery** @@ -884,6 +960,7 @@ expect unverified-enforcement-claim 5 expect verification-masks-failure 1 expect mechanical-check-skipped-before-review 0 echo "ledger precondition exit=$rc" +exit "$rc" ``` Expected: `ledger precondition exit=0`. @@ -933,6 +1010,7 @@ for pair in "docs-drift 6" "unverified-enforcement-claim 6" "verification-masks- [ "$got" = "$want" ] || rc=1 done echo "ledger exit=$rc" +exit "$rc" ``` Expected: four rows each `fields=7 pipes=8`; counts `6 / 6 / 2 / 1`; `ledger exit=0`. @@ -990,7 +1068,7 @@ cd "$(git rev-parse --show-toplevel)" h=$(grep -c '^## 0\.8\.1' plugins/dev-workflow/CHANGELOG.md || true) v=$(grep -o '"version": *"0\.8\.1"' plugins/dev-workflow/.claude-plugin/plugin.json | head -1) printf 'changelog headings=%s (want 1) manifest=%s\n' "$h" "$v" -[ "$h" = 1 ] && [ -n "$v" ] && echo OK || echo "WRONG — stop and surface" +if [ "$h" = 1 ] && [ -n "$v" ]; then echo OK; exit 0; else echo "WRONG — stop and surface"; exit 1; fi ``` Expected: `OK`. @@ -1033,7 +1111,15 @@ Story: docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story ## battery command: (the quality command from AGENTS.md § Commands) -result: +exit status: +terminal line, hook suite under sh: +terminal line, hook suite under dash: +terminal line, invariants suite: +terminal line, version-bump suite: +claude plugin validate --strict: +derived hook-suite assertion counts (the suite prints none): + HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' -> + HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' -> ## check (named verification), with its counterfactual §5.2 applied to the four cases in spec §10 — each must FAIL on the wiring half: @@ -1059,30 +1145,41 @@ Task 1 Step 6 re-run result: Task 0 verdict: EOF echo "created: $EVIDENCE" -[ -s "$EVIDENCE" ] && echo "non-empty OK" || echo "EMPTY — stop and surface" +if [ -s "$EVIDENCE" ]; then echo "non-empty OK"; exit 0; else echo "EMPTY — stop and surface"; exit 1; fi ``` `$EVIDENCE` is defined here, in this step, before any later step reads it. A dry run that assigns the variable itself would prove the shell mechanics and never that this plan defines it — the `verification-masks-failure` case row C records. - [ ] **Step 2: Squash the WIP commits into one snapshot** +Inspect first. This block is read-only and prints the hash the next block needs: + ```bash cd "$(git rev-parse --show-toplevel)" n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) echo "matching WIP anchors: $n (want exactly 1)" -if [ "$n" != "1" ]; then echo "AMBIGUOUS OR MISSING ANCHOR — stop and surface"; else - FIRST_WIP=$(git log --format='%H %s' | awk '/^[0-9a-f]+ WIP: three §5 sentences/{print $1}') - echo "first WIP: $FIRST_WIP" - git log --oneline "$FIRST_WIP"^..HEAD +if [ "$n" != "1" ]; then + echo "AMBIGUOUS OR MISSING ANCHOR — stop and surface"; exit 1 fi +first_wip=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') +echo "first WIP: $first_wip" +echo "its parent: $(git rev-parse "$first_wip"^)" +echo "--- commits that will be squashed ---" +git log --oneline "$first_wip"^..HEAD +exit 0 ``` -Expected: `matching WIP anchors: 1`, then a log listing exactly the WIP commits from Tasks 1–8 and nothing else. If a resumed or repeated task produced two matching subjects, **stop and surface** — a reset against an ambiguous anchor rewrites history at the wrong point. +Expected: `matching WIP anchors: 1`, one hash, its parent, and a log listing exactly the WIP commits from Tasks 1–8. If a resumed or repeated task produced two matching subjects, **stop and surface** — a reset against an ambiguous anchor rewrites history at the wrong point. -Then: +Then squash, **recomputing and re-validating the anchor inside the same block that resets**, because the variable above does not survive into a fresh shell and a reset against an empty target would rewrite from the wrong commit: ```bash -git reset --soft "$FIRST_WIP"^ +cd "$(git rev-parse --show-toplevel)" +n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) +[ "$n" = "1" ] || { echo "ANCHOR NO LONGER UNIQUE — stop"; exit 1; } +first_wip=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') +[ -n "$first_wip" ] || { echo "ANCHOR EMPTY — stop"; exit 1; } +git reset --soft "$first_wip"^ ``` ```bash @@ -1093,7 +1190,26 @@ Expected afterwards: `git log --oneline -2` shows the single WIP snapshot on top - [ ] **Step 3: Run the full battery and record the real numbers** -Run the quality command. Write the actual counts it prints into `$EVIDENCE` under `## battery` — shellcheck files, hook suite assertions under both `sh` and `dash`, invariant assertions, version-bump assertions, `claude plugin validate` result. **Do not write a number you did not read from output.** +Run the quality command and record, under `## battery`, its exit status plus **each suite's terminal line verbatim**. What the battery actually emits, established by running it: + +| Component | What it prints on success | +|---|---| +| `shellcheck` (six invocations) | nothing | +| hook suite, `HOOK_SH=sh` | `all passed` — **no assertion count** | +| hook suite, `HOOK_SH=dash` | `all passed` — **no assertion count** | +| `check-invariants.test.sh` | `all passed (N assertions)` | +| `check-version-bump.test.sh` | `all passed (N assertions)` | +| `check-invariants.sh` | `invariant checks: ok` | +| `claude plugin validate . --strict` | its own success line | + +**Do not write a number you did not read from output.** The hook suite prints no total, so if a count is wanted it must be **derived**, and the evidence template labels it as derived: + +```bash +cd "$(git rev-parse --show-toplevel)" +HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' +``` + +That command was run while writing this plan and returned `467`; it is recorded here because `AGENTS.md`'s "Never document a command that wasn't run" applies to plans as much as to the Commands table. Expect the count to change as the suite grows — record what your run prints, not this number. - [ ] **Step 4: Run the named verification and record it** From 8fb2b66cbb82af9d5947f1027a5e10f505b38705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20S=C3=A4nger?= <20968534+dsnger@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:47:44 +0200 Subject: [PATCH 08/11] docs(plan): cut the verification scaffolding to the load-bearing few MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate-A plan pass 3 returned two Blockers and a rebound to 19 findings, and the pattern was clear: the plan's content had been stable since pass 1, while the 38 bespoke shell blocks around it were a second implementation generating more defects than they caught. Both Blockers and most Majors lived in that surface. Blocker 1: no step created a feature branch, and the checkout was main, seven commits ahead. `git push -u origin HEAD` would have pushed straight to main, bypassing the version-bump gate that invariant 12's checker only applies on pull requests. Task 0 now creates the branch before any edit, and both the push and the PR refuse main explicitly. Blocker 2: the amend and PR fences read $EVIDENCE in fresh shells without defining it — the self-contained rule added in pass 2, broken in the blocks written while adding it. `cat ""` fails while `git commit --amend` still succeeds, so the evidence would have vanished silently. Every block reading it now defines and validates it first. A third defect came from pass 2's own fix: `tr '\n' ' ' | grep -c` flattens the file to one line, so it returns 0 or 1 and cannot count duplicates. Verified against a two-duplicate fixture. The cardinality checks added to detect duplicates could not detect duplicates. Five checks are kept, each self-contained and each exiting nonzero on failure: branch preflight, battery, ledger precondition, self-tests, /dev/null; then - rm -f "$tmp"; echo "INSTALLED $p"; exit 0 -fi -rm -f "$tmp" -if [ -e "$p" ] || [ -L "$p" ]; then - echo "COLLISION: an entry appeared at $p between the check and the install — stop and surface" -else - echo "CREATE FAILED and no entry exists — investigate permissions, a missing parent directory, or a full or read-only filesystem" -fi -exit 1 -``` - -The failure branch **re-stats the path before naming a cause**. Reporting every install failure as a concurrent writer would send the engineer hunting for another session when the real cause is a permission or a missing directory — the `prompt-diagnostic-cause-unnamed` class in this repo's own taxonomy. - -The story text: - -- [ ] **Step 2: Write the story** +- [ ] **Step 1: Write the story** ```markdown # `harden-finding`'s recurrence rule is scope-blind — Story @@ -384,8 +279,8 @@ it did not pass through `dev-workflow:intake` — intake excludes items that hav solution design. A profile is proposed and human-confirmed at intake time. Writing one here would produce a header that **looks** confirmed and is not, and nothing would reveal that: `CLAUDE.md` §5 stops on a profile that is malformed or internally inconsistent, not on one -whose values are well-formed but unconfirmed. That is exactly why the debt is carried as -acceptance criterion 1 rather than by fabricating a header. +whose values are well-formed but unconfirmed. That is why the debt is carried as acceptance +criterion 1 rather than by fabricating a header. ## 1. Problem statement @@ -466,21 +361,11 @@ the six questions above are real design; below an epic because they all concern in one file. ``` -- [ ] **Step 3: Verify shape** +**Must be true:** the file exists as a regular file at that exact path, carries no +`**Risk:**`/`**Security:**`/`**Validation:**` line, has exactly six `##` sections, and its +**first** acceptance criterion is the profile-confirmation one. -```bash -cd "$(git rev-parse --show-toplevel)" -p=docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md -prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" || true) -crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' || true) -sect=$(grep -c '^## ' "$p" || true) -printf 'profile lines=%s (want 0) criterion=%s (want 1) sections=%s (want 6)\n' "$prof" "$crit" "$sect" -if [ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ]; then echo OK; exit 0; else echo "WRONG — stop and surface"; exit 1; fi -``` - -Expected: `OK`. - -- [ ] **Step 4: Commit** +- [ ] **Step 2: Commit** ```bash git add docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md @@ -494,22 +379,19 @@ git commit -m "WIP: split story for the harden-finding guard-scope precheck" ### Task 5: The three trigger stories -**Files:** -- Create: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md` -- Create: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md` -- Create: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md` +**Files:** Create the three paths below. -**Interfaces:** -- Consumes: nothing. -- Produces: three story paths `todos.md` rows point at (Task 6). +Each replaces a parked `todos.md` row carrying settled analysis, so each holds an inheritance +inventory marking every condition of its source row **kept, moved, or deliberately dropped** — +`AGENTS.md`'s "Never replace a decision procedure without accounting for its old conditions" +applies to that replacement. The inventory is a `###` subsection of §1, so the story keeps +exactly six `##` sections. -Each replaces a parked `todos.md` row carrying settled analysis, so each holds an inheritance inventory marking every condition of its source row **kept, moved, or deliberately dropped** — `AGENTS.md`'s "Never replace a decision procedure without accounting for its old conditions" applies to that replacement. The inventory is a `###` subsection of §1 so the story keeps exactly six `##` sections. +**Classify each path immediately before writing it**, by Task 4's rule: symlink or directory → +stop; regular file with identical bytes → reuse; regular file with different bytes → stop; +absent → write. -**Classify each path immediately before its own write**, using the Task 4 Step 1 procedure verbatim with that story's path substituted. A single up-front classification of all three is stale by construction for the second and third. - -- [ ] **Step 1: Classify, then write the ledger-supersession story** - -Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`, then write: +- [ ] **Step 1: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`** ```markdown # The hardening ledger has no supersession convention — Story @@ -565,12 +447,10 @@ row falsified by a later change can be marked as such without breaking either st - `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites silently.** Idempotent: missing → write; identical → report unchanged; present and different → show the diff and ask; additive files (`.gitattributes`, `.mcp.json`, …) → merge." -- **Conditional, if the design reaches the scaffolded template** — `## Key invariants` → - `### Prompts and scaffolding` — "11. **Prompt changes pass `docs/prompt-standards.md`** — all - 12 checklist items, for any skill, command, agent definition, hook message, or scaffolded - template." — and `### Packaging` — "12. **A plugin change requires a version bump.**" Both - bind only if the convention must reach `/workflow-init`'s inline ledger header, which is §5's - open question. +- **Conditional, if the design reaches the scaffolded template** — "11. **Prompt changes pass + `docs/prompt-standards.md`**" and "12. **A plugin change requires a version bump.**" Both bind + only if the convention must reach `/workflow-init`'s inline ledger header, which is §5's open + question. ## 5. Open questions @@ -583,9 +463,7 @@ row falsified by a later change can be marked as such without breaking either st `story` — one file's header convention plus possibly one inline template, one spec → plan → PR. ``` -- [ ] **Step 2: Classify, then write the no-PR ledger route story** - -Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, then write: +- [ ] **Step 2: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`** ```markdown # A route from a fixed finding to the ledger, for projects that never open PRs — Story @@ -616,7 +494,7 @@ never open PRs.**": | It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it | | Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | | Trigger, first alternative: the next round that touches §5 | **moved** — fired by the 2026-08-03 round, recorded here | -| Trigger, second alternative: a project reporting an empty ledger across cycles that fixed findings | **kept** — it did not fire, and it remains the condition that would raise this independently if the first had not | +| Trigger, second alternative: a project reporting an empty ledger across cycles that fixed findings | **kept** — it did not fire, and it remains the condition that would raise this independently | ## 2. Desired outcome @@ -650,9 +528,7 @@ or a handoff. `story` — one route, one spec → plan → PR. ``` -- [ ] **Step 3: Classify, then write the §5 version stamp story** - -Run Task 4 Steps 1 and 2's blocks (classify, then atomic install) with `p=docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, then write: +- [ ] **Step 3: `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`** ```markdown # A §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the plugin — Story @@ -720,28 +596,11 @@ without comparing the two by hand. shipped provisionally cannot be taken back. ``` -- [ ] **Step 4: Verify all three** +**Must be true, for all three:** each exists as a regular file at its exact path, carries no +profile line, has exactly six `##` sections, has one `### Conditions inherited from the source +row` subsection, and has the profile-confirmation criterion first. -```bash -cd "$(git rev-parse --show-toplevel)" -rc=0 -for p in docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md \ - docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md \ - docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md ; do - prof=$(grep -c '^\*\*Risk:\*\*\|^\*\*Security:\*\*\|^\*\*Validation:\*\*' "$p" || true) - crit=$(tr '\n' ' ' < "$p" | tr -s ' ' | grep -c 'proposes both axes and the mode derived from them' || true) - sect=$(grep -c '^## ' "$p" || true) - inv=$(grep -c '^### Conditions inherited from the source row' "$p" || true) - printf '%-58s profile=%s crit=%s sections=%s inventory=%s\n' "$(basename "$p")" "$prof" "$crit" "$sect" "$inv" - [ "$prof" = 0 ] && [ "$crit" = 1 ] && [ "$sect" = 6 ] && [ "$inv" = 1 ] || rc=1 -done -echo "stories exit=$rc" -exit "$rc" -``` - -Expected: three rows reading `profile=0 crit=1 sections=6 inventory=1`, and `stories exit=0`. - -- [ ] **Step 5: Commit** +- [ ] **Step 4: Commit** ```bash git add docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md @@ -755,40 +614,16 @@ git commit -m "WIP: three split stories for the fired triggers" ### Task 6: `todos.md` — one new row, six existing rows -**Files:** -- Modify: `todos.md` - -**Interfaces:** -- Consumes: the four story paths from Tasks 4–5. -- Produces: the parked C4/C5 row ledger row B cross-references (Task 7). - -**Every mutation is idempotent, and idempotence is decided on the whole block, not on a marker.** Before each mutation, run the guard below with that step's marker and its full intended text. A marker count alone cannot tell an identical completed mutation from a partial or independently edited one, and skipping on a marker hit would leave `todos.md` semantically wrong while the final count check still passed. +**Files:** Modify `todos.md`. -The guard is written out in each step rather than defined once, because **agentic workers run each fenced block in a fresh shell** — a helper defined in one block is not in scope in the next. - -```bash -# Guard template. MARKER is a regex identifying the block; INTENDED is a file -# holding the exact text this step would add. -cd "$(git rev-parse --show-toplevel)" -n=$(tr '\n' ' ' < todos.md | tr -s ' ' | grep -c 'MARKER' || true) -if [ "$n" = "0" ]; then - echo "ABSENT — apply this step"; exit 0 -elif [ "$n" != "1" ]; then - echo "MARKER APPEARS $n TIMES — stop and surface"; exit 1 -fi -# present exactly once: compare the whole intended block, whitespace-normalized -have=$(tr '\n' ' ' < todos.md | tr -s ' ') -want=$(tr '\n' ' ' < INTENDED | tr -s ' ') -if printf '%s' "$have" | grep -qF "$want"; then - echo "ALREADY APPLIED, identical — skip this step"; exit 0 -else - echo "PRESENT BUT DIFFERENT — stop and surface"; exit 1 -fi -``` +**Each mutation is idempotent, decided on the whole block.** Before applying one, check whether +its full text is already present: absent → apply; present and identical → skip and say so; +present but different, or present more than once → **stop and surface**. A marker substring +alone cannot tell an identical completed edit from a partial or independently edited one. - [ ] **Step 1: Add the parked C4/C5 compliance row** -Guard: `applied 'The gate-claims Don.t is correct and was not followed, twice'` must be `0`. Add under `### Parked (trigger-gated)`: +Under `### Parked (trigger-gated)`: ```markdown - [ ] **The gate-claims Don't is correct and was not followed, twice.** PR #21's C4 (an @@ -807,7 +642,7 @@ Guard: `applied 'The gate-claims Don.t is correct and was not followed, twice'` - [ ] **Step 2: Record evidence case 3 on the scope-blind row** -Guard: `applied 'Evidence case 3 \(2026-08-04\)'` must be `0`. In `**`harden-finding`'s recurrence rule is scope-blind.**`, insert before its `*Trigger:*` sentence: +In `**`harden-finding`'s recurrence rule is scope-blind.**`, before its `*Trigger:*` sentence: ``` **Evidence case 3 (2026-08-04):** the 2026-08-03 hardening round ran the precheck as a @@ -820,8 +655,6 @@ Guard: `applied 'Evidence case 3 \(2026-08-04\)'` must be `0`. In `**`harden-fin - [ ] **Step 3: Mark Finding A's row fired** -Guard: `applied 'TRIGGER FIRED \(2026-08-04\): the 2026-08-03 hardening round edits §5\.'` must be `0`. In `**Finding A — a route from a fixed finding to the ledger for projects that never open PRs.**`, append: - ``` **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5. Story: `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, which @@ -830,8 +663,6 @@ Guard: `applied 'TRIGGER FIRED \(2026-08-04\): the 2026-08-03 hardening round ed - [ ] **Step 4: Mark Finding B's row fired** -Guard: `applied 'edits the §5 inline template'` must be `0`. In `**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the installed plugin.**`, append: - ``` **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits the §5 inline template. Story: @@ -841,8 +672,6 @@ Guard: `applied 'edits the §5 inline template'` must be `0`. In `**Finding B - [ ] **Step 5: Mark the ledger-supersession row fired** -Guard: `applied 'the second falsified row this trigger names'` must be `0`. In `**The hardening ledger has no supersession convention.**`, append: - ``` **TRIGGER FIRED (2026-08-04):** the 2026-07-20 row now teaches pre-0.8.0 counting behaviour as current — the second falsified row this trigger names. Story: @@ -851,7 +680,7 @@ Guard: `applied 'the second falsified row this trigger names'` must be `0`. In ` - [ ] **Step 6: Record that the slot-collision row did NOT fire** -Guard: `applied 'NOT FIRED \(2026-08-04\)'` must be `0`. In `**Each Gate cycle destroys the previous cycle's review record.**`, append: +In `**Each Gate cycle destroys the previous cycle's review record.**`: ``` **NOT FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5 prose and its template @@ -862,7 +691,7 @@ Guard: `applied 'NOT FIRED \(2026-08-04\)'` must be `0`. In `**Each Gate cycle d - [ ] **Step 7: Append the env-var observation, leaving the row open** -Guard: `applied 'OBSERVATION \(2026-08-04\)'` must be `0`. In `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**`, append: +In `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**`: ``` **OBSERVATION (2026-08-04), and the row stays open.** With the variable set to `0`, five @@ -875,49 +704,14 @@ Guard: `applied 'OBSERVATION \(2026-08-04\)'` must be `0`. In `**`/workflow-init unbuilt, so the row is not discharged by this. ``` -- [ ] **Step 8: Verify all seven changes landed exactly once, and every cited story exists** +**Must be true:** all seven changes are present, each exactly once; every +`docs/superpowers/stories/2026-08-04-*.md` path cited in `todos.md` exists as a regular file; +and there are exactly four such distinct paths. A `todos.md` pointing at a story that does not +exist is the docs-drift class this round hardens. -```bash -cd "$(git rev-parse --show-toplevel)" -n=$(tr '\n' ' ' < todos.md | tr -s ' ') -rc=0 -for spec in \ - 'parked C4/C5 row=The gate-claims Don.t is correct and was not followed, twice' \ - 'scope-blind evidence case 3=Evidence case 3 \(2026-08-04\)' \ - 'Finding A fired=hardening round edits §5\. Story' \ - 'Finding B fired=edits the §5 inline template' \ - 'ledger-supersession fired=the second falsified row this trigger names' \ - 'slot-collision not fired=NOT FIRED \(2026-08-04\)' \ - 'env-var observation=OBSERVATION \(2026-08-04\)' ; do - label=${spec%%=*}; pat=${spec#*=} - c=$(printf '%s' "$n" | grep -c "$pat" || true) - printf '%-30s %s (want 1)\n' "$label" "$c" - [ "$c" = "1" ] || rc=1 -done - -echo "--- every cited story path exists, and there are exactly four ---" -grep -o 'docs/superpowers/stories/2026-08-04-[a-z0-9-]*\.md' todos.md | sort -u > /tmp/cited-stories.txt -cited=$(grep -c . /tmp/cited-stories.txt || true) -printf 'distinct cited story paths=%s (want 4)\n' "$cited" -[ "$cited" = "4" ] || rc=1 -while IFS= read -r sp; do - if [ -f "$sp" ] && [ ! -L "$sp" ]; then echo "OK $sp"; else echo "MISS or NOT A REGULAR FILE: $sp"; rc=1; fi -done < /tmp/cited-stories.txt -rm -f /tmp/cited-stories.txt - -echo "todos exit=$rc" -exit "$rc" -``` +- [ ] **Step 8: Run the battery.** Expected: exit 0. -Expected: seven `1 (want 1)` lines, `distinct cited story paths=4`, four `OK` lines, `todos exit=0`, and **shell status 0**. - -The loop reads from a redirected file rather than a pipeline, because `... | while read` runs the loop body in a subshell and discards every `rc=1` set inside it — the check would print `MISS` and still report `todos exit=0`. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens, so the check has to be able to fail. - -- [ ] **Step 9: Run the battery** - -Expected: exit 0. - -- [ ] **Step 10: Commit** +- [ ] **Step 9: Commit** ```bash git add todos.md @@ -931,41 +725,38 @@ git commit -m "WIP: park the compliance recurrence and record the fired triggers ### Task 7: The four ledger rows -**Files:** -- Modify: `docs/hardening-log.md` (append four rows) +**Files:** Modify `docs/hardening-log.md`. -**Interfaces:** -- Consumes: the three §5 sentences (Task 1), the `AGENTS.md` amendment (Task 2), the taxonomy class (Task 3), the parked row (Task 6). -- Produces: nothing downstream. +Append in order A, B, C, D at the end of the table. **Each row is one line** — the blocks below +are single lines this document wraps for display. -Append in order A, B, C, D at the end of the table. Each row is **one line** — the blocks below are single lines that this document wraps for display; write each as one line with no internal newline. - -- [ ] **Step 1: Re-read the log and require exact fingerprint counts** +- [ ] **Step 1: Ledger precondition (KEPT CHECK)** ```bash -cd "$(git rev-parse --show-toplevel)" +cd "$(git rev-parse --show-toplevel)" || exit 1 +log=docs/hardening-log.md +[ -f "$log" ] || { echo "missing $log — stop"; exit 1; } rc=0 -expect() { - fp=$1; want=$2 - got=$(grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md || true) +set -- "docs-drift 5" "unverified-enforcement-claim 5" "verification-masks-failure 1" "mechanical-check-skipped-before-review 0" +for pair in "$@"; do + fp=${pair% *}; want=${pair#* } + got=$(grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" "$log" || true) printf '%-42s got=%s want=%s\n' "$fp" "$got" "$want" if [ "$got" != "$want" ]; then rc=1 - echo " --- rows found, for the stop report ---" - grep -nE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md | cut -c1-160 + grep -nE "^\| *[0-9-]{10} *\| *$fp *\|" "$log" | cut -c1-140 fi -} -expect docs-drift 5 -expect unverified-enforcement-claim 5 -expect verification-masks-failure 1 -expect mechanical-check-skipped-before-review 0 +done echo "ledger precondition exit=$rc" exit "$rc" ``` -Expected: `ledger precondition exit=0`. +Expected: four `got=N want=N` lines and `exit 0`. -**Any inequality — higher or lower — stops the task.** Higher means a row appeared since this plan was written; lower means a row this plan's premise depends on was removed or rewritten. Either invalidates the reviewed precondition. **Stop and surface, naming the rows the command printed.** Do not append. Resolving a mid-run collision is the split story's subject (Task 4), and inventing the procedure here would re-import what was split out. +**Any inequality stops the task, in either direction.** Higher means a row appeared since this +plan was written; lower means a row the premise depends on was removed or rewritten. **Stop and +surface, naming the rows the command printed.** Resolving a mid-run collision is the split +story's subject (Task 4), and inventing the procedure here would re-import what was split out. - [ ] **Step 2: Append row A** @@ -991,35 +782,20 @@ Expected: `ledger precondition exit=0`. | 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | ``` -- [ ] **Step 6: Verify the appended rows** +**Must be true:** each appended row is a single line with exactly seven fields and no unescaped +`|`; and after appending, the fingerprint counts are `docs-drift 6`, +`unverified-enforcement-claim 6`, `verification-masks-failure 2`, +`mechanical-check-skipped-before-review 1`. -```bash -cd "$(git rev-parse --show-toplevel)" -rc=0 -echo "--- the four new rows: 7 fields, 8 pipes, one line each ---" -tail -4 docs/hardening-log.md | awk -F'|' '{ - pipes = gsub(/\|/,"|") - printf "row %d: fields=%d pipes=%d\n", NR, NF-2, pipes - if (NF-2 != 7 || pipes != 8) exit_code=1 -} END { exit exit_code+0 }' || rc=1 -echo "--- fingerprint counts after append ---" -for pair in "docs-drift 6" "unverified-enforcement-claim 6" "verification-masks-failure 2" "mechanical-check-skipped-before-review 1"; do - fp=${pair% *}; want=${pair#* } - got=$(grep -cE "^\| *[0-9-]{10} *\| *$fp *\|" docs/hardening-log.md || true) - printf '%-42s got=%s want=%s\n' "$fp" "$got" "$want" - [ "$got" = "$want" ] || rc=1 -done -echo "ledger exit=$rc" -exit "$rc" -``` - -Expected: four rows each `fields=7 pipes=8`; counts `6 / 6 / 2 / 1`; `ledger exit=0`. - -- [ ] **Step 7: Run the battery** +Note the concurrency limit the spec states and this plan does not exceed: **a row landing after +Step 1's read is not detected.** Step 1 is the only observation point, and supplying another is +the split story's subject. -Expected: exit 0. `check-invariants.sh` excludes `docs/hardening-log.md` from checks 4a/4b — a ledger that quotes defects would otherwise self-reject. +- [ ] **Step 6: Run the battery.** Expected: exit 0. `check-invariants.sh` excludes +`docs/hardening-log.md` from checks 4a/4b — a ledger that quotes defects would otherwise +self-reject. -- [ ] **Step 8: Commit** +- [ ] **Step 7: Commit** ```bash git add docs/hardening-log.md @@ -1033,14 +809,11 @@ git commit -m "WIP: four ledger rows for the 0.8.0 and PR #21 hardenings" ### Task 8: The CHANGELOG entry -**Files:** -- Modify: `plugins/dev-workflow/CHANGELOG.md` (new `## 0.8.1` section directly above `## 0.8.0`) - -The manifest bump already landed in Task 1. This task adds the entry describing what shipped, which is only writable once the content exists. +**Files:** Modify `plugins/dev-workflow/CHANGELOG.md`. The manifest bump landed in Task 1. - [ ] **Step 1: Add the entry** -Insert immediately above `## 0.8.0`: +Immediately above `## 0.8.0`: ```markdown ## 0.8.1 @@ -1061,23 +834,12 @@ Insert immediately above `## 0.8.0`: collisions that a rule paragraph cannot carry. It is split to its own story. ``` -- [ ] **Step 2: Verify** - -```bash -cd "$(git rev-parse --show-toplevel)" -h=$(grep -c '^## 0\.8\.1' plugins/dev-workflow/CHANGELOG.md || true) -v=$(grep -o '"version": *"0\.8\.1"' plugins/dev-workflow/.claude-plugin/plugin.json | head -1) -printf 'changelog headings=%s (want 1) manifest=%s\n' "$h" "$v" -if [ "$h" = 1 ] && [ -n "$v" ]; then echo OK; exit 0; else echo "WRONG — stop and surface"; exit 1; fi -``` - -Expected: `OK`. +**Must be true:** exactly one `## 0.8.1` heading, directly above `## 0.8.0`, and the manifest +still reads `0.8.1`. -- [ ] **Step 3: Run the battery** +- [ ] **Step 2: Run the battery.** Expected: exit 0. -Expected: exit 0. - -- [ ] **Step 4: Commit** +- [ ] **Step 3: Commit** ```bash git add plugins/dev-workflow/CHANGELOG.md @@ -1091,194 +853,203 @@ git commit -m "WIP: 0.8.1 changelog entry" ### Task 9: Validation evidence and the Gate-B cycle -**Files:** -- Create: `.context/evidence-0.8.1.md` — the durable evidence record. `.context/` is git-ignored, so it never enters the diff; its content is folded into the closing commit body at Step 8. +**Files:** Create `.context/evidence-0.8.1.md` — git-ignored, so it never enters the diff; its +content is folded into the closing commit body. -**Interfaces:** -- Consumes: every prior task. -- Produces: the final commit. +- [ ] **Step 1: Create the evidence file** -- [ ] **Step 1: Create the evidence file with its full structure** +If `.context/evidence-0.8.1.md` already exists from a resumed run, **do not overwrite it** — +read it, keep what is filled, and continue. Otherwise create it with this structure: -```bash -cd "$(git rev-parse --show-toplevel)" -EVIDENCE="$(git rev-parse --show-toplevel)/.context/evidence-0.8.1.md" -mkdir -p "$(dirname "$EVIDENCE")" -cat > "$EVIDENCE" <<'EOF' +```markdown # Validation evidence — hardening round 0.8.1 Story: docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md +Branch: harden-0-8-0-and-pr-21 ## battery -command: (the quality command from AGENTS.md § Commands) exit status: -terminal line, hook suite under sh: -terminal line, hook suite under dash: -terminal line, invariants suite: -terminal line, version-bump suite: -claude plugin validate --strict: -derived hook-suite assertion counts (the suite prints none): - HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' -> - HOOK_SH=dash dash plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' -> +hook suite under sh: +hook suite under dash: +invariants suite: +version-bump suite: +check-invariants.sh: +claude plugin validate --strict: +derived hook-suite assertion count (the suite prints none) — capture the suite's own +exit status separately, and record a count only if that status is 0: + HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh > out 2>&1; echo $? + grep -c '^ok ' out + -> status , count ## check (named verification), with its counterfactual §5.2 applied to the four cases in spec §10 — each must FAIL on the wiring half: - 1. $EVIDENCE dry run -> - 2. single-shell regression -> - 3. timed regression row -> - 4. dash release evidence -> -§5.4 applied to "It bounds the scan, not memory" — must FAIL on exhaustiveness: - + 1. $EVIDENCE dry run -> + 2. single-shell regression -> + 3. timed regression row -> + 4. dash release evidence -> +§5.4 applied to "It bounds the scan, not memory" — must FAIL on exhaustiveness: counterfactual: all four were reviewed during the 0.8.0 cycle under the §5 text as it read before this change and each was accepted by at least one review looking for exactly this; one (the single-shell test) reached the released artifact. ## prompt conformance -workflow-init.md (invariant 11 surface) — all 12 items: +workflow-init.md (invariant 11 surface) — all 12 items: CLAUDE.md, AGENTS.md (not on invariant 11's list) — items 6,7,8,9,11,12: four split stories — in-spirit brief review: ## mirror parity -Task 1 Step 6 re-run result: +each §5 sentence appears once in CLAUDE.md and once in workflow-init.md: ## cross-finding conflict check -Task 0 verdict: -EOF -echo "created: $EVIDENCE" -if [ -s "$EVIDENCE" ]; then echo "non-empty OK"; exit 0; else echo "EMPTY — stop and surface"; exit 1; fi +Task 0 Step 2 verdict: ``` -`$EVIDENCE` is defined here, in this step, before any later step reads it. A dry run that assigns the variable itself would prove the shell mechanics and never that this plan defines it — the `verification-masks-failure` case row C records. +- [ ] **Step 2: Squash the WIP commits** -- [ ] **Step 2: Squash the WIP commits into one snapshot** +**Must be true before resetting:** the working tree and index are clean, the commits from +`HEAD` back to the first WIP are exactly the eight this plan created and nothing else, and the +first-WIP subject matches exactly once. If any of those fails, **stop and surface** — a reset +against an ambiguous or contaminated range rewrites the wrong history. -Inspect first. This block is read-only and prints the hash the next block needs: +Then, in one block that recomputes its own anchor: ```bash -cd "$(git rev-parse --show-toplevel)" +cd "$(git rev-parse --show-toplevel)" || exit 1 +[ -z "$(git status --porcelain)" ] || { echo "tree or index not clean — stop"; exit 1; } n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) -echo "matching WIP anchors: $n (want exactly 1)" -if [ "$n" != "1" ]; then - echo "AMBIGUOUS OR MISSING ANCHOR — stop and surface"; exit 1 -fi -first_wip=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') -echo "first WIP: $first_wip" -echo "its parent: $(git rev-parse "$first_wip"^)" -echo "--- commits that will be squashed ---" -git log --oneline "$first_wip"^..HEAD +[ "$n" = "1" ] || { echo "anchor not unique (n=$n) — stop"; exit 1; } +first=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') +[ -n "$first" ] || { echo "anchor empty — stop"; exit 1; } +parent=$(git rev-parse "$first"^) || { echo "cannot resolve parent — stop"; exit 1; } +echo "will squash these onto $parent:" +git log --oneline "$parent"..HEAD || exit 1 +git reset --soft "$parent" || exit 1 +echo "reset OK" exit 0 ``` -Expected: `matching WIP anchors: 1`, one hash, its parent, and a log listing exactly the WIP commits from Tasks 1–8. If a resumed or repeated task produced two matching subjects, **stop and surface** — a reset against an ambiguous anchor rewrites history at the wrong point. - -Then squash, **recomputing and re-validating the anchor inside the same block that resets**, because the variable above does not survive into a fresh shell and a reset against an empty target would rewrite from the wrong commit: - -```bash -cd "$(git rev-parse --show-toplevel)" -n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) -[ "$n" = "1" ] || { echo "ANCHOR NO LONGER UNIQUE — stop"; exit 1; } -first_wip=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') -[ -n "$first_wip" ] || { echo "ANCHOR EMPTY — stop"; exit 1; } -git reset --soft "$first_wip"^ -``` - ```bash git commit -m "WIP: hardening round — the 0.8.0 cycle and PR #21" ``` -Expected afterwards: `git log --oneline -2` shows the single WIP snapshot on top of the pre-round HEAD. - -- [ ] **Step 3: Run the full battery and record the real numbers** - -Run the quality command and record, under `## battery`, its exit status plus **each suite's terminal line verbatim**. What the battery actually emits, established by running it: - -| Component | What it prints on success | -|---|---| -| `shellcheck` (six invocations) | nothing | -| hook suite, `HOOK_SH=sh` | `all passed` — **no assertion count** | -| hook suite, `HOOK_SH=dash` | `all passed` — **no assertion count** | -| `check-invariants.test.sh` | `all passed (N assertions)` | -| `check-version-bump.test.sh` | `all passed (N assertions)` | -| `check-invariants.sh` | `invariant checks: ok` | -| `claude plugin validate . --strict` | its own success line | - -**Do not write a number you did not read from output.** The hook suite prints no total, so if a count is wanted it must be **derived**, and the evidence template labels it as derived: - -```bash -cd "$(git rev-parse --show-toplevel)" -HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh 2>&1 | grep -c '^ok ' -``` - -That command was run while writing this plan and returned `467`; it is recorded here because `AGENTS.md`'s "Never document a command that wasn't run" applies to plans as much as to the Commands table. Expect the count to change as the suite grows — record what your run prints, not this number. +- [ ] **Step 3: Run the battery (KEPT CHECK) and record it** -- [ ] **Step 4: Run the named verification and record it** +Run the quality command. Record its exit status and each suite's terminal line verbatim in the +evidence file. **Do not write a number you did not read from output.** The hook suite prints no +total, so a count must be derived — and the derivation must capture the suite's own exit status +separately, because a pipeline into `grep -c` returns grep's status and would record a partial +count from a suite that died. -Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10. Each must **fail** on the second half. Then apply §5.4's appended clause to `It bounds the **scan**, not memory` — it must fail on exhaustiveness. Write all five verdicts into `$EVIDENCE`. +- [ ] **Step 4: Run the self-tests (KEPT CHECK) and record them** -If any case passes the sentence written to reject it, that sentence is miswired: **stop and surface**, do not proceed to Gate B. +Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10 — +each must **fail** on the second half. Apply §5.4's appended clause to `It bounds the **scan**, +not memory` — it must fail on exhaustiveness. Record all five verdicts. -- [ ] **Step 5: Run the prompt-conformance reviews and record them** +If any case passes the sentence written to reject it, that sentence is miswired: **stop and +surface**, do not proceed to Gate B. -`plugins/dev-workflow/commands/workflow-init.md` is the only changed file inside invariant 11's enumerated surface. Review it against all 12 items of `docs/prompt-standards.md`; all 12 must pass, with an exception only where the checklist item itself authorizes one (items 9 and 12 do), recorded with that item's stated reason. +- [ ] **Step 5: Record prompt conformance and mirror parity** -`CLAUDE.md` and `AGENTS.md` are not on that list — neither carries a `Target model:` line, because neither is executed against a named model. Review their edits against items 6, 7, 8, 9, 11 and 12. §5.4's clause continues an existing prohibition; item 9's own text exempts rules whose subject is the prohibition, so cite that exemption rather than assuming it. +`workflow-init.md` is the only changed file inside invariant 11's enumerated surface — all 12 +items must pass, with an exception only where the checklist item itself authorizes one (items 9 +and 12 do). `CLAUDE.md` and `AGENTS.md` are not on that list and carry no `Target model:` line; +review their edits against items 6, 7, 8, 9, 11 and 12. §5.4's clause continues an existing +prohibition, so cite item 9's own exemption rather than assuming it. Give each split story an +in-spirit brief review. Record all of it, plus the mirror-parity result. -Give each of the four split stories a recorded in-spirit brief review — success criteria, stop conditions, verified claims — without asserting the mandatory 12 apply. +- [ ] **Step 6: Run Gate B** -Write all results into `$EVIDENCE`. +Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the +WIP commit. Delete both branch target files first and confirm they are gone. Carry the story +path and the evidence content verbatim in `additionalContext`, plus the standing lens. -- [ ] **Step 6: Re-run mirror parity and record it** +Minimum three passes, final pass clean. Re-review after every fix — a fix changes the diff. +Validate every pass before reading it: terminator exact, count matching, nothing but finding +lines, both branch files present. -Re-run Task 1 Step 6's block and write the result into `$EVIDENCE`. Parity is checked here, again after every Gate-B fix, and once more immediately before Step 8 — a review fix can touch only `CLAUDE.md` or only the template and otherwise reach the final commit unnoticed. +**After every fix: stage the fixed paths, re-run the battery, re-check mirror parity, update the +evidence file.** A fix left unstaged is a fix the closing amend does not carry. -- [ ] **Step 7: Run Gate B** +**Must be true before the amend:** the working tree is clean, so nothing reviewed remains +unstaged. -Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the WIP commit. Delete both branch target files first and confirm they are gone. Carry the story path and `$EVIDENCE`'s content verbatim in `additionalContext`, plus the standing lens. - -Minimum three passes, final pass clean. Re-review after every fix — a fix changes the diff. Every pass writes to `.context/codex-reviews/gate-b--pass-

.md` and is validated before it is read: terminator exact, count matching, nothing but finding lines, both branch files present. - -After each fix: re-run the battery, re-run mirror parity, and update `$EVIDENCE` before the next call. - -- [ ] **Step 8: Close the cycle by amend** +- [ ] **Step 7: Close the cycle by amend (KEPT CHECK — the ` Date: Tue, 4 Aug 2026 15:17:10 +0200 Subject: [PATCH 09/11] =?UTF-8?q?docs(plan):=20cite=20CLAUDE.md=20=C2=A75?= =?UTF-8?q?=20instead=20of=20restating=20its=20protocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate-A plan pass 4: one Blocker, ten Majors, eight of eleven inside the git/Gate-B/PR procedure while the hardening content stayed stable for a fourth pass. The Blocker: Task 9 told the executor to stage each Gate-B fix and never to amend it into the WIP commit. mcp__codex__review reads a git range, so a staged-only fix is not in it — every post-fix pass would have re-reviewed the pre-fix commit and reported clean on unreviewed content, while the staged index made the pre-amend clean-tree precondition unsatisfiable. That is the parked "Gate-B fingerprints disk; the reviewer reads history" row, inside the fix loop. The cause was structural: §5 already specifies that protocol, and Task 9 restated it. Every restatement is a copy that can drift, which prompt-standards item 11 says in as many words, and this one drifted by dropping the amend. Task 9 now keeps only the round-specifics — evidence template, the four self-test cases, prompt-conformance scope — and cites §5 for the squash, Gate-B loop, amend and close. One clause names the dropped step, because naming a step is not re-specifying a procedure. Also corrected: the plan claimed five executable checks each exiting nonzero on failure, when the self-tests are prose readings with no oracle. It now says four executable checks plus one recorded reading, and says why — applying a prompt sentence to a case is a judgement, and a script asserting it would assert its author's opinion. An enforcement claim with no mechanism, in the round that hardens that class. The rest: the branch preflight now requires a clean tree; atomic no-clobber story creation is restored per the spec; identical-file reuse skips its commit instead of failing on an empty one; the hook-count derivation uses mktemp and captures the suite's status before counting; push and PR require the exact branch name rather than merely not-main; the PR body is read with an explicit stop so a cat failure cannot open an empty PR; and resumed evidence must be a regular file naming this story and branch. 22 blocks now, from 38 two passes ago. All parse under sh and dash. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 212 ++++++++++-------- 1 file changed, 124 insertions(+), 88 deletions(-) diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md index 4c862a4..d5554ed 100644 --- a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -20,11 +20,22 @@ reviews the diff that actually resulted — a bespoke verification script per st implementation that needs its own review, and in this plan's own history that scaffolding produced more defects than it caught. -Five checks are kept as executable blocks, because each is load-bearing and each is simple -enough to read at a glance: the **branch preflight**, the **battery**, the **ledger -precondition**, the **self-tests**, and the **` Task 0 Step 2 verdict: ``` -- [ ] **Step 2: Squash the WIP commits** - -**Must be true before resetting:** the working tree and index are clean, the commits from -`HEAD` back to the first WIP are exactly the eight this plan created and nothing else, and the -first-WIP subject matches exactly once. If any of those fails, **stop and surface** — a reset -against an ambiguous or contaminated range rewrites the wrong history. +- [ ] **Step 2: Run the battery (KEPT CHECK) and record it** -Then, in one block that recomputes its own anchor: +Run the quality command from Global Constraints. Record its exit status and each suite's +terminal line verbatim in the evidence file. **Do not write a number you did not read from +output.** The hook suite prints no total, so a count must be derived — and the derivation must +capture the suite's own exit status separately, because a pipeline into `grep -c` returns +grep's status and would record a partial count from a suite that died: ```bash cd "$(git rev-parse --show-toplevel)" || exit 1 -[ -z "$(git status --porcelain)" ] || { echo "tree or index not clean — stop"; exit 1; } -n=$(git log --format='%s' | grep -c '^WIP: three §5 sentences' || true) -[ "$n" = "1" ] || { echo "anchor not unique (n=$n) — stop"; exit 1; } -first=$(git log --format='%H %s' | awk '/ WIP: three §5 sentences/{print $1}') -[ -n "$first" ] || { echo "anchor empty — stop"; exit 1; } -parent=$(git rev-parse "$first"^) || { echo "cannot resolve parent — stop"; exit 1; } -echo "will squash these onto $parent:" -git log --oneline "$parent"..HEAD || exit 1 -git reset --soft "$parent" || exit 1 -echo "reset OK" +out=$(mktemp) || exit 1 +HOOK_SH=sh sh plugins/dev-workflow/hooks/codex-gate.test.sh > "$out" 2>&1 +status=$? +count=$(grep -c '^ok ' "$out" || true) +rm -f "$out" +echo "hook suite (sh): status=$status derived-ok-count=$count" +[ "$status" = "0" ] || { echo "suite failed — the count is not evidence"; exit 1; } exit 0 ``` -```bash -git commit -m "WIP: hardening round — the 0.8.0 cycle and PR #21" -``` - -- [ ] **Step 3: Run the battery (KEPT CHECK) and record it** - -Run the quality command. Record its exit status and each suite's terminal line verbatim in the -evidence file. **Do not write a number you did not read from output.** The hook suite prints no -total, so a count must be derived — and the derivation must capture the suite's own exit status -separately, because a pipeline into `grep -c` returns grep's status and would record a partial -count from a suite that died. +`mktemp` rather than a fixed path: a literal `out` in the repo root would collide with a user's +file and leave an untracked artifact that the clean-tree precondition then trips over. -- [ ] **Step 4: Run the self-tests (KEPT CHECK) and record them** +- [ ] **Step 3: Run the self-tests and record them (RECORDED READING, not an executable check)** Apply §5.2's sentence, exactly as it now reads in `CLAUDE.md`, to the four cases in spec §10 — each must **fail** on the second half. Apply §5.4's appended clause to `It bounds the **scan**, -not memory` — it must fail on exhaustiveness. Record all five verdicts. +not memory` — it must fail on exhaustiveness. Record all five verdicts with one line of reason +each. + +There is no shell block here on purpose. Deciding whether a prompt sentence rejects a case is a +reading; a script asserting it would be asserting its own author's opinion. If any case passes the sentence written to reject it, that sentence is miswired: **stop and surface**, do not proceed to Gate B. -- [ ] **Step 5: Record prompt conformance and mirror parity** +- [ ] **Step 4: Record prompt conformance and mirror parity** `workflow-init.md` is the only changed file inside invariant 11's enumerated surface — all 12 items must pass, with an exception only where the checklist item itself authorizes one (items 9 @@ -958,28 +984,34 @@ review their edits against items 6, 7, 8, 9, 11 and 12. §5.4's clause continues prohibition, so cite item 9's own exemption rather than assuming it. Give each split story an in-spirit brief review. Record all of it, plus the mirror-parity result. -- [ ] **Step 6: Run Gate B** +- [ ] **Step 5: Close the cycle — `CLAUDE.md` §5 governs** + +**The squash, the Gate-B loop, the amend and the close follow `CLAUDE.md` §5 and its Mechanics +section. This plan does not restate that protocol.** A draft did, and the restatement dropped a +step — which is why §5 is cited rather than paraphrased. -Per CLAUDE.md §5. Tool: `mcp__codex__review`, `reviewType: full`, `baseSha` = the parent of the -WIP commit. Delete both branch target files first and confirm they are gone. Carry the story -path and the evidence content verbatim in `additionalContext`, plus the standing lens. +Two things this plan adds, neither of them a re-specification: -Minimum three passes, final pass clean. Re-review after every fix — a fix changes the diff. -Validate every pass before reading it: terminator exact, count matching, nothing but finding -lines, both branch files present. +1. **The step the restatement dropped, named so it is not dropped again:** §5 requires that + **every Gate-B fix is amended into the WIP commit before the next review call.** + `mcp__codex__review` reads a **git range**; a fix that is only staged is not in that range, + so the next pass would re-review the pre-fix commit and report clean on unreviewed content. +2. **What Gate B carries from this round:** `reviewType: full`; the story path + `docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md`; the evidence + file's content verbatim in `additionalContext`; and the standing lens. -**After every fix: stage the fixed paths, re-run the battery, re-check mirror parity, update the -evidence file.** A fix left unstaged is a fix the closing amend does not carry. +**Must be true at the close:** the working tree is clean, every reviewed fix is in the commit +being amended, and the closing message carries the evidence body. -**Must be true before the amend:** the working tree is clean, so nothing reviewed remains -unstaged. +- [ ] **Step 6: The ` Date: Tue, 4 Aug 2026 15:33:19 +0200 Subject: [PATCH 10/11] docs(plan): apply Gate-A plan pass 5 and close the plan gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine findings, five Major, no Blockers, none requiring a decision. The sharpest: the plan appended evidence case 3 beneath the scope-blind row's own premise — that the workaround "lives in ledger prose, which agents do not read" — which this round disproved, since harden-finding step 3 does re-read the log. Task 6 now corrects the premise in the same change before appending. Leaving it would have been docs-drift created by the round that hardens it. The rest, in order of consequence: - The branch preflight's resumed path printed an ahead-count while claiming the executor could confirm the range holds only this plan's commits. It now prints the actual main..feature commit list with an explicit stop. - Evidence creation had the classify-then-create race the story paths were already fixed for. Same temp-file-plus-ln treatment; this file is quoted into the commit body and the PR, so foreign content there is worse, not better. - The before gh pr create. Gate A closes for the plan at five passes, all valid under the file-first protocol, every finding dispositioned under .context/codex-reviews/. Both settled decisions the pass was asked to judge survived: no dropped check whose violation would reach the commit undetected, and nothing round-specific lost in citing §5 rather than restating it. Docs-only (docs/**.md), so Gate B is N/A per CLAUDE.md §5's prose exemption. --- ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 99 +++++++++++++++---- 1 file changed, 81 insertions(+), 18 deletions(-) diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md index d5554ed..ab199cc 100644 --- a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -92,8 +92,10 @@ if [ "$branch" = "$default" ]; then echo "on $default — creating $want" git checkout -b "$want" || exit 1 elif [ "$branch" = "$want" ]; then - ahead=$(git rev-list --count "$default".."$want") || exit 1 - echo "already on $want, $ahead commit(s) ahead of $default" + echo "already on $want — every commit ahead of $default:" + git log --oneline "$default".."$want" || exit 1 + echo "STOP unless every subject above was created by this plan." + echo "Any unrelated commit here would be folded into the squash and the PR." else echo "on unexpected branch '$branch' — stop and surface"; exit 1 fi @@ -181,7 +183,9 @@ Apply Steps 1–3 at the three corresponding sites in the inline template. Change `"version": "0.8.0",` to `"version": "0.8.1",` in `plugins/dev-workflow/.claude-plugin/plugin.json`. It lands here because `check-version-bump.sh` fails a pull request that changes a `plugins/` path without a bump. -**Must be true:** the manifest reads `0.8.1`. Note that running `check-version-bump.sh main` locally on this branch compares against `main` and is a real check; the same command on `main` itself compares HEAD to HEAD and proves nothing. +**Must be true:** the manifest reads `0.8.1`. + +**What the battery's version-bump component does and does not observe here.** `check-version-bump.sh` compares *commits*. Run at Step 6 — before this task commits — it sees a range containing neither the plugin edit nor the bump, so its result is **vacuous**, not evidence. The first meaningful observation is the next battery run, at Task 2 Step 3, once Step 7's commit exists. On `main` it would be vacuous always, since the merge-base is HEAD; that is one reason Task 0 moves off `main` first. - [ ] **Step 6: Run the battery (KEPT CHECK)** @@ -289,10 +293,17 @@ git commit -m "WIP: mint mechanical-check-skipped-before-review" **Before writing, classify the path.** A **symlink** at the target is an unconditional stop — never a reuse candidate, even if its target holds identical text, because `git add` would stage a link rather than the story bytes. A **directory** is a stop. An existing **regular file** with byte-identical content is a reuse; with different content it is a stop, per invariant 9. Only an absent path is written. **Create it with an operation that fails if the path appeared meanwhile**, which the spec -requires: write the content to a temporary file in the same directory and `ln` it into place, -since `ln` fails when the target exists. A plain redirect would truncate a file another session -created between the classification and the write, and that clobber is invisible in the final -diff — nothing distinguishes "we wrote this" from "we overwrote someone else's". +requires: write the content to a temporary file in the same directory, `ln` it into place — +`ln` fails when the target exists — then remove the temporary name. A plain redirect would +truncate a file another session created between the classification and the write, and that +clobber is invisible in the final diff: nothing distinguishes "we wrote this" from "we +overwrote someone else's". + +**Remove the temporary file on every exit path**, success and failure alike. A successful `ln` +leaves a second untracked copy of the story, and a collision stop leaves scratch content — both +are picked up by `check-invariants.sh`, which scans the working tree rather than the tracked +set, and both break the clean-tree preconditions later steps rely on. A `trap 'rm -f "$tmp"' EXIT` +set immediately after the temporary name is allocated covers both. **Resume semantics.** If the file is already present and byte-identical, the deliverable is done: **skip the commit rather than creating an empty one.** The closing squash folds whatever @@ -653,6 +664,12 @@ its full text is already present: absent → apply; present and identical → sk present but different, or present more than once → **stop and surface**. A marker substring alone cannot tell an identical completed edit from a partial or independently edited one. +**Placement within each row is fixed, not left to judgement.** Every parked row ends with an +italic `*Trigger: …*` sentence. Steps 2–7 each insert their block **immediately before that +row's `*Trigger:*` sentence**, so the trigger stays last and a reader finds the status note +attached to the row's body rather than dangling after its trigger. Step 1 appends a whole new +row at the end of `### Parked (trigger-gated)`. + - [ ] **Step 1: Add the parked C4/C5 compliance row** Under `### Parked (trigger-gated)`: @@ -674,7 +691,18 @@ Under `### Parked (trigger-gated)`: - [ ] **Step 2: Record evidence case 3 on the scope-blind row** -In `**`harden-finding`'s recurrence rule is scope-blind.**`, before its `*Trigger:*` sentence: +**First correct the row's premise, then append the evidence — both in this one change.** The row +currently says the scope-blind workaround "lives in ledger prose, which agents do not read — they +read the skill." That is false, and this round established it: `harden-finding` step 3 does say +to re-read the log. Appending evidence beneath a premise the same round disproved would leave the +backlog internally contradictory and point a future reader at the wrong diagnosis — `docs-drift`, +in the round that hardens it. + +Replace that clause so the row reads that the skill's recurrence step *does* re-read the ledger, +and the defect is that its **decision branch** keys on the fingerprint and the latest row's rung +without letting that row's stated guard control the verdict. + +Then, in the same row, before its `*Trigger:*` sentence: ``` **Evidence case 3 (2026-08-04):** the 2026-08-03 hardening round ran the precheck as a @@ -894,7 +922,13 @@ If `.context/evidence-0.8.1.md` already exists, **do not overwrite it** — but either. Accept it only if it is a **regular file, not a symlink**, and its header names this story and the branch `harden-0-8-0-and-pr-21`; anything else is a stale or foreign file, and reusing it would feed the ` Date: Tue, 4 Aug 2026 16:28:12 +0200 Subject: [PATCH 11/11] Harden four classes from the 0.8.0 cycle and PR #21 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Validation evidence — hardening round 0.8.1 Story: docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md Branch: harden-0-8-0-and-pr-21 ## battery exit status: 0 hook suite under sh: "all passed" (no assertion total, as expected) hook suite under dash: "all passed" (no assertion total, as expected) invariants suite: "all passed (123 assertions)" version-bump suite: "all passed (36 assertions)" check-invariants.sh: "invariant checks: ok" check-version-bump.sh main: "version-bump check: ok" claude plugin validate --strict: "✔ Validation passed" derived hook-suite assertion count (the suite prints none) — suite exit status captured separately from the count, so a partial count from a dead suite is not recorded: HOOK_SH=sh -> status 0, count 467 HOOK_SH=dash -> status 0, count 467 ## check (named verification), with its counterfactual §5.2 applied to the four cases in spec §10 — each must FAIL on the wiring half: 1. $EVIDENCE dry run -> FAIL: the falsifying observation is the commands erroring on an unset variable, and the wiring supplies its own input by assigning $EVIDENCE itself. 2. single-shell regression -> FAIL: the falsifying observation is a non-zero exit of the `:` builtin, which occurs under dash; the check runs under macOS sh, where the defect cannot appear. 3. timed regression row -> FAIL: the falsifying observation is a newline-rich sub-ceiling payload costing far more than measured; single-record fixtures never reach the record accumulator's quadratic branch. 4. dash release evidence -> FAIL: the falsifying observation is a hook-level dash failure; the hook ran under /bin/sh, so the defect cannot appear in that run. §5.4 applied to "It bounds the scan, not memory" — must FAIL on exhaustiveness: FAIL: it names the two axes it was checked against (scan, memory) and never states whether that list is exhaustive, which is exactly what the appended clause requires. counterfactual: all four were reviewed during the 0.8.0 cycle under the §5 text as it read before this change and each was accepted by at least one review looking for exactly this; one (the single-shell test) reached the released artifact. ## prompt conformance workflow-init.md (invariant 11 surface) — all 12 items: 1 Target model named — PASS on the naming half: line 9 names Claude via Claude Code and the edit does not touch it. Second half, stated rather than glossed: the check against that model's current prompting page was made against this repo's distilled reading of those pages, docs/prompt-standards.md § "Verified model-specific notes (read 2026-07-04 — re-verify per Revalidation)", not against a live fetch in this cycle. Revalidation's stated trigger is a model generation change, which has not occurred since that read. Two of those notes bear on these additions and both hold: "literal instruction following — state the scope" (the lens sentence enumerates what to name rather than asking generally), and "coverage first, filter later" (unchanged; the additions ask for more coverage, never less). A live re-read was not performed, and this line says so instead of implying one. 2 Success criteria explicit — PASS: each inserted sentence states the artifact the reviewer must produce (the named list plus its grep; the named observation plus the wiring judgement; the settled mechanical facts). 3 Stop conditions defined — PASS: the sentences sit inside existing loops that already carry their stop rules (clean final pass, stop-and-surface); none introduces a new terminal state. 4 Output format with example — PASS: the surrounding finding-line format and the literal NO FINDINGS example are unchanged and are what these questions feed. 5 Structured sections — PASS: each sentence lands inside its existing paragraph; no section boundary moves. 6 Rules carry their why — PASS after a pass-3 fix. The lens sentence carries "because asked as an open question alone this lens missed three such statements in one cycle"; the counterfactual carries "reports success because of how it was wired, not because the thing it checks succeeded". The sweep sentence originally carried a reason only for its subordinate constraint (inspect rather than run, "since a command quoted in a spec may be destructive or an intentional failure") and none for the constraint itself; Gate-B pass 3's quality branch found that, and the sentence now carries "because a read pass spends expensive judgement on what a parser settles in seconds and misses it anyway". This line previously recorded a PASS on the strength of the subordinate clause, which is the item-11 shape this round hardens — noted rather than quietly corrected. 7 No contradictions with CLAUDE.md / AGENTS.md — PASS: the cross-finding conflict check returned none, and mirror parity confirms the template says what CLAUDE.md says. 8 Token-lean — PASS under the exception item 11 states by name: an inline template cannot point at a file the downstream project does not have, so it restates and the copies are kept in sync (invariant 8). Mirror parity is the sync evidence. 9 Positive instructions — PASS, no exception needed: all three are positive imperatives (Name what…, Name the observation…, settle mechanically…). 10 Diagnostic states name their causes — PASS: the edit introduces no failure-state report, so it adds no unresolvable symptom. 11 Enforcement claims name their mechanism — PASS: none of the three claims anything is enforced or caught; they are asks, and the CHANGELOG entry and ledger rows record "none of the three is a check" explicitly rather than leaving the reader to infer coverage. 12 Calibrated emphasis — PASS: bold falls only on the imperative clause, matching the §5 discipline-gate emphasis this template already carries as a deliberate exception. CLAUDE.md, AGENTS.md (not on invariant 11's list) — items 6,7,8,9,11,12: 6 PASS — each of the three §5 sentences and the AGENTS.md clause carries its reason ("because an enumeration read as complete guarantees the axes it omits"). 7 PASS — conflict check verdict: none; the four sites are distinct and no pair weakens another. 8 PASS — CLAUDE.md is the authoritative copy; only the template restates, under invariant 8. 9 PASS by the exemption item 9 states in its own text, cited rather than assumed: §5.4's clause continues an existing prohibition whose subject IS the prohibition ("delete any part of the sentence that outruns it"), and restating it positively would lose the boundary it draws. The three §5 sentences are positive imperatives and need no exemption. 11 PASS — no sentence claims enforcement; each ledger row states STILL INSTRUCTION-BACKED and names what is not done (nothing runs the grep, nothing tests whether a check could have failed, nothing runs or records the sweep). 12 PASS — emphasis limited to the imperative clause, consistent with §5's existing gate language. five split stories — in-spirit brief review: PASS. Each names its success criteria as acceptance criteria; each carries an explicit stop condition as criterion 1 (design pauses for Daniel's confirmation of the profile); each states what is established versus assumed — the four trigger stories carry a kept/moved/dropped inventory of their source row's conditions, and the unprofiled headers say why no profile was written rather than fabricating one. The fifth (passive metrics over the ledger) was added at Gate-B pass 1 and reviewed on the same terms. ## mirror parity each §5 sentence appears once in CLAUDE.md and once in workflow-init.md: CONFIRMED — all three matched exactly once per file under whitespace normalization (identical word sequences; wrapping differs only), each under the corresponding heading. ## gate-b fix record Pass 1 (both branches, 1 Major each, same subject): the four appended ledger rows take the ledger from 18 to 22 and cross P8's stated 20-row trigger, which the round had not recorded. Validated against the counts (18 at main, 22 after) and against spec D3, whose scope is every trigger THIS ROUND fires and whose rejected alternative excludes only triggers already fired in the backlog. Applied: P8's row marked TRIGGER FIRED, a fifth split story written, and the statements the fix falsified corrected. Found by the standing lens this round adds: the diff changed a count, and a statement keyed to that count sat in a file the diff did not touch. Pass 2 (spec 6 findings, quality 1; 6 Major, 1 Minor): the pass-1 fix corrected the headline counts and missed the sites that carry the same claim in other words — spec §2's summary, §8's inventory sentence, completion sentence and numbered list, the cited story's acceptance criteria and amendment log, and the plan's Task 5 and Task 6 bodies, step counts and must-be-true checks. All corrected here, and the P8 story is now a numbered item in §8 with the same detail as items 1–4. The quality branch found item 1 of the prompt-standards checklist recorded on its naming half only; the record above now states what was and was not checked. The pass-1 fix record originally claimed every falsified statement had been corrected — pass 2 disproved that claim, which is why this entry names the sites rather than asserting completeness. Pass 3 (spec 1 Major, quality 2 Major; one raised by both branches): Task 5's heading, file count and classification sentence still said "three" though Step 3b makes it four — the same count-keyed drift a third time, now inside the amendment itself; and the Gate-A sweep sentence carried a reason for its subordinate constraint and none for the constraint itself, which prompt-standards item 6 requires. Both fixed. The sweep fix changed a sentence quoted verbatim in four other places, so ledger row D's ref, spec §5.3's block quote, the plan's Task 1 Step 3 block and the plan's copy of row D were all updated in the same commit; mirror parity re-verified after the rewording (one occurrence per file, identical word sequence). Row D was amended rather than superseded because it is being authored in this cycle and has not landed — the append-only rule protects the committed record, and the absence of a sanctioned move for a landed row is what the ledger-supersession story exists to settle. Pass 4: NO FINDINGS on both branches, validated on disk (two lines each, `NO FINDINGS` then `END OF FINDINGS (0 total)`). Four passes against a floor of three, final pass clean, every fix amended into the WIP commit before the next call so each review read a range containing it. ## cross-finding conflict check Task 0 Step 2 verdict: no conflict. The four hardenings touch four distinct sites and act at different moments on different objects — the lens on a Gate-B diff, the counterfactual on a Gate-B evidence claim, the sweep before a Gate-A read pass, the Don't on prose about mechanisms. No pair asks a reviewer for contradictory behaviour, and §5.3's "inspect rather than run" limits the sweep without exempting anything from §5.1 or §5.2. --- AGENTS.md | 5 +- CLAUDE.md | 19 +++- docs/hardening-log.md | 4 + docs/hardening-taxonomy.md | 9 ++ ...6-08-04-hardening-round-0-8-0-and-pr-21.md | 80 +++++++++++++---- ...-hardening-round-0-8-0-and-pr-21-design.md | 52 ++++++++--- ...3-hardening-round-0-8-0-and-pr-21-story.md | 15 ++++ ...rden-finding-guard-scope-precheck-story.md | 89 +++++++++++++++++++ ...-04-hardening-ledger-supersession-story.md | 67 ++++++++++++++ ...edger-route-without-pull-requests-story.md | 60 +++++++++++++ ...4-passive-metrics-over-the-ledger-story.md | 72 +++++++++++++++ ...026-08-04-section-5-version-stamp-story.md | 63 +++++++++++++ .../dev-workflow/.claude-plugin/plugin.json | 2 +- plugins/dev-workflow/CHANGELOG.md | 17 ++++ .../dev-workflow/commands/workflow-init.md | 17 +++- todos.md | 68 ++++++++++++-- 16 files changed, 594 insertions(+), 45 deletions(-) create mode 100644 docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md create mode 100644 docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md create mode 100644 docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md create mode 100644 docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md create mode 100644 docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md diff --git a/AGENTS.md b/AGENTS.md index 64bc11b..ed904f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -215,7 +215,10 @@ reader can judge whether it still holds. in CI — it is a recipe a human runs — and an overclaim phrased without those totality words escapes it entirely. It raises the floor; it does not close the class. The underlying rule is the check itself: for every sentence about a gate, name the exact - comparison the code performs, and delete any part of the sentence that outruns it. + comparison the code performs, and delete any part of the sentence that outruns it — and + where the sentence says what the mechanism does *not* cover, name the axes it was checked + against and state whether that list is exhaustive, because an enumeration read as complete + guarantees the axes it omits. - **Never replace a decision procedure without accounting for its old conditions.** List what the previous prose required, then mark each one kept, moved, or deliberately dropped. A rewrite reliably preserves the condition that motivated it and silently loses diff --git a/CLAUDE.md b/CLAUDE.md index 0363e84..fbfc833 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -221,7 +221,12 @@ not all of `.context/`, which would strip the committed `codex-gate.on` adoption NO FINDINGS ``` - Each pass: validate, revise, re-run. (Large/high-risk artifact: optional focused + Each pass: validate, revise, re-run. Before each read pass, settle mechanically what + the artifact asserts and a machine can decide without side effects — cited paths, + quoted passages, stated counts, the syntax of standalone fenced blocks — because a read + pass spends expensive judgement on what a parser settles in seconds and misses it + anyway, inspecting quoted commands rather than running them, since a command quoted in + a spec may be destructive or an intentional failure. (Large/high-risk artifact: optional focused per-dimension passes on top.) - **Gate B — Code.** Tests green, before `git commit`. Tool: `mcp__codex__review` (args `instruction`, `whatWasImplemented`, `baseSha`; `reviewType: full` runs @@ -251,7 +256,11 @@ not all of `.context/`, which would strip the committed `codex-gate.on` adoption asks, nothing enforces the ask or validates the answer, and no comprehensive check covers arbitrary semantic drift. Ask anyway — in one cycle it surfaced a shipped command that would have let a one-line fix skip Gate B entirely, plus two user-facing docs - teaching a rule the same change had just narrowed. + teaching a rule the same change had just narrowed. **Name what this diff changes the + size, value or position of** — a list, a count, a version, an identifier, a cited + line — and grep for where each is described elsewhere, because asked as an open + question alone this lens missed three such statements in one cycle while being carried + with unusual force. **What counts as prose (the only Gate-B exemption).** Every staged path is explanatory documentation — `docs/**.md`, `README.md`, `MANIFEST.md` → N/A. @@ -342,7 +351,11 @@ A check need not be an automated test — where none is possible, a **named veri satisfies it and the entry says which route was taken and why. Either route owes the **counterfactual**: the observation against the prior state. An **unobservable counterfactual is a blocking evidence gap**, not a free pass — stop and surface; the human -may then lower the mode as a logged override. A fabricated test satisfies nothing. +may then lower the mode as a logged override. A fabricated test satisfies nothing. **Name +the observation that would exist if the claim were false, and confirm the wiring could +have produced it** — a check that supplies its own input, runs where the defect cannot +appear, or uses a fixture that never reaches the branch it covers reports success because +of how it was wired, not because the thing it checks succeeded. **The evidence entry lives in the commit body** (see Mechanics), carries the **story path and the named evidence but not the mode value**, and is **revalidated before every Gate-B diff --git a/docs/hardening-log.md b/docs/hardening-log.md index 4a0c1b6..1683a44 100644 --- a/docs/hardening-log.md +++ b/docs/hardening-log.md @@ -32,3 +32,7 @@ escape `\|`, one line), `source` (gate-a|gate-b|bot|manual), | 2026-07-27 | unverified-enforcement-claim | FOURTH occurrence: a methodology paragraph describing the gate hook's path matcher was corrected in four consecutive Gate-B rounds — false rationale, then a categorical claim that agent definitions and hook messages always fire Gate B, then a partial-matcher claim ("a bare `agents/` matches nothing", while non-`.md` paths reach the reminder anyway), then an enforcement blur between an advisory hook and the policy — each correction a subtler version of the last, inside a paragraph being rewritten to fix that very class | gate-b | major | P std | docs/prompt-standards.md item 11, new sub-paragraph "Cite the source; do not paraphrase it" + "when a claim about a mechanism needs a fourth correction, delete the claim rather than refine it a fifth time". Sharpens item 11 rather than adding item 13, deliberately: a new numbered item ripples the checklist count through prompt-standards, the workflow-init template and AGENTS.md invariant 11, all pinned by check 4b. COUNT, stated because the escalation depends on it: the prior lineage is 2026-07-18 (first), 2026-07-19 (second), 2026-07-25 (third, `pending`) and 2026-07-26, which RESOLVES that pending row rather than recording a new defect — so this is the fourth occurrence, not the fifth. NOT ESCALATED to a mechanical rung despite the 2026-07-26 `2 lint` row being the latest: that check guards one spelling (`Target model:` lines) and this defect is outside it — exactly the over-escalation the 2026-07-26 row warns about by name. NO CURRENT DETERMINISTIC RUNG covers this paraphrase shape — the one candidate evaluated, a grep for the matcher's terms, fires on the matcher and the policy file themselves; that rules out the naive check, not every conceivable one, and the recurrence count here is a signal a human reads rather than anything that blocks a commit. The fix that finally held was DELETION — the enumeration was replaced by a pointer to the policy file; the same shape closed a second instance in the same cycle, where three successive `git status` recipes were replaced by naming the paths you edited | | 2026-07-27 | rewrite-drops-prior-condition | NEW CLASS, minted this change: ten instances in one cycle where a multi-condition rule was rewritten, the new condition survived and an old one vanished with the prose it replaced — `process-pr-review` losing the no-story and all-stories branches, then losing fix-triviality so an eligible profile alone authorized a Gate-B skip (a gate-off path invented by the change that exists to close one); a spec surfaces sentence describing the skip as keyed "on the story profile, not on fix size"; story AC 6 dropping triviality in the very edit that added effective-level eligibility; and the §5 authority leaving triviality implicit inside the phrase "triviality skip", which is what every downstream summary copied and then lost | gate-b | major | 1 prose | AGENTS.md Don'ts, "Never replace a decision procedure without accounting for its old conditions" — list what the previous prose required and mark each kept, moved, or deliberately dropped; also check the paragraphs around any criterion you amend, since amending one does not touch the prose that motivated it. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against `docs-drift` stated (drift is two artifacts disagreeing and is fixed by updating the stale one; this is one rewritten rule wrong on its own terms, fixed by restoring the condition). NO DETERMINISTIC RUNG: a dropped condition is textually indistinguishable from text that was never there, so no grep or checker decides it | | 2026-07-27 | docs-drift | FOURTH occurrence, different mechanism from the first three: a change invalidated sentences in files it never touched — `plugins/dev-workflow/commands/process-pr-review.md` (a SHIPPED command still skipping Gate B on fix size alone, which would have let a one-line fix on a security-relevant story bypass the narrowed rule entirely), `docs/getting-started.md` twice, and `docs/coding-workflow.md`. Every one was correct before the change and false after it | gate-b | major | P std | CLAUDE.md §5 Gate B + the workflow-init inline template: a standing lens on every Gate-B call — "which existing statements does this diff falsify?". A TARGETED ASK found all four; the checks that ran — parity diffs, resyncs, greps of the edited paths — are scoped to files the change touched and do not look where these live, since those files were correct until the change landed elsewhere and nothing in the diff points at them. WHAT THIS RUNG DOES NOT DO, stated because the lens is the whole hardening: it is prompt text, nothing enforces that a reviewer asks it or checks the answer, and it guarantees no discovery. A narrow deterministic check could pin one stale spelling the way check 4b pins one count spelling; what does not exist is a comprehensive check for arbitrary semantic drift, which is why the rung is P and not 2. NOT ESCALATED past the 2026-07-26 `2 lint` row for the same reason as the sibling row above: that check guards prose count claims, and this defect is outside its spelling — the over-escalation the row warns about. Escalation from the 2026-07-18 `P std` row (spec-updated-with-fix) is real but narrow: that rule reaches the spec of the change being made, not arbitrary prose elsewhere in the repo | +| 2026-08-04 | docs-drift | fifth occurrence: three statements the 0.8.0 diff falsified — AGENTS.md's prerequisites list omitting `dash` while the battery invokes it twice, a plan's "four script runs" for a block holding five, and a spec citing a hook line the same rewrite had moved — all found by a PR bot, none by the standing Gate-B lens | bot | major | P std | CLAUDE.md §5 Gate-B lens + the same block in the workflow-init inline template: the lens now asks what the diff changes the size, value or position of, and to grep for where each is described elsewhere. PRIOR ROW: 2026-07-27 docs-drift (P std), guard "which existing statements does this diff falsify?" — this finding is INSIDE that guard, so it is a regression repaired at the same rung, not an escalation: the lens was carried on the 0.8.0 cycle (the execution notes record it under "Standing lens, with unusual force") and missed all three. WHAT CHANGED AND WHY THE OLD FORM MISSED IT: the open question asks for recall; all three cases are a fact recorded elsewhere that the diff changed — a list's membership, a count, a position — and enumerating those needs a search. STILL INSTRUCTION-BACKED: nothing runs the grep or validates the answer, so this raises the floor and does not close the class | +| 2026-08-04 | unverified-enforcement-claim | fifth occurrence: "It bounds the **scan**, not memory" named one uncovered axis and left the reader to infer the others were covered — the bound was a size bound, the work was quadratic in size, and the runtime it was read as bounding held only for the shapes that had been measured | gate-a | major | 1 prose | AGENTS.md Don't "Never describe what a gate proves without checking what it actually compares", closing rule extended: where a sentence says what a mechanism does NOT cover, name the axes it was checked against and state whether that list is exhaustive. PRIOR ROW: 2026-07-19 (1 prose), the same Don't, whose operative instruction covers "every sentence about a gate" — INSIDE it, so this is a regression, and the amendment closes a gap the comparison-focused wording left open. GUARD, exactly: the exhaustiveness statement. The motivating sentence names two axes and gives each a verdict, so a rule asking only for the axes checked would approve it; declaring the list exhaustive or not is what it never does. NOT LOGGED HERE: PR #21's C4 and C5 fall inside the same Don't but need nothing it does not already say, so no repair exists to name — they are parked in todos.md as a compliance recurrence with their own trigger. STILL INSTRUCTION-BACKED: no checker reads a coverage claim | +| 2026-08-04 | verification-masks-failure | second occurrence, four cases in one cycle: a dry run that defined `$EVIDENCE` itself, proving the git mechanics and never that the plan defines the variable; a regression test that only ever ran under macOS `sh` while the defect it guards appears under `dash`; timed rows whose single-record fixtures never reach the record accumulator's quadratic path; and release evidence claiming `dash` coverage from a run that executed the harness under `dash` and the hook under `/bin/sh` | gate-b | major | P std | CLAUDE.md §5 Profiles counterfactual + the same block in the workflow-init inline template: name the observation that would exist if the claim were false, and confirm the wiring could have produced it. GUARD, exactly: the second half — a check that supplies its own input, runs where the defect cannot appear, or uses a fixture that never reaches the branch it covers. Each of the four cases fails on it. PRIOR ROW: 2026-07-20 (1 prose), whose ref states its own scope — "nothing checks new plans for the same shape" — so all four are OUTSIDE it and this is the fitting rung rather than an escalation on the count. RUNG P NOT 1: the artifact is a prompt; the rung follows the artifact, not the count. SOURCE: two gate-a cases and two gate-b, so the tie-break applies — the triggering case is the `dash` release evidence, which is gate-b. STILL INSTRUCTION-BACKED: nothing tests whether a check could have failed | +| 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — because a read pass spends expensive judgement on what a parser settles in seconds and misses it anyway, inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | diff --git a/docs/hardening-taxonomy.md b/docs/hardening-taxonomy.md index 0b26ab6..aab3168 100644 --- a/docs/hardening-taxonomy.md +++ b/docs/hardening-taxonomy.md @@ -94,6 +94,15 @@ the synonyms a future reader might search for instead. writing the thing down at all. Also not `truncated-tool-output-read-as-complete` — nothing here is truncated or misread as complete; the content was simply never durable. +- `mechanical-check-skipped-before-review` — an artifact carrying machine-checkable assertions + goes to an expensive read pass before anything parses it, so attention is spent on what a + tool decides in seconds. Aliases: `sh -n` after the fact, the parser would have caught it, + read pass before the sweep, manual review of machine-decidable claims. + + **Not `verification-masks-failure`.** There a check ran and could not fail; here the cheap + check never ran at all. Grep this one when the sentence is "a parser would have found it + immediately"; grep the other when it is "the check passed and proved nothing". + **Promotion candidate.** These classes are stack-neutral, not project vocabulary, so they belong in the `harden-finding` base list rather than here. They live here because the skill says to mint into this file (the plugin ships the base classes, the project diff --git a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md index ab199cc..0a0b658 100644 --- a/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md +++ b/docs/superpowers/plans/2026-08-04-hardening-round-0-8-0-and-pr-21.md @@ -2,9 +2,11 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. -**Goal:** Land four hardenings from the 0.8.0 cycle and PR #21 as text, record them in four ledger rows, park what has no repair, and open four split stories — without changing any skill file. +**Goal:** Land four hardenings from the 0.8.0 cycle and PR #21 as text, record them in four ledger rows, park what has no repair, and open five split stories — without changing any skill file. -**Architecture:** Three one-sentence additions to `CLAUDE.md` §5, each mirrored into the inline template in `plugins/dev-workflow/commands/workflow-init.md`; one clause appended to an existing `AGENTS.md` Don't; one new taxonomy class; four appended ledger rows; seven `todos.md` row changes; four new story files. No executable code changes. +**Amended at Gate-B pass 1.** The round opened four stories and made seven `todos.md` row changes as planned; a fifth story and an eighth row change were added during the Gate-B loop, when both reviewers found that the four appended ledger rows cross P8's 20-row threshold (18 → 22) and D3 makes a trigger this round fires a round deliverable. The counts below are the amended ones. + +**Architecture:** Three one-sentence additions to `CLAUDE.md` §5, each mirrored into the inline template in `plugins/dev-workflow/commands/workflow-init.md`; one clause appended to an existing `AGENTS.md` Don't; one new taxonomy class; four appended ledger rows; eight `todos.md` row changes; five new story files. No executable code changes. **Tech Stack:** Markdown prompts. The quality battery in `AGENTS.md § Commands` is the test cycle — there is no unit test for prose. @@ -45,8 +47,8 @@ a procedure, and every restatement is a copy that can drift. - [ ] **Version 0.8.1** in the manifest, with the first plugin change (Task 1) - [ ] **One `AGENTS.md` Don't amendment** (Task 2) - [ ] **One taxonomy class** — `mechanical-check-skipped-before-review` (Task 3) -- [ ] **Four split stories**, unprofiled, six `##` sections each (Tasks 4–5) -- [ ] **Seven `todos.md` row changes** — one new, six updated (Task 6) +- [ ] **Five split stories**, unprofiled, six `##` sections each (Tasks 4–5, plus the P8 story added at Gate-B pass 1) +- [ ] **Eight `todos.md` row changes** — one new, seven updated (Task 6, plus the P8 row marked fired at Gate-B pass 1) - [ ] **Four ledger rows** — A, B, C, D (Task 7) - [ ] **A `## 0.8.1` CHANGELOG entry** (Task 8) - [ ] **Validation evidence** in `.context/evidence-0.8.1.md`, folded into the closing commit (Task 9) @@ -60,7 +62,7 @@ a procedure, and every restatement is a copy that can drift. - **`grep -c` counts matching *lines* and exits 1 on zero.** Flattening a file with `tr '\n' ' '` and then counting makes every result 0 or 1, so it cannot detect duplicates. Count occurrences with `grep -o ... | grep -c .`. - **Version is `0.8.1`**, committed in Task 1 with the first `plugins/` change. - **The ledger is append-only.** Never edit an existing row. Escape a literal `|` as `\|`. -- **The four stories are unprofiled**, six `##` sections, profile-confirmation criterion first. +- **The five stories are unprofiled**, six `##` sections, profile-confirmation criterion first. - **No `Co-Authored-By: Claude` or `Generated with` trailers.** - **`git add` and `git commit` are always separate calls** — the gate hook derives its docs-only file list at `PreToolUse`, so a compound call presents an empty index and fires a spurious STOP. - **Before every `git add`, confirm the index holds nothing unrelated**, and after staging confirm the staged set is exactly the task's paths. @@ -169,10 +171,15 @@ Immediately after `Each pass: validate, revise, re-run.` and before the parenthe ``` Before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone -fenced blocks — inspecting quoted commands rather than running them, since a command quoted in -a spec may be destructive or an intentional failure. +fenced blocks — because a read pass spends expensive judgement on what a parser settles in +seconds and misses it anyway, inspecting quoted commands rather than running them, since a +command quoted in a spec may be destructive or an intentional failure. ``` +The "because" clause was added at Gate-B pass 3, where the quality branch found the sentence +gave a reason for inspecting quoted commands rather than running them and none for the sweep +itself — `docs/prompt-standards.md` item 6 requires one per constraint. + - [ ] **Step 4: Mirror all three into `workflow-init.md`** Apply Steps 1–3 at the three corresponding sites in the inline template. @@ -419,9 +426,10 @@ git commit -m "WIP: split story for the harden-finding guard-scope precheck" --- -### Task 5: The three trigger stories +### Task 5: The four trigger stories -**Files:** Create the three paths below. +**Files:** Create the four paths below. Three were planned; the fourth (Step 3b) was added at +Gate-B pass 1, when this round's own ledger appends crossed the threshold its trigger names. Each replaces a parked `todos.md` row carrying settled analysis, so each holds an inheritance inventory marking every condition of its source row **kept, moved, or deliberately dropped** — @@ -432,7 +440,8 @@ exactly six `##` sections. **Classify each path immediately before writing it**, by Task 4's rule: symlink or directory → stop; regular file with identical bytes → reuse and skip the commit; regular file with different bytes → stop; absent → create with the same fail-if-it-appeared operation Task 4 describes. A -single classification of all three up front is stale by construction for the second and third. +single classification of all four up front is stale by construction for every path after the +first — and most of all for Step 3b, which is written a whole Gate-B pass later. - [ ] **Step 1: `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`** @@ -639,7 +648,23 @@ without comparing the two by hand. shipped provisionally cannot be taken back. ``` -**Must be true, for all three:** each exists as a regular file at its exact path, carries no +- [ ] **Step 3b: `docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md` + (added at Gate-B pass 1)** + +Written after Task 7 appended the four ledger rows, because that is what fires its trigger: the +ledger goes 18 → 22, crossing P8's stated "10 stories or 20 ledger rows". Same shape as Steps +1–3 — classify the path immediately before writing it, create with the same fail-if-it-appeared +operation, unprofiled, six `##` sections, one `### Conditions inherited from the source row` +subsection carrying P8's conditions (analysis only, no new state file, no instrumentation, +nothing written back, the scope of questions the ledger already answers, and the threshold +itself) each marked kept, moved or dropped, and the profile-confirmation criterion first. Its +open question is where the analysis lives, since "analysis only" rules out state but not a +surface. + +Because it lands after Task 7 in execution order, it is committed with the Gate-B fix rather +than in Step 4's commit; the closing squash folds whatever commits exist. + +**Must be true, for all four:** each exists as a regular file at its exact path, carries no profile line, has exactly six `##` sections, has one `### Conditions inherited from the source row` subsection, and has the profile-confirmation criterion first. @@ -655,7 +680,7 @@ git commit -m "WIP: three split stories for the fired triggers" --- -### Task 6: `todos.md` — one new row, six existing rows +### Task 6: `todos.md` — one new row, seven existing rows **Files:** Modify `todos.md`. @@ -668,7 +693,8 @@ alone cannot tell an identical completed edit from a partial or independently ed italic `*Trigger: …*` sentence. Steps 2–7 each insert their block **immediately before that row's `*Trigger:*` sentence**, so the trigger stays last and a reader finds the status note attached to the row's body rather than dangling after its trigger. Step 1 appends a whole new -row at the end of `### Parked (trigger-gated)`. +row at the end of `### Parked (trigger-gated)`. Step 7b was added at Gate-B pass 1 and follows +the same placement rule. - [ ] **Step 1: Add the parked C4/C5 compliance row** @@ -764,9 +790,24 @@ In `**`/workflow-init` preflight checks `CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS`.**` unbuilt, so the row is not discharged by this. ``` -**Must be true:** all seven changes are present, each exactly once; every +- [ ] **Step 7b: Mark P8's row fired (added at Gate-B pass 1)** + +In `**P8 — passive metrics, read-only over the ledger and git.**`, whose trigger is a sample-size +threshold this round's own ledger appends cross: + +``` + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round appended four ledger rows, + taking the ledger from 18 to 22 and crossing the 20-row arm; its fifth story takes the + story count to 10, crossing the other arm as well. Story: + `docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md`, which + carries this row's conditions with each marked kept, moved or dropped. +``` + +Like Step 3b, this lands after Task 7 in execution order and is committed with the Gate-B fix. + +**Must be true:** all eight changes are present, each exactly once; every `docs/superpowers/stories/2026-08-04-*.md` path cited in `todos.md` exists as a regular file; -and there are exactly four such distinct paths. A `todos.md` pointing at a story that does not +and there are exactly five such distinct paths. A `todos.md` pointing at a story that does not exist is the docs-drift class this round hardens. - [ ] **Step 8: Run the battery.** Expected: exit 0. @@ -839,7 +880,7 @@ story's subject (Task 4), and inventing the procedure here would re-import what - [ ] **Step 5: Append row D** ``` -| 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | +| 2026-08-04 | mechanical-check-skipped-before-review | NEW CLASS, minted this change: eight read-only Gate-A passes over one plan missed eight defects that thirteen machine checks then found in a single sweep, including a rollback that would have byte-verified against the wrong hook | manual | major | P std | CLAUDE.md §5 Gate-A pass procedure + the same block in the workflow-init inline template: before each read pass, settle mechanically what the artifact asserts and a machine can decide without side effects — cited paths, quoted passages, stated counts, the syntax of standalone fenced blocks — because a read pass spends expensive judgement on what a parser settles in seconds and misses it anyway, inspecting quoted commands rather than running them, since a command quoted in a spec may be destructive or an intentional failure. Class added to docs/hardening-taxonomy.md in this same change, with its boundary against verification-masks-failure stated: there a check ran and could not fail; here the cheap check never ran at all. NO PRIOR ROW — this is the first occurrence. STILL INSTRUCTION-BACKED: nothing runs the sweep, records that it ran, or checks what it settled | ``` **Must be true:** each appended row is a single line with exactly seven fields and no unescaped @@ -964,7 +1005,7 @@ this; one (the single-shell test) reached the released artifact. ## prompt conformance workflow-init.md (invariant 11 surface) — all 12 items: CLAUDE.md, AGENTS.md (not on invariant 11's list) — items 6,7,8,9,11,12: -four split stories — in-spirit brief review: +five split stories — in-spirit brief review: ## mirror parity each §5 sentence appears once in CLAUDE.md and once in workflow-init.md: @@ -1132,7 +1173,8 @@ Then run `/dev-workflow:process-pr-review` once the bots report. ## Self-Review **Spec coverage.** §5.1–§5.4 → Tasks 1–2. §6 rows A–D → Task 7. §6.4's concurrency limit → -Task 7, stated as the spec states it. §6.5 class → Task 3. §8 four stories → Tasks 4–5. §9 +Task 7, stated as the spec states it. §6.5 class → Task 3. §8 five stories → Tasks 4–5 plus the +Gate-B pass 1 addition. §9 deliverables → Task 6, Task 1 Step 5, Task 8, Task 3. §10 validation → Task 9. §10 conflict check → Task 0 Step 2, before any edit. D7 (no skill file) → Global Constraints, Task 3. @@ -1140,7 +1182,7 @@ check → Task 0 Step 2, before any edit. D7 (no skill file) → Global Constrai and the evidence template is written out in full. **Type consistency.** `mechanical-check-skipped-before-review` matches across Tasks 3 and 7. The -four story paths match across Tasks 4, 5, 6. `EVIDENCE` is defined in every block that reads it. +five story paths match across Tasks 4, 5, 6. `EVIDENCE` is defined in every block that reads it. **Verification surface.** **Four** executable checks — branch preflight, battery, ledger precondition, ` Before each read pass, settle mechanically what the artifact asserts and a machine can decide > without side effects — cited paths, quoted passages, stated counts, the syntax of standalone -> fenced blocks — inspecting quoted commands rather than running them, since a command quoted -> in a spec may be destructive or an intentional failure. +> fenced blocks — because a read pass spends expensive judgement on what a parser settles in +> seconds and misses it anyway, inspecting quoted commands rather than running them, since a +> command quoted in a spec may be destructive or an intentional failure. + +(The "because" clause was added at Gate-B pass 3: the sentence carried a reason for inspecting +quoted commands rather than running them, and none for the sweep itself, which +`docs/prompt-standards.md` item 6 requires of every constraint.) ### 5.4 The 2026-07-19 `AGENTS.md` Don't, amended — F2 (claim half), F8 @@ -301,16 +308,24 @@ amendment. An entry that replaces text names the text it replaced; one that exte names what was added. The story is the writable copy, and this section does not restate the entries. -## 8. The four split stories +## 8. The five split stories -Four story files at `docs/superpowers/stories/YYYY-MM-DD--story.md`. A note or a +Five story files at `docs/superpowers/stories/YYYY-MM-DD--story.md`. A note or a commit-body summary does not satisfy this: the point is an artifact that outlives the session. +**The fifth was added during the Gate-B loop**, when both reviewers found that this round's four +appended ledger rows take the ledger from 18 to 22 and cross P8's stated 20-row threshold. D3 +covers it — the trigger fires by this round's own action, not in the backlog — so P8's row is +marked fired and its story is a round deliverable like the other four. It is recorded here +rather than only in the plan because the count in this section was falsified by the fix, which +is the drift this round hardens. + **They are written directly rather than produced by `dev-workflow:intake`, and each file says so.** Intake excludes "items that already have an approved story/spec or have moved into -solution design", and all four are that: three carry cut-short design analysis from earlier -rounds, and the fourth (F7) is the ledger-format work this round declined to fold in and has -already scoped. Making an intake-produced artifact acceptance-critical would deadlock the round +solution design", and all five are that: three carry cut-short design analysis from earlier +rounds, the fourth (F7) is the ledger-format work this round declined to fold in and has +already scoped, and the fifth (P8) is a parked row whose analysis was settled when it was +written and whose only open condition was a sample-size threshold. Making an intake-produced artifact acceptance-critical would deadlock the round against a skill that declines the input. **Each file uses the story template's six sections and its `**Date:**`/`**Size:**` header line, @@ -332,7 +347,7 @@ Nothing else would pick the debt up — `intake` declines work already in soluti unprofiled — so a sentence merely saying the profile is owed would name no step at all, which is the unbacked-claim shape this round hardens. -**Each story inherits its source row's conditions, accounted for.** Three of the four replace a +**Each story inherits its source row's conditions, accounted for.** Four of the five replace a parked `todos.md` row that carries settled analysis, and the `AGENTS.md` Don't "Never replace a decision procedure without accounting for its old conditions" applies to that replacement as much as to any rule: each story lists every condition its source row states and marks it kept, @@ -347,7 +362,7 @@ appeared meanwhile rather than one that truncates: a classification taken early later is check-then-write, and another session can create or change the file in the gap. On any such failure, stop and surface — the same answer as a collision seen at classification time. -**The round is incomplete until all four exist.** If one cannot be grounded — no statable +**The round is incomplete until all five exist.** If one cannot be grounded — no statable problem, outcome, or three checkable criteria — that is a stop-and-surface: say which and why, rather than padding a story into existence. @@ -385,6 +400,17 @@ rather than padding a story into existence. the one known-stale instance is being re-synced by hand, so this carries no schedule pressure. Open question: how a scaffolded `CLAUDE.md` can tell its reader that it lags the installed plugin. +5. **Passive metrics, read-only over the ledger and git (P8)** → + `docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md`. **Added at + Gate-B pass 1, not at design time** — the trigger is a sample-size threshold, "10 stories or + 20 ledger rows", and this round's four appended rows cross it (18 → 22); its own fifth story + takes the story count to 10, crossing the other arm. Conditions to account for, from that + row: analysis only, with no new state file, no instrumentation and nothing written back; the + scope is questions the ledger already holds the data for — which fingerprints recur and how + often a rung holds; and the threshold itself, whose reason was that a smaller sample says + more about the last week than about the workflow. Open question: where the analysis lives — a + command, a script, or a documented reading recipe — given that "analysis only" rules out + state but not a surface. **Not fired:** the slot-collision row, whose trigger names the §5 **file protocol**. This round changes no part of it — not the slot names, not the pre-call delete, not the terminator or @@ -414,6 +440,10 @@ acceptance rules. followed twice; no textual repair is available, because its operative instruction already requires what both cases omitted. *Trigger: a third compliance miss against that Don't, or a feasible mechanical rung emerging from the split skill story (§8 item 1).* +- **`todos.md`, the P8 passive-metrics row** — marked **fired** and pointed at §8 item 5's story + path. Its trigger is a sample-size threshold, and this round's four appended rows cross it + (18 → 22). Found at Gate-B pass 1 by both reviewers, applying the standing lens this round + adds: the diff changed a count, and a statement keyed to that count sat elsewhere. - **`docs/hardening-taxonomy.md`** — the §6.5 class. - **`plugins/dev-workflow/.claude-plugin/plugin.json`** — `version` to `0.8.1`, required by invariant 12. @@ -483,7 +513,7 @@ to a rule statement — 6, 7, 8, 9, 11 and 12 — with the result recorded. §5. phrased as a continuation of an existing prohibition; item 9's own text exempts rules whose subject is the prohibition, and that exemption is cited in the record rather than assumed. -The four split stories are agent inputs, and `docs/prompt-standards.md` holds briefs to the +The five split stories are agent inputs, and `docs/prompt-standards.md` holds briefs to the checklist in spirit. Each gets a recorded in-spirit review — success criteria, stop conditions, verified claims — without asserting the mandatory 12 apply. diff --git a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md index 9dc73fb..34a2209 100644 --- a/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md +++ b/docs/superpowers/stories/2026-08-03-hardening-round-0-8-0-and-pr-21-story.md @@ -4,6 +4,14 @@ **Risk:** standard · **Security:** none · **Validation:** battery+check **Amendment log:** +- 2026-08-04 · AC added (the P8 criterion) · the round's acceptance criteria covered the four + stories designed up front and said nothing about a parked row whose trigger the round's own + output crosses · both Gate-B pass 1 reviewers found that the four appended ledger rows take + the ledger from 18 to 22, past P8's stated 20-row threshold, and D3 makes a trigger this round + fires a round deliverable. Without the criterion the round could satisfy every stated + criterion while omitting a deliverable D3 required, so the criterion was added rather than the + omission argued away. Raised at Gate-B pass 1; the fifth story and the eighth `todos.md` row + change landed in the same commit. - 2026-08-03 · AC 1 amended · replaced "has exactly one recorded disposition" with the current wording · a finding whose halves fall in different fingerprint classes needs a disposition in each; F2's two halves are `unverified-enforcement-claim` and @@ -160,6 +168,13 @@ squeezed in. design resumes. - [ ] Each of the three parked rows whose trigger names §5 is read literally against its own text, and the round records which fired and which did not, with the reason. +- [ ] **Added at Gate-B pass 1.** Any parked row whose trigger this round's own output crosses is + read the same way, marked fired, and given its story as a round deliverable — which at + minimum covers P8, whose "10 stories or 20 ledger rows" threshold the four appended rows + cross (18 → 22). A fifth story exists on disk on the same terms as the other four — + written directly in the story template's shape, unprofiled, carrying its source row's + conditions marked kept, moved or dropped, and carrying the profile-confirmation criterion + first — and `todos.md` carries the eighth row change that marks P8 fired. - [ ] **Fired at Gate-A spec pass 3.** Making the precheck work turned out to require decisions about what a durable record holds, how a guard citation survives the ledger's format, and what happens when a row appears mid-run — more than the rule paragraph this criterion diff --git a/docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md b/docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md new file mode 100644 index 0000000..496a741 --- /dev/null +++ b/docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md @@ -0,0 +1,89 @@ +# `harden-finding`'s recurrence rule is scope-blind — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately.** This story is a split from a designed round, not a raw idea, so +it did not pass through `dev-workflow:intake` — intake excludes items that have moved into +solution design. A profile is proposed and human-confirmed at intake time. Writing one here +would produce a header that **looks** confirmed and is not, and nothing would reveal that: +`CLAUDE.md` §5 stops on a profile that is malformed or internally inconsistent, not on one +whose values are well-formed but unconfirmed. That is why the debt is carried as acceptance +criterion 1 rather than by fabricating a header. + +## 1. Problem statement + +`harden-finding`'s recurrence step tells you to re-read the ledger, and then decides from the +fingerprint and the latest matching row's rung. It never lets that row's **stated guard** +control the verdict. So a later in-class defect outside the guarded spelling is proposed for a +stronger rung than anything justifies — which the 2026-07-26 rows warn about by name, in ledger +prose the decision branch does not consult. + +The 2026-08-03 hardening round applied the missing precheck by hand, on a standing instruction +from Daniel, and still reached a wrong verdict twice by reading one prior row's guard and +stopping. Landing the rule in the skill turned out to require decisions a rule paragraph cannot +carry, which is why it is here rather than in that round. + +## 2. Desired outcome + +A recurrence proposal states which prior rows it read, what each guards, and whether this +finding falls inside or outside — and a later reader can check that reasoning against the +ledger. Escalation follows from a guard that failed, never from a count. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A recurrence decision names every prior matching row it considered, not only the latest, + and records what each guards. +- [ ] The record a decision leaves behind is sufficient for a later reader to reach the same + inside/outside verdict without re-deriving it. +- [ ] The rule states what happens when a same-fingerprint row appears mid-run, including when + that row is `pending` or its guard cannot be determined. +- [ ] Whether automatic escalation on recurrence is kept, narrowed, or dropped is settled + explicitly, with a stated reason. +- [ ] The `AGENTS.md` Don't "Never replace a decision procedure without accounting for its old + conditions" is satisfied for every condition the current step 3 and step 7 carry. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "10. **The base taxonomy stays + stack-neutral.** Project vocabulary — tables, auth helpers, framework APIs — goes only in + that project's `docs/hardening-taxonomy.md`, never into the `harden-finding` skill. Otherwise + one project leaks into every other." +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" +- `## Don'ts` — "**Never replace a decision procedure without accounting for its old + conditions.** List what the previous prose required, then mark each one kept, moved, or + deliberately dropped." + +## 5. Open questions + +Six, each paired with the failure that raised it: + +- A verdict was reached by reading one prior row's guard and stopping, when an older row's guard + covered the case. **Which prior rows must a recurrence be judged against before "outside" is + safe?** +- A reader cannot tell whether a past rung choice followed from a guard reading or from a count, + because nothing durable records the reading. **What does a later reader need, and where does + it belong?** +- Guards are frequently exact spellings containing regex alternation, and the ledger's stated + escaping rule covers the `finding` column. **How does a guard citation survive a Markdown + table without changing what the columns mean?** +- Step 7 re-reads the ledger and then appends, so a row landing after that read is not seen, and + a row seen at the read is treated as a duplicate without anyone consulting what it guards. + **What should happen when a row appears mid-run?** +- Widening the branch condition from "latest matching row is a real rung" to "a matching row + exists at a real rung" makes it fire when the latest row is `pending`, bypassing the + prerequisite rule. **How do a guard scan and a prerequisite block compose?** +- The old step-3 text required proposing one rung stronger on recurrence, and a replacement + choosing "the rung that fits the repair" does not carry it. **Is automatic escalation kept, + narrowed, or deliberately dropped?** + +## 6. Suggested size + +`story` — one skill file, one decision procedure, one spec → plan → PR. Above a chore because +the six questions above are real design; below an epic because they all concern one procedure +in one file. diff --git a/docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md b/docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md new file mode 100644 index 0000000..e9ac5ca --- /dev/null +++ b/docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md @@ -0,0 +1,67 @@ +# The hardening ledger has no supersession convention — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`, which excludes work already in solution design. A profile written now +would look confirmed without being confirmed, and nothing would reveal that; acceptance +criterion 1 carries the debt instead. + +## 1. Problem statement + +`docs/hardening-log.md`'s header says never edit a row, and one row per hardening. When a row's +"what this does NOT do" narration is later falsified by a feature change, neither move is +sanctioned: editing breaks the first rule, appending breaks the second. + +This is live. The 2026-07-20 row describes pre-0.8.0 counting behaviour as current, and a reader +who trusts it is misled about how the gate hook counts today. That is the second falsified row, +which is the condition the parked backlog row named as its trigger. + +### Conditions inherited from the source row + +From `todos.md`, "**The hardening ledger has no supersession convention.**": + +| Condition | Disposition | +|---|---| +| Never edit a row | **kept** — any solution must preserve it | +| One row per hardening | **kept** — any solution must preserve it | +| The 2026-07-20 row now describes pre-0.8.0 behaviour as current | **kept** as the motivating instance | +| The 2026-07-20 *spec* took a version-qualified supersession note and it worked | **kept** as prior art the design should evaluate first | +| Alternative: an explicit "rows are historical, read the newest row for current behaviour" header statement | **kept** as a candidate | +| Trigger: the next row falsified by a later change — this is the second | **moved** — fired, and recorded here | + +## 2. Desired outcome + +A reader who opens any ledger row can tell whether it still describes current behaviour, and a +row falsified by a later change can be marked as such without breaking either standing rule. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] The 2026-07-20 row no longer reads as current behaviour, and it was not edited. +- [ ] The append-only rule and the one-row-per-hardening rule both still hold after the change. +- [ ] A reader can determine, from the ledger alone, which row to trust for current behaviour. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay + inline** in the command body." +- `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites + silently.** Idempotent: missing → write; identical → report unchanged; present and different → + show the diff and ask; additive files (`.gitattributes`, `.mcp.json`, …) → merge." +- **Conditional, if the design reaches the scaffolded template** — "11. **Prompt changes pass + `docs/prompt-standards.md`**" and "12. **A plugin change requires a version bump.**" Both bind + only if the convention must reach `/workflow-init`'s inline ledger header, which is §5's open + question. + +## 5. Open questions + +- Which artifacts must the convention reach before a reader can trust any row — this repo's + ledger alone, or every ledger `/workflow-init` scaffolds? A repo-only fix ships a rule this + kit's ledger obeys and every scaffolded one does not. + +## 6. Suggested size + +`story` — one file's header convention plus possibly one inline template, one spec → plan → PR. diff --git a/docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md b/docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md new file mode 100644 index 0000000..f2e7260 --- /dev/null +++ b/docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md @@ -0,0 +1,60 @@ +# A route from a fixed finding to the ledger, for projects that never open PRs — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`. A profile written now would look confirmed without being confirmed; +acceptance criterion 1 carries the debt instead. + +## 1. Problem statement + +The only mandated ledger check lives in `/dev-workflow:process-pr-review` step 5, so a project +that never opens a pull request never reaches it. One project has 51 Gate-A pass files and +**zero** ledger rows: findings were raised, validated and fixed, and none was ever considered +for hardening. + +### Conditions inherited from the source row + +From `todos.md`, "**Finding A — a route from a fixed finding to the ledger for projects that +never open PRs.**": + +| Condition | Disposition | +|---|---| +| Scope must match `process-pr-review` step 5 **exactly** — check every accepted actionable fixed finding, but invoke `harden-finding` only when a class matches or a new one is clearly warranted | **kept** — every approximating draft got this wrong | +| Cannot rest on same-session memory: a compaction, interruption or handoff loses the fixed-finding set and nothing detects the loss | **kept** | +| A durable handoff needs real design — identity, deduplication, consumption semantics | **kept**, and it is why this is a story rather than a mid-round addition | +| It mints `mandatory-step-anchored-to-optional-path` when it lands; minting earlier leaves a class no row uses | **kept** — the class is minted by the change that uses it | +| Evidence: canvas has 51 Gate-A pass files and 0 ledger rows | **kept** as the motivating instance | +| Trigger, first alternative: the next round that touches §5 | **moved** — fired by the 2026-08-03 round, recorded here | +| Trigger, second alternative: a project reporting an empty ledger across cycles that fixed findings | **kept** — it did not fire, and it remains the condition that would raise this independently | + +## 2. Desired outcome + +A project that fixes review findings without opening a pull request still reaches the ledger +check, and a fixed finding that warrants hardening is not lost to a compaction, an interruption +or a handoff. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A project with no pull requests reaches a ledger check on the findings it fixed. +- [ ] The route does not depend on same-session memory. +- [ ] The check's scope matches `process-pr-review` step 5 exactly — no wider, no narrower. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" + +## 5. Open questions + +- What has to be true for a fixed finding to reach the ledger in a project that never opens a + pull request? + +## 6. Suggested size + +`story` — one route, one spec → plan → PR. diff --git a/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md b/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md new file mode 100644 index 0000000..07b4638 --- /dev/null +++ b/docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md @@ -0,0 +1,72 @@ +# Passive metrics, read-only over the ledger and git — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`, which excludes work already in solution design. A profile written now +would look confirmed without being confirmed; acceptance criterion 1 carries the debt instead. + +## 1. Problem statement + +The hardening ledger already contains the data to answer questions nobody can currently ask of +it — which fingerprints recur, how often a rung holds after it lands, which classes escalate on +count rather than on a guard that failed. Reading those answers today means a human scanning 22 +rows by eye, which is how a recurrence count gets asserted from memory rather than from the +file. + +The row was parked behind a sample-size threshold, and this round crossed it: appending four +rows took the ledger from 18 to 22, past the stated 20. The fifth story of this round also takes +the story count to 10, crossing the other arm of the same trigger. The threshold existed so the +sample would say something about the workflow rather than about the last week, and it now does. + +### Conditions inherited from the source row + +From `todos.md`, "**P8 — passive metrics, read-only over the ledger and git.**": + +| Condition | Disposition | +|---|---| +| Analysis only — no new state file | **kept** — a metrics feature that writes state is a different, larger thing | +| No instrumentation | **kept** — the data already exists; collecting more is out of scope | +| Nothing written back | **kept** — the ledger stays append-only and human-authored | +| It answers questions the ledger already contains the data for — which fingerprints recur, how often a rung holds | **kept** as the scope statement | +| Trigger: 10 stories or 20 ledger rows, below which the sample says more about the last week than about the workflow | **moved** — the 20-row arm fired at 22 rows in the 2026-08-04 round, and the story arm reaches 10 with this story; recorded here | + +## 2. Desired outcome + +A reader can ask the ledger which fingerprints recur and how a rung has held, and get the answer +from the file rather than from recall — without the analysis writing anything back. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] The analysis reads the ledger and git and writes nothing — no new state file, no + instrumentation. +- [ ] A recurrence count it reports can be checked against the ledger by a reader who does not + trust it. +- [ ] What the analysis cannot answer from the ledger alone is stated, rather than left for a + reader to infer from what it does answer. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items, for any skill, command, agent + definition, hook message, or scaffolded template." Binds if the analysis ships as a command + or skill. +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" + Binds on the same condition. +- `## Don'ts` — "**Never describe what a gate proves without checking what it actually + compares.**" A metrics report is a claim about the ledger's contents, and a count stated + without naming what was counted is the shape this repo logs most often. + +## 5. Open questions + +- Where does the analysis live — a command, a script, or a documented reading recipe? The row + says analysis only, which rules out state but not a surface. +- What does it count, exactly? A fingerprint's recurrence count already drives escalation, and a + second number derived differently would give two answers to one question. + +## 6. Suggested size + +`story` — one read-only analysis over two existing sources, one spec → plan → PR. diff --git a/docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md b/docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md new file mode 100644 index 0000000..bc3fe2e --- /dev/null +++ b/docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md @@ -0,0 +1,63 @@ +# A §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the plugin — Story + +**Date:** 2026-08-04 · **Size:** story + +**Unprofiled, deliberately** — a split from a designed round, so it bypassed +`dev-workflow:intake`. A profile written now would look confirmed without being confirmed; +acceptance criterion 1 carries the debt instead. + +## 1. Problem statement + +`/workflow-init` scaffolds `CLAUDE.md` §5 from an inline template. When the plugin's §5 changes, +every previously scaffolded copy silently lags, and nothing in the scaffolded file tells its +reader so. One known-stale instance exists and is being re-synced by hand. + +### Conditions inherited from the source row + +From `todos.md`, "**Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags +the installed plugin.**": + +| Condition | Disposition | +|---|---| +| A semantic §5 locator is needed — `/workflow-init` may append the section renumbered, so "no §5 heading" can misread a valid section and append a duplicate | **kept** | +| Per-state merge semantics: invariant 9 forbids a silent overwrite, and "re-run init to sync" promises what the command cannot give | **kept** | +| Stamp cardinality: absent, duplicate, malformed | **kept** | +| The binding must be real on **every** push path; the version-bump coupling first proposed was false, since invariant 12's checker is `pull_request`-only | **kept** — the false coupling is recorded so it is not re-proposed | +| A stamp is a **wire format**: shipping a provisional one writes legacy into every scaffolded file | **kept** — it is why a provisional stamp is unacceptable | +| The one known-stale instance is being re-synced by hand, so this carries no schedule pressure | **kept** — the work is not urgent | +| Trigger: the next round that touches the §5 template | **moved** — fired by the 2026-08-03 round, recorded here | + +## 2. Desired outcome + +A reader of a scaffolded `CLAUDE.md` can tell whether its §5 matches the installed plugin's, +without comparing the two by hand. + +## 3. Acceptance criteria + +- [ ] Before design resumes on this story, whoever picks it up proposes both axes and the mode + derived from them, pauses for Daniel's confirmation, and writes the confirmed profile into + this header. Design continues only after that. +- [ ] A scaffolded `CLAUDE.md` whose §5 lags the installed plugin is detectable as such. +- [ ] Re-running `/workflow-init` on a stale file behaves per invariant 9 — it does not silently + overwrite accumulated content. +- [ ] Whatever binding the design chooses holds on every push path, not only on pull requests. + +## 4. Affected AGENTS.md invariants + +- `## Key invariants` → `### Prompts and scaffolding` — "8. **`/workflow-init`'s templates stay + inline** in the command body. Claude Code does not expand `${CLAUDE_PLUGIN_ROOT}` inside + command markdown (verified), and the cache path is not an API." +- `## Key invariants` → `### Prompts and scaffolding` — "9. **`/workflow-init` never overwrites + silently.**" +- `## Key invariants` → `### Prompts and scaffolding` — "11. **Prompt changes pass + `docs/prompt-standards.md`** — all 12 checklist items." +- `## Key invariants` → `### Packaging` — "12. **A plugin change requires a version bump.**" + +## 5. Open questions + +- How can a scaffolded `CLAUDE.md` tell its reader that it lags the installed plugin? + +## 6. Suggested size + +`story` — one stamp format and its detection, one spec → plan → PR. Not a chore: a wire format +shipped provisionally cannot be taken back. diff --git a/plugins/dev-workflow/.claude-plugin/plugin.json b/plugins/dev-workflow/.claude-plugin/plugin.json index 654ea02..7ce8394 100644 --- a/plugins/dev-workflow/.claude-plugin/plugin.json +++ b/plugins/dev-workflow/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "dev-workflow", "displayName": "Cross-Model Review Workflow", - "version": "0.8.0", + "version": "0.8.1", "description": "Spec-driven workflow with two independent cross-model review gates, an append-only hardening ledger with an escalation ladder, and repo-enforced quality. Requires the superpowers plugin.", "author": { "name": "Daniel Sänger", diff --git a/plugins/dev-workflow/CHANGELOG.md b/plugins/dev-workflow/CHANGELOG.md index d011622..659e881 100644 --- a/plugins/dev-workflow/CHANGELOG.md +++ b/plugins/dev-workflow/CHANGELOG.md @@ -22,6 +22,23 @@ unambiguously, still fails. Deleting only a plugin's *manifest* while the direct keeps shipping fails too. AGENTS.md invariant 12 carries the complete list. +## 0.8.1 + +- **Three sentences added to the §5 gate protocol, and to the template `/workflow-init` + scaffolds.** The Gate-B standing lens now asks what a diff changes the size, value or position + of, and to grep for where each is described elsewhere — asked as an open question alone it + missed three such statements in one cycle while being carried with unusual force. The Profiles + counterfactual now asks for both halves: name the observation that would exist if the claim + were false, and confirm the wiring could have produced it. The Gate-A pass procedure now asks + for a mechanical sweep before each read pass, inspecting quoted commands rather than running + them, since a command quoted in a spec may be destructive. +- **None of the three is a check.** Nothing runs the grep, tests whether a check could have + failed, or records that a sweep happened. They sharpen questions a reader asks; the ledger + rows say so rather than implying otherwise. +- **No skill changed.** The `harden-finding` guard-scope precheck — the change this round set out + to make — turned out to need decisions about durable records, ledger format and mid-run + collisions that a rule paragraph cannot carry. It is split to its own story. + ## 0.8.0 - **The gate hook reads the result of a gate call before counting it.** Until now it diff --git a/plugins/dev-workflow/commands/workflow-init.md b/plugins/dev-workflow/commands/workflow-init.md index ee704d1..7f35bb7 100644 --- a/plugins/dev-workflow/commands/workflow-init.md +++ b/plugins/dev-workflow/commands/workflow-init.md @@ -416,7 +416,12 @@ Add `/.context/codex-reviews/` to `.gitignore` — that entry specifically, not NO FINDINGS ``` - Each pass: validate, revise, re-run. (Large/high-risk artifact: optional focused + Each pass: validate, revise, re-run. Before each read pass, settle mechanically what the + artifact asserts and a machine can decide without side effects — cited paths, quoted + passages, stated counts, the syntax of standalone fenced blocks — because a read pass + spends expensive judgement on what a parser settles in seconds and misses it anyway, + inspecting quoted commands rather than running them, since a command quoted in a spec + may be destructive or an intentional failure. (Large/high-risk artifact: optional focused per-dimension passes on top.) - **Gate B — Code.** Tests green, before `git commit`. Tool: `mcp__codex__review` (args `instruction`, `whatWasImplemented`, `baseSha`; `reviewType: full` runs @@ -444,6 +449,10 @@ Add `/.context/codex-reviews/` to `.gitignore` — that entry specifically, not the file was correct until your change landed elsewhere. This lens is prompt text: it asks, nothing enforces the ask or validates the answer, and no comprehensive check covers arbitrary semantic drift. Ask anyway; in practice it is what surfaces them. + **Name what this diff changes the size, value or position of** — a list, a count, a + version, an identifier, a cited line — and grep for where each is described elsewhere, + because asked as an open question alone this lens missed three such statements in one + cycle while being carried with unusual force. **What counts as prose (the only Gate-B exemption).** Every staged path is explanatory documentation — `docs/**.md`, `README.md` → N/A. Those describe the @@ -532,7 +541,11 @@ A check need not be an automated test — where none is possible, a **named veri satisfies it and the entry says which route was taken and why. Either route owes the **counterfactual**: the observation against the prior state. An **unobservable counterfactual is a blocking evidence gap**, not a free pass — stop and surface; the human -may then lower the mode as a logged override. A fabricated test satisfies nothing. +may then lower the mode as a logged override. A fabricated test satisfies nothing. **Name +the observation that would exist if the claim were false, and confirm the wiring could have +produced it** — a check that supplies its own input, runs where the defect cannot appear, or +uses a fixture that never reaches the branch it covers reports success because of how it was +wired, not because the thing it checks succeeded. **The evidence entry lives in the commit body** (see Mechanics), carries the **story path and the named evidence but not the mode value**, and is **revalidated before every Gate-B diff --git a/todos.md b/todos.md index f0dfb8c..5a64a95 100644 --- a/todos.md +++ b/todos.md @@ -55,7 +55,11 @@ driven by recurrence rather than by enthusiasm. row now describes pre-0.8.0 counting behaviour as current. The 2026-07-20 *spec* took a version-qualified supersession note and that worked; the ledger needs the same convention written into its header, or an explicit "rows are historical, read the - newest row for current behaviour" statement. *Trigger: the next row falsified by a + newest row for current behaviour" statement. + **TRIGGER FIRED (2026-08-04):** the 2026-07-20 row now teaches pre-0.8.0 counting + behaviour as current — the second falsified row this trigger names. Story: + `docs/superpowers/stories/2026-08-04-hardening-ledger-supersession-story.md`. + *Trigger: the next row falsified by a later change — this is the second.* - [ ] **Locator selects the `text` element by RAW BYTE comparison of `type`.** A Unicode-escaped spelling of `text` is legal JSON meaning `text` and is not selected; @@ -178,6 +182,18 @@ driven by recurrence rather than by enthusiasm. throwaway index writes loose blobs/trees into the user's repo (verified: 3 → 5 objects per review). Unreachable, so gc collects them, but a temporary `GIT_OBJECT_DIRECTORY` with the real store as an alternate would avoid the churn. +- [ ] **The gate-claims Don't is correct and was not followed, twice.** PR #21's C4 (an + unqualified jq-parity criterion that outran what `field()` compares for a malformed outer + document) and C5 (a README claim that a typo cannot quietly unhook a gate) both fall + inside the 2026-07-19 `AGENTS.md` Don't, whose operative instruction already requires + exactly what they omitted — name the exact comparison the code performs, and delete any + part of the sentence that outruns it. **No textual repair exists**, which is why these are + parked rather than logged: a ledger row would have to name a hardening, and a rule needing + no change means the failure was compliance, not wording. The 2026-08-04 amendment covers + coverage enumerations (F8's shape) and reaches neither a positive parity claim nor a + positive prevention claim. *Trigger: a third compliance miss against that Don't, or a + feasible mechanical rung emerging from + `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`.* ## Next @@ -218,6 +234,14 @@ backlog. variable prevents the situation; the hook only recognizes today's spelling of it. The result-classification story documents it in `README.md` § Setup only, deliberately — a preflight check is a second surface and was kept out of that diff. + **OBSERVATION (2026-08-04), and the row stays open.** With the variable set to `0`, five + Gate-A passes of the 2026-08-03 round ran 458 s, 550 s, 757 s, 663 s and 780 s; all five + stayed in the foreground and returned ordinary `success: true` envelopes the hook could + read. **No control run was made** with the variable unset, so this is a correlation + observed under one setting, not a demonstration that the variable held those calls in the + foreground. What it does establish is that calls well past 120 s can return as ordinary + foreground results here. The row's deliverable — a `/workflow-init` preflight check — is + unbuilt, so the row is not discharged by this. *Trigger: after that story lands* (spec: `docs/superpowers/specs/2026-07-31-failed-codex-call-counts-as-a-pass-design.md`). @@ -245,7 +269,13 @@ backlog. - [ ] **P8 — passive metrics, read-only over the ledger and git.** Analysis only: no new state file, no instrumentation, nothing written back. It answers questions the ledger already contains the data for (which fingerprints recur, how often a rung - holds). *Trigger: 10 stories or 20 ledger rows* — below that the sample says more + holds). + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round appended four ledger rows, + taking the ledger from 18 to 22 and crossing the 20-row arm; its fifth story takes the + story count to 10, crossing the other arm as well. Story: + `docs/superpowers/stories/2026-08-04-passive-metrics-over-the-ledger-story.md`, which + carries this row's conditions with each marked kept, moved or dropped. + *Trigger: 10 stories or 20 ledger rows* — below that the sample says more about the last week than about the workflow. - [ ] **`/capture-finding` as an intake extension of `harden-finding`.** An extension, not a sibling command: a finding captured outside the ladder is how a ledger @@ -294,7 +324,12 @@ backlog. so; it does not cover *sequential cycles* reusing them. Note the dispositions and resume-note companions have the same property. Any fix has to keep the pre-call delete — that check is load-bearing — so it is about naming (a cycle component in the slot) or - archiving, not about relaxing the protocol. *Trigger: the next round touching the §5 + archiving, not about relaxing the protocol. + **NOT FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5 prose and its template + mirror, and changes no part of the §5 **file protocol** this row's trigger names — not the + slot names, not the pre-call delete, not the terminator or acceptance rules. Recorded so a + later reader can check the reading rather than re-derive it. + *Trigger: the next round touching the §5 file protocol.* - [ ] **Finding B — a §5 version stamp, so a scaffolded CLAUDE.md can tell it lags the installed plugin.** Split out of the canvas-findings round after two Gate-A passes @@ -307,16 +342,29 @@ backlog. invariant 12's checker is `pull_request`-only. A stamp is a **wire format**: shipping a provisional one writes legacy into every scaffolded file. *The one known-stale instance (canvas) is being re-synced by hand, so this carries no - schedule pressure.* *Trigger: the next round that touches the §5 template.* + schedule pressure.* + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits the §5 inline + template. Story: + `docs/superpowers/stories/2026-08-04-section-5-version-stamp-story.md`, which carries this + row's conditions with each marked kept, moved or dropped. + *Trigger: the next round that touches the §5 template.* - [ ] **`harden-finding`'s recurrence rule is scope-blind.** Rungs guard *scopes*; the - skill compares only *fingerprints*; a ledger-prose workaround is unenforceable - because agents follow the skill, not the row. Sketched fix — before proposing + skill's recurrence step *does* re-read the ledger, and the defect is that its + **decision branch** keys on the fingerprint and the latest matching row's rung + without letting that row's stated guard control the verdict. Sketched fix — before proposing escalation on a same-fingerprint recurrence, read the prior row's stated guard: **outside** it the prior mechanism never claimed that shape, so its rung did not fail — pick the fitting rung, do **not** escalate; **inside** it, the mechanism was meant to catch this and did not, so that is a regression to repair or strengthen. (An earlier draft had those branches inverted, which would have entrenched the bug - it was filed against; Gate A caught it.) *Trigger: the first human rejection of an + it was filed against; Gate A caught it.) + **Evidence case 3 (2026-08-04):** the 2026-08-03 hardening round ran the precheck as a + standing manual instruction from Daniel — which is this row's own diagnosis, since a rule + that exists only in chat is not one the skill carries — and still reached a wrong verdict + twice by reading a single prior row's guard and stopping. Split to + `docs/superpowers/stories/2026-08-04-harden-finding-guard-scope-precheck-story.md`; this + row stays open because the fix it sketches has not landed. + *Trigger: the first human rejection of an over-escalation the 2026-07-26 rows predicted, or the next round touching the skill.* - [ ] **Finding A — a route from a fixed finding to the ledger for projects that never open PRs.** The only mandated ledger check lives in `process-pr-review` step 5, so a @@ -331,7 +379,11 @@ backlog. needs real design (identity, dedup, consumption semantics), which is why it was refused as a mid-round addition. It mints `mandatory-step-anchored-to-optional-path` when it lands — minting it earlier would - leave a class no row uses. *Trigger: the next round that touches §5, or a project + leave a class no row uses. + **TRIGGER FIRED (2026-08-04):** the 2026-08-03 hardening round edits §5. Story: + `docs/superpowers/stories/2026-08-04-ledger-route-without-pull-requests-story.md`, which + carries this row's conditions with each marked kept, moved or dropped. + *Trigger: the next round that touches §5, or a project reporting an empty ledger across cycles that fixed findings.* - [ ] **Escalation trigger for the invariant checker — read this before patching it.** The checker asserts only the spellings its fixtures cover. Adding one more regex