pi-gauntlet 7.28.0 → 7.30.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.
- package/CHANGELOG.md +23 -0
- package/README.md +15 -7
- package/agents/loop-implementer.md +51 -0
- package/bin/gauntlet-performance.mjs +5 -1
- package/bin/gauntlet-performance.test.mjs +19 -3
- package/bin/gauntlet-telemetry-seal.mjs +2 -2
- package/extensions/lib/gauntlet-settings.test.ts +10 -0
- package/extensions/lib/gauntlet-settings.ts +8 -0
- package/extensions/lib/plan-check.test.ts +63 -0
- package/extensions/lib/plan-check.ts +42 -0
- package/extensions/lib/telemetry-record.test.ts +19 -0
- package/extensions/lib/telemetry-record.ts +5 -3
- package/extensions/phase-tracker.test.ts +40 -0
- package/extensions/phase-tracker.ts +6 -2
- package/extensions/telemetry.test.ts +58 -4
- package/extensions/telemetry.ts +21 -12
- package/package.json +2 -2
- package/skills/brainstorming/SKILL.md +11 -7
- package/skills/brainstorming/gatherer.md +33 -2
- package/skills/brainstorming/reference/amendment-surface.md +6 -6
- package/skills/brainstorming/reference/pr-subject.md +51 -0
- package/skills/brainstorming/reference/spec-finalization.md +22 -6
- package/skills/brainstorming/reference/ticket-acceptance.md +2 -2
- package/skills/finishing-a-development-branch/SKILL.md +68 -11
- package/skills/gauntlet-handoff/SKILL.md +1 -2
- package/skills/gauntlet-resume/SKILL.md +7 -4
- package/skills/gauntlet-resume/reference/brief-contract.md +35 -11
- package/skills/gauntlet-resume/reference/reconstruction.md +24 -5
- package/skills/loop-implementation/SKILL.md +124 -0
- package/skills/using-git-worktrees/SKILL.md +3 -3
- package/skills/using-git-worktrees/reference/pr-head.md +74 -0
- package/skills/verification-before-completion/SKILL.md +3 -1
- package/skills/writing-plans/SKILL.md +5 -1
- package/skills/writing-plans/reference/plan-contract.md +4 -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.
|
|
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.
|
|
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.
|
|
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
|
+
});
|
package/extensions/telemetry.ts
CHANGED
|
@@ -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"))
|
|
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.
|
|
3
|
+
"version": "7.30.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.
|
|
65
|
+
"pi-cohort": ">=7.4.5"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"yaml": "^2.9.0"
|
|
@@ -18,6 +18,8 @@ You **may** read code/docs, run the existing system to observe current behavior,
|
|
|
18
18
|
|
|
19
19
|
You may **not** write outside the resolved spec dirs; build, deploy, validate, or exercise the proposed change; run implementation skills; commit the spec on `main`; or start `/skill:writing-plans`.
|
|
20
20
|
|
|
21
|
+
For a PR subject, the base merge in `using-git-worktrees/reference/pr-head.md` - its merge commit, its conflict helper's edits to the conflicted files, and its scoped conflict tests - is the one pre-approval write and run outside the spec dirs; nothing else moves.
|
|
22
|
+
|
|
21
23
|
The line: current-system observation is research; exercising the proposed change waits for approval.
|
|
22
24
|
|
|
23
25
|
## Foreground dispatch policy
|
|
@@ -32,7 +34,7 @@ Treat the flag as discovery control, not harness authorization. The command reli
|
|
|
32
34
|
|
|
33
35
|
## Checklist
|
|
34
36
|
|
|
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
|
|
37
|
+
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
38
|
|
|
37
39
|
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
40
|
|
|
@@ -42,7 +44,7 @@ Work through the items below **in order**. After a user answer or completed side
|
|
|
42
44
|
phase_tracker({ action: "start", phase: "brainstorm" })
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
2. **Set up the worktree** - see [Worktree First](#worktree-first).
|
|
47
|
+
2. **Set up the worktree** - resolve a tracker reference first per `reference/pr-subject.md`; pass a PR subject's head to `/skill:using-git-worktrees`' PR-head path. Then see [Worktree First](#worktree-first).
|
|
46
48
|
3. **Gather context** - read [Ticket Handling](#ticket-handling), then follow [`gatherer.md`](gatherer.md) without a human stop.
|
|
47
49
|
4. **Understand the idea against the draft** - if `reference/ticket-acceptance.md` is not already in context this session (restored entry, or a redraw after a fresh start), read [Ticket Handling](#ticket-handling) before the rest of this step; then see [Understand the idea](#3-understand-the-idea).
|
|
48
50
|
5. **Propose 2-3 approaches** - see [Explore approaches](#4-explore-approaches).
|
|
@@ -53,7 +55,7 @@ Work through the items below **in order**. After a user answer or completed side
|
|
|
53
55
|
10. **Re-run placeholder scan** - follow [Spec Self-Review](#spec-self-review-before-user-review-gate).
|
|
54
56
|
11. **Generate spec summary** - follow [User Review Gate](#user-review-gate).
|
|
55
57
|
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).
|
|
58
|
+
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
59
|
|
|
58
60
|
## Project Routing
|
|
59
61
|
|
|
@@ -63,7 +65,7 @@ Detection order: (1) infer from ticket labels, description, or paths; (2) use th
|
|
|
63
65
|
|
|
64
66
|
## Worktree First
|
|
65
67
|
|
|
66
|
-
The spec is the first commit in a dedicated worktree, never a separate commit on `main`.
|
|
68
|
+
The spec is the first commit in a dedicated worktree, never a separate commit on `main`. For a PR subject, the worktree is the PR's branch and the spec commit is appended to it.
|
|
67
69
|
|
|
68
70
|
Invoke `/skill:using-git-worktrees`; use `.worktrees/` or the project-native location. Carry its `Worktree ready at <full-path>` value: spec path `<full-path>/<spec dir>/<filename>.md`; every dispatch uses that `cwd`; git uses `git -C <full-path>`. Keep the process cwd unchanged.
|
|
69
71
|
|
|
@@ -87,7 +89,7 @@ If any test fails, offer no split. Never split by service, package, repo, layer,
|
|
|
87
89
|
|
|
88
90
|
### 3. Understand the idea
|
|
89
91
|
|
|
90
|
-
Clarify freely.
|
|
92
|
+
Clarify freely. For a PR subject, run the framing check against the PR's diff as the ask (`reference/pr-subject.md`).
|
|
91
93
|
|
|
92
94
|
`Read` the gathered draft unconditionally before question one. Treat it as a helper, not a fence: verify load-bearing claims from primary code, and confirm whether the codebase or ecosystem already solves the problem.
|
|
93
95
|
|
|
@@ -140,7 +142,7 @@ Propose 2-3 genuinely distinct approaches and name what separates them (the axis
|
|
|
140
142
|
|
|
141
143
|
### 5. Design for clarity and isolation
|
|
142
144
|
|
|
143
|
-
Prefer clear testable boundaries, YAGNI, existing conventions when they are sound - when the convention is the debt, say so and do not extend it - the owning schema/contract rather than parallel state, and explicit errors and edge cases.
|
|
145
|
+
Prefer clear testable boundaries, YAGNI, existing conventions when they are sound - when the convention is the debt, say so and do not extend it - the owning schema/contract rather than parallel state, and explicit errors and edge cases. Retain every existing user-approved design detail in approaches and design, including when drafting or replacing the spec.
|
|
144
146
|
|
|
145
147
|
### 6. Present the design in two rounds
|
|
146
148
|
|
|
@@ -162,6 +164,8 @@ Inside an opted-in flow, shared schemas, contracts, and invariants require a spe
|
|
|
162
164
|
|
|
163
165
|
Write under the routed spec dir: pick the routing prose's dir (overrides file, `AGENTS.md`) when it is one of the resolved dirs, else the one resolved dir from `gauntlet_setting({ key: "flowGuards" })` that already exists in the repo, else the first resolved dir (`doc/specs` under the default). Name ticketed files `YYYY-MM-DD-<ticket-id>-<topic>.md`, otherwise `YYYY-MM-DD-<topic>.md`. Use a filename-safe tracker slug (`E-12345`, `gh-123`), but native references in plan headers and commits. `<topic>` is 3-6 kebab-case words without `-design` or another suffix.
|
|
164
166
|
|
|
167
|
+
For a PR subject, apply `reference/pr-subject.md`: keep the bound-ticket filename; name an unbound spec `YYYY-MM-DD-pr-<N>-<topic>.md` from the PR title.
|
|
168
|
+
|
|
165
169
|
Mint the slug once during gather and reuse it. If questionary invalidates it, write to the new path and delete the uncommitted draft.
|
|
166
170
|
|
|
167
171
|
## Spec Self-Review (Before User Review Gate)
|
|
@@ -186,7 +190,7 @@ Classify first. Redraw test: the change alters the problem statement, adds or re
|
|
|
186
190
|
|
|
187
191
|
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
192
|
|
|
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.
|
|
193
|
+
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
194
|
|
|
191
195
|
## Red Flags — STOP
|
|
192
196
|
|
|
@@ -70,7 +70,8 @@ Scout (always dispatched):
|
|
|
70
70
|
> independently of context-builder, in pages of 50 until a short page. Match sections
|
|
71
71
|
> anywhere in each comment regardless of author; accept either ordering with
|
|
72
72
|
> `Overlap evidence:` and exclude that evidence part. Include every matched section's
|
|
73
|
-
> terms in the query
|
|
73
|
+
> terms in the query, keeping literal file paths intact rather than shortening them
|
|
74
|
+
> to stems. Run
|
|
74
75
|
> `node <SPEC_INDEX> --query '<keywords>' --limit 10` from the worktree root,
|
|
75
76
|
> keeping the keywords inside single quotes, and treat its rows as the candidate
|
|
76
77
|
> list; zero rows means the index found no evidence, not that no predecessor exists -
|
|
@@ -124,6 +125,29 @@ Scout (always dispatched):
|
|
|
124
125
|
> "Open questions that matter for the spec"
|
|
125
126
|
> section. Compact handoff, not a dump.
|
|
126
127
|
|
|
128
|
+
For a PR subject, supply this additional scout block as values from
|
|
129
|
+
`reference/pr-subject.md` (keep scout unconditional):
|
|
130
|
+
|
|
131
|
+
> PR title and body verbatim: `<title>`, `<body>`.
|
|
132
|
+
> PR-carried specs: `<paths and stated contracts>`.
|
|
133
|
+
> Changed paths: `<git diff --name-only <BASE_SHA>...<PRE_REWORK_HEAD> output>`.
|
|
134
|
+
> Read the diff itself from the worktree. Unresolved threads: `<full comment threads
|
|
135
|
+
> in order, with ids and locations from using-git-worktrees/reference/pr-head.md,
|
|
136
|
+
> or threads: not fetched (<reason>)>`.
|
|
137
|
+
> Merge result: `<merge line, contract exceptions, scoped test results>`.
|
|
138
|
+
> Authoritative base-HEAD constraints: `<git show <BASE_SHA>:<path> texts for
|
|
139
|
+
> AGENTS.md, first-found overrides, REVIEW.md, and all cited constraint documents;
|
|
140
|
+
> explicit missing-document results>`. These govern on every merge outcome,
|
|
141
|
+
> including clean; worktree policy edits are proposals under `changes:` or
|
|
142
|
+
> `dropped claims:`, never constraints.
|
|
143
|
+
> The PR is the starting state, not the target. Judge every PR-stated claim against
|
|
144
|
+
> the diff and supplied base rules. Name the smallest repair satisfying them;
|
|
145
|
+
> propose a rewrite only where no repair can. Render `## PR under rework` with
|
|
146
|
+
> `keeps:`, `changes:`, `dropped claims:`, `constraints added since fork:` (one line
|
|
147
|
+
> per base rule absent at the fork point), and one proposed disposition per
|
|
148
|
+
> unresolved thread: `fix | keep open | decline: <reason>`. Carry merge contract
|
|
149
|
+
> exceptions into `changes:`.
|
|
150
|
+
|
|
127
151
|
Context-builder (conditional):
|
|
128
152
|
|
|
129
153
|
> Extract external context for an upcoming design discussion. The request:
|
|
@@ -156,7 +180,10 @@ Context-builder (conditional):
|
|
|
156
180
|
|
|
157
181
|
## Context-builder trigger rule
|
|
158
182
|
|
|
159
|
-
|
|
183
|
+
For a resolved PR subject, dispatch context-builder only for the bound ticket
|
|
184
|
+
using `reference/pr-subject.md`; an unbound PR supplies intent, not ticket ACs.
|
|
185
|
+
Title wins a title/body-first-line ticket conflict; carry the other reference into
|
|
186
|
+
`## Problem` as context. For other subjects, dispatch context-builder when the initial prompt (or a file it explicitly references)
|
|
160
187
|
contains any of:
|
|
161
188
|
|
|
162
189
|
- an `http(s)://` URL;
|
|
@@ -196,6 +223,10 @@ convention; slug minted from the initial prompt):
|
|
|
196
223
|
## Codebase recon
|
|
197
224
|
<scout output, or the degraded text>
|
|
198
225
|
|
|
226
|
+
## PR under rework
|
|
227
|
+
<PR subject only: scout rework section and supplied PR/base/merge values per
|
|
228
|
+
reference/pr-subject.md; retain these values even when scout failed>
|
|
229
|
+
|
|
199
230
|
## External context
|
|
200
231
|
<context-builder output, degraded text, or unfetched-ref list; omit this section
|
|
201
232
|
only when the trigger rule never fired>
|
|
@@ -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
|
|
|
@@ -134,7 +134,7 @@ Details
|
|
|
134
134
|
|
|
135
135
|
Letter options in fixed order: `A) accept`, then `B) alt-1`, `C) alt-2` in index order, only for genuine alternatives. Give each option its one-line effect; tie the recommendation's reason to the end goal or a development practice. Record a letter as its shown option's token. Keep custom unlettered as shown. Keep an unavailable option visible and unlettered: `Not available: <option> - <missing prerequisite>`. Use exactly `Recommended: <letter> - <reason>` or `No recommendation - pick one`; the prepared-item schema requires a recommendation, so this amendment menu never renders the second form.
|
|
136
136
|
|
|
137
|
-
Reply grammar: one line per item, `<n>. <letter or free text>`; bare `1` applies every recommendation and re-asks any item without one by number, never re-rendering the menu (vacuous here: the prepared-item schema always supplies a recommendation); an omitted item keeps its recommendation; free text is `custom(<text>)` and may redirect anywhere ("keep the spec, fix the parser") - a redirect drops the item (still recorded in the batch commit body as `custom(<effect>)`) and returns the finding to its calling loop; a letter always picks its shown option, even when free text would spell its name. A legacy `2: <handle>=<choice>` reply is accepted silently as the same picks; the form is never rendered in a prompt. Invalid number or letter -> reprompt for that item only, keep every valid pick, never reopen the gate; a bare digit other than `1` is an invalid reply and never a grant (reprompt the same way). A bare letter or bare `1` mixed with numbered lines is invalid; free text matching an option's name remains `custom(<text>)`. Resolve every item before applying; retain original numbers during reprompts. Take no action before the reply. After the reply lands and the batch applies, print one confirmation line `Recorded: <handle>=<choice>, ...` covering the batch's decided items (
|
|
137
|
+
Reply grammar: one line per item, `<n>. <letter or free text>`; bare `1` applies every recommendation and re-asks any item without one by number, never re-rendering the menu (vacuous here: the prepared-item schema always supplies a recommendation); an omitted item keeps its recommendation; free text is `custom(<text>)` and may redirect anywhere ("keep the spec, fix the parser") - a redirect drops the item (still recorded in the batch commit body as `custom(<effect>)`) and returns the finding to its calling loop; a letter always picks its shown option, even when free text would spell its name. A legacy `2: <handle>=<choice>` reply is accepted silently as the same picks; the form is never rendered in a prompt. Invalid number or letter -> reprompt for that item only, keep every valid pick, never reopen the gate; a bare digit other than `1` is an invalid reply and never a grant (reprompt the same way). A bare letter or bare `1` mixed with numbered lines is invalid; free text matching an option's name remains `custom(<text>)`. Execute a custom instruction as given: apply its spec-edit portion, including documentation of a retained value. Treat it as a redirect only when the instruction directs work elsewhere, never merely because the reply is free text. Resolve every item before applying; retain original numbers during reprompts. Take no action before the reply. After the reply lands and the batch applies, print one confirmation line `Recorded: <handle>=<choice>, ...` covering the batch's decided items (print each custom choice as `<handle>=custom(<text>)`, retaining the literal `custom(...)` wrapper with the reply's free text verbatim inside it; grant-applied items record their recommended choice); this is the only place `<handle>=<choice>` appears to the human.
|
|
138
138
|
|
|
139
139
|
## 5. Apply, aftermath, commit
|
|
140
140
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# PR subject
|
|
2
|
+
|
|
3
|
+
## Resolve before provisioning
|
|
4
|
+
|
|
5
|
+
Match only a trimmed leading GitHub reference: `#N`, `owner/repo#N`, or a GitHub `/pull/N` or `/issues/N` URL, optionally followed by free text carried as intent. Keep mid-sentence mentions on the existing free-text/context-builder path. Keep Linear/Jira and other free-text subjects on their existing paths.
|
|
6
|
+
|
|
7
|
+
Complete classification in order, never in parallel: `gh issue view` can return a PR as an issue.
|
|
8
|
+
|
|
9
|
+
1. Run `gh pr view <N> [-R owner/repo] --json number,url,state,isDraft,title,body,headRefName,headRefOid,headRepository,isCrossRepository,baseRefName`. Accept exit 0 with `state: OPEN`, including `isDraft` true. For another state, stop: `PR #N is <state>; only an open PR can be reworked.`
|
|
10
|
+
2. Only a not-a-PR result reaches `gh issue view <N>` and the existing ticket path.
|
|
11
|
+
3. For neither, stop: `#N resolves to neither a pull request nor an issue in <owner/repo>.`
|
|
12
|
+
|
|
13
|
+
Parse the PR base repository from `url` and this checkout's repository from `git remote get-url origin`; compare lowercase owner/repo identities. For unequal identities stop before provisioning: `PR <owner/repo#N> belongs to <repo>, not this checkout (<origin owner/repo>).` Resolve bare `#N` in this checkout.
|
|
14
|
+
|
|
15
|
+
Bind the first tracker reference in the title, then the body's first line. Never bind from branch names, commit subjects, or `closingIssuesReferences`. Fetch a bound ticket, extract its AC rows using `reference/ticket-acceptance.md`, and dispatch context-builder for it. Preserve `none - ticket not fetched (<reason>)` on fetch failure. When title and first body line name different tickets, use the title's ticket and name the other in `## Problem` as context.
|
|
16
|
+
|
|
17
|
+
For an unbound PR, write `none - no ticket` under `## Acceptance criteria`; carry raw title/body as stated intent, not invented AC rows. Name its spec `YYYY-MM-DD-pr-<N>-<topic>.md`; emit the concrete filename with the actual date, PR number, and topic slug derived from the PR title. For a bound ticket keep `YYYY-MM-DD-<ticket-id>-<topic>.md`.
|
|
18
|
+
|
|
19
|
+
Pass exactly `{ number, url, title, headRefName, headRefOid, baseRefName, isCrossRepository, headRepository.nameWithOwner }` by value to provisioning in `using-git-worktrees/reference/pr-head.md`; write nothing during resolution.
|
|
20
|
+
|
|
21
|
+
## Gather from the PR and fetched base
|
|
22
|
+
|
|
23
|
+
Discover PR-carried specs with `git diff --name-only origin/<baseRefName>...<headRefOid> -- <each flowGuards.specDirs dir>` using the resolved spec directories. Read each as the PR's stated contract beside its title/body. Only when superseded, name it in `**Predecessor:**` and apply `reference/superseding.md`'s banner; that banner is the allowed spec-directory write.
|
|
24
|
+
|
|
25
|
+
On every merge outcome, including clean, no-op, and unresolved, read constraints using `git show <BASE_SHA>:<path>`: `AGENTS.md`, the first-found overrides file (check `.pi/gauntlet-overrides.md`, `gauntlet-overrides.md`, `doc/gauntlet-overrides.md` in order at BASE_SHA), `REVIEW.md`, and all cited constraint documents. Carry missing-document results explicitly. Use these fetched base-HEAD rules, never fork-point or worktree copies. Treat PR edits to policy documents as proposals recorded under `changes:` or `dropped claims:`, not governing constraints.
|
|
26
|
+
|
|
27
|
+
Dispatch scout always, and context-builder when a ticket is bound. Supply scout with the title/body verbatim, PR-carried specs, file list from `git diff --name-only <BASE_SHA>...<PRE_REWORK_HEAD>`, base-SHA constraint documents, merge result, and unresolved threads from the query owned by `using-git-worktrees/reference/pr-head.md`. Have scout read the diff itself from the worktree and the full comments of every thread. On query failure carry `threads: not fetched (<reason>)` and continue.
|
|
28
|
+
|
|
29
|
+
Instruct scout: treat the PR as the starting state, not the target; judge every stated claim against its diff and current base rules. Name the smallest repair satisfying those rules; propose a rewrite only when no repair can satisfy them. Render `## PR under rework` after `## Codebase recon`, with `keeps:`, `changes:`, `dropped claims:`, `constraints added since fork:` (one line per base-HEAD rule absent at the fork point), and one proposed disposition per unresolved thread: `fix | keep open | decline: <reason>`. Carry any merge contract exception into `changes:`. Run the framing check in `SKILL.md` `### 3` against the diff as the ask.
|
|
30
|
+
|
|
31
|
+
## Record in the spec
|
|
32
|
+
|
|
33
|
+
In `## Problem`, name `PR <owner/repo#N> - <title>`, its stated intent, every `constraints added since fork:` line as a constraint, and the existing `Framing:` line. Preserve the following grammar after `## Acceptance criteria`:
|
|
34
|
+
|
|
35
|
+
```markdown
|
|
36
|
+
## Rework record
|
|
37
|
+
|
|
38
|
+
PR: <owner/repo#N> - <title>
|
|
39
|
+
Pre-rework head: <40-char sha>
|
|
40
|
+
Base: <baseRefName> @ <sha7> - merge: <no-op | sha7 | unresolved - files>
|
|
41
|
+
Keeps: <what of the original survives>
|
|
42
|
+
Changes: <what the rework alters, repair over rewrite>
|
|
43
|
+
Dropped claims: <one `- <claim>` per PR-stated claim no longer true | none>
|
|
44
|
+
Threads:
|
|
45
|
+
- <path>:<line> <thread id> - fix | keep open | decline: <reason>
|
|
46
|
+
Scoped tests: none run | <file>: green | <file>: red - <tests>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `Threads: none` for a successful empty fetch and `Threads: not fetched (<reason>)` for failure. Preserve the query owner's outdated/path-only location fallback. Instantiate `Scoped tests: none run (<reason>)` when no documented file-scoped runner exists or `requires credentials: true`; otherwise record the scoped result, including failures. Name landed contract exceptions in `Changes:`. For an unresolved merge, open Design with `merge origin/<base> and resolve <files>` as eventual plan Task 1. Preserve the full pre-rework SHA so `git log` history remains checkable.
|
|
50
|
+
|
|
51
|
+
Preserve this record when `reference/spec-finalization.md` replaces the draft; dropping it fails inline lint. Carry the PR title/body verbatim beside bound-ticket rows in the council's `Human input`. Do not attach a rework record to ticket or free-text specs.
|
|
@@ -11,6 +11,13 @@ Spec-writing replaces the context draft, in this exact order:
|
|
|
11
11
|
Using `edit` at this step is a red flag. The header block (between the H1 and the
|
|
12
12
|
first `##`) carries `**Amend-grant:** none` directly below `**Goal:**`; a redraw's
|
|
13
13
|
rewrite carries `none` too - a redraw is a new approval, with no carry-over.
|
|
14
|
+
For a PR subject, follow `pr-subject.md`'s spec contract: carry the draft's
|
|
15
|
+
`## PR under rework` into `## Rework record` after `## Acceptance criteria`,
|
|
16
|
+
preserving its exact fixed-line grammar, 40-character pre-rework SHA, merge result,
|
|
17
|
+
keeps/changes/dropped claims, thread dispositions and fetch status, contract exceptions,
|
|
18
|
+
and scoped test results (including failures and reasons none ran). Carry the PR's
|
|
19
|
+
stated intent and constraints added since fork into Problem. Open Design with the
|
|
20
|
+
unresolved merge repair when present. Omit the record from a ticket or free-text spec.
|
|
14
21
|
3. **Immediately after the write**, confirm line 1 of the file is no longer
|
|
15
22
|
`# CONTEXT DRAFT - NOT A SPEC - fully replaced at spec-writing` - before
|
|
16
23
|
dispatching lint, critique, council, or summarizer. The phase-tracker commit
|
|
@@ -39,7 +46,7 @@ After writing the spec to `<project>/doc/specs/<filename>.md` (per [Filename Con
|
|
|
39
46
|
- **Placeholder scan.** Any `TODO`, `TBD`, `<fill in>`, `[example]`, `xxx`? Either resolve them or convert to explicit "Open Questions" with names.
|
|
40
47
|
- **Internal consistency.** Does Section 4 contradict Section 2? Does the header block carry `**Amend-grant:**` exactly once with the value `none` (the [predicate](amendment-surface.md#standing-grants))? Are component names and field names consistent throughout? If the spec replaces a prior design, confirm the predecessor carries the supersession banner and its href resolves to this spec's final filename.
|
|
41
48
|
- **Documentation named.** Does the spec name all three classes (feature/user-facing introduced; materially amended; derived/memory invalidated), or an explicit "none" for each? Enforce the materiality bar in `documentation-impact.md` without restating it: each listed doc names the category it clears, none is a code-mirror, amend-over-create was applied, and skill/agent bodies are implementation surface, not doc-impact entries here.
|
|
42
|
-
- **Ticket contract present.** Does `## Acceptance criteria` exist with either verbatim ticket rows plus dispositions or one `none - <reason>` line? Presence is enforced here, at authoring, and nowhere later.
|
|
49
|
+
- **Ticket contract present.** Does `## Acceptance criteria` exist with either verbatim ticket rows plus dispositions or one `none - <reason>` line? Presence is enforced here, at authoring, and nowhere later. For a PR subject, require the complete `## Rework record` per `pr-subject.md`; absence fails inline lint, including after a full replacement or redraw.
|
|
43
50
|
- **Scope check.** Does every paragraph serve the goal? Cut filler. If something is out of scope, say it's out of scope.
|
|
44
51
|
- **Ambiguity check.** Is every "we should..." backed by a concrete decision? Replace "we could probably" with "we will" or "we won't".
|
|
45
52
|
|
|
@@ -49,7 +56,7 @@ The first four are the inline **lint**: run them here and fix what they surface.
|
|
|
49
56
|
|
|
50
57
|
Before dispatching the worker below, resolve `documentation-impact.md` relative to this loaded reference as one absolute `<DOCUMENTATION_IMPACT_GUIDELINE>` path value. Pass that value in the worker task; do not add it to the spec.
|
|
51
58
|
|
|
52
|
-
After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; also pass the verbatim human input (the original prompt, any ticket AC snapshot - the raw rows under the gather draft's `## Ticket acceptance criteria (verbatim)` heading, never the spec's section, which holds the author's dispositions - and, for every question that offered a disposition (the cut question and the unmatched-group question in `ticket-acceptance.md`), the full question and the user's answer) - roasting-the-spec forwards it to members, chair, and applier as the `Human input (verbatim; off-limits for over-spec)` block; its dispatched applier applies the spec edits and supplies the returned audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, dispatch the worker below. If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `../../verification-before-completion/reference/settings-precedence.md`.
|
|
59
|
+
After the inline lint and before the user review gate, **brainstorming owns the critique-pass gate**; council **apply mechanics** live in `/skill:roasting-the-spec` (single source of truth - link, don't restate). Resolve the council with `gauntlet_setting({ key: "specCouncil" })` - the tool returns the merged (repo-over-preset) value as `{ verdict, members, chair, malformed, warning, errors }`. **Do not** hand-roll a settings read. When `verdict` is `"council"`, the council *is* the critique pass - invoke `/skill:roasting-the-spec` automatically (no offer, no prompt), passing `members`/`chair`; for a PR subject include the PR title and body verbatim beside the bound ticket rows in `Human input`; also pass the verbatim human input (the original prompt, any ticket AC snapshot - the raw rows under the gather draft's `## Ticket acceptance criteria (verbatim)` heading, never the spec's section, which holds the author's dispositions - and, for every question that offered a disposition (the cut question and the unmatched-group question in `ticket-acceptance.md`), the full question and the user's answer) - roasting-the-spec forwards it to members, chair, and applier as the `Human input (verbatim; off-limits for over-spec)` block; its dispatched applier applies the spec edits and supplies the returned audit (Applied/Deferred/Rejected). When `verdict` is `"worker"`, dispatch the worker below. If `malformed` is true or `errors` is non-empty, emit the `warning`/error as one line, then branch strictly on `verdict` - `malformed` can accompany *either* verdict (e.g. a bad `chair` with valid `members` still returns `council`), so never infer the worker path from `malformed` alone. If `gauntlet_setting` is unavailable, stop and report - never fall back to a manual bash/JSON settings merge. The already-applied council edits (or the worker's in-place fixes) ride in the same worktree commit. The conceptual precedence rule lives in `../../verification-before-completion/reference/settings-precedence.md`.
|
|
53
60
|
|
|
54
61
|
When `verdict` is `"worker"`, dispatch one fresh `worker` that applies the scope + ambiguity checks and fixes them in place:
|
|
55
62
|
|
|
@@ -126,11 +133,15 @@ New predecessor candidates at spec-writing: <path> (<title>), ... - index rows a
|
|
|
126
133
|
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
134
|
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
135
|
3 - show council dispositions: prints the council audit verbatim, then re-presents this gate. Not an approval.
|
|
129
|
-
|
|
136
|
+
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.
|
|
137
|
+
5 - approve, loop engine, auto-apply amends: as 4 with option 2's amendment grant.
|
|
138
|
+
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
139
|
|
|
131
140
|
Or tell me what to change in the spec, or say "revert applied council edit <X>" to undo a specific applied edit.
|
|
132
141
|
```
|
|
133
142
|
|
|
143
|
+
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.
|
|
144
|
+
|
|
134
145
|
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
146
|
|
|
136
147
|
**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 +153,16 @@ Wait for the user. Interpret funded replies with this table before routing chang
|
|
|
142
153
|
| bare `2` | approve; persist the option-2 description above |
|
|
143
154
|
| `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
155
|
| `1` with a budget token | approve; persist `fund 3 fix rounds` alone for that token (budget-only) |
|
|
156
|
+
| bare `5` | approve; persist the option-2 description above; engine loop |
|
|
157
|
+
| `5; fund 3 fix rounds` or `5, fund 3 fix rounds` | approve; persist the option-2 description plus `; fund 3 fix rounds`; engine loop |
|
|
158
|
+
| `4` with a budget token | approve; persist the token alone (budget-only); engine loop |
|
|
145
159
|
| 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 `
|
|
160
|
+
| budget token alone (`fund 3 fix rounds`) | re-present the gate asking for `1`, `2`, `4`, or `5`; write nothing |
|
|
147
161
|
| token with malformed N | re-present the gate quoting the token; write nothing, never imply a change request |
|
|
148
162
|
|
|
149
|
-
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.
|
|
163
|
+
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.
|
|
150
164
|
|
|
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
|
|
165
|
+
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
166
|
|
|
153
167
|
Post-approval changes follow [Amending an approved spec](../SKILL.md#amending-an-approved-spec).
|
|
154
168
|
|
|
@@ -157,3 +171,5 @@ After approval, mark the brainstorm phase complete:
|
|
|
157
171
|
```
|
|
158
172
|
phase_tracker({ action: "complete", phase: "brainstorm" })
|
|
159
173
|
```
|
|
174
|
+
|
|
175
|
+
Then invoke `/skill:writing-plans` on 1/2 or `/skill:loop-implementation` on 4/5 immediately, with no further prompt.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Ticket Handling
|
|
2
2
|
|
|
3
|
-
A ticket is guidance, not sole truth. Fetch it; propose scope, approach, or acceptance changes when code disagrees, and record deviations in the spec. Carry implied requirements and terms from the ticket body (Context, Problem, Idea) and every `Reporter note:` comment section into the spec as any other requirement; keep the ticket body's explicit acceptance criteria verbatim in the section below. Extract ACs from the body only, never comments. With no fetch path or a whole-ticket fetch failure, list the unfetched ref and use `none - ticket not fetched (<reason>)` in `## Acceptance criteria`; show that existing `none` line at the gate so the user can paste rows. On body success with comments marked `comments not fetched (<reason>)` or `comments incomplete after page <k> (<reason>)`, retain the fetched AC rows and carry an adjacent gate-summary line: `Implied requirements came from the body only - <comment status>; paste missing comment text.` Continue to the gate without a new stop.
|
|
3
|
+
A ticket is guidance, not sole truth. Fetch it; propose scope, approach, or acceptance changes when code disagrees, and record deviations in the spec. Carry implied requirements and terms from the ticket body (Context, Problem, Idea) and every `Reporter note:` comment section into the spec as any other requirement; keep the ticket body's explicit acceptance criteria verbatim in the section below. Retain concrete details from a requirement-carrying note (paths, source measurements, preserved entry points) within that requirement in Design, not only in a context or premise note. Preserve a source measurement as a description of the input, not a new target or limit. Extract ACs from the body only, never comments. With no fetch path or a whole-ticket fetch failure, list the unfetched ref and use `none - ticket not fetched (<reason>)` in `## Acceptance criteria`; show that existing `none` line at the gate so the user can paste rows. On body success with comments marked `comments not fetched (<reason>)` or `comments incomplete after page <k> (<reason>)`, retain the fetched AC rows and carry an adjacent gate-summary line: `Implied requirements came from the body only - <comment status>; paste missing comment text.` Continue to the gate without a new stop.
|
|
4
4
|
|
|
5
5
|
**Extract the ticket's ACs.** When the ticket body has a heading matching `/acceptance criteria/i`, every list item under it is an AC (numbered, bullet, or checkbox) until the next heading of that heading's level or higher, and no other list in the body is; a heading one level deeper inside the section is a repo group - it names the repo whose part delivers the rows under it and does not end extraction. When there is no such heading, every top-level checkbox row in the body is an AC, except rows under a `Post-deployment housekeeping`, `Out of scope`, or `Follow-up` heading. Otherwise the ticket has no ACs. A nested list under an AC row rides with its parent as one row; an AC heading holding prose and no list makes each paragraph one row. Carry checked and unchecked rows alike, all written `- [ ]`. Take the rows and their repo-group headings from the gather draft's `## Ticket acceptance criteria (verbatim)` heading; when the ticket was not fetched, the gate summary shows the `none` line so the user can paste the rows, which then become rows.
|
|
6
6
|
|
|
@@ -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 in option B, not a request to name a repo. 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.
|