pi-gauntlet 8.1.2 → 8.1.4
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/README.md +4 -3
- package/extensions/lib/plan-check.test.ts +17 -1
- package/extensions/lib/plan-check.ts +3 -2
- package/extensions/lib/telemetry-collect.test.ts +12 -0
- package/extensions/phase-tracker.test.ts +3 -1
- package/extensions/plan-tracker.test.ts +273 -10
- package/extensions/plan-tracker.ts +80 -15
- package/package.json +1 -1
- package/skills/brainstorming/reference/amendment-surface.md +3 -3
- package/skills/brainstorming/reference/spec-finalization.md +14 -12
- package/skills/gauntlet-resume/SKILL.md +2 -2
- package/skills/gauntlet-resume/reference/brief-contract.md +4 -2
- package/skills/gauntlet-resume/reference/reconstruction.md +10 -6
- package/skills/loop-implementation/SKILL.md +30 -29
- package/skills/loop-implementation/reference/coordinated-legs.md +4 -4
|
@@ -12,11 +12,13 @@ import { StringEnum } from "@earendil-works/pi-ai";
|
|
|
12
12
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
13
13
|
import { Text } from "@earendil-works/pi-tui";
|
|
14
14
|
import { type Static, Type } from "@sinclair/typebox";
|
|
15
|
+
import { checkoutOfSync } from "./lib/checkout.ts";
|
|
16
|
+
import { planSpecHeader } from "./lib/telemetry-paths.ts";
|
|
15
17
|
|
|
16
18
|
const TASK_STATUSES = ["pending", "in_progress", "complete", "failed", "skipped"] as const;
|
|
17
19
|
type TaskStatus = (typeof TASK_STATUSES)[number];
|
|
18
20
|
|
|
19
|
-
interface Task { name: string; status: TaskStatus; }
|
|
21
|
+
interface Task { name: string; status: TaskStatus; label?: string; }
|
|
20
22
|
interface PlanTrackerDetails { action: "init" | "add" | "update" | "status" | "clear" | "refresh"; tasks: Task[]; error?: string; }
|
|
21
23
|
|
|
22
24
|
const PlanTrackerParams = Type.Object({
|
|
@@ -43,6 +45,42 @@ export function validateSnapshot(tasks: Task[]): number[] {
|
|
|
43
45
|
return offenders;
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
function criterionDescriptions(spec: string): string[] {
|
|
49
|
+
const descriptions: string[] = [];
|
|
50
|
+
let inAcceptance = false;
|
|
51
|
+
let disposition = false;
|
|
52
|
+
let fence: string | undefined;
|
|
53
|
+
for (const line of spec.split("\n")) {
|
|
54
|
+
const delimiter = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
|
|
55
|
+
if (delimiter && !fence) { fence = delimiter; continue; }
|
|
56
|
+
if (fence) {
|
|
57
|
+
if (delimiter?.[0] === fence[0] && delimiter.length >= fence.length) fence = undefined;
|
|
58
|
+
else if (inAcceptance && descriptions.length && !disposition) descriptions[descriptions.length - 1] += ` ${line}`;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (/^#{1,2}\s/.test(line)) {
|
|
62
|
+
inAcceptance = /^## Acceptance criteria\s*$/i.test(line);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!inAcceptance) continue;
|
|
66
|
+
const criterion = /^- \[[ xX]\](?:\s+(.*))?$/.exec(line);
|
|
67
|
+
if (criterion) {
|
|
68
|
+
descriptions.push(criterion[1] ?? "");
|
|
69
|
+
disposition = false;
|
|
70
|
+
} else if (descriptions.length) {
|
|
71
|
+
if (/^\s*(?:in-scope\s*$|(?:deviates|deferred|elsewhere|venue):)/.test(line)) disposition = true;
|
|
72
|
+
if (!disposition) descriptions[descriptions.length - 1] += ` ${line}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return descriptions.map(text => text.replace(/\s+/g, " ").trim());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function compactDescription(description: string): string {
|
|
79
|
+
if (description.length <= 80) return description;
|
|
80
|
+
const boundary = description.lastIndexOf(" ", 77);
|
|
81
|
+
return `${description.slice(0, Math.max(0, boundary))}...`;
|
|
82
|
+
}
|
|
83
|
+
|
|
46
84
|
function ledgerTasks(text: string, path: string): Task[] {
|
|
47
85
|
const lines = text.split("\n");
|
|
48
86
|
if (lines[0] !== "# Ledger - loop engine") {
|
|
@@ -52,11 +90,13 @@ function ledgerTasks(text: string, path: string): Task[] {
|
|
|
52
90
|
const header = lines.findIndex(line => /^\|\s*row\s*\|\s*status\s*\|\s*attempts\s*\|\s*evidence\s*\|$/.test(line));
|
|
53
91
|
if (header < 0) malformed(0);
|
|
54
92
|
if (!/^\|\s*-+\s*\|\s*-+\s*\|\s*-+\s*\|\s*-+\s*\|$/.test(lines[header + 1] ?? "")) malformed(header + 1);
|
|
55
|
-
const
|
|
93
|
+
const allowanceMatch = lines.slice(1, header).map(line => /^allowance:\s*([1-9]\d*)\s*$/.exec(line)).find(Boolean);
|
|
94
|
+
const allowance = allowanceMatch ? Number(allowanceMatch[1]) : undefined;
|
|
95
|
+
const rows: { row: number; status: string; attempts: number }[] = [];
|
|
56
96
|
for (let i = header + 2; i < lines.length && lines[i].trim() && !/^#{1,2}\s/.test(lines[i]); i++) {
|
|
57
97
|
const cells = lines[i].split(/(?<!\\)\|/).slice(1, -1).map(cell => cell.trim());
|
|
58
|
-
if (!lines[i].startsWith("|") || !lines[i].endsWith("|") || cells.length !== 4 || !/^\d+$/.test(cells[0]) || !/^(open|done|failed|cut)$/.test(cells[1])) malformed(i);
|
|
59
|
-
rows.push({ row: Number(cells[0]), status: cells[1] });
|
|
98
|
+
if (!lines[i].startsWith("|") || !lines[i].endsWith("|") || cells.length !== 4 || !/^\d+$/.test(cells[0]) || !/^(open|done|failed|cut)$/.test(cells[1]) || !/^\d+$/.test(cells[2])) malformed(i);
|
|
99
|
+
rows.push({ row: Number(cells[0]), status: cells[1], attempts: Number(cells[2]) });
|
|
60
100
|
}
|
|
61
101
|
if (!rows.length) malformed(header + 2);
|
|
62
102
|
const byRow = new Map(rows.map(row => [row.row, row]));
|
|
@@ -66,7 +106,8 @@ function ledgerTasks(text: string, path: string): Task[] {
|
|
|
66
106
|
throw new Error(`refresh: ${path} pending: names row ${pending} which is absent`);
|
|
67
107
|
}
|
|
68
108
|
const dispatched = new Set<number>();
|
|
69
|
-
|
|
109
|
+
const retries = new Map<number, number>();
|
|
110
|
+
let mapping: { row: number; ac: number; repo: string }[] | undefined;
|
|
70
111
|
let section = "";
|
|
71
112
|
for (const line of lines) {
|
|
72
113
|
if (/^#{1,2}\s/.test(line)) {
|
|
@@ -77,12 +118,17 @@ function ledgerTasks(text: string, path: string): Task[] {
|
|
|
77
118
|
if (section === "## Decisions") {
|
|
78
119
|
const decision = /^row (\d+) done \| .+ \| .+$/.exec(line);
|
|
79
120
|
if (decision) dispatched.add(Number(decision[1]));
|
|
121
|
+
const retry = /^row (\d+) retry \| .* \| allowed attempts \d+$/.exec(line);
|
|
122
|
+
if (retry) {
|
|
123
|
+
const row = Number(retry[1]);
|
|
124
|
+
retries.set(row, (retries.get(row) ?? 0) + 1);
|
|
125
|
+
}
|
|
80
126
|
} else if (section === "## Leg mapping") {
|
|
81
|
-
const entry = /^\|\s*(\d+)\s*\|\s
|
|
127
|
+
const entry = /^\|\s*(\d+)\s*\|\s*(\d+)\s*\|\s*(\S+)\s*\|$/.exec(line);
|
|
82
128
|
if (!entry) continue;
|
|
83
129
|
const row = Number(entry[1]);
|
|
84
130
|
if (!byRow.has(row)) throw new Error(`refresh: ${path} leg mapping names row ${row} which is absent`);
|
|
85
|
-
mapping!.push({ row,
|
|
131
|
+
mapping!.push({ row, ac: Number(entry[2]), repo: entry[3] });
|
|
86
132
|
}
|
|
87
133
|
}
|
|
88
134
|
const status = (row: number): TaskStatus => {
|
|
@@ -92,11 +138,28 @@ function ledgerTasks(text: string, path: string): Task[] {
|
|
|
92
138
|
if (row === pending) return "in_progress";
|
|
93
139
|
return value === "failed" ? "failed" : "pending";
|
|
94
140
|
};
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
141
|
+
const specRef = planSpecHeader(lines.slice(0, header).join("\n"));
|
|
142
|
+
if (!specRef) throw new Error(`refresh: ${path} missing Spec reference`);
|
|
143
|
+
const checkout = checkoutOfSync(path);
|
|
144
|
+
if (!checkout) throw new Error(`refresh: ${path} cannot resolve repository root for Spec reference ${specRef}`);
|
|
145
|
+
const specPath = resolve(checkout.toplevel, specRef);
|
|
146
|
+
let spec: string;
|
|
147
|
+
try {
|
|
148
|
+
spec = readFileSync(specPath, "utf8");
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new Error(`refresh: ${path} cannot read spec ${specPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
151
|
+
}
|
|
152
|
+
const descriptions = criterionDescriptions(spec);
|
|
153
|
+
const entries = mapping ?? rows.map(({ row }) => ({ row, ac: row, repo: undefined }));
|
|
154
|
+
return entries.sort((a, b) => a.row - b.row).map(({ row, ac, repo }) => {
|
|
155
|
+
const description = descriptions[ac - 1];
|
|
156
|
+
if (!description) throw new Error(`refresh: ${path} row ${row}: spec ${specPath} AC ${ac} ${description === undefined ? "is missing" : "has an empty description"}`);
|
|
157
|
+
const value = byRow.get(row)!;
|
|
158
|
+
const owner = repo === undefined ? "" : ` (${repo})`;
|
|
159
|
+
const budget = allowance !== undefined && value.status !== "cut" && (value.status !== "done" || value.attempts > 0)
|
|
160
|
+
? `${value.attempts}/${allowance + (retries.get(row) ?? 0)}: ` : "";
|
|
161
|
+
return { name: `row ${row}${owner}`, status: status(row), label: `${budget}${compactDescription(description)}${owner}` };
|
|
162
|
+
});
|
|
100
163
|
}
|
|
101
164
|
|
|
102
165
|
const RULE_LINE = "Rule: pending tasks must trail every started or finished task.";
|
|
@@ -104,6 +167,8 @@ const snapshotLine = (tasks: Task[]): string => tasks.map((t, i) => `${i} ${t.na
|
|
|
104
167
|
const offenderList = (tasks: Task[], indices: number[]): string => indices.map((i) => `${i} "${tasks[i].name}"`).join(", ");
|
|
105
168
|
const normalizeTask = (t: string | { name: string; status: TaskStatus }): Task => typeof t === "string" ? { name: t, status: "pending" } : { name: t.name, status: t.status };
|
|
106
169
|
|
|
170
|
+
const statusName = (task: Task): string => task.label ? `${task.name} | ${task.label}` : task.name;
|
|
171
|
+
|
|
107
172
|
const glyph = (status: TaskStatus, theme?: Theme): string => {
|
|
108
173
|
const [color, icon]: [string, string] = status === "complete" ? ["success", "✓"] : status === "in_progress" ? ["warning", "→"] : status === "failed" ? ["error", "✗"] : status === "skipped" ? ["dim", "⊘"] : ["dim", "○"];
|
|
109
174
|
return theme ? theme.fg(color as Parameters<Theme["fg"]>[0], icon) : icon;
|
|
@@ -121,7 +186,7 @@ function formatWidget(tasks: Task[], theme: Theme): string {
|
|
|
121
186
|
const { done } = counts(tasks);
|
|
122
187
|
const icons = tasks.map((t) => glyph(t.status, theme)).join("");
|
|
123
188
|
const current = tasks.find((t) => t.status === "in_progress") ?? tasks.find((t) => t.status === "pending");
|
|
124
|
-
const currentName = current ? ` ${current.name}` : "";
|
|
189
|
+
const currentName = current ? ` ${current.label ?? current.name}` : "";
|
|
125
190
|
return `${theme.fg("muted", "Tasks:")} ${icons} ${theme.fg("muted", `(${done}/${tasks.length})`)}${currentName}`;
|
|
126
191
|
}
|
|
127
192
|
|
|
@@ -131,7 +196,7 @@ function formatStatus(tasks: Task[]): string {
|
|
|
131
196
|
const groups = [[c.inProgress, "in progress"], [c.pending, "pending"], [c.failed, "failed"], [c.skipped, "skipped"]] as const;
|
|
132
197
|
const detail = groups.filter(([n]) => n > 0).map(([n, label]) => `${n} ${label}`);
|
|
133
198
|
const lines: string[] = [`Plan: ${c.done}/${tasks.length} done${detail.length > 0 ? ` (${detail.join(", ")})` : ""}`, ""];
|
|
134
|
-
for (let i = 0; i < tasks.length; i++) lines.push(` ${glyph(tasks[i].status)} [${i}] ${tasks[i]
|
|
199
|
+
for (let i = 0; i < tasks.length; i++) lines.push(` ${glyph(tasks[i].status)} [${i}] ${statusName(tasks[i])}`);
|
|
135
200
|
return lines.join("\n");
|
|
136
201
|
}
|
|
137
202
|
|
|
@@ -440,7 +505,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
440
505
|
let text = theme.fg("muted", `${c.done}/${taskList.length} done${c.suffix}`);
|
|
441
506
|
for (const t of taskList) {
|
|
442
507
|
const icon = glyph(t.status, theme);
|
|
443
|
-
text += `\n${icon} ${theme.fg("muted", t
|
|
508
|
+
text += `\n${icon} ${theme.fg("muted", statusName(t))}`;
|
|
444
509
|
}
|
|
445
510
|
return new Text(text, 0, 0);
|
|
446
511
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Amendment surface
|
|
2
2
|
|
|
3
|
-
`read` this file (the `read` tool at its absolute path, never a shell command) from `skills/brainstorming/SKILL.md` section 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. Classify a text correction to an approved drafted acceptance-criteria row backed by implementer evidence (a quoted test, assertion, or code line in a `BLOCKED`/`NEEDS_CONTEXT` return) as an amend item. Keep ticket rows, row add/remove, reviewer-only or taste objections, problem statement changes, and component boundary changes as redraws at batch preparation, before any standing grant: re-draft from the new design and ratify at the full gate. Keep a disposition-only change to such a row in this grammar so it keeps the cut question. Apply an item the human has answered by letter or custom text as recorded. Keep human-facing amendment text with the main loop holding `edit`/`write`; delegate implement-phase spec authorship only through the implement-only route below. Keep all other callers' authorship with the main loop. Use the human last: a fresh reviewer clears evidence-backed factual corrections; present the rest to the human once per batch plainly.
|
|
3
|
+
`read` this file (the `read` tool at its absolute path, never a shell command) from `skills/brainstorming/SKILL.md` section 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; stop for human authorization before redraw resets or deletions. Classify a text correction to an approved drafted acceptance-criteria row backed by implementer evidence (a quoted test, assertion, or code line in a `BLOCKED`/`NEEDS_CONTEXT` return) as an amend item. Keep ticket rows, row add/remove, reviewer-only or taste objections, problem statement changes, and component boundary changes as redraws at batch preparation, before any standing grant: re-draft from the new design and ratify at the full gate. Keep a disposition-only change to such a row in this grammar so it keeps the cut question. Apply an item the human has answered by letter or custom text as recorded. Keep human-facing amendment text with the main loop holding `edit`/`write`; delegate implement-phase spec authorship only through the implement-only route below. Keep all other callers' authorship with the main loop. 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
|
|
|
@@ -19,7 +19,7 @@ A grant is the spec's `**Amend-grant:**` line read through the shared predicate;
|
|
|
19
19
|
| exactly one line whose trimmed value is any other non-empty single-line text | grant active; the sentence is that value |
|
|
20
20
|
| two or more lines, one line with an empty value, or a value continued onto the next physical line (any non-blank line directly below the field that is neither a `**Key:**` header line nor a `> **Superseded by:**` banner line) | malformed -> stop, print the offending line(s) |
|
|
21
21
|
|
|
22
|
-
**Value decoding**. Run the structural predicate first; missing or `none` gives neither capability, and malformed lines stop with the offending lines quoted. Decode every active value: section 1 at each batch boundary, SDD Fix-Loop Rounds at each funded dispatch, and `spec-finalization.md` before writing the value. Match exactly one `fund N fix rounds` or `N more fix rounds` anywhere on the line, with N matching `[1-9][0-9]*`; no match funds nothing. Stop and quote the line before any item applies or any round is funded on two or more matches or a token-shaped invalid phrase (`fund 0 fix rounds`, `fund three fix rounds`, `-2 more fix rounds`). Remove the matched token and its `; ` / `, ` separators for amend decoding: a non-empty remainder keeps wording-agnostic amend permission (`auto-apply amends`, `apply spec fixes without asking`, or equivalent); an empty remainder is budget-only, with no amend permission. Keep grants without a budget unchanged. Decode `auto-apply amends; fund 3 fix rounds` as both capabilities and `2 more fix rounds` as budget-only. The budget is non-retroactive: rounds spent before the line was written do not count against or toward it. Write `none` on revocation such as `revoke auto-apply amends`, clearing both capabilities; retain funding alone by writing the token alone.
|
|
22
|
+
**Value decoding**. Run the structural predicate first; missing or `none` gives neither capability, and malformed lines stop with the offending lines quoted. Decode every active value: section 1 at each batch boundary, SDD Fix-Loop Rounds at each funded dispatch, and `spec-finalization.md` before writing the value. Match exactly one `fund N fix rounds` or `N more fix rounds` anywhere on the line, with N matching `[1-9][0-9]*`; no match funds nothing. Stop and quote the line before any item applies or any round is funded on two or more matches or a token-shaped invalid phrase (`fund 0 fix rounds`, `fund three fix rounds`, `-2 more fix rounds`). Remove the matched token and its `; ` / `, ` separators for amend decoding: a non-empty remainder keeps wording-agnostic amend permission (`auto-apply amends`, `apply spec fixes without asking`, or equivalent); an empty remainder is budget-only, with no amend permission. Keep grants without a budget unchanged. Decode `auto-apply amends; fund 3 fix rounds` as both capabilities and `2 more fix rounds` as budget-only. Apply the budget to the waves engine only; loop ignores the budget token and keeps only the decoded amendment permission (none for a budget-only value). The waves budget is non-retroactive: rounds spent before the line was written do not count against or toward it. Write `none` on revocation such as `revoke auto-apply amends`, clearing both capabilities; retain funding alone by writing the token alone.
|
|
23
23
|
|
|
24
24
|
**Writers.** Spec-writing and the spec gate are owned by `spec-finalization.md`; this file owns the mid-flow changes:
|
|
25
25
|
|
|
@@ -169,7 +169,7 @@ reread rule; amend the block before using a changed contract. Legacy specs
|
|
|
169
169
|
without that section retain finish-time rereading. A one-member run has no
|
|
170
170
|
companion artifact or companion aftermath.
|
|
171
171
|
|
|
172
|
-
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` and `plan_check`. 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 and
|
|
172
|
+
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` and `plan_check`. 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 and allowance. Apply evidenced drafted-row text corrections as amend items while preserving row identity; retain the cut/reopen rules above. Ticket text and row add/remove changes remain redraws. Commit spec + ledger together with subject `loop: cut row <n>` for a batch containing only one cut, otherwise `loop: amend` (including mixed batches), carrying the amendment audit body below (including each `granted` record and granting sentence); after the commit, use the driver's next ledger-path refresh to project the new statuses and continue without task-plan aftermath. Keep task-plan `add`/`update`/`init` out of this route. Project `done -> cut` as `complete -> skipped` and a re-bound `done -> open` as `complete -> pending`; count neither as a reopen. During verify, stop on a re-bound row with `Stopped: row <n> reopened by amendment during verify`; keep verify active and offer authorization to return that row to ledger implementation or stop. On authorization, invoke `loop-implementer` through the driver's `## Dispatch` and `## Validate the return` (tuple, refresh, admission, refresh), with no phase reset; close the row on disk, then rerun the step 1 gate before verify continues. Never close it with `plan_tracker update`. 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).
|
|
173
173
|
|
|
174
174
|
At that return point, classify human-facing outcomes with this table; let writing-plans' first `Findings:` repair run as written.
|
|
175
175
|
|
|
@@ -137,6 +137,8 @@ If the council path ran, the body is its audit (`Coverage:` when present, then `
|
|
|
137
137
|
|
|
138
138
|
Evaluate the summary in two stages (the **Degrade path** referenced in each is defined just below):
|
|
139
139
|
|
|
140
|
+
Before the summary `Read` below, resolve `gauntlet_setting({ key: "loopEngine" })` once for this gate. Retain `maxIterationsPerRow` (default 4) as `<n>` for both loop options. Render any settings warning as one adjacent line. Stop on an unavailable settings tool; never guess the allowance.
|
|
141
|
+
|
|
140
142
|
1. **From the dispatch tool result, before the `Read`.** If the result is **not** an `"Output saved to: <path> (<N> KB, <M> lines)"` reference (an exit-0 save error returns the save error plus a `Full reply preserved at` / `not preserved` pointer, no inline output), or the reference reports under ~500 bytes, or over ~45 KB (the `Read` truncates at 50KB / 2000 lines, so a larger file cannot render whole) - skip the `Read` and take the degrade path. Use the reference's reported figures; do not re-derive them.
|
|
141
143
|
2. **The `Read` itself, as the last content-producing tool call before composing the gate.** `Read` `$SUMMARY_PATH` and paste its contents verbatim immediately after the gate marker. If the `Read` fails, returns 0 bytes, or reports truncation - take the degrade path. The `Read` must be last: pi-condense does not protect a `/tmp` read, so any turn boundary between the `Read` and the render lets the read result be pruned, reproducing the bug.
|
|
142
144
|
|
|
@@ -167,10 +169,10 @@ New predecessor candidates at spec-writing: <path> (<title>), ... - index rows a
|
|
|
167
169
|
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.
|
|
168
170
|
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.
|
|
169
171
|
3 - show council dispositions: prints the council audit verbatim, then re-presents this gate. Not an approval.
|
|
170
|
-
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.
|
|
171
|
-
5 - approve, loop engine, auto-apply amends: as 4 with option 2's amendment grant.
|
|
172
|
+
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; allowance <n> per row.
|
|
173
|
+
5 - approve, loop engine, auto-apply amends: as 4 with option 2's amendment grant, allowance <n> per row.
|
|
172
174
|
6 - approve, hand off as draft PR: approve the spec's content only; push this branch and open a draft PR carrying just the spec; engine and amendment grant are chosen later by whoever runs `brainstorming #N` on that PR. Ends this flow without planning.
|
|
173
|
-
Add "; fund N fix rounds" (or "N more fix rounds") to 1
|
|
175
|
+
Add "; fund N fix rounds" (or "N more fix rounds") to 1 or 2 to fund N implement fix rounds past the escalated waves round. Add "; allowance N" to 4 or 5 to set this run's per-row allowance.
|
|
174
176
|
|
|
175
177
|
Or tell me what to change in the spec, or say "revert applied council edit <X>" to undo a specific applied edit.
|
|
176
178
|
```
|
|
@@ -185,21 +187,21 @@ If you believe the summary needs correcting, do **not** silently rewrite it - re
|
|
|
185
187
|
|
|
186
188
|
**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.
|
|
187
189
|
|
|
188
|
-
Wait for the user. Interpret
|
|
190
|
+
Wait for the user. Interpret budget replies with this table before routing change requests. For 4/5, accept one optional `; allowance N` or `, allowance N` suffix with N matching `[1-9][0-9]*` and a positive safe integer. Carry the accepted value to loop entry and persist it only in the ledger, not in the spec or settings. On a malformed allowance suffix (zero, negative, non-numeric, unsafe, duplicate or trailing text), re-present the gate with exactly one clarifying question quoting it, then wait without writing; a second invalid reply leaves the gate waiting.
|
|
189
191
|
|
|
190
192
|
| Reply | Action / written value |
|
|
191
193
|
|---|---|
|
|
192
194
|
| bare `2` | approve; persist the option-2 description above |
|
|
193
195
|
| `2; fund 3 fix rounds` or `2, fund 3 fix rounds` | approve; persist that description plus `; fund 3 fix rounds`, not a change request |
|
|
194
196
|
| `1` with a budget token | approve; persist `fund 3 fix rounds` alone for that token (budget-only) |
|
|
195
|
-
| bare `5` | approve; persist the option-2 description above; engine loop |
|
|
196
|
-
| `
|
|
197
|
-
| `4` with a
|
|
198
|
-
| approval prose with a token | approve; preserve the prose collapsed to one physical line |
|
|
199
|
-
| budget token alone (`fund 3 fix rounds`) | re-present the gate asking for
|
|
200
|
-
| token with malformed N | re-present the gate quoting the token; write nothing, never imply a change request |
|
|
197
|
+
| bare `4` or `5` | approve; persist `none` for 4 or the option-2 description above for 5; engine loop; use the settings allowance |
|
|
198
|
+
| `4; allowance 6` / `4, allowance 6` or `5; allowance 6` / `5, allowance 6` | approve as the corresponding bare option; carry `run allowance: 6` to ledger seeding |
|
|
199
|
+
| `4` or `5` with a funding token | Funding is waves-only: re-present the gate quoting the token; write nothing and wait for a valid approval |
|
|
200
|
+
| waves approval prose with a funding token | approve; preserve the prose collapsed to one physical line |
|
|
201
|
+
| budget token alone (`fund 3 fix rounds` or `allowance 6`) | re-present the gate asking for an approval option; write nothing |
|
|
202
|
+
| waves funding token with malformed N, or allowance suffix on an option other than 4/5 | re-present the gate quoting the token; write nothing, never imply a change request |
|
|
201
203
|
|
|
202
|
-
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. In persistence narration, quote the exact resulting `**Amend-grant:**` value in full, including any funding suffix; never substitute an option name or "option-2 description plus suffix" for the persisted text.
|
|
204
|
+
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 waves budget-only approvals. Strip only an accepted loop allowance suffix before decoding the grant; keep the run allowance separate from `**Amend-grant:**`. In persistence narration, quote the exact resulting `**Amend-grant:**` value in full, including any funding suffix; never substitute an option name or "option-2 description plus suffix" for the persisted text.
|
|
203
205
|
|
|
204
206
|
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 implementation approval (1/2/4/5), `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.
|
|
205
207
|
|
|
@@ -211,4 +213,4 @@ After implementation approval (1/2/4/5), mark the brainstorm phase complete:
|
|
|
211
213
|
phase_tracker({ action: "complete", phase: "brainstorm" })
|
|
212
214
|
```
|
|
213
215
|
|
|
214
|
-
Then invoke `/skill:writing-plans` on 1/2 or `/skill:loop-implementation` on 4/5 immediately, with no further prompt.
|
|
216
|
+
Then invoke `/skill:writing-plans` on 1/2 or `/skill:loop-implementation` on 4/5 immediately, with no further prompt. For an accepted allowance suffix on 4/5, print exactly one line `run allowance: <n>` immediately before invoking `/skill:loop-implementation`; omit that line on a bare approval. Keep this carrier in the approving turn only, never in the spec or settings.
|
|
@@ -147,9 +147,9 @@ owning skill **without** its reset-bearing entry:
|
|
|
147
147
|
| 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) |
|
|
148
148
|
| 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": label Entry steps 1-5 explicitly in the response as Entry step 1: resolved settings; Entry step 2: verification; Entry step 3: driver's plan skip; Entry step 4: clean named-branch check and ledger creation; Entry step 5: `plan_check`, commit and implement start; no writing-plans body or task tracker initialization; refresh by ledger path after implement starts |
|
|
149
149
|
| plan with task engine | writing-plans body | skip its `start plan` call (already in_progress) |
|
|
150
|
-
| 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, pending recovery, then one ledger-path refresh, no `init`; state the entire ordered sequence explicitly even when entry checks are already reported as passed; then continue at step 5 (`Before each dispatch`), applying the shared
|
|
150
|
+
| 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, pending recovery against the unmigrated ledger, clean-tree migration, final allowance validation, then one ledger-path refresh, no `init`; state the entire ordered sequence explicitly even when entry checks are already reported as passed; then continue at step 5 (`Before each dispatch`), applying the shared allowance boundary in reconstruction step 4 before continuation and preserving iterations/attempts/retry decisions; defer migration through recovery stops; reuse the existing ledger |
|
|
151
151
|
| 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 |
|
|
152
|
-
| verify with `Ledger:` (including ship-stage ledger briefs restored at verify) | /skill:loop-implementation | after `start verify`, restore the saved mixed task list with `init` as `reference/brief-contract.md` specifies, then pending recovery and one ledger-path refresh through the shared
|
|
152
|
+
| verify with `Ledger:` (including ship-stage ledger briefs restored at verify) | /skill:loop-implementation | after `start verify`, restore the saved mixed task list with `init` as `reference/brief-contract.md` specifies, then pending recovery against the unmigrated ledger, clean-tree migration, final allowance validation and one ledger-path refresh through the shared allowance boundary in `reference/reconstruction.md` "Spec with ledger" step 4; legacy briefs without tasks omit init, not recovery/migration; render allowance and observed task/ledger state; read `After the loop` and its references; continue from `After the loop` step 1, the gate set per the loop skill's `## Gate set`; let that skill own execution, with no resume-owned gate run; retain whole-diff code review, happy path when triggered, fresh home conformance review and finishing; never start implement or synthesize Gn completion |
|
|
153
153
|
| verify without `Ledger:` | verification-before-completion | full conformance gate; nothing carried over |
|
|
154
154
|
|
|
155
155
|
## Red flags — STOP
|
|
@@ -136,6 +136,8 @@ Plan: 2/5 done (1 in progress, 2 pending)
|
|
|
136
136
|
or `No plan active.`. Task glyphs `✓` complete, `→` in_progress, `✗` failed, `⊘` skipped,
|
|
137
137
|
`○` pending, mapping 1:1 onto `plan_tracker init` `{name, status}` statuses.
|
|
138
138
|
|
|
139
|
+
For ledger task rows, match the suffix pattern `^(row \d+(?: \(\S+\))?) \| .+$` against the text after `[i] ` and restore only capture 1. Treat everything after the first canonical name's ` | ` as display text, including pipes and row-like text; never derive identity from the description. For example, `row 3 | 2/5: Verify loop behavior and recovery` restores `row 3`. Retain legacy plain names and the numeric prefix form: remove the prefix pattern `^\d+/\d+: ` only when the remainder matches the canonical pattern `^row \d+(?: \(\S+\))?$`. Strip recognized labels before verify/ship `plan_tracker init` and snapshot name comparisons. Keep Gn and every other task name byte-identical, including numeric prefixes on non-ledger names. Never restore labels from a brief: the post-restore refresh recomputes descriptions and allowance from the current spec and ledger, so a changed description or denominator is not drift or a second task.
|
|
140
|
+
|
|
139
141
|
Parse stops (print the offending line, make no tracker call):
|
|
140
142
|
|
|
141
143
|
- a task line not matching `^\s*[✓→✗⊘○] \[\d+\] .+$` - the "unparsable process-state
|
|
@@ -192,14 +194,14 @@ Honor SKILL.md entry check 6's deferred stop before any process-state arming cal
|
|
|
192
194
|
|---|---|
|
|
193
195
|
| brainstorm | `start brainstorm`; `substep` if the brief shows one |
|
|
194
196
|
| plan | `start brainstorm`; `skip brainstorm S`; `start plan`; `substep` if shown |
|
|
195
|
-
| 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`; pending recovery, then one ledger-path refresh, no `init`; apply the shared
|
|
197
|
+
| 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`; pending recovery against the unmigrated ledger, clean-tree migration, final allowance validation, then one ledger-path refresh, no `init`; apply the shared allowance boundary in `reconstruction.md` "Spec with ledger" step 4 before continuation; render allowance, rows and decisions; 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. |
|
|
196
198
|
| 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` |
|
|
197
199
|
| verify | `start brainstorm`; `skip brainstorm S`; `start plan`; `plan_check({ planPath })`; on FAIL run `## Plan repair`; on PASS `skip plan R`, then `skip implement R`; `start verify`; `substep` if shown; `plan_tracker init`. Never call `start implement`. |
|
|
198
200
|
| ship | With a correctly stripped coordinated plan, apply "Coordinated admission"'s fully receipt-validated retry route instead of plan repair; otherwise 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 |
|
|
199
201
|
|
|
200
202
|
Apply reconstruction's pending recovery before dispatch or verification on every ledger restore, including verify/ship. Use the home return contract for a home tuple and the companion protocol only for an actual multi-member companion; record a validated interrupted return before continuing.
|
|
201
203
|
|
|
202
|
-
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 every ledger brief apply the shared
|
|
204
|
+
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 every ledger brief apply the shared allowance boundary in `reconstruction.md` "Spec with ledger" step 4 before dispatch or verification: recover against the unmigrated ledger, migrate only after clean admission, validate allowance, then refresh and continue the original phase. Defer migration through retained recovery stops. For verify/ship ledger briefs, use the ledger restore sequence through `skip plan`, then `skip implement R`; `start verify`; restore the full saved task list with `plan_tracker init` (canonical names restored per the task-line grammar above - the ` | ` description suffix or the legacy numeric prefix stripped; order and mixed statuses unchanged), then apply pending recovery, eligible migration/final allowance validation and one ledger-path refresh through reconstruction step 4. A legacy ledger brief without task rows omits init but keeps the same ordered recovery/migration boundary. Never call `start implement` on this route or invent completed Gn tasks; refresh replaces only ledger entries and preserves every non-ledger name/status. Never send a ledger task list through task-plan repair, even on a failed ledger check. Continue verify/ship ledger briefs in `/skill:loop-implementation` `After the loop` step 1 (the gate set per the loop skill's `## Gate set`), after the already-restored `start verify`; let that skill own execution, with no resume-owned gate run, retaining whole-diff code review, happy path when triggered, fresh conformance review and finishing. Ship still stops at verify for fresh conformance.
|
|
203
205
|
|
|
204
206
|
`planPath` (task-plan implement onward): the brief carries none. Resolve as reconstruction does
|
|
205
207
|
(`reconstruction.md`, "Candidates", including its `flowGuards.specDirs` resolution): the
|
|
@@ -92,10 +92,14 @@ After confirmation:
|
|
|
92
92
|
|
|
93
93
|
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. Run Pending recovery's read-only admission-evidence check below.
|
|
94
94
|
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" })`.
|
|
95
|
-
3. Call `plan_check({ planPath: <absolute ledger path> })
|
|
96
|
-
4. **Shared
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
3. Admit exactly one positive safe-integer `cap:` or `allowance:` header; both or neither is `ledger header malformed: <observed header lines>`, requiring human repair by hand. Call `plan_check({ planPath: <absolute ledger path> })` with legacy-compatible admission. On FAIL, stop with findings, retaining plan in_progress. Start the message with `gauntlet: stuck` followed by the findings reason on that line; 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. Restore implement through refresh, not `init`.
|
|
96
|
+
4. **Shared allowance boundary (every ledger restore, before phase continuation).** Read `/skill:loop-implementation`. Skip Entry step 1 entirely; restore base, verification, iterations, row attempts and decisions from the ledger without reseeding. Apply the following order on bare-worktree and brief routes alike:
|
|
97
|
+
1. Apply pending recovery below against the unmigrated ledger, including home returns whose child commit removed the tuple. Validate the original one-commit `<pre-dispatch HEAD>..HEAD` range against its unchanged before-image. For companions, retain unchanged home HEAD and admit the expected iterations-bump-plus-tuple dirt. On unrecoverable attribution or before-image, stop before refresh. Resolve ambiguous-history admission before tracker calls as required below. Retain every discard/reset and interrupted-dispatch gate; migration changes neither admission ranges nor the home HEAD used for recovery.
|
|
98
|
+
2. Migrate only an admitted, clean, tuple-free ledger with no retained recovery stop. With `cap:` and no `allowance:`, resolve `gauntlet_setting({ key: "loopEngine" })` for this migration only and replace the cap line with `allowance: <maxIterationsPerRow>`. Commit only that header replacement as parent `loop: migrate allowance`; preserve iterations, every attempts cell and all decisions. On an unavailable tool, stop before any write with `cannot migrate allowance: settings tool unavailable (loopEngine.maxIterationsPerRow)`. With an existing allowance, use it without settings resolution or a migration commit. Defer migration through any retained recovery stop or dirty tree; after authorized recovery reaches a clean tuple-free tree, migrate before the next dispatch or verification.
|
|
99
|
+
3. Require `allowance:` and no `cap:` before continuation, including for all-closed ledgers; otherwise stop with `ledger has no allowance: line - resume through /skill:gauntlet-resume`. For a retained recovery stop or unresolved dirty tree, defer this final validation with migration and project the unmigrated ledger solely to report the stop.
|
|
100
|
+
4. Call `plan_tracker({ action: "refresh", path: <absolute ledger path> })` exactly once after phase restoration, recovery and eligible migration/final validation, before any unresolved dirty-tree stop or dispatch. On verify/ship, restore the saved mixed task list first as `brief-contract.md` specifies; on implement, use no `init`. On refresh failure, stop with its findings and leave the existing task list untouched. Read `plan_tracker status` and report its exact observed state, never an inferred list.
|
|
101
|
+
Render the allowance header, rows, iterations, attempts, effective allowances and all decisions; distinguish the latest operational `row <n> ...` decision from the last physical history line. Compute each effective allowance as the header plus one per matching retry decision; ignore the retry's audit ceiling. Keep `cap +N` history as inert text and restore no funded rounds. Report any retained recovery stop now with no redispatch, using the driver's unresolved-row counts and discard-authorization menu for dirty trees. Resume the original phase only after this boundary: implement at `Before each dispatch`; verify/ship at `After the loop` step 1.
|
|
102
|
+
5. Continue at loop step 5, the delivered driver's `Before each dispatch`, with durable iterations and retry data; an all-closed ledger completes implement through refresh and takes `After the loop` step 0. A migrated unresolved row at or above its effective allowance stops under rule 2 without an attempt; keep an allowance-stopped row `failed`. Render a row whose `pending:` tuple is restored or retained as `row <n> in_progress (pending tuple restored)`. Never reset counters or reseed a validated seed.
|
|
99
103
|
|
|
100
104
|
### Pending recovery
|
|
101
105
|
|
|
@@ -103,7 +107,7 @@ Recover before dispatch or verification on every ledger restore. Run the read-on
|
|
|
103
107
|
|
|
104
108
|
With no `pending:` on disk, inspect home commit history before any refresh. Find the latest home child row/seed commit that changed this ledger, even when unrelated commits follow it; read its subject and body with `git -C <worktree> log --format=%H -- <ledger path>` and `git -C <worktree> show -s --format=%B <sha>`. Exclude returns superseded by a parent admission/repair or approved ledger mutation proven by the evidence below; never replay those over the parent's current ledger.
|
|
105
109
|
|
|
106
|
-
Treat ordinary home admission as a refresh with no parent commit or ledger mutation. Prove a later parent repair or approved mutation from its actual ledger diff, not its subject alone. Use a saved post-admission snapshot only with its observed HEAD and clean `dirty` state: the brief's core fields plus task rows, or existing parent admission/refresh output bound to that checked HEAD. Require the child to be an ancestor of the saved HEAD, that HEAD to be an ancestor of live HEAD, and the ledger to be unchanged from the saved HEAD through live HEAD and the working tree. Match the snapshot's ledger task names/statuses to that HEAD's tuple-free ledger. Treat a matching snapshot as settled evidence and retain the current ledger without replaying the old dispatch. Terminal rows, a verify phase label or the child's attribution alone prove no admission.
|
|
110
|
+
Treat ordinary home admission as a refresh with no parent commit or ledger mutation. Prove a later parent repair or approved mutation from its actual ledger diff, not its subject alone. Use a saved post-admission snapshot only with its observed HEAD and clean `dirty` state: the brief's core fields plus task rows, or existing parent admission/refresh output bound to that checked HEAD. Require the child to be an ancestor of the saved HEAD, that HEAD to be an ancestor of live HEAD, and the ledger to be unchanged from the saved HEAD through live HEAD and the working tree. Match the snapshot's ledger task names/statuses to that HEAD's tuple-free ledger after stripping only the recognized ledger-description suffix or legacy ledger-label prefix defined in `brief-contract.md`; keep Gn and other names byte-identical. Ignore label differences in this comparison; the post-restore refresh recomputes descriptions and allowance labels from the current spec and ledger without duplicating tasks. Treat a matching snapshot as settled evidence and retain the current ledger without replaying the old dispatch. Terminal rows, a verify phase label or the child's attribution alone prove no admission.
|
|
107
111
|
|
|
108
112
|
For an unsuperseded home return, recover the commit body's first two lines as the exact `pending:` tuple and consumed `iteration:`. Require the tuple's home identity and target to match the spec and subject, its pre-dispatch SHA to be an ancestor of live HEAD, and its consumed count to equal the committed ledger's iterations and pre-dispatch ledger's iterations +1. Read the before-image at that SHA, not at `HEAD^`. On missing, malformed or mismatched attribution, or unavailable pre-dispatch bytes, stop before refresh, dispatch or verification; name the commit and failed check rather than inferring admission from terminal rows. With no home child return to recover, retain the existing ledger restore path.
|
|
109
113
|
|
|
@@ -118,7 +122,7 @@ Revalidate a clean, exactly-one-commit return through the home row below. For la
|
|
|
118
122
|
| Tuple target | Recovery |
|
|
119
123
|
|---|---|
|
|
120
124
|
| Home leg or single-member `home` | Reconstruct the pre-dispatch ledger from `HEAD:<ledger path>` when HEAD is the tuple SHA, otherwise from `<pre-dispatch HEAD>:<ledger path>`. Compare live HEAD with that SHA. Admit exactly one new home commit with subject `loop: row <n> <status>` or `loop: seed evidence` only through the driver's `## Validate the return`: require a clean tree and the bounded row/decision result, allowing only the consumed iteration and pending removal header deltas. Clear the tuple through the admitted close or ordinary-miss repair. With zero new commits, keep the tuple and consumed iteration and take the interrupted-dispatch stop. Unrelated movement or multiple commits stop without redispatch or double counting. For a rejected terminal child result, restore the target row and the tuple in the working-tree ledger from the pre-dispatch copy before refresh (all dispatched non-cut rows for seed); preserve other child changes, child commits and consumed iterations for the human's discard/reset decision. |
|
|
121
|
-
| Actual multi-member companion | Apply `/skill:loop-implementation` `reference/coordinated-legs.md` `## Resume`: require unchanged home HEAD and exactly one new companion commit with tuple attribution and bounded result evidence. Validate and record only that leg's target result or seeded state and SHA, removing the tuple in the home-ledger commit; never rerun the recovered row. Zero, unrelated or multiple commits and missing evidence take that protocol's stop, preserving attempts, consumed iterations and
|
|
125
|
+
| Actual multi-member companion | Apply `/skill:loop-implementation` `reference/coordinated-legs.md` `## Resume`: require unchanged home HEAD and exactly one new companion commit with tuple attribution and bounded result evidence. Validate and record only that leg's target result or seeded state and SHA, removing the tuple in the home-ledger commit; never rerun the recovered row. Zero, unrelated or multiple commits and missing evidence take that protocol's stop, preserving attempts, consumed iterations and the unmigrated header. |
|
|
122
126
|
|
|
123
127
|
## Spec with plan
|
|
124
128
|
|