@gobing-ai/spur 0.3.73 → 0.3.75

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 (93) 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 +117 -55
  9. package/config/workflows/feature-dev.yaml +185 -121
  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 +123 -43
  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 +965 -905
  16. package/config/workflows/wayfinder-resolution.yaml +124 -90
  17. package/config/workflows/wrapup-pipeline.yaml +304 -107
  18. package/package.json +1 -1
  19. package/plugins/sp/agents/expert-spur.md +7 -4
  20. package/plugins/sp/commands/dev-idea.md +1 -1
  21. package/plugins/sp/plugin.json +1 -1
  22. package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
  23. package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
  24. package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
  25. package/plugins/sp/scripts/pr-reviewing.ts +7 -1
  26. package/plugins/sp/scripts/transition-shim-check.ts +6 -6
  27. package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
  28. package/plugins/sp/skills/spur-cli/references/self.md +17 -3
  29. package/plugins/sp/skills/spur-cli/references/tasks.md +4 -2
  30. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
  31. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  32. package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
  33. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +23 -8
  34. package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
  35. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  36. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
  37. package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
  38. package/spur.js +2999 -1010
  39. package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.BOW1815F.js} +101 -100
  40. package/web/_astro/BoardApp.C4zimv1U.js +1 -0
  41. package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.CX6C7dSJ.js} +1 -1
  42. package/web/_astro/{arc.BySSh34M.js → arc.CwSvH1ji.js} +1 -1
  43. package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.Dxne1RvP.js} +1 -1
  44. package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.D9Fb14rC.js} +1 -1
  45. package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.DmvWSFvv.js} +1 -1
  46. package/web/_astro/channel.CQHdDVp9.js +1 -0
  47. package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.BCCktIrE.js} +1 -1
  48. package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.BzCk9q27.js} +1 -1
  49. package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.CZXVgTk4.js} +1 -1
  50. package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.CJL8UIpX.js} +1 -1
  51. package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.Dz8MDEdw.js} +1 -1
  52. package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.CkQeYYUw.js} +1 -1
  53. package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.KiC1QgzH.js} +1 -1
  54. package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.AJ08mw2e.js} +1 -1
  55. package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.DRWRDKzK.js} +1 -1
  56. package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.DRWRDKzK.js} +1 -1
  57. package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.CavydfLP.js} +1 -1
  58. package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.D_7o_a0B.js} +1 -1
  59. package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.LWk2dKgg.js} +1 -1
  60. package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.82lbq6aB.js} +1 -1
  61. package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.hyNL1pwY.js} +1 -1
  62. package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.BaLqVf-b.js} +1 -1
  63. package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.DQJxj0oz.js} +1 -1
  64. package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DhlL8cG2.js} +1 -1
  65. package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.HoTnXwkF.js} +1 -1
  66. package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.DkFKlZIZ.js} +1 -1
  67. package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.9qPfOTDb.js} +1 -1
  68. package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.B4G18Dwc.js} +1 -1
  69. package/web/_astro/index.Dcr_8fiK.css +1 -0
  70. package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.e3KJkXAM.js} +1 -1
  71. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.CY3yddhD.js} +1 -1
  72. package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.DaAKIO1t.js} +1 -1
  73. package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.DGkDddtc.js} +1 -1
  74. package/web/_astro/{linear.D7uqzENp.js → linear.DnDPcpd1.js} +1 -1
  75. package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.1uBmxa9t.js} +4 -4
  76. package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.XFJVayxw.js} +1 -1
  77. package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.BXm2OgHh.js} +1 -1
  78. package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DgB2p5fc.js} +1 -1
  79. package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.jBR39bnS.js} +1 -1
  80. package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.N3u9OhE3.js} +1 -1
  81. package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.CZhqZXNL.js} +1 -1
  82. package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.DlNu1VEa.js} +1 -1
  83. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.BEqi8NQV.js} +1 -1
  84. package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BPcexclc.js} +1 -1
  85. package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.BZalxKGQ.js} +1 -1
  86. package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.BqOljod9.js} +1 -1
  87. package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.tkO4ppN5.js} +1 -1
  88. package/web/index.html +2 -2
  89. package/config/corpus-baseline.json +0 -1805
  90. package/config/workflow-composition-baseline.json +0 -615
  91. package/web/_astro/BoardApp.E12MFjOS.js +0 -1
  92. package/web/_astro/channel.5cYKr5cs.js +0 -1
  93. package/web/_astro/index.B5MTfe7k.css +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: "2"
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: >
@@ -295,11 +327,11 @@ states:
295
327
  onEnter:
296
328
  - kind: shell
297
329
  options:
298
- command: "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-decompose-retry-count; rm -f .spur/run/$__runId-idea-task-batch.json .spur/run/$__runId-idea-task-order.json .spur/run/$__runId-idea-batch-create.done .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-handoff.md"
330
+ command: "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-decompose-retry-count; rm -f .spur/run/$__runId-idea-task-batch.json .spur/run/$__runId-idea-task-order.json .spur/run/$__runId-idea-batch-create.done .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-handoff.md .spur/run/$__runId-idea-ready.json"
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
@@ -353,7 +385,7 @@ states:
353
385
  command: >-
354
386
  if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi;
355
387
  rm -f .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-batch-create-result.json.tmp;
356
- if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e ".created == (.wbs | length)" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then
388
+ if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --skip-ready --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e ".created == (.wbs | length)" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then
357
389
  mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&
358
390
  date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;
359
391
  else
@@ -361,6 +393,49 @@ states:
361
393
  date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;
362
394
  fi
363
395
 
396
+ - id: ready-prepare
397
+ description: >
398
+ Ready-by-default preparation (0788): the planning owner applies the
399
+ ready-refinement checklist to each created task (requirements, design,
400
+ plan, ac, decisions, dependencies, premises) so the deterministic task
401
+ check passes, then writes the run-scoped ready evidence sidecar
402
+ (.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs,
403
+ status (ready|failed|skipped), planningDigest, and seven check rows.
404
+ Handoff-finalize verifies presence, digest and checklist evidence before
405
+ recommending auto runall; missing or failing evidence never fails the
406
+ run — it degrades the recommendation to ready-depth refineall.
407
+ onEnter:
408
+ - kind: agent.run
409
+ options:
410
+ agent: ${vars.planningAgent}
411
+ # Declared Layer-1 role (0538 R2), same planner executor as decompose.
412
+ role: planner
413
+ timeoutMs: ${vars.stepTimeoutMs}
414
+ input: >-
415
+ Ready-by-default preparation for feature ${vars.featureId} (0788). The created WBS
416
+ values are in .spur/run/${vars.__runId}-idea-batch-create-result.json under .wbs.
417
+ For EACH wbs: resolve the task file with `spur task path <wbs> --json`; open the
418
+ task doc and apply the ready-refinement checklist — make requirements, design,
419
+ plan, acceptance criteria, decisions, dependencies and premises present and
420
+ non-placeholder so `spur task check <wbs> --json` exits 0; edit only planning
421
+ sections, never Solution/Testing/Review/History. Record one checklist row per id
422
+ with concrete evidence of how you verified it. Compute the planning digest with
423
+ the project's own implementation when this is a monorepo checkout:
424
+ `bun -e 'const m = await import("./packages/app/src/services/task-readiness"); console.log(m.computePlanningDigest(await Bun.file(process.argv[1]).text()))' <task-file>`
425
+ — when that is impossible in this checkout, set status "skipped" instead of
426
+ guessing a digest. Finally write .spur/run/${vars.__runId}-idea-ready.json with
427
+ exactly this shape: {"runId":"${vars.__runId}","depth":"ready","tasks":[{"wbs":"<wbs>","status":"ready" or "failed" or "skipped","planningDigest":"<sha256 hex>","checks":[{"id":"requirements" or "design" or "plan" or "ac" or "decisions" or "dependencies" or "premises","pass":true or false,"evidence":"<how verified>"}]}]}. A task you cannot fully prepare gets status "failed" or "skipped" — never fabricate evidence; the handoff degrades to refineall.
428
+ # Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
429
+ # normalized to an empty sidecar: finalize and the seeded fallback then degrade
430
+ # the recommendation to refineall instead of failing the run. A PRESENT but
431
+ # malformed sidecar fails the run here — it must not masquerade as evidence.
432
+ - kind: shell
433
+ options:
434
+ command: >-
435
+ READY=".spur/run/$__runId-idea-ready.json" &&
436
+ if ! test -f "$READY"; then printf '{"runId":"%s","depth":"ready","tasks":[]}\n' "$__runId" > "$READY"; fi &&
437
+ jq -e 'type == "object" and (.tasks | type == "array") and (all(.tasks[]; (.wbs | type == "string") and (.status == "ready" or .status == "failed" or .status == "skipped") and (.planningDigest | type == "string") and (.checks | type == "array") and (all(.checks[]; (.id | type == "string") and (.pass | type == "boolean") and (.evidence | type == "string")))))' "$READY" >/dev/null
438
+
364
439
  - id: handoff-finalize
365
440
  description: >
366
441
  Post-create finalization (0518): zip batch item names to the created WBS
@@ -426,7 +501,9 @@ states:
426
501
  CHECK_ROWS=$(wc -l < "$CHECKS" | tr -d ' ') &&
427
502
  WBS_COUNT=$(printf '%s\n' $WBS_LIST | wc -l | tr -d ' ') &&
428
503
  test "$CHECK_ROWS" = "$WBS_COUNT" &&
429
- NEXT=$(jq -r --arg feature "$featureId" --slurpfile c "$CHECKS" 'if ($c | any(.[]; .pass == false)) then "/sp:dev-refineall --feature \($feature) --auto --depth ready" else "/sp:dev-runall --feature \($feature) --auto" end' "$RESULT") &&
504
+ READY=".spur/run/$__runId-idea-ready.json" &&
505
+ (test -f "$READY" || printf '{"tasks":[]}\n' > "$READY") &&
506
+ NEXT=$(jq -r --arg feature "$featureId" --slurpfile c "$CHECKS" --slurpfile k "$READY" 'if (($c | any(.[]; .pass == false)) or (($k[0].tasks // []) | length == 0) or (($k[0].tasks // []) | any(.[]; .status != "ready"))) then "/sp:dev-refineall --feature \($feature) --auto --depth ready" else "/sp:dev-runall --feature \($feature) --auto" end' "$RESULT") &&
430
507
  {
431
508
  echo "# Idea pipeline handoff report";
432
509
  echo;
@@ -466,10 +543,6 @@ states:
466
543
  options:
467
544
  path: .spur/run/${vars.__runId}-idea-handoff.md
468
545
  artifactKind: idea-handoff
469
- # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
470
- - kind: shell
471
- options:
472
- command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: idea-pipeline handoff feature=$featureId ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/idea-checkpoint.md'
473
546
 
474
547
  - id: cancelled
475
548
  description: Terminal — pipeline cancelled by operator or error.
@@ -545,9 +618,10 @@ transitions:
545
618
 
546
619
  # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
547
620
  # 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.
621
+ # and design-gen→handoff). Under profile=auto, the recorded `idea-ac-check` result routes
622
+ # directly to the appropriate next state guards never re-run the CLI (task 0769). Under
623
+ # interactive, the always fallback enters the feature-check state whose onEnter hitl.confirm
624
+ # pauses for operator confirmation.
551
625
  #
552
626
  # Auto-skip 1: pass + design route → system-design (design=auto + needs_design != false)
553
627
  - from: ac-generate
@@ -556,7 +630,7 @@ transitions:
556
630
  guard:
557
631
  kind: shell
558
632
  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'
633
+ 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
634
  # Auto-skip 2: pass + skip-design route → decompose
561
635
  - from: ac-generate
562
636
  to: decompose
@@ -564,7 +638,7 @@ transitions:
564
638
  guard:
565
639
  kind: shell
566
640
  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))'
641
+ 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
642
  # Auto-skip 3: check failed, retry < 3 → loop back to ac-generate (self-loop)
569
643
  - from: ac-generate
570
644
  to: ac-generate
@@ -572,7 +646,7 @@ transitions:
572
646
  guard:
573
647
  kind: shell
574
648
  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'
649
+ 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
650
  # Auto-skip 4: check failed, retry cap reached → escalate to failed
577
651
  - from: ac-generate
578
652
  to: failed
@@ -580,7 +654,7 @@ transitions:
580
654
  guard:
581
655
  kind: shell
582
656
  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'
657
+ 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
658
  # Interactive fallback: enter feature-check HITL gate
585
659
  - from: ac-generate
586
660
  to: feature-check
@@ -596,28 +670,28 @@ transitions:
596
670
  guard:
597
671
  kind: shell
598
672
  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'
673
+ 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
674
  - from: feature-check
601
675
  to: decompose
602
676
  description: Feature check passed, skip-design route — go directly to decompose.
603
677
  guard:
604
678
  kind: shell
605
679
  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))'
680
+ 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
681
  - from: feature-check
608
682
  to: ac-generate
609
683
  description: "Feature check failed — revise AC (retry cap: 3)."
610
684
  guard:
611
685
  kind: shell
612
686
  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'
687
+ 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
688
  - from: feature-check
615
689
  to: failed
616
690
  description: Feature check failed after 3 retries — escalate to failed.
617
691
  guard:
618
692
  kind: shell
619
693
  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'
694
+ 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
695
  - from: feature-check
622
696
  to: cancelled
623
697
  description: Operator cancelled the feature-check gate.
@@ -630,11 +704,11 @@ transitions:
630
704
  # Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
631
705
  - from: system-design
632
706
  to: decompose
633
- description: profile=auto AND design_approved=true AND feature check passes — skip design approval gate.
707
+ description: profile=auto AND design_approved=true AND recorded design check passes — skip design approval gate.
634
708
  guard:
635
709
  kind: shell
636
710
  options:
637
- command: 'test "$profile" = auto && test "$design_approved" = true && $spurBin feature check "$featureId"'
711
+ command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
638
712
  - from: system-design
639
713
  to: design-approval
640
714
  description: System design done — gate on design approval.
@@ -644,18 +718,18 @@ transitions:
644
718
  # ── design-approval -> decompose (after operator approval), AC-failure -> feature-check ──
645
719
  - from: design-approval
646
720
  to: decompose
647
- description: Design approved and feature check passes — proceed to decomposition.
721
+ description: Design approved and recorded design check passes — proceed to decomposition.
648
722
  guard:
649
723
  kind: shell
650
724
  options:
651
- command: 'test "$__hitlAnswer" = yes && $spurBin feature check "$featureId"'
725
+ command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
652
726
  - from: design-approval
653
727
  to: feature-check
654
- description: "Design approved but feature check fails — route back through the AC gate (revise AC, retry cap: 3)."
728
+ description: "Design approved but recorded design check fails — route back through the AC gate (revise AC, retry cap: 3)."
655
729
  guard:
656
730
  kind: shell
657
731
  options:
658
- command: 'test "$__hitlAnswer" = yes && ! $spurBin feature check "$featureId"'
732
+ command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'
659
733
  - from: design-approval
660
734
  to: system-design
661
735
  description: "Design rejected - revise system design (cap: 1 revise)."
@@ -715,14 +789,20 @@ transitions:
715
789
  options:
716
790
  command: 'test "$__hitlAnswer" = cancel'
717
791
 
718
- # ── batch-create-run: success -> handoff-finalize, failure -> decompose (retry cap: 3), failure+cap -> failed ──
792
+ # ── batch-create-run: success -> ready-prepare, failure -> decompose (retry cap: 3), failure+cap -> failed ──
719
793
  - from: batch-create-run
720
- to: handoff-finalize
721
- description: Batch created and result captured — apply ordering, refresh roster, write handoff report.
794
+ to: ready-prepare
795
+ description: Batch created and result captured — apply ready-by-default preparation, then finalize the handoff.
722
796
  guard:
723
797
  kind: shell
724
798
  options:
725
799
  command: "test -f .spur/run/$__runId-idea-batch-create.done"
800
+ # ── ready-prepare: -> handoff-finalize (always; evidence absence degrades to refineall, never fails the run) ──
801
+ - from: ready-prepare
802
+ to: handoff-finalize
803
+ description: Ready evidence sidecar written (or normalized empty) — finalize the handoff report.
804
+ guard:
805
+ kind: always
726
806
  - from: batch-create-run
727
807
  to: decompose
728
808
  description: "Batch-create failed — revise decomposition (retry cap: 3)."
@@ -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