pi-gauntlet 5.2.3 → 5.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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## v5.3.1 - 2026-09-06
4
+
5
+ - Task tracking: planning initializes the list once; execution starts and completes existing indices explicitly. Execution retries and fix loops reuse their task; conformance adds only new remediation gaps and reuses their `Gn` indices across rounds.
6
+ - `phase_tracker`: explicit implement/verify completion rejects recorded pending or in-progress tasks under the existing flow guards. Terminal-negative `failed` tasks remain failed; existing escalation rules still apply. Sequential tool execution prevents same-batch tracker updates from being read too early. Requires Pi 0.85.1 or newer; no new settings or tracker schema.
7
+ - Foreground flow dispatch: agent calls explicitly set `async: false`, preserving independent implementation, per-patch SR and council fan-outs. Final order is parent full verification -> whole-diff CR -> conformance; removes async review/test overlap and its classification/polling branches. Pi-cohort's `forceTopLevelAsync` setting is incompatible with this policy.
8
+ - Verification: runtime regressions and real Pi batch ordering pass. Behavioral diagnostics retain observed model-following limits: missed task reopening, an omitted async flag and malformed deferred-gap output. These are documented limits, not claims of infallible tracking or runtime enforcement of foreground dispatch. Spec: `doc/specs/2026-09-06-task-tracking-reliability.md`.
9
+
10
+ ## v5.3.0 - 2026-09-04
11
+
12
+ - `chase-bug`: the real-bug verdict menu gains a fourth row, "Implement hotfix now", for small, evidenced, urgent fixes - a middle ceremony tier between the trivial carve-out and the full gauntlet. It hands off to the new companion `skills/chase-bug/hotfix.md`: dedicated `hotfix/<slug>` worktree, one implementer (TDD), one code review as the last mutation gate, an advisory conformance pass, then an unpushed squash commit on the default branch with guaranteed worktree/branch cleanup (or a PR when the pick says "as a PR"). Three safety invariants (no schema/persistence change, no public-contract change, one-commit rollback) gate the row's availability; three judgment predicates steer `[recommended]`. Triage itself stays read-only; the row is reachable only through the verdict menu. Zero changes to finishing, SDD, verification, extensions, or ci.mjs. Spec: `doc/specs/2026-09-03-chase-bug-hotfix.md`.
13
+
3
14
  ## v5.2.3 - 2026-09-01
4
15
 
5
16
  - Isolated `subagent` dispatch examples now mint an OS-temp report directory and use absolute `output:` paths, preventing reports from being captured in and deleted with throwaway worktrees. The parallel-dispatch Output capture guidance now distinguishes that isolated failure mode from non-isolated shared-working-tree commit/overwrite risk. Fixes #23. Spec: `doc/specs/2026-09-01-gh-23-absolute-report-paths.md`.
package/README.md CHANGED
@@ -35,11 +35,11 @@ pi-gauntlet's only hard dependency is pi-cohort - every gate that dispatches a r
35
35
 
36
36
  Concretely, one change through the gauntlet:
37
37
 
38
- 0. *(Optional)* Before there's even a spec, `/skill:shape-ticket` can create or repair a single tracker issue - shaping a raw ask into a Context/Problem/Idea/Acceptance Criteria ticket, gated by an AC integrity check, a cheap council roast, and one human-confirmed write that may include one optional gated Reporter-note comment. A failed roast is retried once, then surfaced inline at the gate if it fails again. It's a tool, not a phase: no worktree, no plan/phase tracker, runs from any repo state. It never activates on its own (`disable-model-invocation: true`) - invoke it explicitly. Similarly, `/skill:chase-bug` triages a raw bug report into an evidenced verdict - and can hand off to shape-ticket or brainstorming - before any spec exists.
38
+ 0. *(Optional)* Before there's even a spec, `/skill:shape-ticket` can create or repair a single tracker issue - shaping a raw ask into a Context/Problem/Idea/Acceptance Criteria ticket, gated by an AC integrity check, a cheap council roast, and one human-confirmed write that may include one optional gated Reporter-note comment. A failed roast is retried once, then surfaced inline at the gate if it fails again. It's a tool, not a phase: no worktree, no plan/phase tracker, runs from any repo state. It never activates on its own (`disable-model-invocation: true`) - invoke it explicitly. Similarly, `/skill:chase-bug` triages a raw bug report into an evidenced verdict - and can hand off to shape-ticket, brainstorming, or a bounded hotfix - before any spec exists.
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
- 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.
42
- 4. **verify**: a whole-diff code review, then the **conformance gate** - a 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.
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.
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
 
@@ -69,9 +69,9 @@ Everything between gate 1 and gate 2 - task breakdown, implementation, both revi
69
69
 
70
70
  pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
71
71
 
72
- - **17 skills** - the workflow logic. Thirteen activate automatically when pi sees the matching kind of task, and each one gates the next: `brainstorming`, `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `writing-skills`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Four more are explicit-invocation-only (`disable-model-invocation: true`): `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; the project's verification command runs only as fallback), a rubric-based review, then a deterministic authorship-aware menu with stable finding IDs (P#/L#/C#/F#) and numbered pre-composed courses (fixes execute as a single parallel-safe wave: one gate run, one re-review, one push); nothing mutates (fixes, pushes, reviews, merges) until you pick a row - run it with `/skill:gatekeep-pr <pr>`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage - run it with `/skill:chase-bug`.
72
+ - **17 skills** - the workflow logic. Thirteen activate automatically when pi sees the matching kind of task, and each one gates the next: `brainstorming`, `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `writing-skills`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Four more are explicit-invocation-only (`disable-model-invocation: true`): `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; the project's verification command runs only as fallback), a rubric-based review, then a deterministic authorship-aware menu with stable finding IDs (P#/L#/C#/F#) and numbered pre-composed courses (fixes execute as a single parallel-safe wave: one gate run, one re-review, one push); nothing mutates (fixes, pushes, reviews, merges) until you pick a row - run it with `/skill:gatekeep-pr <pr>`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/hotfix/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage; the hotfix row hands off to `skills/chase-bug/hotfix.md` after the menu - run it with `/skill:chase-bug`.
73
73
  - **7 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `spec-council-member`, `spec-council-synthesizer`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
74
- - **3 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you push or open a PR without a passing test run since your last edit; a phase-tracker flow guard reminds on implement-phase commits missing spec/code review. phase-tracker also registers plan_check, a deterministic plan linter (8 mechanical plan-vs-spec checks) whose pass stamp gates implement-start inside a gauntlet flow. See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
74
+ - **3 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you push or open a PR without a passing test run since your last edit; a phase-tracker flow guard reminds on implement-phase commits missing spec/code review. In a brainstorming-entered flow, phase-tracker rejects `implement` or `verify` completion while tracker tasks remain pending or in progress; see [its configuration reference](./doc/configuration.md#phase-tracker). phase-tracker also registers plan_check, a deterministic plan linter (8 mechanical plan-vs-spec checks) whose pass stamp gates implement-start inside a gauntlet flow. See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
75
75
 
76
76
  pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this one pipeline, entered through `brainstorming`. Enforcement is opt-in by entry, not ambient: once brainstorming starts a flow, the phase-tracker extension mechanically blocks a phase from closing before its gate runs, and warns once if the main loop writes code during implement (subagents own implement-phase edits). A change made *without* entering the flow (a typo, a formatting run, a dependency bump - see "When to use / when NOT to use") is not gated; the discipline of routing real work through the pipeline is a convention the tooling supports, not a trap it springs on every edit.
77
77
 
@@ -89,12 +89,12 @@ pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this
89
89
 
90
90
  **Use it** for any change with more than one moving part: a feature, a refactor across files, anything where "what did we actually agree to build" matters by the time it's done.
91
91
 
92
- **Don't use it** for a one-line fix, a typo, or a throwaway spike you're going to discard. The gates have real overhead - a spec, a plan, a conformance check - and that overhead isn't worth paying for a change trivial enough to just make.
92
+ **Don't use it** for a one-line fix, a typo, or a throwaway spike you're going to discard. The gates have real overhead - a spec, a plan, a conformance check - and that overhead isn't worth paying for a change trivial enough to just make. Between that carve-out and the full pipeline sits one middle tier: `chase-bug`'s hotfix row - a small, evidenced, urgent fix landed as an unpushed squash, entered only through the triage verdict menu.
93
93
 
94
94
  ## Requirements
95
95
 
96
- - [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.1.0
97
- - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 - required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `writing-skills`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call.
96
+ - [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.85.1 - tested minimum for sequential `phase_tracker` tool execution, which persists a tracker update before a later tool call in the same model message.
97
+ - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 - required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `writing-skills`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics.
98
98
 
99
99
  Both packages must be listed in your `.pi/settings.json#packages` array (pi adds them automatically when you `pi install`). pi-gauntlet and pi-cohort are versioned independently but release together whenever dispatch semantics change - pin compatible versions of both.
100
100
 
@@ -56,11 +56,12 @@ function harness(options: { cwd?: string; branch?: unknown[]; idle?: boolean; be
56
56
  const tools: { name: string; execute: (...args: any[]) => unknown }[] = [];
57
57
  const sent: { message: any; options: any }[] = [];
58
58
  let idle = options.idle ?? true;
59
+ let branch = options.branch ?? [];
59
60
  const ctx = {
60
61
  cwd: options.cwd ?? tempCwd(),
61
62
  hasUI: false,
62
63
  isIdle: () => idle,
63
- sessionManager: { getBranch: () => options.branch ?? [] },
64
+ sessionManager: { getBranch: () => branch },
64
65
  };
65
66
  const pi = {
66
67
  on(event: string, handler: (event: unknown, context: unknown) => unknown) {
@@ -68,7 +69,7 @@ function harness(options: { cwd?: string; branch?: unknown[]; idle?: boolean; be
68
69
  registered.push(handler);
69
70
  handlers.set(event, registered);
70
71
  },
71
- registerTool(tool: { name: string; execute: (...args: any[]) => unknown }) {
72
+ registerTool(tool: { name: string; executionMode?: string; execute: (...args: any[]) => unknown }) {
72
73
  tools.push(tool);
73
74
  },
74
75
  sendMessage(message: unknown, sendOptions: unknown) {
@@ -86,7 +87,7 @@ function harness(options: { cwd?: string; branch?: unknown[]; idle?: boolean; be
86
87
  for (const handler of handlers.get(name) ?? []) results.push(await handler(event, ctx));
87
88
  return results;
88
89
  };
89
- return { emit, emitEvent, sent, tools, ctx, setIdle: (next: boolean) => (idle = next) };
90
+ return { emit, emitEvent, sent, tools, ctx, setIdle: (next: boolean) => (idle = next), setBranch: (next: unknown[]) => (branch = next) };
90
91
  }
91
92
 
92
93
  const settle = async (h: ReturnType<typeof harness>) => {
@@ -289,6 +290,163 @@ test("resumed session: closure gate blocks complete verify without a conformance
289
290
  assert.equal(res.details.error, "no conformance-reviewer dispatch observed");
290
291
  });
291
292
 
293
+ const taskSnapshot = (tasks: { name: string; status: string }[], isError = false) => ({
294
+ type: "message",
295
+ message: { role: "toolResult", toolName: "plan_tracker", isError, details: { tasks } },
296
+ });
297
+
298
+ const completePhase = async (h: ReturnType<typeof harness>, phase: "implement" | "verify") => {
299
+ const tool = h.tools.find((t) => t.name === "phase_tracker")!;
300
+ return (await tool.execute("complete", { action: "complete", phase }, undefined, undefined, h.ctx)) as {
301
+ content: { text: string }[];
302
+ details: { error?: string; phases: Record<Phase, { status: string }> };
303
+ };
304
+ };
305
+
306
+ test("phase_tracker registration requests sequential execution", () => {
307
+ const h = harness();
308
+ assert.equal(h.tools.find((t) => t.name === "phase_tracker")!.executionMode, "sequential");
309
+ });
310
+
311
+ test("all-complete plan activity auto-completes an active implement phase", async () => {
312
+ const h = harness({ branch: implementBranch() });
313
+ await h.emit("session_start");
314
+ await h.emitEvent("tool_execution_end", {
315
+ toolName: "plan_tracker",
316
+ isError: false,
317
+ result: { details: { tasks: [{ status: "complete" }, { status: "complete" }] } },
318
+ });
319
+ const tool = h.tools.find((t) => t.name === "phase_tracker")!;
320
+ const status = (await tool.execute("t1", { action: "status" }, undefined, undefined, h.ctx)) as {
321
+ details: { phases: { implement: { status: string } } };
322
+ };
323
+ assert.equal(status.details.phases.implement.status, "complete");
324
+ });
325
+
326
+ test("cold implement and verify completions ignore unfinished snapshots", async () => {
327
+ for (const phase of ["implement", "verify"] as const) {
328
+ const h = harness({
329
+ branch: [
330
+ phaseResult("start", phases({ [phase]: "in_progress" })),
331
+ taskSnapshot([{ name: "standalone task", status: "pending" }]),
332
+ ],
333
+ });
334
+ await h.emit("session_start");
335
+ const completed = await completePhase(h, phase);
336
+ assert.equal(completed.details.error, undefined, phase);
337
+ assert.equal(completed.details.phases[phase].status, "complete", phase);
338
+ }
339
+ });
340
+
341
+ test("completion backstop rejects unfinished snapshot indices, preserves state, and permits same-index retry", async () => {
342
+ const branch: unknown[] = [
343
+ ...resumedBranch({ plan: "complete", implement: "complete", verify: "in_progress" }),
344
+ subagentResult(["conformance-reviewer"]),
345
+ taskSnapshot([
346
+ { name: "T1 implementation", status: "complete" },
347
+ { name: "G1 conformance", status: "pending" },
348
+ { name: "G2 conformance", status: "in_progress" },
349
+ ]),
350
+ ];
351
+ const h = harness({ branch });
352
+ await h.emit("session_start");
353
+ const rejected = await completePhase(h, "verify");
354
+ assert.equal(rejected.details.error, "unfinished tasks");
355
+ assert.equal(rejected.details.phases.verify.status, "in_progress");
356
+ assert.match(rejected.content[0].text, /1: G1 conformance \(pending\)/);
357
+ assert.match(rejected.content[0].text, /2: G2 conformance \(in_progress\)/);
358
+
359
+ branch.push(taskSnapshot([
360
+ { name: "T1 implementation", status: "complete" },
361
+ { name: "G1 conformance", status: "complete" },
362
+ { name: "G2 conformance", status: "complete" },
363
+ ]));
364
+ const completed = await completePhase(h, "verify");
365
+ assert.equal(completed.details.error, undefined);
366
+ assert.equal(completed.details.phases.verify.status, "complete");
367
+ });
368
+
369
+ test("completion backstop uses the latest successful current-branch snapshot", async () => {
370
+ const branch: unknown[] = [
371
+ ...resumedBranch({ plan: "complete", implement: "in_progress" }),
372
+ taskSnapshot([{ name: "old", status: "pending" }]),
373
+ taskSnapshot([{ name: "errored", status: "pending" }], true),
374
+ ];
375
+ const h = harness({ branch });
376
+ await h.emit("session_start");
377
+ const rejected = await completePhase(h, "implement");
378
+ assert.equal(rejected.details.error, "unfinished tasks");
379
+ assert.match(rejected.content[0].text, /0: old \(pending\)/);
380
+
381
+ branch.push(taskSnapshot([])); // clear/init supersedes the old snapshot
382
+ assert.equal((await completePhase(h, "implement")).details.phases.implement.status, "complete");
383
+
384
+ const resetOnlyBranch: unknown[] = [
385
+ ...resumedBranch({ plan: "complete", implement: "in_progress" }),
386
+ taskSnapshot([{ name: "retained through reset", status: "in_progress" }]),
387
+ phaseResult("reset", phases()),
388
+ ...resumedBranch({ plan: "complete", implement: "in_progress" }),
389
+ ];
390
+ const resetOnly = harness({ branch: resetOnlyBranch });
391
+ await resetOnly.emit("session_start");
392
+ assert.equal((await completePhase(resetOnly, "implement")).details.error, "unfinished tasks");
393
+ resetOnlyBranch.push(taskSnapshot([{ name: "retained through reset", status: "complete" }]));
394
+ assert.equal((await completePhase(resetOnly, "implement")).details.phases.implement.status, "complete");
395
+ });
396
+
397
+ test("completion backstop preserves exclusions and closure-error precedence", async () => {
398
+ const unfinished = taskSnapshot([{ name: "failed", status: "failed" }]);
399
+ const explicitImplement = harness({ branch: [...resumedBranch({ plan: "complete", implement: "in_progress" }), unfinished] });
400
+ await explicitImplement.emit("session_start");
401
+ assert.equal((await completePhase(explicitImplement, "implement")).details.phases.implement.status, "complete");
402
+
403
+ const closureFirst = harness({
404
+ branch: [...resumedBranch({ plan: "complete", implement: "complete", verify: "in_progress" }), taskSnapshot([{ name: "T1", status: "pending" }])],
405
+ });
406
+ await closureFirst.emit("session_start");
407
+ assert.equal((await completePhase(closureFirst, "verify")).details.error, "no conformance-reviewer dispatch observed");
408
+
409
+ const disabled = harness({
410
+ cwd: tempCwd({ piGauntlet: { flowGuards: { enforce: false } } }),
411
+ branch: [...resumedBranch({ plan: "complete", implement: "in_progress" }), taskSnapshot([{ name: "T1", status: "pending" }])],
412
+ });
413
+ await disabled.emit("session_start");
414
+ assert.equal((await completePhase(disabled, "implement")).details.phases.implement.status, "complete");
415
+
416
+ const adHoc = harness({ branch: [taskSnapshot([{ name: "T1", status: "pending" }])] });
417
+ await adHoc.emit("session_start");
418
+ const tool = adHoc.tools.find((t) => t.name === "phase_tracker")!;
419
+ assert.equal((await tool.execute("x", { action: "complete", phase: "plan" }, undefined, undefined, adHoc.ctx)).details.error, undefined);
420
+ });
421
+
422
+ test("completion backstop leaves no/empty snapshots and skip alone, and uses the switched branch", async () => {
423
+ const noSnapshot = harness({ branch: resumedBranch({ plan: "complete", implement: "in_progress" }) });
424
+ await noSnapshot.emit("session_start");
425
+ assert.equal((await completePhase(noSnapshot, "implement")).details.phases.implement.status, "complete");
426
+
427
+ const skipped = harness({
428
+ branch: [...resumedBranch({ plan: "complete", implement: "in_progress" }), taskSnapshot([{ name: "T1", status: "pending" }])],
429
+ });
430
+ await skipped.emit("session_start");
431
+ const skipTool = skipped.tools.find((t) => t.name === "phase_tracker")!;
432
+ assert.equal((await skipTool.execute("skip", { action: "skip", phase: "implement", reason: "waived" }, undefined, undefined, skipped.ctx)).details.error, undefined);
433
+
434
+ const switched = harness({
435
+ branch: [...resumedBranch({ plan: "complete", implement: "in_progress" }), taskSnapshot([{ name: "off branch", status: "pending" }])],
436
+ });
437
+ await switched.emit("session_start");
438
+ switched.setBranch([...resumedBranch({ plan: "complete", implement: "in_progress" }), taskSnapshot([{ name: "active", status: "complete" }])]);
439
+ await switched.emit("session_switch");
440
+ assert.equal((await completePhase(switched, "implement")).details.phases.implement.status, "complete");
441
+
442
+ const closureOff = harness({
443
+ cwd: tempCwd({ piGauntlet: { closureReview: { enforce: false } } }),
444
+ branch: [...resumedBranch({ plan: "complete", implement: "complete", verify: "in_progress" }), taskSnapshot([{ name: "T1", status: "pending" }])],
445
+ });
446
+ await closureOff.emit("session_start");
447
+ assert.equal((await completePhase(closureOff, "verify")).details.error, "unfinished tasks");
448
+ });
449
+
292
450
  const subagentResult = (agents: string[]) => ({
293
451
  type: "message",
294
452
  message: {
@@ -774,6 +774,7 @@ export default function (pi: ExtensionAPI) {
774
774
  "Track workflow phase progress (brainstorm → plan → implement → verify → ship); " +
775
775
  "ad-hoc calls do not arm gates. Not for ad-hoc use.",
776
776
  parameters: PhaseTrackerParams,
777
+ executionMode: "sequential",
777
778
 
778
779
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
779
780
  switch (params.action) {
@@ -891,6 +892,43 @@ export default function (pi: ExtensionAPI) {
891
892
  } as PhaseTrackerDetails,
892
893
  };
893
894
  }
895
+ if (
896
+ gauntletEntered &&
897
+ (params.phase === "implement" || params.phase === "verify") &&
898
+ resolveFlowGuards(loadGauntletSettings(ctx.cwd).gauntlet).enforce
899
+ ) {
900
+ let tasks: { name: string; status: string }[] = [];
901
+ for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
902
+ if (
903
+ entry.type !== "message" ||
904
+ entry.message.role !== "toolResult" ||
905
+ entry.message.toolName !== "plan_tracker" ||
906
+ entry.message.isError
907
+ ) {
908
+ continue;
909
+ }
910
+ const details = entry.message.details as { tasks?: { name: string; status: string }[]; error?: string } | undefined;
911
+ if (!details || details.error || !details.tasks) continue;
912
+ tasks = details.tasks;
913
+ break;
914
+ }
915
+ const unfinished = tasks.flatMap((task, index) =>
916
+ task.status === "pending" || task.status === "in_progress"
917
+ ? [`${index}: ${task.name} (${task.status})`]
918
+ : [],
919
+ );
920
+ if (unfinished.length) {
921
+ return {
922
+ content: [
923
+ {
924
+ type: "text",
925
+ text: `Cannot complete ${params.phase}: unfinished tasks:\n${unfinished.join("\n")}\nReconcile these same indices against acceptance evidence, update them and retry.`,
926
+ },
927
+ ],
928
+ details: { action: "complete", phases: { ...phases }, error: "unfinished tasks" } as PhaseTrackerDetails,
929
+ };
930
+ }
931
+ }
894
932
  phases = { ...phases, [params.phase]: transitionPhaseState("complete") as PhaseState };
895
933
  firedGuards.clear();
896
934
  updateWidget(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "5.2.3",
3
+ "version": "5.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",
@@ -36,6 +36,12 @@ The line: exercising the system **as it is today** is research; exercising the *
36
36
 
37
37
  This skill ends with a **written, user-reviewed spec inside a worktree**. Nothing else.
38
38
 
39
+ ## Foreground dispatch policy
40
+
41
+ Flow-owned execution dispatches run in the foreground: set top-level `async: false` on gather, critique, council, summary, implementation, review, conformance, and retry calls. `forceTopLevelAsync` must remain unset or false; it is incompatible with this flow. See [pi-cohort dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md). If a dispatch returns an async handle despite `async: false`, stop and report the configuration error: do not poll it, relaunch work, or advance the flow. An intercom-detached child is likewise incomplete work; use the existing coordination path and never accept or duplicate it.
42
+
43
+ Foreground does not serialize independent work: preserve existing isolated parallel `tasks` batches and await their terminal results before acceptance or tracker/phase advancement.
44
+
39
45
  ## Checklist
40
46
 
41
47
  Work through the items below **in order**. This is your own checklist to follow, not a `plan_tracker` plan — brainstorming is open-ended exploration, and `plan_tracker` is execution-only (the implement phase). The terminal state is the user review gate; after approval the **only** next skill is `/skill:writing-plans`. Do not jump to implementation, and do not silently drop the critique pass.
@@ -280,7 +286,7 @@ The first three checks — **placeholder scan**, **internal consistency**, and *
280
286
  - **Otherwise** → dispatch one fresh `worker` that applies the scope + ambiguity checks and fixes them in place:
281
287
 
282
288
  ```
283
- subagent({ agent: "worker", context: "fresh", cwd: "<abs worktree path, from git rev-parse --show-toplevel>", task:
289
+ subagent({ agent: "worker", context: "fresh", async: false, cwd: "<abs worktree path, from git rev-parse --show-toplevel>", task:
284
290
  "Problem statement: <the problem the spec addresses + the user's stated intent>.\n" +
285
291
  "Read the spec at <abs path to doc/specs/...>. Edit ONLY that file. Apply two checks and\n" +
286
292
  "fix what you find in place: (1) Scope — does every paragraph serve the goal? Cut filler;\n" +
@@ -311,7 +317,7 @@ SUMMARY_PATH=$(mktemp "${TMPDIR:-/tmp}/gauntlet-spec-summary.XXXXXX") # absolu
311
317
  ```
312
318
 
313
319
  ```
314
- subagent({ agent: "spec-summarizer", context: "fresh", cwd: "<abs worktree path, from git rev-parse --show-toplevel>",
320
+ subagent({ agent: "spec-summarizer", context: "fresh", async: false, cwd: "<abs worktree path, from git rev-parse --show-toplevel>",
315
321
  output: "<SUMMARY_PATH>", outputMode: "file-only", task:
316
322
  "Summarize the spec at <abs path to doc/specs/...> for the user review gate. Read ONLY that file." })
317
323
  ```
@@ -12,12 +12,14 @@ Mint a temp dir outside the worktree (never committed):
12
12
  GATHER_DIR=$(mktemp -d)
13
13
  ```
14
14
 
15
- Set the substep, then dispatch one parallel-tasks `subagent` call (foreground no
16
- `async:`; no `model:` — pi-cohort `agentOverrides` owns builder models):
15
+ Set the substep, then dispatch one foreground parallel-tasks `subagent` call (`async: false`; no
16
+ `model:` — pi-cohort `agentOverrides` owns builder models). Preserve the parallel batch and await
17
+ its terminal result before assembling the draft:
17
18
 
18
19
  ```
19
20
  phase_tracker({ action: "substep", phase: "brainstorm", substep: "gather" })
20
21
  subagent({
22
+ async: false,
21
23
  tasks: [
22
24
  { agent: "scout", cwd: "<abs worktree path>", phase: "context-gather",
23
25
  output: "<GATHER_DIR>/scout.md",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: chase-bug
3
- description: Use when a human hands you a bug report to triage - a Slack paste, tracker ticket, GitHub issue, or described symptom - and the goal is an evidenced verdict (real bug, not-a-bug, cannot reproduce, already fixed or reported), not a fix.
3
+ description: Use when a human hands you a bug report to triage - a Slack paste, tracker ticket, GitHub issue, or described symptom - and the goal is an evidenced verdict (real bug, not-a-bug, cannot reproduce, already fixed or reported); triage itself never fixes - a real-bug verdict may hand off to a bounded hotfix after the menu.
4
4
  disable-model-invocation: true
5
5
  ---
6
6
 
@@ -8,13 +8,16 @@ disable-model-invocation: true
8
8
 
9
9
  ## Overview
10
10
 
11
- Triage a bug report to an evidenced verdict, never a fix.
11
+ Triage a bug report to an evidenced verdict. Triage never
12
+ fixes; a real-bug verdict may hand off to a bounded hotfix (hotfix.md) after
13
+ the menu.
12
14
 
13
15
  ## Boundaries
14
16
 
15
17
  - Reads: anything - code, history, tracker, origin text.
16
18
  - Writes: `$TMPDIR` scratch only (repro captures, notes), plus at most one gated push to the
17
- origin's response channel at the very end.
19
+ origin's response channel at the very end; after a valid hotfix pick: the hotfix
20
+ worktree and one default-branch squash commit, per hotfix.md.
18
21
  - Does NOT: touch tracked files; touch tracker state (never closes, relabels, or
19
22
  reassigns an existing issue).
20
23
  - The zero-mutation invariant below mechanically enforces tracked-file immutability
@@ -25,7 +28,8 @@ Triage a bug report to an evidenced verdict, never a fix.
25
28
 
26
29
  ## Hard constraint
27
30
 
28
- **No verdict without evidenced root cause; no fix, ever.** (write surface: see
31
+ **No verdict without evidenced root cause; no fix during
32
+ triage. After a valid hotfix pick, writes follow hotfix.md.** (write surface: see
29
33
  Boundaries; enforcement: see the zero-mutation invariant below.)
30
34
 
31
35
  The invariant is baseline-relative, checked at three points. Never revert
@@ -41,17 +45,24 @@ pre-existing work - only ever revert damage this skill caused.
41
45
  rendering the summary). Re-run the same command and confirm it still matches
42
46
  the baseline.
43
47
 
48
+ **Handoff check** (hotfix row only). Immediately before handing off to
49
+ `hotfix.md`, run the same command on the primary checkout and require it to
50
+ match the baseline. Checkpoint 3 still runs at skill end, on the primary
51
+ checkout: a landed squash
52
+ commit and a restored branch leave tracked porcelain clean, so the invariant
53
+ holds literally on both exits.
54
+
44
55
  ## When to Use
45
56
 
46
57
  - A human pastes a bug report (Slack message, tracker ticket, GitHub issue, plain
47
58
  description of broken behavior) and wants to know whether it is real.
48
59
  - The ask is "is this a bug" / "can we reproduce this" / "what's causing this",
49
60
  not "fix this".
61
+ - An evidenced, urgent "fix this" request also enters
62
+ here - triage stops at the menu, where the hotfix row is offered.
50
63
 
51
64
  ## When NOT to Use
52
65
 
53
- - The report already has an evidenced root cause and the ask is to implement a
54
- fix - exit into `/skill:brainstorming` directly.
55
66
  - The item is an already-shaped ticket ready for implementation, not a report
56
67
  needing triage.
57
68
 
@@ -153,33 +164,64 @@ Render **only the matching action set** - never merge real-bug and negative-verd
153
164
  menus into one list. The human may **overrule the verdict in prose** - that is a
154
165
  change request, not a menu row.
155
166
 
156
- **Real bug** - three actions (all rendered unless noted), exactly one tagged
157
- `[recommended]`:
167
+ **Real bug** - four actions (all rendered unless noted), exactly one tagged
168
+ `[recommended]`. Untracked origin:
158
169
 
159
170
  ```
160
- 1. [ ] File a ticket - one /skill:shape-ticket create-mode invocation, seeded
161
- with this evidence.
171
+ 1. [ ] File a ticket - /skill:shape-ticket, seeded with this evidence.
162
172
  2. [ ] Brainstorm now - /skill:brainstorming with this evidence as the seed.
163
- Handoff happens AFTER gate 2 (step 5).
173
+ 3. [ ] Implement hotfix now - follow hotfix.md; add "as a PR" for a PR.
174
+ 4. [ ] Respond to reporter only.
175
+ ```
176
+
177
+ Tracker/GitHub origin (ticket row omitted as today, renumbered):
178
+
179
+ ```
180
+ 1. [ ] Brainstorm now - /skill:brainstorming with this evidence as the seed.
181
+ 2. [ ] Implement hotfix now - follow hotfix.md; add "as a PR" for a PR.
164
182
  3. [ ] Respond to reporter only.
165
183
  ```
166
184
 
167
- For unaddressable origins, action 3 reads `Finish with rendered summary`
168
- instead of "Respond to reporter only", and action 2's handoff happens after
169
- the rendered summary instead of gate 2. Exactly one rendered action still
170
- carries `[recommended]`.
185
+ Unaddressable variants adjust the last row's label exactly as today. Exactly
186
+ one `[recommended]` preserved. The eligibility line renders under the menu:
187
+ `eligible` or `not eligible: <predicate>`, from the predicate evaluation below.
171
188
 
172
- If the origin is itself a tracker/GitHub ticket, it's already tracked: omit
173
- action 1 and renumber the remaining two as 1 (Brainstorm now) and 2 (Respond
174
- to reporter only). Exactly one rendered action still carries `[recommended]`.
189
+ For unaddressable origins, the last action reads `Finish with rendered summary`
190
+ instead of "Respond to reporter only", and the Brainstorm handoff happens after
191
+ the rendered summary instead of gate 2.
175
192
 
176
- Heuristic for the `[recommended]` tag: pressing (user-facing break, data loss,
177
- security) or trivially fixable -> recommend brainstorm now; real but deferrable
178
- -> recommend file a ticket; blocked on another party (needs reporter input,
179
- upstream fix, another team) -> recommend respond-only (rendered as "Finish with
180
- rendered summary" for unaddressable origins). Root cause found but the
181
- fix cost is unclear still stays a **real-bug** verdict - state the uncertainty
182
- plainly in the fault story, do not downgrade the verdict to hedge on cost.
193
+ **Predicates and row availability.** Evaluate the six predicates in
194
+ `hotfix.md` once, here, from the root cause - a prediction of the fix shape
195
+ (read-only judgment). A failed safety invariant (1-3) renders the row as
196
+ `Implement hotfix now - unavailable: <invariant>`: never pickable, never
197
+ `[recommended]`; a pick of it is a change request. Judgment predicates (4-6)
198
+ only steer `[recommended]` and the eligibility line.
199
+
200
+ Heuristic for the `[recommended]` tag - rows top-down, first match wins:
201
+
202
+ | Situation | `[recommended]` |
203
+ |---|---|
204
+ | Pressing or trivially fixable, all predicates pass | Hotfix now |
205
+ | Pressing, any predicate fails | Brainstorm now |
206
+ | Trivially fixable, any predicate fails | Brainstorm now |
207
+ | Real but deferrable | File a ticket (tracker-origin: respond-only) |
208
+ | Blocked on another party | Respond-only |
209
+
210
+ Respond-only renders as "Finish with rendered summary" for unaddressable
211
+ origins. Root cause found but the fix cost is unclear still stays a
212
+ **real-bug** verdict - state the uncertainty plainly in the fault story, do
213
+ not downgrade the verdict to hedge on cost.
214
+
215
+ **Hotfix handoff.** On a hotfix pick, write the handoff record to
216
+ `$TMPDIR/hotfix-<slug>.md`: `delivery-mode` (`squash`, or `pr` when the pick
217
+ says "as a PR"); the evidence pack (fault story; trigger, observed, and
218
+ expected values from Phase 1; repro command; `file:line`; falsification
219
+ result); `slug` (kebab-case, from the symptom); origin type and response
220
+ target (step 1); the predicate evaluation. Run the handoff check (see the
221
+ zero-mutation invariant), then follow `hotfix.md`, which reads only this
222
+ record. On a hotfix abort, `hotfix.md` runs its baseline re-check and this
223
+ menu re-renders - the same gate, re-fired: with the hotfix row when the abort
224
+ created nothing, without it otherwise.
183
225
 
184
226
  **Negative verdicts** - exactly five, each with its own named citation source:
185
227
 
@@ -236,6 +278,10 @@ any handoff:
236
278
  response citing the new ticket link -> gate 2 -> done.
237
279
  - Brainstorm now chosen -> draft the response first ("confirmed, investigating
238
280
  now - fix to follow") -> gate 2 -> **then** hand off to `/skill:brainstorming`.
281
+ - Hotfix now chosen -> hand off to `hotfix.md` first; on completion, draft the
282
+ response citing `fixed in <SHA>` or the PR link -> gate 2 (`send it`) -> done.
283
+ Gate 2 fires once. On abort, `hotfix.md` returns to the step-4 menu; step 5
284
+ then runs for the new pick, the draft citing the abort reason.
239
285
  - Respond-only, or any negative verdict -> draft -> gate 2 -> done.
240
286
 
241
287
  **Draft template:**
@@ -298,6 +344,9 @@ action renders the verdict as a **summary to the human**, then the skill ends
298
344
  link.
299
345
  - Brainstorm now chosen -> render the summary -> **then** hand off to
300
346
  `/skill:brainstorming`.
347
+ - Hotfix now chosen -> hand off to `hotfix.md`; on completion render the
348
+ summary citing `fixed in <SHA>` or the PR link -> done. On abort, return to
349
+ the step-4 menu.
301
350
  - Finish with rendered summary chosen -> render the summary -> done.
302
351
 
303
352
  **Summary template** (same four fields as the draft - the difference is
@@ -319,7 +368,7 @@ rendered is out of skill scope - the skill has ended.
319
368
 
320
369
  | Verdict | Citation source | Response next-step |
321
370
  |---|---|---|
322
- | Real bug | Falsification test run + passed | Ticket link, fix branch, or ack |
371
+ | Real bug | Falsification test run + passed | Ticket link, squash SHA, fix branch, or ack |
323
372
  | `not-a-bug` | Contract satisfied (spec/schema/API doc) | Explain the contract |
324
373
  | `intended-behavior` | Decision record (design doc/ADR/commit) | Point to the decision |
325
374
  | `cannot-replicate` | Phase 1 repro attempts, missing input named | Ask reporter for missing input; offer discovery ticket |
@@ -337,11 +386,14 @@ falls through to the 404 handler.
337
386
  Proof: `curl /widgets/` -> 404 | src/router.ts:88 | expected match, got none
338
387
 
339
388
  1. [ ] File a ticket - /skill:shape-ticket, seeded with the above.
340
- 2. [x] Brainstorm now - user-facing 404 on a common URL shape. [recommended]
341
- 3. [ ] Respond to reporter only.
389
+ 2. [ ] Brainstorm now - /skill:brainstorming with the above as the seed.
390
+ 3. [x] Implement hotfix now - follow hotfix.md; add "as a PR" for a PR. [recommended]
391
+ 4. [ ] Respond to reporter only.
392
+
393
+ eligible - all predicates pass
342
394
  ```
343
395
 
344
- (The example assumes an addressable origin - action 3's label is the addressable one.)
396
+ (The example assumes an addressable origin - action 4's label is the addressable one.)
345
397
 
346
398
  **Negative-verdict example** (citation-source contrast):
347
399
 
@@ -361,7 +413,7 @@ nested resources" (the decision that made it so).
361
413
 
362
414
  | Excuse | Reality |
363
415
  |---|---|
364
- | "Trivial fix, faster to just do it" | Fixing during triage is the one thing this skill forbids - hand it to the human at the menu, always |
416
+ | "Trivial fix, faster to just do it" | Fixing during triage is forbidden - the hotfix row after the menu is the sanctioned path |
365
417
  | "Root cause is obvious, skip falsification" | Obvious and evidenced are different things - run the test or report it blocked |
366
418
  | "Reporter is waiting, skip the gate" | The gate is what makes the response trustworthy - urgency is not a bypass |
367
419
  | "I already know there's no prior report" | A guess isn't a search - use the ladder or declare it unreachable |
@@ -0,0 +1,198 @@
1
+ # Hotfix (chase-bug supplementary)
2
+
3
+ Consumed only by `SKILL.md` in this directory, at the real-bug menu handoff. Input:
4
+ the handoff record at `$TMPDIR/hotfix-<slug>.md` - read nothing else about the bug.
5
+ Automatic end to end: the menu pick was the consent. The only human gate left is
6
+ chase-bug's `send it`, reached in step 10 below.
7
+
8
+ ## Boundaries
9
+
10
+ - Reads: anything.
11
+ - Writes: the hotfix worktree; exactly one squash commit on `<default>`, made in the
12
+ primary checkout with a clean index, `<orig-branch>` restored after; `$TMPDIR`.
13
+ - Does NOT: run tests or create files in the primary checkout; push without an
14
+ explicit request; touch tracker state; commit on the source checkout during setup;
15
+ touch pre-existing dirt or any resource this run did not create.
16
+
17
+ ## Ownership
18
+
19
+ Two flags, `created-worktree` and `created-branch`, set only when `git worktree add`
20
+ succeeds (it creates both). Every destructive command below is gated on its flag.
21
+
22
+ ## Procedure
23
+
24
+ 1. **Read the record; print the eligibility line.** Reprint the menu-time
25
+ evaluation verbatim: `eligible` or `not eligible: <predicate>`. Never
26
+ re-derive, never block.
27
+ 2. **Resolve before mutating.** `SCOPED_TEST_COMMANDS`: the gauntlet overrides
28
+ file's verification section (`## verification-before-completion`, or a section
29
+ matching the verification topic), else `AGENTS.md` or the project's documented
30
+ test command. The full project test target is acceptable. Add the record's repro
31
+ command when present. No exact command -> abort (pre-land). Draft the implementer
32
+ task text from the record; not writable -> abort (pre-land). Nothing exists yet.
33
+ 3. **Worktree.** From the primary checkout:
34
+
35
+ ```bash
36
+ PRIMARY_ROOT=$(git rev-parse --show-toplevel)
37
+ DEFAULT=$(git symbolic-ref --short refs/remotes/origin/HEAD) && DEFAULT=${DEFAULT#origin/}
38
+ BASE_SHA=$(git rev-parse "$DEFAULT")
39
+ ORIG_BRANCH=$(git branch --show-current)
40
+ git status --porcelain > "$TMPDIR/hotfix-<slug>.baseline"
41
+ ```
42
+
43
+ Abort (pre-land, nothing created) when: `git rev-parse --git-dir` differs from
44
+ `git rev-parse --git-common-dir` (inside a linked worktree); `DEFAULT` empty
45
+ (never guess a squash target); `ORIG_BRANCH` empty (detached HEAD); `git status
46
+ --porcelain --untracked-files=no` non-empty; `git check-ignore -q .worktrees/`
47
+ fails (never commit on the source checkout); `git show-ref --verify -q
48
+ refs/heads/hotfix/<slug>` succeeds or `.worktrees/hotfix/<slug>` exists (never
49
+ reuse, never force); `git worktree add` itself failing (creation failure).
50
+
51
+ ```bash
52
+ git worktree add ".worktrees/hotfix/<slug>" -b "hotfix/<slug>" "$DEFAULT"
53
+ ```
54
+
55
+ Success sets both flags. Run the project's dependency install inside the
56
+ worktree. Every dispatch below: `cwd` = the worktree path, the record path in the
57
+ task text, `output:` (when used) an absolute `$TMPDIR` path.
58
+ 4. **Implement.** One `implementer`, fork context:
59
+
60
+ > Read `$TMPDIR/hotfix-<slug>.md`; the evidence pack is also inlined here:
61
+ > <evidence pack>. The evidence pack replaces plan and spec; do
62
+ > not report BLOCKED for a missing plan. TDD: write the regression test, run it,
63
+ > confirm it fails, then the minimal fix (dependency-bump fallback: the repro
64
+ > re-run is the regression evidence). SCOPED_TEST_COMMANDS: <commands>. Commit on
65
+ > `hotfix/<slug>` before reporting. End with the SDD status line verbatim.
66
+
67
+ `DONE` -> step 5. `DONE_WITH_CONCERNS` -> step 5 unless a concern names a safety
68
+ invariant -> abort. `NEEDS_CONTEXT` or `BLOCKED` -> abort. A regression command
69
+ named in the report joins `SCOPED_TEST_COMMANDS` only if it uses the resolved
70
+ command's runner.
71
+ 5. **Test.** Run `SCOPED_TEST_COMMANDS` in the worktree. Red -> one implementer
72
+ retry -> red -> abort.
73
+ 6. **Verify (advisory).** One fresh `conformance-reviewer`. Its entire origin, in
74
+ the task text, located as `prompt`:
75
+
76
+ > R1: the trigger `<trigger>` now yields `<expected>` (was `<observed>`).
77
+ > R2: regression evidence exists (test or repro re-run).
78
+ > R3: the diff stays inside the envelope: files implementing the mechanism at
79
+ > the recorded `file:line`, the regression test, and (dependency exception)
80
+ > manifest + lockfile.
81
+
82
+ Parent filter: only rows located as `prompt` count; a row sourced from a spec
83
+ file or ticket is not-a-gap. Real gaps -> one implementer round -> re-run step
84
+ 5 -> no second audit; unfixable -> abort. Dispatch failure or malformed output
85
+ -> note it, continue.
86
+ 7. **Review - last mutation gate.** One fresh `code-reviewer` over
87
+ `<base-sha>..HEAD` in the worktree. Task text: the record path,
88
+ `SCOPED_TEST_COMMANDS`, invariants 1-3 and predicates 4-6 as named review items
89
+ (a predicate miss is Moderate unless it trips an invariant). `SHIP` -> step 8.
90
+ `FIX_FIRST` -> one implementer round fixing every Critical and Moderate ->
91
+ re-run step 5 -> one re-review; `SHIP` -> step 8, else abort. `REJECT` or any
92
+ invariant violation -> abort, no round. Dispatch failure or malformed output ->
93
+ one redispatch, then abort.
94
+ 8. **Finish.** Branch on the record's `delivery-mode`: `squash` -> squash path;
95
+ `pr` -> PR path. Every command from `PRIMARY_ROOT`. Squash path, by state:
96
+
97
+ ```bash
98
+ cd "$PRIMARY_ROOT"
99
+ [ -z "$(git status --porcelain --untracked-files=no)" ] || land_abort
100
+ [ "$(git rev-parse "$DEFAULT")" = "$BASE_SHA" ] || land_abort # base moved: never touch <default>
101
+ git checkout "$DEFAULT" || land_abort # pre-squash ends here
102
+ git merge --squash "hotfix/<slug>" || { git reset --hard "$BASE_SHA"; land_abort; }
103
+ git diff --cached --quiet && { git reset --hard "$BASE_SHA"; land_abort; } # empty squash
104
+ git commit -m "fix: <slug>" -m "<fault story>" || { git reset --hard "$BASE_SHA"; land_abort; }
105
+ [ "$(git rev-parse HEAD^{tree})" = "$(git rev-parse "hotfix/<slug>^{tree}")" ] \
106
+ || { git reset --hard "$BASE_SHA"; land_abort; } # proven; never reset after this line
107
+ SHA=$(git rev-parse HEAD)
108
+ [ "$ORIG_BRANCH" = "$DEFAULT" ] || git checkout "$ORIG_BRANCH" # failure: report, commit stays
109
+ git worktree remove --force ".worktrees/hotfix/<slug>" && git worktree prune \
110
+ && git branch -D "hotfix/<slug>" # failure: keep commit, report residual
111
+ ```
112
+
113
+ `git branch -D` is forced by construction: a squash commit never has the branch
114
+ tip as ancestor, so `-d` always refuses; the proven tree is the loss guard. The
115
+ commit stays **unpushed**. Report: `$SHA`; revert line `git checkout <default>
116
+ && git reset --hard <base-sha>` (unpushed) or `git revert <sha>` (after any
117
+ push); `git status --porcelain` diff against the baseline (expected none); a
118
+ push nudge.
119
+
120
+ PR path, cwd = the worktree: `git push -u origin hotfix/<slug>` - failure ->
121
+ report branch + worktree path, no URL, preserve. `gh pr create --base <default>
122
+ --head hotfix/<slug>` - `gh` missing or failing after one retry -> report the
123
+ pushed branch and compare URL. Worktree and branch preserved; print the closing
124
+ line.
125
+ 9. **Cleanup evidence.** Squash exit and pre-land aborts: `git worktree list`
126
+ without the hotfix entry; `git branch --list hotfix/<slug>` empty; porcelain
127
+ delta vs baseline none. PR exit and land-stage aborts: both present, plus
128
+ `git worktree remove --force .worktrees/hotfix/<slug> && git branch -D hotfix/<slug>`.
129
+ 10. **Response.** chase-bug step 5 rules apply unchanged: addressable -> draft
130
+ citing `fixed in <SHA>` or the PR link -> `send it`; unaddressable -> summary.
131
+ Abort never reaches this step - it returns to the menu.
132
+
133
+ ## Eligibility predicates
134
+
135
+ Evaluated once by `SKILL.md` at menu time from the root cause; re-checked against
136
+ the diff in step 7.
137
+
138
+ Safety invariants (row availability):
139
+
140
+ 1. No schema, migration, or persistence change.
141
+ 2. No public API, contract, or config-shape change.
142
+ 3. Rollback is reverting one commit - no data or state side effects.
143
+
144
+ Judgment predicates (`[recommended]` only; Moderate review items):
145
+
146
+ 4. Existing code only - new files limited to the regression test.
147
+ 5. Dependencies unchanged, one exception: a patch/minor bump whose upstream issue or
148
+ changelog names the symptom, diff = manifest + lockfile, no call-site change.
149
+ Replacement, major/breaking upgrade, new dependency -> fails.
150
+ 6. Regression evidence writable in the existing harness (bump fallback: the repro).
151
+
152
+ ## Abort
153
+
154
+ Both classes end with the baseline re-check, then chase-bug step 4 re-renders.
155
+
156
+ **Pre-land** (steps 2-7): unresolvable commands or task text; setup precondition
157
+ unmet; `NEEDS_CONTEXT`/`BLOCKED` or a concern naming an invariant; red after retry;
158
+ unfixable conformance gap; review `REJECT`, invariant violation, or a non-`SHIP`
159
+ re-review.
160
+
161
+ ```bash
162
+ [ "$created_worktree" = 1 ] && git worktree remove --force ".worktrees/hotfix/<slug>"
163
+ git worktree prune
164
+ [ "$created_branch" = 1 ] && git branch -D "hotfix/<slug>"
165
+ ```
166
+
167
+ Nothing else is touched. The re-rendered menu keeps the hotfix row when neither
168
+ flag was set, omits it otherwise.
169
+
170
+ **Land-stage** (step 8 before *proven*, after review `SHIP`): base moved, checkout failure, empty
171
+ squash, merge/commit failure, tree mismatch. `git reset --hard <base-sha>` only when
172
+ this run moved `<default>` (squash-applied or later) and `<default>` is checked
173
+ out (the step 8 lines do
174
+ exactly that); on base moved `<default>` is never touched and both SHAs are
175
+ reported. Restore `<orig-branch>`. Preserve worktree and branch (reviewed work).
176
+ Report path, tip SHA, closing line. The menu re-renders without the hotfix row.
177
+
178
+ **Baseline re-check**: `git status --porcelain --untracked-files=no` matches the
179
+ triage baseline; full porcelain delta reported; `<default>` == `<base-sha>` asserted
180
+ only when this run touched `<default>`. Pre-existing dirt is never touched.
181
+
182
+ ## Harness fallback
183
+
184
+ No `subagent` tool and no personas (the Claude Code marketplace ships `agents: []`):
185
+ run the duties inline, same order. Write the failing regression test, confirm red,
186
+ minimal fix, confirm green, commit on `hotfix/<slug>`. Self-review the diff against
187
+ the record, invariants 1-3, predicates 4-6. Run `SCOPED_TEST_COMMANDS`. Apply the
188
+ abort classes as written. Finish and report per steps 8-9.
189
+
190
+ ## Red Flags - STOP
191
+
192
+ - Any mutation before every step 3 precondition passes
193
+ - Reusing or force-replacing an existing `hotfix/<slug>` branch or path
194
+ - `git reset --hard` after *proven*, on `<orig-branch>`, or inside the worktree
195
+ - Re-running tests in the primary checkout
196
+ - Merging onto a moved base
197
+ - Landing a diff the last review did not see
198
+ - Pushing without an explicit request
@@ -15,6 +15,8 @@ When you have multiple **independent tasks** — unrelated test failures, or imp
15
15
 
16
16
  This skill is the **mechanic home** for parallel fan-out: fresh-context isolation, `worktree: true` filesystem isolation, and serial patch integration. `subagent-driven-development`'s Parallel-Wave Mode builds its per-wave dispatch on this skill — debugging is the worked example below, but the mechanics are identical for implementation tasks.
17
17
 
18
+ Gauntlet flow callers dispatch foreground: explicitly set top-level `async: false`, including retries and prose-described dispatches. `forceTopLevelAsync` is incompatible; if an async handle is returned anyway, stop and report rather than polling or relaunching. See [pi-cohort dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md).
19
+
18
20
  **Why parallel subagents:** each agent gets a fresh context window with only its problem domain. No cross-contamination between investigations, smaller diffs, faster wall-clock time. You stay the orchestrator — you read the summaries, resolve any file overlap, and run the integrated tests.
19
21
 
20
22
  **Fresh context is not the default.** Some packaged subagents (including `worker`) fork the parent context unless you opt out. Always pass `context: "fresh"` on every task entry — if it's missing, you're getting forked agents and losing the isolation that makes parallel dispatch worth doing in the first place.
@@ -78,6 +80,7 @@ Use the `subagent` tool in parallel mode, with explicit fresh context per task:
78
80
  ```ts
79
81
  subagent({
80
82
  context: "fresh",
83
+ async: false,
81
84
  tasks: [
82
85
  { agent: "worker", task: "Fix agent-tool-abort.test.ts failures" },
83
86
  { agent: "worker", task: "Fix batch-completion-behavior.test.ts failures" },
@@ -123,7 +126,7 @@ Grammar (identical across producers, modulo id prefix — `F` for code/spec revi
123
126
 
124
127
  **After the fix wave:** integrate patches serially per "Review and Integrate" above (mis-partition is self-healing: integrate the successes, re-run the conflicting finding sequentially on integrated HEAD); run the consuming loop's scoped test gate on the integrated tree; then one re-review of the integrated fix delta, per the consuming loop's own rules. The fan-out counts as one fix round against the consuming loop's budget — it grants no extra rounds.
125
128
 
126
- **Progress:** `plan_tracker({ action: "add" })` one task per fixed finding, named mechanically — `"<prefix>fix F<n>: <finding's first line verbatim>"`, where `<prefix>` is `"W<k>-"` inside an execution wave and empty elsewhere. Fix tasks always extend the tracker, never re-init. Mark `in_progress` at dispatch, `complete` at integration.
129
+ **Progress:** In a gauntlet flow, fix work reuses the consuming task/wave index (or durable `Gn:` gap index) and preserves it through retries, fan-out, tests, and re-review; do not append a fix task or replace an active flow list. Mark the existing index `in_progress` before dispatch and `complete` only after its consuming acceptance point. For standalone non-flow consumers with no owning task, preserve the generic append behavior: `plan_tracker({ action: "add" })` one task per fixed finding, named mechanically — `"<prefix>fix F<n>: <finding's first line verbatim>"`; mark it `in_progress` at dispatch and `complete` at integration.
127
130
 
128
131
  ## Agent Prompt Structure
129
132
 
@@ -192,6 +195,7 @@ REPORT_DIR=$(mktemp -d)
192
195
  ```ts
193
196
  subagent({
194
197
  context: "fresh",
198
+ async: false,
195
199
  worktree: true, // isolate edits; omit for read-only investigations
196
200
  concurrency: 3,
197
201
  tasks: [
@@ -37,10 +37,10 @@ Fill the template at `code-reviewer.md` in this skill directory, then dispatch a
37
37
 
38
38
  **How to dispatch:**
39
39
 
40
- Use the `subagent` tool with the code-reviewer template filled in:
40
+ Use the `subagent` tool with the code-reviewer template filled in. This foreground dispatch must return a terminal result before acting on review feedback:
41
41
 
42
42
  ```ts
43
- subagent({ agent: "code-reviewer", task: "... filled template ..." })
43
+ subagent({ agent: "code-reviewer", async: false, task: "... filled template ..." })
44
44
  ```
45
45
 
46
46
  **Placeholders:**
@@ -57,7 +57,7 @@ subagent({ agent: "code-reviewer", task: "... filled template ..." })
57
57
  - Note Minor issues for later
58
58
  - Push back if reviewer is wrong (with reasoning)
59
59
 
60
- **Fix rounds.** Critical and Moderate findings trigger a fix round; when dispatched from an orchestrating skill, fixes go to `implementer` subagents (per the orchestrator's no-self-coding rule), fanned out per `dispatching-parallel-agents` "Fix fan-out" when the review's `Parallel-safe:` line certifies a `disjoint` group of ≥ 2 findings. Before fanning out, validate the review's `Parallel-safe:` line with the structural probe in `dispatching-parallel-agents` § Fix fan-out (exactly-one-line grammar check, one re-ask, then explicit sequential fallback). After integration and the project's test command, re-dispatch the reviewer once on the integrated delta. If Critical or Moderate findings remain, run one more fix round and one more re-review; still failing → escalate to the user. Minor findings never trigger the fan-out.
60
+ **Fix rounds.** Critical and Moderate findings trigger a fix round; when dispatched from an orchestrating skill, fixes go to `implementer` subagents (per the orchestrator's no-self-coding rule), fanned out per `dispatching-parallel-agents` "Fix fan-out" when the review's `Parallel-safe:` line certifies a `disjoint` group of ≥ 2 findings. Before fanning out, validate the review's `Parallel-safe:` line with the structural probe in `dispatching-parallel-agents` § Fix fan-out (exactly-one-line grammar check, one re-ask, then explicit sequential fallback). After integration and the project's test command, re-dispatch the reviewer once on the integrated delta in the foreground with top-level `async: false`; await its terminal result. If Critical or Moderate findings remain, run one more fix round and one more foreground re-review; still failing → escalate to the user. Minor findings never trigger the fan-out.
61
61
 
62
62
  ## Example
63
63
 
@@ -58,6 +58,7 @@ Capture the worktree path once (`git rev-parse --show-toplevel`, run from inside
58
58
 
59
59
  ```
60
60
  subagent({
61
+ async: false,
61
62
  control: { needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 300000, inFlightSilenceKillMs: 600000 },
62
63
  tasks: members.map((model, i) => ({
63
64
  agent: "spec-council-member",
@@ -76,7 +77,7 @@ subagent({
76
77
 
77
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 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
 
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
+ **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.
80
81
 
81
82
  **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.
82
83
 
@@ -87,6 +88,7 @@ Dispatch the chair once. It reads the member files (not you), the spec, and the
87
88
  ```
88
89
  subagent({
89
90
  agent: "spec-council-synthesizer",
91
+ async: false,
90
92
  model: <chair from config, else omit to inherit>,
91
93
  cwd: "<abs worktree path>",
92
94
  control: { needsAttentionAfterMs: 300000, inFlightSilenceCeilingMs: 600000, inFlightSilenceKillMs: 900000 },
@@ -99,11 +101,11 @@ subagent({
99
101
  })
100
102
  ```
101
103
 
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").
104
+ The chair runs one long foreground single-turn synthesis; await its terminal result before applying findings. 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").
103
105
 
104
106
  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.
105
107
 
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.
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.
107
109
 
108
110
  ### 3 — Decide and apply
109
111
 
@@ -13,6 +13,8 @@ Execute a plan by dispatching a **fresh subagent per task**, with two-stage revi
13
13
 
14
14
  If a tool result contains a ⚠️ workflow warning, stop immediately and address it before continuing.
15
15
 
16
+ Gauntlet execution dispatches are foreground: set top-level `async: false` on every implementation, review, conformance, retry, and prose-described dispatch. Preserve parallel `tasks` batches and chain groups. `forceTopLevelAsync` is incompatible; an unexpected async handle means stop and report, never poll or relaunch. See [pi-cohort dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md).
17
+
16
18
  ## Why Subagents
17
19
 
18
20
  Your context window holds the full plan, prior decisions, and conversation history. Each subagent gets a fresh window with only the current task's text and context.
@@ -39,7 +41,7 @@ Periodic "should I continue?" prompts add latency without adding safety. The pla
39
41
 
40
42
  - Running inside a dedicated worktree (the same one the spec and plan were authored in). If you're on `main` in the primary checkout, stop and invoke `/skill:using-git-worktrees` first — implementation never lands directly on `main`.
41
43
  - Approved plan or clear task scope
42
- - `plan_tracker` initialized with the full task list
44
+ - `plan_tracker` list initialized at writing-plans handoff with the full wave-ordered task list. Consume and preserve it on continuation; only direct recovery with no tracker initializes the full plan list once, before dispatch, and never over an existing list.
43
45
 
44
46
  ## Sequential vs. Parallel-Wave
45
47
 
@@ -53,17 +55,17 @@ Before the first task, enter the implement phase: `phase_tracker({ action: "star
53
55
 
54
56
  For each task in `plan_tracker`:
55
57
 
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.
58
+ 1. **Start, then dispatch implementer.** Mark the task's existing `plan_tracker` index `in_progress` before dispatch. 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
59
  2. **Handle implementer status** (see below).
58
60
  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 change satisfies the anchored spec — nothing missing, nothing extra.
59
61
  4. If spec reviewer finds gaps → re-dispatch implementer to fix → re-review. Loop until ✅, within [Fix-Loop Rounds](#fix-loop-rounds).
60
62
  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
63
  6. If quality reviewer finds issues → re-dispatch implementer → re-review. Loop until ✅, within [Fix-Loop Rounds](#fix-loop-rounds).
62
- 7. Mark task complete in `plan_tracker`.
64
+ 7. After its existing reviews accept the work (and its required commit point), mark that same task index `complete` in `plan_tracker`. A subagent exit or green tests alone are not acceptance.
63
65
 
64
66
  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
67
 
66
- After all tasks: proceed to [After All Tasks](#after-all-tasks-complete) - it owns the whole-diff code review dispatch and the full verification run (concurrent when the verification set classifies safe).
68
+ After all tasks: proceed to [After All Tasks](#after-all-tasks-complete) - it owns parent full verification, then whole-diff code review, then conformance.
67
69
 
68
70
  ## Fix-Loop Rounds
69
71
 
@@ -121,6 +123,7 @@ Pi-subagents accepts a per-task `model` override. Use it.
121
123
  ```ts
122
124
  subagent({
123
125
  agent: "implementer",
126
+ async: false,
124
127
  task: "...",
125
128
  model: "anthropic/claude-haiku-4" // cheap tier
126
129
  })
@@ -132,18 +135,18 @@ When in doubt, default. Don't downgrade reviewers — false negatives are expens
132
135
 
133
136
  ```ts
134
137
  // implementer
135
- subagent({ agent: "implementer", task: "<task text + context + SCOPED_TEST_COMMANDS + status protocol>" })
138
+ subagent({ agent: "implementer", async: false, task: "<task text + context + SCOPED_TEST_COMMANDS + status protocol>" })
136
139
 
137
140
  // spec compliance
138
- subagent({ agent: "spec-reviewer", task: "<task text + patch diff + absolute spec path + task's Spec: anchors>" })
141
+ subagent({ agent: "spec-reviewer", async: false, task: "<task text + patch diff + absolute spec path + task's Spec: anchors>" })
139
142
 
140
143
  // code quality
141
- subagent({ agent: "code-reviewer", task: "<diff range + SCOPED_TEST_COMMANDS (task commands; wave: union; whole-diff: none) + ask: production-ready?>" })
144
+ subagent({ agent: "code-reviewer", async: false, task: "<diff range + SCOPED_TEST_COMMANDS (task commands; wave: union; whole-diff: none) + ask: production-ready?>" })
142
145
 
143
146
  // closing-loop conformance (origin vs deliverable) — its OWN dispatch, never fused with code quality
144
147
  // model: call gauntlet_setting({ key: "closureReview" }) first; use the returned model (omit model: if undefined to inherit) and maxFixRounds
145
148
  // If gauntlet_setting is unavailable, stop and report - never fall back to a manual bash/JSON settings merge.
146
- subagent({ agent: "conformance-reviewer", model: /* gauntlet_setting({ key: "closureReview" }).model, else omit to inherit */, task: "<spec path + verbatim original prompt + full diff vs main; per conformance-check.md>" })
149
+ subagent({ agent: "conformance-reviewer", async: false, model: /* gauntlet_setting({ key: "closureReview" }).model, else omit to inherit */, task: "<spec path + verbatim original prompt + full diff vs main; per conformance-check.md>" })
147
150
  ```
148
151
 
149
152
  Prompt templates live alongside this SKILL.md:
@@ -160,22 +163,22 @@ Auto-selected at handoff by `writing-plans` (any wave with ≥2 tasks) when the
160
163
 
161
164
  **Progress tracking (`plan_tracker`).** `plan_tracker` is a flat list with no native group concept, so waves are *encoded*, not modeled:
162
165
 
163
- - **Init once, wave-ordered:** `init` with every task across all waves in wave order, each name prefixed with its wave (`"W1: <title>"`, `"W2: <title>"`, …). Indices are positional and stable; never re-init mid-run (it drops statuses).
164
- - **Wave fan-out → `in_progress`:** mark every task index in the wave `in_progress`. Multiple simultaneous `in_progress` entries is expected (sequential mode has one).
165
- - **Wave commit → `complete`:** after the wave's gate passes and it commits, mark all that wave's indices `complete`. `complete` = durably committed, so a task in conflict-fallback stays `in_progress` until its wave commits.
166
+ - **Consume, preserve, recover only if absent:** consume the wave-ordered list initialized at writing-plans handoff; indices are positional and stable, so never re-init on continuation or mid-run. Only direct recovery with no tracker initializes the full plan list once before dispatch.
167
+ - **Wave fan-out → `in_progress`:** unconditionally mark every task index in the wave `in_progress` before dispatch. Multiple simultaneous entries are expected (sequential mode has one).
168
+ - **Wave commit → `complete`:** after the wave's gate passes and it commits, unconditionally mark all those same indices `complete`. `complete` = durably committed, so a task in conflict fallback stays `in_progress` until its wave commits.
166
169
  - **Lifecycle per task:** `pending → in_progress (wave fan-out) → complete (wave commit)`.
167
170
  - **Widget caveat (known, deliberately unfixed).** The persistent `plan_tracker` widget's icon strip (`○ → ✓`) and `(c/total)` count reflect every task, but its trailing *name* shows only the **first** `in_progress` task. In parallel mode the icon strip and the `status` action are the full in-flight view; a richer multi-task widget is a separate extension change, out of scope (YAGNI).
168
- - **Sequential mode is unchanged:** init the full list, one `in_progress` at a time; wave prefixes are harmless if present.
171
+ - **Sequential mode:** consume the same existing full list, one `in_progress` index at a time; wave prefixes are harmless.
169
172
 
170
173
  **Per-wave loop:**
171
174
 
172
175
  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.
173
- 2. **Fan out.** One parallel dispatch (shape below): `implementer` per task, `context: "fresh"`, `worktree: true`. Each returns a status + a patch.
176
+ 2. **Start, then fan out.** Mark every wave index `in_progress` before one parallel foreground dispatch (shape below): `implementer` per task, `context: "fresh"`, `worktree: true`. Each returns a status + a patch.
174
177
  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 starts from the diff (its hunks carry `file:line`) and reads each touched file in full, 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.
175
178
  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.
176
179
  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).
177
180
  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).
178
- 7. **Commit the wave.** Leaves a clean tree; the next wave's children branch from this commit and so see the integrated work.
181
+ 7. **Commit and complete the wave.** After the gate passes and the wave commits, mark all of its existing indices `complete`. Leaves a clean tree; the next wave's children branch from this commit and so see the integrated work.
179
182
 
180
183
  **Two-stage review is preserved:** spec review per task (pre-integration, dispatched `spec-reviewer` — not inline), quality review per wave (post-integration). A wave commit requires one spec-review verdict per accepted task, plus one code-review verdict on the integrated diff for waves that touch code. A doc-only wave (every task's `Files:` block documentation-only, per `writing-plans`' Wave Grouping) is SR-only — the CR gate does not apply.
181
184
 
@@ -192,6 +195,7 @@ REPORT_DIR=$(mktemp -d)
192
195
  ```ts
193
196
  subagent({
194
197
  context: "fresh",
198
+ async: false,
195
199
  cwd: "/abs/path/to/this/worktree", // REQUIRED: the worktree you're in, else children branch from main
196
200
  worktree: true, // each task in its own git worktree, branched from cwd's HEAD
197
201
  concurrency: 4, // default; cap = wave size
@@ -223,11 +227,9 @@ For the fan-out + worktree + patch-integration + conflict mechanics, see `dispat
223
227
  ## After All Tasks Complete
224
228
 
225
229
  0. Call `phase_tracker({ action: "start", phase: "verify" })`. (The `implement` phase was started at execution start and auto-completes from `plan_tracker` once all tasks are done; this flow runs its own verify gate instead of `/skill:verification-before-completion`, so it must mark verify itself.)
226
- 1. **Classify, then dispatch both audits.** Classify the plan header's `**Verification:**` commands once per verify entry (post-fix re-runs are serial by nature no re-classification): **unsafe** = anything that can rewrite tracked files write-mode formatters (`--write`, `-w`), autofixers (`--fix`, `-u` snapshot updates), codegen, migrations regenerating checked-in artifacts; **safe** = commands that write only untracked/ignored paths test runners, check-mode linters/formatters (`--check`, `--diff`), type checkers, builds only when their outputs are untracked; wrappers (`script/verify`, `package.json` aliases, `Makefile` targets) read one level in, safe iff every invoked command classifies safe; deeper nesting or an undeterminable write destination unsafe. The rule is the write destination; examples are illustrative, not authoritative. Unclear serial. Orchestrator judgment, not config no settings key, never a prompt.
227
- - **Safe concurrent (default).** Dispatch the whole-diff review per `/skill:requesting-code-review` against the worktree's full diff vs `main`, passing `SCOPED_TEST_COMMANDS: none` (the full run below is the orchestrator's): `subagent({ agent: "code-reviewer", context: "fresh", async: true, cwd: <worktree>, output: <absolute non-colliding $TMPDIR path>, task: <review template> })` capture the returned run id; never a relative `output:` path (it lands untracked in the worktree). In the same turn, run the full `**Verification:**` set foreground in your own bash: tests + style + format (a single bundling entrypoint, or the listed individual commands). This is the only full run before conformance task and wave gates ran scoped commands only.
228
- - **Unsafe/unclear serial.** Same two audits, review first (a plain synchronous dispatch), then the verification set, with one declarative notice naming the offender e.g. `Serial review->verify: 'npm run fmt' writes tracked files.` Only the start order changes; step 2's join invariant is identical.
229
- 2. **Join, then disposition.** Never busy-wait: after verification returns, check the review at most once via `subagent({ action: "status", id: <run id> })`; if still running, end the turn with no disposition — pi delivers the async completion, and the review output file is read only after terminal completion. If the async dispatch errored or the child died, re-dispatch the review serially (the verification result is already in hand). **No disposition of either result — no fix dispatch, no finding triage, no verify-complete claim, no `conformance-reviewer` dispatch — before both results are in hand and any fix-triggered re-run is green.** Post-join, address Critical and Moderate findings before handoff. Verification failed, review clean → dispatch fixes, then re-run the full set before any subsequent gate. Both audits dirty → strictly ordered, never merged: the review's certified `Parallel-safe:` fan-out first (when present, else sequential fixes), then the remaining verification failures sequentially, then one re-review and one full verification re-run — verification failures never join a `Parallel-safe:` group (they carry no finding IDs and no disjointness certification, and `dispatching-parallel-agents` forbids orchestrator-invented partitions). Both audits bind to the committed HEAD: any post-join fix commit — review-derived or conformance-derived — invalidates the verification result; re-run the full set before re-dispatching any gate. (Consumers wanting an in-flow project-specific audit re-add it via the gauntlet overrides file (see Project overrides), or run `/self-audit` manually.)
230
- 3. **Close the loop — conformance check.** The review in step 1 is plan-vs-code (single-step); it inherits any requirement the plan already dropped. Before marking verify complete, dispatch a fresh-context **`conformance-reviewer`** — its **own** dispatch, never fused into the step-1 review — to confront the deliverable (code **and** docs) against the *origin* — the spec **and** the original prompt — per `verification-before-completion/reference/conformance-check.md`. Pass the spec path, the verbatim original prompt, and the full diff. Follow that reference for the partition rule, concern decomposition, and fix-loop mechanics; do not reimplement them here. The fix loop may drive `plan_tracker` to surface fix-wave progress (task naming and lifecycle per conformance-check.md's fix loop / the Fix fan-out Progress rule); it never calls `phase_tracker`. Call `phase_tracker({ action: "complete", phase: "verify" })` only when the reference says the handoff is durably complete: either a current `CONFORMS` result, or a current `## Closure / conformance` inventory whose carried-open concerns all come from valid deferred gaps, including `recommended: fix` gaps carried open because a declared precondition made the fix loop unavailable (`maxFixRounds: 0`, or no eligible named-branch worktree). A started positive-cap fix loop that blocks, fails, or exhausts its rounds with an open `fix` gap is escalation, not completion; on escalation, do not complete verify, stop and report.
230
+ 1. **Parent full verification.** Run the complete plan-header `**Verification:**` command set foreground: tests plus every declared lint, type, format, and build check. A failure must be repaired and the full set rerun successfully before the next step. Before dispatching a verification repair, reopen (`in_progress`) every existing plan-task index whose `Files:` ownership includes its touched files; leave unowned cross-cutting repair work in the verification report. Those indices stay `in_progress` through the successful full rerun **and** step 2's whole-diff review accepting the repair that acceptance is their completion point, not the passing rerun. Commit any verification-produced tracked changes; use the resulting `HEAD_SHA` in the review task and include the commands/results in its existing `DESCRIPTION`.
231
+ 2. **Whole-diff code review.** Only after passing full verification, dispatch one foreground whole-diff `code-reviewer` per `/skill:requesting-code-review` against that committed HEAD, with `SCOPED_TEST_COMMANDS: none`; the reviewer does not repeat the full suite. Address Critical and Moderate findings. Before dispatching a review repair, reopen (`in_progress`) every existing plan-task index whose `Files:` ownership includes its touched files; leave unowned cross-cutting repair work in the review report. Mark each reopened index `complete` only once the repair is re-verified and the re-review accepts it — this is the same completion point step 1's reopened indices wait for, not an extra gate, and the gate order stays full verification -> whole-diff CR -> conformance. Any repair invalidates prior full verification, so rerun the full set successfully before the next gate.
232
+ 3. **Close the loop conformance check.** The review in step 2 is plan-vs-code (single-step); it inherits any requirement the plan already dropped. Before marking verify complete, dispatch a fresh-context **`conformance-reviewer`** — its **own** dispatch, never fused into the step-2 review — to confront the deliverable (code **and** docs) against the *origin* — the spec **and** the original prompt — per `verification-before-completion/reference/conformance-check.md`. Pass the spec path, the verbatim original prompt, and the full diff. Follow that reference for the partition rule, concern decomposition, and fix-loop mechanics; do not reimplement them here. The fix loop reuses durable `Gn` gap indices as defined in conformance-check.md; it never calls `phase_tracker`. Call `phase_tracker({ action: "complete", phase: "verify" })` only when the reference says the handoff is durably complete: either a current `CONFORMS` result, or a current `## Closure / conformance` inventory whose carried-open concerns all come from valid deferred gaps, including `recommended: fix` gaps carried open because a declared precondition made the fix loop unavailable (`maxFixRounds: 0`, or no eligible named-branch worktree). A started positive-cap fix loop that blocks, fails, or exhausts its rounds with an open `fix` gap is escalation, not completion; on escalation, do not complete verify, stop and report.
231
233
  4. Summarize what was implemented (tasks completed, files changed, test counts, code-review verdict). Emit the `## Closure / conformance` block exactly as defined in `verification-before-completion/reference/conformance-check.md`: it must open with the two-line sentinel (`status: CONFORMS (0 open)` or `status: GAPS (N open)`, then `audited-base: <full HEAD SHA>`), then carry the exact durable concern schema by reference with no renamed or reformatted fields. `finishing-a-development-branch` Step 3.5 consumes that block verbatim.
232
234
  5. **Proceed to finishing — no confirmation prompt.** Once verify is complete per step 3's criterion, invoke `/skill:finishing-a-development-branch` immediately. Its Step 4 menu (squash / PR / keep / discard) is the human gate; a separate "ready to finish?" prompt only stacks a second stop in front of it. Carried-open concerns are resolved there per concern via the `## Closure / conformance` block from step 4. Manual testing is a follow-up after the finishing choice (on `<base-branch>` after a squash-merge, or on the PR branch), never a reason to hold this gate.
233
235
 
@@ -251,7 +253,9 @@ For the fan-out + worktree + patch-integration + conflict mechanics, see `dispat
251
253
  - Dispatching `code-reviewer` per task inside a wave (CR binds to the integrated wave diff)
252
254
  - Dispatching an implementer or code-reviewer without a `SCOPED_TEST_COMMANDS` value (commands or `none`)
253
255
  - About to run the full verification entrypoint during the implement phase — task and wave gates run scoped, plan-declared commands only; the full set belongs to verify
254
- - Dispositioning either after-all-tasks audit fix dispatch, finding triage, verify-complete claim, or `conformance-reviewer` dispatch before both the whole-diff review and the full verification run have completed
256
+ - Dispatching a verification or review repair before reopening (`in_progress`) the plan-task indices that own its touched files, or completing them on the passing rerun instead of on the accepting whole-diff review
257
+ - Dispatching whole-diff CR before parent full verification passes, or conformance before the foreground CR result and any invalidating repair re-verification/re-review are accepted
258
+ - Polling, joining, or relaunching an unexpectedly asynchronous gauntlet dispatch instead of stopping and reporting
255
259
 
256
260
  ## Integration
257
261
 
@@ -219,7 +219,7 @@ Otherwise → not TDD
219
219
 
220
220
  No exceptions without your human partner's permission.
221
221
 
222
- When the TDD implementation cycle is complete (all tests green, code committed), mark the implement phase complete:
222
+ When the TDD implementation cycle is complete (all tests green, code committed), mark the implement phase complete only for a direct, standalone task. In an orchestrated gauntlet task or review loop, green tests and a commit do not accept the parent task or complete its phase: the parent retains tracker and phase ownership through its required reviews.
223
223
 
224
224
  ```
225
225
  phase_tracker({ action: "complete", phase: "implement" })
@@ -160,7 +160,7 @@ Defaults recognise `make ci`, `make test`, `npm test`, `pnpm test`, `yarn test`,
160
160
 
161
161
  Before running the verification gate, call `phase_tracker({ action: "start", phase: "verify" })`.
162
162
 
163
- When all verification passes, mark the verify phase complete:
163
+ When all verification passes, finish and await the required `plan_tracker` updates before marking verify complete. Reconcile accepted work on its existing indices; never clear or reinitialize to obtain closure. In a direct verification path, complete only after those updates have actually returned.
164
164
 
165
165
  ```
166
166
  phase_tracker({ action: "complete", phase: "verify" })
@@ -130,7 +130,7 @@ Mirrors `subagent-driven-development` Parallel-Wave Mode and reuses its
130
130
  `plan_tracker` progress surface. Runs entirely inside the gate — it invokes
131
131
  **no** `phase_tracker` calls (`phase_tracker({ phase: "implement" })` errors
132
132
  while verify is `in_progress`) and does **not** enter SDD's phase machinery.
133
- Only the fan-out/integrate/review shape and `plan_tracker` are reused.
133
+ Only the fan-out/integrate/review shape and `plan_tracker` are reused. Every execution dispatch is foreground with top-level `async: false`, including retries and prose-described dispatches; an unexpected async handle is a configuration failure: stop and report, never poll or relaunch. `forceTopLevelAsync` is incompatible; see [pi-cohort dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md).
134
134
 
135
135
  **Precondition — worktree required.** The loop needs a worktree HEAD to branch
136
136
  fixes from. On the ad-hoc `finishing-a-development-branch` paths that run in a
@@ -143,23 +143,16 @@ prerequisites hold.
143
143
 
144
144
  Per round:
145
145
 
146
- 1. **`plan_tracker` add** — append the round's gaps as tasks (`Gn: <gap origin
147
- clause verbatim, truncated>`; carry the gap's requirement text mechanically,
148
- no orchestrator-authored summaries); never `init`, which would wipe the
149
- implement phase's completed task list. Lifecycle per gap: `pending` →
150
- `in_progress` → `complete`. The widget now shows fix-wave progress during
151
- verify.
146
+ 1. **Synchronize gap tasks** — append only a genuinely new gap that is entering remediation, named `Gn: <gap origin clause verbatim, truncated>`; never `init`. Find existing gaps by their exact `Gn:` prefix and reuse that index even if origin wording changes. Carried-OPEN inventory-only gaps add nothing. Before dispatch, mark every remediated gap's existing index `in_progress`; a re-audit needing more work reopens that same `Gn` index. The lifecycle traces `[T1,T2]`, then `[T1,T2,G1]`, then `[T1,T2,G1,G2]`; no test-retry or review-round wrapper task.
152
147
  2. **Fix dispatch** — per `dispatching-parallel-agents` "Fix fan-out": a `disjoint`
153
148
  group of ≥ 2 gaps (per the report's `Parallel-safe:` line) fixes in one parallel
154
- dispatch — one `implementer` per gap (fresh context, `worktree: true`, `cwd` =
155
- the conformance worktree, task = the gap block verbatim with `touched-files` as
156
- the ownership boundary). The dispatch adds `SCOPED_TEST_COMMANDS` to the gap
157
- block: the gap-relevant plan-declared commands, or `none` (the round's test
158
- gate owns execution). `conflicts` pairs serialize. Gaps outside any ≥ 2-ID
159
- `disjoint` group run sequentially as before. Then dispatch `spec-reviewer` per
160
- gap on the gap-block reference contract below. Task lifecycle: mark `in_progress` at
161
- dispatch; `complete` is deferred until the gap's patch is successfully
162
- integrated in step 3 below.
149
+ foreground dispatch — one `implementer` per gap (fresh context, `async: false`,
150
+ `worktree: true`, `cwd` = the conformance worktree, task = the gap block verbatim
151
+ with `touched-files` as the ownership boundary). The dispatch adds `SCOPED_TEST_COMMANDS`
152
+ to the gap block: the gap-relevant plan-declared commands, or `none` (the round's
153
+ test gate owns execution). `conflicts` pairs serialize. Gaps outside any ≥ 2-ID
154
+ `disjoint` group run sequentially as before. Then dispatch foreground `spec-reviewer`
155
+ per gap on the gap-block reference contract below.
163
156
  3. **Integrate** serially via `git apply` onto the worktree HEAD, one gap's
164
157
  patch at a time. Failure handling is inherited verbatim from
165
158
  `dispatching-parallel-agents` "Review and Integrate": textual conflict →
@@ -168,10 +161,9 @@ Per round:
168
161
  offending task sequentially on integrated HEAD; a failed agent → integrate
169
162
  the successes, then retry the failure with fresh context including the
170
163
  integrated changes. A `BLOCKED`/`NEEDS_CONTEXT` return surfaces to the user.
171
- 4. **Test gate** on the integrated tree, using the project's canonical test
172
- command. A failure re-enters the failure-handling rules above.
173
- 5. **`code-reviewer` once** on the round's cumulative fix delta (not per gap), with `SCOPED_TEST_COMMANDS` = the round's gap-relevant commands, or `none` (the round's test gate owns execution).
174
- 6. **Re-audit**: re-dispatch `conformance-reviewer` over the fixes **plus** the
164
+ 4. **Test gate** on the integrated tree. In a plan flow, run the full plan-header `Verification` set once here; on an ad-hoc no-plan path, use the project's canonical test command. A failure re-enters the failure-handling rules above.
165
+ 5. **Round CR and completion** — run `code-reviewer` once on the round's cumulative fix delta (not per gap), foreground with `async: false` and `SCOPED_TEST_COMMANDS` = the round's gap-relevant commands, or `none` (the round's test gate owns execution). After integration, tests, and this CR accept the work, explicitly mark every remediated gap's same `Gn` index `complete`, before re-audit.
166
+ 6. **Re-audit**: foreground re-dispatch `conformance-reviewer` with `async: false` over the fixes **plus** the
175
167
  regression guard (any prior-`DELIVERED` requirement whose `evidence` file
176
168
  the fix diff touched). Pass the full prior conformance report (every row,
177
169
  including DELIVERED rows and their `evidence` `file:line`) and the round's
@@ -85,7 +85,7 @@ Otherwise one plan. Service, contract, or schema count is not a split signal - o
85
85
  Before mapping files, dispatch a scout to build the implementation map. Foreground, no announcement, no user interaction. The task template below is fixed — fill exactly **one** variable, the absolute spec path; compose nothing else:
86
86
 
87
87
  ```
88
- subagent({ agent: "scout", context: "fresh", cwd: "<abs worktree path>",
88
+ subagent({ agent: "scout", context: "fresh", async: false, cwd: "<abs worktree path>",
89
89
  phase: "plan-recon", output: "<abs plan path — same filename as the spec, per the table above>",
90
90
  task: <the fixed template below, with the spec path filled> })
91
91
  ```
@@ -316,10 +316,11 @@ Fix what this review finds before handoff.
316
316
 
317
317
  ## Execution Handoff
318
318
 
319
- After saving the plan, mark the planning phase complete:
319
+ After saving the plan, mark the planning phase complete, then initialize `plan_tracker` once with every plan task in wave order. Name each entry `W<k>: <title>` using its containing wave and task title. This is the execution list: do not initialize it again on continuation.
320
320
 
321
321
  ```
322
322
  phase_tracker({ action: "complete", phase: "plan" })
323
+ plan_tracker({ action: "init", tasks: ["W1: <title>", "W1: <title>", "W2: <title>"] })
323
324
  ```
324
325
 
325
326
  Then auto-select the execution mode and proceed — no pause, no picker. The mode is a pure function of the plan's wave structure: