pi-gauntlet 7.27.0 → 7.29.0

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.
Files changed (32) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +16 -7
  3. package/agents/loop-implementer.md +51 -0
  4. package/bin/gauntlet-performance.mjs +5 -1
  5. package/bin/gauntlet-performance.test.mjs +19 -3
  6. package/bin/gauntlet-telemetry-seal.mjs +2 -2
  7. package/extensions/lib/gauntlet-settings.test.ts +10 -0
  8. package/extensions/lib/gauntlet-settings.ts +8 -0
  9. package/extensions/lib/plan-check.test.ts +63 -0
  10. package/extensions/lib/plan-check.ts +42 -0
  11. package/extensions/lib/telemetry-record.test.ts +19 -0
  12. package/extensions/lib/telemetry-record.ts +5 -3
  13. package/extensions/phase-tracker.test.ts +40 -0
  14. package/extensions/phase-tracker.ts +6 -2
  15. package/extensions/telemetry.test.ts +58 -4
  16. package/extensions/telemetry.ts +21 -12
  17. package/package.json +2 -2
  18. package/skills/brainstorming/SKILL.md +3 -3
  19. package/skills/brainstorming/reference/amendment-surface.md +5 -5
  20. package/skills/brainstorming/reference/spec-finalization.md +12 -3
  21. package/skills/brainstorming/reference/ticket-acceptance.md +1 -1
  22. package/skills/finishing-a-development-branch/SKILL.md +6 -5
  23. package/skills/gauntlet-handoff/SKILL.md +1 -2
  24. package/skills/gauntlet-resume/SKILL.md +7 -4
  25. package/skills/gauntlet-resume/reference/brief-contract.md +19 -6
  26. package/skills/gauntlet-resume/reference/reconstruction.md +18 -5
  27. package/skills/loop-implementation/SKILL.md +124 -0
  28. package/skills/shape-ticket/SKILL.md +37 -92
  29. package/skills/shape-ticket/reference/examples.md +41 -0
  30. package/skills/shape-ticket/reference/overlap-check.md +2 -2
  31. package/skills/shape-ticket/reference/repo-audit.md +108 -0
  32. package/skills/shape-ticket/reference/tracker-ladder.md +28 -0
@@ -173,7 +173,7 @@
173
173
  await h.phaseResult("substep", P({ brainstorm: "in_progress" }));
174
174
  await h.phaseResult("complete", P({ brainstorm: "complete" }));
175
175
  const rec2 = h.readRecord();
176
- assert.equal(rec2.approved_at, rec2.events.at(-1)!.ts);
176
+ assert.equal(rec2.approved_at, rec2.events.findLast((e) => e.kind === "phase")!.ts);
177
177
  assert.equal(h.gitWrites().length, 0, "the recorder never runs git add or git commit");
178
178
  assert.equal(readFileSync(h.excludeFile(), "utf8"), ".pi/gauntlet/telemetry/\n");
179
179
  await h.phaseResult("reset", P());
@@ -191,7 +191,7 @@
191
191
  assert.equal(h.readRecord("docs/specs/x.md").spec, "docs/specs/x.md");
192
192
  await h.phaseResult("complete", P({ brainstorm: "complete" }));
193
193
  const rec = h.readRecord("docs/specs/x.md");
194
- assert.equal(rec.approved_at, rec.events.at(-1)!.ts);
194
+ assert.equal(rec.approved_at, rec.events.findLast((e) => e.kind === "phase")!.ts);
195
195
  assert.equal(h.gitWrites().length, 0);
196
196
  });
197
197
 
@@ -310,7 +310,8 @@
310
310
  const rec = parseRecord(readFileSync(first.recordPath(), "utf8"))!;
311
311
  assert.equal(rec.run_id, runId);
312
312
  assert.deepEqual(rec.sessions, ["s1", "s2"]);
313
- assert.equal(rec.events.length, 1, "replay emits no duplicate events");
313
+ assert.equal(rec.events.filter((e) => e.kind !== "engine").length, 1, "replay emits no duplicate phase events");
314
+ assert.equal(rec.events.filter((e) => e.kind === "engine").length, 1);
314
315
  });
315
316
 
316
317
  test("rename at spec-writing: write to a new spec while the bound one is a draft rebinds and moves the record", async () => {
@@ -1111,7 +1112,7 @@
1111
1112
  const rec = parseRecord(readFileSync(first.recordPath(), "utf8"))!;
1112
1113
  assert.equal(rec.run_id, runId);
1113
1114
  assert.deepEqual(rec.sessions, ["s1", "s2"]);
1114
- assert.equal(rec.approved_at, rec.events.at(-1)!.ts);
1115
+ assert.equal(rec.approved_at, rec.events.findLast((e) => e.kind === "phase")!.ts);
1115
1116
  });
1116
1117
 
1117
1118
  test("reset then a new spec binds a fresh run with an empty buffer; the old record is untouched", async () => {
@@ -1280,3 +1281,56 @@
1280
1281
  const [planPass] = await h3.emit("tool_call", { toolName: "write", toolCallId: "w3", input: { path: "doc/specs/a.md", content: "# again" } });
1281
1282
  assert.equal(planPass, undefined);
1282
1283
  });
1284
+
1285
+ for (const { engine, example } of [
1286
+ { engine: "loop", example: false },
1287
+ { engine: "waves", example: false },
1288
+ { engine: undefined, example: false },
1289
+ { engine: "waves", example: true },
1290
+ { engine: undefined, example: true },
1291
+ ]) {
1292
+ test(`brainstorm complete records bound spec engine ${engine ?? "legacy"}${example ? " with fenced loop example" : ""}, unrelated phases do not`, async () => {
1293
+ const h = harness();
1294
+ await h.phaseResult("start", P({ brainstorm: "in_progress" }));
1295
+ await h.writeSpec("doc/specs/a.md", `# Spec\n${engine ? `**Engine:** ${engine}\n` : ""}${example ? "\n## Design\n```markdown\n**Engine:** loop\n```\n" : ""}`);
1296
+ await h.writeSpec("doc/specs/other.md", "**Engine:** waves\n");
1297
+ await h.phaseResult("complete", P({ brainstorm: "complete" }));
1298
+ await h.phaseResult("start", P({ brainstorm: "complete", plan: "in_progress" }));
1299
+ const rec = h.readRecord();
1300
+ assert.deepEqual(rec.events.filter((e) => e.kind === "engine").map((e) => e.engine), [engine ?? "waves"]);
1301
+ assert.equal(rec.derived.engine, engine ?? "waves");
1302
+ h.setBranch([
1303
+ { type: "message", message: { role: "toolResult", toolName: "phase_tracker", details: { action: "start", phases: P({ brainstorm: "in_progress" }) } } },
1304
+ { type: "message", message: { role: "toolResult", toolName: "phase_tracker", details: { action: "complete", phases: P({ brainstorm: "complete" }) } } },
1305
+ { type: "message", message: { role: "toolResult", toolName: "plan_check", details: { status: "pass", specPath: "doc/specs/a.md" } } },
1306
+ ]);
1307
+ for (const event of ["session_start", "session_switch", "session_fork", "session_tree"]) {
1308
+ await h.emit(event, {});
1309
+ assert.deepEqual(h.readRecord().events.filter((e) => e.kind === "engine"), rec.events.filter((e) => e.kind === "engine"));
1310
+ }
1311
+ });
1312
+ }
1313
+
1314
+ test("resume skip reads engine from bound spec live and on branch replay", async () => {
1315
+ for (const replay of [false, true]) {
1316
+ const h = harness();
1317
+ writeFileSync(join(h.root, "doc/specs/a.md"), "# Spec\n**Engine:** loop\n");
1318
+ const started = P({ brainstorm: "in_progress" });
1319
+ const skipped = { ...P({ brainstorm: "skipped" }), brainstorm: { status: "skipped", reason: "resume: doc/specs/a.md" } };
1320
+ if (replay) {
1321
+ h.setBranch([started, skipped].map((phases, i) => ({ type: "message", message: { role: "toolResult", toolName: "phase_tracker", details: { action: i ? "skip" : "start", phases } } })));
1322
+ await h.emit("session_start", {});
1323
+ } else {
1324
+ await h.phaseResult("start", started);
1325
+ await h.phaseResult("skip", skipped);
1326
+ }
1327
+ assert.equal(h.readRecord().derived.engine, "loop");
1328
+ assert.equal(h.readRecord().events.filter((e) => e.kind === "engine").length, 1);
1329
+ h.setBranch([started, skipped].map((phases, i) => ({ type: "message", message: { role: "toolResult", toolName: "phase_tracker", details: { action: i ? "skip" : "start", phases } } })));
1330
+ const events = h.readRecord().events.filter((e) => e.kind === "engine");
1331
+ for (const event of ["session_start", "session_switch", "session_fork", "session_tree"]) {
1332
+ await h.emit(event, {});
1333
+ assert.deepEqual(h.readRecord().events.filter((e) => e.kind === "engine"), events);
1334
+ }
1335
+ }
1336
+ });
@@ -420,11 +420,16 @@
420
420
  };
421
421
 
422
422
  // ---- phase mirror ----
423
- const applyPhaseDetails = async (details: { action?: string; phases?: PhaseMap; error?: string; rounds?: number } | undefined, snap: SettingsSnapshot) => {
423
+ const applyPhaseDetails = async (details: { action?: string; phases?: PhaseMap; error?: string; rounds?: number } | undefined, snap: SettingsSnapshot, replay = false) => {
424
424
  if (!details?.phases || details.error) return;
425
425
  const action = details.action ?? "";
426
426
  gauntletEntered = nextGauntletEntered(gauntletEntered, action, details.phases.brainstorm.status);
427
- const transitions = diffPhases(phases, details.phases, action);
427
+ const transitions = diffPhases(replay ? emptyPhases() : phases, details.phases, action).filter((t) => !replay || t.name === "brainstorm");
428
+ if (action === "skip" && !boundSpec && canBind() && lastCtx) {
429
+ const resumed = resumeSpecOf(details.phases, snap.specDirs);
430
+ const loc = resumed ? await locate(lastCtx, resumed) : undefined;
431
+ if (loc && isSpecPath(loc.rel, snap.specDirs)) await bind(loc.rel, snap, loc.toplevel, loc.via);
432
+ }
428
433
  if (action === "grant_fix_rounds") {
429
434
  live().gates.fix_round_grants += 1;
430
435
  pushEvent({ kind: "gate", gate: "fix_round_grant", rounds: typeof details.rounds === "number" ? details.rounds : undefined });
@@ -432,7 +437,7 @@
432
437
  }
433
438
  for (const t of transitions) {
434
439
  const model = ctxModel();
435
- const e = pushEvent(
440
+ const e = replay ? undefined : pushEvent(
436
441
  t.action === "start"
437
442
  ? { kind: "phase", action: "start", name: t.name, model, thinking: ctxThinking() }
438
443
  : t.action === "reset"
@@ -441,7 +446,15 @@
441
446
  );
442
447
  if (t.action === "start" && t.name === "brainstorm") councilPending = undefined;
443
448
  phases = details.phases;
444
- if (record && t.name === "brainstorm" && (t.action === "complete" || t.action === "skip")) record.approved_at ??= e.ts;
449
+ if (record && boundSpec && t.name === "brainstorm" && (t.action === "complete" || t.action === "skip")) {
450
+ if (e) record.approved_at ??= e.ts;
451
+ if (!replay || !record.events.some((event) => event.kind === "engine")) {
452
+ const body = deps.fs.readFile(abs(boundSpec)) ?? "";
453
+ const header = body.split(/^## /m, 1)[0];
454
+ const engine = /^\*\*Engine:\*\*[ \t]+loop[ \t]*$/m.test(header) ? "loop" : "waves";
455
+ pushEvent({ kind: "engine", engine });
456
+ }
457
+ }
445
458
  if (record && t.name === "ship" && t.action === "complete" && checkoutVia === "jj") await onShipKeep(snap);
446
459
  flush();
447
460
  if (t.action === "reset") {
@@ -452,14 +465,6 @@
452
465
  }
453
466
  }
454
467
  phases = details.phases;
455
- if (action === "skip" && !boundSpec && canBind() && lastCtx) {
456
- const resumed = resumeSpecOf(details.phases, snap.specDirs);
457
- const loc = resumed ? await locate(lastCtx, resumed) : undefined;
458
- if (loc && isSpecPath(loc.rel, snap.specDirs)) {
459
- await bind(loc.rel, snap, loc.toplevel, loc.via);
460
- flush();
461
- }
462
- }
463
468
  };
464
469
 
465
470
  let lastCtx: ExtensionContext | undefined;
@@ -493,6 +498,10 @@
493
498
  const loc = candidate ? await locate(ctx, candidate) : undefined;
494
499
  if (loc && isSpecPath(loc.rel, snap.specDirs)) {
495
500
  await bind(loc.rel, snap, loc.toplevel, loc.via);
501
+ const status = replay.phases.brainstorm.status;
502
+ if (status === "complete" || status === "skipped") {
503
+ await applyPhaseDetails({ action: status === "complete" ? "complete" : "skip", phases: replay.phases }, snap, true);
504
+ }
496
505
  flush();
497
506
  }
498
507
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "7.27.0",
3
+ "version": "7.29.0",
4
4
  "description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
5
5
  "author": "Jacek Juraszek",
6
6
  "type": "module",
@@ -62,7 +62,7 @@
62
62
  "test": "node scripts/ci.mjs"
63
63
  },
64
64
  "peerDependencies": {
65
- "pi-cohort": ">=7.4.4"
65
+ "pi-cohort": ">=7.4.5"
66
66
  },
67
67
  "dependencies": {
68
68
  "yaml": "^2.9.0"
@@ -32,7 +32,7 @@ Treat the flag as discovery control, not harness authorization. The command reli
32
32
 
33
33
  ## Checklist
34
34
 
35
- Work through the items below **in order**. After a user answer or completed side task, resume the current checklist step and perform the next authorized action in the same turn, advancing once that step's required decisions and approvals are satisfied. Continue until a required question is unanswered, an existing approval is pending, the user explicitly pauses, or a failed required tool or unavailable required resource blocks progress. 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.
35
+ Work through the items below **in order**. After a user answer or completed side task, resume the current checklist step and perform the next authorized action in the same turn, advancing once that step's required decisions and approvals are satisfied. Continue until a required question is unanswered, an existing approval is pending, the user explicitly pauses, or a failed required tool or unavailable required resource blocks progress. 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 route 1/2 to `/skill:writing-plans` and 4/5 to `/skill:loop-implementation` per the gate. Do not jump ahead of approval, and do not silently drop the critique pass.
36
36
 
37
37
  1. **Start brainstorm tracking (fresh epoch)** - as the first action on authorized fresh entry, before reading code, the worktree, or answering the user, reset both trackers and start the phase. A new brainstorm owns a clean slate, so stale phases and tasks from earlier work are cleared; re-entering mid-brainstorm is safe (nothing to lose, same clean slate):
38
38
 
@@ -53,7 +53,7 @@ Work through the items below **in order**. After a user answer or completed side
53
53
  10. **Re-run placeholder scan** - follow [Spec Self-Review](#spec-self-review-before-user-review-gate).
54
54
  11. **Generate spec summary** - follow [User Review Gate](#user-review-gate).
55
55
  12. **User review gate** - follow [User Review Gate](#user-review-gate).
56
- 13. **Transition** - after approval, invoke `/skill:writing-plans` per [User Review Gate](#user-review-gate).
56
+ 13. **Transition** - after approval, invoke `/skill:writing-plans` on 1/2 or `/skill:loop-implementation` on 4/5 per [User Review Gate](#user-review-gate).
57
57
 
58
58
  ## Project Routing
59
59
 
@@ -186,7 +186,7 @@ Classify first. Redraw test: the change alters the problem statement, adds or re
186
186
 
187
187
  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.
188
188
 
189
- 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); step 4's first-use check covers the read. Spec-writing overwrites it; the full gate follows.
189
+ 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 and the loop ledger (identified by line 1 `# Ledger - loop engine` in the resolved plan dir), resume at checklist step 4 with the approved spec as the draft (steps 2-3 skipped); step 4's first-use check covers the read. Spec-writing overwrites it; the full gate follows.
190
190
 
191
191
  ## Red Flags — STOP
192
192
 
@@ -4,11 +4,11 @@
4
4
 
5
5
  ## Standing grants
6
6
 
7
- Honor a user sentence in this flow that waives per-diff review for later amends (`auto-apply amends`, `approve, auto-apply amends` or a bare `2` at the spec gate, `auto-apply amends, stop only for redraws`, `apply spec fixes without asking`, or the same intent in other words). The digit grants only at the spec gate, and a prose grant after a `1` approval still counts. Apply every later amend-class change without review, scope changes included; redraws always stop and the grant never satisfies the spec gate.
7
+ Honor a user sentence in this flow that waives per-diff review for later amends (`auto-apply amends`, `approve, auto-apply amends` or a bare `2` at the spec gate (a bare `5` grants the same way), `auto-apply amends, stop only for redraws`, `apply spec fixes without asking`, or the same intent in other words). The digit grants only at the spec gate, and a prose grant after a `1` approval still counts. Apply every later amend-class change without review, scope changes included; redraws always stop and the grant never satisfies the spec gate.
8
8
 
9
9
  A grant is the spec's `**Amend-grant:**` line read through the shared predicate; `none` or a missing line means no grant. A new brainstorm writes `none`; a resume honors the line as written. Never take a grant from commit history, a handoff brief, or the transcript; the one read of a prior commit is the post-revert restore, which reads the pre-revert HEAD's spec line.
10
10
 
11
- **The line.** `**Amend-grant:** none` or `**Amend-grant:** <granting sentence verbatim, one physical line>`, in the header block (the span between the H1 and the first `##`). Writers place it directly below `**Goal:**` when that line exists, otherwise on the first line after the H1, its blank line, and any `> **Superseded by:**` banner lines; readers never depend on the position. When the reply was the digit `2`, the sentence is the grant description from the gate menu (the text after `2 - approve, auto-apply amends:` in `spec-finalization.md`), never the digit. Collapse a prose grant to one physical line before the write: newlines and runs of whitespace become single spaces, wording preserved. The same substitution applies wherever this file quotes the granting sentence in a commit body.
11
+ **The line.** `**Amend-grant:** none` or `**Amend-grant:** <granting sentence verbatim, one physical line>`, in the header block (the span between the H1 and the first `##`). Writers place it directly below `**Goal:**` when that line exists, otherwise on the first line after the H1, its blank line, and any `> **Superseded by:**` banner lines; readers never depend on the position. When the reply was the digit `2` or `5`, the sentence is the grant description from the gate menu (the text after `2 - approve, auto-apply amends:` in `spec-finalization.md`), never the digit. Collapse a prose grant to one physical line before the write: newlines and runs of whitespace become single spaces, wording preserved. The same substitution applies wherever this file quotes the granting sentence in a commit body.
12
12
 
13
13
  **The predicate.** Every reader (gauntlet-resume at entry, this file at each batch boundary) applies it verbatim over the header block; occurrences below the first `##` never count.
14
14
 
@@ -26,7 +26,7 @@ A grant is the spec's `**Amend-grant:**` line read through the shared predicate;
26
26
  | Event | Edit |
27
27
  |---|---|
28
28
  | Grant given at a tier-2 footer | `edit` the value to the sentence |
29
- | Line absent (legacy spec) | insert the line at the writer placement above; when a plan exists, the insertion shifts every heading by one line, so run the section 5 aftermath's anchor repair (the `plan-writer` amend dispatch, then the parent `plan_check`) and commit spec and plan together |
29
+ | Line absent (legacy spec) | insert the line at the writer placement above; when a loop ledger exists, use section 5's ledger aftermath; otherwise when a plan exists, the insertion shifts every heading by one line, so run the section 5 aftermath's anchor repair (the `plan-writer` amend dispatch, then the parent `plan_check`) and commit spec and plan together |
30
30
  | Revocation (`revoke auto-apply amends`, `stop auto-applying amends`, or the same intent in other words) | `edit` the value to `none` |
31
31
  | Hand-edited line | not an event; the next batch boundary re-reads it and the value found is the current consent |
32
32
 
@@ -142,7 +142,7 @@ Before the aftermath, `read` this section again; never run it from memory.
142
142
 
143
143
  Apply accepted items only: reviewer clears, `accept`, `alt-n`, and state-changing `custom`. Print one line per applied item: `amended the spec: <title> - <what>`.
144
144
 
145
- 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 -> read `git -C "<abs worktree path>" status --porcelain` (plain jj: `jj -R "<abs worktree path>" diff --summary`); collect the parent's in-flight workflow edits as worktree-relative paths, including the amended spec and staged in-place wave-repair deliverables. Dispatch `plan-writer` per `writing-plans` `## Author the plan` step 3 with an `Amend:` block naming the amended spec sections, an `In-flight:` list of those paths (the parent supplies the set; the child never infers it), and the rule "preserve every task title and wave position the amendment does not touch; edit only affected anchors and tasks", then run that section's steps 5-6 (the parent `plan_check` and its branches), then read the amended plan once (step 7's read, without its phase completion or `plan_tracker` init) - the main loop never edits the plan text; then update the tracker: `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`). 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.
145
+ Aftermath, once per batch, only when at least one item applied (nothing applied -> skip to the commit below). Ledger exists (line 1 `# Ledger - loop engine` in the resolved plan dir) -> skip `plan-writer`, `plan_check`, and `plan_tracker`. Reconcile the ledger against the approved spec dispositions: `deviates:`, `deferred:`, or `elsewhere:` -> `cut`, evidence = the disposition; factual or requirement-preserving amendment -> preserve rows; re-bounded `done` row -> `open`, evidence = `reopened by amendment`. Preserve every row's attempts and the ledger's iterations. Never auto-edit ticket AC row texts. Commit spec + ledger together with subject `loop: cut row <n>` for a single cut, otherwise `loop: amend`, carrying the amendment audit body below (including each `granted` record and granting sentence); continue without task-plan aftermath. No plan yet -> commit the spec; continue. Plan exists -> read `git -C "<abs worktree path>" status --porcelain` (plain jj: `jj -R "<abs worktree path>" diff --summary`); collect the parent's in-flight workflow edits as worktree-relative paths, including the amended spec and staged in-place wave-repair deliverables. Dispatch `plan-writer` per `writing-plans` `## Author the plan` step 3 with an `Amend:` block naming the amended spec sections, an `In-flight:` list of those paths (the parent supplies the set; the child never infers it), and the rule "preserve every task title and wave position the amendment does not touch; edit only affected anchors and tasks", then run that section's steps 5-6 (the parent `plan_check` and its branches), then read the amended plan once (step 7's read, without its phase completion or `plan_tracker` init) - the main loop never edits the plan text; then update the tracker: `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`). 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.
146
146
 
147
147
  One commit per batch:
148
148
 
@@ -153,7 +153,7 @@ amend: <N> item(s) - <first title>[, <second title>]
153
153
  <the granting sentence, quoted, when a grant applied>
154
154
  ```
155
155
 
156
- `amend:` is the subject marker `finishing-a-development-branch` Step 4 greps for its digest; `<what>` is the item's one-sentence what-changes field, so the digest renders `<title> - <what changed>` from the body alone. When no item applied (every item dropped or redirected), nothing changed on disk; the commit still lands, with `git commit --allow-empty`, so the per-item `custom(<effect>)` records stay in the batch body - the digest ignores them because it reads only `auto-apply` and `granted` records. Wrong apply -> `git revert` the batch commit; then read the `**Amend-grant:**` line from the pre-revert HEAD (`git -C <abs worktree path> show HEAD^:<spec path>` through the predicate) and, if the working tree's line differs, restore that value and persist it per [Standing grants](#standing-grants) before re-entering this surface for the items to keep - the pre-revert HEAD holds every consent change committed so far, revocations included.
156
+ For a ledger batch, use the loop subject above and retain this same audit body. `amend:` is the subject marker `finishing-a-development-branch` Step 4 greps for its digest; `<what>` is the item's one-sentence what-changes field, so the digest renders `<title> - <what changed>` from the body alone. When no item applied (every item dropped or redirected), nothing changed on disk; the commit still lands, with `git commit --allow-empty`, so the per-item `custom(<effect>)` records stay in the batch body - the digest ignores them because it reads only `auto-apply` and `granted` records. Wrong apply -> `git revert` the batch commit; then read the `**Amend-grant:**` line from the pre-revert HEAD (`git -C <abs worktree path> show HEAD^:<spec path>` through the predicate) and, if the working tree's line differs, restore that value and persist it per [Standing grants](#standing-grants) before re-entering this surface for the items to keep - the pre-revert HEAD holds every consent change committed so far, revocations included.
157
157
 
158
158
  ## Conformance entry
159
159
 
@@ -126,11 +126,15 @@ New predecessor candidates at spec-writing: <path> (<title>), ... - index rows a
126
126
  1 - approve: proceed to planning under the existing amendment review; a fresh reviewer applies evidence-backed factual corrections on its own, and every other spec amendment (scope, acceptance-criteria, or contract edits, and redraws) stops for your review.
127
127
  2 - approve, auto-apply amends: every later spec amendment in this flow (corrected facts, paths, verification lines, and scope, acceptance-criteria, or public-contract edits alike) applies without asking; only a redraw (changed problem statement, component added, removed, or re-bounded) still stops for you, and the grant never stands in for a spec approval.
128
128
  3 - show council dispositions: prints the council audit verbatim, then re-presents this gate. Not an approval.
129
- Add "; fund N fix rounds" (or "N more fix rounds") to 1 or 2 to fund N implement fix rounds past the escalated round.
129
+ 4 - approve, loop engine (experimental): skip the plan; iterate a fresh loop-implementer over the acceptance-criteria ledger; amendments stop for your review as under 1.
130
+ 5 - approve, loop engine, auto-apply amends: as 4 with option 2's amendment grant.
131
+ Add "; fund N fix rounds" (or "N more fix rounds") to 1, 2, 4, or 5 to fund N extra attempts per stagnant loop row or N implement fix rounds past the escalated waves round.
130
132
 
131
133
  Or tell me what to change in the spec, or say "revert applied council edit <X>" to undo a specific applied edit.
132
134
  ```
133
135
 
136
+ For `none - <reason>` in `## Acceptance criteria`, render only options 1-3 and explain: "Loop engine unavailable: this spec has no acceptance-criteria rows." Keep the worker-path omission of option 3; do not invent a council audit.
137
+
134
138
  If you believe the summary needs correcting, do **not** silently rewrite it - re-dispatch the summarizer or note the discrepancy as an adjacent line beneath the verbatim block.
135
139
 
136
140
  **Revert valve.** "Revert applied council edit X" is a normal change request: revise the spec to undo edit X, re-dispatch the summarizer with a **fresh** temp path (per the re-dispatch rule below), and re-present the gate. This is cheap here - the spec is not yet plan- or code-bearing.
@@ -142,13 +146,16 @@ Wait for the user. Interpret funded replies with this table before routing chang
142
146
  | bare `2` | approve; persist the option-2 description above |
143
147
  | `2; fund 3 fix rounds` or `2, fund 3 fix rounds` | approve; persist that description plus `; fund 3 fix rounds`, not a change request |
144
148
  | `1` with a budget token | approve; persist `fund 3 fix rounds` alone for that token (budget-only) |
149
+ | bare `5` | approve; persist the option-2 description above; engine loop |
150
+ | `5; fund 3 fix rounds` or `5, fund 3 fix rounds` | approve; persist the option-2 description plus `; fund 3 fix rounds`; engine loop |
151
+ | `4` with a budget token | approve; persist the token alone (budget-only); engine loop |
145
152
  | approval prose with a token | approve; preserve the prose collapsed to one physical line |
146
- | budget token alone (`fund 3 fix rounds`) | re-present the gate asking for `1` or `2`; write nothing |
153
+ | budget token alone (`fund 3 fix rounds`) | re-present the gate asking for `1`, `2`, `4`, or `5`; write nothing |
147
154
  | token with malformed N | re-present the gate quoting the token; write nothing, never imply a change request |
148
155
 
149
156
  For every value to persist, read [Standing grants](amendment-surface.md#standing-grants) and run Value decoding. Decode before `edit` or `--amend`; never persist a malformed token. Use the decoded table value in the approval persistence below, including budget-only approvals.
150
157
 
151
- On a change request (including a revert), revise the spec and re-present - mint a **fresh** temp path for the re-dispatched summarizer (never reuse a prior round's path, so stale content can never be mistaken for the new summary). On `3` or its label alone: print the audit verbatim from `git -C <abs worktree path> show -s --format=%b HEAD` (never from a held tool result; the body survives the grant's `--amend --no-edit`), then re-present the same gate - the briefing text of the gate message just rendered, reused verbatim (no summarizer re-dispatch, no read of the removed `$SUMMARY_PATH`; if that text is no longer in context, the one-line degrade note takes its place), the `Council:` line, the adjacent lines, the three rows; nothing in the spec or the commit changes. `3` plus a change request is a change request. `3` plus `1`/`2` prints and re-presents, and the approval is read only from a clean `1`/`2` on the re-presented gate - a reply carrying `3` never grants. A gate-round recommit (change request or revert) re-runs the same heredoc commit with the audit as its body, so HEAD always carries it; on a revert of edit X, move X's `Applied:` line to `Rejected:` with `-> reverted at gate` and recompute the `Council:` counts from the revised audit. On approval - `1`, `approve`, or equivalent prose approves without a grant; `2`, `approve, auto-apply amends`, or equivalent prose approves and grants - proceed immediately to `/skill:writing-plans` with no further prompt; first read [Standing grants](amendment-surface.md#standing-grants) (stop if unreadable). On a grant or funded approval, `edit` the spec's `**Amend-grant:**` value to the table value for funded replies, otherwise the granting sentence (the digit `2` becomes the grant description after `2 - approve, auto-apply amends:` above), then `git -C <abs worktree path> add -- <spec path>` and `git -C <abs worktree path> commit --amend --no-edit -q`, so the spec commit carries the line and keeps its council-audit body; then confirm `git -C <abs worktree path> show HEAD:<spec path>` reads "grant active" through the predicate. On a plain approval (`1`, `approve`) the value stays `none` and the commit is untouched. A reply that mixes approval with a change request ("2 but rename the section") is a change request: revise, re-present, and read the grant only from the reply to the re-presented gate. If the amend or the HEAD check fails, stop and report; never proceed as granted. The plan and execution mode are mechanical derivatives, so the only human gate here is spec approval itself. Don't land the spec on `main`; it stays in the worktree and ships in the same squash commit as the implementation.
158
+ On a change request (including a revert), revise the spec and re-present - mint a **fresh** temp path for the re-dispatched summarizer (never reuse a prior round's path, so stale content can never be mistaken for the new summary). On `3` or its label alone: print the audit verbatim from `git -C <abs worktree path> show -s --format=%b HEAD` (never from a held tool result; the body survives the grant's `--amend --no-edit`), then re-present the same gate - the briefing text of the gate message just rendered, reused verbatim (no summarizer re-dispatch, no read of the removed `$SUMMARY_PATH`; if that text is no longer in context, the one-line degrade note takes its place), the `Council:` line, the adjacent lines, the rendered option rows; nothing in the spec or the commit changes. `3` plus a change request is a change request. `3` plus `1`/`2`/`4`/`5` prints and re-presents, and the approval is read only from a clean `1`/`2`/`4`/`5` on the re-presented gate - a reply carrying `3` never grants. A gate-round recommit (change request or revert) re-runs the same heredoc commit with the audit as its body, so HEAD always carries it; on a revert of edit X, move X's `Applied:` line to `Rejected:` with `-> reverted at gate` and recompute the `Council:` counts from the revised audit. On approval - `1`, `approve`, or equivalent prose approves without a grant; `2`, `approve, auto-apply amends`, or equivalent prose approves and grants; `4` approves without a grant; `5` approves and grants using option 2's description - first read [Standing grants](amendment-surface.md#standing-grants) (stop if unreadable). On every approval, `edit` the spec's `**Amend-grant:**` value to the decoded table value for funded replies, otherwise the granting sentence for 2/5 or `none` for 1/4. Write exactly one `**Engine:** loop` for 4/5 or `**Engine:** waves` for 1/2 directly below `**Amend-grant:**` in the header block (readers: absent -> `waves`). Then `git -C <abs worktree path> add -- <spec path>` and `git -C <abs worktree path> commit --amend --no-edit -q`, keeping the council-audit body; confirm `git -C <abs worktree path> show HEAD:<spec path>` contains both fields with the selected values and satisfies the grant predicate and Value decoding. A reply that mixes approval with a change request ("2 but rename the section") is a change request: revise, re-present, and read the grant only from the reply to the re-presented gate. If the amend or the HEAD check fails, stop and report; never proceed as granted. The plan and execution mode are mechanical derivatives, so the only human gate here is spec approval itself. Don't land the spec on `main`; it stays in the worktree and ships in the same squash commit as the implementation.
152
159
 
153
160
  Post-approval changes follow [Amending an approved spec](../SKILL.md#amending-an-approved-spec).
154
161
 
@@ -157,3 +164,5 @@ After approval, mark the brainstorm phase complete:
157
164
  ```
158
165
  phase_tracker({ action: "complete", phase: "brainstorm" })
159
166
  ```
167
+
168
+ Then invoke `/skill:writing-plans` on 1/2 or `/skill:loop-implementation` on 4/5 immediately, with no further prompt.
@@ -17,6 +17,6 @@ Ticket <ref>, <heading or "checkbox list">, rows verbatim:
17
17
  <disposition>
18
18
  ```
19
19
 
20
- The heading `## Acceptance criteria` names the ticket contract only; the spec's own requirements stay in Design, and the section stays flat - a row's repo lives in its disposition token, never in a heading. Disposition is exactly one of `in-scope`, `deviates: <why>`, `deferred: <where>`, `venue: <env> - <observation>`, `elsewhere: <repo>`; default `in-scope`. Never edit the row text: an ambiguous row stays `in-scope` with the chosen reading written as a Design clause, and a moved path or a workflow artifact (spec, plan, changelog, telemetry file) is a Design clause too, never a disposition and never asked. A row under a repo group that is not this repo is `elsewhere: <repo>`, stated, never asked; a group matching no repo asks `A) this repo B) elsewhere: <name> C) repair the ticket - /skill:shape-ticket <ref>`. Name a `venue:` row's enabling change in Design; `venue:` states where the observation can happen and is never `prd`, `prod`, or `production` - a row observable only in production is a wrong row. An `in-scope` row is a requirement as written; disposition reasons are never normative for the plan or the reviewer, so a `deviates:` reason that adopts part of a row restates that part as a Design clause. A `deferred:` destination is a tracker ref (`#N`, `owner/repo#N`, `ABC-123`), a spec path, or a URL; a prose destination with none of these is a gap at the PR gate.
20
+ The heading `## Acceptance criteria` names the ticket contract only; the spec's own requirements stay in Design, and the section stays flat - a row's repo lives in its disposition token, never in a heading. Disposition is exactly one of `in-scope`, `deviates: <why>`, `deferred: <where>`, `venue: <env> - <observation>`, `elsewhere: <repo>`; default `in-scope`. Never edit the row text: an ambiguous row stays `in-scope` with the chosen reading written as a Design clause, and a moved path or a workflow artifact (spec, plan, changelog, telemetry file) is a Design clause too, never a disposition and never asked. A row under a repo group that is not this repo is `elsewhere: <repo>`, stated, never asked; a group matching no repo asks `A) this repo B) elsewhere: <name> C) repair the ticket - /skill:shape-ticket <ref>`. Replace `<name>` with the supplied unmatched group heading verbatim. Name a `venue:` row's enabling change in Design; `venue:` states where the observation can happen and is never `prd`, `prod`, or `production` - a row observable only in production is a wrong row. An `in-scope` row is a requirement as written; disposition reasons are never normative for the plan or the reviewer, so a `deviates:` reason that adopts part of a row restates that part as a Design clause. A `deferred:` destination is a tracker ref (`#N`, `owner/repo#N`, `ABC-123`), a spec path, or a URL; a prose destination with none of these is a gap at the PR gate.
21
21
 
22
22
  **Cut question.** `deviates:` and `deferred:` come only from the user's answer to this question; never propose one. Ask it, in the questionary format, when the user's prompt or an answer raises dropping or changing a row, or when code, a cited contract, or the user shows a row is wrong or impossible: `A) in-scope - <reading> B) deviates: <why> C) deferred: <where> D) repair the ticket - /skill:shape-ticket <ref> <row and evidence>`, `Recommendation:` A or D with the reason the row ships in this gauntlet; B and C are never recommended. On D, run `/skill:shape-ticket <ref> <row and evidence>` (repair mode per `shape-ticket/SKILL.md:43`, its own confirm gate), then re-fetch the ticket, replace the gather draft's `## Ticket acceptance criteria (verbatim)` block, and re-extract; a cancel at that gate returns to this question with the row unchanged. Carry the full question and the user's answer of every question that offered a disposition into the council's `Human input` block. The gate lists every `deferred:`, `deviates:`, and `venue:` row on its `Scope cuts:` line (`spec-finalization.md`). A disposition changes in any later phase - a reviewer finding `recommended: rescope` at the finish gate, a wrong row noticed mid-implementation, or a ticket edited after approval when the user asks - through [Amending an approved spec](../SKILL.md#amending-an-approved-spec) (`amendment-surface.md`); the row text still never changes. With no ticket, or a ticket without ACs, the section body is the single line `none - no ticket`, `none - ticket has no acceptance criteria`, or `none - ticket not fetched (<reason>)`. Never author acceptance criteria on the ticket's behalf; that is `/skill:shape-ticket`'s job.
@@ -34,7 +34,7 @@ Then call `phase_tracker({ action: "start", phase: "ship" })`.
34
34
 
35
35
  **Hard verification gate.** Tests/format/lint must pass before presenting any options — including Discard. The user's stated intent to throw the branch away does not change whether the diff is in a verifiable state; verifying first surfaces accidental damage to unrelated code before the branch is gone forever. The skip rule below is the only exception.
36
36
 
37
- Run the plan header's `**Verification:**` set in the worktree: `(cd "$WORKTREE" && <command>)`. With no plan in this session, run the verification command(s) of the affected services from the gauntlet overrides file or `AGENTS.md` (look for "verification", "CI", or "test" sections).
37
+ Recognize a ledger by line 1 exactly `# Ledger - loop engine`. For a ledger, use its `verification:` command in place of the task plan header's `**Verification:**` set throughout finish, including the minors round. Run that set in the worktree: `(cd "$WORKTREE" && <command>)`. With no plan in this session, run the verification command(s) of the affected services from the gauntlet overrides file or `AGENTS.md` (look for "verification", "CI", or "test" sections).
38
38
 
39
39
  **Skip rule.** Skip the run only when this set passed on a known clean commit in the verify phase of this session and no edit or write landed outside `<telemetry.dir>` since. Check the current tree: `git -C "$WORKTREE" diff --quiet <commit the run passed on> -- . ':!<telemetry.dir>'` must exit 0, and `git -C "$WORKTREE" status --porcelain --untracked-files=all -- . ':!<telemetry.dir>'` must exit 0 with empty output (`<telemetry.dir>` defaults to `.pi/gauntlet/telemetry`). These checks cover committed, staged, unstaged, and untracked changes outside telemetry. Otherwise run it once; a missing verified commit, a failed check, or uncertainty means run.
40
40
 
@@ -169,7 +169,7 @@ A **heavy** revert is not a menu toggle — say so explicitly to the user before
169
169
 
170
170
  ### Step 4: Present Options
171
171
 
172
- **Amendment digest (both variants).** Before the options, read `git -C "$WORKTREE" log <base-branch>..HEAD --grep '^amend:'` and take the `auto-apply` and `granted` records from those commit bodies. Render, then the options:
172
+ **Amendment digest (both variants).** Before the options, read `git -C "$WORKTREE" log <base-branch>..HEAD --extended-regexp --grep '^(amend:|loop: cut row [0-9]+|loop: amend)'` and take the `auto-apply` and `granted` records from those commit bodies. Render, then the options:
173
173
 
174
174
  ```
175
175
  Amendments auto-applied (N):
@@ -221,11 +221,11 @@ Rows 1-2 run the Option 1/2 blocks; row 3 runs the Keep block and row 4 the Disc
221
221
 
222
222
  Run this on the feature branch before any landing path. The spec and its telemetry record (`<telemetry.dir>/<spec path with .md -> .yaml>`, default `.pi/gauntlet/telemetry/doc/specs/<spec>.yaml`) are deliverables and ship in the squash; only the plan is stripped. The record is an untracked, git-excluded file until this step commits it (in a plain jj workspace there is no commit; jj snapshots the stamped record into the working-copy change). `<bin>` is `<directory of this skill's SKILL.md>/../../bin`, resolved from the skill's `<location>` in the system prompt.
223
223
 
224
- Read the plan's `**Spec:**` header (repo-relative, backticks stripped) before the strip; pass that value as `--spec`. Resolve the spec dirs with `gauntlet_setting({ key: "flowGuards" })` and use their sibling `plans` dirs for plan paths.
224
+ Read the existing artifact's path-only `**Spec:**` header (repo-relative, backticks stripped) before removal; pass that value as `--spec`, including for a line-1 ledger. Resolve the spec dirs with `gauntlet_setting({ key: "flowGuards" })` and use their sibling `plans` dirs for plan paths. Enumerate both PLAN_PATH candidates: the task plan paired by basename and `<sibling plans dir>/<spec stem>-ledger.md`. Preserve the spec and telemetry.
225
225
 
226
226
  ```bash
227
227
  # Plans are ephemeral - if one was committed on this branch, remove it before landing.
228
- PLAN_PATH=<sibling-plans-dir>/<plan-file>.md # the sibling plans dir of the spec's dir
228
+ for PLAN_PATH in <basename-task-plan-path> <sibling-plans-dir>/<spec-stem>-ledger.md; do
229
229
  if git -C "$WORKTREE" ls-files --error-unmatch "$PLAN_PATH" >/dev/null 2>&1; then
230
230
  git -C "$WORKTREE" rm "$PLAN_PATH" && git -C "$WORKTREE" commit -m "Remove ephemeral plan doc"
231
231
  elif [ -f "$WORKTREE/$PLAN_PATH" ] && jj -R "$WORKTREE" root >/dev/null 2>&1; then
@@ -233,10 +233,11 @@ elif [ -f "$WORKTREE/$PLAN_PATH" ] && jj -R "$WORKTREE" root >/dev/null 2>&1; th
233
233
  # root: anchors the path at the workspace root - jj resolves bare paths against cwd, not -R.
234
234
  jj -R "$WORKTREE" restore --from <base-branch> -- "root:\"$PLAN_PATH\""
235
235
  fi
236
+ done
236
237
 
237
238
  # Seal the telemetry record: stamp shipped, compute the diff, commit it once.
238
239
  # The bin detects a plain jj workspace (no .git) itself: jj diff, no commit step.
239
- # --option is pr for Options 1-2 and squash for Option 3; --spec is the plan header's **Spec:** value (omit when no plan is known).
240
+ # --option is pr for Options 1-2 and squash for Option 3; --spec is the artifact header's **Spec:** value (omit when no artifact is known).
240
241
  node <bin>/gauntlet-telemetry-seal.mjs --worktree "$WORKTREE" --option <pr|squash> --base <base-branch> --spec <spec path>
241
242
  ```
242
243
 
@@ -83,8 +83,7 @@ loop: tracker state is extension state no subagent can read.
83
83
  - A phase `in_progress` (verify or ship included - resume stops at verify on its own)
84
84
  -> append the process-state section exactly as `## Producers` in
85
85
  `reference/brief-contract.md` lays it out: both status outputs verbatim
86
- (`No plan active.` verbatim when there is no plan), the active-task line naming
87
- the first `→` task else `none`, the gate-history line. Append with a single
86
+ (`No plan active.` verbatim when there is no plan), the contract's artifact-specific fields, and the gate-history line. Recognize the loop alternative by ledger line 1; read the ledger from the run worktree for its durable last decision. Use only the contract's grammar; write no second state file. Append with a single
88
87
  `cat >> "<brief path>" <<'EOF' ... EOF` whose body is that block.
89
88
  - Re-read the file tail and confirm it ends with the gate-history line from the
90
89
  contract and nothing after.
@@ -100,7 +100,7 @@ checks 2, 4, 5 do not apply; check 6 runs on the seed's pinned spec path.
100
100
  6. **Grant field.** Runs once the route has resolved `<spec path>` - the `S` path that
101
101
  `reference/brief-contract.md` "Process-state restore" resolves for `skip brainstorm`
102
102
  (process-state briefs), the spec reconstruction resolves and the human approves
103
- (Spec without plan) or confirms (Spec with plan) (`reference/reconstruction.md`), or
103
+ (Spec without plan) or confirms (Spec with ledger or Spec with plan) (`reference/reconstruction.md`), or
104
104
  the pinned `<full-path>/<rel>` of a spec tracked on `main` (`reference/seed.md`) -
105
105
  and before that route's first tracker call; the path is carried, never re-resolved.
106
106
  Read the spec's header block (between the H1 and the first `##`) through the
@@ -142,9 +142,12 @@ owning skill **without** its reset-bearing entry:
142
142
  | Active after restore | Continue in | Entry point |
143
143
  |---|---|---|
144
144
  | brainstorm | [brainstorming checklist](../brainstorming/SKILL.md#checklist) | on-disk state decides the step: draft marker on line 1 of the spec file -> step 4; spec title -> step 8. Never `/skill:brainstorming` entry (it resets both trackers) |
145
- | plan | writing-plans body | skip its `start plan` call (already in_progress) |
146
- | implement | subagent-driven-development | re-validate `<task>` first (re-run its `Tests:`), then the task loop from the first non-complete task |
147
- | verify | verification-before-completion | full conformance gate; nothing carried over |
145
+ | approved spec with `**Engine:** loop`, no ledger | loop-implementation | after check 6, arm with `start brainstorm`, then `skip brainstorm resume: <spec path>` before driver admission; carry absolute worktree/spec paths and plan directory through Entry steps 1-5 per `reference/reconstruction.md` "Spec without plan": settings step 1, verification step 2, driver's plan skip step 3, ledger creation step 4, check/commit and implement start step 5; no writing-plans body or task tracker |
146
+ | plan with task engine | writing-plans body | skip its `start plan` call (already in_progress) |
147
+ | implement with line-1 ledger | loop-implementation | complete `reference/reconstruction.md` "Spec with ledger" ordered restore, cited by `reference/brief-contract.md`'s call table: entry checks 1-6, brainstorm start/skip, plan start/check/skip with reason `resume: <spec> (loop engine)`, implement start, no task tracker; state the entire ordered sequence explicitly even when entry checks are already reported as passed; then continue at step 5 (`Before each dispatch`), preserving ledger iterations/retry data; dirty tree stops before dispatch; reuse the existing ledger |
148
+ | implement with task plan | subagent-driven-development | re-validate `<task>` first (re-run its `Tests:`), then the task loop from the first non-complete task |
149
+ | verify with `Ledger:` (including ship-stage ledger briefs restored at verify) | /skill:loop-implementation | read `After the loop` and its references; continue from step 1, full ledger verification, after the already-restored `start verify`; retain whole-diff code review, happy path when triggered, fresh conformance review and finishing; no task-tracker calls |
150
+ | verify without `Ledger:` | verification-before-completion | full conformance gate; nothing carried over |
148
151
 
149
152
  ## Red flags — STOP
150
153
 
@@ -29,9 +29,17 @@ Headings, fixed order. A consumer keys on headings, never prose.
29
29
  | `## Skills loaded` | frontmatter `name`s; `## Skills loaded: none` when none | always |
30
30
  | `## Process state` | `phase_tracker status` then `plan_tracker status` verbatim; `Active task: <name|none>`; the line `Gate history not restored - re-validate before advancing.` | only when a phase is `in_progress` and no hotfix flow is in context |
31
31
 
32
+ For a loop ledger (line 1 exactly `# Ledger - loop engine`), replace `Active task` with these two fields, retaining both tracker status outputs and the gate-history line:
33
+
34
+ ```text
35
+ Ledger: <path>
36
+ Last decision: <line>
37
+ ```
38
+
39
+ Use the ledger path relative to the worktree and the last physical decision line, or `none` for an empty Decisions section. Read the ledger on disk; the brief is a pointer, not a second state carrier. Reject a block mixing these fields with `Active task`, missing either loop field, or carrying task rows with a ledger. Never initialize a task tracker for a ledger.
40
+
32
41
  Consumer rules: process state absent -> plain handoff (the consumer starts its
33
- own process from `## Intent`); present with `No plan active.` -> phase-only, restore no
34
- plan; loading named skills is the consumer's job.
42
+ own process from `## Intent`). Give valid ledger fields precedence over `No plan active.` and run ledger admission. Restore phase-only state for `No plan active.` only with no `Ledger` field. Load named skills as the consumer.
35
43
 
36
44
  A brief is any text whose first line starts `# Handoff:` - a file on disk or pasted
37
45
  inline. `## Repo state` is matched by prefix: `## Repo state` or
@@ -44,7 +52,7 @@ cohort's `handoff` skill wrote, which ends at `## Skills loaded`. Layout, byte-e
44
52
  one blank line, the heading on its own line, a blank line, the `phase_tracker status`
45
53
  output verbatim, a blank line, the `plan_tracker status` output verbatim, a blank line,
46
54
  `Active task: <name|none>`, then the gate-history line - unfenced, nothing after.
47
- `Active task` is the first `→` task name, else `none` (also for `No plan active.`).
55
+ For a task plan, set `Active task` to the first `→` task name, else `none` (also for `No plan active.`). For a line-1 ledger, read its path and last decision and use the loop alternative above.
48
56
 
49
57
  Example appended block (the fence is documentation; the brief carries no fence):
50
58
 
@@ -130,11 +138,14 @@ announcement without re-resolving it.
130
138
  |---|---|
131
139
  | brainstorm | `start brainstorm`; `substep` if the brief shows one |
132
140
  | plan | `start brainstorm`; `skip brainstorm S`; `start plan`; `substep` if shown |
133
- | implement | `start brainstorm`; `skip brainstorm S`; `start plan`; `plan_check({ planPath })`; on FAIL run `## Plan repair`; on PASS `skip plan R`; `start implement`; `substep` if shown; `plan_tracker init` |
141
+ | implement with ledger | Read ledger line 1; resolve its path-only `**Spec:**` header; run entry checks 1-6, naming check 6 (Grant field); `start brainstorm`; `skip brainstorm` reason `resume: <spec path>`; `start plan`; `plan_check` on the absolute ledger path; on PASS `skip plan` reason `resume: <spec> (loop engine)` using the resolved absolute spec path as `<spec>`; `start implement`; no `plan_tracker init`; render rows and last decision; continue at loop step 5. Follow `reconstruction.md` "Spec with ledger" for this self-contained ordered restore. On FAIL stop with findings, never `## Plan repair` or writing-plans. |
142
+ | implement with task plan | `start brainstorm`; `skip brainstorm S`; `start plan`; `plan_check({ planPath })`; on FAIL run `## Plan repair`; on PASS `skip plan R`; `start implement`; `substep` if shown; `plan_tracker init` |
134
143
  | verify | as implement through `skip plan R`, then `skip implement R`; `start verify`; `substep` if shown; `plan_tracker init` |
135
144
  | ship | as verify. Restoration stops at verify in_progress: `complete verify` needs a fresh conformance dispatch and `skip verify` would bypass a real gate. Announce that the closure review re-runs before ship |
136
145
 
137
- `planPath` (implement onward): the brief carries none. Resolve as reconstruction does
146
+ For a ledger brief, pair by the ledger's path-only `**Spec:**` header, not basename; require the resolved spec under `flowGuards.specDirs` and the ledger under its sibling plans directory. Keep same-repository binding and human confirmation. For verify/ship ledger briefs, use the ledger restore sequence through `skip plan`, then `skip implement R`; `start verify`, with no task tracker. Continue verify/ship ledger briefs in `/skill:loop-implementation` `After the loop` from step 1 (full ledger verification), after the already-restored `start verify`, retaining whole-diff code review, happy path when triggered, fresh conformance review and finishing without task-tracker calls. Ship still stops at verify for fresh conformance.
147
+
148
+ `planPath` (task-plan implement onward): the brief carries none. Resolve as reconstruction does
138
149
  (`reconstruction.md`, "Candidates", including its `flowGuards.specDirs` resolution): the
139
150
  single plan added after base, paired with `<specDir>/<same basename>` whether that
140
151
  spec was added after base or tracked from base (`reconstruction.md` "Candidates");
@@ -214,7 +225,9 @@ Every restore ends with:
214
225
  Gate history not restored; re-validating `<task>` before any stage advance
215
226
  ```
216
227
 
217
- where `<task>` is the brief's `Active task`, else the first in-progress task, else `none`
228
+ For a ledger, use `ledger rows` as `<task>` and render the current rows and last decision from disk.
229
+
230
+ For a task plan, `<task>` is the brief's `Active task`, else the first in-progress task, else `none`
218
231
  (whole-deliverable re-validation). Gate history, closure-review evidence, and fix rounds
219
232
  are never inferred or restored; a ship-stage brief resumes at verify.
220
233
 
@@ -35,7 +35,7 @@ git -C <worktree> ls-files --others --exclude-standard -- <dirs>
35
35
  Candidates are files under those directories added after base, plus untracked files
36
36
  there. Paths returned by these commands are relative to `<worktree>`; resolve them to
37
37
  absolute paths under `<worktree>` before reading artifacts, showing paths in prompts,
38
- or calling `plan_check`. A candidate plan pairs with
38
+ or calling `plan_check`. Read every plan-directory candidate's first line. A line exactly `# Ledger - loop engine` pairs by its path-only `**Spec:**` header, not basename; resolve that spec under the worktree's configured spec dirs, including a spec tracked from base. Stop on a missing or foreign spec. A task-plan candidate pairs with
39
39
  `<worktree>/<specDir>/<same basename>` whether that spec is a candidate or tracked
40
40
  from base; count a base-tracked spec so paired as a spec in the route table below.
41
41
  The plan commit is the first
@@ -48,7 +48,8 @@ plan commit; treat every task as `pending`.
48
48
  | no spec | stop; offer `/skill:brainstorming` or pass the spec: `/skill:gauntlet-resume <specDir>/<name>.md`; no tracker call |
49
49
  | more than one spec | the human picks one, then continue below with that spec |
50
50
  | one spec, no plan | "Spec without plan" |
51
- | one spec with plan | "Spec with plan" |
51
+ | one spec with ledger | "Spec with ledger" (takes precedence over a task plan) |
52
+ | one spec with task plan only | "Spec with plan" |
52
53
 
53
54
  ## Spec without plan
54
55
 
@@ -56,14 +57,26 @@ Ask exactly one question: is this spec approved, or do you select `/skill:brains
56
57
  with this unapproved spec as the draft? Show the spec path (and the brief's
57
58
  `## Intent`/`## Decisions` when present).
58
59
 
59
- - Approved: run SKILL.md entry check 6 on `<spec path>` first (a malformed `**Amend-grant:**` line stops here, no tracker call); then `start brainstorm`; `skip brainstorm resume: <spec path>`; `start plan`;
60
- continue in writing-plans (its own `start plan` is skipped - plan is already
61
- in_progress).
60
+ - Approved: run SKILL.md entry check 6 on `<spec path>` first (a malformed `**Amend-grant:**` line stops here, no tracker call). Read the spec's `**Engine:**` line and take the matching route:
61
+ - `loop`: call `start brainstorm`, then `skip brainstorm` with reason `resume: <spec path>` to arm the flow before `plan_check`. Carry absolute worktree/spec paths and the resolved plan directory into `/skill:loop-implementation` Entry steps 1-5: resolve settings at step 1 and verification at step 2, retain the driver's `skip plan` at step 3, create the ledger at step 4, then check/commit it and start implement at step 5. Never enter the writing-plans body or initialize a task tracker.
62
+ - `waves` or absent: `start brainstorm`; `skip brainstorm resume: <spec path>`; `start plan`; continue in writing-plans, skipping its `start plan` call.
62
63
  - Selected unapproved-draft brainstorming handoff: invoke [`/skill:brainstorming`](../../brainstorming/SKILL.md) with
63
64
  the spec as the draft; arm nothing. using-git-worktrees Step 0 detects the existing
64
65
  worktree and creates none. A bare "not approved" answer is not consent; wait for
65
66
  explicit handoff selection.
66
67
 
68
+ ## Spec with ledger
69
+
70
+ Show the spec and ledger paths, current rows, iterations, last decision, and `git -C <worktree> status --porcelain`. Ask the human to confirm the approved spec and continuation; carry brief Intent/Decisions when present. Keep entry and same-repository checks.
71
+
72
+ After confirmation:
73
+
74
+ 1. Read ledger line 1: require `# Ledger - loop engine`. Resolve its path-only `**Spec:**` header to an absolute spec path under the worktree's configured spec directories, not by basename. Run SKILL.md entry checks 1-6 in order, including check 6, **Grant field**, on that spec, before any tracker mutation.
75
+ 2. Call `phase_tracker({ action: "start", phase: "brainstorm" })`, then `phase_tracker({ action: "skip", phase: "brainstorm", reason: "resume: <spec path>" })`, then `phase_tracker({ action: "start", phase: "plan" })`.
76
+ 3. Call `plan_check({ planPath: <absolute ledger path> })`. On FAIL, stop with findings, retaining plan in_progress; never enter `## Plan repair` or writing-plans. On PASS, call `phase_tracker({ action: "skip", phase: "plan", reason: "resume: <spec> (loop engine)" })`, then `phase_tracker({ action: "start", phase: "implement" })`. Use the resolved absolute spec path as `<spec>` in the reason. Never call `plan_tracker init`.
77
+ 4. Read `/skill:loop-implementation`; resolve its Entry settings/maxIterations without creating or overwriting the ledger. Restore base, verification, iterations, row attempts, funding and retry allowances from the ledger/spec and branch. Render rows and last decision. A dirty tree stops before any dispatch using the driver's dirty-tree stop note; preserve changes for the human.
78
+ 5. Continue at loop step 5, the delivered driver's `Before each dispatch`, with durable iterations and retry data. Never reset counters or reseed a validated seed.
79
+
67
80
  ## Spec with plan
68
81
 
69
82
  Show, and ask the human to confirm or edit both in one reply: