pi-gauntlet 4.4.1 → 4.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +4 -4
- package/extensions/lib/phase-tracker-helpers.test.ts +49 -0
- package/extensions/lib/phase-tracker-helpers.ts +40 -0
- package/extensions/phase-tracker.ts +22 -4
- package/package.json +1 -1
- package/skills/finishing-a-development-branch/SKILL.md +67 -22
- package/skills/subagent-driven-development/SKILL.md +3 -3
- package/skills/verification-before-completion/SKILL.md +1 -1
- package/skills/verification-before-completion/reference/conformance-check.md +253 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v4.4.3 - 2026-07-19
|
|
4
|
+
|
|
5
|
+
Brainstorming is the sole gauntlet entry point (#2): gate all three phase-tracker
|
|
6
|
+
enforcement surfaces (closure completion gate, closure-model guard, flow guards) on a
|
|
7
|
+
durable `gauntletEntered` marker so the gauntlet is dormant unless brainstorming started
|
|
8
|
+
the flow. A reflexive `phase_tracker start verify` on an ad-hoc one-liner no longer trips
|
|
9
|
+
the closure gate or forces a spurious conformance dispatch.
|
|
10
|
+
|
|
11
|
+
The marker is derived in `reconstructState` from the session branch (mirroring
|
|
12
|
+
`conformanceDispatched`: reset on reconstruct, threaded through replay, updated in the live
|
|
13
|
+
start/reset handlers via one `nextGauntletEntered` transition), so it survives pi-condense
|
|
14
|
+
pruning and `--session` resume. All three surfaces gate marker-first, so a dormant
|
|
15
|
+
out-of-flow session short-circuits before any settings load; in-flow behavior is unchanged
|
|
16
|
+
(`brainstormActive` implies `gauntletEntered`). New pure helpers `nextGauntletEntered` /
|
|
17
|
+
`closureGateBlocks` / `closureModelGuardApplies` / `flowGuardApplies` are unit-tested; the
|
|
18
|
+
two settings-dependent surfaces inline-match their predicate (the `markerGuardApplies`
|
|
19
|
+
convention) to preserve marker-first laziness. Docs (README, configuration.md, AGENTS.md,
|
|
20
|
+
conformance-check.md, the `phase_tracker` tool description) reworded: enforcement is opt-in
|
|
21
|
+
by brainstorming entry, not ambient.
|
|
22
|
+
|
|
23
|
+
## v4.4.2 - 2026-07-15
|
|
24
|
+
|
|
25
|
+
Condense the finish-time conformance disposition gate to a dense, human-readable
|
|
26
|
+
decision list - the corrective second pass over v4.4.1's disposition work, which
|
|
27
|
+
delivered the right behavior but bloated the render and the freshness protocol.
|
|
28
|
+
|
|
29
|
+
- **Finish gate (`finishing-a-development-branch`):** carried-open conformance
|
|
30
|
+
findings now render as a dense one-line-per-decision list - each line carries a
|
|
31
|
+
plain-language title, the unresolved point, its question, and an inline recommended
|
|
32
|
+
choice - with a two-option reply (`1` = every recommendation; `2:` =
|
|
33
|
+
recommended-except comma-separated `<handle>=<choice>` overrides). Replaces the
|
|
34
|
+
terse `Gn` tokens and the flat D1-D5 per-gap menu. Single-concern gaps show no
|
|
35
|
+
`/Cn` symbol; `UNAUTHORIZED` items note availability deviations inline; the
|
|
36
|
+
zero-gap path still goes straight to branch options with no sign-off.
|
|
37
|
+
- **Freshness (`verification-before-completion/reference/conformance-check.md`,
|
|
38
|
+
`finishing-a-development-branch`, `subagent-driven-development`):** the four-field
|
|
39
|
+
audit-identity/hash protocol (duplicated across three files) collapses to a
|
|
40
|
+
`status`/`audited-base` sentinel plus a two-command working-tree drift check;
|
|
41
|
+
any change or doubt re-audits. The durable `## Closure / conformance` handoff
|
|
42
|
+
header drops `audited-spec-path`/`audited-diff-hash`/`audited-spec-hash`.
|
|
43
|
+
- **De-duplication:** the disposition-availability table, the `UNAUTHORIZED`
|
|
44
|
+
question, the `recommended: none` preflight, and the fix loop now live only in
|
|
45
|
+
`conformance-check.md`; the finish gate and SDD consume them by link.
|
|
46
|
+
|
|
3
47
|
## v4.4.1 - 2026-07-13
|
|
4
48
|
|
|
5
49
|
Gates ratify, not ceremony: two gauntlet gates now auto-apply mechanical/drafting
|
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Concretely, one change through the gauntlet:
|
|
|
38
38
|
1. You describe the change. **`brainstorming`** sets up an isolated worktree, explores the codebase, and turns your description into a written spec. A multi-model critique runs on it automatically. **You read and approve the spec - human gate 1.** No implementation code exists yet.
|
|
39
39
|
2. **`writing-plans`** decomposes the approved spec into atomic, independently-verifiable tasks, grouped into parallel waves where they don't touch the same files.
|
|
40
40
|
3. **`subagent-driven-development`** executes the plan one task at a time, each in a fresh subagent, behind spec-compliance review then code-quality review. TDD-locked: red, green, refactor.
|
|
41
|
-
4. **verify**: a whole-diff code review, then the **conformance gate** - a subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized.
|
|
41
|
+
4. **verify**: a whole-diff code review, then the **conformance gate** - a subagent reads the finished code and docs against your *original words* from step 1, not the plan, and reports per-requirement: delivered, partial, missing, drifted, or unauthorized. Inside a brainstorming-entered flow this gate is machine-blocked from being skipped. Compatible executable recommendations auto-run through an isolated fix-and-re-audit loop with no prompt; anything still open surfaces as a dense list - one line per decision, plain-language, with its recommended choice inline. Reply `1` to take every recommendation, or `2:` with per-item overrides; a current `CONFORMS` / no-concerns result goes straight to the branch options with no extra conformance sign-off.
|
|
42
42
|
5. **`finishing-a-development-branch`**: squash, PR, keep, or discard. **Human gate 2** - the only other decision you make.
|
|
43
43
|
|
|
44
44
|
```mermaid
|
|
@@ -66,15 +66,15 @@ pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
|
|
|
66
66
|
- **7 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `spec-council-member`, `spec-council-synthesizer`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
|
|
67
67
|
- **3 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you commit/push without a passing test run since your last edit. See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
|
|
68
68
|
|
|
69
|
-
pi-gauntlet is **opinionated**: every non-trivial change
|
|
69
|
+
pi-gauntlet is **opinionated**: every non-trivial change is *meant* to ride this one pipeline, entered through `brainstorming`. Enforcement is opt-in by entry, not ambient: once brainstorming starts a flow, the phase-tracker extension mechanically blocks a phase from closing before its gate runs. A change made *without* entering the flow (a typo, a formatting run, a dependency bump - see "When to use / when NOT to use") is not gated; the discipline of routing real work through the pipeline is a convention the tooling supports, not a trap it springs on every edit.
|
|
70
70
|
|
|
71
71
|
## Key concepts
|
|
72
72
|
|
|
73
73
|
| Term | Meaning |
|
|
74
74
|
| --- | --- |
|
|
75
|
-
| Gate | A machine-enforced checkpoint between phases (e.g. `complete verify` is blocked until conformance review has run). Not a suggestion. |
|
|
75
|
+
| Gate | A machine-enforced checkpoint between phases (e.g. within a brainstorming-entered flow, `complete verify` is blocked until conformance review has run). Not a suggestion. |
|
|
76
76
|
| Spec council | Multi-model critique of the spec before you see it (`roasting-the-spec`); falls back to a single-model critique if no council is configured. |
|
|
77
|
-
| Conformance gate | The closing check: does the delivered code + docs match your *original prompt*, not the derived plan?
|
|
77
|
+
| Conformance gate | The closing check: does the delivered code + docs match your *original prompt*, not the derived plan? Compatible executable recommendations auto-fix first; anything still open renders as a dense one-line-per-decision list with each recommended choice inline. Reply `1` to accept all recommendations or `2:` with per-item overrides; a current `CONFORMS` / no-concerns handoff goes straight to branch options with no extra sign-off. |
|
|
78
78
|
| Wave | A batch of plan tasks that don't touch the same files, dispatched to implementers in parallel. |
|
|
79
79
|
| Overrides file | `.pi/gauntlet-overrides.md` - where you put project-specific detail the generic skills don't know (CI command, worktree wrapper, routing rules). |
|
|
80
80
|
|
|
@@ -3,6 +3,10 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import {
|
|
4
4
|
CONTEXT_DRAFT_MARKER,
|
|
5
5
|
checkSubstep,
|
|
6
|
+
closureGateBlocks,
|
|
7
|
+
closureModelGuardApplies,
|
|
8
|
+
flowGuardApplies,
|
|
9
|
+
nextGauntletEntered,
|
|
6
10
|
phaseLabel,
|
|
7
11
|
parseGitCommit,
|
|
8
12
|
resolveRepoDir,
|
|
@@ -118,3 +122,48 @@ test("markerGuardApplies: gated by flowGuards.enforce and brainstorm in_progress
|
|
|
118
122
|
assert.equal(markerGuardApplies(true, "in_progress"), true);
|
|
119
123
|
assert.equal(markerGuardApplies(true, "pending"), false);
|
|
120
124
|
});
|
|
125
|
+
|
|
126
|
+
test("nextGauntletEntered: arms only when a start makes brainstorm in_progress", () => {
|
|
127
|
+
assert.equal(nextGauntletEntered(false, "start", "in_progress"), true);
|
|
128
|
+
assert.equal(nextGauntletEntered(true, "start", "in_progress"), true); // re-arm idempotent
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("nextGauntletEntered: reset always disarms", () => {
|
|
132
|
+
assert.equal(nextGauntletEntered(true, "reset", "pending"), false);
|
|
133
|
+
assert.equal(nextGauntletEntered(true, "reset", "in_progress"), false);
|
|
134
|
+
assert.equal(nextGauntletEntered(false, "reset", "pending"), false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("nextGauntletEntered: marker survives downstream actions", () => {
|
|
138
|
+
assert.equal(nextGauntletEntered(true, "start", "complete"), true); // start plan/implement (brainstorm already complete)
|
|
139
|
+
assert.equal(nextGauntletEntered(true, "complete", "complete"), true);
|
|
140
|
+
assert.equal(nextGauntletEntered(true, "substep", "in_progress"), true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("nextGauntletEntered: a non-brainstorm start never arms a dormant flow", () => {
|
|
144
|
+
assert.equal(nextGauntletEntered(false, "start", "complete"), false); // cold start verify/implement
|
|
145
|
+
assert.equal(nextGauntletEntered(false, "start", "skipped"), false);
|
|
146
|
+
assert.equal(nextGauntletEntered(false, "complete", "pending"), false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("closureGateBlocks: blocks only for verify + entered + enforce + not-yet-dispatched", () => {
|
|
150
|
+
assert.equal(closureGateBlocks("verify", true, true, false), true);
|
|
151
|
+
assert.equal(closureGateBlocks("verify", false, true, false), false); // incident: not entered -> no block
|
|
152
|
+
assert.equal(closureGateBlocks("verify", true, true, true), false); // already dispatched
|
|
153
|
+
assert.equal(closureGateBlocks("verify", true, false, false), false); // enforce off
|
|
154
|
+
assert.equal(closureGateBlocks("plan", true, true, false), false); // wrong phase
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("flowGuardApplies: requires both an active guard phase and an entered flow", () => {
|
|
158
|
+
assert.equal(flowGuardApplies(true, true), true);
|
|
159
|
+
assert.equal(flowGuardApplies(true, false), false); // not entered
|
|
160
|
+
assert.equal(flowGuardApplies(false, true), false); // phase not active
|
|
161
|
+
assert.equal(flowGuardApplies(false, false), false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("closureModelGuardApplies: requires both an entered flow and closure enforcement", () => {
|
|
165
|
+
assert.equal(closureModelGuardApplies(true, true), true);
|
|
166
|
+
assert.equal(closureModelGuardApplies(false, true), false); // not entered -> dormant
|
|
167
|
+
assert.equal(closureModelGuardApplies(true, false), false); // enforce off
|
|
168
|
+
assert.equal(closureModelGuardApplies(false, false), false);
|
|
169
|
+
});
|
|
@@ -90,6 +90,46 @@ export function markerGuardApplies(flowGuardsEnforced: boolean, brainstormStatus
|
|
|
90
90
|
return flowGuardsEnforced && brainstormStatus === "in_progress";
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// Flow-entry marker (spec 2026-07-19-sole-gauntlet-entry-point). The gauntlet is
|
|
94
|
+
// opt-in: enforcement is dormant unless brainstorming started this flow. A `start`
|
|
95
|
+
// that leaves brainstorm in_progress is the unique arming signal (exactly one phase
|
|
96
|
+
// is in_progress at a time; the start target is not stored but is inferable). `reset`
|
|
97
|
+
// disarms; every other action preserves the running marker so it survives brainstorm
|
|
98
|
+
// -> plan -> implement -> verify. brainstormStatus is the phase status AFTER the action.
|
|
99
|
+
export function nextGauntletEntered(prev: boolean, action: string, brainstormStatus: string): boolean {
|
|
100
|
+
if (action === "reset") return false;
|
|
101
|
+
if (action === "start" && brainstormStatus === "in_progress") return true;
|
|
102
|
+
return prev;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Closure completion-gate decision. Marker-first: a non-entered flow never blocks
|
|
106
|
+
// `complete verify`. Executable contract for the extension's inlined check (same
|
|
107
|
+
// define-and-test-but-inline pattern as markerGuardApplies).
|
|
108
|
+
export function closureGateBlocks(
|
|
109
|
+
phase: string,
|
|
110
|
+
gauntletEntered: boolean,
|
|
111
|
+
closureEnforced: boolean,
|
|
112
|
+
conformanceDispatched: boolean,
|
|
113
|
+
): boolean {
|
|
114
|
+
return phase === "verify" && gauntletEntered && closureEnforced && !conformanceDispatched;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Closure-model-guard applicability. Marker-first: the extension inlines this with the
|
|
118
|
+
// settings read (closureEnforced()) as a lazy call AFTER gauntletEntered, so a dormant
|
|
119
|
+
// (out-of-flow) session performs no settings I/O. This predicate is the tested contract
|
|
120
|
+
// for that inlined check (same define-test-and-inline pattern as markerGuardApplies /
|
|
121
|
+
// closureGateBlocks); it takes the already-resolved booleans, never triggering the read.
|
|
122
|
+
export function closureModelGuardApplies(gauntletEntered: boolean, closureEnforced: boolean): boolean {
|
|
123
|
+
return gauntletEntered && closureEnforced;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Flow-guard applicability: a guard fires only when its phase is active AND the flow
|
|
127
|
+
// was entered via brainstorming. Both inputs are in-memory, so the extension calls
|
|
128
|
+
// this directly with no settings-load cost.
|
|
129
|
+
export function flowGuardApplies(phaseActive: boolean, gauntletEntered: boolean): boolean {
|
|
130
|
+
return phaseActive && gauntletEntered;
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
export const markerBlockReason = (file: string): string =>
|
|
94
134
|
`Blocked: ${file} still begins with the context-draft marker - the spec-writing ` +
|
|
95
135
|
`overwrite has not happened. Overwrite the draft with the real spec (write tool, ` +
|
|
@@ -26,7 +26,9 @@ import { loadGauntletSettings } from "./lib/gauntlet-settings-loader.ts";
|
|
|
26
26
|
import {
|
|
27
27
|
checkSubstep,
|
|
28
28
|
findMarkerFile,
|
|
29
|
+
flowGuardApplies,
|
|
29
30
|
markerBlockReason,
|
|
31
|
+
nextGauntletEntered,
|
|
30
32
|
parseGitCommit,
|
|
31
33
|
phaseLabel,
|
|
32
34
|
resolveRepoDir,
|
|
@@ -239,6 +241,7 @@ function formatStatus(phases: PhaseMap): string {
|
|
|
239
241
|
export default function (pi: ExtensionAPI) {
|
|
240
242
|
let phases: PhaseMap = emptyPhases();
|
|
241
243
|
let conformanceDispatched = false;
|
|
244
|
+
let gauntletEntered = false;
|
|
242
245
|
|
|
243
246
|
// Warn-once-per-phase ledger; cleared on every phase transition and on reconstruct.
|
|
244
247
|
const firedGuards = new Map<string, boolean>();
|
|
@@ -286,6 +289,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
286
289
|
const reconstructState = (ctx: ExtensionContext) => {
|
|
287
290
|
phases = emptyPhases();
|
|
288
291
|
conformanceDispatched = false;
|
|
292
|
+
gauntletEntered = false;
|
|
289
293
|
firedGuards.clear();
|
|
290
294
|
pendingGuardWarnings.clear();
|
|
291
295
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
@@ -296,6 +300,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
296
300
|
const details = msg.details as PhaseTrackerDetails | undefined;
|
|
297
301
|
if (details && !details.error) {
|
|
298
302
|
phases = details.phases;
|
|
303
|
+
gauntletEntered = nextGauntletEntered(gauntletEntered, details.action, details.phases.brainstorm.status);
|
|
299
304
|
if (details.action === "reset") conformanceDispatched = false;
|
|
300
305
|
}
|
|
301
306
|
} else if (msg.toolName === "subagent") {
|
|
@@ -345,7 +350,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
345
350
|
const specDirs = () => resolveFlowGuards(g()).specDirs;
|
|
346
351
|
|
|
347
352
|
// Closure-review model guard - independent of flowGuards, gated by closureReview.enforce.
|
|
348
|
-
|
|
353
|
+
// gating contract: closureModelGuardApplies (see helpers). Marker-first: gauntletEntered
|
|
354
|
+
// short-circuits before closureEnforced()'s settings load, so an ad-hoc (out-of-flow)
|
|
355
|
+
// subagent dispatch never loads settings or leaks a settingsErrorWarning onto its result.
|
|
356
|
+
// Inline-matched (not called) so closureEnforced() stays a lazy second conjunct.
|
|
357
|
+
if (event.toolName === "subagent" && gauntletEntered && closureEnforced()) {
|
|
349
358
|
const model = closureReviewModel();
|
|
350
359
|
// Only execution-mode dispatches carry a model; management/control modes
|
|
351
360
|
// (action: list/get/create/update/delete/status/...) execute nothing, so skip them.
|
|
@@ -371,8 +380,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
371
380
|
// event no guard inspects (any read-only tool, or any tool in a dormant session)
|
|
372
381
|
// returns here without touching disk. Only genuinely guardable events pay for g().
|
|
373
382
|
const brainstormActive = phases.brainstorm.status === "in_progress";
|
|
374
|
-
const guardableWrite =
|
|
375
|
-
|
|
383
|
+
const guardableWrite =
|
|
384
|
+
(event.toolName === "write" || event.toolName === "edit") && flowGuardApplies(brainstormActive, gauntletEntered);
|
|
385
|
+
const guardableBash =
|
|
386
|
+
event.toolName === "bash" && flowGuardApplies(activeGuardPhase() !== undefined, gauntletEntered);
|
|
376
387
|
if (!guardableWrite && !guardableBash) return undefined;
|
|
377
388
|
|
|
378
389
|
if (!flowGuardsEnforced()) return undefined;
|
|
@@ -527,7 +538,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
527
538
|
description:
|
|
528
539
|
"Track workflow phase progress (brainstorm → plan → implement → verify → ship). " +
|
|
529
540
|
"Actions: start (mark phase in_progress), complete (mark phase complete), " +
|
|
530
|
-
"skip (mark phase skipped with reason), status (show all phases), reset (clear all phases), substep (set/clear a substep label on an in_progress phase)."
|
|
541
|
+
"skip (mark phase skipped with reason), status (show all phases), reset (clear all phases), substep (set/clear a substep label on an in_progress phase). " +
|
|
542
|
+
"Drives gauntlet-flow enforcement entered via brainstorming; the closure gate, closure-model guard, and flow guards arm only when brainstorming started the flow. Ad-hoc start verify/start implement calls do not arm the gates. Not for ad-hoc use.",
|
|
531
543
|
parameters: PhaseTrackerParams,
|
|
532
544
|
|
|
533
545
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -572,6 +584,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
572
584
|
};
|
|
573
585
|
}
|
|
574
586
|
phases = { ...phases, [params.phase]: transitionPhaseState("in_progress") as PhaseState };
|
|
587
|
+
gauntletEntered = nextGauntletEntered(gauntletEntered, "start", phases.brainstorm.status);
|
|
575
588
|
firedGuards.clear();
|
|
576
589
|
updateWidget(ctx);
|
|
577
590
|
return {
|
|
@@ -598,8 +611,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
598
611
|
} as PhaseTrackerDetails,
|
|
599
612
|
};
|
|
600
613
|
}
|
|
614
|
+
// gating contract: closureGateBlocks (see helpers). gauntletEntered is the leading
|
|
615
|
+
// conjunct so a cold-session `complete verify` (the #2 incident) neither blocks nor
|
|
616
|
+
// loads settings; the remaining conjuncts match closureGateBlocks exactly.
|
|
601
617
|
if (
|
|
602
618
|
params.phase === "verify" &&
|
|
619
|
+
gauntletEntered &&
|
|
603
620
|
resolveClosureReview(loadGauntletSettings(ctx.cwd).gauntlet).enforce &&
|
|
604
621
|
!conformanceDispatched
|
|
605
622
|
) {
|
|
@@ -694,6 +711,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
694
711
|
PHASES.map((p) => [p, transitionPhaseState("pending")]),
|
|
695
712
|
) as PhaseMap;
|
|
696
713
|
conformanceDispatched = false;
|
|
714
|
+
gauntletEntered = nextGauntletEntered(gauntletEntered, "reset", phases.brainstorm.status);
|
|
697
715
|
firedGuards.clear();
|
|
698
716
|
updateWidget(ctx);
|
|
699
717
|
return {
|
package/package.json
CHANGED
|
@@ -72,46 +72,91 @@ Or ask: "This branch split from main - is that correct?"
|
|
|
72
72
|
|
|
73
73
|
### Step 3.5: Closure / Conformance Disposition Gate
|
|
74
74
|
|
|
75
|
-
This is an **enforced
|
|
75
|
+
This is an **enforced disposition gate**, not a surface-only notice. The user is about to choose how to ship; every carried-open decision must get an explicit disposition here, before Step 4's menu. Tests prove the code runs; conformance proves it does what was requested - different gates.
|
|
76
76
|
|
|
77
|
-
**
|
|
77
|
+
`verification-before-completion/reference/conformance-check.md` is **canonical** for the durable handoff schema, concern-decomposition rules, the single disposition-availability table, the `UNAUTHORIZED` question text, the `recommended: none` preflight, the freshness rule, and the concern-scoped fix projection. This step owns only **render, response, and execute-order** and consumes the rest by link - it does not restate the availability table, the `UNAUTHORIZED` question, or the preflight prose.
|
|
78
78
|
|
|
79
|
-
**If
|
|
79
|
+
**If no conformance check has run in this flow** (e.g. ad-hoc work that landed without an execution skill): say so, then dispatch a fresh-context `conformance-reviewer` against the origin (spec + verbatim prompt + full diff vs base) per that reference - it owns the audit-time input rule (stage/commit untracked deliverables before auditing). Closing the loop is cheap relative to shipping unverified intent. Route the raw reviewer verdict through the reference's canonical pipeline (gap/concern partition, auto-fix where eligible, concern decomposition, emission of a durable `## Closure / conformance` block), then consume that block through the branching below exactly as a carried handoff.
|
|
80
80
|
|
|
81
|
-
**
|
|
81
|
+
**Freshness precondition - before any verdict branch, including `CONFORMS`.** The durable block opens with a two-line sentinel: `status: CONFORMS (0 open)` or `status: GAPS (N open)`, then `audited-base: <full HEAD SHA at audit time>`. Read the sentinel, then apply the reference's freshness rule (its `## Closure / conformance` block is the single source): compare `audited-base` to the current working tree; any change, doubt, missing/mismatched sentinel, legacy terse row, or malformed structured reviewer block triggers a fresh audit and replacement of the closure block. Never infer `CONFORMS` from the absence of bullets. Only a clean, valid `status: CONFORMS (0 open)` handoff enters the zero-gap fast path.
|
|
82
82
|
|
|
83
|
+
**Zero-gap fast path:** print exactly
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Closure / conformance: CONFORMS
|
|
83
87
|
```
|
|
84
|
-
G<n>: <verdict> — recommended: <fix|accept|rescope> — touched-files: <paths>
|
|
85
|
-
round history: R1 ..., R2 ...
|
|
86
88
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
then continue directly to Step 4. No approval prompt, no menu, no shared options line, no sign-off. If the run auto-applied fixes, surface the flat `auto-applied fix commits: <Gn: SHA>, ...` index from the durable block as **one informational, non-blocking line** with a one-line revert offer (see "Revert semantics") - a gap that auto-converged mid-verify has no bullet, so this index is the only place its fix commit stays revertable. Do not wait for acknowledgment.
|
|
90
|
+
|
|
91
|
+
**Carried-open render (dense).** Read the `## Closure / conformance` block. Render a header with the decision count, one bullet per decision unit (a gap by default; a `Gn/Cn` concern only where the reference split it), then the shared options line, then the recommended-set reply. Never show durable-card internals (ownership, evidence tokens, identity, hashes) in the render.
|
|
92
|
+
|
|
93
|
+
Each bullet:
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
`* <handle> - <plain title>: <what's unresolved, one clause>. <short question> Recommended: <choice> (<one-clause why>).`
|
|
96
|
+
|
|
97
|
+
- `<handle>` leads the bullet and is a short unique human word derived from the title (`Cache coverage` -> `cache`); on collision append a digit. It is the token option 2 targets. When a gap split and no clean word fits, use the bare `Gn/Cn`; a single-concern gap uses its gap ID `Gn`.
|
|
98
|
+
- The shared options line sits below the bullets: `Other options per item: fix-now / accept / rescope / follow-up / custom`, listing the options **generally available across items**. When a specific item's availability deviates - an option unavailable for it, or an `UNAUTHORIZED` item whose `rescope` is unavailable and whose `fix-now` means removal - note that deviation as a short parenthetical on **that item's bullet** (one clause, not a block), e.g. `(rescope N/A: scope creep)`. The shared line appears **only in the carried-open render**, never in the zero-gap path. Full per-option effects only on request, or when option 2 targets an unclear choice.
|
|
99
|
+
- Group items under one recommended line only when they share a disposition and rationale; each grouped handle repeats its title.
|
|
100
|
+
- Availability per concern comes from the reference's single availability table - apply it against current context (worktree state, `maxFixRounds`, ownership, resource accessibility), do not restate it. `UNAUTHORIZED` bullets ask the reference's question verbatim (`Should this unrequested behavior become part of the current workflow?`); `rescope-into-spec` is shown **unavailable** (not dropped) and `fix-now` means **removal** of the unrequested code.
|
|
101
|
+
- `revert conformance fix Gn`, when the gap has an auto-applied fix, renders on the shared options line as a **separate one-off action** - never inside a bullet's recommendation and never in the option-2 list. Name the parent gap and warn that revert undoes the entire gap-level commit (see "Revert semantics").
|
|
102
|
+
|
|
103
|
+
Representative carried-open render (multi-concern gap split to `e2e`; single-concern gap `cache`; `UNAUTHORIZED` gap `auth`):
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Conformance: 3 decisions needed before shipping.
|
|
107
|
+
|
|
108
|
+
* e2e - Source-image E2E validation: not run; blocked (HYDRA1.png, HTTP 401).
|
|
109
|
+
Still in scope for this branch? Recommended: rescope (defer until image available) (fix-now N/A: needs HYDRA1.png).
|
|
110
|
+
* cache - Cache coverage: implemented but the spec is silent on it.
|
|
111
|
+
In scope? Recommended: accept into spec (behavior is intentional).
|
|
112
|
+
* auth - Unrequested admin bypass: adds an unlisted route. Should this unrequested behavior become part of the current workflow? Recommended: fix-now = remove it (rescope N/A: scope creep).
|
|
113
|
+
|
|
114
|
+
Other options per item: fix-now / accept / rescope / follow-up / custom.
|
|
115
|
+
|
|
116
|
+
1. Go with recommended
|
|
117
|
+
2. Recommended except <handle>=<choice> e.g. "2: cache=follow-up"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
A single-concern render is identical minus the split: one bullet whose handle is the gap ID or word, no sibling.
|
|
121
|
+
|
|
122
|
+
#### Response grammar
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
1 -> apply every recommendation
|
|
126
|
+
2: cache=follow-up
|
|
127
|
+
2: e2e=custom(open ticket after image lands), cache=follow-up
|
|
94
128
|
```
|
|
95
129
|
|
|
96
|
-
|
|
130
|
+
- `1` (or `apply recommended`) applies all recommendations.
|
|
131
|
+
- `2:` takes a comma-separated override list, each `<handle>=<choice>`; omitted items keep their recommendation. A handle may appear at most once (repeat = invalid).
|
|
132
|
+
- `custom(<concrete effect>)` supplies an inline effect. Manual fix-in-place is expressed only as `custom(...)` where isolated `fix-now` is unavailable.
|
|
133
|
+
- `recommended: none` items follow the reference's preflight (linked, not restated): the item needs a `<handle>=custom(...)` decision, and option 1 is withheld until every open item has an executable recommendation; after the custom decision the menu re-renders for the remainder.
|
|
134
|
+
- `revert conformance fix Gn` is a valid standalone reply, mutually exclusive with `1` and `2:`; it never appears inside a `2:` override list.
|
|
135
|
+
- Invalid handle or choice -> focused reprompt naming only that item, retaining every valid pick and never reopening the gate. Unknown token: list the valid titled handles. Known item, bad choice: repeat its title + its available choices.
|
|
97
136
|
|
|
98
|
-
|
|
137
|
+
#### Execute order
|
|
99
138
|
|
|
100
|
-
|
|
101
|
-
2. Run the **full** loop from `verification-before-completion/reference/conformance-check.md` "Fix loop" (implementer → integrate → test → `code-reviewer` → re-audit). Do not re-describe the loop steps here — that file is the single source.
|
|
102
|
-
3. Re-run Step 1's test verification on the result.
|
|
103
|
-
4. Re-enter this step (3.5) with the re-audited `## Closure / conformance` block before re-presenting ship options.
|
|
139
|
+
Take **no** disposition action before the reply. Then, once, in order:
|
|
104
140
|
|
|
105
|
-
**
|
|
141
|
+
1. **Normalize** every `custom(...)` into explicit operations; classify state-changing (edits code or spec) vs not. Clarify only an ambiguous or unexecutable effect.
|
|
142
|
+
2. **Commit spec edits** (`accept-into-spec`, `rescope-into-spec`, state-changing spec `custom`) - the main session edits the spec directly, before any fix dispatch (a dirty tree rejects `worktree: true`, and the re-audit must read the amended spec).
|
|
143
|
+
3. **Re-audit if step 2 changed the spec**; regenerate the inventory and re-render if it changed. Project `fix-now` only from the refreshed inventory.
|
|
144
|
+
4. **fix-now + code-changing custom:** project the selected concerns per gap into the reference's concern-scoped fix contract (excluding accepted/rescoped/followed-up siblings); run the reference "Fix loop" (unchanged - do not re-describe it). A code-changing `custom` runs the project's tests + `code-reviewer` on its delta before proceeding. Re-run Step 1's canonical tests.
|
|
145
|
+
5. **Re-audit after all state-changing work;** obtain fresh decisions **only if** the refreshed inventory differs from the approved one, else proceed.
|
|
146
|
+
6. **follow-up** from the current inventory: create the item via the project's issue-tracker convention (`.pi/gauntlet-overrides.md`), record the ticket ID/URL; on failure keep the concern open.
|
|
147
|
+
7. **Non-state-changing custom:** execute and record the result.
|
|
148
|
+
8. **revert** (`revert conformance fix Gn`): light-revert the indexed commit, re-run Step 1's canonical tests; on failure stop; on pass re-audit and regenerate.
|
|
149
|
+
9. Re-enter Step 3.5 with the re-audited block if any concern remains open.
|
|
106
150
|
|
|
107
|
-
|
|
151
|
+
Record every final disposition with its **stable ID** as `Gn/Cn - <title>: <disposition>` (or `Gn - <title>: <disposition>` for a single-concern gap) - ticket ID/URL for `follow-up`, result for `custom`; never relabel a `custom` result as a recommendation. This durable record is the machine/audit surface; the interactive render stays handle-based. Then continue to Step 4:
|
|
108
152
|
|
|
109
153
|
```
|
|
110
154
|
Closure / conformance: CONFORMS
|
|
111
|
-
(or: GAPS
|
|
155
|
+
(or: GAPS resolved - G1/C1 - Source-image E2E validation: rescope-into-spec;
|
|
156
|
+
G2 - Cache coverage: follow-up (PROJ-123); ...)
|
|
112
157
|
```
|
|
113
158
|
|
|
114
|
-
|
|
159
|
+
No auto-proceed: every carried-open decision needs an explicit disposition before Step 4 renders.
|
|
115
160
|
|
|
116
161
|
### Revert semantics
|
|
117
162
|
|
|
@@ -190,9 +190,9 @@ For the fan-out + worktree + patch-integration + conflict mechanics, see `dispat
|
|
|
190
190
|
|
|
191
191
|
0. Call `phase_tracker({ action: "start", phase: "verify" })`. (The `implement` phase was started at execution start and auto-completes from `plan_tracker` once all tasks are done; this flow runs its own verify gate instead of `/skill:verification-before-completion`, so it must mark verify itself.)
|
|
192
192
|
1. **Run the whole-diff code review.** Dispatch `/skill:requesting-code-review` against the worktree's full diff vs `main` (already covered in [The Process](#the-process) step "After all tasks"). Address Critical and Moderate findings before handoff. (Consumers wanting an in-flow project-specific audit re-add it as an explicit step in `.pi/gauntlet-overrides.md`, or run `/self-audit` manually.)
|
|
193
|
-
2. **Close the loop — conformance check.** The review in step 1 is plan-vs-code (single-step); it inherits any requirement the plan already dropped. Before marking verify complete, dispatch a fresh-context **`conformance-reviewer`** — its **own** dispatch, never fused into the step-1 review — to confront the deliverable (code **and** docs) against the *origin* — the spec **and** the original prompt — per `verification-before-completion/reference/conformance-check.md`. Pass the spec path, the verbatim original prompt, and the full diff.
|
|
194
|
-
3. Summarize what was implemented (tasks completed, files changed, test counts, code-review verdict). Emit the `## Closure / conformance` block
|
|
195
|
-
4. **Proceed to finishing — no confirmation prompt.** Once verify is complete per step 2's criterion, invoke `/skill:finishing-a-development-branch` immediately. Its Step 4 menu (squash / PR / keep / discard) is the human gate; a separate "ready to finish?" prompt only stacks a second stop in front of it. Carried-open
|
|
193
|
+
2. **Close the loop — conformance check.** The review in step 1 is plan-vs-code (single-step); it inherits any requirement the plan already dropped. Before marking verify complete, dispatch a fresh-context **`conformance-reviewer`** — its **own** dispatch, never fused into the step-1 review — to confront the deliverable (code **and** docs) against the *origin* — the spec **and** the original prompt — per `verification-before-completion/reference/conformance-check.md`. Pass the spec path, the verbatim original prompt, and the full diff. Follow that reference for the partition rule, concern decomposition, and fix-loop mechanics; do not reimplement them here. The fix loop may drive `plan_tracker` to surface fix-wave progress (task name = `Gn: <short desc>`, lifecycle `pending → in_progress → complete` per gap); it never calls `phase_tracker`. Call `phase_tracker({ action: "complete", phase: "verify" })` only when the reference says the handoff is durably complete: either a current `CONFORMS` result, or a current `## Closure / conformance` inventory whose carried-open concerns all come from valid deferred gaps, including `recommended: fix` gaps carried open because a declared precondition made the fix loop unavailable (`maxFixRounds: 0`, or no eligible named-branch worktree). A started positive-cap fix loop that blocks, fails, or exhausts its rounds with an open `fix` gap is escalation, not completion; on escalation, do not complete verify, stop and report.
|
|
194
|
+
3. Summarize what was implemented (tasks completed, files changed, test counts, code-review verdict). Emit the `## Closure / conformance` block exactly as defined in `verification-before-completion/reference/conformance-check.md`: it must open with the two-line sentinel (`status: CONFORMS (0 open)` or `status: GAPS (N open)`, then `audited-base: <full HEAD SHA>`), then carry the exact durable concern schema by reference with no renamed or reformatted fields. `finishing-a-development-branch` Step 3.5 consumes that block verbatim.
|
|
195
|
+
4. **Proceed to finishing — no confirmation prompt.** Once verify is complete per step 2's criterion, invoke `/skill:finishing-a-development-branch` immediately. Its Step 4 menu (squash / PR / keep / discard) is the human gate; a separate "ready to finish?" prompt only stacks a second stop in front of it. Carried-open concerns are resolved there per concern via the `## Closure / conformance` block from step 3. Manual testing is a follow-up after the finishing choice (on `<base-branch>` after a squash-merge, or on the PR branch), never a reason to hold this gate.
|
|
196
196
|
|
|
197
197
|
## Red Flags — STOP
|
|
198
198
|
|
|
@@ -57,7 +57,7 @@ Skip any step = lying, not verifying
|
|
|
57
57
|
| Regression test works | Red-green cycle verified | Test passes once |
|
|
58
58
|
| Agent completed | VCS diff shows changes | Agent reports "success" |
|
|
59
59
|
| Requirements met | Line-by-line checklist | Tests passing |
|
|
60
|
-
| Intent delivered (loop closed) | Fresh reviewer audits code+doc vs origin spec + prompt; verify completes
|
|
60
|
+
| Intent delivered (loop closed) | Fresh reviewer audits code+doc vs origin spec + prompt; verify completes only on a current `CONFORMS` result or a current durable carried-open closure inventory permitted by `reference/conformance-check.md` (including declared-precondition-unavailable `recommended: fix` gaps). A started positive-cap fix loop that blocks, fails, or exhausts rounds with an open `fix` gap is escalation, not completion. | Plan-vs-code review, tests passing |
|
|
61
61
|
|
|
62
62
|
## Rationalization Prevention
|
|
63
63
|
|
|
@@ -101,17 +101,18 @@ No prompt, no menu: this partition is deterministic and exhaustive.
|
|
|
101
101
|
regardless of its `recommended` value. Never auto-remove or auto-accept
|
|
102
102
|
unrequested code here.
|
|
103
103
|
3. **Every remaining `PARTIAL`/`MISSING`/`DRIFTED` gap**:
|
|
104
|
-
- `recommended: fix` → auto-run the fix loop below — **unless
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
- `recommended: fix` → auto-run the fix loop below — **unless a declared
|
|
105
|
+
fix-loop precondition is unavailable** (`maxFixRounds: 0`, or no eligible
|
|
106
|
+
named-branch worktree), in which case carry the gap **OPEN** (see the fix
|
|
107
|
+
loop's precondition and `maxFixRounds: 0` notes).
|
|
107
108
|
- `recommended: accept` or `recommended: rescope` → carry the gap **OPEN**,
|
|
108
109
|
deferred to the finish gate. Do not apply a spec edit here — the finish
|
|
109
110
|
gate owns disposition of deferred gaps.
|
|
110
111
|
|
|
111
|
-
So the fast path (all gaps `recommended: fix`, none `UNAUTHORIZED`, cap > 0
|
|
112
|
-
therefore auto-runs the fix loop with no menu,
|
|
113
|
-
|
|
114
|
-
run. Record every gap's outcome (`CONFORMS`-closed or carried OPEN) in the
|
|
112
|
+
So the fast path (all gaps `recommended: fix`, none `UNAUTHORIZED`, cap > 0,
|
|
113
|
+
eligible named-branch worktree) therefore auto-runs the fix loop with no menu,
|
|
114
|
+
stop, or confirmation; any other mix carries the
|
|
115
|
+
`accept`/`rescope`/`UNAUTHORIZED` gaps OPEN while the `fix` gaps run. Record every gap's outcome (`CONFORMS`-closed or carried OPEN) in the
|
|
115
116
|
`## Closure / conformance` block (schema below).
|
|
116
117
|
|
|
117
118
|
**Re-partition after every re-audit.** A re-audit can introduce `Gn+1` or flip a
|
|
@@ -130,8 +131,10 @@ Only the fan-out/integrate/review shape and `plan_tracker` are reused.
|
|
|
130
131
|
**Precondition — worktree required.** The loop needs a worktree HEAD to branch
|
|
131
132
|
fixes from. On the ad-hoc `finishing-a-development-branch` paths that run in a
|
|
132
133
|
normal repo (`GIT_DIR == GIT_COMMON`) or detached HEAD, there is no such HEAD:
|
|
133
|
-
skip this loop, carry every `fix` gap OPEN, and resolve
|
|
134
|
-
|
|
134
|
+
skip this loop, carry every `fix` gap OPEN, and resolve it at finish via the
|
|
135
|
+
canonical Disposition catalog and availability table below. `fix-now` is
|
|
136
|
+
unavailable there; any other disposition is offered only when its table
|
|
137
|
+
prerequisites hold.
|
|
135
138
|
|
|
136
139
|
Per round:
|
|
137
140
|
|
|
@@ -165,10 +168,10 @@ Per round:
|
|
|
165
168
|
including DELIVERED rows and their `evidence` `file:line`) and the round's
|
|
166
169
|
fix diff. Inject `model:` call-site per `gauntlet_setting({ key:
|
|
167
170
|
"closureReview" }).model` — same mechanism as the initial audit; omit
|
|
168
|
-
`model:` when it is `undefined` to inherit the parent's model.
|
|
169
|
-
phase-tracker closure guard blocks a dispatch
|
|
170
|
-
`closureReview.model` is set, and warns (non-blocking)
|
|
171
|
-
differs.
|
|
171
|
+
`model:` when it is `undefined` to inherit the parent's model. Inside a
|
|
172
|
+
brainstorming-entered flow, the phase-tracker closure guard blocks a dispatch
|
|
173
|
+
that omits `model:` when `closureReview.model` is set, and warns (non-blocking)
|
|
174
|
+
on one whose model differs.
|
|
172
175
|
7. **Converge or continue**: verdict `CONFORMS` → record it, done. Open gaps
|
|
173
176
|
within the cap → re-partition (per the rule above) and start the next
|
|
174
177
|
round. Cap (`gauntlet_setting({ key: "closureReview" }).maxFixRounds`,
|
|
@@ -203,31 +206,262 @@ per-gap dispatch against the **gap block**, not a plan task:
|
|
|
203
206
|
|
|
204
207
|
This is a task-framing contract in the dispatch, not a new persona.
|
|
205
208
|
|
|
209
|
+
## Concern decomposition
|
|
210
|
+
|
|
211
|
+
The main verification orchestrator — **not** `conformance-reviewer` — decomposes
|
|
212
|
+
each carried-open gap into concerns when it writes the completion summary. A
|
|
213
|
+
**gap** is the reviewer's overall finding; a **concern** (`Gn/Cn`) is one
|
|
214
|
+
consequence within it that could reasonably receive a *different* disposition.
|
|
215
|
+
Observations that necessarily move together stay one concern.
|
|
216
|
+
|
|
217
|
+
The orchestrator reasons from:
|
|
218
|
+
|
|
219
|
+
- The final reviewer coverage row and structured gap block.
|
|
220
|
+
- The origin spec and original prompt already supplied to verification.
|
|
221
|
+
- Session-observed blockers, prerequisite checks, and fix-loop outcomes.
|
|
222
|
+
- The round history and current touched-file/resource ownership.
|
|
223
|
+
|
|
224
|
+
Per gap, apply these rules **in order**:
|
|
225
|
+
|
|
226
|
+
1. Extract atomic unmet clauses and remediation actions from `origin`,
|
|
227
|
+
`evidence`, and `remediation`. Add a session-observed blocker **only** when a
|
|
228
|
+
tool result or fix-loop outcome established it.
|
|
229
|
+
2. **Split** two points when either could be fixed, accepted, rescoped, or
|
|
230
|
+
followed up without imposing the same disposition on the other.
|
|
231
|
+
3. **Keep together** when one action is meaningful only with the other, or no
|
|
232
|
+
different disposition could be executed independently.
|
|
233
|
+
4. Map **every** unmet clause, remediation action, and established blocker to at
|
|
234
|
+
least one concern. Do **not** add a requirement absent from the origin or
|
|
235
|
+
prompt — invent nothing.
|
|
236
|
+
5. Order concerns by first appearance in the origin/remediation and assign
|
|
237
|
+
`C1`, `C2`, and so on (source order).
|
|
238
|
+
|
|
239
|
+
A valid decomposition has no omitted source point, no invented requirement or
|
|
240
|
+
evidence, and a concern-scoped remediation plus ownership estimate for every
|
|
241
|
+
`fix-now` candidate. Shared evidence may appear in multiple concerns. When the
|
|
242
|
+
source cannot support a safe split, the fallback is **one indivisible `Gn/C1`**
|
|
243
|
+
concern carrying the complete gap and the known blocker; decomposition never
|
|
244
|
+
blocks on obtaining richer prose from the unchanged reviewer.
|
|
245
|
+
|
|
246
|
+
Field derivation is explicit: `title` and `unresolved` summarize the unmet
|
|
247
|
+
clause; `origin` narrows the gap `origin` to this concern's specific clause
|
|
248
|
+
without inventing a new requirement; `remediation` states the concern-scoped
|
|
249
|
+
remediation action drawn from the gap `remediation`; `impact` states the
|
|
250
|
+
consequence already implied by the origin requirement; `evidence` copies
|
|
251
|
+
reviewer evidence plus verified session observations;
|
|
252
|
+
`touched-files`/`touched-resources` narrow the gap ownership where supported,
|
|
253
|
+
otherwise `unknown`/`none`. For an `UNAUTHORIZED` concern there is no origin
|
|
254
|
+
requirement to narrow: keep `origin: none (scope creep)` verbatim — never invent
|
|
255
|
+
an origin clause; derive `unresolved` from the reviewer's `evidence`/`remediation`
|
|
256
|
+
as a plain description of the unrequested behavior; and derive `impact` from the
|
|
257
|
+
consequence of retaining, removing, or ratifying that behavior, not from an origin
|
|
258
|
+
requirement. These per-concern fields persist the exact contract
|
|
259
|
+
the concern-scoped fix projection consumes after pruning. **`evidence: absent`
|
|
260
|
+
is an unmet-delivery fact, not
|
|
261
|
+
an external blocker** — never relabel missing implementation evidence as a
|
|
262
|
+
blocker. A malformed structured reviewer gap block — missing its stable `Gn`
|
|
263
|
+
label or any required field (`verdict`, `origin`, `evidence`, `remediation`,
|
|
264
|
+
`touched-files`, `touched-resources`, `recommended`) — triggers a **fresh
|
|
265
|
+
audit**; a complete structured reviewer gap block does not — the orchestrator
|
|
266
|
+
decomposes it or emits the indivisible fallback.
|
|
267
|
+
|
|
268
|
+
## Disposition catalog and availability
|
|
269
|
+
|
|
270
|
+
Each concern lists **every** supported disposition, its concrete effect, and
|
|
271
|
+
current availability. This table is the single availability contract, referenced
|
|
272
|
+
by both this verify gate and `finishing-a-development-branch`:
|
|
273
|
+
|
|
274
|
+
|Disposition|Ordinary gap effect|`UNAUTHORIZED` effect|Available when|
|
|
275
|
+
|---|---|---|---|
|
|
276
|
+
|`fix-now`|Complete missing implementation or validation.|Remove the unrequested code or behavior.|Named-branch worktree; `maxFixRounds > 0`; concern ownership is known; every required local/external resource is accessible.|
|
|
277
|
+
|`accept-into-spec`|Ratify intentional implemented behavior when the written contract is stale.|Ratify the unrequested behavior as approved scope.|A writable spec exists and there is concrete behavior to ratify.|
|
|
278
|
+
|`rescope-into-spec`|Explicitly remove or defer the origin requirement from this workflow.|Unavailable: scope creep has no origin requirement to defer.|The verdict is not `UNAUTHORIZED` and a writable spec exists.|
|
|
279
|
+
|`follow-up`|Keep the concern valid but transfer it to separately owned work.|Transfer a separately valid decision or removal task.|The concern can stand alone and the project defines an executable issue-tracker convention. Without one, mark unavailable and direct the user to `custom` to name another durable owner.|
|
|
280
|
+
|`custom`|Execute another user-defined disposition after its effect is clarified.|Same.|Always visible; executable only after the user supplies a concrete effect. It is never an automatic recommendation.|
|
|
281
|
+
|
|
282
|
+
A failed condition leaves the disposition visible and names the exact missing
|
|
283
|
+
prerequisite. `touched-files: unknown` makes `fix-now` unavailable until
|
|
284
|
+
ownership is established. A normal checkout (`GIT_DIR == GIT_COMMON`) or detached
|
|
285
|
+
HEAD cannot dispatch the isolated fix loop at all - it has no named-branch
|
|
286
|
+
worktree to branch fixes from - so `fix-now` stays unavailable there regardless
|
|
287
|
+
of ownership; resolve those concerns manually at finish. `maxFixRounds: 0`
|
|
288
|
+
(audit-only) likewise leaves `fix-now` visible but unavailable — the user
|
|
289
|
+
configured no auto-fix loop, and finish-time selection never bypasses or resets
|
|
290
|
+
that cap; a concrete `custom`/manual-fix disposition remains possible. `UNAUTHORIZED` cards
|
|
291
|
+
replace the primary question with
|
|
292
|
+
`Should this unrequested behavior become part of the current workflow?`, so
|
|
293
|
+
removal and ratification are not presented as missing-feature choices.
|
|
294
|
+
|
|
295
|
+
Reviewer tokens map to executable concern recommendations:
|
|
296
|
+
|
|
297
|
+
- `fix` maps to `fix-now`; for `UNAUTHORIZED`, that means removal.
|
|
298
|
+
- `accept` maps to `accept-into-spec`; for `UNAUTHORIZED`, that means ratification.
|
|
299
|
+
- `rescope` maps to `rescope-into-spec` and is invalid for `UNAUTHORIZED`.
|
|
300
|
+
- `follow-up` may replace an unavailable `fix-now`/`rescope-into-spec` **only**
|
|
301
|
+
when the concern remains valid, has separate ownership, and the issue-tracker
|
|
302
|
+
action is executable.
|
|
303
|
+
|
|
304
|
+
The orchestrator may choose another named disposition only when current evidence
|
|
305
|
+
supports its stated effect. If no named executable disposition is available, the
|
|
306
|
+
concern serializes `recommended: none` - the durable state for "no named
|
|
307
|
+
executable choice exists" - and its `rationale` must name the exact missing
|
|
308
|
+
prerequisites plus the concrete custom effect still required. `custom` is never
|
|
309
|
+
auto-recommended, so `none` never silently resolves to `custom`.
|
|
310
|
+
|
|
311
|
+
A concern with `recommended: none` is excluded from the recommended set. Before
|
|
312
|
+
any exhaustive `Apply recommended set`, the finish gate asks for a targeted
|
|
313
|
+
custom approval that supplies a concrete effect for that one concern (finishing
|
|
314
|
+
owns the exact reply syntax - a per-item `custom(...)` decision - not an `apply
|
|
315
|
+
recommended` variant). That reply approves only that concrete custom effect for
|
|
316
|
+
that concern. Validate and normalize it immediately: if the effect is ambiguous or
|
|
317
|
+
unexecutable, clarify and keep the concern open; if executable, record it as a
|
|
318
|
+
**pending approved custom decision** and remove that concern from the still-open
|
|
319
|
+
inventory used to build the recommended set. If other open concerns remain,
|
|
320
|
+
render the exhaustive recommended set for them and visibly carry the pending
|
|
321
|
+
approved custom decision through the existing action-order and freshness
|
|
322
|
+
barriers; `apply recommended` approves only the remaining recommendations. If no
|
|
323
|
+
other concerns remain, execute the approved custom directly through the existing
|
|
324
|
+
mechanics. The final disposition record keeps this concern as a `custom` result;
|
|
325
|
+
never relabel it as a model recommendation.
|
|
326
|
+
|
|
327
|
+
**Never recommend an unavailable disposition or an unexecutable `custom`
|
|
328
|
+
placeholder.**
|
|
329
|
+
|
|
330
|
+
`revert` is a separate action, not a generic concern disposition. The commit
|
|
331
|
+
index stays gap-granular (`conformance fix Gn`). A listed gap-level commit makes
|
|
332
|
+
revert visible on every concern card under that gap, with an explicit warning
|
|
333
|
+
that the **entire gap commit** is reverted. Existing light-revert semantics
|
|
334
|
+
remain: revert the indexed commit, re-audit, and return to the decision gate
|
|
335
|
+
only if a concern remains open.
|
|
336
|
+
|
|
206
337
|
## Closure / conformance
|
|
207
338
|
|
|
208
339
|
Emit this block in the verify completion summary. It is the durable handoff
|
|
209
340
|
`finishing-a-development-branch` Step 3.5 consumes — parseable even if session
|
|
210
341
|
context was pruned. Verify completes when every gap is either fixed
|
|
211
|
-
(`CONFORMS`) or carried OPEN as a deferred `accept`/`rescope`/`UNAUTHORIZED
|
|
212
|
-
|
|
213
|
-
|
|
342
|
+
(`CONFORMS`) or carried OPEN as a deferred gap - `accept`/`rescope`/`UNAUTHORIZED`,
|
|
343
|
+
or a `recommended: fix` gap carried open because a declared fix-loop precondition
|
|
344
|
+
was unavailable so the loop never started (`maxFixRounds: 0`, or no eligible
|
|
345
|
+
named-branch worktree - normal checkout / detached HEAD). Escalation - a started
|
|
346
|
+
positive-cap loop that exhausted its rounds or blocked/failed with an open `fix`
|
|
347
|
+
gap - is the one non-completing terminal state; the precondition-unavailable
|
|
348
|
+
carried-open `fix` state is valid closure inventory, not escalation.
|
|
349
|
+
|
|
350
|
+
### Handoff sentinel and freshness anchor - every handoff
|
|
351
|
+
|
|
352
|
+
Every `## Closure / conformance` block - a `CONFORMS` no-card handoff and a
|
|
353
|
+
carried-open GAPS handoff alike - **opens with a two-line sentinel** that lets
|
|
354
|
+
the finish gate re-verify freshness after context pruning, with no session
|
|
355
|
+
history:
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
status: CONFORMS (0 open) # or: status: GAPS (N open)
|
|
359
|
+
audited-base: <full HEAD SHA at audit time>
|
|
360
|
+
```
|
|
214
361
|
|
|
215
|
-
|
|
362
|
+
`N` = count of open concerns (decision units), matching the number of emitted
|
|
363
|
+
concern cards. Record `audited-base` as the full 40-char HEAD SHA at audit time;
|
|
364
|
+
never abbreviate. This block is the **single source** for the freshness rule;
|
|
365
|
+
`finishing-a-development-branch` links here rather than restating it.
|
|
216
366
|
|
|
367
|
+
**Freshness rule.** The audit-input rule requires deliverables committed before
|
|
368
|
+
auditing, so `audited-base` captures the audited state; freshness compares that
|
|
369
|
+
commit to the **current working tree**, not to HEAD (a commit-to-commit diff
|
|
370
|
+
misses staged/unstaged edits when HEAD has not moved). Run two cheap commands:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
ROOT=$(git rev-parse --show-toplevel)
|
|
374
|
+
git -C "$ROOT" diff --stat <audited-base> -- . # tracked changes since the audited commit (staged + unstaged)
|
|
375
|
+
git -C "$ROOT" status --porcelain --untracked-files=all # new/untracked deliverables
|
|
217
376
|
```
|
|
218
|
-
|
|
377
|
+
|
|
378
|
+
Any output from either command, any doubt, a missing/mismatched sentinel, or any
|
|
379
|
+
closure block not opening with the two-line sentinel above (e.g. a legacy
|
|
380
|
+
`Gn: PARTIAL - recommended: ...` row) triggers a fresh audit - never infer
|
|
381
|
+
`CONFORMS` from the absence of cards. This is a lightweight freshness check (two
|
|
382
|
+
git commands, no hashing or identity fields).
|
|
383
|
+
|
|
384
|
+
**Sentinel validation.** `status: CONFORMS` requires `N = 0` and **no** emitted
|
|
385
|
+
concern cards; `status: GAPS` requires `N > 0` exactly matching the emitted card
|
|
386
|
+
count (`Gn/Cn` blocks), not merely gap headers. A mismatch is stale -> re-audit.
|
|
387
|
+
|
|
388
|
+
**Audit-time input rule.** Stage or commit untracked deliverables before
|
|
389
|
+
auditing, since `git diff <base> -- .` omits untracked files from the reviewer
|
|
390
|
+
payload.
|
|
391
|
+
|
|
392
|
+
For each carried-open gap, emit a durable gap header plus one card per concern
|
|
393
|
+
(decomposition and derivation rules above). In rendered cards, recommendation
|
|
394
|
+
lists, clarification lists, and final disposition records, every user-facing
|
|
395
|
+
identifier carries both its ID **and** title; bare `Gn`/`Cn` or grouped
|
|
396
|
+
identifier lists without titles are prohibited. The only exceptions are the
|
|
397
|
+
machine/interaction tokens the grammar requires: bare `Gn/Cn` inside typed
|
|
398
|
+
response tokens, the literal `conformance fix Gn` commit/action identifiers, and
|
|
399
|
+
the flat `auto-applied fix commits` index below. The surrounding prompt or list
|
|
400
|
+
must map each token to its titled concern or gap before asking for input.
|
|
401
|
+
|
|
402
|
+
```text
|
|
403
|
+
G1 - Source-image validation is incomplete
|
|
404
|
+
verdict: PARTIAL
|
|
405
|
+
origin: <requirement source and clause>
|
|
406
|
+
evidence: <current file:line or observed state>
|
|
407
|
+
blocker: <specific blocker, or none>
|
|
408
|
+
touched-files: <paths or unknown>
|
|
409
|
+
touched-resources: <resources or none>
|
|
219
410
|
round history: R1 <verdict/action>, R2 <verdict/action>, ...
|
|
411
|
+
|
|
412
|
+
G1/C1 - End-to-end OCR output has not been validated
|
|
413
|
+
unresolved: <plain statement of the concern>
|
|
414
|
+
impact: <why it matters to the current workflow>
|
|
415
|
+
origin: <requirement source and clause, narrowed from the gap origin; or none (scope creep) for UNAUTHORIZED>
|
|
416
|
+
remediation: <concern-scoped remediation action>
|
|
417
|
+
evidence: <concern-specific evidence or blocker>
|
|
418
|
+
touched-files: <concern-scoped paths, narrowed from the gap; or unknown>
|
|
419
|
+
touched-resources: <concern-scoped resources, narrowed from the gap; or none>
|
|
420
|
+
available dispositions:
|
|
421
|
+
fix-now: <effect and availability>
|
|
422
|
+
accept-into-spec: <effect and availability>
|
|
423
|
+
rescope-into-spec: <effect and availability>
|
|
424
|
+
follow-up: <effect and availability>
|
|
425
|
+
custom: <effect and availability>
|
|
426
|
+
recommended: <available named disposition, or none>
|
|
427
|
+
rationale: <why this is the best current choice; for none, the missing prerequisites and the concrete custom effect required>
|
|
220
428
|
```
|
|
221
429
|
|
|
430
|
+
All concerns in a gap must be represented.
|
|
431
|
+
|
|
222
432
|
Then a single flat revert index of **every** `conformance fix Gn` commit the fix
|
|
223
433
|
loop produced this run — including gaps that later converged to `CONFORMS`
|
|
224
|
-
(a closed gap has no
|
|
434
|
+
(a closed gap has no card above, so its commit lives only here) — since the
|
|
225
435
|
finish gate's revert option needs them all:
|
|
226
436
|
|
|
227
437
|
```
|
|
228
438
|
auto-applied fix commits: <Gn: SHA>, <Gm: SHA>, ... (revertable)
|
|
229
439
|
```
|
|
230
440
|
|
|
441
|
+
## Concern-scoped fix projection
|
|
442
|
+
|
|
443
|
+
When the finish gate selects `fix-now` on some concerns of a gap, the fix loop
|
|
444
|
+
above runs against a **projected** contract, not the original whole-gap block.
|
|
445
|
+
For each parent gap with selected `fix-now` concerns, project only those
|
|
446
|
+
concerns into one gap-scoped fix contract:
|
|
447
|
+
|
|
448
|
+
- The parent gap ID.
|
|
449
|
+
- The selected concern IDs and titles.
|
|
450
|
+
- Their origin clauses, concern remediations, and evidence.
|
|
451
|
+
- The **union** of the selected concerns' touched files/resources (ownership
|
|
452
|
+
boundary).
|
|
453
|
+
|
|
454
|
+
Rescoped, accepted, and followed-up sibling concerns are **excluded** from the
|
|
455
|
+
projection. The `implementer` and the pre-integration `spec-reviewer` receive
|
|
456
|
+
this projected contract in place of the original whole-gap block.
|
|
457
|
+
|
|
458
|
+
The projected task runs the existing full loop above: it retains the gap-level
|
|
459
|
+
`conformance fix Gn` commit name, reruns the project's tests, runs
|
|
460
|
+
`code-reviewer`, re-audits against the amended spec, and reenters the gate only
|
|
461
|
+
if concerns remain. Gap-level revert stays available through the flat commit
|
|
462
|
+
index. The gate records the final result by concern ID and title before showing
|
|
463
|
+
branch integration options.
|
|
464
|
+
|
|
231
465
|
## Checklist
|
|
232
466
|
|
|
233
467
|
- [ ] Located canonical requirements (spec → prompt → ticket fallback)
|