pi-gauntlet 7.0.2 → 7.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/extensions/phase-tracker.test.ts +108 -0
- package/extensions/phase-tracker.ts +51 -24
- package/extensions/test-support/pi-stubs.mjs +6 -3
- package/package.json +1 -1
- package/skills/brainstorming/SKILL.md +3 -1
- package/skills/brainstorming/reference/amendment-surface.md +6 -4
- package/skills/finishing-a-development-branch/SKILL.md +1 -1
- package/skills/subagent-driven-development/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v7.0.3 - 2026-10-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Amendment aftermath keeps the phase; reopened tasks close in place.** A spec amendment approved during `verify` or `ship` reopens its plan tasks inside that phase; `amendment-surface.md` no longer prescribes `skip <phase>` + `start implement force: true`, and the batch card's phase slot is the constant `no phase change`. Each reopened task is repaired through the current phase's fix loop and closed with `plan_tracker update ... complete` once its re-review or re-audit accepts. Supersedes the phase-restart clauses of the 2026-09-10 gh-27 spec and the 2026-09-22 batch-card spec.
|
|
8
|
+
- **Skill resources load with `read`, never a shell command.** Brainstorming's amend section states the rule (pi-condense's `**/skills/**/*.md` protection keys on `read`'s `path`, so a `bash sed` load was pruned mid-flow); `amendment-surface.md`, SDD, and finishing name `read` at their amendment entry points, and section 5 re-reads itself before the aftermath.
|
|
9
|
+
- **phase-tracker: open-task nudge after a verify-phase repair wave.** Under `flowGuards.enforce`, every non-error implementer-wave result while `verify` is in progress is prefixed with the latest `plan_tracker` snapshot's `pending`/`in_progress` rows and the `update ... complete` call that closes them. Advisory only - no block, no fix-round or phase change - and it reads session entries, so context pruning cannot hide it. The completion backstop's branch scan is shared with it (`latestPlanSnapshot`/`unfinishedTaskLines`). Documented in `doc/configuration.md`.
|
|
10
|
+
|
|
3
11
|
## v7.0.2 - 2026-10-02
|
|
4
12
|
|
|
5
13
|
### Added
|
|
@@ -1814,3 +1814,111 @@ test("grant: accepts MAX_SAFE_INTEGER and current on-disk cap controls whether t
|
|
|
1814
1814
|
writeFileSync(join(changed.ctx.cwd, ".pi", "settings.json"), JSON.stringify({ piGauntlet: { closureReview: { maxFixRounds: 1 } } }));
|
|
1815
1815
|
assert.match((await grant(changed, "g", { rounds: 1, reason: "ok" })).details.error ?? "", /0 used, cap 1/);
|
|
1816
1816
|
});
|
|
1817
|
+
|
|
1818
|
+
const eightTasks = (open: number[]) =>
|
|
1819
|
+
Array.from({ length: 8 }, (_, i) => ({ name: `T${i + 1} task`, status: open.includes(i) ? "in_progress" : "complete" }));
|
|
1820
|
+
|
|
1821
|
+
const resultText = async (h: ReturnType<typeof harness>, id: string, isError = false) => {
|
|
1822
|
+
const [res] = (await h.emitEvent("tool_result", waveResult(id, [{ agent: "implementer", exitCode: 0 }], isError))) as (
|
|
1823
|
+
| { content: { text: string }[] }
|
|
1824
|
+
| undefined
|
|
1825
|
+
)[];
|
|
1826
|
+
return res?.content[0]?.text;
|
|
1827
|
+
};
|
|
1828
|
+
|
|
1829
|
+
test("open-task nudge: verify-phase implementer wave lists mid-list open rows with the closure instruction, before any audit", async () => {
|
|
1830
|
+
const h = harness({ branch: verifyBranch([taskSnapshot(eightTasks([3]))]) });
|
|
1831
|
+
await h.emit("session_start");
|
|
1832
|
+
const text = await resultText(h, "w1");
|
|
1833
|
+
assert.ok(text, "nudge present");
|
|
1834
|
+
assert.match(text, /^Plan tasks still open after this repair wave:\n3: T4 task \(in_progress\)\nOnce this wave's repair is integrated and its re-review or re-audit accepts it, close that task: plan_tracker\(\{ action: "update", index: N, status: "complete" \}\)\. Leave unaccepted repairs in_progress\. A pending row is a task still to run, not to close\. Open tasks block complete verify\.$/);
|
|
1835
|
+
assert.doesNotMatch(text, /0: T1 task/);
|
|
1836
|
+
});
|
|
1837
|
+
|
|
1838
|
+
// fixRounds is observed through the cap guard: one nudged wave against maxFixRounds 1
|
|
1839
|
+
// blocks the next dispatch exactly as an un-nudged wave would.
|
|
1840
|
+
test("open-task nudge: fires on every qualifying wave and leaves fix-round counting unchanged", async () => {
|
|
1841
|
+
const h = guardedHarness({ piGauntlet: { closureReview: { enforce: true, maxFixRounds: 1 } } }, [taskSnapshot(eightTasks([0, 3]))]);
|
|
1842
|
+
await h.emit("session_start");
|
|
1843
|
+
assert.equal(await firstCallResult(h, "c1", implementerWave(1)), undefined);
|
|
1844
|
+
assert.match((await resultText(h, "c1")) ?? "", /0: T1 task \(in_progress\)\n3: T4 task \(in_progress\)/);
|
|
1845
|
+
const blocked = await firstCallResult(h, "c2", implementerWave(1));
|
|
1846
|
+
assert.equal(blocked?.block, true, "one wave counted, cap 1 reached");
|
|
1847
|
+
const h2 = guardedHarness({ piGauntlet: { closureReview: { enforce: true } } }, [taskSnapshot(eightTasks([3]))]);
|
|
1848
|
+
await h2.emit("session_start");
|
|
1849
|
+
assert.match((await resultText(h2, "a")) ?? "", /^Plan tasks still open/);
|
|
1850
|
+
assert.match((await resultText(h2, "b")) ?? "", /^Plan tasks still open/);
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1853
|
+
// The closure-model mismatch warning is stashed at tool_call; it must render first.
|
|
1854
|
+
test("open-task nudge: a stashed tool_call warning stays ahead of the nudge on the same result", async () => {
|
|
1855
|
+
const h = harness({
|
|
1856
|
+
cwd: tempCwd({ piGauntlet: { closureReview: { enforce: true, model: "p/configured" } } }),
|
|
1857
|
+
branch: verifyBranch([taskSnapshot(eightTasks([3]))]),
|
|
1858
|
+
});
|
|
1859
|
+
await h.emit("session_start");
|
|
1860
|
+
const call = { tasks: [{ agent: "conformance-reviewer", model: "p/other", task: "audit" }, { agent: "implementer", task: "fix", worktree: true }] };
|
|
1861
|
+
assert.equal(await firstCallResult(h, "m1", call), undefined);
|
|
1862
|
+
const text = (await resultText(h, "m1")) ?? "";
|
|
1863
|
+
const warningAt = text.indexOf("p/other");
|
|
1864
|
+
const nudgeAt = text.indexOf("Plan tasks still open after this repair wave:");
|
|
1865
|
+
assert.ok(warningAt >= 0 && nudgeAt > warningAt, `warning first, nudge second: ${text}`);
|
|
1866
|
+
});
|
|
1867
|
+
|
|
1868
|
+
test("open-task nudge: silent outside the verify window and without open rows", async () => {
|
|
1869
|
+
const cases: [string, ReturnType<typeof harness>][] = [
|
|
1870
|
+
["implement in progress", harness({ branch: implementBranch([taskSnapshot(eightTasks([3]))]) })],
|
|
1871
|
+
[
|
|
1872
|
+
"ship in progress",
|
|
1873
|
+
harness({
|
|
1874
|
+
branch: verifyBranch([
|
|
1875
|
+
phaseResult("complete", phases({ brainstorm: "complete", plan: "complete", implement: "complete", verify: "complete" })),
|
|
1876
|
+
phaseResult("start", phases({ brainstorm: "complete", plan: "complete", implement: "complete", verify: "complete", ship: "in_progress" })),
|
|
1877
|
+
taskSnapshot(eightTasks([3])),
|
|
1878
|
+
]),
|
|
1879
|
+
}),
|
|
1880
|
+
],
|
|
1881
|
+
["all tasks complete or skipped", harness({ branch: verifyBranch([taskSnapshot([...eightTasks([]).slice(0, 7), { name: "T8 task", status: "skipped" }])]) })],
|
|
1882
|
+
["no snapshot on the branch", harness({ branch: verifyBranch() })],
|
|
1883
|
+
["latest snapshot is a clear", harness({ branch: verifyBranch([taskSnapshot(eightTasks([3])), taskSnapshot([])]) })],
|
|
1884
|
+
["rejected update is skipped, prior all-complete snapshot used", harness({ branch: verifyBranch([taskSnapshot(eightTasks([])), { type: "message", message: { role: "toolResult", toolName: "plan_tracker", details: { tasks: eightTasks([3]), error: "rejected" } } }]) })],
|
|
1885
|
+
["flowGuards.enforce: false", harness({ cwd: tempCwd({ piGauntlet: { flowGuards: { enforce: false } } }), branch: verifyBranch([taskSnapshot(eightTasks([3]))]) })],
|
|
1886
|
+
["async handle results: []", harness({ branch: verifyBranch([taskSnapshot(eightTasks([3]))]) })],
|
|
1887
|
+
];
|
|
1888
|
+
for (const [label, h] of cases) {
|
|
1889
|
+
await h.emit("session_start");
|
|
1890
|
+
if (label === "async handle results: []") {
|
|
1891
|
+
assert.deepEqual(await h.emitEvent("tool_result", waveResult("x", [])), [undefined], label);
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
assert.equal(await resultText(h, "x"), undefined, label);
|
|
1895
|
+
}
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
test("open-task nudge: closureReview.enforce: false does not silence it", async () => {
|
|
1899
|
+
const h = harness({ cwd: tempCwd({ piGauntlet: { closureReview: { enforce: false } } }), branch: verifyBranch([taskSnapshot(eightTasks([3]))]) });
|
|
1900
|
+
await h.emit("session_start");
|
|
1901
|
+
assert.match((await resultText(h, "k")) ?? "", /^Plan tasks still open/);
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
test("open-task nudge: silent on an isError result, a degraded settings read, and in a subagent child", async () => {
|
|
1905
|
+
const errored = harness({ branch: verifyBranch([taskSnapshot(eightTasks([3]))]) });
|
|
1906
|
+
await errored.emit("session_start");
|
|
1907
|
+
assert.equal(await resultText(errored, "e", true), undefined, "isError result");
|
|
1908
|
+
|
|
1909
|
+
const malformed = tempCwd();
|
|
1910
|
+
mkdirSync(join(malformed, ".pi"), { recursive: true });
|
|
1911
|
+
writeFileSync(join(malformed, ".pi", "settings.json"), "{ not json");
|
|
1912
|
+
const degraded = harness({ cwd: malformed, branch: verifyBranch([taskSnapshot(eightTasks([3]))]) });
|
|
1913
|
+
await degraded.emit("session_start");
|
|
1914
|
+
assert.equal(await resultText(degraded, "d"), undefined, "settings errors");
|
|
1915
|
+
|
|
1916
|
+
process.env.PI_SUBAGENT_DEPTH = "1";
|
|
1917
|
+
try {
|
|
1918
|
+
const child = harness({ branch: verifyBranch([taskSnapshot(eightTasks([3]))]) });
|
|
1919
|
+
await child.emit("session_start");
|
|
1920
|
+
assert.equal(await resultText(child, "c"), undefined, "subagent child");
|
|
1921
|
+
} finally {
|
|
1922
|
+
process.env.PI_SUBAGENT_DEPTH = "0";
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
@@ -101,6 +101,33 @@ const isImplementerWave = (details: unknown, isError: boolean | undefined): bool
|
|
|
101
101
|
return d.results.some((r) => r?.agent === "implementer");
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
type Task = { name: string; status: string };
|
|
105
|
+
|
|
106
|
+
// Latest successful plan_tracker snapshot on the current branch. Shared by the
|
|
107
|
+
// completion backstop and the open-task nudge so both reason about one state;
|
|
108
|
+
// read from session entries, so context pruning cannot hide it.
|
|
109
|
+
const latestPlanSnapshot = (ctx: ExtensionContext): Task[] => {
|
|
110
|
+
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
|
111
|
+
if (
|
|
112
|
+
entry.type !== "message" ||
|
|
113
|
+
entry.message.role !== "toolResult" ||
|
|
114
|
+
entry.message.toolName !== "plan_tracker" ||
|
|
115
|
+
entry.message.isError
|
|
116
|
+
) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const details = entry.message.details as { tasks?: Task[]; error?: string } | undefined;
|
|
120
|
+
if (!details || details.error || !details.tasks) continue;
|
|
121
|
+
return details.tasks;
|
|
122
|
+
}
|
|
123
|
+
return [];
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const unfinishedTaskLines = (tasks: Task[]): string[] =>
|
|
127
|
+
tasks.flatMap((task, index) =>
|
|
128
|
+
task.status === "pending" || task.status === "in_progress" ? [`${index}: ${task.name} (${task.status})`] : [],
|
|
129
|
+
);
|
|
130
|
+
|
|
104
131
|
// Review-cadence guard (spec 2026-08-12-execution-fidelity-hardening): presence-only
|
|
105
132
|
// advisory ledger of the most recent completed implementer / spec-reviewer /
|
|
106
133
|
// code-reviewer dispatch. Agents completing in the SAME dispatch share a sequence
|
|
@@ -210,6 +237,13 @@ const loneImplementerBlockReason =
|
|
|
210
237
|
"a lone agent call runs unisolated and produces no worktree diff. " +
|
|
211
238
|
"To disable this gate, set piGauntlet.closureReview.enforce: false.";
|
|
212
239
|
|
|
240
|
+
// Preview the completion backstop so reopened tasks close at acceptance,
|
|
241
|
+
// not before the repair is integrated and reviewed.
|
|
242
|
+
const openTaskNudge = (rows: string[]): string =>
|
|
243
|
+
`Plan tasks still open after this repair wave:\n${rows.join("\n")}\n` +
|
|
244
|
+
'Once this wave\'s repair is integrated and its re-review or re-audit accepts it, close that task: plan_tracker({ action: "update", index: N, status: "complete" }). ' +
|
|
245
|
+
"Leave unaccepted repairs in_progress. A pending row is a task still to run, not to close. Open tasks block complete verify.";
|
|
246
|
+
|
|
213
247
|
const fixRoundCapBlockReason = (used: number, cap: number, settingsPath: string): string =>
|
|
214
248
|
`Conformance fix loop: ${used} fix round(s) used against a cap of ${cap} (granted rounds included); ` +
|
|
215
249
|
"escalate to the human with the verdict trail instead of re-looping.\n" +
|
|
@@ -382,6 +416,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
382
416
|
// Warnings stashed at tool_call, prepended at tool_result (verify-before-ship pattern).
|
|
383
417
|
// Relies on tool_result firing for every tool_call; reconstructState clears any stragglers.
|
|
384
418
|
const pendingGuardWarnings = new Map<string, string>();
|
|
419
|
+
const addGuardWarning = (id: string, text: string) => {
|
|
420
|
+
const prior = pendingGuardWarnings.get(id);
|
|
421
|
+
pendingGuardWarnings.set(id, prior ? prior + "\n\n" + text : text);
|
|
422
|
+
};
|
|
385
423
|
|
|
386
424
|
const activeGuardPhase = (): Phase | undefined =>
|
|
387
425
|
GUARD_PHASES.find((p) => phases[p].status === "in_progress");
|
|
@@ -519,10 +557,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
519
557
|
|
|
520
558
|
pi.on("tool_call", async (event, ctx) => {
|
|
521
559
|
// D3.b: one fresh settings read per event, lazily, only if a guard needs it.
|
|
522
|
-
const addGuardWarning = (id: string, text: string) => {
|
|
523
|
-
const prior = pendingGuardWarnings.get(id);
|
|
524
|
-
pendingGuardWarnings.set(id, prior ? prior + "\n\n" + text : text);
|
|
525
|
-
};
|
|
526
560
|
let gauntletCache: ReturnType<typeof loadGauntletSettings> | undefined;
|
|
527
561
|
const g = () => {
|
|
528
562
|
if (!gauntletCache) {
|
|
@@ -706,6 +740,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
706
740
|
observeFixWave(event.details, event.isError, ctx);
|
|
707
741
|
if (qualifiesAsClosureDispatch(event.details)) conformanceDispatched = true;
|
|
708
742
|
observeCadence(event.details);
|
|
743
|
+
if (
|
|
744
|
+
!isSubagentChild &&
|
|
745
|
+
gauntletEntered &&
|
|
746
|
+
phases.verify.status === "in_progress" &&
|
|
747
|
+
isImplementerWave(event.details, event.isError)
|
|
748
|
+
) {
|
|
749
|
+
const loaded = loadGauntletSettings(ctx.cwd);
|
|
750
|
+
if (loaded.errors.length === 0 && resolveFlowGuards(loaded.gauntlet).enforce) {
|
|
751
|
+
const open = unfinishedTaskLines(latestPlanSnapshot(ctx));
|
|
752
|
+
if (open.length) addGuardWarning(event.toolCallId, openTaskNudge(open));
|
|
753
|
+
}
|
|
754
|
+
}
|
|
709
755
|
const warning = pendingGuardWarnings.get(event.toolCallId);
|
|
710
756
|
if (warning) {
|
|
711
757
|
pendingGuardWarnings.delete(event.toolCallId);
|
|
@@ -989,26 +1035,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
989
1035
|
(params.phase === "implement" || params.phase === "verify") &&
|
|
990
1036
|
resolveFlowGuards(loadGauntletSettings(ctx.cwd).gauntlet).enforce
|
|
991
1037
|
) {
|
|
992
|
-
|
|
993
|
-
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
|
994
|
-
if (
|
|
995
|
-
entry.type !== "message" ||
|
|
996
|
-
entry.message.role !== "toolResult" ||
|
|
997
|
-
entry.message.toolName !== "plan_tracker" ||
|
|
998
|
-
entry.message.isError
|
|
999
|
-
) {
|
|
1000
|
-
continue;
|
|
1001
|
-
}
|
|
1002
|
-
const details = entry.message.details as { tasks?: { name: string; status: string }[]; error?: string } | undefined;
|
|
1003
|
-
if (!details || details.error || !details.tasks) continue;
|
|
1004
|
-
tasks = details.tasks;
|
|
1005
|
-
break;
|
|
1006
|
-
}
|
|
1007
|
-
const unfinished = tasks.flatMap((task, index) =>
|
|
1008
|
-
task.status === "pending" || task.status === "in_progress"
|
|
1009
|
-
? [`${index}: ${task.name} (${task.status})`]
|
|
1010
|
-
: [],
|
|
1011
|
-
);
|
|
1038
|
+
const unfinished = unfinishedTaskLines(latestPlanSnapshot(ctx));
|
|
1012
1039
|
if (unfinished.length) {
|
|
1013
1040
|
return {
|
|
1014
1041
|
content: [
|
|
@@ -6,13 +6,16 @@ const sources = {
|
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
export class SettingsManager {
|
|
8
8
|
static create(cwd) { return new SettingsManager(cwd); }
|
|
9
|
-
constructor(cwd) { this.cwd = cwd; }
|
|
9
|
+
constructor(cwd) { this.cwd = cwd; this.errors = []; }
|
|
10
10
|
getGlobalSettings() { return {}; }
|
|
11
11
|
getProjectSettings() {
|
|
12
12
|
try { return JSON.parse(readFileSync(this.cwd + "/.pi/settings.json", "utf8")); }
|
|
13
|
-
catch
|
|
13
|
+
catch (error) {
|
|
14
|
+
if (error?.code !== "ENOENT") this.errors.push({ scope: "project", error });
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
|
-
drainErrors() {
|
|
18
|
+
drainErrors() { const drained = this.errors; this.errors = []; return drained; }
|
|
16
19
|
}
|
|
17
20
|
export const getAgentDir = () => "/tmp/pi-gauntlet-test-agent";
|
|
18
21
|
`,
|
package/package.json
CHANGED
|
@@ -146,9 +146,11 @@ Follow [User Review Gate](reference/spec-finalization.md#user-review-gate) for s
|
|
|
146
146
|
|
|
147
147
|
Execute in place from any later phase; never invoke `/skill:brainstorming` for it (its entry resets both trackers). Worktree, spec commits, and plan survive.
|
|
148
148
|
|
|
149
|
+
Every skill resource in this flow (a `SKILL.md` or a `reference/*.md`) is loaded with the `read` tool at its absolute path under `skills/`, never with a shell command - pruning protection keys on `read`'s `path`.
|
|
150
|
+
|
|
149
151
|
Classify first. Redraw test: the change alters the problem statement, adds or removes a component, or moves a component boundary -> redraw. A change inside one component (a persistence mechanism, a worker's HTTP client, dropping a fallback and its task) -> amend. State the call; the user overrides either way.
|
|
150
152
|
|
|
151
|
-
Amend ->
|
|
153
|
+
Amend -> `read` `reference/amendment-surface.md` and follow it (unreadable -> stop with a blocking error; never improvise the grammar). Apply its [Standing grants](reference/amendment-surface.md#standing-grants) boundaries.
|
|
152
154
|
|
|
153
155
|
Redraw: keep the worktree and the approved spec file. `plan_tracker({ action: "clear" })`, `phase_tracker({ action: "reset" })`, `phase_tracker({ action: "start", phase: "brainstorm" })`, delete the plan file, resume at checklist step 4 with the approved spec as the draft (steps 2-3 skipped). Spec-writing overwrites it; the full gate follows.
|
|
154
156
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Amendment surface
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`read` this file (the `read` tool at its absolute path, never a shell command) from `skills/brainstorming/SKILL.md` § Amending an approved spec, from any phase, for amend-class changes; at approval, load only [Standing grants](#standing-grants), not the batch procedure. Keep redraws outside this procedure. Only the main loop holding `edit`/`write` authors amendments and human-facing amendment text. Use the human last: a fresh reviewer clears evidence-backed factual corrections; present the rest to the human once per batch plainly.
|
|
4
4
|
|
|
5
5
|
## Standing grants
|
|
6
6
|
|
|
@@ -77,7 +77,7 @@ Fail closed: a dispatch error, an async handle, a silence-kill, or a missing or
|
|
|
77
77
|
Render only escalated and prefiltered items; `<N>` counts them. Nothing symbol-dense above the fold; each item's `old -> new` sits under `Details`, after the footer.
|
|
78
78
|
|
|
79
79
|
```
|
|
80
|
-
Spec amendments: <N> need your call - from <trigger>; applying as recommended <reopens | adds | removes> <task ids | no tasks>;
|
|
80
|
+
Spec amendments: <N> need your call - from <trigger>; applying as recommended <reopens | adds | removes> <task ids | no tasks>; no phase change.
|
|
81
81
|
|
|
82
82
|
* <handle> - <title>: <what>. <why>.
|
|
83
83
|
Example: <before -> after>
|
|
@@ -93,7 +93,7 @@ Details
|
|
|
93
93
|
<handle>: <location> - old: <text> -> new: <text>
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
`<trigger>` is the step the main loop is running when the batch forms: `the spec review`, `planning Task <n>`, `Task <n> BLOCKED`, `the verify-phase code review (FIX_FIRST <ids>)`, `the finish-gate council-edit revert`, else `the <phase> phase`; `your request` only for an amend the user raised in prose. The plan clause is the section 5 aftermath as `recommended` would land, over reviewer-cleared plus rendered items, read from the plan file and tracker state - call no `plan_tracker`/`phase_tracker` before the reply: any of `reopens <ids>`, `adds <n> task(s)`, `removes <ids>`, comma-joined (`reopens no tasks` when the plan is untouched), then `;
|
|
96
|
+
`<trigger>` is the step the main loop is running when the batch forms: `the spec review`, `planning Task <n>`, `Task <n> BLOCKED`, `the verify-phase code review (FIX_FIRST <ids>)`, `the finish-gate council-edit revert`, else `the <phase> phase`; `your request` only for an amend the user raised in prose. The plan clause is the section 5 aftermath as `recommended` would land, over reviewer-cleared plus rendered items, read from the plan file and tracker state - call no `plan_tracker`/`phase_tracker` before the reply: any of `reopens <ids>`, `adds <n> task(s)`, `removes <ids>`, comma-joined (`reopens no tasks` when the plan is untouched), then `; no phase change`.
|
|
97
97
|
|
|
98
98
|
`Reviewer:` quotes the `<one-line reason>` of the section 3 reply verbatim; the `probed:` half stays in the commit body; a prefiltered item carries `not reviewed - <the rule that prefiltered it>`. `Impact:` restates the design-contract shift from `location` in the reader's words, never quoted spec text, one clause per touched decision, `;`-joined; `(none) -> <new>` for a contract added, `<old> -> (removed)` for a contract removed. `Details` keeps the verbatim `old -> new`.
|
|
99
99
|
|
|
@@ -101,9 +101,11 @@ Details
|
|
|
101
101
|
|
|
102
102
|
## 5. Apply, aftermath, commit
|
|
103
103
|
|
|
104
|
+
Before the aftermath, `read` this section again; never run it from memory.
|
|
105
|
+
|
|
104
106
|
Apply accepted items only: reviewer clears, `accept`, `alt-n`, and state-changing `custom`. Print one line per applied item: `amended the spec: <title> - <what>`.
|
|
105
107
|
|
|
106
|
-
Aftermath, once per batch, only when at least one item applied (nothing applied -> skip to the commit below). No plan yet -> commit the spec; continue. Plan exists -> update affected anchors and tasks: `plan_tracker` `add` for new tasks; anchor-changed completed tasks are reopened as `in_progress` and re-run the task loop (`update` never sets `pending`). A removed task is deleted from the plan; then re-`init` the tracker with `{ name, status }` elements: preserved tasks keep their order and statuses, reopened tasks are `in_progress` in place, every still-`pending` task (including newly added ones, whatever wave label they carry) trails the non-pending ones, removed tasks are the only deletions (the only permitted `init` after handoff; never `clear`). Re-run `plan_check` until it passes, commit spec + plan together; continue. A task reopened while `verify` or `ship` is in progress
|
|
108
|
+
Aftermath, once per batch, only when at least one item applied (nothing applied -> skip to the commit below). No plan yet -> commit the spec; continue. Plan exists -> update affected anchors and tasks: `plan_tracker` `add` for new tasks; anchor-changed completed tasks are reopened as `in_progress` and re-run the task loop (`update` never sets `pending`). A removed task is deleted from the plan; then re-`init` the tracker with `{ name, status }` elements: preserved tasks keep their order and statuses, reopened tasks are `in_progress` in place, every still-`pending` task (including newly added ones, whatever wave label they carry) trails the non-pending ones, removed tasks are the only deletions (the only permitted `init` after handoff; never `clear`). Re-run `plan_check` until it passes, commit spec + plan together; continue. A task reopened while `verify` or `ship` is in progress stays in that phase - no `phase_tracker` call. Repair it through the current phase's fix loop and `plan_tracker({ action: "update", index: N, status: "complete" })` it once that repair's review or re-audit accepts; open tasks block `complete verify`, and in `ship` nothing checks them, so close them explicitly.
|
|
107
109
|
|
|
108
110
|
One commit per batch:
|
|
109
111
|
|
|
@@ -94,7 +94,7 @@ then continue directly to Step 4. No approval prompt, no menu, no shared options
|
|
|
94
94
|
|
|
95
95
|
When the sentinel carries a `happy-path:` line, print it directly under `Closure / conformance: CONFORMS` as one informational, non-blocking line in the same shape as the `auto-applied fix commits` line: `happy-path: passed`, `happy-path: failed - unattributable`, or `happy-path: not run - <reason>` (a `failed - attributed to G<n>` value cannot reach this branch: its gaps are open).
|
|
96
96
|
|
|
97
|
-
**Pre-menu amendment funnel (GAPS only).** Before rendering the carried-open menu, run [`amendment-surface.md` § Conformance entry](../brainstorming/reference/amendment-surface.md) over the inventory once: gaps with `recommended: accept`, verdict `DRIFTED` or `PARTIAL`, not `UNAUTHORIZED`, whose `origin` is not an acceptance criterion are drafted as `accept-into-spec` items (the edit built from `origin` + `evidence`) and sent to the reviewer in one call; cleared items apply and land as one batch commit, the spec is re-audited, the inventory regenerated. Only concerns the re-audit actually closed drop out; sibling concerns in the same gap keep their rows and dispositions. Survivors and every other gap render as ordinary rows below - one menu, never two.
|
|
97
|
+
**Pre-menu amendment funnel (GAPS only).** Before rendering the carried-open menu, `read` and run [`amendment-surface.md` § Conformance entry](../brainstorming/reference/amendment-surface.md) over the inventory once: gaps with `recommended: accept`, verdict `DRIFTED` or `PARTIAL`, not `UNAUTHORIZED`, whose `origin` is not an acceptance criterion are drafted as `accept-into-spec` items (the edit built from `origin` + `evidence`) and sent to the reviewer in one call; cleared items apply and land as one batch commit, the spec is re-audited, the inventory regenerated. Only concerns the re-audit actually closed drop out; sibling concerns in the same gap keep their rows and dispositions. Survivors and every other gap render as ordinary rows below - one menu, never two.
|
|
98
98
|
|
|
99
99
|
**Carried-open (`status: GAPS (N open)`).** Read `reference/disposition-protocol.md` and follow it for the carried-open render (dense) grammar, the response grammar, and the 9-step execute order. Render the human decision menu in the shape below, with the sentinel's `happy-path:` line (when present) printed as one informational line directly under the `Conformance: N decisions needed before shipping.` header - a `failed - attributed to G<n>,...` value names the rows whose evidence is the transcript, a `rescope` recommendation on such a row means the failure lies in code the change never owned - drive the dispositions per that reference, then print the summary render and continue to Step 4. If that reference file cannot be read, stop and surface a blocking error — do **not** improvise the grammar from memory.
|
|
100
100
|
|
|
@@ -65,7 +65,7 @@ For each task in `plan_tracker`:
|
|
|
65
65
|
6. If the quality review is issue-bearing → re-dispatch the implementer per `/skill:requesting-code-review` Fix rounds → re-review. Loop until ✅, within [Fix-Loop Rounds](#fix-loop-rounds).
|
|
66
66
|
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.
|
|
67
67
|
|
|
68
|
-
The orchestrator is the spec's only writer during execution. Amendment trigger: implementer `BLOCKED` citing a spec defect, or a review finding showing the spec (not the code) is wrong -> pause the fix loop, execute brainstorming's [amendment path](../brainstorming/SKILL.md#amending-an-approved-spec) in place (it reviews first, stops only on escalation or redraw), resume. Code-vs-spec mismatch stays in the SR loop. 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.
|
|
68
|
+
The orchestrator is the spec's only writer during execution. Amendment trigger: implementer `BLOCKED` citing a spec defect, or a review finding showing the spec (not the code) is wrong -> pause the fix loop, `read` and execute brainstorming's [amendment path](../brainstorming/SKILL.md#amending-an-approved-spec) in place (it reviews first, stops only on escalation or redraw), resume. Code-vs-spec mismatch stays in the SR loop. 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.
|
|
69
69
|
|
|
70
70
|
After all tasks: proceed to [After All Tasks](#after-all-tasks-complete) - it owns parent full verification, then whole-diff code review, then conformance.
|
|
71
71
|
|