@gobing-ai/spur 0.3.91 → 0.3.92

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.
Files changed (120) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/plugin-scripts.json +17 -1
  4. package/config/workflows/feature-lifecycle.yaml +14 -5
  5. package/config/workflows/feature-verification.yaml +35 -27
  6. package/config/workflows/history-anatomy.yaml +14 -25
  7. package/config/workflows/idea-pipeline.yaml +9 -5
  8. package/config/workflows/task-pipeline.yaml +185 -4
  9. package/config/workflows/wrapup-pipeline.yaml +47 -5
  10. package/package.json +9 -9
  11. package/plugins/sp/agents/super-planner.md +14 -5
  12. package/plugins/sp/commands/dev-dogfood.md +4 -4
  13. package/plugins/sp/commands/dev-fixall.md +8 -5
  14. package/plugins/sp/commands/dev-run.md +6 -0
  15. package/plugins/sp/commands/dev-runall.md +12 -6
  16. package/plugins/sp/commands/dev-verify.md +9 -0
  17. package/plugins/sp/commands/dev-verifyall.md +5 -0
  18. package/plugins/sp/lib/idea-handoff.generated.mjs +301 -300
  19. package/plugins/sp/lib/inline-run.generated.d.mts +17 -0
  20. package/plugins/sp/lib/inline-run.generated.mjs +1460 -0
  21. package/plugins/sp/plugin.json +1 -1
  22. package/plugins/sp/references/environment-lens.md +1 -1
  23. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +136 -0
  24. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +196 -2
  25. package/plugins/sp/scripts/feature-verification-steps.mjs +174 -0
  26. package/plugins/sp/scripts/feature-verification-steps.ts +275 -0
  27. package/plugins/sp/scripts/history-anatomy-cache.mjs +104 -4
  28. package/plugins/sp/scripts/history-anatomy-cache.ts +137 -13
  29. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -0
  30. package/plugins/sp/scripts/inline-run-setup.mjs +349 -0
  31. package/plugins/sp/scripts/inline-run-setup.ts +192 -75
  32. package/plugins/sp/scripts/record-feature-sync.mjs +63 -0
  33. package/plugins/sp/scripts/record-feature-sync.ts +84 -0
  34. package/plugins/sp/scripts/residual-scan.mjs +476 -0
  35. package/plugins/sp/scripts/residual-scan.ts +614 -0
  36. package/plugins/sp/scripts/task-evidence-precheck.ts +8 -3
  37. package/plugins/sp/scripts/task-size-precheck.ts +8 -3
  38. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  39. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +2 -0
  40. package/plugins/sp/skills/code-implementation/SKILL.md +17 -0
  41. package/plugins/sp/skills/code-verification/SKILL.md +21 -0
  42. package/plugins/sp/skills/code-verification/references/verdict-schema.md +1 -0
  43. package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -3
  44. package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +63 -26
  45. package/plugins/sp/skills/dogfood-testing/references/report-template.md +33 -10
  46. package/plugins/sp/skills/history-anatomy/references/modes.md +5 -3
  47. package/plugins/sp/skills/next-feature/references/ranking-rubric.md +1 -1
  48. package/plugins/sp/skills/next-router/references/routing-table.md +7 -0
  49. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  50. package/plugins/sp/skills/session-review/SKILL.md +12 -2
  51. package/plugins/sp/skills/spur-cli/references/features.md +1 -1
  52. package/plugins/sp/skills/spur-cli/references/projects.md +3 -1
  53. package/plugins/sp/skills/spur-cli/references/workflows.md +39 -19
  54. package/plugins/sp/skills/spur-dev/SKILL.md +11 -4
  55. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -1
  56. package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -2
  57. package/plugins/sp/skills/spur-dev/references/execution-batch.md +285 -57
  58. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +1 -1
  59. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +19 -4
  60. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +43 -16
  61. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -0
  62. package/spur.js +21827 -19615
  63. package/web/_astro/BoardApp.CerSBgis.js +192 -0
  64. package/web/_astro/BoardApp.eoTz0pZs.js +1 -0
  65. package/web/_astro/{TaskDetail.CXGltuT_.js → TaskDetail.DCqiC-OZ.js} +1 -1
  66. package/web/_astro/arc.CPwg6Rw0.js +1 -0
  67. package/web/_astro/{architectureDiagram-3BPJPVTR.DJ8DHkWE.js → architectureDiagram-3BPJPVTR.DM_vp_hO.js} +1 -1
  68. package/web/_astro/{blockDiagram-GPEHLZMM.D8DHK3Jl.js → blockDiagram-GPEHLZMM.DXVIiv0p.js} +1 -1
  69. package/web/_astro/{c4Diagram-AAUBKEIU.BugQbX9u.js → c4Diagram-AAUBKEIU.BbF_zCxW.js} +1 -1
  70. package/web/_astro/channel.MYZLKNwy.js +1 -0
  71. package/web/_astro/{chunk-2J33WTMH.Mv26KlVn.js → chunk-2J33WTMH.CAgQHpPC.js} +1 -1
  72. package/web/_astro/{chunk-4BX2VUAB.CD51JoT_.js → chunk-4BX2VUAB.BN-5tpw4.js} +1 -1
  73. package/web/_astro/{chunk-55IACEB6.D_PFaIEe.js → chunk-55IACEB6.CnPkEEr0.js} +1 -1
  74. package/web/_astro/{chunk-727SXJPM.DemMW1ao.js → chunk-727SXJPM.BQzQeMVm.js} +4 -4
  75. package/web/_astro/{chunk-AQP2D5EJ.Iu2V5-ex.js → chunk-AQP2D5EJ.B6xNyDnL.js} +1 -1
  76. package/web/_astro/{chunk-FMBD7UC4.MsNgSP-E.js → chunk-FMBD7UC4.C7f9Ih78.js} +1 -1
  77. package/web/_astro/{chunk-ND2GUHAM.DfbRaAlm.js → chunk-ND2GUHAM.CNV1dFXT.js} +1 -1
  78. package/web/_astro/{chunk-QZHKN3VN.BbJAQ4h-.js → chunk-QZHKN3VN.Cudn2TkJ.js} +1 -1
  79. package/web/_astro/{classDiagram-4FO5ZUOK.CPurtiC2.js → classDiagram-4FO5ZUOK.D1NwP50q.js} +1 -1
  80. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CPurtiC2.js → classDiagram-v2-Q7XG4LA2.D1NwP50q.js} +1 -1
  81. package/web/_astro/{cose-bilkent-S5V4N54A.CKKdx1bM.js → cose-bilkent-S5V4N54A.B1wSL-Xb.js} +1 -1
  82. package/web/_astro/{cynefin-OW5HDTMX.CoKMTg-R.js → cynefin-OW5HDTMX.BmK52w8G.js} +1 -1
  83. package/web/_astro/{dagre-BM42HDAG.D4h4_k56.js → dagre-BM42HDAG.Bfy5CTDT.js} +2 -2
  84. package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +43 -0
  85. package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +10 -0
  86. package/web/_astro/{diagram-KO2AKTUF.BFoCkiCr.js → diagram-KO2AKTUF.CW_vMJ4z.js} +3 -3
  87. package/web/_astro/{diagram-LMA3HP47.exHn9OVx.js → diagram-LMA3HP47.B_8ZGF67.js} +1 -1
  88. package/web/_astro/{diagram-OG6HWLK6.CeqO34nN.js → diagram-OG6HWLK6.BppnHsdS.js} +1 -1
  89. package/web/_astro/{erDiagram-TEJ5UH35.D_v7HqxR.js → erDiagram-TEJ5UH35.BEuHXcjJ.js} +5 -5
  90. package/web/_astro/{flowDiagram-I6XJVG4X.EUmrpbwh.js → flowDiagram-I6XJVG4X.CH-UlnGr.js} +4 -4
  91. package/web/_astro/{ganttDiagram-6RSMTGT7.BOCF5lII.js → ganttDiagram-6RSMTGT7.BO81S85v.js} +1 -1
  92. package/web/_astro/{gitGraphDiagram-PVQCEYII.Di7otYZD.js → gitGraphDiagram-PVQCEYII.XnPxPPZN.js} +1 -1
  93. package/web/_astro/index.Hjbr15fG.css +1 -0
  94. package/web/_astro/{infoDiagram-5YYISTIA.TcBkCAJk.js → infoDiagram-5YYISTIA.JyjYRu_T.js} +1 -1
  95. package/web/_astro/{ishikawaDiagram-YF4QCWOH.D-2y4M0c.js → ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js} +5 -5
  96. package/web/_astro/{journeyDiagram-JHISSGLW.DPbJI_n2.js → journeyDiagram-JHISSGLW.C_iymSyp.js} +1 -1
  97. package/web/_astro/{kanban-definition-UN3LZRKU.EFxhQ9Fj.js → kanban-definition-UN3LZRKU.DdfW-Oqt.js} +7 -7
  98. package/web/_astro/{linear.DSAsQLzs.js → linear.C2_IkbZT.js} +1 -1
  99. package/web/_astro/mermaid.core.GAOYeSR0.js +303 -0
  100. package/web/_astro/{mindmap-definition-RKZ34NQL.CJY1N_7V.js → mindmap-definition-RKZ34NQL.DAZIxQSK.js} +2 -2
  101. package/web/_astro/{pieDiagram-4H26LBE5.567ZNoL2.js → pieDiagram-4H26LBE5.CN8sIhKM.js} +3 -3
  102. package/web/_astro/{quadrantDiagram-W4KKPZXB.mqfz9-MY.js → quadrantDiagram-W4KKPZXB.3dGcX5GP.js} +1 -1
  103. package/web/_astro/{requirementDiagram-4Y6WPE33.Bv1Gv9In.js → requirementDiagram-4Y6WPE33.BV2y4dd6.js} +3 -3
  104. package/web/_astro/{sankeyDiagram-5OEKKPKP.B6Gs4X4r.js → sankeyDiagram-5OEKKPKP.Cqo15Tvo.js} +4 -4
  105. package/web/_astro/{sequenceDiagram-3UESZ5HK.BhYj4v-m.js → sequenceDiagram-3UESZ5HK.CROCPMJB.js} +1 -1
  106. package/web/_astro/{stateDiagram-AJRCARHV.BPbBnkpw.js → stateDiagram-AJRCARHV.RfXZrkFE.js} +1 -1
  107. package/web/_astro/{stateDiagram-v2-BHNVJYJU.C4squMNK.js → stateDiagram-v2-BHNVJYJU.CPXmbBs9.js} +1 -1
  108. package/web/_astro/{timeline-definition-PNZ67QCA.C_SwIHgl.js → timeline-definition-PNZ67QCA.DdgKTiO8.js} +3 -3
  109. package/web/_astro/{vennDiagram-CIIHVFJN.Bz4NZGpQ.js → vennDiagram-CIIHVFJN.CPNVSHF1.js} +5 -5
  110. package/web/_astro/{wardleyDiagram-YWT4CUSO.CozMVZ3i.js → wardleyDiagram-YWT4CUSO.CQhA0Jyr.js} +3 -3
  111. package/web/_astro/{xychartDiagram-2RQKCTM6.BwMGBwjB.js → xychartDiagram-2RQKCTM6.n61BWyy4.js} +1 -1
  112. package/web/index.html +2 -2
  113. package/web/_astro/BoardApp.BEDWpzsr.js +0 -188
  114. package/web/_astro/BoardApp.DQG2xfEz.js +0 -1
  115. package/web/_astro/arc.C0rrflm_.js +0 -1
  116. package/web/_astro/channel.SRrg1P-w.js +0 -1
  117. package/web/_astro/diagram-2AECGRRQ.DJ0h9zgw.js +0 -43
  118. package/web/_astro/diagram-5GNKFQAL.DvPk1jYd.js +0 -10
  119. package/web/_astro/index.Bx6GY4RH.css +0 -1
  120. package/web/_astro/mermaid.core.kAZjgJHG.js +0 -301
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.91",
10
+ "version": "0.3.92",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -36,10 +36,10 @@
36
36
  "decision": null
37
37
  },
38
38
  "history-anatomy": {
39
- "modelQueries": 4,
39
+ "modelQueries": 3,
40
40
  "wallClockMs": null,
41
41
  "tokenCostUsd": null,
42
- "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
42
+ "source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0. 0920 recount 2026-09-23: 4 -> 3 — the resolve-scope model hop was removed; scope/window validation is deterministic in the history-anatomy-cache paths command.",
43
43
  "decision": null
44
44
  },
45
45
  "wayfinder-resolution": {
@@ -48,7 +48,13 @@
48
48
  },
49
49
  {
50
50
  "rel": "inline-run-setup.ts",
51
- "contract": "repo-only"
51
+ "contract": "standard",
52
+ "twin": "inline-run-setup.mjs"
53
+ },
54
+ {
55
+ "rel": "feature-verification-steps.ts",
56
+ "contract": "standard",
57
+ "twin": "feature-verification-steps.mjs"
52
58
  },
53
59
  {
54
60
  "rel": "idea-coverage-check.ts",
@@ -63,11 +69,21 @@
63
69
  "contract": "standard",
64
70
  "twin": "pr-reviewing.mjs"
65
71
  },
72
+ {
73
+ "rel": "record-feature-sync.ts",
74
+ "contract": "standard",
75
+ "twin": "record-feature-sync.mjs"
76
+ },
66
77
  {
67
78
  "rel": "quality-gate.ts",
68
79
  "contract": "standard",
69
80
  "twin": "quality-gate.mjs"
70
81
  },
82
+ {
83
+ "rel": "residual-scan.ts",
84
+ "contract": "standard",
85
+ "twin": "residual-scan.mjs"
86
+ },
71
87
  {
72
88
  "rel": "script-contract-check.ts",
73
89
  "contract": "repo-only"
@@ -40,9 +40,17 @@ states:
40
40
  onEnter:
41
41
  # Caller wiring (task 0880, ADR-119): entering verification RUNS the
42
42
  # feature-scoped pass instead of only passively reading its status file.
43
+ # 0948 R1: pass BOTH vars the inner workflow needs. `--vars` was documented as
44
+ # replace-not-merge and a partial override silently blanked `spurBin`; the engine
45
+ # merges declared defaults today (0948 R2), but the caller stays explicit so the
46
+ # nested run is self-sufficient under either semantic.
43
47
  - kind: shell
44
48
  options:
45
- command: '$spurBin workflow run feature-verification.yaml --vars "{\"featureId\":\"$featureId\"}"'
49
+ # Pass featureId and spurBin together. A partial --vars map used to be
50
+ # applied as a replacement, so the inner run lost spurBin and loadModule
51
+ # fell into source mode (0948 R1). The nested JSON is shell-expanded:
52
+ # $spurBin inside the double quotes keeps a multi-word invocation intact.
53
+ command: '$spurBin workflow run feature-verification.yaml --vars "{\"featureId\":\"$featureId\",\"spurBin\":\"$spurBin\"}"'
46
54
  - id: blocked
47
55
  description: Impediment; work suspended.
48
56
  - id: done
@@ -68,13 +76,14 @@ transitions:
68
76
  - from: verifying
69
77
  to: done
70
78
  description: >
71
- Feature verified — complete. Requires the feature-scoped verification pass
72
- (ADR-119) to have recorded PASS, then strict check (AC validated,
73
- traceability clean).
79
+ Feature verified — complete. The strict check at the done boundary also
80
+ validates the bound verification receipt (D63 task 0915): current, PASS,
81
+ matching feature identity and verifier contract, digest-chained to the
82
+ checked inputs. Stale/missing/failed evidence denies the hop.
74
83
  guard:
75
84
  kind: shell
76
85
  options:
77
- command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS && $spurBin feature check $featureId --strict --as done'
86
+ command: '$spurBin feature check $featureId --strict --as done'
78
87
 
79
88
  # Rework: verifying → active (mandatory History entry)
80
89
  - from: verifying
@@ -1,29 +1,34 @@
1
- # Feature-scoped verification pass (ADR-119, feature D62, task 0872).
1
+ # Feature-scoped verification pass (ADR-119, feature D62, task 0872; D63 task
2
+ # 0915 remediation folds the evidence contract into a workflow-owned script).
2
3
  #
3
4
  # Owns a lifecycle boundary no per-task graph owns: the repo-wide checks whose
4
- # invariant spans the repository rather than a single task's diff — corpus
5
- # consistency, contract baselines, dependency/schema drift, the frozen History
6
- # surface, and the repo-wide test set. They run once per feature, deliberately
7
- # the slow pass; `feature-lifecycle`'s verifying entry invokes this workflow (task
8
- # 0880 caller wiring), and its verifying→done guard refuses to complete the
9
- # feature until this pass records PASS (ADR-119 consequence: a feature is not
10
- # done until it passes).
5
+ # invariant spans the repository rather than a single task's diff. They run
6
+ # once per feature, deliberately the slow pass; `feature-lifecycle`'s verifying
7
+ # entry invokes this workflow (task 0880 caller wiring), and its verifying→done
8
+ # guard refuses to complete the feature until this pass records PASS (ADR-119
9
+ # consequence: a feature is not done until it passes).
11
10
  #
12
- # Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D62"}'
11
+ # Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D63"}'
13
12
  #
14
- # Shape: verify -> done | failed. The shell action writes the verdict to
15
- # `.spur/run/<featureId>-feature-verification.status` and always exits 0; the
16
- # transition guard reads the status so a missing, corrupt or FAIL verdict routes
17
- # to `failed` (fail-closed), never a silent PASS.
13
+ # Shape: verify -> done | failed. The onEnter step runs the standard script
14
+ # `feature-verification-steps.ts` (no public CLI verb, D63 task 0915): it
15
+ # resolves this very definition for the verifier identity (name, source path,
16
+ # layer, definition digest), records the RUNNING receipt (run-scoped +
17
+ # feature-latest copies), registers the run-scoped receipt as a run artifact,
18
+ # runs the configured verification command via the safe launch splitter, and
19
+ # completes the receipt PASS/FAIL with the after-pass proof-input digest. A
20
+ # changed tree during the pass records FAIL.
18
21
  #
19
22
  # Reliability (aligned with task-pipeline / ADR-043): no agent.run nodes; the
20
- # verification command is a trusted config var executed via `sh -c` (same
21
- # surface as task-pipeline's qualityGateCmd — never interpolate untrusted input).
23
+ # verification command comes from `vars.verificationCmd` below and is never
24
+ # interpolated through a shell. The always-exit-0 onEnter means missing,
25
+ # corrupt or FAIL evidence routes to `failed` via the guard (fail-closed),
26
+ # never a silent PASS; infrastructure errors exit nonzero and surface.
22
27
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
23
28
  kind: state-machine
24
29
  name: feature-verification
25
30
  version: "1"
26
- description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature and records PASS/FAIL. Invoked by feature-lifecycle's verifying entry."
31
+ description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature and records a bound PASS/FAIL evidence receipt. Invoked by feature-lifecycle's verifying entry."
27
32
  iterationBound: 2
28
33
  initialState: verify
29
34
  terminalStates:
@@ -35,37 +40,40 @@ vars:
35
40
  featureId: "X"
36
41
  spurBin: "spur"
37
42
  verificationCmd: "bun run spur-check-feature"
43
+ __runId: ""
38
44
 
39
45
  states:
40
46
  - id: verify
41
47
  description: >
42
- Run the repo-wide check set, record the verdict, always exit 0.
48
+ Run the repo-wide check set via the standard verification script, which
49
+ records the bound evidence receipt (verifier identity, checked-input
50
+ digest, run identity) and the coarse status file, always exit 0 so the
51
+ transition guard routes missing/corrupt/FAIL to `failed`.
43
52
  onEnter:
44
53
  - kind: shell
45
54
  options:
46
55
  command: >-
47
- mkdir -p .spur/run;
48
- sh -c "$verificationCmd" > ".spur/run/$featureId-feature-verification.log" 2>&1
49
- && printf 'PASS\n' > ".spur/run/$featureId-feature-verification.status"
50
- || printf 'FAIL\n' > ".spur/run/$featureId-feature-verification.status";
51
- exit 0
56
+ bun plugins/sp/scripts/feature-verification-steps.ts verify
57
+ --feature-id "$featureId"
58
+ --run-id "$__runId"
59
+ --spur-bin "$spurBin"
52
60
 
53
61
  - id: done
54
- description: Terminal — repo-wide checks passed.
62
+ description: Terminal — repo-wide checks passed and the receipt is bound.
55
63
 
56
64
  - id: failed
57
- description: Terminal — repo-wide checks failed or the verdict is missing/corrupt.
65
+ description: Terminal — repo-wide checks failed or the evidence is missing/corrupt.
58
66
 
59
67
  transitions:
60
68
  - from: verify
61
69
  to: done
62
- description: Repo-wide checks passed — the feature-scoped verification pass records PASS.
70
+ description: Repo-wide checks passed — the script completed the receipt with PASS.
63
71
  guard:
64
72
  kind: shell
65
73
  options:
66
- command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS'
74
+ command: 'test "$(cat .spur/run/$__runId-feature-verification.status 2>/dev/null)" = PASS'
67
75
  - from: verify
68
76
  to: failed
69
- description: Repo-wide checks failed (or the verdict is missing/corrupt) — stop at failed.
77
+ description: Repo-wide checks failed (or the evidence is missing/corrupt) — stop at failed.
70
78
  guard:
71
79
  kind: always
@@ -11,7 +11,7 @@
11
11
  # would exceed the shell composition threshold and be flagged as an owned-capability candidate.
12
12
  #
13
13
  # Shape:
14
- # start -> resolve-scope -> resolve-paths -> analyze -> cache-probe
14
+ # start -> resolve-paths -> analyze -> cache-probe
15
15
  # (hit -> refresh-provenance -> publish -> published)
16
16
  # (miss -> render -> enrich -> structure-gate -> validate -> stamp -> publish)
17
17
  # (structure-gate FAIL -> correct (max 2 total) -> structure-gate; exhausted -> failed)
@@ -28,7 +28,7 @@
28
28
  #
29
29
  # Vars (all must be declared here — the vars: block is the only safe shape; `spur workflow
30
30
  # validate` and the skill-structure test enforce it):
31
- # mode — "daily" (default) | "ad-hoc" (validated in resolve-scope)
31
+ # mode — "daily" (default) | "ad-hoc" (validated deterministically by the helper `paths` command)
32
32
  # date — YYYY-MM-DD for --date (daily); empty for ad-hoc
33
33
  # since — inclusive ISO lower bound (ad-hoc; also normalizes daily via the DST-aware rule)
34
34
  # until — inclusive ISO upper bound (ad-hoc)
@@ -39,7 +39,7 @@
39
39
  # focus — ad-hoc focus string (required in ad-hoc mode, rejected in daily)
40
40
  # recompute — "true" forces the full analyze/render/enrich/validate path (cache disposition
41
41
  # forced-recompute)
42
- # output — explicit report output path; default run directory
42
+ # output — explicit report output path; default docs/report/<date>-history-anatomy.md
43
43
  # agent — executor for the agent.run stages (enrich/validate)
44
44
  # spurBin — PATH-independent spur invocation (overridden by CLI at run start)
45
45
  # __runId — run-scoped id for explicit artifact paths (allocated in start)
@@ -85,28 +85,22 @@ states:
85
85
  description: >
86
86
  Allocate the run-scoped id and root. Every analyze/render path in this workflow writes to
87
87
  an explicit unique path under this run id — no stage reads the mutable latest.json pointer.
88
+ The retained identity is `.spur/run/<runId>-history-anatomy-run.id` (0948 R8). The fixed
89
+ `history-anatomy-run.id` name is only a latest pointer and is overwritten by the next run.
88
90
  onEnter:
89
91
  - kind: shell
90
92
  options:
91
93
  command: >-
92
94
  mkdir -p .spur/run;
93
95
  if [ -z "$__runId" ]; then __runId=$(uuidgen | tr 'A-Z' 'a-z' | cut -c1-8); fi;
94
- echo "$__runId" > .spur/run/history-anatomy-run.id
96
+ echo "$__runId" > ".spur/run/$__runId-history-anatomy-run.id";
97
+ cp ".spur/run/$__runId-history-anatomy-run.id" .spur/run/history-anatomy-run.id
98
+ # 0948 R8 retention disposition: `history-anatomy-run.id` is a SINGLE fixed-name
99
+ # pointer that is overwritten every run — it does not accumulate, so it needs no
100
+ # reclamation path (unlike the run-record pair and legacy `.log` files, which
101
+ # `spur workflow clean --logs` owns). It is deliberately NOT a run record and is
102
+ # exempt from the catalog record-name sweep (see run-record-catalog.test.ts).
95
103
 
96
- - id: resolve-scope
97
- description: >
98
- Dispatch the skill's mode validation (0658 references/modes.md): daily is the default and
99
- rejects focus/since/until/output; ad-hoc requires a non-empty focus plus two ordered
100
- inclusive bounds and rejects --date/--recompute. Writes the normalized selector artifact.
101
- onEnter:
102
- - kind: agent.run
103
- options:
104
- agent: ${vars.agent}
105
- input: "Validate the mode arguments per sp:history-anatomy references/modes.md and write the normalized selector to .spur/run/${vars.__runId}-selector.json. mode=${vars.mode} date=${vars.date} since=${vars.since} until=${vars.until} focus=${vars.focus}. Reject conflicting arguments, naming the offending one; a rejection marks the run failed."
106
- # Declared Layer-1 role (0538 R2): mode validation is a reviewer-standard judgment.
107
- role: reviewer
108
- expectFile: .spur/run/${vars.__runId}-selector.json
109
- timeoutMs: ${vars.stepTimeoutMs}
110
104
 
111
105
  - id: resolve-paths
112
106
  description: >
@@ -122,6 +116,7 @@ states:
122
116
  h=$(superskill script path sp history-anatomy-cache.mjs);
123
117
  node "$h" paths --helper "$h" --report-dir "$reportDir" --date "$date"
124
118
  --output "$output" --mode "$mode" --since "$since" --until "$until"
119
+ --focus "$focus" --recompute "$recompute" --run-id "$__runId"
125
120
  --out .spur/run/$__runId-paths.txt
126
121
 
127
122
  - id: analyze
@@ -338,14 +333,8 @@ states:
338
333
 
339
334
  transitions:
340
335
  - from: start
341
- to: resolve-scope
342
- description: Scope resolved.
343
- guard:
344
- kind: always
345
-
346
- - from: resolve-scope
347
336
  to: resolve-paths
348
- description: Mode valid — resolve helper/skill/target paths once.
337
+ description: Mode/window grammar validated and paths resolved once (deterministic).
349
338
  guard:
350
339
  kind: always
351
340
 
@@ -461,10 +461,12 @@ states:
461
461
  plan, ac, decisions, dependencies, premises) so the deterministic task
462
462
  check passes, then writes the run-scoped ready evidence sidecar
463
463
  (.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs,
464
- status (ready|failed|skipped), planningDigest, and seven check rows.
465
- Handoff-finalize verifies presence, digest and checklist evidence before
466
- recommending auto runall; missing or failing evidence never fails the
467
- run — it degrades the recommendation to ready-depth refineall.
464
+ status (ready|failed|skipped), planningDigest, and seven check rows,
465
+ with the premises row carrying tree-verified path:line citations
466
+ (0947). Handoff-finalize verifies presence, digest, feature binding and
467
+ checklist evidence before recommending auto runall; missing or failing
468
+ evidence never fails the run — it degrades the recommendation to
469
+ ready-depth refineall.
468
470
  onEnter:
469
471
  - kind: agent.run
470
472
  options:
@@ -483,7 +485,9 @@ states:
483
485
  for feature ${vars.featureId} per sp:spur-dev
484
486
  references/planning-workflow.md § Step 5.6 (Ready preparation): read
485
487
  .spur/run/${vars.__runId}-idea-batch-create-result.json and write
486
- .spur/run/${vars.__runId}-idea-ready.json.
488
+ .spur/run/${vars.__runId}-idea-ready.json. The premises check row passes only
489
+ when each material premise was verified against the current tree and its evidence
490
+ cites at least one verified path:line (handoff-finalize lints it; task 0947).
487
491
  answerFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
488
492
  expectFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
489
493
  # Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
@@ -6,13 +6,18 @@
6
6
  # through the normal `spur task update <wbs> <status>` verb so the lifecycle guards
7
7
  # (0055) apply identically. Run linkage is written to `task_run_links` (kind=pipeline).
8
8
  #
9
- # Shape: precheck → implement → test[→test-fix↔test-recheck] → review → approve(HITL)
9
+ # Shape: precheck → implement[→escalate] → test[→test-fix↔test-recheck] → review → approve(HITL)
10
10
  # → verify → record → done
11
11
  # (precheck failure short-circuits to `failed`; approve routes to `failed` on
12
12
  # operator rejection or `cancelled` on operator cancel — R1, bug-750).
13
13
  # `test` is the project quality gate (shell + bounded /sp:dev-fixall), not
14
14
  # /sp:dev-unit (coverage gap-fill; router C3/C5).
15
15
  #
16
+ # F96 residual sweep (0950): precheck captures the resume-safe base commit; verify
17
+ # scans and folds residuals between the verdict and the proof bind (blocking leftovers
18
+ # downgrade PASS → PARTIAL and take the existing remediation edge); done settles
19
+ # deferrals; failed renders the recovery report. No new state, edge, or model query.
20
+ #
16
21
  # Vars (passed as a JSON object via `--vars`):
17
22
  # wbs — task WBS (required)
18
23
  # profile — "auto" skips HITL approve (R4)
@@ -21,6 +26,7 @@
21
26
  # implementTimeoutMs — implement agent.run budget (ms)
22
27
  # qualityGateCmd — project gate (default: bun run spur-check)
23
28
  # qualityGateMaxFixAttempts — max /sp:dev-fixall hops after a red gate (default: 2)
29
+ # maxEscalations — max operator-question pauses per implement chain (default: 2)
24
30
  #
25
31
  # Seeded by `spur init`. agent.run inputs are pure slash commands (ADR-043).
26
32
 
@@ -90,6 +96,10 @@ vars:
90
96
  # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
91
97
  # Empty by default; only meaningful once the approve state has been entered.
92
98
  __hitlAnswer: ""
99
+ # Operator answer captured by the escalate hop's hitl.input (0933). Empty by
100
+ # default; set by `spur workflow continue --answer-text` after the pause and read
101
+ # by the escalate→implement / escalate→failed guards. Non-empty = answered.
102
+ __hitlInput: ""
93
103
  # Proof-state bracket (task 0612, ADR-071; restructured by task 0703). `proofDigest` is the
94
104
  # canonical capture taken at quality-gate ENTRY — immediately before the evidence-producing
95
105
  # final chain (quality → review → verify) — and re-captured at `test-recheck` when bounded
@@ -134,6 +144,14 @@ vars:
134
144
  # Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
135
145
  # Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
136
146
  qualityGateMaxFixAttempts: "2"
147
+ # Max operator-question pauses (0933) before the implement chain routes to failed.
148
+ # Escalation counter: .spur/run/<wbs>-escalation-count. Default 2 = two answered
149
+ # questions; a third pause routes to failed with a report note.
150
+ maxEscalations: "2"
151
+ # Runtime-written, 0933: the escalate hop's file.read.into-var fills this from
152
+ # .spur/run/<wbs>-question.md; the hitl.input prompt below references it. Declared
153
+ # here (empty) so the static var-reference validator sees the template source.
154
+ escalationQuestion: ""
137
155
  # Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
138
156
  # project can point it at its own formatter; invoked best-effort (a missing or
139
157
  # failing formatter must never abort a run — the quality gate is the real gate).
@@ -156,6 +174,12 @@ vars:
156
174
  # (default) = on; set to "off" to bypass:
157
175
  # `--vars '{"implementScopeGuard":"off"}'`.
158
176
  implementScopeGuard: ""
177
+ # 0931 R5: parallel batches defer the per-task feature sync so a task branch never
178
+ # touches feature files or docs/features/INDEX.md. When "true", the record step's
179
+ # post-record shell appends a deferral note and skips the sync; the parallel batch
180
+ # orchestrator runs the sync + `spur feature refresh` once per touched feature on the
181
+ # base ref after integration. Sequential/inline keep the default "false" (unchanged).
182
+ deferFeatureSync: "false"
159
183
 
160
184
  states:
161
185
  - id: precheck
@@ -189,6 +213,12 @@ states:
189
213
  options:
190
214
  command: >-
191
215
  mkdir -p .spur/run; S=plugins/sp/scripts/task-evidence-precheck.ts; [ -f "$S" ] || S="$(superskill script path sp task-evidence-precheck.ts 2>/dev/null)"; if [ -f "$S" ]; then bun "$S" "$wbs" --spur-bin "$spurBin"; else echo "task-evidence-precheck failed closed — checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'." >&2; echo "FAIL" > ".spur/run/$wbs-precheck-evidence.status"; fi; exit 0
216
+ # (e) F96 R1 (0950): capture the run's base commit once — a resumed or re-entered run
217
+ # keeps its original base so residual scans stay anchored to the same diff.
218
+ - kind: shell
219
+ options:
220
+ command: >-
221
+ mkdir -p .spur/run; [ -f ".spur/run/$wbs-base.sha" ] || git rev-parse HEAD > ".spur/run/$wbs-base.sha"; exit 0
192
222
  # (e) route-reason lookup and routes log; proportional-routing tests locate it
193
223
  # (0759 R1/R5 run-scoped artifact; 0804 R8 run-id safety — one line, no in-scalar `#`).
194
224
  - kind: shell
@@ -222,6 +252,20 @@ states:
222
252
  that command DRIVES this pipeline, so calling it here recurses.
223
253
  --mode implement is the single-step implement entry.
224
254
  onEnter:
255
+ # 0933 R27 (guard-lines compression): the transcript append lives HERE, on
256
+ # (re-)entry, not in the escalate→implement guard. On first entry there is no
257
+ # pending question (no-op); on a resume entry the guard already confirmed a
258
+ # non-empty $__hitlInput and a fresh question file, so this shell records the
259
+ # Q/A pair (## Q<n>/## A<n>, R3) and consumes the question file before the
260
+ # agent.run re-dispatches — which also disarms the implement→escalate edge.
261
+ - kind: shell
262
+ options:
263
+ command: >-
264
+ test -n "$__hitlInput" -a -s .spur/run/$wbs-question.md || exit 0;
265
+ qn="$(cat .spur/run/$wbs-escalation-count 2>/dev/null || echo 0)";
266
+ printf '## Q%s\n%s\n\n## A%s\n%s\n' "$qn" "$(cat .spur/run/$wbs-question.md 2>/dev/null)" "$qn" "$__hitlInput" >> .spur/run/$wbs-escalation.md;
267
+ rm -f .spur/run/$wbs-question.md;
268
+ exit 0
225
269
  - kind: agent.run
226
270
  options:
227
271
  agent: ${vars.implementAgent}
@@ -232,13 +276,23 @@ states:
232
276
  # resumes the implement session instead of re-reading the task cold.
233
277
  session: reuse
234
278
  # lives in /sp:dev-run --mode implement → sp:code-implementation, not YAML prose.
235
- input: /sp:dev-run --mode implement ${vars.wbs} --auto
279
+ # 0933 R28: --escalation-file names the Q/A transcript the implement
280
+ # agent appends to when pausing and reads back on re-dispatch; absent
281
+ # on a first attempt means "no prior escalations".
282
+ input: /sp:dev-run --mode implement ${vars.wbs} --auto --escalation-file .spur/run/${vars.wbs}-escalation.md
236
283
  timeoutMs: ${vars.implementTimeoutMs}
237
284
  # R3 (task 0424): empty-implement no-op guard — the agent.run action
238
285
  # fails the step when exit 0 produced zero non-corpus file changes, so
239
286
  # a silent no-op routes the run to `failed` here instead of drifting
240
287
  # into test/review and being caught a full pass later.
241
288
  requireDiff: true
289
+ # 0933 R26: escalation contract — the agent.run option names the QUESTION
290
+ # file (the pause signal, deleted pre-dispatch for freshness); a paused
291
+ # attempt (agent wrote its question there and exited 0) succeeds with
292
+ # data.escalated=true and skips requireDiff for THAT attempt only. The
293
+ # Q/A transcript (.spur/run/<wbs>-escalation.md) is separate: it reaches
294
+ # the agent via --escalation-file in the input below.
295
+ escalationFile: .spur/run/${vars.wbs}-question.md
242
296
  # 0706 R6: this stage mutates the working tree unattended under the
243
297
  # auto profile, so it declares minimum execution-capability
244
298
  # requirements. Dispatch fails closed (before spawn) when the
@@ -273,6 +327,39 @@ states:
273
327
  options:
274
328
  command: "$formatCmd ; exit 0"
275
329
 
330
+ # ── escalate hop (operator-question pause, 0933) ──────────────────────────
331
+ # The implement agent paused on an operator question: it wrote
332
+ # .spur/run/<wbs>-question.md and exited 0 (agent.run reported
333
+ # data.escalated=true, skipping requireDiff for that attempt). This hop bounds
334
+ # the asks (counter), surfaces the question verbatim through the HITL responder
335
+ # (the run pauses here), and — after `spur workflow continue --answer-text <a>`
336
+ # delivers __hitlInput — resumes implement with the Q/A transcript
337
+ # (.spur/run/<wbs>-escalation.md) named in the implement input via
338
+ # --escalation-file. The transcript append + question-file consumption happen in
339
+ # the escalate→implement guard so a stale question can never re-trigger the hop
340
+ # after the answered attempt.
341
+ - id: escalate
342
+ description: >-
343
+ Operator-question pause (0933): the implement agent asked a question
344
+ headlessly; surface it to the operator and resume implement with the answer.
345
+ onEnter:
346
+ # Bounded asks: increment the per-task escalation counter before pausing.
347
+ - kind: shell
348
+ options:
349
+ command: >-
350
+ mkdir -p .spur/run; n="$(cat .spur/run/$wbs-escalation-count 2>/dev/null || echo 0)"; echo $((n + 1)) > .spur/run/$wbs-escalation-count; exit 0
351
+ # The agent's question, verbatim, becomes the pause prompt.
352
+ - kind: file.read.into-var
353
+ options:
354
+ file: .spur/run/${vars.wbs}-question.md
355
+ var: escalationQuestion
356
+ # Pause for the operator (responder pause:true stops the run after this
357
+ # onEnter; `spur workflow continue --answer-text <answer>` writes
358
+ # __hitlInput and re-evaluates the escalate guards).
359
+ - kind: hitl.input
360
+ options:
361
+ prompt: "${vars.escalationQuestion}"
362
+
276
363
  # ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
277
364
  # NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
278
365
  # a coverage target; it is not the project quality gate. Coverage gap-fill remains
@@ -358,6 +445,14 @@ states:
358
445
  options:
359
446
  command: >-
360
447
  mkdir -p .spur/run; A=".spur/run/$wbs-test-fix-attempt"; n=$(cat "$A" 2>/dev/null || echo 0); printf '%s\n' "$((n + 1))" > "$A"; [ ! -f ".spur/run/$wbs-verdict.json" ] || { echo '--- verify verdict (remediation input, task 0703 R4) ---'; cat ".spur/run/$wbs-verdict.json"; } >> ".spur/run/$wbs-test-gate.log"; exit 0
448
+ # (d) F96 R3 (0950): hand the residual list to the remediation loop — dev-fixall
449
+ # treats residual items as fix targets and may defer P3/markers to
450
+ # residual-deferrals.json (see dev-fixall.md). Fold already merged the anchors
451
+ # into <wbs>-test-gate.findings; this carries the structured artifact too.
452
+ - kind: shell
453
+ options:
454
+ command: >-
455
+ [ ! -f ".spur/run/$wbs-residuals.json" ] || { echo '--- residual artifact (F96 fix targets; deferrals go to residual-deferrals.json, P3/markers only) ---'; cat ".spur/run/$wbs-residuals.json"; } >> ".spur/run/$wbs-test-gate.log"; exit 0
361
456
  # R3 (0482): project the extracted gate anchors into a var so the dispatch input
362
457
  # can NAME the failing file:line, not merely point at a log. A vars template cannot
363
458
  # run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
@@ -530,6 +625,16 @@ states:
530
625
  - kind: shell
531
626
  options:
532
627
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
628
+ # (d) F96 R2 (0950): residual-scan.ts owns the sweep; shell only resolves it.
629
+ # ONE hard action (scan+fold): a scanner crash fails verify closed — the
630
+ # verify→failed catch-all routes a missing/malformed verdict. Runs AFTER
631
+ # `task verdict` and BEFORE the jq bind so a blocking residual turns PASS
632
+ # into PARTIAL (fold rewrites the verdict artifact) and takes the existing
633
+ # verify→test-fix edge while attempts remain.
634
+ - kind: shell
635
+ options:
636
+ command: >-
637
+ S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ ! -f "$S" ]; then echo "residual-scan failed closed — scanner not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'" >&2; exit 1; fi; case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" scan "$wbs" --spur-bin "$spurBin" && "$RUNNER" "$S" fold "$wbs" --spur-bin "$spurBin"
533
638
  # (e) one jq mutation binds the verdict to the proof digest (0703 R3; runId 0730 §B.2 /
534
639
  # 0757 R4; definitionDigest 0759 R5; honest review stamp 0785 R4; soft action + hard guard).
535
640
  - kind: shell
@@ -584,10 +689,16 @@ states:
584
689
  # (d) feature-sync-bounded.ts owns the sync; shell adds the orphan note and fallbacks
585
690
  # (0411 retry-suppression; 0328 / ADR-0322). Best-effort `exit 0` — feature status sync is a
586
691
  # follow-up, not a completion gate; `record → done` runs `spur task check`.
692
+ # 0931 R5: with deferFeatureSync "true" (parallel mode) the deferral note lands in the
693
+ # task report and the sync is skipped entirely, so task branches never touch feature
694
+ # corpus files; the batch orchestrator performs the deferred sync on the base ref.
695
+ # The sync itself is owned by record-feature-sync.ts (ADR-115: the guard plus the old
696
+ # inline chain cannot share one shell); resolution follows the standard in-repo-first,
697
+ # superskill-staged fallback used by every pipeline checker.
587
698
  - kind: shell
588
699
  options:
589
700
  command: >-
590
- FID=$($spurBin task show $wbs --json 2>/dev/null | jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null); if [ -z "$FID" ]; then echo "Orphan task $wbs — no feature_id linked — proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt"; elif [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json; elif M="$(superskill script path sp feature-sync-bounded.mjs 2>/dev/null)" && [ -f "$M" ]; then node "$M" "$FID" --spur-bin "$spurBin" --json; else $spurBin feature sync "$FID" --json; fi; exit 0
701
+ S=plugins/sp/scripts/record-feature-sync.ts; [ -f "$S" ] || S="$(superskill script path sp record-feature-sync.mjs 2>/dev/null)"; [ "$deferFeatureSync" = "true" ] && echo "feature sync deferred to batch integration" >> ".spur/run/$wbs-report.txt" || if [ -f "$S" ]; then bun "$S" --spur-bin "$spurBin"; else echo "feature sync skipped — record-feature-sync not found in plugins/sp/scripts/ nor staged" >> ".spur/run/$wbs-report.txt"; fi; exit 0
591
702
 
592
703
  - id: done
593
704
  description: >
@@ -595,6 +706,13 @@ states:
595
706
  guard runs `spur task check` before certifying; a genuinely non-compliant
596
707
  task routes to `failed` instead of a silent bad `done`.
597
708
  onEnter:
709
+ # (d) F96 R4 (0950): residual-scan.ts owns the settle; shell only resolves it.
710
+ # Soft best-effort (exit 0) — runs after certification and must never change
711
+ # the outcome; failure prints the re-run command.
712
+ - kind: shell
713
+ options:
714
+ command: >-
715
+ S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" settle "$wbs" --spur-bin "$spurBin" || echo "residual-settle failed — re-run: residual-scan settle $wbs" >&2; else echo "residual-scan not staged — settle skipped — re-run: residual-scan settle $wbs" >&2; fi; exit 0
598
716
  # (c) command.gate owns the done transition with classified transient retry.
599
717
  - kind: command.gate
600
718
  options:
@@ -620,7 +738,28 @@ states:
620
738
  - id: failed
621
739
  description: >
622
740
  Terminal — precheck, quality-gate exhaustion, verify non-PASS, record check
623
- failure, or operator rejection; reported, not advanced.
741
+ failure, or operator rejection; reported, not advanced. Also receives an
742
+ escalation-bound pause or an operator give-up (0933).
743
+ onEnter:
744
+ # 0933 R30 (guard-lines compression): the escalation-bound report note lives
745
+ # here as a conditional — no-op unless the run reached failed with a pending
746
+ # question at (or above) the ask bound (bound pause or final give-up). The
747
+ # unanswered question itself is appended to the task report (report.txt, R2).
748
+ - kind: shell
749
+ options:
750
+ command: >-
751
+ n="$(cat .spur/run/$wbs-escalation-count 2>/dev/null || echo 0)";
752
+ test "$n" -ge "$maxEscalations" -a -s .spur/run/$wbs-question.md || exit 0;
753
+ printf '\n## Escalation bound reached\n\nThe implement step asked an operator question %s times (bound %s) and ended without a resumable answer. Unanswered question:\n\n' "$n" "$maxEscalations" >> .spur/run/$wbs-report.txt;
754
+ cat .spur/run/$wbs-question.md >> .spur/run/$wbs-report.txt;
755
+ exit 0
756
+ # (d) F96 R5 (0950): residual-scan.ts owns the report; shell only resolves it.
757
+ # Soft (exit 0): a run that exhausts its fix budget on residuals leaves the
758
+ # report + recovery line while the task stays `wip`.
759
+ - kind: shell
760
+ options:
761
+ command: >-
762
+ S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" report "$wbs" --spur-bin "$spurBin" || echo "residual-report failed — re-run: residual-scan report $wbs" >&2; else echo "residual-scan not staged — report skipped — re-run: residual-scan report $wbs" >&2; fi; exit 0
624
763
 
625
764
  - id: cancelled
626
765
  description: Terminal — pipeline cancelled by operator at the approval gate (R1).
@@ -644,12 +783,54 @@ transitions:
644
783
  guard:
645
784
  kind: always
646
785
 
786
+ # ── implement routing: escalation first (0933 R26/R30), then the linear body ──
787
+ # Declaration order matters: the escalation bound is checked FIRST — a paused
788
+ # attempt that already exhausted its maxEscalations asks routes to failed (with a
789
+ # report note appended in the same guard) instead of asking forever; then the
790
+ # question-presence hop; then the normal always edge. The bound guard requires a
791
+ # fresh question file so a COMPLETED implement after max asks still routes to test.
792
+ - from: implement
793
+ to: failed
794
+ description: Escalation bound exhausted — the agent paused again after maxEscalations operator answers.
795
+ # 0933 R30 (guard-lines compression): thin predicate; the report note is written
796
+ # by the failed state's onEnter when it observes a bound-exhausted pause.
797
+ guard:
798
+ kind: shell
799
+ options:
800
+ command: >-
801
+ n="$(cat .spur/run/$wbs-escalation-count 2>/dev/null || echo 0)";
802
+ test "$n" -ge "$maxEscalations" -a -s .spur/run/$wbs-question.md
803
+ - from: implement
804
+ to: escalate
805
+ description: Implement agent paused on an operator question (non-empty question file) — pause for an answer.
806
+ guard:
807
+ kind: shell
808
+ options:
809
+ command: 'test -s .spur/run/$wbs-question.md'
647
810
  # ── linear body ──
648
811
  - from: implement
649
812
  to: test
650
813
  description: Implementation done — quality-gate probe.
651
814
  guard:
652
815
  kind: always
816
+ # escalate branching (0933 R27): an answered pause resumes implement — the transcript
817
+ # append and question consumption happen in implement's onEnter (guards stay thin
818
+ # under ADR-115 guard-lines caps); an empty answer is an operator give-up → failed
819
+ # (question + transcript preserved as evidence).
820
+ - from: escalate
821
+ to: implement
822
+ description: Operator answered — re-enter implement, which records the Q/A transcript and consumes the question.
823
+ guard:
824
+ kind: shell
825
+ options:
826
+ command: 'test -n "$__hitlInput"'
827
+ - from: escalate
828
+ to: failed
829
+ description: Empty operator answer — give-up routes to failed (question + transcript preserved as evidence).
830
+ guard:
831
+ kind: shell
832
+ options:
833
+ command: 'test -z "$__hitlInput"'
653
834
  # Soft probe branching (declaration order: PASS first, then FAIL, then defense).
654
835
  - from: test
655
836
  to: verify