ai-fob 1.7.1 → 1.7.2
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/assets/pi/extensions/task-state/reconcile.ts +9 -157
- package/assets/pi/prompts/build-phase-pi.md +65 -38
- package/assets/pi/skills/phase-build-workflow/SKILL.md +5 -8
- package/assets/pi/skills/phase-build-workflow/references/resume-reconciliation-table.md +35 -95
- package/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -27,47 +27,14 @@ export type ReconcileAction = {
|
|
|
27
27
|
reason: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Per-step resume verdict — contract with
|
|
32
|
-
* `references/resume-reconciliation-table.md` §1/§2/§4/§5. The recipe
|
|
33
|
-
* (`prompts/build-phase-pi.md`) gates STATE.md mutation on the
|
|
34
|
-
* `requires_confirmation` / `reset` cases.
|
|
35
|
-
*/
|
|
36
|
-
export type StepVerdict =
|
|
37
|
-
| "valid"
|
|
38
|
-
| "valid_lenient"
|
|
39
|
-
| "stale"
|
|
40
|
-
| "missing"
|
|
41
|
-
| "reset"
|
|
42
|
-
| "requires_confirmation";
|
|
43
|
-
|
|
44
|
-
export type StepReconcile = {
|
|
45
|
-
n: number; // 0..6
|
|
46
|
-
marker: "[ ]" | "[~]" | "[x]";
|
|
47
|
-
artifact: string; // canonical artifact path the matrix names
|
|
48
|
-
verdict: StepVerdict;
|
|
49
|
-
reason?: string; // human-readable; surfaced in renderResult + recipe confirmation prompt
|
|
50
|
-
};
|
|
51
|
-
|
|
52
30
|
export type ReconcileResult = {
|
|
53
31
|
scan: ArtifactScan;
|
|
54
|
-
entryStep: number; // 0..6
|
|
32
|
+
entryStep: number; // 0..6
|
|
55
33
|
actions: ReconcileAction[];
|
|
56
34
|
specialCases: string[];
|
|
57
35
|
table: string; // expanded cross-reference table (markdown)
|
|
58
|
-
// Additive: per-step verdict array and deterministic resume-step integer.
|
|
59
|
-
steps: StepReconcile[];
|
|
60
|
-
resumeAt: number; // 0..6 — first step whose verdict is NOT in {valid, valid_lenient}
|
|
61
36
|
};
|
|
62
37
|
|
|
63
|
-
// Internal classification — separates on-disk absence from content-check
|
|
64
|
-
// failure so decideVerdict() can map (marker, classification) ⇒ StepVerdict.
|
|
65
|
-
type ArtifactClassification =
|
|
66
|
-
| { kind: "valid" }
|
|
67
|
-
| { kind: "valid_lenient"; reason: string }
|
|
68
|
-
| { kind: "stale"; reason: string }
|
|
69
|
-
| { kind: "missing"; reason: string };
|
|
70
|
-
|
|
71
38
|
// Canonical 4-value Step 5 result vocabulary — see
|
|
72
39
|
// .pi/skills/phase-build-workflow/references/result-vocabulary.md. Mirrors
|
|
73
40
|
// state-md.ts's STEP5_COMPLETE_RESULTS exactly (Coordination Note 6 of the
|
|
@@ -262,28 +229,13 @@ export async function reconcile(
|
|
|
262
229
|
const scan = await scanArtifacts(cwd, task, phase, onProgress);
|
|
263
230
|
const stateMarkers = readStepMarkers(stateText, task, phase);
|
|
264
231
|
const actions: ReconcileAction[] = [];
|
|
265
|
-
const steps: StepReconcile[] = [];
|
|
266
232
|
|
|
267
233
|
// Walk steps 0..6 deciding per matrix in resume-reconciliation-table.md §2.
|
|
268
234
|
for (let step = 0; step <= 6; step++) {
|
|
269
235
|
const marker = stateMarkers[step] ?? " ";
|
|
270
236
|
const artifact = primaryArtifactStatus(scan, step);
|
|
271
|
-
const
|
|
272
|
-
actions.push({ step, action:
|
|
273
|
-
|
|
274
|
-
// Per-step verdict (additive, recipe-facing). Classification keeps the
|
|
275
|
-
// file-on-disk check (missing) separate from the content-check-miss case
|
|
276
|
-
// (valid_lenient / stale), so the recipe can gate STATE.md mutation on
|
|
277
|
-
// requires_confirmation/reset without blindly cascading a reset.
|
|
278
|
-
const classification = classifyStep(scan, step);
|
|
279
|
-
const verdict = decideVerdict(marker, classification);
|
|
280
|
-
steps.push({
|
|
281
|
-
n: step,
|
|
282
|
-
marker: `[${marker}]` as "[ ]" | "[~]" | "[x]",
|
|
283
|
-
artifact: primaryArtifactName(step),
|
|
284
|
-
verdict: verdict.verdict,
|
|
285
|
-
reason: verdict.reason,
|
|
286
|
-
});
|
|
237
|
+
const verdict = decide(marker, artifact);
|
|
238
|
+
actions.push({ step, action: verdict.action, reason: verdict.reason });
|
|
287
239
|
}
|
|
288
240
|
|
|
289
241
|
// Special cases §3.1–3.4 — fold into actions/specialCases list.
|
|
@@ -312,27 +264,14 @@ export async function reconcile(
|
|
|
312
264
|
specialCases.push("Interrupted Step 4 (§3.3): post-build-sha pending.");
|
|
313
265
|
}
|
|
314
266
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
let resumeAt = -1;
|
|
321
|
-
for (const s of steps) {
|
|
322
|
-
if (s.verdict !== "valid" && s.verdict !== "valid_lenient") {
|
|
323
|
-
resumeAt = s.n;
|
|
267
|
+
// Entry step — first step whose final action is "run" or "reset".
|
|
268
|
+
let entryStep = 6;
|
|
269
|
+
for (const a of actions) {
|
|
270
|
+
if (a.action === "run" || a.action === "reset") {
|
|
271
|
+
entryStep = a.step;
|
|
324
272
|
break;
|
|
325
273
|
}
|
|
326
274
|
}
|
|
327
|
-
if (resumeAt < 0) {
|
|
328
|
-
// All steps valid/valid_lenient — find the last [x] and advance.
|
|
329
|
-
let lastCompleted = -1;
|
|
330
|
-
for (const s of steps) {
|
|
331
|
-
if (s.marker === "[x]") lastCompleted = s.n;
|
|
332
|
-
}
|
|
333
|
-
resumeAt = Math.min(6, lastCompleted + 1);
|
|
334
|
-
}
|
|
335
|
-
const entryStep = resumeAt;
|
|
336
275
|
|
|
337
276
|
const tableHead =
|
|
338
277
|
"| step | marker | artifact | action | reason |\n| ---- | ------ | -------- | ------ | ------ |";
|
|
@@ -350,8 +289,6 @@ export async function reconcile(
|
|
|
350
289
|
actions,
|
|
351
290
|
specialCases,
|
|
352
291
|
table: `${tableHead}\n${tableRows}`,
|
|
353
|
-
steps,
|
|
354
|
-
resumeAt,
|
|
355
292
|
};
|
|
356
293
|
}
|
|
357
294
|
|
|
@@ -372,17 +309,8 @@ function primaryArtifactStatus(scan: ArtifactScan, step: number): ArtifactStatus
|
|
|
372
309
|
if (stepEntries.some((e) => e.status === "invalid")) return "invalid";
|
|
373
310
|
return "missing";
|
|
374
311
|
}
|
|
375
|
-
// Step 1 has two canonical entries (`explorer_findings.md` required +
|
|
376
|
-
// `docs_research.md` optional companion). Mirror Step 4's OR-pattern so a
|
|
377
|
-
// present-on-disk explorer artifact reports Step 1 as "present" even if the
|
|
378
|
-
// optional docs companion is absent (kanban-core regression facet).
|
|
379
|
-
if (step === 1) {
|
|
380
|
-
const stepEntries = scan.entries.filter((e) => e.step === 1);
|
|
381
|
-
if (stepEntries.some((e) => e.status === "present")) return "present";
|
|
382
|
-
if (stepEntries.some((e) => e.status === "invalid")) return "invalid";
|
|
383
|
-
return "missing";
|
|
384
|
-
}
|
|
385
312
|
const PRIMARY: Record<number, string> = {
|
|
313
|
+
1: "explorer_findings.md",
|
|
386
314
|
2: "plan_V1.md",
|
|
387
315
|
3: "plan_validation_report.md",
|
|
388
316
|
5: "build_validation_report.md",
|
|
@@ -392,82 +320,6 @@ function primaryArtifactStatus(scan: ArtifactScan, step: number): ArtifactStatus
|
|
|
392
320
|
return e?.status ?? "missing";
|
|
393
321
|
}
|
|
394
322
|
|
|
395
|
-
/** Canonical artifact name surfaced in details.steps[].artifact. */
|
|
396
|
-
function primaryArtifactName(step: number): string {
|
|
397
|
-
if (step === 0) return "(none — Step 0 has no artifact)";
|
|
398
|
-
const NAMES: Record<number, string> = {
|
|
399
|
-
1: "explorer_findings.md",
|
|
400
|
-
2: "plan_V1.md",
|
|
401
|
-
3: "plan_validation_report.md",
|
|
402
|
-
4: "build_report.md",
|
|
403
|
-
5: "build_validation_report.md",
|
|
404
|
-
6: "phase_completion_report.md",
|
|
405
|
-
};
|
|
406
|
-
return NAMES[step] ?? "";
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Map ArtifactStatus → ArtifactClassification. Bug-fix behaviour: a
|
|
410
|
-
// present-on-disk artifact that failed a content check is `valid_lenient`
|
|
411
|
-
// (non-fatal); escalated to `stale` only on STRUCTURAL frontmatter mismatch.
|
|
412
|
-
function classifyStep(scan: ArtifactScan, step: number): ArtifactClassification {
|
|
413
|
-
if (step === 0) return { kind: "valid" };
|
|
414
|
-
const status = primaryArtifactStatus(scan, step);
|
|
415
|
-
if (status === "present") return { kind: "valid" };
|
|
416
|
-
// Find a representative entry to source the reason string and decide
|
|
417
|
-
// lenient vs stale.
|
|
418
|
-
const stepEntries = scan.entries.filter((e) => e.step === step);
|
|
419
|
-
if (status === "missing") {
|
|
420
|
-
const reason =
|
|
421
|
-
stepEntries.find((e) => e.status === "missing")?.reason ??
|
|
422
|
-
`${primaryArtifactName(step)} not present on disk`;
|
|
423
|
-
return { kind: "missing", reason };
|
|
424
|
-
}
|
|
425
|
-
// status === "invalid" — file exists, content check missed.
|
|
426
|
-
const invalidEntry = stepEntries.find((e) => e.status === "invalid");
|
|
427
|
-
const reason = invalidEntry?.reason ?? "content check failed";
|
|
428
|
-
// Heuristic: structural frontmatter mismatches escalate to `stale`
|
|
429
|
-
// (requires_confirmation); coarse line-count or schema-version drift is
|
|
430
|
-
// `valid_lenient` (non-fatal, no reset).
|
|
431
|
-
const structural =
|
|
432
|
-
/unparseable frontmatter|missing frontmatter|missing checks-passed|not in pass/.test(reason);
|
|
433
|
-
if (structural) return { kind: "stale", reason };
|
|
434
|
-
return { kind: "valid_lenient", reason };
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Cross-reference matrix from references/resume-reconciliation-table.md §2.
|
|
438
|
-
// Validity is DATA — never throws.
|
|
439
|
-
function decideVerdict(
|
|
440
|
-
marker: " " | "~" | "x",
|
|
441
|
-
status: ArtifactClassification,
|
|
442
|
-
): { verdict: StepVerdict; reason?: string } {
|
|
443
|
-
if (marker === "x") {
|
|
444
|
-
// [x] + valid ⇒ valid (skip)
|
|
445
|
-
// [x] + valid_lenient ⇒ valid_lenient (continue past; no reset)
|
|
446
|
-
// [x] + stale ⇒ requires_confirmation (gated reset)
|
|
447
|
-
// [x] + missing ⇒ requires_confirmation (gated reset)
|
|
448
|
-
if (status.kind === "valid") return { verdict: "valid" };
|
|
449
|
-
if (status.kind === "valid_lenient")
|
|
450
|
-
return { verdict: "valid_lenient", reason: status.reason };
|
|
451
|
-
return { verdict: "requires_confirmation", reason: status.reason };
|
|
452
|
-
}
|
|
453
|
-
if (marker === "~") {
|
|
454
|
-
// [~] markers are always re-runnable; classify as `reset` so the recipe
|
|
455
|
-
// can re-run without prompting for in-progress crashes.
|
|
456
|
-
if (status.kind === "valid")
|
|
457
|
-
return { verdict: "reset", reason: "crash before STATE.md updated; re-run" };
|
|
458
|
-
return { verdict: "reset", reason: status.reason ?? "mid-execution interrupt" };
|
|
459
|
-
}
|
|
460
|
-
// marker === " ": untouched semantic — this row is not the bug. If the
|
|
461
|
-
// artifact exists, mark valid (recipe promotes via existing actions[]);
|
|
462
|
-
// otherwise this is the resume entry point.
|
|
463
|
-
if (status.kind === "valid") return { verdict: "valid" };
|
|
464
|
-
if (status.kind === "valid_lenient")
|
|
465
|
-
return { verdict: "valid_lenient", reason: status.reason };
|
|
466
|
-
if (status.kind === "stale")
|
|
467
|
-
return { verdict: "stale", reason: status.reason };
|
|
468
|
-
return { verdict: "missing", reason: status.reason };
|
|
469
|
-
}
|
|
470
|
-
|
|
471
323
|
function decide(
|
|
472
324
|
marker: " " | "~" | "x",
|
|
473
325
|
status: ArtifactStatus,
|
|
@@ -29,45 +29,73 @@ command with no arguments on its own line:
|
|
|
29
29
|
4. `/skill:agent-browser` — Browser Tool Constraint (NEVER macOS `open`;
|
|
30
30
|
ALWAYS `agent-browser open`).
|
|
31
31
|
|
|
32
|
-
Derive identifiers from $1:
|
|
33
|
-
|
|
34
|
-
`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
Derive identifiers from $1:
|
|
33
|
+
- `SPEC_DIR` = `dirname($1)` — the actual on-disk directory, numeric prefix
|
|
34
|
+
preserved (e.g. `specs/01_kanban-core-ui-schema/HL_PLAN.md` ⇒
|
|
35
|
+
`specs/01_kanban-core-ui-schema/`).
|
|
36
|
+
- `task` (slug) = basename of `SPEC_DIR` minus the leading `NN_` prefix
|
|
37
|
+
(e.g. `01_kanban-core-ui-schema` ⇒ `kanban-core-ui-schema`). The slug
|
|
38
|
+
is what STATE.md uses internally.
|
|
39
|
+
- `PHASE_DIR` = `${SPEC_DIR}/phase-$2/` — where your artifacts live.
|
|
40
|
+
- `phaseKebab` = the HL plan's phase-$2 section heading (lowercase,
|
|
41
|
+
hyphenated). If any of these is unavailable, ask the user.
|
|
42
|
+
|
|
43
|
+
Scaffold the Phase block in STATE.md if needed (idempotent — leaves any
|
|
44
|
+
existing Phase block intact):
|
|
38
45
|
|
|
39
46
|
```json
|
|
40
|
-
{ "operation": "mark_step_start", "task": "<slug>", "phase": $2, "step": 0 }
|
|
41
47
|
{ "operation": "init_phase", "task": "<slug>", "phase": $2, "phaseKebab": "<derived>" }
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Resume detection — YOU do it, not a tool.** Trust your eyes over any
|
|
51
|
+
classifier. Read `specs/STATE.md` and inspect `PHASE_DIR` on disk:
|
|
52
|
+
|
|
53
|
+
1. Read `specs/STATE.md`; locate the `Phase $2` block under your task. Note
|
|
54
|
+
which Step markers are `[x]`, `[~]`, or `[ ]`.
|
|
55
|
+
2. List `PHASE_DIR` (use the `bash` tool: `ls -la PHASE_DIR`). Note which
|
|
56
|
+
files are present.
|
|
57
|
+
3. Canonical artifact ↔ Step mapping (from `phase-build-workflow` skill):
|
|
58
|
+
Step 1 → `explorer_findings.md` AND `docs_research.md`
|
|
59
|
+
Step 2 → `plan_V1.md`
|
|
60
|
+
Step 3 → `plan_validation_report.md`
|
|
61
|
+
Step 4 → `build_report.md`
|
|
62
|
+
Step 5 → `build_validation_report.md`
|
|
63
|
+
Step 6 → `phase_completion_report.md`
|
|
64
|
+
4. Compute the resume entry step (`ENTRY_STEP`) as the lowest-numbered
|
|
65
|
+
step that is NOT already complete. For each step 1..6, "complete"
|
|
66
|
+
means BOTH:
|
|
67
|
+
- STATE.md marker is `[x]`, AND
|
|
68
|
+
- the canonical artifact(s) for that step are present in `PHASE_DIR`.
|
|
69
|
+
5. Disagreement handling:
|
|
70
|
+
- STATE `[x]` AND artifact present → the step is done. Skip past it.
|
|
71
|
+
Do NOT re-run it. Do NOT modify its marker.
|
|
72
|
+
- STATE `[x]` AND artifact missing on disk → STOP and ask the user.
|
|
73
|
+
Show them the inconsistency (which step, which file expected,
|
|
74
|
+
directory listing). Do NOT silently reset markers.
|
|
75
|
+
- STATE `[~]` or `[ ]` → that's the `ENTRY_STEP`.
|
|
76
|
+
6. Echo a one-line banner: `Resuming at Step <ENTRY_STEP> — Steps 1..<ENTRY_STEP-1> verified complete (STATE markers [x] AND artifacts present in PHASE_DIR).`
|
|
77
|
+
Then echo `$@` for any user context.
|
|
78
|
+
|
|
79
|
+
**Step 0 marker handling — do not touch STATE on a resume.** If
|
|
80
|
+
`ENTRY_STEP > 1` (this is a resume from a prior run), Step 0 was already
|
|
81
|
+
completed previously — leave its marker `[x]` alone and proceed directly
|
|
82
|
+
to `ENTRY_STEP`. Do NOT call `mark_step_start` or `mark_step_complete`
|
|
83
|
+
for Step 0.
|
|
84
|
+
|
|
85
|
+
If `ENTRY_STEP == 1` (this is a fresh phase, Steps 1..6 all `[ ]`), bracket
|
|
86
|
+
this Step 0 preparation with the usual markers:
|
|
66
87
|
|
|
67
88
|
```json
|
|
89
|
+
{ "operation": "mark_step_start", "task": "<slug>", "phase": $2, "step": 0 }
|
|
68
90
|
{ "operation": "mark_step_complete", "task": "<slug>", "phase": $2, "step": 0 }
|
|
69
91
|
```
|
|
70
92
|
|
|
93
|
+
(The legacy `state.reconcile` operation still exists in the `task-state`
|
|
94
|
+
extension for diagnostic use, but is NO LONGER CONSULTED for resume.
|
|
95
|
+
Tool-codified verdicts proved less reliable than direct inspection: the
|
|
96
|
+
agent reading STATE.md and listing the phase directory is the source of
|
|
97
|
+
truth.)
|
|
98
|
+
|
|
71
99
|
# Step 1 — Research (parallel: phase-explorer + phase-docs-researcher)
|
|
72
100
|
|
|
73
101
|
Mark step start (step 1) via `state`.
|
|
@@ -872,11 +900,9 @@ summary as your final assistant message. Include:
|
|
|
872
900
|
|
|
873
901
|
Do NOT:
|
|
874
902
|
|
|
875
|
-
- Skip Step 0
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
explicit user confirmation. The reconciler DETERMINES; the recipe gates
|
|
879
|
-
the MUTATION.
|
|
903
|
+
- Skip Step 0 — even on resume you must verify STATE.md ↔ PHASE_DIR
|
|
904
|
+
yourself before proceeding (read STATE.md, list the phase directory,
|
|
905
|
+
decide ENTRY_STEP; do NOT delegate the decision to `state.reconcile`).
|
|
880
906
|
- Bypass either validator — `phase-plan-validator` and `phase-build-validator`
|
|
881
907
|
MUST be called.
|
|
882
908
|
- Continue past 3 fix cycles in either loop — escalate to the user instead.
|
|
@@ -905,5 +931,6 @@ Do NOT:
|
|
|
905
931
|
# Execute this now
|
|
906
932
|
|
|
907
933
|
Start by loading the four skills, then call `state` with
|
|
908
|
-
`operation=init_phase, task=<derived>, phase=$2, phaseKebab=<derived>`,
|
|
909
|
-
`
|
|
934
|
+
`operation=init_phase, task=<derived>, phase=$2, phaseKebab=<derived>`,
|
|
935
|
+
then inspect `specs/STATE.md` and `PHASE_DIR` yourself to identify
|
|
936
|
+
`ENTRY_STEP` (the resume entry), then proceed step by step from there.
|
|
@@ -243,14 +243,11 @@ browser/UI verification check OR the phase has a Frontend domain
|
|
|
243
243
|
## Resume / Reconciliation (summary)
|
|
244
244
|
|
|
245
245
|
On every invocation, the workflow reconciles STATE.md against the on-disk
|
|
246
|
-
artifacts in `PHASE_DIR
|
|
247
|
-
(
|
|
248
|
-
|
|
249
|
-
`
|
|
250
|
-
|
|
251
|
-
and gated on explicit confirmation BEFORE STATE.md is mutated. Full table,
|
|
252
|
-
the per-step artifact validity rules, the resume decision tree, and the
|
|
253
|
-
extension contract live in
|
|
246
|
+
artifacts in `PHASE_DIR` using a six-row decision matrix
|
|
247
|
+
(STATE marker × artifact validity ⇒ action). Special cases handled:
|
|
248
|
+
phase already complete, partial Step 1, interrupted Step 4 with
|
|
249
|
+
`post-build-sha: (pending)`. Full table, the per-step artifact validity
|
|
250
|
+
rules, and the resume decision tree live in
|
|
254
251
|
[references/resume-reconciliation-table.md](references/resume-reconciliation-table.md).
|
|
255
252
|
|
|
256
253
|
## Fix-Loop Budgets
|
|
@@ -12,32 +12,16 @@ A Step is "valid" only when its primary artifact exists on disk AND meets
|
|
|
12
12
|
the line / frontmatter requirements below. Validity is the input to the
|
|
13
13
|
decision matrix in Section 2.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`requires_confirmation` for completed (`[x]`) markers.
|
|
26
|
-
- `missing` — file ABSENT on disk.
|
|
27
|
-
|
|
28
|
-
§2 then maps `(marker, classification)` to one of the user-facing
|
|
29
|
-
verdicts `{valid, valid_lenient, stale, missing, reset, requires_confirmation}`.
|
|
30
|
-
|
|
31
|
-
| Step | Primary Artifact | Validity Rule | Classification |
|
|
32
|
-
|------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------|----------------|
|
|
33
|
-
| 1 | `{PHASE_DIR}/explorer_findings.md` | exists AND > 10 lines | absent ⇒ `missing`; ≤10 lines ⇒ `valid_lenient`; otherwise `valid` |
|
|
34
|
-
| 1 | `{PHASE_DIR}/docs_research.md` (optional companion) | exists AND > 10 lines (absent is acceptable — only required when HL plan cites third-party APIs)| absent ⇒ `missing` (acceptable for Step 1 as long as `explorer_findings.md` is `valid` — Step 1 OR's over its canonical entries); ≤10 lines ⇒ `valid_lenient` |
|
|
35
|
-
| 2 | `{PHASE_DIR}/plan_V1.md` | exists AND > 20 lines AND frontmatter `type: phase-implementation-plan` | absent ⇒ `missing`; ≤20 lines ⇒ `valid_lenient`; missing/wrong frontmatter `type:` ⇒ `stale`; otherwise `valid` |
|
|
36
|
-
| 3 | `{PHASE_DIR}/plan_validation_report.md` | frontmatter `result: pass` AND `checks-passed:` present | absent ⇒ `missing`; missing `checks-passed:` or `result:` not in accepted enum ⇒ `stale`; otherwise `valid` |
|
|
37
|
-
| 4 | `{PHASE_DIR}/build_report.md` (single) | exists AND > 5 lines | absent ⇒ `missing`; ≤5 lines ⇒ `valid_lenient`; otherwise `valid` |
|
|
38
|
-
| 4 | `{PHASE_DIR}/build_report_*.md` (parallel — per domain) | each file exists AND > 5 lines; at least one such file present | Step 4 OR's over single + per-domain entries (any `valid` ⇒ Step 4 `valid`) |
|
|
39
|
-
| 5 | `{PHASE_DIR}/build_validation_report.md` | frontmatter `result: pass` OR `result: fail-asset` AND `checks-passed:` present (see [result-vocabulary.md](result-vocabulary.md)) | (Step 5 row is the result-vocabulary cross-reference — see [result-vocabulary.md](result-vocabulary.md). Do NOT alter this row.) |
|
|
40
|
-
| 6 | `{PHASE_DIR}/phase_completion_report.md` | exists AND > 10 lines AND frontmatter `type: phase-report` | absent ⇒ `missing`; ≤10 lines ⇒ `valid_lenient`; missing/wrong frontmatter `type:` ⇒ `stale`; otherwise `valid` |
|
|
15
|
+
| Step | Primary Artifact | Validity Rule |
|
|
16
|
+
|------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------|
|
|
17
|
+
| 1 | `{PHASE_DIR}/explorer_findings.md` | exists AND > 10 lines |
|
|
18
|
+
| 1 | `{PHASE_DIR}/docs_research.md` (optional companion) | exists AND > 10 lines (absent is acceptable — only required when HL plan cites third-party APIs)|
|
|
19
|
+
| 2 | `{PHASE_DIR}/plan_V1.md` | exists AND > 20 lines AND frontmatter `type: phase-implementation-plan` |
|
|
20
|
+
| 3 | `{PHASE_DIR}/plan_validation_report.md` | frontmatter `result: pass` AND `checks-passed:` present |
|
|
21
|
+
| 4 | `{PHASE_DIR}/build_report.md` (single) | exists AND > 5 lines |
|
|
22
|
+
| 4 | `{PHASE_DIR}/build_report_*.md` (parallel — per domain) | each file exists AND > 5 lines; at least one such file present |
|
|
23
|
+
| 5 | `{PHASE_DIR}/build_validation_report.md` | frontmatter `result: pass` OR `result: fail-asset` AND `checks-passed:` present (see [result-vocabulary.md](result-vocabulary.md)) |
|
|
24
|
+
| 6 | `{PHASE_DIR}/phase_completion_report.md` | exists AND > 10 lines AND frontmatter `type: phase-report` |
|
|
41
25
|
|
|
42
26
|
Notes:
|
|
43
27
|
|
|
@@ -59,29 +43,17 @@ Notes:
|
|
|
59
43
|
## 2. Cross-Reference Reconciliation Matrix
|
|
60
44
|
|
|
61
45
|
For each Step, combine the STATE.md marker with the artifact-validity
|
|
62
|
-
|
|
63
|
-
0 → 6
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `[x]` (completed) |
|
|
69
|
-
| `[
|
|
70
|
-
| `[
|
|
71
|
-
| `[
|
|
72
|
-
| `[
|
|
73
|
-
| `[ ]` (pending) | `valid` | `valid` | Unexpected (artifact present without STATE.md tracking). Warn. Mark `[x]`. Skip. |
|
|
74
|
-
| `[ ]` (pending) | `valid_lenient` | `valid_lenient` | Treat as `valid` for resume; surface the reason once. |
|
|
75
|
-
| `[ ]` (pending) | `stale` | `stale` | This is the resume entry point; the structural mismatch will be re-emitted by the step's writer. |
|
|
76
|
-
| `[ ]` (pending) | `missing` | `missing` | Not started. This step is the resume entry point if no later step is `[x]`. |
|
|
77
|
-
|
|
78
|
-
> Footnote: the pre-repair single-row "`[x]` + invalid/missing ⇒ reset"
|
|
79
|
-
> was the source of the resume-cascade bug (the kanban-core Phase-2
|
|
80
|
-
> regression). This matrix replaces it. See
|
|
81
|
-
> `extensions/task-state/reconcile.ts`'s `decideVerdict()` (formerly
|
|
82
|
-
> `decide()` lines 323–346) for the runtime implementation. Every
|
|
83
|
-
> verdict name in this matrix MUST appear verbatim in `reconcile.ts`'s
|
|
84
|
-
> exported `StepVerdict` type.
|
|
46
|
+
verdict and look up the action below. The recipe applies these row by row
|
|
47
|
+
in step order (0 → 6) at startup.
|
|
48
|
+
|
|
49
|
+
| STATE.md marker | Artifact | Action |
|
|
50
|
+
|---------------------|---------------|---------------------------------------------------------------------------------------------------|
|
|
51
|
+
| `[x]` (completed) | valid | Trust both. Skip the step. |
|
|
52
|
+
| `[x]` (completed) | invalid / missing | STATE.md is wrong. Warn the user. Reset Step to `[ ]`. Re-run it. |
|
|
53
|
+
| `[~]` (in_progress) | valid | Crash occurred after the artifact was written but before STATE.md was updated. Mark `[x]`. Skip. |
|
|
54
|
+
| `[~]` (in_progress) | invalid / missing | Mid-execution interrupt. Reset Step to `[ ]`. Re-run it. |
|
|
55
|
+
| `[ ]` (pending) | valid | Unexpected (artifact present without STATE.md tracking). Warn. Mark `[x]`. Skip. |
|
|
56
|
+
| `[ ]` (pending) | missing | Not started. Run the step normally. |
|
|
85
57
|
|
|
86
58
|
Cascade behaviour: each row's "Mark `[x]`" or "Reset to `[ ]`" goes
|
|
87
59
|
through `task-state.MARK_STEP_COMPLETE` / direct write so that Phase and
|
|
@@ -148,31 +120,13 @@ Once per invocation, after STATE.md is loaded:
|
|
|
148
120
|
2. **Check Phase-already-complete.** If Section 3.1 applies, stop with
|
|
149
121
|
the "phase complete" message.
|
|
150
122
|
3. **Walk Steps 0 → 6 in order.** For each Step:
|
|
151
|
-
- Compute artifact
|
|
152
|
-
- Apply the matrix row (Section 2)
|
|
123
|
+
- Compute artifact validity (Section 1).
|
|
124
|
+
- Apply the matrix row (Section 2) — Skip, Mark `[x]`, or Reset.
|
|
153
125
|
- For Step 1, additionally apply Section 3.2 (partial research).
|
|
154
126
|
- For Step 4, additionally apply Section 3.3 (interrupted build).
|
|
155
|
-
4. **
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
the "everything is fine, advance" semantic). `entryStep` mirrors
|
|
159
|
-
`resumeAt` for legacy consumers.
|
|
160
|
-
5. **User-confirmation gate (mandatory before any STATE.md mutation).**
|
|
161
|
-
If ANY step's verdict ∈ `{requires_confirmation, reset}`, the recipe
|
|
162
|
-
MUST:
|
|
163
|
-
a. Present `details.steps[]` verbatim to the user (use the
|
|
164
|
-
`renderResult` table).
|
|
165
|
-
b. For each `requires_confirmation` entry, ask the user to confirm
|
|
166
|
-
or skip the proposed reset.
|
|
167
|
-
c. ONLY AFTER explicit confirmation may the recipe call
|
|
168
|
-
`state.mark_step_reset` (or equivalent) to mutate STATE.md.
|
|
169
|
-
`reset` verdicts (only emitted for `[~]` markers) do not require user
|
|
170
|
-
confirmation but must still be reported in the same banner.
|
|
171
|
-
6. **Re-execution boundary.** The recipe begins re-execution at
|
|
172
|
-
`resumeAt`. Steps with verdict `valid_lenient` are NOT re-executed
|
|
173
|
-
(their reasons are surfaced once via `renderResult` and the run
|
|
174
|
-
continues).
|
|
175
|
-
7. **Plan / Build aborted-loop re-entry.** If `resumeAt` is Step 3
|
|
127
|
+
4. **Determine entry point.** The first Step whose post-matrix state is
|
|
128
|
+
`[ ]` is the entry point — the recipe begins execution there.
|
|
129
|
+
5. **Plan / Build aborted-loop re-entry.** If the entry point is Step 3
|
|
176
130
|
or Step 5 and the previous validation report exists with a non-passing
|
|
177
131
|
result for its own vocabulary (Step 3: `result: fail`; Step 5:
|
|
178
132
|
`result: fail-code`), apply Section 3.4: start a fresh fix loop from
|
|
@@ -180,34 +134,20 @@ Once per invocation, after STATE.md is loaded:
|
|
|
180
134
|
`result: fail-asset` is NOT an aborted-loop case (Section 3.4 does not
|
|
181
135
|
apply) — it is a closed Step 5; the resume table treats that artifact
|
|
182
136
|
as valid (Section 1).
|
|
183
|
-
|
|
184
|
-
spawned: "Resuming Phase N at Step
|
|
185
|
-
|
|
186
|
-
`requires_confirmation` verdict occurred (per step 5 above).
|
|
137
|
+
6. **Report the resume decision** to the user before any agent is
|
|
138
|
+
spawned: "Resuming Phase N at Step K. Skipped: <list>. Reset:
|
|
139
|
+
<list>." Wait for explicit confirmation only if any Reset occurred.
|
|
187
140
|
|
|
188
141
|
## 5. `task-state` Extension Contract
|
|
189
142
|
|
|
190
143
|
The `task-state` extension (Phase 11) MUST expose, at minimum:
|
|
191
144
|
|
|
192
|
-
- `state.reconcile(phase_dir, phase_number)` — returns
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`{ n, marker, artifact, verdict, reason? }`.
|
|
196
|
-
`verdict ∈ {valid, valid_lenient, stale, missing, reset, requires_confirmation}`
|
|
197
|
-
— every name MUST match `reconcile.ts`'s exported `StepVerdict` type
|
|
198
|
-
byte-for-byte.
|
|
199
|
-
- `details.resumeAt` — integer 0–6; the first step whose verdict is
|
|
200
|
-
NOT in `{valid, valid_lenient}` (else `lastCompletedStep + 1`).
|
|
201
|
-
- Preserved fields (legacy consumers): `entryStep` (mirrors
|
|
202
|
-
`resumeAt`), `actions[]`, `scan`, `specialCases`, `table`.
|
|
145
|
+
- `state.reconcile(phase_dir, phase_number)` — returns the resume entry
|
|
146
|
+
point (an integer 0-6) AND a structured summary of every Skip / Mark /
|
|
147
|
+
Reset decision applied.
|
|
203
148
|
- The matrix and the special-case rules above are the spec; the
|
|
204
149
|
extension MUST NOT introduce additional resume heuristics without
|
|
205
|
-
updating this file.
|
|
206
|
-
`reconcile.ts`'s `decideVerdict()` in the SAME change set.
|
|
150
|
+
updating this file.
|
|
207
151
|
- Reconciliation MUST be idempotent: invoking it twice in a row on the
|
|
208
|
-
same state produces the same
|
|
209
|
-
|
|
210
|
-
- The extension PROPOSES verdicts via `details.steps[]`; the RECIPE
|
|
211
|
-
gates STATE.md mutation. The extension MUST NOT mutate STATE.md based
|
|
212
|
-
on a `requires_confirmation` verdict without recipe-mediated user
|
|
213
|
-
confirmation.
|
|
152
|
+
same state produces the same entry point and the same (empty on the
|
|
153
|
+
second call) set of actions.
|
package/manifest.json
CHANGED