@gobing-ai/spur 0.3.30 → 0.3.31

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 +214 -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 +1210 -677
  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,39 @@ 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
+ qualityGateCmd: "bun run autofix && bun run spur-check"
80
+ # Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
81
+ # Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
82
+ qualityGateMaxFixAttempts: "2"
83
+ # Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
84
+ # project can point it at its own formatter; invoked best-effort (a missing or
85
+ # failing formatter must never abort a run — the quality gate is the real gate).
86
+ formatCmd: "bun run format"
70
87
 
71
88
  states:
72
89
  - id: precheck
73
90
  description: >
74
- Pre-flight structural validation. The precheck→implement guard runs
75
- `spur task check <wbs>`; a failure routes to `failed` (blocked report).
91
+ Pre-flight: soft agent doctor + transition guards for doctor PASS and
92
+ `spur task check <wbs>`. Failures route to the `failed` terminal state
93
+ (not a raw lifecycle abort mid-enter).
76
94
  onEnter:
95
+ # Soft doctor — write status and always exit 0 so transitions can branch to
96
+ # `failed` cleanly (same pattern as the quality-gate soft probe).
77
97
  - kind: shell
78
98
  options:
79
- command: "${vars.spurBin} agent doctor ${vars.agent}"
99
+ command: >-
100
+ mkdir -p .spur/run &&
101
+ DOCTOR_FILE=".spur/run/$wbs-precheck-doctor.status" &&
102
+ if $spurBin agent doctor $agent; then
103
+ echo PASS > "$DOCTOR_FILE";
104
+ else
105
+ echo FAIL > "$DOCTOR_FILE";
106
+ fi &&
107
+ exit 0
80
108
  - kind: note
81
109
  options:
82
110
  # R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
@@ -101,40 +129,103 @@ states:
101
129
  - kind: agent.run
102
130
  options:
103
131
  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).
132
+ # Pure slash command only (ADR-043). Anti-recursion / implement discipline
133
+ # lives in /sp:dev-run --mode implement sp:code-implementation, not YAML prose.
134
+ input: /sp:dev-run --mode implement ${vars.wbs} --auto
109
135
  timeoutMs: ${vars.implementTimeoutMs}
136
+ # R3 (task 0424): empty-implement no-op guard — the agent.run action
137
+ # fails the step when exit 0 produced zero non-corpus file changes, so
138
+ # a silent no-op routes the run to `failed` here instead of drifting
139
+ # into test/review and being caught a full pass later.
140
+ requireDiff: true
110
141
  - kind: shell
111
142
  options:
112
- command: "${vars.spurBin} task update ${vars.wbs} wip --no-lifecycle"
143
+ command: "$spurBin task update $wbs wip --no-lifecycle"
113
144
  # Post-implement cleanup: auto-format any unformatted output the agent produced.
114
145
  # The agent may leave files that don't pass `biome check`, which then fails
115
146
  # `bun run lint` downstream. Running format here is cheap (< 1 s) and prevents
116
147
  # the test stage from tripping on a purely mechanical formatting defect (dogfood
117
148
  # bug-733 — omp left agent-run.test.ts unformatted, causing a spurious lint gate
118
149
  # failure that the ## Testing section mis-attributed to "pre-existing gaps").
150
+ # Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
151
+ # non-Bun project cannot abort a run mid-implement. Formatting is a convenience;
152
+ # `${vars.qualityGateCmd}` at `test` is the gate that actually decides.
119
153
  - kind: shell
120
154
  options:
121
- command: "bun run format"
155
+ command: '$formatCmd ; exit 0'
122
156
 
157
+ # ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
158
+ # NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
159
+ # a coverage target; it is not the project quality gate. Coverage gap-fill remains
160
+ # available via router C3/C5 (`/sp:dev-unit`) or inside implement — not this hop.
161
+ #
162
+ # Shape (engine has no per-action "run next only if previous failed" without
163
+ # `onError`, and Spur's action schema does not expose `onError`):
164
+ # test — soft probe: qualityGateCmd → PASS|FAIL status; always exit 0
165
+ # (green path: one gate run → review; no fixall/recheck tax)
166
+ # test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
167
+ # test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
123
168
  - id: test
124
- description: Test execution + coverage via /sp:dev-unit.
169
+ description: >
170
+ Soft quality-gate probe (single full gate on the green path). Runs
171
+ `${vars.qualityGateCmd}`, records PASS|FAIL under
172
+ `.spur/run/<wbs>-test-gate.status`, resets the fix attempt counter, always exit 0.
125
173
  onEnter:
174
+ - kind: shell
175
+ options:
176
+ command: >-
177
+ mkdir -p .spur/run &&
178
+ STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
179
+ ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
180
+ echo 0 > "$ATTEMPT_FILE" &&
181
+ set +e &&
182
+ ( $qualityGateCmd ); gate_rc=$?; set -e &&
183
+ if [ "$gate_rc" -eq 0 ]; then
184
+ printf 'PASS\n' > "$STATUS_FILE";
185
+ else
186
+ printf 'FAIL\n' > "$STATUS_FILE";
187
+ fi &&
188
+ exit 0
189
+
190
+ - id: test-fix
191
+ description: >
192
+ Bounded auto-fix hop when the quality gate is red. Increments
193
+ `.spur/run/<wbs>-test-fix-attempt`, then pure slash (ADR-043)
194
+ `/sp:dev-fixall` against `${vars.qualityGateCmd}`.
195
+ onEnter:
196
+ - kind: shell
197
+ options:
198
+ command: >-
199
+ mkdir -p .spur/run &&
200
+ ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
201
+ n=$(cat "$ATTEMPT_FILE" 2>/dev/null || echo 0) &&
202
+ printf '%s\n' "$((n + 1))" > "$ATTEMPT_FILE"
126
203
  - kind: agent.run
127
204
  options:
128
205
  agent: ${vars.agent}
129
- input: /sp:dev-unit ${vars.wbs} --auto
206
+ input: /sp:dev-fixall "${vars.qualityGateCmd}"
130
207
  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).
208
+
209
+ - id: test-recheck
210
+ description: >
211
+ Soft recheck after fixall. Writes PASS|FAIL (always exit 0). Transitions
212
+ branch to review (PASS), another test-fix (FAIL and under max attempts),
213
+ or the pipeline `failed` state (FAIL and attempts exhausted) — never a
214
+ raw lifecycle abort that skips the terminal `failed` state.
215
+ onEnter:
135
216
  - kind: shell
136
217
  options:
137
- command: "bun run lint"
218
+ command: >-
219
+ mkdir -p .spur/run &&
220
+ STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
221
+ set +e &&
222
+ ( $qualityGateCmd ); gate_rc=$?; set -e &&
223
+ if [ "$gate_rc" -eq 0 ]; then
224
+ printf 'PASS\n' > "$STATUS_FILE";
225
+ else
226
+ printf 'FAIL\n' > "$STATUS_FILE";
227
+ fi &&
228
+ exit 0
138
229
 
139
230
  - id: review
140
231
  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 +266,7 @@ states:
175
266
  answerFile: .spur/run/${vars.wbs}-verify-answer.txt
176
267
  - kind: shell
177
268
  options:
178
- command: "${vars.spurBin} task verdict ${vars.wbs} --from-answer .spur/run/${vars.wbs}-verify-answer.txt"
269
+ command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
179
270
 
180
271
  - id: record
181
272
  description: >
@@ -188,10 +279,28 @@ states:
188
279
  onEnter:
189
280
  - kind: shell
190
281
  options:
191
- command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
282
+ command: "$spurBin task record $wbs --solution-from-diff --transition testing"
283
+ # Best-effort, and portable across layouts. `feature-sync-bounded.ts` is a
284
+ # Spur-monorepo path — `spur init` never scaffolds `plugins/sp/`, so a seeded
285
+ # project must fall back to the plain `spur feature sync` verb rather than
286
+ # abort a run that already produced a PASS verdict. Trailing `exit 0` keeps the
287
+ # whole step advisory for the same reason (feature status sync is a follow-up,
288
+ # not a completion gate — `record → done` runs `spur task check`).
192
289
  - kind: shell
193
290
  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'
291
+ command: >-
292
+ FID=$($spurBin task show $wbs --json 2>/dev/null |
293
+ jq -r ".feature_id // .frontmatter.feature_id // empty" 2>/dev/null);
294
+ if [ -n "$FID" ]; then
295
+ if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
296
+ bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json;
297
+ else
298
+ $spurBin feature sync "$FID" --json;
299
+ fi;
300
+ else
301
+ echo "Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt";
302
+ fi;
303
+ exit 0
195
304
 
196
305
  - id: done
197
306
  description: >
@@ -201,47 +310,96 @@ states:
201
310
  onEnter:
202
311
  - kind: shell
203
312
  options:
204
- command: "${vars.spurBin} task update ${vars.wbs} done --no-lifecycle"
313
+ command: "$spurBin task update $wbs done --no-lifecycle"
205
314
  - kind: note
206
315
  options:
207
316
  message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
208
317
  # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
209
318
  - kind: shell
210
319
  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'
320
+ 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
321
 
213
322
  - id: failed
214
- description: Terminal — precheck (or a gated step) failed; reported, not advanced.
323
+ description: >
324
+ Terminal — precheck, quality-gate exhaustion, verify non-PASS, record check
325
+ failure, or operator rejection; reported, not advanced.
215
326
 
216
327
  - id: cancelled
217
328
  description: Terminal — pipeline cancelled by operator at the approval gate (R1).
218
329
 
219
330
  transitions:
220
- # ── precheck: shell guard runs `spur task check`; pass → implement, fail → failed ──
331
+ # ── precheck: doctor PASS + task check → implement; else → failed ──
221
332
  - from: precheck
222
333
  to: implement
223
- description: Precheck passed — begin implementation.
334
+ description: Agent doctor PASS and task check green — begin implementation.
224
335
  guard:
225
336
  kind: shell
226
337
  options:
227
- command: "${vars.spurBin} task check ${vars.wbs}"
338
+ command: 'test "$(cat .spur/run/$wbs-precheck-doctor.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
228
339
  - from: precheck
229
340
  to: failed
230
- description: Precheck failed report and stop (no fall-through to implement).
341
+ description: Doctor FAIL and/or task check failed stop before implement.
231
342
  guard:
232
- kind: shell
233
- options:
234
- command: '! ${vars.spurBin} task check ${vars.wbs}'
343
+ kind: always
235
344
 
236
345
  # ── linear body ──
237
346
  - from: implement
238
347
  to: test
239
- description: Implementation done — run tests.
348
+ description: Implementation done — quality-gate probe.
240
349
  guard:
241
350
  kind: always
351
+ # Soft probe branching (declaration order: PASS first, then FAIL, then defense).
242
352
  - from: test
243
353
  to: review
244
- description: Tests done — run review.
354
+ description: Quality gate already green one gate run only; skip fixall/recheck.
355
+ guard:
356
+ kind: shell
357
+ options:
358
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
359
+ - from: test
360
+ to: test-fix
361
+ description: Quality gate red — start bounded fixall loop.
362
+ guard:
363
+ kind: shell
364
+ options:
365
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
366
+ # Defense: missing/corrupt status — treat as FAIL path, not silent PASS.
367
+ - from: test
368
+ to: test-fix
369
+ description: Probe status missing/corrupt — attempt fixall then recheck.
370
+ guard:
371
+ kind: always
372
+ - from: test-fix
373
+ to: test-recheck
374
+ description: Fixall finished — soft recheck the same quality gate.
375
+ guard:
376
+ kind: always
377
+ # Recheck branching (PASS first; under-max FAIL → fixall again; exhausted → failed).
378
+ - from: test-recheck
379
+ to: review
380
+ description: Quality gate green after fixall — proceed to review.
381
+ guard:
382
+ kind: shell
383
+ options:
384
+ command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
385
+ - from: test-recheck
386
+ to: test-fix
387
+ description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
388
+ guard:
389
+ kind: shell
390
+ options:
391
+ 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"'
392
+ - from: test-recheck
393
+ to: failed
394
+ description: Still red after max fixall attempts — stop at failed (not silent abort).
395
+ guard:
396
+ kind: shell
397
+ options:
398
+ 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"'
399
+ # Defense: corrupt recheck status — failed, not review.
400
+ - from: test-recheck
401
+ to: failed
402
+ description: Recheck status missing/corrupt — stop at failed.
245
403
  guard:
246
404
  kind: always
247
405
  # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
@@ -255,7 +413,7 @@ transitions:
255
413
  guard:
256
414
  kind: shell
257
415
  options:
258
- command: 'test "${vars.profile}" = auto'
416
+ command: 'test "$profile" = auto'
259
417
  - from: review
260
418
  to: approve
261
419
  description: Review done — gate on human approval.
@@ -273,21 +431,21 @@ transitions:
273
431
  guard:
274
432
  kind: shell
275
433
  options:
276
- command: 'test "${vars.__hitlAnswer}" = yes'
434
+ command: 'test "$__hitlAnswer" = yes'
277
435
  - from: approve
278
436
  to: failed
279
437
  description: Operator rejected at the approval gate — report and stop.
280
438
  guard:
281
439
  kind: shell
282
440
  options:
283
- command: 'test "${vars.__hitlAnswer}" = no'
441
+ command: 'test "$__hitlAnswer" = no'
284
442
  - from: approve
285
443
  to: cancelled
286
444
  description: Operator cancelled at the approval gate.
287
445
  guard:
288
446
  kind: shell
289
447
  options:
290
- command: 'test "${vars.__hitlAnswer}" = cancel'
448
+ command: 'test "$__hitlAnswer" = cancel'
291
449
 
292
450
  # ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
293
451
  # The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
@@ -299,14 +457,14 @@ transitions:
299
457
  guard:
300
458
  kind: shell
301
459
  options:
302
- command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" = PASS'
460
+ command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS'
303
461
  - from: verify
304
462
  to: failed
305
463
  description: Verification verdict is not PASS (PARTIAL/FAIL/missing) — block before done.
306
464
  guard:
307
465
  kind: shell
308
466
  options:
309
- command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" != PASS'
467
+ command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" != PASS'
310
468
  # ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23) ──
311
469
  # The record step guarantees every done-required section ([Solution, Testing, Review])
312
470
  # has real content (each owned by its pipeline step). This guard is defense-in-depth —
@@ -318,11 +476,11 @@ transitions:
318
476
  guard:
319
477
  kind: shell
320
478
  options:
321
- command: "${vars.spurBin} task check ${vars.wbs}"
479
+ command: "$spurBin task check $wbs"
322
480
  - from: record
323
481
  to: failed
324
482
  description: Task check failed — block before done; investigate missing sections.
325
483
  guard:
326
484
  kind: shell
327
485
  options:
328
- command: '! ${vars.spurBin} task check ${vars.wbs}'
486
+ command: '! $spurBin task check $wbs'