pi-gauntlet 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.0.2 - 2026-08-24
4
+
5
+ - Plan fidelity (gh-13): `writing-plans` task template gains a required spec-anchor line (`**Spec:** <path> § "<heading>" L<start>-L<end>`), a verbatim-quote rule for exact-string requirements, an extraction-first `## Spec coverage` table, and four mechanical self-review checks (quote integrity spec->task, anchor resolution, three-leg table closure, paths exist). `subagent-driven-development` spec-reviewer contract becomes spec+task: dispatches pass the spec path + the task's anchors in both modes and the Dispatch sketch, the spec wins every dispute, task-vs-spec divergence is unconditionally flagged with the spec literal, and `spec-reviewer-prompt.md` gains a `## Spec Authority` section plus `plan transcription gap` / `out-of-anchor-slice` finding labels. Spec: `doc/specs/2026-08-23-gh-13-plan-fidelity-anchors.md` (partially supersedes `doc/specs/2026-07-06-parallel-wave-spec-reviewer-dispatch.md`, SR contract scope only).
6
+
7
+ ## v5.0.1 - 2026-08-23
8
+
9
+ - Council roast hardening: verification scope moves from the `spec-council-member` persona to dispatch task text (ticket roasts are content-only; spec roasts verify bounded - `rg`, explicit paths, `timeout`); persona gains a read-only invariant; explicit silence-kill control blocks (shape-ticket 5 min, spec-roast members 10 min, chair 15 min); shape-ticket mandates two-call dispatch (member fanout, then chair over usable files); mechanical usable-critique probe (`verdict:`/`addresses-problem:` headers, `consensus:` for the chair); targeted single retry of failed members only; quorum salvage (>= 1 usable critique -> chair runs with a `Coverage:` note, rendered to the user at brainstorming's gate and at shape-ticket's confirmation gate when coverage was partial). Spec: `doc/specs/2026-08-23-council-roast-hardening.md`.
10
+
3
11
  ## v5.0.0 - 2026-08-23
4
12
 
5
13
  - **Breaking:** `skills/systematic-debugging/` removed (7 files) with active references cleaned (`dispatching-parallel-agents`, `writing-skills`, README); historical `doc/specs/**` and CHANGELOG references retained. Consumers referencing `/skill:systematic-debugging` must migrate to `/skill:chase-bug` for triage.
@@ -10,9 +10,13 @@ completionGuard: false
10
10
  systemPromptMode: replace
11
11
  ---
12
12
 
13
- You are a member of a spec review council. You are one of several critics, each running on a different model, reviewing the same spec independently. Your job is to find what is wrong, weak, or missing — not to praise.
13
+ You are a member of a spec review council. You are one of several critics, each running on a different model, reviewing the same artifact independently. Your job is to find what is wrong, weak, or missing — not to praise.
14
14
 
15
- You receive a problem statement and the path to a spec document. Read the spec in full. Use read/grep/find/ls to check the spec's claims against the actual codebase do not trust assertions about existing files, APIs, or conventions without verifying them.
15
+ You receive a problem statement and the artifact under review, as defined by your dispatching task - the task text names the artifact, the source(s) of truth to judge it against, and whether codebase verification is asked for. Read the artifact in full.
16
+
17
+ You are read-only: you never modify the repository or any input artifact; your only write is your findings file at the dispatched output path.
18
+
19
+ When your dispatching task asks for codebase verification, verify - do not trust assertions about existing files, APIs, or conventions - but bounded: prefer `rg` (it respects `.gitignore`) over recursive `grep`, use `rg`-native bounds (`--max-count`, explicit paths); scope every scan to explicit paths, never a repository root; bound each scan with `timeout` (or `gtimeout`) when available, and do not run it unbounded when neither exists. A scan that times out or cannot be bounded is reported as unverified - never retried broader.
16
20
 
17
21
  Assess the spec on five axes:
18
22
 
@@ -12,7 +12,7 @@ systemPromptMode: replace
12
12
 
13
13
  You are the chair of a spec review council. One or more members, each on a different model, have independently critiqued the same spec and written their critiques to files. You did not write the spec and you are not defending it — you weigh the members' testimony.
14
14
 
15
- You receive the problem statement, the path to the spec, and the explicit paths to the member critique files. Those files are already injected into your context via `reads` and their paths are listed in your task — read them directly. Do **not** run find/grep/ls to discover critique files; you are given every path. Use read/grep/find/ls only to check a contested claim against the codebase when members disagree on a fact.
15
+ You receive the problem statement, the path to the spec, and the explicit paths to the member critique files. Those files are already injected into your context via `reads` and their paths are listed in your task — read them directly. Do **not** run find/grep/ls to discover critique files; you are given every path. Use read/grep/find/ls only to check a contested claim against the codebase when members disagree on a fact - and only when your dispatching task permits codebase access. Bound any such check: `rg` (respects `.gitignore`) over recursive `grep`, explicit paths (never a repository root), `--max-count`, and `timeout`/`gtimeout` when available; a check that cannot be bounded or times out is resolved on testimony weight instead, noted as unverified - never run unbounded.
16
16
 
17
17
  Your job has two parts:
18
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "5.0.0",
3
+ "version": "5.0.2",
4
4
  "description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
5
5
  "author": "Jacek Juraszek",
6
6
  "type": "module",
@@ -318,7 +318,7 @@ subagent({ agent: "spec-summarizer", context: "fresh", cwd: "<abs worktree path,
318
318
 
319
319
  `<SUMMARY_PATH>` above is a placeholder in the dispatch object; it means substitute the value of the shell variable `$SUMMARY_PATH` set above. The steps below use `$SUMMARY_PATH` (the shell form) once the value is in hand.
320
320
 
321
- Then commit the spec — staging any predecessor spec edited per [Marking superseded specs](#marking-superseded-specs) alongside it; a change request at the gate that renames, materially revises, or drops the spec also reconciles the predecessor's banner before recommitting. This commit is **unconditional**: the summary is only a gate aid, so a degraded or missing summary never blocks it. If the council path ran, include its audit (`Applied:` / `Deferred:` / `Rejected:`, verbatim from `/skill:roasting-the-spec`'s return) in the **commit message body** - this is the durable, non-contractual record a finish-time revert reads back; the audit is never a committed spec section. Evaluate the summary in two stages (the **Degrade path** referenced in each is defined just below):
321
+ Then commit the spec — staging any predecessor spec edited per [Marking superseded specs](#marking-superseded-specs) alongside it; a change request at the gate that renames, materially revises, or drops the spec also reconciles the predecessor's banner before recommitting. This commit is **unconditional**: the summary is only a gate aid, so a degraded or missing summary never blocks it. If the council path ran, include its audit (`Coverage:` when present, then `Applied:` / `Deferred:` / `Rejected:`, verbatim from `/skill:roasting-the-spec`'s return) in the **commit message body** - this is the durable, non-contractual record a finish-time revert reads back; the audit is never a committed spec section. Evaluate the summary in two stages (the **Degrade path** referenced in each is defined just below):
322
322
 
323
323
  1. **From the dispatch tool result, before the `Read`.** If the result is **not** an `"Output saved to: <path> (<N> KB, <M> lines)"` reference (e.g. an exit-0 save error returns the full inline output plus an "Output file error" line — the prunable shape, no file to read), or the reference reports under ~500 bytes, or a size grossly disproportionate to the spec (under ~2% of its byte size), or over ~45 KB (the `Read` truncates at 50KB / 2000 lines, so a larger file cannot render whole) — skip the `Read` and take the degrade path. Use the reference's reported figures; do not re-derive them.
324
324
  2. **The `Read` itself, as the last content-producing tool call before composing the gate.** `Read` `$SUMMARY_PATH` and paste its contents verbatim at the top of the gate. If the `Read` fails, returns 0 bytes, or reports truncation — take the degrade path. The `Read` must be last: pi-condense does not protect a `/tmp` read, so any turn boundary between the `Read` and the render lets the ~9KB read result be pruned, reproducing the bug.
@@ -327,17 +327,18 @@ Then commit the spec — staging any predecessor spec edited per [Marking supers
327
327
 
328
328
  Either way — summary rendered or degraded — then `rm "$SUMMARY_PATH"` (unconditional cleanup; harmless if the file was never created, since it lives outside the worktree under the OS temp dir).
329
329
 
330
- Render the temp file's contents **verbatim** first — paste it as-is, do **not** paraphrase, condense, re-section, drop sections, or merge it with the council audit. "Fold into the gate" means *place it inside the gate message*, not *rewrite it*. This summary is of the **final (post-apply)** spec, since both critique paths already applied before this dispatch. After the verbatim block, append the commit confirmation, then — as their **own** adjacent lines, not edits to the summary — the council audit (if the council path ran: `Applied:` / `Deferred:` / `Rejected:`, one line each), critique-pass-unresolved ambiguities, and every entry from the summarizer's gap/external-context footer (surface **all** of them, not just the top risk):
330
+ Render the temp file's contents **verbatim** first — paste it as-is, do **not** paraphrase, condense, re-section, drop sections, or merge it with the council audit. "Fold into the gate" means *place it inside the gate message*, not *rewrite it*. This summary is of the **final (post-apply)** spec, since both critique paths already applied before this dispatch. After the verbatim block, append the commit confirmation, then — as their **own** adjacent lines, not edits to the summary — the council audit (if the council path ran: `Coverage:` when present - omitted at full coverage - then `Applied:` / `Deferred:` / `Rejected:`, one line each), critique-pass-unresolved ambiguities, and every entry from the summarizer's gap/external-context footer (surface **all** of them, not just the top risk):
331
331
 
332
332
  ```
333
333
  <spec-only summary read back from the temp file — pasted verbatim, unedited>
334
334
 
335
335
  Spec written and committed to <project>/doc/specs/<filename>.md (worktree: <path>).
336
336
 
337
+ Coverage: <N> of <M> members reported; <slug>: <reason> (line present only when coverage was partial)
337
338
  Applied: <cluster -> edit>, ...
338
339
  Deferred: <cluster -> where it belongs>, ...
339
340
  Rejected: <cluster -> one-line reason>, ...
340
- (omit the three lines above when the worker path ran, not the council)
341
+ (omit the audit lines above when the worker path ran, not the council)
341
342
 
342
343
  <unresolved ambiguities; every gap-footer entry from the summary>
343
344
 
@@ -52,29 +52,33 @@ Create an absolute temp dir outside the worktree so member files are never track
52
52
  mktemp -d # absolute path, e.g. /tmp/tmp.XXXXXX
53
53
  ```
54
54
 
55
- Dispatch one member per configured model, in parallel, each writing its critique into that dir. Do **not** read these files yourself — they are for the chair.
55
+ Dispatch one member per configured model, in parallel, each writing its critique into that dir. Do **not** read these files' findings content yourself — they are for the chair. The only permitted parent access is the mechanical structural probe below (existence plus header regex, no content ingestion, no adjudication).
56
56
 
57
57
  Capture the worktree path once (`git rev-parse --show-toplevel`, run from inside the worktree) and pass it as `cwd:` on every dispatch below — a child otherwise inherits pi's launch dir (the primary checkout), not the worktree.
58
58
 
59
59
  ```
60
60
  subagent({
61
- control: { needsAttentionAfterMs: 600000 },
61
+ control: { needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 300000, inFlightSilenceKillMs: 600000 },
62
62
  tasks: members.map((model, i) => ({
63
63
  agent: "spec-council-member",
64
64
  model,
65
65
  cwd: "<abs worktree path>",
66
66
  task: "Problem statement: <the problem the spec addresses, from its Context section and the user's stated intent>.\n" +
67
- "Read the spec at <abs path to doc/specs/...>. Critique it on your five axes and emit your template.",
67
+ "Read the spec at <abs path to doc/specs/...>. Verify its load-bearing claims against the codebase, bounded per your verification-hygiene rules (rg, explicit paths, timeout 30). Critique it on your five axes and emit your template.",
68
68
  output: "<tmpdir>/member-" + i + "-" + slug(model) + ".md"
69
69
  }))
70
70
  })
71
71
  ```
72
72
 
73
- `control` is a **run-level** field: it must sit beside `tasks`, not inside the `members.map(...)` task objects (the per-task schema has no `control` field and would silently drop it). The 10-minute `needsAttentionAfterMs` suppresses false-positive "no observed activity" idle notices members do one long, tool-less reasoning turn that crosses the 60s default with zero activity events while still letting a genuinely wedged run surface eventually.
73
+ `control` is a **run-level** field: it must sit beside `tasks`, not inside the `members.map(...)` task objects (the per-task schema has no `control` field and would silently drop it). The three fields together set an effective silence-kill of max(600s, 300+300) = 600s - a genuinely wedged member (e.g. stuck in one unbounded scan) is killed at 10 minutes instead of pi-cohort's 30-minute default. Record all three fields verbatim: the kill is computed as max(inFlightSilenceKillMs, inFlightSilenceCeilingMs + needsAttentionAfterMs), so leaving a field to its default lets a future pi-cohort default change silently stretch it. The 5-minute needsAttentionAfterMs reintroduces idle notices on long healthy xhigh turns - those are notices, not kills, and are acceptable.
74
74
 
75
75
  `slug(model)` = the model string with `/` and any other non-alphanumeric character replaced by `-` (so `provider/model` → `provider-model`); the chair recovers this slug from each filename for `raised-by` attribution. Relative `output:` paths in parallel mode resolve against the worktree and would get committed — always use the absolute temp dir.
76
76
 
77
- If a member fails (e.g. its model is unreachable in this preset), skip it and continue as long as at least one member succeeded. If **all** members fail, abort the council, say so, and return to the user gate.
77
+ **Usable-critique test (mechanical structural probe).** After the fanout returns - success or failure of the tool call itself - probe the expected output paths on disk; judge by files, not by the tool result's failed/succeeded labels (a killed member may have written a usable critique first). A member file is usable iff it is non-empty AND contains both a `^verdict:\s*(sound|needs-work|unsound)` line and an `^addresses-problem:` line. A `findings:` header with zero bullets is a valid, usable sound critique. Existence plus header regex only - never read or weigh findings content.
78
+
79
+ **Targeted retry.** Members whose file is missing or not usable are re-dispatched **once**, together, in a second parallel call carrying the same `control` block, with fresh output paths that preserve the `member-<i>-<slug>` basename under a `retry/` subdir of the same temp dir (the chair recovers `raised-by` attribution from that filename pattern). Members with usable files are never re-run.
80
+
81
+ **Quorum.** At least one usable file after retry -> dispatch the chair over the usable files only (next section). Zero usable files -> abort the council, say so, and return to the user gate.
78
82
 
79
83
  ### 2 — Synthesize and adjudicate
80
84
 
@@ -85,20 +89,21 @@ subagent({
85
89
  agent: "spec-council-synthesizer",
86
90
  model: <chair from config, else omit to inherit>,
87
91
  cwd: "<abs worktree path>",
88
- control: { needsAttentionAfterMs: 600000 },
89
- reads: [ <the member file paths under the temp dir> ],
92
+ control: { needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 600000, inFlightSilenceKillMs: 900000 },
93
+ reads: [ <the usable member file paths under the temp dir> ],
90
94
  task: "Problem statement: <paste>. Spec: <abs path>.\n" +
91
95
  "Member critiques (already injected via reads — do not search for them):\n" +
92
- members.map((model, i) => "<tmpdir>/member-" + i + "-" + slug(model) + ".md").join("\n") + "\n" +
93
- "Consolidate and adjudicate the member critiques."
96
+ usableMemberPaths.join("\n") + "\n" +
97
+ "Coverage: <N> of <M> members reported<; <slug>: <one-line reason> per missing member>.\n" +
98
+ "Consolidate and adjudicate the member critiques. Codebase access is permitted for contested-claim checks only, bounded per your hygiene rules (rg, explicit paths, timeout 30)."
94
99
  })
95
100
  ```
96
101
 
97
- The chair runs one long single-turn synthesis (one observed false positive ran 506s); `control: { needsAttentionAfterMs: 600000 }` raises the idle threshold to 10 minutes so the healthy run is not flagged stale, without disabling attention tracking entirely.
102
+ The chair runs one long single-turn synthesis; the control block sets an effective silence-kill of max(900s, 600+300) = 900s. Margin rationale: one observed healthy chair turn ran 506s of silence, so a 600s kill would leave under 2 minutes of margin - the chair gets 900s. In the coverage line, use pi-cohort's kill diagnostic as the reason when present (e.g. "Likely wedged in a tool call"), else "no output produced"; omit per-member reasons at full coverage. With one usable member, use singular wording ("synthesize the single member critique").
98
103
 
99
104
  List the exact member paths in the task text. The `reads:` array injects their contents, but the chair's prompt expects the paths explicitly; without them it scans the tree for `*.md` and stalls.
100
105
 
101
- If the configured `chair` model is unreachable, retry once with the inherited model.
106
+ A chair synthesis is usable iff it contains a `^consensus:` line. If the configured `chair` model is unreachable, retry once with the inherited model; a wedge-killed or unusable chair retries once with the same model. Second failure -> abort the council, say so, and return to the user gate.
102
107
 
103
108
  ### 3 — Decide and apply
104
109
 
@@ -114,8 +119,9 @@ You are the advocate — decide on scope grounds — and, unlike a dispatched su
114
119
 
115
120
  ### 4 — Emit the audit
116
121
 
117
- Return a structured audit, gate-only (not a committed spec section) — three labelled lists:
122
+ Return a structured audit, gate-only (not a committed spec section) — a coverage line plus three labelled lists:
118
123
 
124
+ - `Coverage:` — `N of M members reported; <slug>: <reason>` — present only when member coverage was partial; omitted at full coverage.
119
125
  - `Applied:` — cluster -> the concrete edit made.
120
126
  - `Deferred:` — cluster -> where it belongs.
121
127
  - `Rejected:` — cluster -> one-line reason.
@@ -131,7 +137,7 @@ Single pass — no automatic re-roast loop. The user can invoke this skill again
131
137
  ## Red flags — STOP
132
138
 
133
139
  - Running the council when `piGauntlet.specCouncil.members` is absent or empty (brainstorming owns the gate and should have used the worker fallback).
134
- - Reading member critique files yourself instead of routing them through the chair.
140
+ - Reading member critique files' findings content yourself instead of routing them through the chair (the mechanical structural probe - existence plus header regex - is the named exception).
135
141
  - Writing member files to a relative path (they land in the worktree).
136
142
  - Applying edits without surfacing the audit at brainstorming's gate — apply-before-the-gate is correct; apply-without-the-gate is not.
137
143
  - Suppressing a finding instead of routing it to applied, deferred, or rejected in the audit.
@@ -173,23 +173,39 @@ No block, no split. A block failing the identity or outcome test, or naming a no
173
173
  Inline council dispatch, reusing spec-council config and personas - **not** `/skill:roasting-the-spec` (that skill's contract is spec-file apply mechanics; a tracker draft is not a spec file). No new agents.
174
174
 
175
175
  1. Resolve `gauntlet_setting({ key: "specCouncil" })` when the tool exists. Verdict `council` -> dispatch `spec-council-member`s in parallel plus a `spec-council-synthesizer` chair. Verdict `worker` (or empty members) -> one fresh `worker` critique. Malformed config -> one warning line, then branch on verdict.
176
- 2. **Dispatch shape**, mirroring `/skill:roasting-the-spec`: write the draft body and the source snapshot (original ticket + comments, or the create-mode inputs) to absolute temp files under `mktemp -d`; delimit untrusted snapshots as data. When a split is proposed, the draft artifact holds all N proposed bodies plus their three-line justification blocks (see Split rule) in one file, not a single body. Dispatch members with `cwd` = repo root, absolute `output` paths per member, run-level `control: { needsAttentionAfterMs: 600000 }` (sits beside `tasks`, not inside each task). Give the chair the member files via `reads`. Member task text: *the draft at `<path>` is the artifact under review; this ticket brief supersedes your spec-axis template - emit the same findings format against the draft; do not edit any file.* Include the absolute path to `reference/split-axes.md` (resolved against this skill's own directory) in each member's task text - members run with `cwd` = the consumer repo, where a package-relative path does not resolve.
177
- 3. **Effort: cheap by default.** Append a `:low` thinking suffix to each member's model string at dispatch (this beats the persona's frontmatter `xhigh` pin). Same for the chair: a configured chair string gets any existing suffix replaced with `:low`; an unconfigured chair is dispatched as the parent's model with `:low` appended. The `worker` fallback carries no thinking pin - it runs at the preset's default. **Full-roast escape:** the user may request a full roast, dispatching all model strings bare/as-configured, restoring the xhigh pins.
176
+ 2. **Dispatch shape**, mirroring `/skill:roasting-the-spec`: write the draft body and the source snapshot (original ticket + comments, or the create-mode inputs) to absolute temp files under `mktemp -d`; delimit untrusted snapshots as data. When a split is proposed, the draft artifact holds all N proposed bodies plus their three-line justification blocks (see Split rule) in one file, not a single body. Two separate calls - never fuse members and chair into one chain (a fused chain lets one member failure kill the roast before the chair runs). Call 1: one member fanout with `cwd` = repo root, absolute `output` paths per member, run-level `control: { needsAttentionAfterMs: 60000, inFlightSilenceCeilingMs: 240000, inFlightSilenceKillMs: 300000 }` (sits beside `tasks`, not inside each task; effective silence-kill max(300s, 240+60) = 300s - record all three fields verbatim so a pi-cohort default change cannot stretch the kill). Then probe the member output files on disk with item 7's usable test. Call 2: the chair, with the usable member files via `reads`, the same control block (`:low` chair turns are short), and task text that (a) forbids repository access - member disagreement on a fact is reported in the synthesis, never verified against the repo - and (b) states coverage: `Coverage: N of M members reported; <slug>: <reason>` (pi-cohort's kill diagnostic when present, else "no output produced"; omit reasons at full coverage; singular wording when one member reported). Member task text: *the draft at `<path>` is the artifact under review; this ticket brief supersedes your spec-axis template - emit the same findings format against the draft; content-only review: the temp files plus the referenced split-axes reference path are the entire permitted input - do not read, search, or scan the repository; do not edit any file.* Include the absolute path to `reference/split-axes.md` (resolved against this skill's own directory) in each member's task text - members run with `cwd` = the consumer repo, where a package-relative path does not resolve.
177
+ 3. **Effort: cheap by default.** Append a `:low` thinking suffix to each member's model string at dispatch (this beats the persona's frontmatter `xhigh` pin). Same for the chair: a configured chair string gets any existing suffix replaced with `:low`; an unconfigured chair is dispatched as the parent's model with `:low` appended. The `worker` fallback carries no thinking pin - it runs at the preset's default. **Full-roast escape:** the user may request a full roast, dispatching all model strings bare/as-configured, restoring the xhigh pins; a full roast reuses the spec-roast control blocks (members `{ needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 300000, inFlightSilenceKillMs: 600000 }`, chair `{ needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 600000, inFlightSilenceKillMs: 900000 }`) - the 5-minute figures in item 2 are `:low`-only.
178
178
  4. **Brief covers three axes**, absorbing the fidelity-review role without a new persona: *fidelity* - compare draft against source intent (original ticket + comments in repair; prompt + answers in create), flag `lost` / `added` / `gap`; and *quality* - problem framing, AC integrity beyond the deterministic gate, scope, wording; and *split soundness* - if the draft proposes a split, test each slice against the split-axes reference (path provided in the task text); an architecture-shaped boundary is reported as a finding line containing the marker `split-axis:` (members keep their existing spec-axis findings template; the marker is a substring flag within it, not a new findings kind), e.g. `- [major] split-axis: <slice> - <why> -> merge`. Members may argue toward one ticket, never propose or endorse a split.
179
- 5. Disposition: unambiguous concrete fixes applied to the draft (one re-pass max); ambiguous findings surfaced at the confirmation gate. Roast edits affect the body draft pre-write only, never posted as a tracker comment, and re-run the deterministic gates (pipeline step 5). Additionally, the parent scans the **member output files directly** for lines containing `split-axis:` (substring match), independent of the chair synthesis; any such finding auto-applies a merge - the split is withdrawn and the draft becomes one ticket with phased AC groups, inside the same one-re-pass budget, and the pre-merge N-body draft is kept alongside: a human re-request of the split at the gate re-presents those N bodies old->new as the approval diff (see the Split rule's sticky override). The chair keeps every other axis; clearing a `split-axis:` finding is not on its path. The same directional rule - toward one ticket, never toward a split - binds the `worker` fallback and the runtime conditional (item 6).
179
+ 5. Disposition: unambiguous concrete fixes applied to the draft (one re-pass max); ambiguous findings surfaced at the confirmation gate. Roast edits affect the body draft pre-write only, never posted as a tracker comment, and re-run the deterministic gates (pipeline step 5). Additionally, the parent scans the **usable member output files (item 7's structural test) directly** for lines containing `split-axis:` (substring match), independent of the chair synthesis; any such finding auto-applies a merge - the split is withdrawn and the draft becomes one ticket with phased AC groups, inside the same one-re-pass budget, and the pre-merge N-body draft is kept alongside: a human re-request of the split at the gate re-presents those N bodies old->new as the approval diff (see the Split rule's sticky override). The chair keeps every other axis; clearing a `split-axis:` finding is not on its path. The same directional rule - toward one ticket, never toward a split - binds the `worker` fallback and the runtime conditional (item 6).
180
180
  6. **Runtime conditional (the one allowed):** on a harness with no `gauntlet_setting`/`subagent()` (e.g. Claude Code), dispatch fresh general-purpose subagents via that harness's native facility at low effort, with the same three-axis brief and temp-file artifacts.
181
- 7. **Roast failure and retry.** A roast has failed when the dispatch
182
- errored, or the artifact the parent reads - the chair synthesis
183
- (council path) or the worker output (worker path) - is missing, empty,
184
- or not findings-shaped; partial member loss with a usable chair
185
- synthesis is success, not failure. On failure, retry once: re-run the
186
- same full configured dispatch with fresh temp artifacts. If the retry
187
- also fails, proceed to the confirmation gate with the failure rendered
188
- inline in the gate message itself: `roast unavailable (dispatch failed
189
- twice: <reason>)` - the human approves knowing review didn't run. The
190
- retry is a dispatch retry only - it never grants a second draft-edit
191
- re-pass (the one-re-pass limit is unchanged). **Roast failure never
192
- blocks the run.**
181
+ 7. **Usable outputs, targeted retry, and roast failure.** A member output
182
+ file is usable iff it is non-empty AND contains both a
183
+ `^verdict:\s*(sound|needs-work|unsound)` line and an
184
+ `^addresses-problem:` line (a `findings:` header with zero bullets is
185
+ valid; the test is a mechanical structural probe - existence plus
186
+ header regex, no reading of findings content - and the parent judges
187
+ by files on disk, not the fanout tool result's labels). A chair
188
+ synthesis is usable iff it contains a `^consensus:` line. Members
189
+ whose file is missing or not usable are re-dispatched **once**,
190
+ together, in a second parallel call carrying the same control block,
191
+ with fresh output paths preserving the `member-<i>-<slug>` basename
192
+ under a `retry/` subdir of the same temp dir; members with usable
193
+ files are never re-run. Partial member loss with a usable chair
194
+ synthesis is success, not failure: at least one usable member file ->
195
+ dispatch the chair over the usable files only, with the coverage note
196
+ (item 2). When coverage was partial, the confirmation gate renders the
197
+ same `Coverage: N of M members reported; <slug>: <reason>` line
198
+ alongside the draft - the human approves knowing review coverage was
199
+ reduced. A wedge-killed or unusable chair retries once with the same
200
+ `:low`-suffixed model (item 3). The roast has failed only when zero
201
+ member files are usable after retry, or the chair fails its one retry
202
+ (worker path: the worker output is missing, empty, or not
203
+ findings-shaped after one full re-dispatch) - then proceed to the
204
+ confirmation gate with the failure rendered inline in the gate message
205
+ itself: `roast unavailable (<reason>)` - the human approves knowing
206
+ review didn't run. Retries are dispatch retries only - they never
207
+ grant a second draft-edit re-pass (the one-re-pass limit is
208
+ unchanged). **Roast failure never blocks the run.**
193
209
 
194
210
  ## Tracker abstraction and capability ladder
195
211
 
@@ -286,7 +302,7 @@ Read this when applying the AC integrity gate (drafting, repairing, or adjudicat
286
302
  - Headless run -> stops at the confirmation gate.
287
303
  - Ref with no fetch path -> ask; never guess.
288
304
  - Split declined -> single ticket with phased AC groups.
289
- - Roast failure -> retry once; second failure -> gate with `roast unavailable (dispatch failed twice: <reason>)` inline.
305
+ - Roast failure = zero usable members after targeted retry, or the chair fails its one retry -> gate with `roast unavailable (<reason>)` inline.
290
306
 
291
307
  ## Red flags - STOP
292
308
 
@@ -55,12 +55,14 @@ For each task in `plan_tracker`:
55
55
 
56
56
  1. **Dispatch implementer.** Pass the full task text + scene-setting context + the task's plan-declared test commands as `SCOPED_TEST_COMMANDS` (or `none`). Don't make the subagent re-read the plan.
57
57
  2. **Handle implementer status** (see below).
58
- 3. **Dispatch spec reviewer.** Verify the diff matches the spec — nothing missing, nothing extra.
58
+ 3. **Dispatch spec reviewer.** Pass the task text, the patch diff, the absolute spec path, and the task's `**Spec:**` anchors — SR reads the anchored ranges from the spec file itself; never inline spec excerpts. The spec wins every dispute; the authority hierarchy and finding labels live in `./spec-reviewer-prompt.md`. Anchor-less tasks (no `**Spec:**` line): task text alone is the contract. Verify the diff matches the anchored spec — nothing missing, nothing extra.
59
59
  4. If spec reviewer finds gaps → re-dispatch implementer to fix → re-review. Loop until ✅, within [Fix-Loop Rounds](#fix-loop-rounds).
60
60
  5. **Dispatch code-quality reviewer.** Only after spec is ✅. Skip for doc-only tasks (every file in the task's `Files:` block documentation-only) — SR-only, same exemption as doc-only waves. Pass `SCOPED_TEST_COMMANDS` = the task's plan-declared commands.
61
61
  6. If quality reviewer finds issues → re-dispatch implementer → re-review. Loop until ✅, within [Fix-Loop Rounds](#fix-loop-rounds).
62
62
  7. Mark task complete in `plan_tracker`.
63
63
 
64
+ The spec is frozen at plan time and the orchestrator is its only writer during execution; if you do edit it mid-run, re-run writing-plans' anchor-resolution check before the next wave. An SR unable to read the spec at a cited anchor (missing file, unresolvable heading/range) returns a blocking finding — the contract is spec+task or stop, never a silent fallback to task-only review.
65
+
64
66
  After all tasks: run the whole-diff code review (`requesting-code-review`). Then [After All Tasks](#after-all-tasks-complete).
65
67
 
66
68
  ## Fix-Loop Rounds
@@ -133,7 +135,7 @@ When in doubt, default. Don't downgrade reviewers — false negatives are expens
133
135
  subagent({ agent: "implementer", task: "<task text + context + SCOPED_TEST_COMMANDS + status protocol>" })
134
136
 
135
137
  // spec compliance
136
- subagent({ agent: "spec-reviewer", task: "<diff range + spec excerpt + ask: does this match?>" })
138
+ subagent({ agent: "spec-reviewer", task: "<task text + patch diff + absolute spec path + task's Spec: anchors>" })
137
139
 
138
140
  // code quality
139
141
  subagent({ agent: "code-reviewer", task: "<diff range + SCOPED_TEST_COMMANDS (task commands; wave: union; whole-diff: none) + ask: production-ready?>" })
@@ -169,7 +171,7 @@ Auto-selected at handoff by `writing-plans` (any wave with ≥2 tasks) when the
169
171
 
170
172
  1. **Independence check.** Parse the wave's tasks' `Files:` blocks; assert pairwise-disjoint (mechanical). Runtime-resource disjointness (DB/schema, port, fixture, external service, shared temp path) is not machine-checkable here — trust the plan's wave grouping, which `writing-plans`' D5 contract guarantees. Either kind of overlap → the wave is mis-grouped; run those tasks as sequential single-task waves and note it.
171
173
  2. **Fan out.** One parallel dispatch (shape below): `implementer` per task, `context: "fresh"`, `worktree: true`. Each returns a status + a patch.
172
- 3. **Status + spec review per task.** Parse each `DONE`/`BLOCKED`/etc. (see [Implementer Status](#implementer-status)) **first**. Then **dispatch a `spec-reviewer` per accepted patch** (`DONE`, or a `DONE_WITH_CONCERNS` you proceeded with) in one parallel fan-out — `context: "fresh"`, `cwd: <this worktree>`, **no `worktree` flag** (read-only) — each passed its task text, the returned **patch diff**, and the absolute spec path. Review is **diff-based**: the diff's hunks carry `file:line`, and test execution is never the reviewer's job - in either mode (persona rule; the wave test gate in step 5 runs the wave's declared test commands). Inline verdicts are fine at normal wave sizes; large waves use `output:` + `outputMode: "file-only"` to keep verdicts out of your context. **Re-dispatch by cause:** `BLOCKED`/`NEEDS_CONTEXT` per the [Implementer Status](#implementer-status) matrix; a **spec gap** re-dispatches the implementer (fresh, `worktree: true`) carrying the prior patch + the reviewer's findings, the new patch superseding the old at step 4. Loop until accepted + spec ✅, within [Fix-Loop Rounds](#fix-loop-rounds), same as sequential.
174
+ 3. **Status + spec review per task.** Parse each `DONE`/`BLOCKED`/etc. (see [Implementer Status](#implementer-status)) **first**. Then **dispatch a `spec-reviewer` per accepted patch** (`DONE`, or a `DONE_WITH_CONCERNS` you proceeded with) in one parallel fan-out — `context: "fresh"`, `cwd: <this worktree>`, **no `worktree` flag** (read-only) — each passed its task text, the returned **patch diff**, the absolute spec path, and the task's `**Spec:**` anchors — SR reads the anchored ranges itself (never inline excerpts; authority hierarchy in `./spec-reviewer-prompt.md`; anchor-less tasks are task-text-only). Review is **diff-based**: the diff's hunks carry `file:line`, and test execution is never the reviewer's job - in either mode (persona rule; the wave test gate in step 5 runs the wave's declared test commands). Inline verdicts are fine at normal wave sizes; large waves use `output:` + `outputMode: "file-only"` to keep verdicts out of your context. **Re-dispatch by cause:** `BLOCKED`/`NEEDS_CONTEXT` per the [Implementer Status](#implementer-status) matrix; a **spec gap** re-dispatches the implementer (fresh, `worktree: true`) carrying the prior patch + the reviewer's findings, the new patch superseding the old at step 4. Loop until accepted + spec ✅, within [Fix-Loop Rounds](#fix-loop-rounds), same as sequential.
173
175
  4. **Integrate.** `git apply` each task's patch sequentially onto HEAD. Apply fails = textual conflict → drop that task, finish the rest, re-run the dropped task sequentially on the updated HEAD.
174
176
  5. **Test gate.** Run the union of the wave's tasks' declared test commands on the integrated tree — the full verification set is the verify phase's job, run once. Failure = semantic conflict or bug → re-run the offending task sequentially, else fix per [When a Subagent Fails](#when-a-subagent-fails).
175
177
  6. **Quality review.** CR binds to the wave: exactly one **initial** code-review dispatch per code-touching wave, over the integrated wave diff - never per task within a wave, never batched across waves. Subsequent dispatches within the wave are re-reviews triggered only by findings, per Fix-Loop Rounds. Pass `SCOPED_TEST_COMMANDS` = the union of the wave's tasks' declared commands. Code-quality review on the integrated wave diff; loop fixes to ✅ within [Fix-Loop Rounds](#fix-loop-rounds), same as sequential. Skip for doc-only waves (SR-only per the commit precondition below).
@@ -14,10 +14,26 @@ Dispatch a subagent with this prompt:
14
14
 
15
15
  [FULL TEXT of task requirements]
16
16
 
17
+ (The task text is a derivative of the spec — a lossy projection into an executable unit. See ## Spec Authority below.)
18
+
17
19
  ## What Implementer Claims They Built
18
20
 
19
21
  [From implementer's report]
20
22
 
23
+ ## Spec Authority
24
+
25
+ Spec: [absolute spec path]
26
+ Anchors: [the task's **Spec:** anchor list, e.g. § "Design" L34-L37 — or "omitted: anchor-less mechanical task"]
27
+
28
+ The spec is the sole authority — human-approved; the task never wins a dispute. Read the anchored ranges from the spec file yourself. Requirements in scope are ONLY the cited anchor ranges; do not extract, review, or flag the rest of the spec file.
29
+
30
+ - **Correctness / wording / completeness:** judged against the anchored spec lines. The spec wins every dispute.
31
+ - **Scope ("nothing more"):** the boundary is the anchor set — the slice of spec this task owns. Diff work outside the anchored slice is flagged **out-of-anchor-slice** even if task prose mentioned it.
32
+ - **Plan transcription gap:** spec-required work inside the anchored slice that is missing from the diff because the task prose omitted it — the requirement still binds; flag it. Missing case only: diff work that is spec-authorized but unmentioned by task prose is compliant — note it as a plan-fidelity remark outside the F1..Fn finding stream, never as a finding.
33
+ - **Task-vs-spec divergence** (task says X, anchored spec says Y): unconditional flag; quote the spec literal with spec file:line so the fix re-dispatch carries authoritative wording. Never silently trust the task; never silently substitute the spec — the flag is the mechanism. Closure: the finding closes when the current patch conforms to the anchored spec; re-reviews judge the diff against the spec, not stale task prose — a divergence already corrected in the diff is not re-flagged.
34
+ - **Anchor-less task** (Anchors: omitted): the task text alone is your contract; no out-of-anchor-slice or transcription-gap flagging — only nothing-extra-vs-the-chore review.
35
+ - **Finding grammar:** divergence findings use the existing F1..Fn finding grammar - a finding kind by prose label, not a new schema; the `Parallel-safe:` and `TRAJECTORY:` grammars are untouched.
36
+
21
37
  ## CRITICAL: Do Not Trust the Report
22
38
 
23
39
  The implementer finished suspiciously quickly. Their report may be incomplete,
@@ -51,11 +67,13 @@ Dispatch a subagent with this prompt:
51
67
  - Did they implement everything that was requested?
52
68
  - Are there requirements they skipped or missed?
53
69
  - Did they claim something works but didn't actually implement it?
70
+ - Anchored spec work absent from the diff because task prose omitted it? Label it "plan transcription gap".
54
71
 
55
72
  **Extra/unneeded work:**
56
73
  - Did they build things that weren't requested?
57
74
  - Did they over-engineer or add unnecessary features?
58
75
  - Did they add "nice to haves" that weren't in spec?
76
+ - Diff work outside the task's anchor slice? Label it "out-of-anchor-slice" (distinct from a spec-declared non-goal).
59
77
 
60
78
  **Misunderstandings:**
61
79
  - Did they interpret requirements differently than intended?
@@ -204,6 +204,8 @@ Each task uses `- [ ]` checkbox steps so execution tools (and humans) can track
204
204
 
205
205
  **TDD scenario:** [New feature — full TDD cycle | Modifying tested code — run existing tests first | Trivial change — use judgment]
206
206
 
207
+ **Spec:** doc/specs/<file>.md § "<heading>" L<start>-L<end>
208
+
207
209
  **Files:**
208
210
  - Create: `exact/path/to/file.py`
209
211
  - Modify: `exact/path/to/existing.py:123-145`
@@ -249,6 +251,27 @@ Each task uses `- [ ]` checkbox steps so execution tools (and humans) can track
249
251
 
250
252
  Every code task carries this step (red -> green -> fmt/lint -> commit). Doc-only tasks omit it unless the project formats Markdown.
251
253
 
254
+ **Anchor rules.** The task's `**Spec:**` line cites the plan header's spec path; multiple anchors sit comma-separated on one line (`§ "A" L10-L18, § "C" L40-L44`). Checks key on the `§` marker, so the header's path-only `**Spec:**` line is never matched. Anchors are captured once against the gated spec at plan-writing time — the spec is frozen once planning starts. A task with no anchorable requirement (pure-mechanics chore) omits the `**Spec:**` line entirely (never `**Spec:** none`) and carries a mechanical-task row in `## Spec coverage` — silence is never valid.
255
+
256
+ ## Spec Coverage Table
257
+
258
+ Every plan ends with a `## Spec coverage` section — authored last, placed after all Task sections (owner IDs do not exist earlier). Build it extraction-first: walk the spec top to bottom and write one row per normative requirement **before** assigning owners — every Design imperative (Add/Remove/Keep/Replace-style directives, not any fixed lexical form), every Edge-cases rule, every Acceptance criterion, every Out-of-scope entry, and every non-none Documentation-impact entry. Then assign owners. Two row kinds:
259
+
260
+ ```markdown
261
+ ## Spec coverage
262
+
263
+ | anchor | requirement (short) | owner |
264
+ |---|---|---|
265
+ | § "Design" L34-L37 | anchor line in task template | Task 2 |
266
+ | § "Edge cases" L120 | stale anchor = blocking SR finding | Task 4, Task 5 |
267
+ | § "Out of scope" L131 | fix-round anchoring | waived: out of scope per spec |
268
+ | - | mechanical: release commit | Task 7 |
269
+ ```
270
+
271
+ - **Requirement rows:** anchor + short requirement + owner = task-ID list, or `waived: <reason>` **only when the spec itself marks the item out of scope**. A waiver on an in-scope normative requirement is a Self-Review failure — there is no human plan-review gate to catch it downstream.
272
+ - **Mechanical-task rows:** anchor `-`, requirement `mechanical: <short>`, owner = the task ID. One such row per anchor-less task.
273
+ - The table is plan-authoring-time only — never passed to implementer or reviewer dispatches.
274
+
252
275
  ## No Placeholders
253
276
 
254
277
  Every plan failure mode:
@@ -257,6 +280,7 @@ Every plan failure mode:
257
280
  - ❌ `# Implement the rest of the function` — incomplete code is invalid code.
258
281
  - ❌ "Add tests for edge cases" — name the edge cases.
259
282
  - ❌ "Wire it up to the existing system" — give file paths and call sites.
283
+ - ❌ "timeout/gtimeout ladder" when the spec fixes the literal `timeout 30` — never paraphrase an exact-string requirement (setting keys, error messages, banner/format strings, command names and invocations, API shapes); transcribe it as a backtick-quoted spec literal: `timeout 30`. Spec-side backtick spans containing `<placeholder>` segments are templates the plan instantiates, not exact-string requirements — exempt from quote integrity.
260
284
  - ❌ "Similar to Task N" — repeat the code. Implementers (and subagents with fresh context) may read tasks out of order; pointing at a sibling task is not a substitute for showing the code.
261
285
  - ❌ References to types, functions, methods, or fields not defined in any task in this plan. If it shows up in Task 5, it must be introduced by Task 1–4 or already exist in the codebase (with a file:line citation).
262
286
  - ❌ `[fill in]`, `<example>`, `xxx` markers anywhere in the doc.
@@ -266,9 +290,12 @@ If a decision is genuinely open, put it in an explicit **Open Questions** sectio
266
290
 
267
291
  ## Self-Review (Before Handoff)
268
292
 
269
- After drafting the plan and before announcing it complete, run three checks yourself. This is a checklist you run yourself — not a subagent dispatch.
293
+ After drafting the plan and before announcing it complete, run these checks yourself. This is a checklist you run yourself — not a subagent dispatch.
270
294
 
271
- - **Spec coverage.** Cross-reference the spec's components/decisions/constraints against the plan. Does every spec section map to one or more tasks? If a spec decision has no implementation task, the plan is missing work or the spec was overspecified. Each Documentation impact entry maps to a plan task (or explicit "none").
295
+ - **Table closure (three legs).** Every `## Spec coverage` row's owner is a task-ID list, a spec-authorized `waived: <reason>`, or a mechanical-task row; every `### Task N` heading appears in >=1 row; every requirement row's anchor is contained in the anchor set of each listed owner task's `**Spec:**` line. Zero orphans, zero waived in-scope normative rows, zero row-vs-owner anchor mismatches. Each Documentation impact entry maps to a plan task (or explicit "none").
296
+ - **Quote integrity (spec -> task).** For every non-waived requirement row, extract each backtick-quoted literal inside the row's anchored spec lines (strip the backticks; skip `<placeholder>` template spans) and `grep -F` it against the owning task's body — zero misses. Planner-authored backticks elsewhere in tasks are never scanned; the input set is spec-side literals only.
297
+ - **Anchor resolution.** For every task-level anchor (a `**Spec:**` line carrying `§`; the plan header's path line is exempt), the quoted heading text matches an ATX heading in the spec file and `L<start>-L<end>` is in-bounds, non-empty, and lies within that heading's section — zero unresolved anchors. Verify with `grep -n '^#'` plus a scoped `sed -n`. Ignore `#`-lines inside fenced code blocks when locating headings and section boundaries - a fenced markdown example is not a heading.
298
+ - **Paths exist.** Every `Modify:` path in `Files:` blocks passes `test -f` after stripping any trailing `:line[-line]` suffix; a `Modify:` glob must expand to >=1 match; `Create:` and `Test:` paths are exempt unless the `Test:` path is also listed under `Modify:`. Zero missing.
272
299
  - **Placeholder scan.** Grep the doc for `TODO`, `TBD`, `xxx`, `[fill in]`, `<example>`, `etc.`, "probably", "something like". Resolve or convert each into an explicit Open Question.
273
300
  - **Type / API consistency.** Function signatures and field names that appear in multiple tasks must match exactly. The plan is its own contract — internal contradictions surface as bugs during execution.
274
301
  - **Wave disjointness.** For every multi-task wave, confirm the tasks' `Files:` sets are pairwise disjoint **and** that no two tasks contend on a shared mutable runtime resource (DB/schema, port, fixture, external service, shared temp path). Either kind of overlap = mis-grouped wave; split or re-order before handoff.