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 +11 -0
- package/README.md +8 -8
- package/extensions/phase-tracker.test.ts +161 -3
- package/extensions/phase-tracker.ts +38 -0
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +8 -2
- package/skills/brainstorming/gatherer.md +4 -2
- package/skills/chase-bug/SKILL.md +82 -30
- package/skills/chase-bug/hotfix.md +198 -0
- package/skills/dispatching-parallel-agents/SKILL.md +5 -1
- package/skills/requesting-code-review/SKILL.md +3 -3
- package/skills/roasting-the-spec/SKILL.md +5 -3
- package/skills/subagent-driven-development/SKILL.md +24 -20
- package/skills/test-driven-development/SKILL.md +1 -1
- package/skills/verification-before-completion/SKILL.md +1 -1
- package/skills/verification-before-completion/reference/conformance-check.md +12 -20
- package/skills/writing-plans/SKILL.md +3 -2
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
|
|
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
|
|
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.
|
|
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: () =>
|
|
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
|
@@ -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 (
|
|
16
|
-
`
|
|
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)
|
|
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
|
|
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
|
|
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** -
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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. [
|
|
341
|
-
3. [
|
|
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
|
|
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
|
|
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>"
|
|
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;
|
|
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. **
|
|
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.
|
|
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
|
|
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
|
-
- **
|
|
164
|
-
- **Wave fan-out → `in_progress`:** mark every task index in the wave `in_progress
|
|
165
|
-
- **Wave commit → `complete`:** after the wave's gate passes and it commits, mark all
|
|
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
|
|
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. **
|
|
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. **
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
-
|
|
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,
|
|
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.
|
|
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, `
|
|
155
|
-
the conformance worktree, task = the gap block verbatim
|
|
156
|
-
the ownership boundary). The dispatch adds `SCOPED_TEST_COMMANDS`
|
|
157
|
-
block: the gap-relevant plan-declared commands, or `none` (the round's
|
|
158
|
-
gate owns execution). `conflicts` pairs serialize. Gaps outside any ≥ 2-ID
|
|
159
|
-
`disjoint` group run sequentially as before. Then dispatch `spec-reviewer`
|
|
160
|
-
gap on the gap-block reference contract below.
|
|
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,
|
|
172
|
-
|
|
173
|
-
|
|
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:
|