@gobing-ai/spur 0.3.30 → 0.3.32

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 (68) hide show
  1. package/config/rules/strict/runtime-boundaries.yaml +2 -1
  2. package/config/workflows/basic.yaml +92 -14
  3. package/config/workflows/docs-pipeline.yaml +71 -25
  4. package/config/workflows/feature-dev.yaml +104 -35
  5. package/config/workflows/feature-lifecycle.yaml +10 -2
  6. package/config/workflows/idea-pipeline.yaml +118 -54
  7. package/config/workflows/planning-pipeline.yaml +78 -30
  8. package/config/workflows/task-lifecycle.yaml +10 -2
  9. package/config/workflows/task-pipeline.yaml +219 -56
  10. package/config/workflows/wayfinder-resolution.yaml +64 -27
  11. package/config/workflows/wrapup-pipeline.yaml +79 -39
  12. package/package.json +9 -9
  13. package/schemas/state-machine-workflow.schema.json +5 -0
  14. package/spur.js +1302 -707
  15. package/web/_astro/BoardApp.CBVEjvSW.js +1 -0
  16. package/web/_astro/{BoardApp.BwAdAgK9.js → BoardApp.CyyIiK76.js} +74 -74
  17. package/web/_astro/TaskDetail.BfUuRsfM.js +1 -0
  18. package/web/_astro/{arc.CO-I_PZe.js → arc.C7fql4LL.js} +1 -1
  19. package/web/_astro/{architectureDiagram-3BPJPVTR.Si-A1Ncl.js → architectureDiagram-3BPJPVTR.IJPsRX-V.js} +1 -1
  20. package/web/_astro/{blockDiagram-GPEHLZMM.CeNy_ta6.js → blockDiagram-GPEHLZMM.B1AUwBxq.js} +1 -1
  21. package/web/_astro/{c4Diagram-AAUBKEIU.BgN_scEU.js → c4Diagram-AAUBKEIU.BuSJPR3z.js} +1 -1
  22. package/web/_astro/channel.D3uZSASb.js +1 -0
  23. package/web/_astro/{chunk-2J33WTMH.C3oDtmdi.js → chunk-2J33WTMH.CaOPTr9l.js} +1 -1
  24. package/web/_astro/{chunk-4BX2VUAB.Bv8Fxh4H.js → chunk-4BX2VUAB.DQzxExn8.js} +1 -1
  25. package/web/_astro/{chunk-55IACEB6.BT-TqtV6.js → chunk-55IACEB6.IIb103c7.js} +1 -1
  26. package/web/_astro/{chunk-727SXJPM.DkGMsI0B.js → chunk-727SXJPM.DOYz1K1b.js} +1 -1
  27. package/web/_astro/{chunk-AQP2D5EJ.B_-SqGnF.js → chunk-AQP2D5EJ.CFcfW-wt.js} +1 -1
  28. package/web/_astro/{chunk-FMBD7UC4.DwR3skMv.js → chunk-FMBD7UC4.C7Hn0jo3.js} +1 -1
  29. package/web/_astro/{chunk-ND2GUHAM.BaTQutFl.js → chunk-ND2GUHAM.Ba83VQ1m.js} +1 -1
  30. package/web/_astro/{chunk-QZHKN3VN.BJMSqqJp.js → chunk-QZHKN3VN.CizNbIIP.js} +1 -1
  31. package/web/_astro/{classDiagram-4FO5ZUOK.SaqCNUPF.js → classDiagram-4FO5ZUOK.2NYYPXHq.js} +1 -1
  32. package/web/_astro/{classDiagram-v2-Q7XG4LA2.SaqCNUPF.js → classDiagram-v2-Q7XG4LA2.2NYYPXHq.js} +1 -1
  33. package/web/_astro/{cose-bilkent-S5V4N54A.BkmjA_E2.js → cose-bilkent-S5V4N54A.DwBqVzml.js} +1 -1
  34. package/web/_astro/{dagre-BM42HDAG.CNUvWhrX.js → dagre-BM42HDAG.DqnYsHWG.js} +1 -1
  35. package/web/_astro/{diagram-2AECGRRQ.C68Cg9uE.js → diagram-2AECGRRQ.D8TKhgy-.js} +1 -1
  36. package/web/_astro/{diagram-5GNKFQAL.DbHkPLEn.js → diagram-5GNKFQAL.BUD6Prj5.js} +1 -1
  37. package/web/_astro/{diagram-KO2AKTUF.Dt0EdErs.js → diagram-KO2AKTUF.Cvg7hmND.js} +1 -1
  38. package/web/_astro/{diagram-LMA3HP47.Bixs6mnm.js → diagram-LMA3HP47.FUG_B9eS.js} +1 -1
  39. package/web/_astro/{diagram-OG6HWLK6.B4KBI6KT.js → diagram-OG6HWLK6.XsBZQ0hQ.js} +1 -1
  40. package/web/_astro/{erDiagram-TEJ5UH35.C_4i9Xvx.js → erDiagram-TEJ5UH35.M_LSGgLQ.js} +1 -1
  41. package/web/_astro/{flowDiagram-I6XJVG4X.CfGFczAY.js → flowDiagram-I6XJVG4X.DZikZBRa.js} +1 -1
  42. package/web/_astro/{ganttDiagram-6RSMTGT7.C2AIvtmg.js → ganttDiagram-6RSMTGT7.DTKuEmXY.js} +1 -1
  43. package/web/_astro/{gitGraphDiagram-PVQCEYII.HlXuGqQg.js → gitGraphDiagram-PVQCEYII.Dt-E9XmY.js} +1 -1
  44. package/web/_astro/index.yAse9IaO.css +1 -0
  45. package/web/_astro/{infoDiagram-5YYISTIA.BWQJsJ4Z.js → infoDiagram-5YYISTIA.D5snevU1.js} +1 -1
  46. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CCHgIHik.js → ishikawaDiagram-YF4QCWOH.DmXdBf9W.js} +1 -1
  47. package/web/_astro/{journeyDiagram-JHISSGLW.D9M6YwOJ.js → journeyDiagram-JHISSGLW.HPJSRkkU.js} +1 -1
  48. package/web/_astro/{kanban-definition-UN3LZRKU.K20D-a1_.js → kanban-definition-UN3LZRKU.BxCqCnL1.js} +1 -1
  49. package/web/_astro/{linear.DeP5Qsol.js → linear.BnXQUpS-.js} +1 -1
  50. package/web/_astro/{mermaid.core.ClzxNMPU.js → mermaid.core.D-ZeOmpg.js} +4 -4
  51. package/web/_astro/{mindmap-definition-RKZ34NQL.CGn9RUNj.js → mindmap-definition-RKZ34NQL.DxrftiFR.js} +1 -1
  52. package/web/_astro/{pieDiagram-4H26LBE5.CoA92Csb.js → pieDiagram-4H26LBE5.D-ZcWxwg.js} +1 -1
  53. package/web/_astro/{quadrantDiagram-W4KKPZXB.CJ_UPjM1.js → quadrantDiagram-W4KKPZXB.C-dVJ7GW.js} +1 -1
  54. package/web/_astro/{requirementDiagram-4Y6WPE33.KkEPNNUU.js → requirementDiagram-4Y6WPE33.lalE_R8X.js} +1 -1
  55. package/web/_astro/{sankeyDiagram-5OEKKPKP.CoCWbQgh.js → sankeyDiagram-5OEKKPKP.C4uPynAF.js} +1 -1
  56. package/web/_astro/{sequenceDiagram-3UESZ5HK.DUFpV3EP.js → sequenceDiagram-3UESZ5HK.uBOenkqh.js} +1 -1
  57. package/web/_astro/{stateDiagram-AJRCARHV.CgWX0_9m.js → stateDiagram-AJRCARHV.CQOusSP2.js} +1 -1
  58. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BKUXeGSf.js → stateDiagram-v2-BHNVJYJU.Bw0ZdjYb.js} +1 -1
  59. package/web/_astro/{timeline-definition-PNZ67QCA.CXayAguY.js → timeline-definition-PNZ67QCA.DbkPA5ce.js} +1 -1
  60. package/web/_astro/{vennDiagram-CIIHVFJN.DPak_Elh.js → vennDiagram-CIIHVFJN.CIKhzwoA.js} +1 -1
  61. package/web/_astro/{wardley-L42UT6IY.t3ZlDKd0.js → wardley-L42UT6IY.0P-tqaFs.js} +1 -1
  62. package/web/_astro/{wardleyDiagram-YWT4CUSO.CwPSVASQ.js → wardleyDiagram-YWT4CUSO.DrwTvM_M.js} +1 -1
  63. package/web/_astro/{xychartDiagram-2RQKCTM6.CJfrQgpF.js → xychartDiagram-2RQKCTM6.BgJsQVms.js} +1 -1
  64. package/web/index.html +2 -2
  65. package/web/_astro/BoardApp.Bk3T0o6s.js +0 -1
  66. package/web/_astro/TaskDetail.L36slEqE.js +0 -1
  67. package/web/_astro/channel.CYdGnFHM.js +0 -1
  68. package/web/_astro/index.D5Wqk2Tw.css +0 -1
@@ -6,20 +6,28 @@
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 → review → approve(HITL) → verify → record → done
9
+ # Shape: precheck → implement → test[→test-fix↔test-recheck] → review → approve(HITL)
10
+ # → verify → record → done
10
11
  # (precheck failure short-circuits to `failed`; approve routes to `failed` on
11
12
  # operator rejection or `cancelled` on operator cancel — R1, bug-750).
13
+ # `test` is the project quality gate (shell + bounded /sp:dev-fixall), not
14
+ # /sp:dev-unit (coverage gap-fill; router C3/C5).
12
15
  #
13
16
  # Vars (passed as a JSON object via `--vars`):
14
- # wbs the task WBS (required), e.g. `--vars '{"wbs":"0042"}'`
15
- # profile set `--vars '{"profile":"auto"}'` to skip the HITL approve gate (R4)
17
+ # wbs — task WBS (required)
18
+ # profile — "auto" skips HITL approve (R4)
19
+ # agent / spurBin — executor + spur binary (CLI overrides spurBin)
20
+ # stepTimeoutMs — agent.run budget for review/verify/test-fix (ms)
21
+ # implementTimeoutMs — implement agent.run budget (ms)
22
+ # qualityGateCmd — project gate (default: bun run autofix && bun run spur-check)
23
+ # qualityGateMaxFixAttempts — max /sp:dev-fixall hops after a red gate (default: 2)
16
24
  #
17
- # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
25
+ # Seeded by `spur init`. agent.run inputs are pure slash commands (ADR-043).
18
26
 
19
27
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
20
28
  kind: state-machine
21
29
  name: task-pipeline
22
- description: "Standard task execution pipeline (precheck → implement → test → review → approve → verify → record → done)"
30
+ description: "Standard task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → record → done"
23
31
  # Task entity lifecycle (owned by task-lifecycle.yaml):
24
32
  # normal: backlog → todo → wip → testing → done
25
33
  # blocked is bidirectional with todo/wip/testing; done may reopen to wip.
@@ -35,6 +43,9 @@ terminalStates:
35
43
  - done
36
44
  - failed
37
45
  - cancelled
46
+ failureStates:
47
+ - failed
48
+ - cancelled
38
49
  vars:
39
50
  wbs: "0000"
40
51
  profile: "standard"
@@ -46,17 +57,11 @@ vars:
46
57
  # `--vars '{"agent":"claude"}'`. Pinned (not left to the AiRunner's <default>
47
58
  # selection) so a broken/misconfigured agent on the box can't silently capture the run.
48
59
  agent: "omp"
49
- # Step-level timeout for agent.run steps in milliseconds. A timed-out agent
50
- # subprocess is killed (not abandoned) and the step fails pipeline routes to
51
- # `failed`. Override per run with `--vars '{"stepTimeoutMs":120000}'`.
52
- # Raised 600s 1800s (task 0398 R4). The H6 dogfood batch lost 3 of its 4
53
- # timeouts on `test` steps hitting exactly the 600s wall (0391, 0395 test;
54
- # `.spur/run/*-test-partial.md`), the same failure signature that justified
55
- # implementTimeoutMs below. Agentic test/review/verify steps read the diff,
56
- # run the suite, and write a section — that does not fit in 10 min on this
57
- # corpus. Same honesty rule as implementTimeoutMs: 30 min is headroom, not a
58
- # licence to run unbounded. If a step still hits this wall, STOP and record it
59
- # rather than raising again without operator sign-off.
60
+ # Step-level timeout for agentic hops (review / verify / test-fix) in ms.
61
+ # Soft quality-gate shells are unbounded by this var (host shell only).
62
+ # Raised 600s 1800s (task 0398 R4 / H6 dogfood). Override:
63
+ # `--vars '{"stepTimeoutMs":"120000"}'`. 30 min is headroom, not a licence to
64
+ # run unbounded if a hop still hits this wall, STOP and record it.
60
65
  stepTimeoutMs: "1800000"
61
66
  # Timeout for the `implement` step specifically (R2a). Implementation is the
62
67
  # heaviest agent.run step (full read/write/test-probe loop) and has timed out
@@ -67,16 +72,41 @@ vars:
67
72
  # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
68
73
  # Empty by default; only meaningful once the approve state has been entered.
69
74
  __hitlAnswer: ""
75
+ # Project quality gate for the `test` hop (probe + fixall + recheck). Override per project
76
+ # with the same package-manager surface (this monorepo is Bun-only):
77
+ # `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
78
+ # and the fixall slash input all use this same var so the command stays single-sourced.
79
+ # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
80
+ # interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
81
+ qualityGateCmd: "bun run autofix && bun run spur-check"
82
+ # Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
83
+ # Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
84
+ qualityGateMaxFixAttempts: "2"
85
+ # Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
86
+ # project can point it at its own formatter; invoked best-effort (a missing or
87
+ # failing formatter must never abort a run — the quality gate is the real gate).
88
+ formatCmd: "bun run format"
70
89
 
71
90
  states:
72
91
  - id: precheck
73
92
  description: >
74
- Pre-flight structural validation. The precheck→implement guard runs
75
- `spur task check <wbs>`; a failure routes to `failed` (blocked report).
93
+ Pre-flight: soft agent doctor + transition guards for doctor PASS and
94
+ `spur task check <wbs>`. Failures route to the `failed` terminal state
95
+ (not a raw lifecycle abort mid-enter).
76
96
  onEnter:
97
+ # Soft doctor — write status and always exit 0 so transitions can branch to
98
+ # `failed` cleanly (same pattern as the quality-gate soft probe).
77
99
  - kind: shell
78
100
  options:
79
- command: "${vars.spurBin} agent doctor ${vars.agent}"
101
+ command: >-
102
+ mkdir -p .spur/run &&
103
+ DOCTOR_FILE=".spur/run/$wbs-precheck-doctor.status" &&
104
+ if $spurBin agent doctor $agent; then
105
+ echo PASS > "$DOCTOR_FILE";
106
+ else
107
+ echo FAIL > "$DOCTOR_FILE";
108
+ fi &&
109
+ exit 0
80
110
  - kind: note
81
111
  options:
82
112
  # R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
@@ -101,40 +131,106 @@ states:
101
131
  - kind: agent.run
102
132
  options:
103
133
  agent: ${vars.agent}
104
- input: >-
105
- /sp:dev-run --mode implement ${vars.wbs} --auto.
106
- Work only in this working tree on task ${vars.wbs}; NEVER invoke
107
- `spur workflow run` or `/sp:dev-run` without `--mode implement` —
108
- this step IS the pipeline (bug-742).
134
+ # Pure slash command only (ADR-043). Anti-recursion / implement discipline
135
+ # lives in /sp:dev-run --mode implement sp:code-implementation, not YAML prose.
136
+ input: /sp:dev-run --mode implement ${vars.wbs} --auto
109
137
  timeoutMs: ${vars.implementTimeoutMs}
138
+ # R3 (task 0424): empty-implement no-op guard — the agent.run action
139
+ # fails the step when exit 0 produced zero non-corpus file changes, so
140
+ # a silent no-op routes the run to `failed` here instead of drifting
141
+ # into test/review and being caught a full pass later.
142
+ requireDiff: true
110
143
  - kind: shell
111
144
  options:
112
- command: "${vars.spurBin} task update ${vars.wbs} wip --no-lifecycle"
145
+ command: "$spurBin task update $wbs wip --no-lifecycle"
113
146
  # Post-implement cleanup: auto-format any unformatted output the agent produced.
114
147
  # The agent may leave files that don't pass `biome check`, which then fails
115
148
  # `bun run lint` downstream. Running format here is cheap (< 1 s) and prevents
116
149
  # the test stage from tripping on a purely mechanical formatting defect (dogfood
117
150
  # bug-733 — omp left agent-run.test.ts unformatted, causing a spurious lint gate
118
151
  # failure that the ## Testing section mis-attributed to "pre-existing gaps").
152
+ # Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
153
+ # non-Bun project cannot abort a run mid-implement. Formatting is a convenience;
154
+ # `${vars.qualityGateCmd}` at `test` is the gate that actually decides.
119
155
  - kind: shell
120
156
  options:
121
- command: "bun run format"
157
+ command: '$formatCmd ; exit 0'
122
158
 
159
+ # ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
160
+ # NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
161
+ # a coverage target; it is not the project quality gate. Coverage gap-fill remains
162
+ # available via router C3/C5 (`/sp:dev-unit`) or inside implement — not this hop.
163
+ #
164
+ # Shape (engine has no per-action "run next only if previous failed" without
165
+ # `onError`, and Spur's action schema does not expose `onError`):
166
+ # test — soft probe: qualityGateCmd → PASS|FAIL status; always exit 0
167
+ # (green path: one gate run → review; no fixall/recheck tax)
168
+ # test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
169
+ # test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
123
170
  - id: test
124
- description: Test execution + coverage via /sp:dev-unit.
171
+ description: >
172
+ Soft quality-gate probe (single full gate on the green path). Runs
173
+ `${vars.qualityGateCmd}`, records PASS|FAIL under
174
+ `.spur/run/<wbs>-test-gate.status`, resets the fix attempt counter, always exit 0.
125
175
  onEnter:
176
+ - kind: shell
177
+ options:
178
+ command: >-
179
+ mkdir -p .spur/run &&
180
+ STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
181
+ ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
182
+ echo 0 > "$ATTEMPT_FILE" &&
183
+ set +e &&
184
+ # sh -c re-parses multi-command gates (&&, ;); do NOT use bare `$qualityGateCmd`
185
+ # word-split (breaks on &&). qualityGateCmd is pipeline-owned trusted config only.
186
+ ( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
187
+ if [ "$gate_rc" -eq 0 ]; then
188
+ printf 'PASS\n' > "$STATUS_FILE";
189
+ else
190
+ printf 'FAIL\n' > "$STATUS_FILE";
191
+ fi &&
192
+ exit 0
193
+
194
+ - id: test-fix
195
+ description: >
196
+ Bounded auto-fix hop when the quality gate is red. Increments
197
+ `.spur/run/<wbs>-test-fix-attempt`, then pure slash (ADR-043)
198
+ `/sp:dev-fixall` against `${vars.qualityGateCmd}`.
199
+ onEnter:
200
+ - kind: shell
201
+ options:
202
+ command: >-
203
+ mkdir -p .spur/run &&
204
+ ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
205
+ n=$(cat "$ATTEMPT_FILE" 2>/dev/null || echo 0) &&
206
+ printf '%s\n' "$((n + 1))" > "$ATTEMPT_FILE"
126
207
  - kind: agent.run
127
208
  options:
128
209
  agent: ${vars.agent}
129
- input: /sp:dev-unit ${vars.wbs} --auto
210
+ input: /sp:dev-fixall "${vars.qualityGateCmd}"
130
211
  timeoutMs: ${vars.stepTimeoutMs}
131
- # Post-test gate: run the project's full lint gate (biome check + tsc). If the
132
- # implement or test step left unformatted files or type errors, the run routes to
133
- # `failed` rather than letting an agent-authored `## Testing` claim "all pass"
134
- # against a red gate (dogfood bug-733).
212
+
213
+ - id: test-recheck
214
+ description: >
215
+ Soft recheck after fixall. Writes PASS|FAIL (always exit 0). Transitions
216
+ branch to review (PASS), another test-fix (FAIL and under max attempts),
217
+ or the pipeline `failed` state (FAIL and attempts exhausted) — never a
218
+ raw lifecycle abort that skips the terminal `failed` state.
219
+ onEnter:
135
220
  - kind: shell
136
221
  options:
137
- command: "bun run lint"
222
+ command: >-
223
+ mkdir -p .spur/run &&
224
+ STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
225
+ set +e &&
226
+ # See test probe: sh -c for multi-command; qualityGateCmd is trusted config only.
227
+ ( sh -c "$qualityGateCmd" ); gate_rc=$?; set -e &&
228
+ if [ "$gate_rc" -eq 0 ]; then
229
+ printf 'PASS\n' > "$STATUS_FILE";
230
+ else
231
+ printf 'FAIL\n' > "$STATUS_FILE";
232
+ fi &&
233
+ exit 0
138
234
 
139
235
  - id: review
140
236
  description: Three-dimensional code review via /sp:dev-review (functional requirements traceability + SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) + architecture depth), findings written to `## Review`.
@@ -175,7 +271,7 @@ states:
175
271
  answerFile: .spur/run/${vars.wbs}-verify-answer.txt
176
272
  - kind: shell
177
273
  options:
178
- command: "${vars.spurBin} task verdict ${vars.wbs} --from-answer .spur/run/${vars.wbs}-verify-answer.txt"
274
+ command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
179
275
 
180
276
  - id: record
181
277
  description: >
@@ -188,10 +284,28 @@ states:
188
284
  onEnter:
189
285
  - kind: shell
190
286
  options:
191
- command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
287
+ command: "$spurBin task record $wbs --solution-from-diff --transition testing"
288
+ # Best-effort, and portable across layouts. `feature-sync-bounded.ts` is a
289
+ # Spur-monorepo path — `spur init` never scaffolds `plugins/sp/`, so a seeded
290
+ # project must fall back to the plain `spur feature sync` verb rather than
291
+ # abort a run that already produced a PASS verdict. Trailing `exit 0` keeps the
292
+ # whole step advisory for the same reason (feature status sync is a follow-up,
293
+ # not a completion gate — `record → done` runs `spur task check`).
192
294
  - kind: shell
193
295
  options:
194
- command: 'FID=$(${vars.spurBin} task show ${vars.wbs} --json 2>/dev/null | jq -r ".feature_id // .frontmatter.feature_id // empty"); if [ -n "$FID" ]; then bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "${vars.spurBin}" --json; else echo "Orphan task ${vars.wbs} — no feature_id linked; proposal: consider linking to a parent feature." >> .spur/run/${vars.wbs}-report.txt; fi'
296
+ command: >-
297
+ FID=$($spurBin task show $wbs --json 2>/dev/null |
298
+ jq -r ".feature_id // .frontmatter.feature_id // empty" 2>/dev/null);
299
+ if [ -n "$FID" ]; then
300
+ if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
301
+ bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json;
302
+ else
303
+ $spurBin feature sync "$FID" --json;
304
+ fi;
305
+ else
306
+ echo "Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt";
307
+ fi;
308
+ exit 0
195
309
 
196
310
  - id: done
197
311
  description: >
@@ -201,47 +315,96 @@ states:
201
315
  onEnter:
202
316
  - kind: shell
203
317
  options:
204
- command: "${vars.spurBin} task update ${vars.wbs} done --no-lifecycle"
318
+ command: "$spurBin task update $wbs done --no-lifecycle"
205
319
  - kind: note
206
320
  options:
207
321
  message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
208
322
  # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
209
323
  - kind: shell
210
324
  options:
211
- command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: task-pipeline done wbs=${vars.wbs} ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/${vars.wbs}-checkpoint.md'
325
+ command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: task-pipeline done wbs=$wbs ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/$wbs-checkpoint.md'
212
326
 
213
327
  - id: failed
214
- description: Terminal — precheck (or a gated step) failed; reported, not advanced.
328
+ description: >
329
+ Terminal — precheck, quality-gate exhaustion, verify non-PASS, record check
330
+ failure, or operator rejection; reported, not advanced.
215
331
 
216
332
  - id: cancelled
217
333
  description: Terminal — pipeline cancelled by operator at the approval gate (R1).
218
334
 
219
335
  transitions:
220
- # ── precheck: shell guard runs `spur task check`; pass → implement, fail → failed ──
336
+ # ── precheck: doctor PASS + task check → implement; else → failed ──
221
337
  - from: precheck
222
338
  to: implement
223
- description: Precheck passed — begin implementation.
339
+ description: Agent doctor PASS and task check green — begin implementation.
224
340
  guard:
225
341
  kind: shell
226
342
  options:
227
- command: "${vars.spurBin} task check ${vars.wbs}"
343
+ command: 'test "$(cat .spur/run/$wbs-precheck-doctor.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
228
344
  - from: precheck
229
345
  to: failed
230
- description: Precheck failed report and stop (no fall-through to implement).
346
+ description: Doctor FAIL and/or task check failed stop before implement.
231
347
  guard:
232
- kind: shell
233
- options:
234
- command: '! ${vars.spurBin} task check ${vars.wbs}'
348
+ kind: always
235
349
 
236
350
  # ── linear body ──
237
351
  - from: implement
238
352
  to: test
239
- description: Implementation done — run tests.
353
+ description: Implementation done — quality-gate probe.
240
354
  guard:
241
355
  kind: always
356
+ # Soft probe branching (declaration order: PASS first, then FAIL, then defense).
242
357
  - from: test
243
358
  to: review
244
- description: Tests done — run review.
359
+ description: Quality gate already green one gate run only; skip fixall/recheck.
360
+ guard:
361
+ kind: shell
362
+ options:
363
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
364
+ - from: test
365
+ to: test-fix
366
+ description: Quality gate red — start bounded fixall loop.
367
+ guard:
368
+ kind: shell
369
+ options:
370
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
371
+ # Defense: missing/corrupt status — treat as FAIL path, not silent PASS.
372
+ - from: test
373
+ to: test-fix
374
+ description: Probe status missing/corrupt — attempt fixall then recheck.
375
+ guard:
376
+ kind: always
377
+ - from: test-fix
378
+ to: test-recheck
379
+ description: Fixall finished — soft recheck the same quality gate.
380
+ guard:
381
+ kind: always
382
+ # Recheck branching (PASS first; under-max FAIL → fixall again; exhausted → failed).
383
+ - from: test-recheck
384
+ to: review
385
+ description: Quality gate green after fixall — proceed to review.
386
+ guard:
387
+ kind: shell
388
+ options:
389
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
390
+ - from: test-recheck
391
+ to: test-fix
392
+ description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
393
+ guard:
394
+ kind: shell
395
+ options:
396
+ 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"'
397
+ - from: test-recheck
398
+ to: failed
399
+ description: Still red after max fixall attempts — stop at failed (not silent abort).
400
+ guard:
401
+ kind: shell
402
+ options:
403
+ 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"'
404
+ # Defense: corrupt recheck status — failed, not review.
405
+ - from: test-recheck
406
+ to: failed
407
+ description: Recheck status missing/corrupt — stop at failed.
245
408
  guard:
246
409
  kind: always
247
410
  # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
@@ -255,7 +418,7 @@ transitions:
255
418
  guard:
256
419
  kind: shell
257
420
  options:
258
- command: 'test "${vars.profile}" = auto'
421
+ command: 'test "$profile" = auto'
259
422
  - from: review
260
423
  to: approve
261
424
  description: Review done — gate on human approval.
@@ -273,21 +436,21 @@ transitions:
273
436
  guard:
274
437
  kind: shell
275
438
  options:
276
- command: 'test "${vars.__hitlAnswer}" = yes'
439
+ command: 'test "$__hitlAnswer" = yes'
277
440
  - from: approve
278
441
  to: failed
279
442
  description: Operator rejected at the approval gate — report and stop.
280
443
  guard:
281
444
  kind: shell
282
445
  options:
283
- command: 'test "${vars.__hitlAnswer}" = no'
446
+ command: 'test "$__hitlAnswer" = no'
284
447
  - from: approve
285
448
  to: cancelled
286
449
  description: Operator cancelled at the approval gate.
287
450
  guard:
288
451
  kind: shell
289
452
  options:
290
- command: 'test "${vars.__hitlAnswer}" = cancel'
453
+ command: 'test "$__hitlAnswer" = cancel'
291
454
 
292
455
  # ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
293
456
  # The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
@@ -299,14 +462,14 @@ transitions:
299
462
  guard:
300
463
  kind: shell
301
464
  options:
302
- command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" = PASS'
465
+ command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS'
303
466
  - from: verify
304
467
  to: failed
305
468
  description: Verification verdict is not PASS (PARTIAL/FAIL/missing) — block before done.
306
469
  guard:
307
470
  kind: shell
308
471
  options:
309
- command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" != PASS'
472
+ command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" != PASS'
310
473
  # ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23) ──
311
474
  # The record step guarantees every done-required section ([Solution, Testing, Review])
312
475
  # has real content (each owned by its pipeline step). This guard is defense-in-depth —
@@ -318,11 +481,11 @@ transitions:
318
481
  guard:
319
482
  kind: shell
320
483
  options:
321
- command: "${vars.spurBin} task check ${vars.wbs}"
484
+ command: "$spurBin task check $wbs"
322
485
  - from: record
323
486
  to: failed
324
487
  description: Task check failed — block before done; investigate missing sections.
325
488
  guard:
326
489
  kind: shell
327
490
  options:
328
- command: '! ${vars.spurBin} task check ${vars.wbs}'
491
+ command: '! $spurBin task check $wbs'