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.
@@ -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 — preserved for legacy consumers (mirrors details.resumeAt)
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 decision = decide(marker, artifact);
272
- actions.push({ step, action: decision.action, reason: decision.reason });
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
- // resumeAt — first step whose verdict is NOT in {valid, valid_lenient}.
316
- // If every step is valid/valid_lenient, resumeAt = lastCompletedStep + 1
317
- // (preserves the "everything is fine, advance" semantic). entryStep mirrors
318
- // resumeAt so legacy consumers (index.ts renderer, recipe fallback) keep
319
- // reading the same integer.
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: task slug = basename of the spec parent directory
33
- minus the leading `NN_` prefix (e.g. `specs/17_pi-build-phase/hl-plan.md`
34
- `pi-build-phase`). `phaseKebab` = the HL plan's phase-$2 section heading
35
- (lowercase, hyphenated). If either is unavailable, ask the user.
36
-
37
- Call the `state` tool three times with arguments shaped like:
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
- { "operation": "reconcile", "task": "<slug>", "phase": $2 }
43
- ```
44
-
45
- After `reconcile` returns, branch on `details.reconcile` as follows:
46
-
47
- - Read `details.reconcile.resumeAt` (preferred) or `details.reconcile.entryStep`
48
- (legacy fallback) as the starting step. Both fields are integers 0..6 and
49
- (when both present) MUST agree.
50
- - Inspect `details.reconcile.steps[]`. If ANY entry has
51
- `verdict {requires_confirmation, reset}`, STOP and present the per-step
52
- verdict table (step number, marker, artifact, verdict, reason) verbatim to
53
- the user, plus `details.reconcile.table` for context (echo $@ as a banner).
54
- For each `requires_confirmation` entry, ask the user to confirm or skip the
55
- proposed reset. ONLY AFTER explicit confirmation may you mutate STATE.md.
56
- - If all verdicts ∈ `{valid, valid_lenient}`, present `details.reconcile.table`
57
- as a banner (echo $@) and resume directly at `resumeAt` without prompting.
58
- `valid_lenient` means the artifact exists on disk but a coarse content check
59
- downgraded its `reason` has already been surfaced via `renderResult`; no
60
- recipe action is required.
61
- - Do NOT re-execute any step `< resumeAt`. The reconciler is the single source
62
- of truth for resume DETERMINATION; the recipe is the gatekeeper for STATE.md
63
- MUTATION.
64
-
65
- When Step 0 is complete:
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 (reconcile is mandatory; resume detection drives the entry).
876
- - Mutate STATE.md (mark any step as `[ ]` or `[~]`) based on a reconciler
877
- verdict of `requires_confirmation` or `reset` without first obtaining
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>`, then
909
- `operation=reconcile` to detect resume, then proceed step by step.
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`. `state.reconcile` returns per-step verdicts
247
- (`valid | valid_lenient | stale | missing | reset | requires_confirmation`)
248
- plus a deterministic `details.resumeAt` integer. Verdicts `valid` and
249
- `valid_lenient` advance past the step without re-execution;
250
- `requires_confirmation` and `reset` cascades MUST be surfaced to the user
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
- **Verdict vocabulary (per-step).** `reconcile.ts` classifies each step's
16
- artifact into one of four classifications:
17
-
18
- - `valid` file exists AND content checks pass.
19
- - `valid_lenient` file exists; a coarse content check missed but the
20
- miss is NON-FATAL (e.g. line-count threshold). Resume continues past
21
- this step; the reason is surfaced once via `renderResult`.
22
- - `stale` — file exists; the mismatch is STRUCTURAL (missing
23
- required frontmatter key, unparseable frontmatter, wrong `type:`,
24
- wrong `result:` enum value). The matrix in §2 escalates `stale` to
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
- classification (§1) and look up the verdict below. The recipe walks Steps
63
- 0 → 6 in order at startup and consumes the per-step verdict from
64
- `details.steps[].verdict`.
65
-
66
- | STATE.md marker | Artifact classification | Verdict | Recipe action |
67
- |---------------------|-------------------------|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
68
- | `[x]` (completed) | `valid` | `valid` | OK. Continue past this step (no execution, no mutation). |
69
- | `[x]` (completed) | `valid_lenient` | `valid_lenient` | OK with note. Continue past this step. The `reason` is recorded in `details.steps[i].reason` for the user-facing `renderResult` table; no recipe action required. |
70
- | `[x]` (completed) | `stale` | `requires_confirmation` | Surface the per-step `reason` to the user verbatim and OBTAIN EXPLICIT CONFIRMATION before resetting Step to `[ ]`. No implicit reset. |
71
- | `[x]` (completed) | `missing` | `requires_confirmation` | Surface the missing-artifact path to the user and OBTAIN EXPLICIT CONFIRMATION before resetting Step to `[ ]`. No implicit reset. |
72
- | `[~]` (in_progress) | any | `reset` | Reset Step to `[ ]` unconditionally. Re-run it. In-progress markers are always re-runnable; no user confirmation needed. |
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 classification (Section 1).
152
- - Apply the matrix row (Section 2) to derive the per-step verdict.
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. **Compute `details.resumeAt`.** The first Step whose verdict is NOT
156
- in `{valid, valid_lenient}` is the entry point. If every step is
157
- `valid`/`valid_lenient`, `resumeAt = lastCompletedStep + 1` (preserves
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
- 8. **Report the resume decision** to the user before any agent is
184
- spawned: "Resuming Phase N at Step `resumeAt`. Skipped: <list>.
185
- Confirmed reset: <list>." Wait for explicit confirmation only if any
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 a `ReconcileResult`
193
- whose `details` carries:
194
- - `details.steps[]` — per-step verdict array; each entry has
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. Any change to the matrix MUST be applied to
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 `resumeAt`, the same per-step verdict
209
- set, and the same (empty on the second call) set of mutating actions.
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.7.1",
2
+ "version": "1.7.2",
3
3
  "presets": {
4
4
  "coding": {
5
5
  "description": "Research-driven coding workflow",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-fob",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Deploy research-driven AI coding assistant assets (skills, agents, commands) into your projects",
5
5
  "bin": {
6
6
  "ai-fob": "bin/install.js"