@gobing-ai/spur 0.3.73 → 0.3.74

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 (88) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +15 -4
  3. package/config/pipeline-budgets.json +4 -4
  4. package/config/rules/strict/runtime-boundaries.yaml +1 -1
  5. package/config/templates/AGENTS.md +5 -0
  6. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +2 -0
  7. package/config/workflows/basic.yaml +5 -2
  8. package/config/workflows/docs-pipeline.yaml +92 -55
  9. package/config/workflows/feature-dev.yaml +43 -20
  10. package/config/workflows/feature-lifecycle.yaml +1 -0
  11. package/config/workflows/history-anatomy.yaml +8 -4
  12. package/config/workflows/idea-pipeline.yaml +66 -33
  13. package/config/workflows/pr-review.yaml +31 -8
  14. package/config/workflows/task-lifecycle.yaml +1 -0
  15. package/config/workflows/task-pipeline.yaml +929 -905
  16. package/config/workflows/wayfinder-resolution.yaml +124 -90
  17. package/config/workflows/wrapup-pipeline.yaml +208 -83
  18. package/package.json +1 -1
  19. package/plugins/sp/plugin.json +1 -1
  20. package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
  21. package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
  22. package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
  23. package/plugins/sp/scripts/pr-reviewing.ts +7 -1
  24. package/plugins/sp/scripts/transition-shim-check.ts +6 -6
  25. package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
  26. package/plugins/sp/skills/spur-cli/references/self.md +17 -3
  27. package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
  28. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
  29. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  30. package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
  31. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  32. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -2
  33. package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
  34. package/spur.js +1125 -784
  35. package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.C60RJZRj.js} +78 -77
  36. package/web/_astro/BoardApp.ymfj1EHA.js +1 -0
  37. package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.Guk8VuNu.js} +1 -1
  38. package/web/_astro/{arc.BySSh34M.js → arc.CAZtlWJM.js} +1 -1
  39. package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.9-XbF_Tz.js} +1 -1
  40. package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.DC4WLx3L.js} +1 -1
  41. package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.JgjUQMgG.js} +1 -1
  42. package/web/_astro/channel.Bxxe2Byc.js +1 -0
  43. package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.D_JFiXa-.js} +1 -1
  44. package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.Bk_RxoeT.js} +1 -1
  45. package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.a_Rj2VxL.js} +1 -1
  46. package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.C2NoS1U6.js} +1 -1
  47. package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.D1Xn6CNa.js} +1 -1
  48. package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.BhJrSBsX.js} +1 -1
  49. package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.ByVGduYQ.js} +1 -1
  50. package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.B47Paunq.js} +1 -1
  51. package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.BERMSD5C.js} +1 -1
  52. package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.BERMSD5C.js} +1 -1
  53. package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.O_rHGlhU.js} +1 -1
  54. package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.qCW2GdNp.js} +1 -1
  55. package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.C5Y4lH_u.js} +1 -1
  56. package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.DxZmRXxF.js} +1 -1
  57. package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.qSxZeYSK.js} +1 -1
  58. package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.-7vl3bXs.js} +1 -1
  59. package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.BHVV-UH3.js} +1 -1
  60. package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DHAuW9sK.js} +1 -1
  61. package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.DMRLQwPN.js} +1 -1
  62. package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.B3EpPp_8.js} +1 -1
  63. package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.BgUrExXM.js} +1 -1
  64. package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.CU-9yVN4.js} +1 -1
  65. package/web/_astro/{index.B5MTfe7k.css → index.BhvM3djt.css} +1 -1
  66. package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.CvVTCRLe.js} +1 -1
  67. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.BUzOfKPf.js} +1 -1
  68. package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.BBTb7ziz.js} +1 -1
  69. package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.CTQQr70i.js} +1 -1
  70. package/web/_astro/{linear.D7uqzENp.js → linear.CLKlJPiS.js} +1 -1
  71. package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.BA4wFhLP.js} +4 -4
  72. package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.t1vG2l5e.js} +1 -1
  73. package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.C8zJhyTu.js} +1 -1
  74. package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DqYYvb7T.js} +1 -1
  75. package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.lzfDZ9nI.js} +1 -1
  76. package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.lm6N5ORM.js} +1 -1
  77. package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.E6_IV4Dj.js} +1 -1
  78. package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.Bnv6Ok2p.js} +1 -1
  79. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js} +1 -1
  80. package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BhRgqSLa.js} +1 -1
  81. package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.pjb5DMC9.js} +1 -1
  82. package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.CW20KUng.js} +1 -1
  83. package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.vX9_yuMl.js} +1 -1
  84. package/web/index.html +2 -2
  85. package/config/corpus-baseline.json +0 -1805
  86. package/config/workflow-composition-baseline.json +0 -615
  87. package/web/_astro/BoardApp.E12MFjOS.js +0 -1
  88. package/web/_astro/channel.5cYKr5cs.js +0 -1
@@ -14,6 +14,13 @@
14
14
  # (idea-eval rejection -> cancelled)
15
15
  # (feature-check failure routes back to ac-generate; batch-create failure to decompose).
16
16
  #
17
+ # Evidence instead of ceremony (task 0769): the feature check is MEASURED exactly once at
18
+ # each author/revise boundary — `idea-ac-check` at the end of ac-generate, `idea-design-check`
19
+ # at the end of system-design — by a deterministic `command.gate` writing a run-scoped
20
+ # PASS/FAIL result file. Transition guards only CONSUME the recorded result (run-scoped path,
21
+ # no CLI re-run per guard); a gate re-runs only after a relevant write (revision pass) or on
22
+ # the next boundary.
23
+ #
17
24
  # Vars (passed as a JSON object via `--vars`):
18
25
  # idea — the idea text (required), e.g. --vars '{"idea":"add a --dry-run flag to dev-wrap"}'
19
26
  # profile — set --vars '{"profile":"auto"}' to skip objective HITL gates (feature-check, batch-create)
@@ -39,6 +46,7 @@
39
46
  # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
40
47
 
41
48
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
49
+ version: "1"
42
50
  kind: state-machine
43
51
  name: idea-pipeline
44
52
  description: "Idea to feature + AC + task batch: discovery, idea-eval, feature-create, ac-generate, feature-check, system-design, decompose, batch-create, handoff"
@@ -169,11 +177,11 @@ states:
169
177
  --from-file`. The agent authors R-numbered Gherkin scenarios tied to the
170
178
  design summary into a captured file; the shell step verifies the file is
171
179
  non-empty, writes it through the CLI, and writes a completion sentinel
172
- (.spur/run/${vars.__runId}-idea-ac-done.txt). AC quality checking lives in the transition
173
- guards (`spur feature check`), NOT in this action chain — a failing
174
- check must route through the capped retry loop, not fail the run (the engine's
175
- default onError policy is `fail`, so an in-action check failure would kill the
176
- run before the retry edges are ever evaluated).
180
+ (.spur/run/${vars.__runId}-idea-ac-done.txt). AC quality is MEASURED once at this
181
+ boundary by the deterministic `idea-ac-check` command.gate below (task 0769): a failing
182
+ check records FAIL and routes through the capped retry loop via guards that consume the
183
+ recorded result it never fails the run (the engine's default onError policy is `fail`,
184
+ so an in-action check failure would kill the run before the retry edges are evaluated).
177
185
  onEnter:
178
186
  - kind: shell
179
187
  options:
@@ -192,8 +200,8 @@ states:
192
200
  # old `test -s … && …` chain exited 1 on empty content — killing the run under
193
201
  # the default `fail` policy before the capped retry edges were ever evaluated,
194
202
  # contradicting this state's own contract. Empty now falls through with exit 0
195
- # so `spur feature check` in the guards routes it back here (cap: 3). A failing
196
- # CLI *write* still exits non-zero and aborts — that one is not retryable.
203
+ # so the recorded FAIL routes it back here (cap: 3). A failing CLI *write* still
204
+ # exits non-zero and aborts — that one is not retryable.
197
205
  - kind: shell
198
206
  options:
199
207
  command: >-
@@ -202,20 +210,31 @@ states:
202
210
  else
203
211
  exit 0;
204
212
  fi
213
+ # 0769: ONE measured check at the AC author/revise boundary. `softFail: true` keeps a
214
+ # FAIL routable (retry loop / failed) instead of aborting; sibling guards consume the
215
+ # recorded result from the run-scoped status file and never re-run the CLI.
216
+ - kind: command.gate
217
+ options:
218
+ id: idea-ac-check
219
+ executable: "${vars.spurBin}"
220
+ args: ["feature", "check", "${vars.featureId}"]
221
+ resultFile: .spur/run/${vars.__runId}-idea-ac-check.status
222
+ softFail: true
223
+ timeoutMs: 120000
205
224
 
206
225
  - id: feature-check
207
226
  description: >
208
- HITL gate: runs spur feature check <id>. Deliberately NOT --strict: before
227
+ HITL gate over the recorded `idea-ac-check` result (measured at the ac-generate
228
+ boundary — this gate never re-runs the CLI). Deliberately NOT --strict: before
209
229
  decompose/batch-create a feature has scenarios but zero linked tasks, which
210
230
  emits L4.orphan-scenarios at severity `warning`; --strict would elevate that
211
231
  to a failure, making the success edges unreachable and looping ac-generate
212
232
  into `failed`. Malformed AC still blocks via the error-severity L3 BDD
213
- checks. Strict coverage belongs at the shippable gate, not here. The check
214
- is objective
215
- (schema/AC validation) and auto-routable under profile=auto the transition
216
- guards route directly from ac-generate to the appropriate next state, so this
217
- state is only entered in interactive mode. On failure, the retry cap routes
218
- back to ac-generate (≤3 retries) or escalates to failed.
233
+ checks. Strict coverage belongs at the shippable gate, not here. The recorded
234
+ result is objective (schema/AC validation) and auto-routable — under profile=auto
235
+ the transition guards route directly from ac-generate to the appropriate next
236
+ state, so this state is only entered in interactive mode. On failure, the retry
237
+ cap routes back to ac-generate (≤3 retries) or escalates to failed.
219
238
  pause: true
220
239
  onEnter:
221
240
  - kind: hitl.confirm
@@ -232,7 +251,9 @@ states:
232
251
  carries operator rejection feedback back into this state: on the first pass the agent
233
252
  fills `## Proposed design`; on retry (operator feedback present) it revises the design,
234
253
  records the reconciliation, and updates invalidated Acceptance Criteria through
235
- `spur feature update` before design can exit. The design-approval state follows (taste gate).
254
+ `spur feature update` before design can exit. Design quality is then MEASURED once at
255
+ this boundary by the deterministic `idea-design-check` command.gate (task 0769). The
256
+ design-approval state follows (taste gate).
236
257
  onEnter:
237
258
  - kind: shell
238
259
  options:
@@ -256,6 +277,17 @@ states:
256
277
  command: >-
257
278
  REVIEW=".spur/run/$__runId-idea-design-review.md" &&
258
279
  awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' "$REVIEW" | grep -q '[^[:space:]]'
280
+ # 0769: ONE measured check at the design author/revise boundary — after this pass's
281
+ # design writes (including AC reconciliation). softFail keeps FAIL routable; sibling
282
+ # guards consume the recorded result and never re-run the CLI.
283
+ - kind: command.gate
284
+ options:
285
+ id: idea-design-check
286
+ executable: "${vars.spurBin}"
287
+ args: ["feature", "check", "${vars.featureId}"]
288
+ resultFile: .spur/run/${vars.__runId}-idea-design-check.status
289
+ softFail: true
290
+ timeoutMs: 120000
259
291
 
260
292
  - id: design-approval
261
293
  description: >
@@ -299,7 +331,7 @@ states:
299
331
  - kind: agent.run
300
332
  options:
301
333
  agent: ${vars.planningAgent}
302
- input: "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. Acceptance Criteria, Design, and Plan sections are filled in by the per-task refine step after batch-create, NOT at decompose time. Validate locally against the schema before emitting. Also emit the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json: a JSON array (one entry per batch item) of `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring ordering/dependencies between the batch items; use `[]` when no ordering exists. Every `name` and every dependency must match exactly one batch item `name` — it is private workflow data, not part of task-batch.schema.json."
334
+ input: "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `design`, `plan`, `acceptance_criteria`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. `design`, `plan`, and `acceptance_criteria` are supported batch fields and normal default planning fills them from your analysis; the per-task refine step after batch-create still deepens them when a task needs more detail. Validate locally against the schema before emitting. Also emit the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json: a JSON array (one entry per batch item) of `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring ordering/dependencies between the batch items; use `[]` when no ordering exists. Every `name` and every dependency must match exactly one batch item `name` — it is private workflow data, not part of task-batch.schema.json."
303
335
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
304
336
  role: planner
305
337
  expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
@@ -545,9 +577,10 @@ transitions:
545
577
 
546
578
  # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
547
579
  # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
548
- # and design-gen→handoff). Under profile=auto, the feature check runs as a transition guard and
549
- # routes directly to the appropriate next state. Under interactive, the always fallback enters
550
- # the feature-check state whose onEnter hitl.confirm pauses for operator confirmation.
580
+ # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result routes
581
+ # directly to the appropriate next state guards never re-run the CLI (task 0769). Under
582
+ # interactive, the always fallback enters the feature-check state whose onEnter hitl.confirm
583
+ # pauses for operator confirmation.
551
584
  #
552
585
  # Auto-skip 1: pass + design route → system-design (design=auto + needs_design != false)
553
586
  - from: ac-generate
@@ -556,7 +589,7 @@ transitions:
556
589
  guard:
557
590
  kind: shell
558
591
  options:
559
- command: 'test "$profile" = auto && $spurBin feature check "$featureId" && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
592
+ 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'
560
593
  # Auto-skip 2: pass + skip-design route → decompose
561
594
  - from: ac-generate
562
595
  to: decompose
@@ -564,7 +597,7 @@ transitions:
564
597
  guard:
565
598
  kind: shell
566
599
  options:
567
- command: 'test "$profile" = auto && $spurBin feature check "$featureId" && (test "$design" = skip || (test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false))'
600
+ 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))'
568
601
  # Auto-skip 3: check failed, retry < 3 → loop back to ac-generate (self-loop)
569
602
  - from: ac-generate
570
603
  to: ac-generate
@@ -572,7 +605,7 @@ transitions:
572
605
  guard:
573
606
  kind: shell
574
607
  options:
575
- command: 'test "$profile" = auto && ! $spurBin feature check "$featureId" && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
608
+ 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'
576
609
  # Auto-skip 4: check failed, retry cap reached → escalate to failed
577
610
  - from: ac-generate
578
611
  to: failed
@@ -580,7 +613,7 @@ transitions:
580
613
  guard:
581
614
  kind: shell
582
615
  options:
583
- command: 'test "$profile" = auto && ! $spurBin feature check "$featureId" && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
616
+ 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'
584
617
  # Interactive fallback: enter feature-check HITL gate
585
618
  - from: ac-generate
586
619
  to: feature-check
@@ -596,28 +629,28 @@ transitions:
596
629
  guard:
597
630
  kind: shell
598
631
  options:
599
- command: 'test "$__hitlAnswer" = yes && $spurBin feature check "$featureId" && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false'
632
+ 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'
600
633
  - from: feature-check
601
634
  to: decompose
602
635
  description: Feature check passed, skip-design route — go directly to decompose.
603
636
  guard:
604
637
  kind: shell
605
638
  options:
606
- command: 'test "$__hitlAnswer" = yes && $spurBin feature check "$featureId" && (test "$design" = skip || (test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" = false))'
639
+ command: 'test "$__hitlAnswer" = yes && 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))'
607
640
  - from: feature-check
608
641
  to: ac-generate
609
642
  description: "Feature check failed — revise AC (retry cap: 3)."
610
643
  guard:
611
644
  kind: shell
612
645
  options:
613
- command: '(test "$__hitlAnswer" = no || ! $spurBin feature check "$featureId") && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
646
+ 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'
614
647
  - from: feature-check
615
648
  to: failed
616
649
  description: Feature check failed after 3 retries — escalate to failed.
617
650
  guard:
618
651
  kind: shell
619
652
  options:
620
- command: '(test "$__hitlAnswer" = no || ! $spurBin feature check "$featureId") && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
653
+ 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'
621
654
  - from: feature-check
622
655
  to: cancelled
623
656
  description: Operator cancelled the feature-check gate.
@@ -630,11 +663,11 @@ transitions:
630
663
  # Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
631
664
  - from: system-design
632
665
  to: decompose
633
- description: profile=auto AND design_approved=true AND feature check passes — skip design approval gate.
666
+ description: profile=auto AND design_approved=true AND recorded design check passes — skip design approval gate.
634
667
  guard:
635
668
  kind: shell
636
669
  options:
637
- command: 'test "$profile" = auto && test "$design_approved" = true && $spurBin feature check "$featureId"'
670
+ command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
638
671
  - from: system-design
639
672
  to: design-approval
640
673
  description: System design done — gate on design approval.
@@ -644,18 +677,18 @@ transitions:
644
677
  # ── design-approval -> decompose (after operator approval), AC-failure -> feature-check ──
645
678
  - from: design-approval
646
679
  to: decompose
647
- description: Design approved and feature check passes — proceed to decomposition.
680
+ description: Design approved and recorded design check passes — proceed to decomposition.
648
681
  guard:
649
682
  kind: shell
650
683
  options:
651
- command: 'test "$__hitlAnswer" = yes && $spurBin feature check "$featureId"'
684
+ command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
652
685
  - from: design-approval
653
686
  to: feature-check
654
- description: "Design approved but feature check fails — route back through the AC gate (revise AC, retry cap: 3)."
687
+ description: "Design approved but recorded design check fails — route back through the AC gate (revise AC, retry cap: 3)."
655
688
  guard:
656
689
  kind: shell
657
690
  options:
658
- command: 'test "$__hitlAnswer" = yes && ! $spurBin feature check "$featureId"'
691
+ command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'
659
692
  - from: design-approval
660
693
  to: system-design
661
694
  description: "Design rejected - revise system design (cap: 1 revise)."
@@ -31,6 +31,7 @@
31
31
  # __runId — injected by WorkflowAppService.run(); scopes .spur/run artifacts.
32
32
 
33
33
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
34
+ version: "1"
34
35
  name: pr-review
35
36
  kind: state-machine
36
37
  description: GitHub Codex PR-review spine — preflight → hygiene → precheck → push → ensure-pr → request → wait → collect
@@ -51,6 +52,10 @@ vars:
51
52
  waitIntervalSec: "30"
52
53
  preReviewCmd: ""
53
54
  __runId: ""
55
+ # Runtime-set by file.read.into-var (0771): the request record's requestedAt|head,
56
+ # extracted once by `request` and read per state — no repeated JSON parsing in shell.
57
+ prSince: ""
58
+ prHead: ""
54
59
 
55
60
  states:
56
61
  - id: preflight
@@ -155,42 +160,60 @@ states:
155
160
  --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-request.json";
156
161
  rc=$?; set -e &&
157
162
  if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
163
+ [ -f ".spur/run/$__runId-pr-request.json" ] &&
164
+ bun -e 'const fs=require("node:fs");const r=JSON.parse(fs.readFileSync(process.argv[1],"utf8"));fs.writeFileSync(process.argv[2],(r.requestedAt ?? "")+"\n");fs.writeFileSync(process.argv[3],(r.head ?? "")+"\n")' ".spur/run/$__runId-pr-request.json" ".spur/run/$__runId-pr-since.txt" ".spur/run/$__runId-pr-head.txt";
158
165
  exit 0
159
166
 
160
167
  - id: wait
161
168
  description: >
162
169
  Bounded poll for Codex output on the current pushed HEAD. Timeout records TIMEOUT
163
170
  and routes to `pending` — never a failure (collect later with /sp:dev-pr-review collect).
171
+ Reads the request record once at `request` (0771): requestedAt/head land in
172
+ run-scoped .txt files, projected into prSince/prHead vars via file.read.into-var.
164
173
  onEnter:
174
+ - kind: file.read.into-var
175
+ options:
176
+ path: .spur/run/${vars.__runId}-pr-since.txt
177
+ var: prSince
178
+ - kind: file.read.into-var
179
+ options:
180
+ path: .spur/run/${vars.__runId}-pr-head.txt
181
+ var: prHead
165
182
  - kind: shell
166
183
  options:
167
184
  command: >-
168
185
  mkdir -p .spur/run &&
169
186
  STATUS_FILE=".spur/run/$__runId-pr-wait.status" &&
170
187
  set +e &&
171
- SINCE="$(bun -e 'const fs=require("node:fs");const value=JSON.parse(fs.readFileSync(process.argv[1],"utf8")).requestedAt;process.stdout.write(value ?? "")' ".spur/run/$__runId-pr-request.json")" &&
172
- REQUEST_HEAD="$(bun -e 'const fs=require("node:fs");const value=JSON.parse(fs.readFileSync(process.argv[1],"utf8")).head;process.stdout.write(value ?? "")' ".spur/run/$__runId-pr-request.json")" &&
173
- bun "$(superskill script path sp pr-reviewing.ts)" wait --since "$SINCE" --head "$REQUEST_HEAD" --timeout "$waitTimeoutSec" --interval "$waitIntervalSec"
188
+ bun "$(superskill script path sp pr-reviewing.ts)" wait --since "$prSince" --head "$prHead" --timeout "$waitTimeoutSec" --interval "$waitIntervalSec"
174
189
  --json --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-wait.json";
175
190
  rc=$?; set -e &&
176
191
  if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
177
192
  exit 0
178
193
 
179
194
  - id: collect
180
- description: Normalize the latest current-HEAD Codex result and record composite PR/CI status.
195
+ description: >-
196
+ Normalize the latest current-HEAD Codex result and record composite PR/CI status.
197
+ Request record read via file.read.into-var vars (0771, see wait).
181
198
  onEnter:
199
+ - kind: file.read.into-var
200
+ options:
201
+ path: .spur/run/${vars.__runId}-pr-since.txt
202
+ var: prSince
203
+ - kind: file.read.into-var
204
+ options:
205
+ path: .spur/run/${vars.__runId}-pr-head.txt
206
+ var: prHead
182
207
  - kind: shell
183
208
  options:
184
209
  command: >-
185
210
  mkdir -p .spur/run &&
186
211
  STATUS_FILE=".spur/run/$__runId-pr-collect.status" &&
187
212
  set +e &&
188
- SINCE="$(bun -e 'const fs=require("node:fs");const value=JSON.parse(fs.readFileSync(process.argv[1],"utf8")).requestedAt;process.stdout.write(value ?? "")' ".spur/run/$__runId-pr-request.json")" &&
189
- REQUEST_HEAD="$(bun -e 'const fs=require("node:fs");const value=JSON.parse(fs.readFileSync(process.argv[1],"utf8")).head;process.stdout.write(value ?? "")' ".spur/run/$__runId-pr-request.json")" &&
190
- bun "$(superskill script path sp pr-reviewing.ts)" collect --since "$SINCE" --head "$REQUEST_HEAD" --json
213
+ bun "$(superskill script path sp pr-reviewing.ts)" collect --since "$prSince" --head "$prHead" --json
191
214
  --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-findings.json";
192
215
  collect_rc=$?;
193
- bun "$(superskill script path sp pr-reviewing.ts)" status --since "$SINCE" --head "$REQUEST_HEAD" --json > ".spur/run/$__runId-pr-status.json";
216
+ bun "$(superskill script path sp pr-reviewing.ts)" status --since "$prSince" --head "$prHead" --json > ".spur/run/$__runId-pr-status.json";
194
217
  status_rc=$?; set -e &&
195
218
  if [ "$collect_rc" -ne 0 ] || [ "$status_rc" -ne 0 ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
196
219
  if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
@@ -20,6 +20,7 @@
20
20
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
21
21
  kind: state-machine
22
22
  name: task-lifecycle
23
+ version: "1"
23
24
  description: >
24
25
  Task lifecycle FSM (design §2.3, §5.1). States are the canonical TaskStatus
25
26
  vocabulary (DD-01); transitions encode the §2.3 graph; guards invoke