pi-gauntlet 7.2.1 → 7.3.1

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## v7.3.1 - 2026-10-06
4
+
5
+ ### Changed
6
+
7
+ - **Spec gate shows council counts, not the audit.** The brainstorming user review gate renders one `Council: <A> applied, <D> deferred, <R> rejected` line (with `; coverage <N> of <M>` on partial coverage) under the briefing and a third menu row, `3 - show council dispositions`, that prints the audit verbatim from the spec commit body and re-presents the gate without approving. The spec commit is made through a quoted heredoc (`git add -- <spec> && git commit -q -F - <<'EOF'`) so the audit lines are in the command; the `telemetry` extension records council dispositions from that command during brainstorm, with assistant text as the fallback. New `eval/spec-gate/` target (five samples, first run recorded) measures the gate render; `scripts/ci.mjs` runs its driver test.
8
+
9
+ ## v7.3.0 - 2026-10-05
10
+
11
+ - eval/brainstorming/replay: the live-tool replay harness code and verdict tables are committed; private inputs and transcripts are documented as non-committable.
12
+
13
+ ### Added
14
+
15
+ - **forge-skill persona rules and eval harness.** `skills/forge-skill/SKILL.md` gains `## Persona rules` (the body is the whole system prompt, `description` is a routing label, frontmatter stays byte-identical unless a knob is named, tools stay as narrow as the job, no `/skill:` or `reference/` machinery, no reload step in pi) and marks its three skill-only authoring rules `Skills only:`. `eval/forge-skill/` is a fact-judged eval driver (worker edit, two reviewers, arithmetic kept/disputed/lost, refusal-guarded `compare`) with ten samples, six of them persona edits (four from `agents/` history, two constructed to exercise the persona rules); it ships outside the package; `scripts/ci.mjs` runs its deterministic driver tests and checks sample shape (exactly `case.md` + `expected.md`). The sample files are `case.md` (fixture files plus the edit request, fenced) and `expected.md`, and raw results stay under `$TMPDIR` - both deliberate deviations from `eval/README.md`, recorded in the spec. Baseline-vs-candidate judgment is recorded in the spec's `## Eval judgment`.
16
+
3
17
  ## v7.2.1 - 2026-10-04
4
18
 
5
19
  ### Changed
package/README.md CHANGED
@@ -159,6 +159,8 @@ Evals compare prompt wording on fixed inputs against human-approved must-hold fa
159
159
 
160
160
  Run an arm with externally supplied models: `node eval/spec-summarizer/run.mjs run --arm candidate --persona agents/spec-summarizer.md --candidate-model <candidate-id> --reviewers <reviewer-a>,<reviewer-b>`. The [eval convention](eval/README.md) owns the layout, baseline/candidate process, and publication rules; the [spec-summarizer eval](eval/spec-summarizer/README.md) is the worked example with eight samples and committed results. Model calls run manually, never in CI; CI runs deterministic eval tests and hygiene checks. `eval/` is checkout-only and excluded from the npm tarball.
161
161
 
162
+ `eval/forge-skill/` measures an edit to `skills/forge-skill/SKILL.md`: `node eval/forge-skill/run.mjs --skill-dir <path> --candidate-model <id> --reviewers <id>,<id>` runs a worker over ten samples (four skill fixtures, six persona fixtures - four from this repo's `agents/` history, two constructed to exercise the persona rules), has two reviewer models vote per must-hold fact, and writes records under `$TMPDIR`; `run.mjs compare <baseline> <candidate>` refuses mismatched inputs and fails on any fact that moved from kept to lost. Models are arguments, never committed. Its samples are `case.md` + `expected.md` and its raw results stay under `$TMPDIR` - the two places it deviates from the convention. Process and sample grammar: [`eval/forge-skill/README.md`](eval/forge-skill/README.md).
163
+
162
164
  ## Use from Claude Code
163
165
 
164
166
  Six skills are exposed to Claude Code via the plugin marketplace at
@@ -626,6 +626,67 @@
626
626
  assert.deepEqual(h.readRecord().derived.council, EXPECTED_COUNCIL);
627
627
  });
628
628
 
629
+ const HEREDOC_COMMIT = (body: string) => `git -C /w commit -q -F - -- doc/specs/a.md <<'EOF'\nspec: a\n\n${body}\nEOF`;
630
+ const ALL_NONE = "Applied: none\nDeferred: none\nRejected: none";
631
+ const EMPTY_CHAIR = "consensus: sound\nclusters:\nresolved:\n";
632
+ test("council records the audit from the spec commit heredoc without a chat render", async () => {
633
+ const h = await boundInBrainstorm();
634
+ await h.emit("tool_result", memberBatch());
635
+ await h.emit("tool_result", chairResult());
636
+ await h.bash("c1", HEREDOC_COMMIT(AUDIT_TEXT));
637
+ assert.deepEqual(h.readRecord().derived.council, EXPECTED_COUNCIL);
638
+ });
639
+ test("council ignores a chat audit after the commit already recorded it", async () => {
640
+ const h = await boundInBrainstorm();
641
+ await h.emit("tool_result", memberBatch());
642
+ await h.emit("tool_result", chairResult());
643
+ await h.bash("c1", HEREDOC_COMMIT(AUDIT_TEXT));
644
+ const before = h.readRecord().derived.council;
645
+ assert.deepEqual(before, EXPECTED_COUNCIL);
646
+ await h.emit("message_end", assistant(AUDIT_TEXT));
647
+ assert.deepEqual(h.readRecord().derived.council, before);
648
+ });
649
+ test("council ignores a commit outside brainstorm or before a chair result", async () => {
650
+ const planned = await boundInPlan();
651
+ await planned.emit("tool_result", memberBatch());
652
+ await planned.emit("tool_result", chairResult());
653
+ await planned.bash("c1", HEREDOC_COMMIT(AUDIT_TEXT));
654
+ assert.equal(planned.readRecord().derived.council, undefined);
655
+ const early = await boundInBrainstorm();
656
+ await early.emit("tool_result", memberBatch());
657
+ await early.bash("c1", HEREDOC_COMMIT(AUDIT_TEXT));
658
+ assert.equal(early.readRecord().derived.council, undefined);
659
+ });
660
+ test("council keeps pending across the grant amend and records a later chat audit", async () => {
661
+ const h = await boundInBrainstorm();
662
+ await h.emit("tool_result", memberBatch());
663
+ await h.emit("tool_result", chairResult());
664
+ await h.bash("c1", "git -C /w commit --amend --no-edit -q");
665
+ assert.equal(h.readRecord().derived.council, undefined);
666
+ await h.emit("message_end", assistant(AUDIT_TEXT));
667
+ assert.deepEqual(h.readRecord().derived.council, EXPECTED_COUNCIL);
668
+ });
669
+ test("council records an all-none audit from the commit against an empty chair", async () => {
670
+ const h = await boundInBrainstorm();
671
+ await h.emit("tool_result", memberBatch());
672
+ await h.emit("tool_result", chairResult(EMPTY_CHAIR));
673
+ await h.bash("c1", HEREDOC_COMMIT(ALL_NONE));
674
+ const zeroed = { total: counts(), unique: counts(), applied: counts(), unique_applied: counts(), deferred: counts(), rejected: counts() };
675
+ assert.deepEqual(h.readRecord().derived.council, {
676
+ chair: { model: "p/chair:medium", dispatches: 1, clusters: 0, members_reported: 0 },
677
+ members: { "p/alpha:xhigh": { dispatches: 1, ...zeroed }, "p/beta:high": { dispatches: 2, ...zeroed } },
678
+ });
679
+ });
680
+ test("council ignores a non-git bash command carrying audit text", async () => {
681
+ const h = await boundInBrainstorm();
682
+ await h.emit("tool_result", memberBatch());
683
+ await h.emit("tool_result", chairResult());
684
+ await h.bash("c1", `printf '%s\\n' "${AUDIT_TEXT.replace(/\n/g, "\\n")}"`);
685
+ assert.equal(h.readRecord().derived.council, undefined);
686
+ await h.emit("message_end", assistant(AUDIT_TEXT));
687
+ assert.deepEqual(h.readRecord().derived.council, EXPECTED_COUNCIL);
688
+ });
689
+
629
690
  test("subagent results: dispatch events, persona tokens, spec_rounds, reviews, conformance_loops; async results: [] contribute nothing", async () => {
630
691
  const h = await boundInPlan();
631
692
  await h.emit("tool_result", subagentResult([
@@ -656,11 +656,11 @@
656
656
  }
657
657
  };
658
658
 
659
- const onCouncilAudit = (content: unknown) => {
659
+ const onCouncilAudit = (text: string) => {
660
660
  const pending = councilPending;
661
661
  const clusters = pending?.chair?.clusters;
662
662
  if (!record || !pending?.chair || !clusters) return;
663
- const audit = parseAudit(textOf(content));
663
+ const audit = parseAudit(text);
664
664
  if (!audit) return;
665
665
  const members = resolveMembers(pending.memberResults);
666
666
  const block = members && buildCouncil({ chair: { ...pending.chair, clusters }, members, audit });
@@ -758,6 +758,7 @@
758
758
  }
759
759
  if (event.toolName === "bash") {
760
760
  const command = String((event.input as { command?: unknown }).command ?? "");
761
+ if (phaseNow() === "brainstorm" && /\bgit\b[^\n]*\bcommit\b/.test(command)) onCouncilAudit(command);
761
762
  if (record && (phases.verify.status === "in_progress" || phases.ship.status === "in_progress")) {
762
763
  const statement = matchTestStatement(command, snap.testCommands ?? DEFAULT_TEST_COMMANDS);
763
764
  if (statement) pendingTest.set(event.toolCallId, statement);
@@ -771,7 +772,7 @@
771
772
  const msg = event.message as { role?: string; usage?: unknown; content?: unknown };
772
773
  if (msg.role !== "assistant") return;
773
774
  addPhaseTokens(usageToTokens(msg.usage));
774
- if (phaseNow() === "brainstorm") onCouncilAudit(msg.content);
775
+ if (phaseNow() === "brainstorm") onCouncilAudit(textOf(msg.content));
775
776
  });
776
777
 
777
778
  pi.on("turn_end", async (_event, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "7.2.1",
3
+ "version": "7.3.1",
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",
@@ -88,7 +88,19 @@ subagent({ agent: "spec-summarizer", context: "fresh", async: false, cwd: "<abs
88
88
 
89
89
  `<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.
90
90
 
91
- Then commit the spec - staging any predecessor spec edited per [Marking superseded specs](superseding.md) 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):
91
+ Then commit the spec - staging any predecessor spec edited per [Marking superseded specs](superseding.md) 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. Commit with the message on stdin through a quoted heredoc - the only permitted form, so audit lines carrying backticks or `$` land unchanged and each starts a line (telemetry reads them from this command):
92
+
93
+ ```bash
94
+ git -C <abs worktree path> add -- <spec path> [<predecessor path>] && git -C <abs worktree path> commit -q -F - -- <spec path> [<predecessor path>] <<'EOF'
95
+ <subject>
96
+
97
+ <audit lines, verbatim>
98
+ EOF
99
+ ```
100
+
101
+ If the council path ran, the body is its audit (`Coverage:` when present, then `Applied:` / `Deferred:` / `Rejected:`, verbatim from `/skill:roasting-the-spec`'s return) - the durable, non-contractual record a finish-time revert and row 3 below read back; the audit is never a committed spec section. On the worker path the body is empty. `-m`, `-F <file>`, `printf | ... -F -`, and `$'...'` strings are not used for this commit. The `add` on the same line is required because the spec is untracked at its first commit and `commit -- <path>` rejects an untracked path.
102
+
103
+ Evaluate the summary in two stages (the **Degrade path** referenced in each is defined just below):
92
104
 
93
105
  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 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.
94
106
  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 read result be pruned, reproducing the bug.
@@ -97,24 +109,22 @@ Then commit the spec - staging any predecessor spec edited per [Marking supersed
97
109
 
98
110
  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).
99
111
 
100
- Paste the summary verbatim, unedited in the template below; use adjacent lines for the audit, unresolved ambiguities, and every `Missing from the spec` entry:
112
+ Paste the summary verbatim, unedited in the template below; use adjacent lines for the `Council:` counts, unresolved ambiguities, and every `Missing from the spec` entry; the audit itself is shown only on row 3:
101
113
 
102
114
  ```
103
115
  <spec-only summary read back from the temp file - pasted verbatim, unedited>
104
116
 
105
117
  Spec written and committed to <project>/doc/specs/<filename>.md (worktree: <path>).
106
118
 
107
- Coverage: <N> of <M> members reported; <slug>: <reason> (line present only when coverage was partial)
108
- Applied: [<severity>] <cluster> - raised-by: [<slugs>] -> <edit>
109
- Deferred: [<severity>] <cluster> - raised-by: [<slugs>] -> <where it belongs>
110
- Rejected: [<severity>] <cluster> - raised-by: [<slugs>] -> <one-line reason>
111
- (one line per item, exactly as returned by roasting-the-spec - `Applied: none` / `Deferred: none` / `Rejected: none` when a list is empty; omit the audit lines when the worker path ran, not the council)
119
+ Council: <A> applied, <D> deferred, <R> rejected
120
+ (council path only: A, D, R count the non-`none` lines under each label of the returned audit; append `; coverage <N> of <M>` only when the return carried a `Coverage:` line, e.g. `Council: 11 applied, 0 deferred, 0 rejected; coverage 2 of 3`; omit the whole line and row 3 on the worker path or when the council aborted before an audit existed)
112
121
 
113
122
  <unresolved ambiguities; every `Missing from the spec` entry from the summary>
114
123
  New predecessor candidates at spec-writing: <path> (<title>), ... - index rows are a hint; code is the source and an absent row proves nothing.
115
124
 
116
125
  1 - approve: proceed to planning under the existing amendment review; a fresh reviewer applies evidence-backed factual corrections on its own, and every other spec amendment (scope, acceptance-criteria, or contract edits, and redraws) stops for your review.
117
126
  2 - approve, auto-apply amends: every later spec amendment in this flow (corrected facts, paths, verification lines, and scope, acceptance-criteria, or public-contract edits alike) applies without asking; only a redraw (changed problem statement, component added, removed, or re-bounded) still stops for you, and the grant never stands in for a spec approval.
127
+ 3 - show council dispositions: prints the council audit verbatim, then re-presents this gate. Not an approval.
118
128
 
119
129
  Or tell me what to change in the spec, or say "revert applied council edit <X>" to undo a specific applied edit.
120
130
  ```
@@ -123,7 +133,7 @@ If you believe the summary needs correcting, do **not** silently rewrite it - re
123
133
 
124
134
  **Revert valve.** "Revert applied council edit X" is a normal change request: revise the spec to undo edit X, re-dispatch the summarizer with a **fresh** temp path (per the re-dispatch rule below), and re-present the gate. This is cheap here - the spec is not yet plan- or code-bearing.
125
135
 
126
- Wait for the user. On a change request (including a revert), revise the spec and re-present - mint a **fresh** temp path for the re-dispatched summarizer (never reuse a prior round's path, so stale content can never be mistaken for the new summary). On approval - `1`, `approve`, or equivalent prose approves without a grant; `2`, `approve, auto-apply amends`, or equivalent prose approves and grants - proceed immediately to `/skill:writing-plans` with no further prompt; first read [Standing grants](amendment-surface.md#standing-grants) (stop if unreadable). On a grant (`2`, `approve, auto-apply amends`, or equivalent prose), `edit` the spec's `**Amend-grant:**` value to the granting sentence (the digit `2` becomes the grant description after `2 - approve, auto-apply amends:` above), then `git -C <abs worktree path> add -- <spec path>` and `git -C <abs worktree path> commit --amend --no-edit -q`, so the spec commit carries the line and keeps its council-audit body; then confirm `git -C <abs worktree path> show HEAD:<spec path>` reads "grant active" through the predicate. On a plain approval (`1`, `approve`) the value stays `none` and the commit is untouched. A reply that mixes approval with a change request ("2 but rename the section") is a change request: revise, re-present, and read the grant only from the reply to the re-presented gate. If the amend or the HEAD check fails, stop and report; never proceed as granted. The plan and execution mode are mechanical derivatives, so the only human gate here is spec approval itself. Don't land the spec on `main`; it stays in the worktree and ships in the same squash commit as the implementation.
136
+ Wait for the user. On a change request (including a revert), revise the spec and re-present - mint a **fresh** temp path for the re-dispatched summarizer (never reuse a prior round's path, so stale content can never be mistaken for the new summary). On `3` or its label alone: print the audit verbatim from `git -C <abs worktree path> show -s --format=%b HEAD` (never from a held tool result; the body survives the grant's `--amend --no-edit`), then re-present the same gate - the briefing text of the gate message just rendered, reused verbatim (no summarizer re-dispatch, no read of the removed `$SUMMARY_PATH`; if that text is no longer in context, the one-line degrade note takes its place), the `Council:` line, the adjacent lines, the three rows; nothing in the spec or the commit changes. `3` plus a change request is a change request. `3` plus `1`/`2` prints and re-presents, and the approval is read only from a clean `1`/`2` on the re-presented gate - a reply carrying `3` never grants. A gate-round recommit (change request or revert) re-runs the same heredoc commit with the audit as its body, so HEAD always carries it; on a revert of edit X, move X's `Applied:` line to `Rejected:` with `-> reverted at gate` and recompute the `Council:` counts from the revised audit. On approval - `1`, `approve`, or equivalent prose approves without a grant; `2`, `approve, auto-apply amends`, or equivalent prose approves and grants - proceed immediately to `/skill:writing-plans` with no further prompt; first read [Standing grants](amendment-surface.md#standing-grants) (stop if unreadable). On a grant (`2`, `approve, auto-apply amends`, or equivalent prose), `edit` the spec's `**Amend-grant:**` value to the granting sentence (the digit `2` becomes the grant description after `2 - approve, auto-apply amends:` above), then `git -C <abs worktree path> add -- <spec path>` and `git -C <abs worktree path> commit --amend --no-edit -q`, so the spec commit carries the line and keeps its council-audit body; then confirm `git -C <abs worktree path> show HEAD:<spec path>` reads "grant active" through the predicate. On a plain approval (`1`, `approve`) the value stays `none` and the commit is untouched. A reply that mixes approval with a change request ("2 but rename the section") is a change request: revise, re-present, and read the grant only from the reply to the re-presented gate. If the amend or the HEAD check fails, stop and report; never proceed as granted. The plan and execution mode are mechanical derivatives, so the only human gate here is spec approval itself. Don't land the spec on `main`; it stays in the worktree and ships in the same squash commit as the implementation.
127
137
 
128
138
  Post-approval changes follow [Amending an approved spec](../SKILL.md#amending-an-approved-spec).
129
139
 
@@ -11,6 +11,8 @@ This skill governs every file a model reads as instructions: `SKILL.md` and its
11
11
 
12
12
  ## Conventions
13
13
 
14
+ Skills only; persona conventions are in ## Persona rules.
15
+
14
16
  Sources: pi - `docs/skills` in `@earendil-works/pi-coding-agent`; Claude Code - `code.claude.com/docs/en/skills`.
15
17
 
16
18
  | Item | pi | Claude Code |
@@ -31,24 +33,36 @@ Sources: pi - `docs/skills` in `@earendil-works/pi-coding-agent`; Claude Code -
31
33
  These rules bind the lines an edit adds or changes. Leave other lines alone.
32
34
 
33
35
  - Imperative voice. Write instructions as commands. Never "should", "consider", "you may want to", "it is recommended".
34
- - Low conditionality. One path per step: branch only on a runtime fact the model can observe - a tool result, a file's presence, a settings value - never on the reader's judgment. Two branches is the ceiling; a third goes into a table or a `reference/` file.
36
+ - Low conditionality. One path per step: branch only on a runtime fact the model can observe - a tool result, a file's presence, a settings value - never on the reader's judgment. Two branches is the ceiling; a third goes into a table, or for a skill a reference/ file.
35
37
  - Minimal diff. A rule change touches the sentence that owns the rule, not the section. Never restate a rule in a second place; link the owner.
36
- - Oversized file. A change touching a file over 500 lines extracts the concern it touches - or the largest self-contained `##` section - into `reference/<topic>.md` in the same change, and leaves a one-line "read X now" pointer at the step that needs it.
38
+ - Skills only: Oversized file. A change touching a file over 500 lines extracts the concern it touches - or the largest self-contained `##` section - into `reference/<topic>.md` in the same change, and leaves a one-line "read X now" pointer at the step that needs it.
37
39
  - Recipe over prohibition. A shape or quality problem gets a statement of what the output is; a discipline problem gets one explicit rule. Never a list of things to avoid.
38
40
  - No nuance clauses. "Unless it matters", "when appropriate", "if needed" reopen the decision; delete them or name the observable condition.
39
- - Description is a trigger. State when to load the skill (symptoms, file types, situations); never summarize its steps.
41
+ - Skills only: Description is a trigger. State when to load the skill (symptoms, file types, situations); never summarize its steps.
40
42
  - One example. One concrete example per rule, never three.
41
43
  - Skip what the model knows. No explanations of git, shell, or the harness.
42
- - Never force-load. Cross-reference a skill by its invocation name (pi `/skill:name`, Claude Code `/name` or `/plugin:name`); never with `@`.
44
+ - Skills only: Never force-load. Cross-reference a skill by its invocation name (pi `/skill:name`, Claude Code `/name` or `/plugin:name`); never with `@`.
43
45
  - ASCII punctuation, compact `|---|` table separators, present tense.
44
46
 
47
+ ## Persona rules
48
+
49
+ A persona is an `agents/<name>.md` file dispatched by name (pi-cohort `subagent({ agent })`, Claude Code subagents); its body is the child's entire system prompt. Frontmatter fields: the "Agent frontmatter" table in pi-cohort's agents-and-chains doc; a project's pins: its personas doc ("Frontmatter knobs" in this repo's `doc/personas`). Every `## Authoring rules` bullet without a `Skills only:` marker binds here too.
50
+
51
+ - Whole prompt. pi-cohort assembles no base prompt, AGENTS.md, or skills catalog around the body unless `inheritProjectContext` or `inheritSkills` opts in; Claude Code replaces its default prompt with it. Open with the role and why it exists, state the output contract the caller parses before any method; when the caller parses a closing line, keep it last.
52
+ - Routing label. `description` is the one-sentence dispatch label the orchestrator's agent list shows: name when to dispatch, and write `Not for direct dispatch` when a skill owns the call. Never a summary of the body.
53
+ - Frontmatter pin. Leave every line the edit does not touch byte-identical, and leave frontmatter untouched unless the request names a knob - a pinned field removes the matching `agentOverrides` knob, so the request states the pin; a line the edit rewrites follows the ASCII rule like any other. Never pin `model`.
54
+ - Narrow tools. `tools` and `inherit*` list the operations the job performs and nothing else; a reviewer keeps `bash` when its caller injects a scoped command. A non-implementing persona with `bash` sets `completionGuard: false`; an implementing persona keeps the guard.
55
+ - Contract strings. A sentence a caller parses - closing line, mode discriminator, verdict word - changes only together with its caller; the project's validator pins them. Example: `lean: nothing to cut` stays verbatim.
56
+ - No skill machinery. No `/skill:` cross-reference and no `reference/` file: the body is read whole and nothing beside it is loaded (frontmatter `skills:` injects named skills; it is a knob, not a file split). A persona over 500 lines splits by job into a second persona.
57
+ - Reload. pi-cohort re-reads `agents/` on every dispatch, so an edit is live at the next call; Claude Code needs a new session.
58
+
45
59
  ## Edit procedure
46
60
 
47
61
  1. Read the whole file.
48
62
  2. Locate and edit the sentence that owns the rule you are changing (see Minimal diff).
49
63
  3. Read the changed lines back against `## Authoring rules`.
50
- 4. Run `wc -l` on the file; over 500 lines, extract per the oversized-file rule.
51
- 5. pi: run `/reload`. Claude Code: start a new session.
64
+ 4. Run wc -l; over 500 lines, a skill extracts per the oversized-file rule, a persona splits by job.
65
+ 5. Skill, pi: run /reload. Persona, pi: nothing. Claude Code: start a new session.
52
66
 
53
67
  ## Test (optional)
54
68
 
@@ -62,6 +76,7 @@ Run after adding a rule or rewording a discipline rule.
62
76
  |---|---|
63
77
  | pi | `subagent({ agent: "worker", context: "fresh", task })` twice, one task with the body appended |
64
78
  | Claude Code | The harness's subagent dispatch tool, twice, same split |
79
+ | pi, persona | baseline: `subagent({ agent: "worker", context: "fresh", task })` with the pre-edit body appended; loaded: `subagent({ agent: "<name>", context: "fresh", task })` after the edit - pi-cohort re-reads `agents/` per dispatch, so capture the pre-edit body first |
65
80
 
66
81
  ## Project overrides
67
82
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: roasting-the-spec
3
- description: Use after writing a spec, when a spec council is configured (the resolved piGauntlet.specCouncil council, via the gauntlet_setting tool, repo settings over the preset). Auto-dispatched by /skill:brainstorming as the critique pass when members is non-empty (no longer offered). N members on different models critique in parallel, a neutral chair consolidates and adjudicates, the parent applies its own dispositions and returns an audit for the user to ratify at brainstorming's gate.
3
+ description: Use after writing a spec, when a spec council is configured (the resolved piGauntlet.specCouncil council, via the gauntlet_setting tool, repo settings over the preset). Auto-dispatched by /skill:brainstorming as the critique pass when members is non-empty (no longer offered). N members on different models critique in parallel, a neutral chair consolidates and adjudicates, the parent applies its own dispositions and returns an audit that brainstorming commits with the spec, counts at its gate, and prints on request.
4
4
  ---
5
5
 
6
6
  # Roasting the Spec (Spec Council)
@@ -126,20 +126,20 @@ For each cluster in the chair's report, decide one of:
126
126
 
127
127
  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.
128
128
 
129
- 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: [<severity>] over-spec: <clause> — raised-by: [<slugs>] -> cut (was adds: M files / N tests / K ACs)` or `Applied: [<severity>] over-spec: <clause> — raised-by: [<slugs>] -> shrunk to <replacement> (was adds: ...)`, so the gate shows what was removed. `defer`/`reject` are unchanged.
129
+ 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: [<severity>] over-spec: <clause> — raised-by: [<slugs>] -> cut (was adds: M files / N tests / K ACs)` or `Applied: [<severity>] over-spec: <clause> — raised-by: [<slugs>] -> shrunk to <replacement> (was adds: ...)`, so row 3 at brainstorming's gate shows what was removed. `defer`/`reject` are unchanged.
130
130
 
131
131
  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.
132
132
 
133
133
  ### 4 — Emit the audit
134
134
 
135
- Return a structured audit, gate-only (not a committed spec section) — a coverage line plus three labelled lists:
135
+ Return a structured audit, for the commit body and row 3 only (not a committed spec section) — a coverage line plus three labelled lists:
136
136
 
137
137
  - `Coverage:` — `N of M members reported; <slug>: <reason>` — present only when member coverage was partial; omitted at full coverage.
138
138
  - `Applied:` — one line per applied cluster: `Applied: [<severity>] <cluster> — raised-by: [<slugs>] -> <edit> (grounded: <member probe>)`, `... -> <edit> (probed: <check> - <result>)` for a confirmed hypothesis, or `... -> open question (<not found | inconclusive: <check> | contradicted: <result>>)`. Copy `[<severity>]` and `raised-by: [...]` verbatim from the cluster line - telemetry joins on them. No applied cluster -> the single line `Applied: none`. The probe rides on the audit line because member files are removed in section 5.
139
139
  - `Deferred:` — one line per deferred cluster, same `[<severity>] <cluster> — raised-by: [<slugs>]` prefix, then `-> <where it belongs>`. None -> `Deferred: none`.
140
140
  - `Rejected:` — one line per rejected cluster, same prefix, then `-> <one-line reason>`. None -> `Rejected: none`.
141
141
 
142
- Hand this audit to brainstorming along with the now-final spec. brainstorming writes it into the **spec commit message body** (git-native, readable pre-squash) so it survives for finish-time revert visibility, then shows it to the user alongside the final spec at its one review gate. The user can revert any applied edit there — that gate, not this skill, is where ratification happens.
142
+ Hand this audit to brainstorming along with the now-final spec. brainstorming writes it into the **spec commit message body** (git-native, readable pre-squash) so it survives for finish-time revert visibility, shows a `Council:` counts line at its one review gate, and prints the audit verbatim when the user picks row 3. The user can revert any applied edit there - that gate, not this skill, is where ratification happens.
143
143
 
144
144
  ### 5 — Clean up
145
145
 
@@ -152,7 +152,7 @@ Single pass — no automatic re-roast loop. The user can invoke this skill again
152
152
  - Running the council when `piGauntlet.specCouncil.members` is absent or empty (brainstorming owns the gate and should have used the worker fallback).
153
153
  - 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).
154
154
  - Writing member files to a relative path (they land in the worktree).
155
- - Applying edits without surfacing the audit at brainstorming's gate — apply-before-the-gate is correct; apply-without-the-gate is not.
155
+ - Applying edits without the `Council:` counts and the row-3 audit at brainstorming's gate - apply-before-the-gate is correct; apply-without-the-gate is not.
156
156
  - Suppressing a finding instead of routing it to applied, deferred, or rejected in the audit.
157
157
  - Surfacing member-vs-member disagreements to the user instead of letting the chair adjudicate.
158
158
  - Editing anything other than the spec under `doc/specs/`.