pi-gauntlet 5.3.2 → 5.3.3
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 +4 -0
- package/README.md +2 -2
- package/agents/conformance-reviewer.md +6 -4
- package/agents/spec-council-member.md +12 -1
- package/agents/spec-council-synthesizer.md +6 -1
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +1 -1
- package/skills/roasting-the-spec/SKILL.md +8 -2
- package/skills/shape-ticket/SKILL.md +5 -5
- package/skills/verification-before-completion/reference/conformance-check.md +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v5.3.3 - 2026-09-07
|
|
4
|
+
|
|
5
|
+
- Subtractive review pass: `spec-council-member` gains the `over-spec` finding kind (three-leg predicate: outside the problem, unrequested by human input, unnecessary to deliver; never cuts verbatim human input) and a mandatory `lean:` close line; `spec-council-synthesizer` preserves `over-spec:` clusters, adjudicates them against leg-3 rebuttals only, and tallies `lean:`. `roasting-the-spec` passes the verbatim human input to members and chair, requires `^lean:` in both probes, and applies an accepted over-spec cluster as a cut with an `Applied: over-spec: ... -> cut (was adds: ...)` audit line; `shape-ticket`'s probe follows. Closure: `conformance-reviewer` reports spec-laundered excess once as `UNAUTHORIZED` (origin literal unchanged; provenance in `evidence:`), and `UNAUTHORIZED` rows now follow `recommended:` like every other verdict - contained removals auto-run in the fix loop with the spec path added to `touched-files`. `scripts/ci.mjs` pins the new tokens and the retired always-defer rule. Spec: `doc/specs/2026-09-06-subtractive-review-pass.md`.
|
|
6
|
+
|
|
3
7
|
## v5.3.2 - 2026-09-06
|
|
4
8
|
|
|
5
9
|
- Plan coverage: `## Spec coverage` rows accept the exact owner `Verification` for requirements the plan header's `**Verification:**` command proves. `table-closure` closes such rows (mechanical row, unparseable anchor, or literal-free anchor -> finding; never counted as task coverage); `quote-integrity` resolves their literals by verbatim containment in the backtick-stripped header. Orphan-owner reason now names `Verification`; anchor-parseability is checked once per row. `header-entrypoint` unchanged. `writing-plans` documents the owner form with a scoped-tests / final-suite example. Spec: `doc/specs/2026-09-06-gh-24-final-verification-ownership.md` (closes #24).
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ Concretely, one change through the gauntlet:
|
|
|
39
39
|
1. You describe the change. **`brainstorming`** sets up an isolated worktree, explores the codebase, and turns your description into a written spec. A multi-model critique runs on it automatically. If the spec replaces a known prior spec, brainstorming marks the predecessor with a `> **Superseded by:**` banner under its title (default format, syntax overridable via `.pi/gauntlet-overrides.md`; event-driven only — gauntlet never sweeps historical specs). **You read and approve the spec - human gate 1.** No implementation code exists yet.
|
|
40
40
|
2. **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files.
|
|
41
41
|
3. **`subagent-driven-development`** executes the plan one task at a time, each in a fresh subagent, behind spec-compliance review then code-quality review. TDD-locked: red, green, refactor. Independent implementation, review, and council batches remain parallel, but their dispatches are foreground: the orchestrator waits for terminal results before accepting work or advancing a phase.
|
|
42
|
-
4. **verify**: the parent first runs the plan's full verification command set; only a passing result permits whole-diff code review, then the **conformance gate**. A review fix invalidates that result, so the parent reruns full verification before the next review or conformance gate. The conformance subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized. Inside a brainstorming-entered flow this gate is machine-blocked from being skipped. Compatible executable recommendations auto-run through an isolated fix-and-re-audit loop with no prompt; anything still open surfaces as a dense list - one line per decision, plain-language, with its recommended choice inline. Reply `1` to take every recommendation, or `2:` with per-item overrides; a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
|
|
42
|
+
4. **verify**: the parent first runs the plan's full verification command set; only a passing result permits whole-diff code review, then the **conformance gate**. A review fix invalidates that result, so the parent reruns full verification before the next review or conformance gate. The conformance subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized. Unauthorized rows - shipped surface no human input asked for, whether it crept in or was laundered through the spec - follow their recommendation like every other row: a contained removal auto-runs, anything another requirement leans on is deferred with a plain-language "I'd cut it / I'd keep it" recommendation. Inside a brainstorming-entered flow this gate is machine-blocked from being skipped. Compatible executable recommendations auto-run through an isolated fix-and-re-audit loop with no prompt; anything still open surfaces as a dense list - one line per decision, plain-language, with its recommended choice inline. Reply `1` to take every recommendation, or `2:` with per-item overrides; a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
|
|
43
43
|
5. **`finishing-a-development-branch`**: squash, PR, keep, or discard. Once a PR exists, run `/skill:gatekeep-pr <pr>` to verify it against its issue before merging. **Human gate 2** - the only other decision you make.
|
|
44
44
|
6. *(Optional)* Once the merge lands, `/skill:check-delivery <ref>` can prove delivery - default-branch landing, delivery target, per-AC evidence - before the tracker status advances. Explicit invocation only, no auto-chain: deploys commonly lag merges by minutes to hours, so an auto-run would routinely check too early.
|
|
45
45
|
|
|
@@ -80,7 +80,7 @@ pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this
|
|
|
80
80
|
| Term | Meaning |
|
|
81
81
|
| --- | --- |
|
|
82
82
|
| Gate | A machine-enforced checkpoint between phases (e.g. within a brainstorming-entered flow, `complete verify` is blocked until conformance review has run). Not a suggestion. |
|
|
83
|
-
| Spec council | Multi-model critique of the spec before you see it (`roasting-the-spec`);
|
|
83
|
+
| Spec council | Multi-model critique of the spec before you see it (`roasting-the-spec`); members also name spec clauses nobody asked for and nothing needs (`over-spec`) so they are cut before planning, every member closes with a `lean:` line - "nothing to cut" is a first-class answer - and the chair tallies them (`lean: <k> of <n> members found nothing to cut`). Falls back to a single-model critique if no council is configured. |
|
|
84
84
|
| Conformance gate | The closing check: does the delivered code + docs match your *original prompt*, not the derived plan? Compatible executable recommendations auto-fix first; anything still open renders as a dense one-line-per-decision list with each recommended choice inline. Reply `1` to accept all recommendations or `2:` with per-item overrides; a current `CONFORMS` / no-concerns handoff goes straight to branch options with no extra sign-off. |
|
|
85
85
|
| Wave | A batch of plan tasks that don't touch the same files, dispatched to implementers in parallel. |
|
|
86
86
|
| Overrides file | `.pi/gauntlet-overrides.md` - where you put project-specific detail the generic skills don't know (CI command, worktree wrapper, routing rules). |
|
|
@@ -31,9 +31,11 @@ Work flows `origin (prompt + spec) → plan → code/doc`. Every hop is lossy: a
|
|
|
31
31
|
## Process
|
|
32
32
|
|
|
33
33
|
1. **Reconstruct the origin.** Read the spec and the verbatim original prompt. Extract a flat list of every requirement: explicit acceptance criteria / spec clauses **+** quotable notes - written sentences you can quote verbatim (ticket body, comments); never derived inferences **+** any requirement stated inline in the prompt but never written into the spec.
|
|
34
|
+
|
|
35
|
+
One exception to "spec is canonical" and to the "this could be cleaner" guard below: a spec clause that is (1) obviously outside the stated problem, (2) required by no human input you hold - the verbatim prompt, the ticket, or a human decision the spec records (e.g. "user chose X") - and (3) not necessary to deliver the feature correctly (necessity beats leanness: a clause another requirement needs is not excess, even if unrequested) is **not** an `Rn`. Report it once, as an `UNAUTHORIZED` row - never as `DELIVERED`, never also as origin drift. Leg 2 uncertain -> it stays an `Rn`. The test is that three-leg predicate, not taste.
|
|
34
36
|
2. **Check origin drift.** If the spec and the prompt/ticket disagree, do **not** absorb it silently. A deviation recorded in the spec → spec wins (it was review-gated). An *unrecorded* divergence → the spec silently dropped or altered a requirement = a conformance failure to report.
|
|
35
37
|
3. **Map each requirement to the deliverable.** Read the diff (code **and** docs) yourself — do not trust any summary. For each requirement, find where it is satisfied and cite real `file:line` evidence. Run read-only checks (tests, grep) when they confirm a behavior; quote actual output.
|
|
36
|
-
4. **Flag the unrequested.** Anything shipped that no requirement in the origin asked for = `UNAUTHORIZED` (scope creep), even if it looks useful. Do not negotiate scope with yourself.
|
|
38
|
+
4. **Flag the unrequested.** Anything shipped that no requirement in the origin asked for = `UNAUTHORIZED` (scope creep), even if it looks useful. Do not negotiate scope with yourself. `UNAUTHORIZED` covers both surface with no origin at all and spec-laundered excess per step 1; the `origin` literal stays `none (scope creep)` for both, and for the spec-laundered case `evidence:` opens with `spec "<section>" - "<clause>" (over-spec)` followed by the surface (files, specs) and the unprotected failure.
|
|
37
39
|
5. **Apply the coverage rule.** Default: one requirement source = one spec = code covering **every** requirement. Source and solution must end in sync. Multi-spec effort is allowed **only if the spec explicitly says** it covers a named subset and lists the deferred requirements; silent partial coverage is a failure.
|
|
38
40
|
|
|
39
41
|
## Output format
|
|
@@ -48,6 +50,7 @@ Requirement coverage:
|
|
|
48
50
|
- [MISSING] G2: <requirement> — origin: spec "Section 3" - "<quoted clause>" — searched: <where you looked>
|
|
49
51
|
- [DRIFTED] G3: delivered <X>, origin asked <Y> — origin: spec "Section 3" - "<quoted clause>" — evidence: file.ts:120
|
|
50
52
|
- [UNAUTHORIZED] G4: <behavior with no origin requirement> — origin: none (scope creep) — evidence: file.ts:200
|
|
53
|
+
- [UNAUTHORIZED] G5: <spec-mandated behavior no human input required> — origin: none (scope creep) — evidence: spec "Section 6" - "<quoted clause>" (over-spec); file.ts:210, file_spec.ts:1-40; no Rn evidence in these files; unprotected: nothing
|
|
51
54
|
|
|
52
55
|
Origin drift (spec vs prompt/ticket):
|
|
53
56
|
- <disagreement> — recorded in spec? yes/no — <one-line reconciliation note>
|
|
@@ -122,15 +125,14 @@ serial waves — identical to planned-execution wave grouping. Runtime-resource
|
|
|
122
125
|
`recommended` is a proposal; you never decide, edit, dispatch, or re-audit.
|
|
123
126
|
|
|
124
127
|
- Default `fix` for every `PARTIAL` / `MISSING` / `DRIFTED` row.
|
|
125
|
-
- For every `UNAUTHORIZED` row:
|
|
126
|
-
`remediation`; otherwise → `fix` (= remove the unrequested code).
|
|
128
|
+
- For every `UNAUTHORIZED` row: `fix` only when removal is **contained** and, for the over-spec shape, leg 2 is established. Contained = no other `Rn`'s `evidence` `file:line` lives in the code/test/helper files being deleted (the spec clause itself never un-contains). List the deletions and the unaffected `Rn` rows in `remediation`; for the over-spec shape the deletions include the spec clause/AC line. Otherwise `accept`, with a human-voice, example-driven recommendation in `remediation`: what it costs, where it came from, what breaks if cut and what already covers that, then "I'd cut it" / "I'd keep it" with the condition that flips it. A bare provenance line is not a recommendation. `accept` for the over-spec shape means keep code and clause; no spec write.
|
|
127
129
|
- `rescope` only when the `origin` requirement is impractical to satisfy in this branch
|
|
128
130
|
(`rescope` is inapplicable to `UNAUTHORIZED` — there is no requirement to defer).
|
|
129
131
|
|
|
130
132
|
## Rules
|
|
131
133
|
|
|
132
134
|
- **Read-only. Never edit.** You audit; you do not fix.
|
|
133
|
-
- **Propose, do not dispose.** For each gap you may suggest a one-line remediation *direction*, but you do **not** decide the disposition - the orchestrator auto-applies `fix` gaps and defers `accept`/`rescope
|
|
135
|
+
- **Propose, do not dispose.** For each gap you may suggest a one-line remediation *direction*, but you do **not** decide the disposition - the orchestrator auto-applies `fix` gaps and defers `accept`/`rescope` gaps to the user at the finish gate (an `UNAUTHORIZED` row follows its `recommended` value like every other verdict). Never present a fix as a decision you made.
|
|
134
136
|
- **Evidence or it didn't happen.** Cite a real `file:line` for every DELIVERED/PARTIAL. If you cannot, downgrade the row to MISSING.
|
|
135
137
|
- **Origin quote or it isn't a gap.** Every non-UNAUTHORIZED gap's `origin` carries a locator AND a verbatim quote: `origin: <file/section, 'prompt', or 'ticket'> - "<quoted clause>"` (truncate long clauses with `[...]` as long as the fragment uniquely identifies the clause). No quotable origin clause = no gap. Do not derive implicit requirements. Do not flag wording preferences. A deviation recorded in the spec wins over an older origin value (Process step 2); report it only if unrecorded.
|
|
136
138
|
- **Spec is canonical; the prompt catches what the spec dropped; the ticket is fallback only** when no spec exists.
|
|
@@ -44,8 +44,19 @@ verdict: sound | needs-work | unsound
|
|
|
44
44
|
addresses-problem: yes | partial | no — <why>
|
|
45
45
|
findings:
|
|
46
46
|
- [blocker|major|minor] <kind> @ <section or quote> — <problem> → <suggested edit>
|
|
47
|
+
lean: nothing to cut | <N> over-spec findings above
|
|
47
48
|
```
|
|
48
49
|
|
|
49
|
-
`<kind>` is one of: gap, oversimplification, ambiguity, scope, not-actionable, external-ref, other. Omit the `findings` bullets entirely if you have none.
|
|
50
|
+
`<kind>` is one of: gap, oversimplification, ambiguity, scope, not-actionable, external-ref, other, over-spec. `scope` means under-scope or wrong problem; excess is `over-spec` only. Omit the `findings` bullets entirely if you have none - the `findings:` header stays, and `lean:` is always the line immediately after the header or its last bullet. `lean: nothing to cut` is a legitimate, expected answer for a tight spec; `<N>` is the count of `over-spec` bullets above it.
|
|
51
|
+
|
|
52
|
+
**`over-spec`.** A clause is `over-spec` only when **all three** hold: (1) it is obviously outside the stated problem; (2) no human input requires it - human input is the verbatim block the dispatch passes you (original prompt, ticket ACs, questionary answers, user chat), and verbatim human input is off-limits; (3) it is not necessary to deliver the feature correctly - LLM-discovered necessities pass this leg and are not findings. Any leg failing -> not a finding. Necessity beats leanness. When leg 2 cannot be established from the human input you hold - none was passed, or its coverage of the clause is unclear - the clause is not over-spec; with no human input at all, every clause fails leg 2 and the report closes `lean: nothing to cut`. Grammar, one line:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
- [major|minor] over-spec @ "<quoted spec clause>" — no human input requires this (closest human input: "<quote>" | none); adds: <M> files / <N> tests / <K> ACs; if cut, unprotected: <failure | nothing> → cut | shrink to <replacement>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`major` when the clause buys >= 1 new file or >= 3 tests, `minor` below; never `blocker` - an unneeded clause never makes a spec unsound. `adds:` is your estimate of the surface the clause mandates; `unprotected:` names the failure that goes uncaught if the clause is cut - `nothing` is itself the evidence. The `closest human input:` quote comes from the passed human-input block, never from the spec's own prose.
|
|
59
|
+
|
|
60
|
+
Finding: spec says "S6: compute a `checksum` over child names, expose `meta.checksum`, add a reconciliation job flagging mismatches"; the human input said "return the folder tree as JSON like the HTML view"; nothing else in the spec depends on S6 -> `- [major] over-spec @ "S6 ... reconciliation job" — no human input requires this (closest human input: "return the folder tree as JSON like the HTML view"); adds: 2 files / 6 tests / 1 AC; if cut, unprotected: nothing → cut`. Non-finding: spec adds `format: false` on three compliance route mounts; nobody asked, but without it `.json` suffixes 404 on those mounts, so the JSON view cannot be delivered - leg 3 fails, not over-spec.
|
|
50
61
|
|
|
51
62
|
Keep `verdict` consistent with `addresses-problem`: `addresses-problem: no` requires `verdict: unsound`; `addresses-problem: partial` rules out `verdict: sound`. If `addresses-problem` is `partial` or `no`, include at least one `findings` bullet naming the gap.
|
|
@@ -17,7 +17,7 @@ You receive the problem statement, the path to the spec, and the explicit paths
|
|
|
17
17
|
Your job has two parts:
|
|
18
18
|
|
|
19
19
|
1. **Consolidate.** Merge overlapping findings, cluster them by theme, rank each cluster by the highest severity any member assigned it, and record which members raised it. Drop pure duplicates. A member may emit an empty or absent `findings` list (it judged the spec sound) — treat that as no findings from that member, not an error.
|
|
20
|
-
2. **Adjudicate — your most important job.** Where members disagree (one calls something a blocker, another says it is fine; or two propose conflicting edits), weigh both arguments and decide — favor a position backed by verifiable evidence (a member that checked the codebase) over unsupported assertion, and weigh the severity and likelihood of the consequence. Fold the winning position into a single suggested edit. Do not pass the disagreement to the reader as an open question. You have the final say on member-vs-member conflicts. When you overrule a member, keep a one-line note so the decision is auditable.
|
|
20
|
+
2. **Adjudicate — your most important job.** Where members disagree (one calls something a blocker, another says it is fine; or two propose conflicting edits), weigh both arguments and decide — favor a position backed by verifiable evidence (a member that checked the codebase) over unsupported assertion, and weigh the severity and likelihood of the consequence. Fold the winning position into a single suggested edit. Do not pass the disagreement to the reader as an open question. You have the final say on member-vs-member conflicts. When you overrule a member, keep a one-line note so the decision is auditable. An `over-spec` cluster loses only to a finding that **rebuts leg 3** of the over-spec predicate - shows the clause is load-bearing, i.e. cutting it causes a product or delivery failure. A spec-quality defect on the excess clause (unnamed algorithm, missing AC, ambiguity) does **not** protect it; the cut resolves that finding - record it in `resolved:`.
|
|
21
21
|
|
|
22
22
|
You do not decide what gets applied to the spec — that is the author's and the user's call. You produce one consolidated, conflict-free report.
|
|
23
23
|
|
|
@@ -26,6 +26,7 @@ Emit exactly this markdown and nothing else:
|
|
|
26
26
|
|
|
27
27
|
```
|
|
28
28
|
consensus: <one-line overall verdict, e.g. needs-work — 2 of 3 members flagged blockers>
|
|
29
|
+
lean: <k> of <n> members found nothing to cut
|
|
29
30
|
clusters:
|
|
30
31
|
- [blocker|major|minor] <theme> — raised-by: [<model>, <model>] — <consolidated finding> → <suggested edit>
|
|
31
32
|
resolved:
|
|
@@ -36,4 +37,8 @@ Every cluster must be pre-resolved — never emit a raw "members disagree" item.
|
|
|
36
37
|
|
|
37
38
|
When any member raises an `external-ref` finding (load-bearing external context the spec does not inline), surface it as its own cluster with the theme prefixed `external-ref:`, e.g. `- [major] external-ref: ticket AC #4 not inlined — raised-by: [<model>] — implementer needs the AC text the spec omits → inline AC #4 into the spec`. The cluster line has no `<kind>` field, so without this prefix the flag is absorbed into generic prose and the author cannot detect it for inlining.
|
|
38
39
|
|
|
40
|
+
When any member raises an `over-spec` finding, surface it as its own cluster with the theme prefixed `over-spec:` and carry the bullet's `adds:` and `unprotected:` values verbatim in the cluster text (when members disagree, the maximum `adds:` and the most specific `unprotected:`), e.g. `- [major] over-spec: S6 checksum/reconciliation — adds: 2 files / 6 tests / 1 AC; unprotected: nothing — raised-by: [<model>] — no human input requires S6 → cut S6 and its AC`. Same reason as `external-ref:`: the cluster line has no `<kind>` field, and the author branches on the prefix to apply the cut. Normalize, do not reject: a bullet that quotes a spec clause and states cut/shrink intent is kept even if `adds:` or `unprotected:` is missing - write `unstated` for the missing value. Drop only bullets whose quoted clause is verbatim human input (checked against the human-input block in your task) or that quote no clause at all; one line each in `resolved:`. A member's `lean:` count that disagrees with its bullet count is noted in `resolved:` and the bullets are used.
|
|
41
|
+
|
|
42
|
+
The `lean:` line is mandatory: `k` = members whose report says `lean: nothing to cut`, `n` = members you received.
|
|
43
|
+
|
|
39
44
|
Attribute each cluster's `raised-by` using the model slug in each member's filename (e.g. `member-0-<slug>.md` → `<slug>`). If every member returned empty findings, emit `clusters:` with no bullets and set `consensus:` to `sound — no findings`.
|
package/package.json
CHANGED
|
@@ -304,7 +304,7 @@ Both paths apply their fixes **before returning** — the council auto-applies i
|
|
|
304
304
|
|
|
305
305
|
## Spec Council (Optional)
|
|
306
306
|
|
|
307
|
-
After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; it applies its apply-set and returns the audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, run the fresh-`worker` critique instead (see [Spec Self-Review](#spec-self-review-before-user-review-gate)). If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `verification-before-completion/reference/settings-precedence.md`.
|
|
307
|
+
After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; also pass the verbatim human input (the original prompt, any ticket AC snapshot, and the questionary answers that changed scope) - roasting-the-spec forwards it to members and chair as the `Human input (verbatim; off-limits for over-spec)` block; it applies its apply-set and returns the audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, run the fresh-`worker` critique instead (see [Spec Self-Review](#spec-self-review-before-user-review-gate)). If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `verification-before-completion/reference/settings-precedence.md`.
|
|
308
308
|
|
|
309
309
|
## User Review Gate
|
|
310
310
|
|
|
@@ -65,17 +65,20 @@ subagent({
|
|
|
65
65
|
model,
|
|
66
66
|
cwd: "<abs worktree path>",
|
|
67
67
|
task: "Problem statement: <the problem the spec addresses, from its Context section and the user's stated intent>.\n" +
|
|
68
|
+
"Human input (verbatim; off-limits for over-spec):\n```\n<original prompt>\n<ticket AC snapshot, if any>\n<questionary answers that changed scope>\n```\n" +
|
|
68
69
|
"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.",
|
|
69
70
|
output: "<tmpdir>/member-" + i + "-" + slug(model) + ".md"
|
|
70
71
|
}))
|
|
71
72
|
})
|
|
72
73
|
```
|
|
73
74
|
|
|
75
|
+
The `Human input (verbatim; off-limits for over-spec)` block is supplied by the caller (brainstorming passes the original prompt, any ticket AC snapshot, and the questionary answers that changed scope; shape-ticket passes the raw ask). It is the only source members and the chair may quote for the over-spec predicate's leg 2; without it members correctly emit `lean: nothing to cut`.
|
|
76
|
+
|
|
74
77
|
`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.
|
|
75
78
|
|
|
76
79
|
`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.
|
|
77
80
|
|
|
78
|
-
**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
|
|
81
|
+
**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 a `^verdict:\s*(sound|needs-work|unsound)` line, an `^addresses-problem:` line, and a `^lean:` line. A `findings:` header with zero bullets is a valid, usable sound critique. Existence plus header regex only - never read or weigh findings content.
|
|
79
82
|
|
|
80
83
|
**Targeted retry.** Members whose file is missing or not usable are re-dispatched **once**, together, in a second foreground parallel call carrying `async: false` and 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). Await its terminal result. Members with usable files are never re-run.
|
|
81
84
|
|
|
@@ -94,6 +97,7 @@ subagent({
|
|
|
94
97
|
control: { needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 600000, inFlightSilenceKillMs: 900000 },
|
|
95
98
|
reads: [ <the usable member file paths under the temp dir> ],
|
|
96
99
|
task: "Problem statement: <paste>. Spec: <abs path>.\n" +
|
|
100
|
+
"Human input (verbatim; off-limits for over-spec):\n```\n<the same block passed to members>\n```\n" +
|
|
97
101
|
"Member critiques (already injected via reads — do not search for them):\n" +
|
|
98
102
|
usableMemberPaths.join("\n") + "\n" +
|
|
99
103
|
"Coverage: <N> of <M> members reported<; <slug>: <one-line reason> per missing member>.\n" +
|
|
@@ -105,7 +109,7 @@ The chair runs one long foreground single-turn synthesis; await its terminal res
|
|
|
105
109
|
|
|
106
110
|
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.
|
|
107
111
|
|
|
108
|
-
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. Each retry remains foreground with top-level `async: false` and is awaited to a terminal result. Second failure -> abort the council, say so, and return to the user gate.
|
|
112
|
+
A chair synthesis is usable iff it contains a `^consensus:` line and a `^lean:` 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. Each retry remains foreground with top-level `async: false` and is awaited to a terminal result. Second failure -> abort the council, say so, and return to the user gate.
|
|
109
113
|
|
|
110
114
|
### 3 — Decide and apply
|
|
111
115
|
|
|
@@ -117,6 +121,8 @@ For each cluster in the chair's report, decide one of:
|
|
|
117
121
|
|
|
118
122
|
Also inline any `external-ref:` cluster you have context for (e.g. a ticket fetched during brainstorming) as part of the apply-set — this is your call, same as any other cluster.
|
|
119
123
|
|
|
124
|
+
An `over-spec:` cluster decided **apply** is executed as deletion or shrink of the quoted clause **and** any acceptance-criteria or testing-approach line that exists only for it. Its audit line reads `Applied: over-spec: <clause> -> cut (was adds: M files / N tests / K ACs)` or `Applied: over-spec: <clause> -> shrunk to <replacement> (was adds: ...)`, so the gate shows what was removed. `defer`/`reject` are unchanged.
|
|
125
|
+
|
|
120
126
|
You are the advocate — decide on scope grounds — and, unlike a dispatched subagent, also the executor: you hold `edit`/`write` tools directly, so apply the edit yourself instead of proposing it for someone else to make. Do this **before** returning to brainstorming.
|
|
121
127
|
|
|
122
128
|
### 4 — Emit the audit
|
|
@@ -175,19 +175,19 @@ No block, no split. A block failing the identity or outcome test, or naming a no
|
|
|
175
175
|
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.
|
|
176
176
|
|
|
177
177
|
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, its task text carrying the absolute path to `reference/ticket-wording.md` (resolved against this skill's own directory). Malformed config -> one warning line, then branch on verdict.
|
|
178
|
-
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 two referenced reference paths (split-axes, ticket-wording) are the entire permitted input - do not read, search, or scan the repository; do not edit any file.* Include the absolute paths to `reference/split-axes.md` and `reference/ticket-wording.md` (both 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.
|
|
178
|
+
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. The raw ask (create mode) or the original ticket body (repair mode) is also passed as the `Human input (verbatim; off-limits for over-spec)` block. 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 two referenced reference paths (split-axes, ticket-wording) are the entire permitted input - do not read, search, or scan the repository; do not edit any file.* Include the absolute paths to `reference/split-axes.md` and `reference/ticket-wording.md` (both 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.
|
|
179
179
|
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.
|
|
180
180
|
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` (unpacking existing claims to satisfy the ticket wording contract is not `added`; contract-conformance findings on Context/Problem/Idea outrank fidelity flags that only object to extra explanation of the same claims); and *quality* - problem framing, AC integrity beyond the deterministic gate, scope, wording, and conformance to the ticket wording contract (reference path provided in every roast brief); 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.
|
|
181
181
|
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).
|
|
182
182
|
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 (including the absolute `reference/ticket-wording.md` path) and temp-file artifacts - on such a harness this conditional IS the roast, so the contract path must ride along.
|
|
183
183
|
7. **Usable outputs, targeted retry, and roast failure.** A member output
|
|
184
|
-
file is usable iff it is non-empty AND contains
|
|
185
|
-
`^verdict:\s*(sound|needs-work|unsound)` line
|
|
186
|
-
`^addresses-problem:` line (a `findings:` header with zero bullets is
|
|
184
|
+
file is usable iff it is non-empty AND contains a
|
|
185
|
+
`^verdict:\s*(sound|needs-work|unsound)` line, an
|
|
186
|
+
`^addresses-problem:` line, and a `^lean:` line (a `findings:` header with zero bullets is
|
|
187
187
|
valid; the test is a mechanical structural probe - existence plus
|
|
188
188
|
header regex, no reading of findings content - and the parent judges
|
|
189
189
|
by files on disk, not the fanout tool result's labels). A chair
|
|
190
|
-
synthesis is usable iff it contains a `^consensus:` line. Members
|
|
190
|
+
synthesis is usable iff it contains a `^consensus:` line and a `^lean:` line. Members
|
|
191
191
|
whose file is missing or not usable are re-dispatched **once**,
|
|
192
192
|
together, in a second parallel call carrying the same control block,
|
|
193
193
|
with fresh output paths preserving the `member-<i>-<slug>` basename
|
|
@@ -98,10 +98,8 @@ No prompt, no menu: this partition is deterministic and exhaustive.
|
|
|
98
98
|
|
|
99
99
|
1. **Verdict `CONFORMS`** (no gaps) → record the verdict in the completion
|
|
100
100
|
summary's closure section and proceed. No loop.
|
|
101
|
-
2. **
|
|
102
|
-
|
|
103
|
-
unrequested code here.
|
|
104
|
-
3. **Every remaining `PARTIAL`/`MISSING`/`DRIFTED` gap**:
|
|
101
|
+
2. **Every gap** (`PARTIAL`/`MISSING`/`DRIFTED`/`UNAUTHORIZED` alike - an
|
|
102
|
+
`UNAUTHORIZED` gap follows its `recommended` value like any other):
|
|
105
103
|
- `recommended: fix` → auto-run the fix loop below — **unless a declared
|
|
106
104
|
fix-loop precondition is unavailable** (`maxFixRounds: 0`, or no eligible
|
|
107
105
|
named-branch worktree), in which case carry the gap **OPEN** and defer it
|
|
@@ -113,14 +111,14 @@ No prompt, no menu: this partition is deterministic and exhaustive.
|
|
|
113
111
|
deferred to the finish gate. Do not apply a spec edit here — the finish
|
|
114
112
|
gate owns disposition of deferred gaps.
|
|
115
113
|
|
|
116
|
-
So the fast path (all gaps `recommended: fix`,
|
|
114
|
+
So the fast path (all gaps `recommended: fix`, cap > 0,
|
|
117
115
|
eligible named-branch worktree) therefore auto-runs the fix loop with no menu,
|
|
118
116
|
stop, or confirmation; any other mix carries the
|
|
119
|
-
`accept`/`rescope
|
|
117
|
+
`accept`/`rescope` gaps OPEN while the `fix` gaps run. Record every gap's outcome (`CONFORMS`-closed or carried OPEN) in the
|
|
120
118
|
`## Closure / conformance` block (schema below).
|
|
121
119
|
|
|
122
120
|
**Re-partition after every re-audit.** A re-audit can introduce `Gn+1` or flip a
|
|
123
|
-
carried gap's `recommended`. Re-run steps 1-
|
|
121
|
+
carried gap's `recommended`. Re-run steps 1-2 above over the **full current
|
|
124
122
|
open-gap set** each time the reviewer returns a report — never reuse a stale
|
|
125
123
|
partition from an earlier round.
|
|
126
124
|
|
|
@@ -148,7 +146,10 @@ Per round:
|
|
|
148
146
|
group of ≥ 2 gaps (per the report's `Parallel-safe:` line) fixes in one parallel
|
|
149
147
|
foreground dispatch — one `implementer` per gap (fresh context, `async: false`,
|
|
150
148
|
`worktree: true`, `cwd` = the conformance worktree, task = the gap block verbatim
|
|
151
|
-
with `touched-files` as the ownership boundary).
|
|
149
|
+
with `touched-files` as the ownership boundary). For an `UNAUTHORIZED` `fix` gap
|
|
150
|
+
whose `evidence` opens with the over-spec provenance (`spec "<section>" - "<clause>" (over-spec)`), the orchestrator adds the spec path to that gap's `touched-files` before dispatch,
|
|
151
|
+
so the implementer deletes the surface **and** the clause/AC line in the same
|
|
152
|
+
fix commit; the re-audit then has no `Rn` for it and no `MISSING` echo. The dispatch adds `SCOPED_TEST_COMMANDS`
|
|
152
153
|
to the gap block: the gap-relevant plan-declared commands, or `none` (the round's
|
|
153
154
|
test gate owns execution). `conflicts` pairs serialize. Gaps outside any ≥ 2-ID
|
|
154
155
|
`disjoint` group run sequentially as before. Then dispatch foreground `spec-reviewer`
|
|
@@ -346,7 +347,7 @@ only if a concern remains open.
|
|
|
346
347
|
Emit this block in the verify completion summary. It is the durable handoff
|
|
347
348
|
`finishing-a-development-branch` Step 3.5 consumes — parseable even if session
|
|
348
349
|
context was pruned. Verify completes when every gap is either fixed
|
|
349
|
-
(`CONFORMS`) or carried OPEN as a deferred gap - `accept`/`rescope
|
|
350
|
+
(`CONFORMS`) or carried OPEN as a deferred gap - `accept`/`rescope`,
|
|
350
351
|
or a `recommended: fix` gap carried OPEN per the precondition-unavailable
|
|
351
352
|
carry-OPEN rule in the partition step above (the loop never started). Escalation - a started
|
|
352
353
|
positive-cap loop that exhausted its rounds or blocked/failed with an open `fix`
|