@gobing-ai/spur 0.3.85 → 0.3.87

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 (139) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -25
  3. package/config/plugin-scripts.json +4 -5
  4. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  5. package/config/templates/feature/default.md +2 -0
  6. package/config/templates/task/brainstorm.md +2 -2
  7. package/config/templates/task/feature-impl.md +2 -2
  8. package/config/templates/task/issue.md +2 -2
  9. package/config/templates/task/meta.md +2 -2
  10. package/config/templates/task/review.md +2 -2
  11. package/config/templates/task/standard.md +2 -2
  12. package/config/transition-shims.json +1 -1
  13. package/config/workflow-candidates.json +19 -0
  14. package/config/workflows/feature-lifecycle.yaml +10 -2
  15. package/config/workflows/feature-verification.yaml +71 -0
  16. package/config/workflows/idea-pipeline.yaml +87 -39
  17. package/config/workflows/task-pipeline.yaml +43 -12
  18. package/config/workflows/wrapup-pipeline.yaml +70 -7
  19. package/package.json +9 -9
  20. package/plugins/sp/README.md +7 -7
  21. package/plugins/sp/commands/dev-idea.md +9 -2
  22. package/plugins/sp/commands/dev-refactor.md +33 -0
  23. package/plugins/sp/hooks/agent-hint.ts +5 -4
  24. package/plugins/sp/hooks/careful-guard.ts +3 -1
  25. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  26. package/plugins/sp/hooks/context-session-start.ts +4 -3
  27. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  28. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  29. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  30. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  31. package/plugins/sp/plugin.json +1 -1
  32. package/plugins/sp/references/roles.md +1 -1
  33. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  34. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  35. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  36. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  37. package/plugins/sp/scripts/idea-coverage-check.ts +168 -0
  38. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  39. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  40. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +115 -4
  41. package/plugins/sp/scripts/inline-run-setup.ts +195 -2
  42. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  43. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  44. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  45. package/plugins/sp/scripts/quality-gate.ts +2 -1
  46. package/plugins/sp/scripts/surface-drift-inventory.ts +1 -4
  47. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  48. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  49. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  50. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  51. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  52. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  53. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  54. package/plugins/sp/skills/brainstorm/SKILL.md +4 -0
  55. package/plugins/sp/skills/code-refactoring/SKILL.md +155 -0
  56. package/plugins/sp/skills/code-refactoring/references/finding-schema.md +74 -0
  57. package/plugins/sp/skills/code-refactoring/references/fix-ladder.md +52 -0
  58. package/plugins/sp/skills/code-refactoring/references/focus-detection.md +44 -0
  59. package/plugins/sp/skills/code-refactoring/references/refactor-finding.schema.json +95 -0
  60. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +23 -16
  61. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +4 -2
  62. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  63. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +2 -2
  64. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +3 -3
  65. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +1 -1
  66. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  67. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +24 -0
  68. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  69. package/plugins/sp/skills/spur-dev/references/dev-operations.md +18 -2
  70. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +22 -3
  72. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  73. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +11 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +91 -1
  75. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +44 -1
  76. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +31 -0
  77. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +43 -0
  78. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +42 -0
  79. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +43 -0
  80. package/schemas/state-machine-workflow.schema.json +5 -0
  81. package/schemas/task-batch.schema.json +2 -2
  82. package/schemas/transition-flow-workflow.schema.json +5 -0
  83. package/spur.js +21106 -20669
  84. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  85. package/web/_astro/{BoardApp.yW425dRZ.js → BoardApp.yBBcFXWP.js} +4 -4
  86. package/web/_astro/{TaskDetail.CGwJAinW.js → TaskDetail.DqFJbRFc.js} +1 -1
  87. package/web/_astro/{arc.B-qNXzSO.js → arc.DL-BpHoi.js} +1 -1
  88. package/web/_astro/{architectureDiagram-3BPJPVTR.Bdg-xiji.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  89. package/web/_astro/{blockDiagram-GPEHLZMM.DFArg3Kp.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  90. package/web/_astro/{c4Diagram-AAUBKEIU.BkOLcjyx.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  91. package/web/_astro/channel.CX5453qQ.js +1 -0
  92. package/web/_astro/{chunk-2J33WTMH.DN3c20V4.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  93. package/web/_astro/{chunk-4BX2VUAB.B64K0Ttd.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  94. package/web/_astro/{chunk-55IACEB6.C-AJvNwa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  95. package/web/_astro/{chunk-727SXJPM.DMkr46uS.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  96. package/web/_astro/{chunk-AQP2D5EJ.4_WGbI64.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  97. package/web/_astro/{chunk-FMBD7UC4.cbFW_lVE.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  98. package/web/_astro/{chunk-ND2GUHAM.CDW_-2O1.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  99. package/web/_astro/{chunk-QZHKN3VN.BoYOt_yg.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  100. package/web/_astro/{classDiagram-4FO5ZUOK.BNT6XpHQ.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  101. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BNT6XpHQ.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  102. package/web/_astro/{cose-bilkent-S5V4N54A.CI1DQxR3.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  103. package/web/_astro/{cynefin-OW5HDTMX.A8ARH61p.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  104. package/web/_astro/{dagre-BM42HDAG.u28ZmqwR.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  105. package/web/_astro/{diagram-2AECGRRQ.Dx8JcrGc.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  106. package/web/_astro/{diagram-5GNKFQAL.BXJUG9GY.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  107. package/web/_astro/{diagram-KO2AKTUF.BtlMuWqW.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  108. package/web/_astro/{diagram-LMA3HP47.C4T7qaAy.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  109. package/web/_astro/{diagram-OG6HWLK6.D5HECE10.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  110. package/web/_astro/{erDiagram-TEJ5UH35.B_hMR3yw.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  111. package/web/_astro/{flowDiagram-I6XJVG4X.BAmpcYl3.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  112. package/web/_astro/{ganttDiagram-6RSMTGT7.DV1bSWK-.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  113. package/web/_astro/{gitGraphDiagram-PVQCEYII.DxYKkKrJ.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  114. package/web/_astro/{infoDiagram-5YYISTIA.BAWY2xMb.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  115. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CZssRn6V.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  116. package/web/_astro/{journeyDiagram-JHISSGLW.C--muARd.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  117. package/web/_astro/{kanban-definition-UN3LZRKU.D_QCK4et.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  118. package/web/_astro/{linear.DzrmTtZ0.js → linear.Nv_xOUjP.js} +1 -1
  119. package/web/_astro/{mermaid.core.Da03W3iu.js → mermaid.core.Bc4LqQgX.js} +4 -4
  120. package/web/_astro/{mindmap-definition-RKZ34NQL.6cy-8hR_.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  121. package/web/_astro/{pieDiagram-4H26LBE5.C5rS1pdU.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  122. package/web/_astro/{quadrantDiagram-W4KKPZXB.w56GZZ6Q.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  123. package/web/_astro/{requirementDiagram-4Y6WPE33.CipX3Pwu.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  124. package/web/_astro/{sankeyDiagram-5OEKKPKP.C0VVzgJm.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  125. package/web/_astro/{sequenceDiagram-3UESZ5HK.BX2dUUbF.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  126. package/web/_astro/{stateDiagram-AJRCARHV.ypCdgODQ.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  127. package/web/_astro/{stateDiagram-v2-BHNVJYJU.In0baEtg.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  128. package/web/_astro/{timeline-definition-PNZ67QCA.CJN4Vkvl.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  129. package/web/_astro/{vennDiagram-CIIHVFJN.Cf8KkIPY.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  130. package/web/_astro/{wardleyDiagram-YWT4CUSO.DCZBo9xw.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  131. package/web/_astro/{xychartDiagram-2RQKCTM6.Cm4v_MiJ.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  132. package/web/index.html +1 -1
  133. package/config/workflows/basic.yaml +0 -146
  134. package/config/workflows/docs-pipeline.yaml +0 -350
  135. package/config/workflows/feature-dev.yaml +0 -288
  136. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  137. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  138. package/web/_astro/BoardApp.CHenHFia.js +0 -1
  139. package/web/_astro/channel.CbDHK5UQ.js +0 -1
@@ -97,7 +97,10 @@ states:
97
97
  - kind: shell
98
98
  options:
99
99
  command: >-
100
- test -n "$idea" || printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
100
+ mkdir -p .spur/run &&
101
+ printf '%s\n' "$idea" > ".spur/run/$__runId-idea-input.md" &&
102
+ awk 'NF' ".spur/run/$__runId-idea-input.md" | grep -q . ||
103
+ printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
101
104
 
102
105
  - id: discovery
103
106
  description: >
@@ -109,12 +112,14 @@ states:
109
112
  brainstorm also emits the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md
110
113
  (template: the `sp:spur-dev` skill's `idea-evaluation` reference — named by skill, not by
111
114
  repo path, because `spur init` never scaffolds `plugins/sp/` into a seeded project).
115
+ The operator's verbatim idea argument is persisted at .spur/run/${vars.__runId}-idea-input.md
116
+ (written by start; the authoritative ask every model-bearing stage reads).
112
117
  expectFile fails a silent no-op discovery (no eval report).
113
118
  onEnter:
114
119
  - kind: agent.run
115
120
  options:
116
121
  agent: ${vars.planningAgent}
117
- input: "Run sp:brainstorm for the idea: ${vars.idea}. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation). At the END of the report, append a provenance footer block of the exact form: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339 timestamp>\\n---` (omit the footer only if run_id is empty)."
122
+ input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative, read it first (${vars.idea} is a convenience echo). Run sp:brainstorm. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) plus the design summary per its `Design Approval Gate` / `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation, plus mandatory `## Requirement inventory`: numbered I<n> items quoting/paraphrasing idea-input lines; `[unclear: ...]` marks ambiguity, `[deferred: <reason>]` marks out-of-scope). End with this exact footer: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339>\\n---` (omit if run_id is empty)."
118
123
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
119
124
  role: planner
120
125
  expectFile: .spur/run/${vars.__runId}-idea-eval-report.md
@@ -143,12 +148,14 @@ states:
143
148
  checklists never enter Goal. Shell actions then persist both sections through
144
149
  `spur feature update --section Goal|Scope --from-file ...`; a missing/empty artifact
145
150
  stops the state. Prefer the enhanced idea from .spur/run/${vars.__runId}-idea-eval-report.md as
146
- context; do not overwrite vars.idea.
151
+ context; do not overwrite vars.idea. The operator's verbatim ask of record is
152
+ .spur/run/${vars.__runId}-idea-input.md (persisted at start; authoritative over any
153
+ paraphrase).
147
154
  onEnter:
148
155
  - kind: agent.run
149
156
  options:
150
157
  agent: ${vars.planningAgent}
151
- input: 'Create a feature for the idea: ${vars.idea}. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
158
+ input: 'Create a feature for the idea. The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md — the idea argument persisted verbatim at start; treat it as the authoritative ask. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it; with --json the feature id is returned under the .ref.id envelope (n), not a top-level .id — read it from there. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
152
159
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
153
160
  role: planner
154
161
  expectFile: .spur/run/${vars.__runId}-idea-feature-id.txt
@@ -182,6 +189,9 @@ states:
182
189
  check records FAIL and routes through the capped retry loop via guards that consume the
183
190
  recorded result — it never fails the run (the engine's default onError policy is `fail`,
184
191
  so an in-action check failure would kill the run before the retry edges are evaluated).
192
+ Requirement coverage is MEASURED alongside it by the soft idea-coverage-check shell
193
+ (0887 R4): the recorded coverage status conjuncts into the profile=auto ac-generate
194
+ guards below so uncovered inventory items route through the same capped retry loop.
185
195
  onEnter:
186
196
  - kind: shell
187
197
  options:
@@ -189,7 +199,7 @@ states:
189
199
  - kind: agent.run
190
200
  options:
191
201
  agent: ${vars.planningAgent}
192
- input: "Generate acceptance criteria for feature ${vars.featureId}. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Output only the complete Acceptance Criteria section body, including the gherkin code fence, with no surrounding commentary and no section heading."
202
+ input: "Generate acceptance criteria for feature ${vars.featureId}. The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Tie every scenario to the `## Requirement inventory` of .spur/run/${vars.__runId}-idea-eval-report.md: under each `Scenario:` heading add a comment `# covers: I1, I3` listing covered ids — every non-`[deferred: ...]` item needs at least one covering scenario. Two `spur task check` rules bind AC text: (1) AC bullets copy scenario titles verbatim — the title is the byte-identical identity key of its bullet; (2) gate-language words (HITL, approval/approved, merged/merge event, content-gate, GATED, capstone standalone) are forbidden in titles, bodies, and enum values (L4.gate-language) — rephrase around them. Output only the Acceptance Criteria section body (gherkin fence included), no commentary, no heading."
193
203
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
194
204
  role: planner
195
205
  answerFile: .spur/run/${vars.__runId}-idea-ac-content.md
@@ -221,6 +231,24 @@ states:
221
231
  resultFile: .spur/run/${vars.__runId}-idea-ac-check.status
222
232
  softFail: true
223
233
  timeoutMs: 120000
234
+ # 0887 R4: requirement-inventory ↔ AC coverage, measured once at the same author/revise
235
+ # boundary. Soft shell (exit 0 always): the checker writes the PASS/FAIL status itself and
236
+ # the guards below consume the recorded result — never re-run the checker (0769 pattern).
237
+ # Repo-checkout path first, then the superskill-staged twin (handoff-finalize resolution
238
+ # shape); neither present fails closed to FAIL so readiness degrades visibly instead of
239
+ # silently skipping coverage.
240
+ - kind: shell
241
+ options:
242
+ command: >-
243
+ mkdir -p .spur/run &&
244
+ S=plugins/sp/scripts/idea-coverage-check.ts &&
245
+ if [ ! -f "$S" ]; then S="$(superskill script path sp idea-coverage-check.ts 2>/dev/null)"; fi &&
246
+ if [ -n "$S" ] && [ -f "$S" ]; then
247
+ bun "$S" --run-id "$__runId" --report ".spur/run/$__runId-idea-eval-report.md" --ac ".spur/run/$__runId-idea-ac-content.md" || printf 'FAIL run=%s checker exited nonzero (bun missing or checker crash)\n' "$__runId" > ".spur/run/$__runId-idea-coverage.reason";
248
+ else
249
+ printf 'FAIL run=%s checker not found — run superskill install sp\n' "$__runId" | tee ".spur/run/$__runId-idea-coverage.reason" >&2;
250
+ printf 'FAIL\n' > ".spur/run/$__runId-idea-coverage.status";
251
+ fi
224
252
 
225
253
  - id: feature-check
226
254
  description: >
@@ -235,11 +263,14 @@ states:
235
263
  the transition guards route directly from ac-generate to the appropriate next
236
264
  state, so this state is only entered in interactive mode. On failure, the retry
237
265
  cap routes back to ac-generate (≤3 retries) or escalates to failed.
266
+ Requirement coverage (.spur/run/${vars.__runId}-idea-coverage.status, 0887 R4) is part of
267
+ the recorded results this gate surfaces: a FAIL there means some inventory items have no
268
+ covering scenario — answer no to route back to ac-generate for revision if needed.
238
269
  pause: true
239
270
  onEnter:
240
271
  - kind: hitl.confirm
241
272
  options:
242
- prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? (Failures route back to ac-generate for revision, capped at 3 retries.)"
273
+ prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? Requirement coverage status: $(cat .spur/run/${vars.__runId}-idea-coverage.status 2>/dev/null || echo unknown) — reason: $(cat .spur/run/${vars.__runId}-idea-coverage.reason 2>/dev/null || echo n/a) (.spur/run/${vars.__runId}-idea-coverage.status[.reason]). (Failures route back to ac-generate for revision, capped at 3 retries.)"
243
274
 
244
275
  - id: system-design
245
276
  description: >
@@ -264,7 +295,7 @@ states:
264
295
  - kind: agent.run
265
296
  options:
266
297
  agent: ${vars.planningAgent}
267
- input: 'Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) following the constitution edit rules. Do not write task or feature corpus files directly. Design-review contract (.spur/run/${vars.__runId}-idea-design-review.md, fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): on the first pass write the proposed design summary under `## Proposed design` and leave `## Operator feedback` empty; on retry with operator feedback present, revise the design/ADR artifacts, document the changes under `## Reconciliation`, and when the feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file and persist it via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>` — never edit feature corpus files directly.'
298
+ input: 'The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md (idea persisted verbatim at start; authoritative). Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) per the constitution edit rules; never write task or feature corpus files directly. Design-review contract (fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): first pass — write the proposed summary under `## Proposed design`, leave `## Operator feedback` empty; retry after operator feedback — revise the design/ADR artifacts, document changes under `## Reconciliation`, and when feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file, persist via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>`.'
268
299
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
269
300
  role: planner
270
301
  expectFile: .spur/run/${vars.__runId}-idea-design-review.md
@@ -331,7 +362,7 @@ states:
331
362
  - kind: agent.run
332
363
  options:
333
364
  agent: ${vars.planningAgent}
334
- input: "Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
365
+ input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea argument persisted verbatim at start; authoritative). Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
335
366
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
336
367
  role: planner
337
368
  expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
@@ -422,7 +453,9 @@ states:
422
453
  # project's own computePlanningDigest), so the bounded prompt pins the skill
423
454
  # reference instead of a command; artifacts are gated by answerFile/expectFile.
424
455
  input: >-
425
- Run the ready-prepare stage for feature ${vars.featureId} per sp:spur-dev
456
+ The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea
457
+ argument persisted verbatim at start; authoritative). Run the ready-prepare stage
458
+ for feature ${vars.featureId} per sp:spur-dev
426
459
  references/planning-workflow.md § Step 5.6 (Ready preparation): read
427
460
  .spur/run/${vars.__runId}-idea-batch-create-result.json and write
428
461
  .spur/run/${vars.__runId}-idea-ready.json.
@@ -559,51 +592,60 @@ transitions:
559
592
 
560
593
  # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
561
594
  # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
562
- # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result routes
563
- # directly to the appropriate next state — guards never re-run the CLI (task 0769). Under
564
- # interactive, the always fallback enters the feature-check state whose onEnter hitl.confirm
565
- # pauses for operator confirmation.
595
+ # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result and the
596
+ # recorded requirement-coverage status (0887 R4) route directly to the appropriate next
597
+ # state — guards never re-run the CLI or the checker (task 0769). Under interactive, the
598
+ # always fallback enters the feature-check state whose onEnter hitl.confirm pauses for
599
+ # operator confirmation (coverage is surfaced in that prompt; the operator's answer governs).
566
600
  #
567
- # Auto-skip 1: pass + design route → system-design (design=auto + needs_design != false)
601
+ # Auto-skip 1: pass + coverage + design route → system-design (design=auto + needs_design != false)
568
602
  - from: ac-generate
569
603
  to: system-design
570
- description: "profile=auto, check passed, design route — run system design."
571
- # (warn) 4 test segments: profile gate + captured status + route signal jointly own the
572
- # route; guards never re-run the CLI (0769), so there is nothing smaller to extract.
604
+ description: "profile=auto, check passed, requirements covered, design route — run system design."
605
+ # (warn) 4 commands: one persistent multi-assignment line captures both statuses; `test -a` folds the
606
+ # profile/ac and design/needs pairs (same operand semantics); guards never re-run the CLI (0769).
573
607
  guard:
574
608
  kind: shell
575
609
  options:
576
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
577
- # Auto-skip 2: pass + skip-design route → decompose
610
+ command: >-
611
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" cov_status="$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)";
612
+ test "$profile" = auto -a "$ac_status" = PASS && test "$cov_status" = PASS && test "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
613
+ # Auto-skip 2: pass + coverage + skip-design route → decompose
578
614
  - from: ac-generate
579
615
  to: decompose
580
- description: "profile=auto, check passed, skip-design route — go directly to decompose."
581
- # (warn) 5 test segments: same as auto-skip 1 plus the OR'd design=auto/needs_design=false
582
- # pair that keeps one captured signal file authoritative for both routes (0769).
616
+ description: "profile=auto, check passed, requirements covered, skip-design route — go directly to decompose."
617
+ # (warn) 4 test segments: profile + inlined ac/coverage reads + the OR'd design=skip/(design=auto AND
618
+ # needs_design=false) pair folded into one `test` (`-a` binds tighter than `-o`), keeping one captured
619
+ # signal file authoritative for both routes (0769).
583
620
  guard:
584
621
  kind: shell
585
622
  options:
586
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && (test "$design" = skip || (test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false))'
587
- # Auto-skip 3: check failed, retry < 3 → loop back to ac-generate (self-loop)
623
+ command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" = PASS && test "$design" = skip -o "$design" = auto -a "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false'
624
+ # Auto-skip 3: check or coverage failed, retry < 3 → loop back to ac-generate (self-loop)
588
625
  - from: ac-generate
589
626
  to: ac-generate
590
- description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
591
- # (warn) 4 test segments: profile gate + captured status + captured retry count; the
592
- # retry loop is the smallest honest formulation of cap<3 routing (0769).
627
+ description: "profile=auto, check or coverage failed, retry cap not reached — re-run ac-generate."
628
+ # (warn) 5 commands: one persistent multi-assignment line captures both statuses and the retry count
629
+ # (with its 0 fallback); the failed-check pair stays verbatim and the profile gate folds into the retry
630
+ # test via `test -a`; the retry loop is the smallest honest formulation of cap<3 routing (0769).
593
631
  guard:
594
632
  kind: shell
595
633
  options:
596
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
597
- # Auto-skip 4: check failed, retry cap reached → escalate to failed
634
+ command: >-
635
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" cov_status="$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
636
+ { test "$ac_status" != PASS || test "$cov_status" != PASS; } && test "$profile" = auto -a "$retry" -lt 3
637
+ # Auto-skip 4: check or coverage failed, retry cap reached → escalate to failed
598
638
  - from: ac-generate
599
639
  to: failed
600
- description: "profile=auto, check failed after 3 retries — escalate to failed."
601
- # (warn) 4 test segments: mirror of the retry guard with cap>=3; keeping escalation and
602
- # retry as one test-chain pair makes the cap boundary auditable in the diff (0769).
640
+ description: "profile=auto, check or coverage failed after 3 retries — escalate to failed."
641
+ # (warn) 5 commands: mirror of the retry guard with cap>=3; keeping escalation and retry as one
642
+ # test-chain pair makes the cap boundary auditable in the diff (0769).
603
643
  guard:
604
644
  kind: shell
605
645
  options:
606
- command: 'test "$profile" = auto && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
646
+ command: >-
647
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" cov_status="$(cat .spur/run/$__runId-idea-coverage.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
648
+ { test "$ac_status" != PASS || test "$cov_status" != PASS; } && test "$profile" = auto -a "$retry" -ge 3
607
649
  # Interactive fallback: enter feature-check HITL gate
608
650
  - from: ac-generate
609
651
  to: feature-check
@@ -616,12 +658,14 @@ transitions:
616
658
  - from: feature-check
617
659
  to: system-design
618
660
  description: Feature check passed, design route — run system design.
619
- # (warn) 4 test segments: HITL answer + captured status + design route; same shape as the
661
+ # (warn) 5 commands (named status capture + 4 conditions): HITL answer + captured status + design route; same shape as the
620
662
  # ac-generate auto-skips, reading the same captured signal files (0769).
621
663
  guard:
622
664
  kind: shell
623
665
  options:
624
- command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
666
+ command: >-
667
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
668
+ test "$__hitlAnswer" = yes && test "$ac_status" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
625
669
  - from: feature-check
626
670
  to: decompose
627
671
  description: Feature check passed, skip-design route — go directly to decompose.
@@ -634,20 +678,24 @@ transitions:
634
678
  - from: feature-check
635
679
  to: ac-generate
636
680
  description: "Feature check failed — revise AC (retry cap: 3)."
637
- # (warn) 4 test segments: HITL reject/failed-status OR-pair + captured retry count; the
681
+ # (warn) 5 commands (named status capture + 4 conditions): HITL reject/failed-status OR-pair + captured retry count; the
638
682
  # interactive retry mirror of the ac-generate cap<3 guard (0769).
639
683
  guard:
640
684
  kind: shell
641
685
  options:
642
- command: '(test "$__hitlAnswer" = no || test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
686
+ command: >-
687
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
688
+ (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
643
689
  - from: feature-check
644
690
  to: failed
645
691
  description: Feature check failed after 3 retries — escalate to failed.
646
- # (warn) 4 test segments: mirror of the revise guard with cap>=3 (0769).
692
+ # (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
647
693
  guard:
648
694
  kind: shell
649
695
  options:
650
- command: '(test "$__hitlAnswer" = no || test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
696
+ command: >-
697
+ ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
698
+ (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
651
699
  - from: feature-check
652
700
  to: cancelled
653
701
  description: Operator cancelled the feature-check gate.
@@ -603,10 +603,14 @@ transitions:
603
603
  - from: precheck
604
604
  to: implement
605
605
  description: Deterministic size, evidence, and task checks are green — begin implementation.
606
+ # (warn) 5 commands: named size/evidence status reads + task check (legibility, 0874).
606
607
  guard:
607
608
  kind: shell
608
609
  options:
609
- command: 'test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
610
+ command: >-
611
+ size_status="$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)";
612
+ evidence_status="$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)";
613
+ test "$size_status" = PASS && test "$evidence_status" = PASS && $spurBin task check $wbs
610
614
  - from: precheck
611
615
  to: failed
612
616
  description: Size and/or task check failed — stop before implement.
@@ -626,14 +630,18 @@ transitions:
626
630
  guard:
627
631
  kind: shell
628
632
  options:
629
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
633
+ command: >-
634
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
635
+ test "$gate_status" = PASS && test "$mode" = fast
630
636
  - from: test
631
637
  to: review
632
638
  description: Quality gate already green and safety mode — proceed to review.
633
639
  guard:
634
640
  kind: shell
635
641
  options:
636
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
642
+ command: >-
643
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
644
+ test "$gate_status" = PASS && test "$mode" != fast
637
645
  - from: test
638
646
  to: test-fix
639
647
  description: Quality gate red — start bounded fixall loop.
@@ -659,28 +667,40 @@ transitions:
659
667
  guard:
660
668
  kind: shell
661
669
  options:
662
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
670
+ command: >-
671
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
672
+ test "$gate_status" = PASS && test "$mode" = fast
663
673
  - from: test-recheck
664
674
  to: review
665
675
  description: Quality gate green after fixall and safety mode — proceed to review.
666
676
  guard:
667
677
  kind: shell
668
678
  options:
669
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
679
+ command: >-
680
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
681
+ test "$gate_status" = PASS && test "$mode" != fast
670
682
  - from: test-recheck
671
683
  to: test-fix
672
684
  description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
685
+ # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
673
686
  guard:
674
687
  kind: shell
675
688
  options:
676
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"'
689
+ command: >-
690
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
691
+ fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
692
+ test "$gate_status" = FAIL && test "$fix_attempts" -lt "$qualityGateMaxFixAttempts"
677
693
  - from: test-recheck
678
694
  to: failed
679
695
  description: Still red after max fixall attempts — stop at failed (not silent abort).
696
+ # (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
680
697
  guard:
681
698
  kind: shell
682
699
  options:
683
- command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -ge "$qualityGateMaxFixAttempts"'
700
+ command: >-
701
+ gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
702
+ fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
703
+ test "$gate_status" = FAIL && test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
684
704
  # Defense: corrupt recheck status — failed, not review.
685
705
  - from: test-recheck
686
706
  to: failed
@@ -748,7 +768,16 @@ transitions:
748
768
  kind: shell
749
769
  options:
750
770
  command: >-
751
- jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '.verdict == "PASS" and (.proof.digest // "") == $d and (.proof.stages.qualityGate.digest // "") == $d and (.proof.stages.review.digest // "") == $d and (.proof.stages.review.status // "") == "completed" and (.proof.stages.verification.digest // "") == $d and (.proof.runId // "") == $r and (.proof.definitionDigest // "") == $dd' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
771
+ jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '
772
+ .verdict == "PASS"
773
+ and (.proof.digest // "") == $d
774
+ and (.proof.stages.qualityGate.digest // "") == $d
775
+ and (.proof.stages.review.digest // "") == $d
776
+ and (.proof.stages.review.status // "") == "completed"
777
+ and (.proof.stages.verification.digest // "") == $d
778
+ and (.proof.runId // "") == $r
779
+ and (.proof.definitionDigest // "") == $dd
780
+ ' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
752
781
  - from: verify
753
782
  to: test-fix
754
783
  description: >-
@@ -760,8 +789,8 @@ transitions:
760
789
  kind: shell
761
790
  options:
762
791
  command: >-
763
- V="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
764
- test -n "$V" && test "$V" != PASS &&
792
+ verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
793
+ test -n "$verdict" && test "$verdict" != PASS &&
765
794
  test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"
766
795
  - from: verify
767
796
  to: failed
@@ -778,6 +807,7 @@ transitions:
778
807
  - from: record
779
808
  to: done
780
809
  description: Task check passed and the verdict proof block still names the captured digest — certify done.
810
+ # (warn) 5 commands: named verdict + proof digest reads after the task check (legibility, 0874).
781
811
  guard:
782
812
  kind: shell
783
813
  options:
@@ -788,8 +818,9 @@ transitions:
788
818
  # one shell line.
789
819
  command: >-
790
820
  $spurBin task check $wbs --as done &&
791
- test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
792
- test "$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
821
+ verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" &&
822
+ proof_digest="$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
823
+ test "$verdict" = PASS && test "$proof_digest" = "$proofDigest"
793
824
  - from: record
794
825
  to: failed
795
826
  description: Task check failed or proof evidence missing/malformed/mismatched — block before done.
@@ -13,10 +13,12 @@
13
13
  # pauses,
14
14
  # even under --auto (Iron Law #6: irreversible action -> surface to human).
15
15
  #
16
- # Shape: start -> task-resolve -> doc-sync -> metrics-record
16
+ # Shape: start -> task-resolve -> doc-sync -> learnings-append -> metrics-record
17
17
  # -> feature-transition (conditional: if vars.feature set)
18
18
  # -> branch-cleanup (conditional: if vars.merge=true)
19
19
  # -> done
20
+ # doc-sync routes a contract violation to `repair` (cheap, no
21
+ # re-dispatch — ADR-118 pilot) and an executor failure to `failed`;
20
22
  # task-resolve with a VALIDATED empty list short-circuits to `skipped`;
21
23
  # invalid wrap input, an unresolved/non-completed task, a failed metrics
22
24
  # capture, a failed required sync, or a failed feature gate routes to
@@ -72,7 +74,7 @@
72
74
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
73
75
  kind: state-machine
74
76
  name: wrapup-pipeline
75
- version: "3"
77
+ version: "4"
76
78
  description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
77
79
  iterationBound: 10
78
80
  initialState: start
@@ -168,13 +170,16 @@ states:
168
170
  learnings (task 0607 R2: two adjacent agent.run hops merged, count 2 -> 1;
169
171
  no operator-decision pause or verdict boundary sits between them). No dedicated
170
172
  slash command yet — free-form skill pointer until a /sp:doc-evolve command
171
- exists (ADR-043 residual). Capture+shell decoupling preserved: expectFile gates
172
- a non-empty capture before the append; append is best-effort soft (empty capture
173
- skips without aborting wrap-up).
173
+ exists (ADR-043 residual). This is the expensive stage; a contract violation
174
+ (a clean exit that missed its declared answerFile/expectFile post-condition)
175
+ routes to the cheap `repair` edge instead of a full re-dispatch (ADR-118
176
+ pilot, 0871) — `onError: continue` lets the transition guards read the
177
+ agent's result instead of halting the run.
174
178
  onEnter:
175
179
  # (warn) free-form prompt: no /sp:doc-evolve command surface yet (ADR-043
176
180
  # residual, 0607 R2 one-query merge); capture is bounded by expectFile.
177
181
  - kind: agent.run
182
+ onError: continue
178
183
  options:
179
184
  agent: ${vars.agent}
180
185
  input: >-
@@ -192,6 +197,13 @@ states:
192
197
  answerFile: .spur/run/${vars.__runId}-wrapup-learnings.md
193
198
  expectFile: .spur/run/${vars.__runId}-wrapup-learnings.md
194
199
  timeoutMs: ${vars.stepTimeoutMs}
200
+
201
+ - id: learnings-append
202
+ description: >
203
+ Best-effort soft append of the captured learnings to .spur/memory/learnings.md
204
+ (0607 R2): an empty capture skips without aborting wrap-up. Runs only after
205
+ doc-sync's contract held; a contract violation never reaches this state.
206
+ onEnter:
195
207
  # (warn) learnings append is intentionally soft (empty capture skips
196
208
  # without aborting wrap-up, 0607 R2).
197
209
  - kind: shell
@@ -207,6 +219,21 @@ states:
207
219
  fi &&
208
220
  exit 0
209
221
 
222
+ - id: repair
223
+ description: >
224
+ Contract-violation repair (ADR-118 pilot, 0871): doc-sync's agent.run missed
225
+ its declared post-condition after a clean exit, so wrap-up does NOT re-dispatch
226
+ the expensive agent on this attempt. The miss is recorded to a run-scoped
227
+ repair status and wrap-up proceeds to metrics-record — the run log and action
228
+ trace already name the violated contract and the observed value.
229
+ onEnter:
230
+ - kind: shell
231
+ options:
232
+ command: >-
233
+ mkdir -p .spur/run &&
234
+ printf 'contract-violation: doc-sync learnings capture missed its declared contract — repair skipped re-dispatch (ADR-118 pilot)\n' > ".spur/run/$__runId-wrapup-repair.status" &&
235
+ exit 0
236
+
210
237
  - id: metrics-record
211
238
  description: >
212
239
  Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
@@ -354,10 +381,46 @@ transitions:
354
381
  guard:
355
382
  kind: always
356
383
 
357
- # ── linear body: doc-sync -> metrics-record ──
384
+ # ── doc-sync: contract-first routing (ADR-118 pilot, 0871) ──
385
+ # Declaration order matters: the contract-violation edge is tried first, then
386
+ # success (action-ok), then the executor-failure defense (always) — a failed
387
+ # agent.run reaches these guards because doc-sync's agent.run declares
388
+ # onError: continue.
358
389
  - from: doc-sync
390
+ to: repair
391
+ description: >
392
+ Contract violation (ADR-118): the agent exited 0 but missed its declared
393
+ post-condition. Route to the cheap repair edge — never re-dispatch the
394
+ full agent.run on this attempt.
395
+ trigger: contract-violation
396
+ guard:
397
+ kind: contract-violation
398
+ - from: doc-sync
399
+ to: learnings-append
400
+ description: Doc drift repair + learning capture succeeded — append learnings.
401
+ guard:
402
+ kind: action-ok
403
+ - from: doc-sync
404
+ to: failed
405
+ description: >
406
+ Executor failure (non-zero exit, signal, or dispatch error) — keep the
407
+ existing fail semantics; a contract violation never falls through here
408
+ because the contract-violation edge is declared first.
409
+ trigger: executor-failure
410
+ guard:
411
+ kind: always
412
+
413
+ # ── linear body: learnings-append -> metrics-record ──
414
+ - from: learnings-append
415
+ to: metrics-record
416
+ description: Learning capture appended (or soft-skipped) — record metrics.
417
+ guard:
418
+ kind: always
419
+
420
+ # ── repair: cheap path, no re-dispatch -> metrics-record ──
421
+ - from: repair
359
422
  to: metrics-record
360
- description: Doc drift repair + learning capture complete — record metrics.
423
+ description: Contract-violation repair recorded — proceed to metrics without re-dispatch.
361
424
  guard:
362
425
  kind: always
363
426
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.85",
3
+ "version": "0.3.87",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -53,14 +53,14 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@commander-js/extra-typings": "^14.0.0",
56
- "@gobing-ai/ts-db": "^0.4.66",
57
- "@gobing-ai/ts-ai-runner": "^0.4.66",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.66",
59
- "@gobing-ai/ts-infra": "^0.4.66",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.66",
61
- "@gobing-ai/ts-rule-engine": "^0.4.66",
62
- "@gobing-ai/ts-runtime": "^0.4.66",
63
- "@gobing-ai/ts-utils": "^0.4.66",
56
+ "@gobing-ai/ts-db": "^0.4.67",
57
+ "@gobing-ai/ts-ai-runner": "^0.4.67",
58
+ "@gobing-ai/ts-dual-workflow-engine": "^0.4.67",
59
+ "@gobing-ai/ts-infra": "^0.4.67",
60
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.4.67",
61
+ "@gobing-ai/ts-rule-engine": "^0.4.67",
62
+ "@gobing-ai/ts-runtime": "^0.4.67",
63
+ "@gobing-ai/ts-utils": "^0.4.67",
64
64
  "@types/bun": "1.3.14",
65
65
  "@types/figlet": "^1.7.0",
66
66
  "@types/node-notifier": "8.0.5",