@gobing-ai/spur 0.4.14 → 0.4.16

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 (121) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +21 -8
  3. package/config/decisions/executor.yaml +53 -0
  4. package/config/decisions/task-pipeline.yaml +44 -28
  5. package/config/pipeline-budgets.json +0 -14
  6. package/config/workflow-candidates.json +18 -0
  7. package/config/workflows/feature-lifecycle.yaml +9 -16
  8. package/config/workflows/idea-pipeline.yaml +158 -740
  9. package/config/workflows/task-pipeline.yaml +66 -54
  10. package/config/workflows/wrapup-pipeline.yaml +91 -623
  11. package/package.json +11 -10
  12. package/plugins/sp/README.md +9 -5
  13. package/plugins/sp/agents/expert-spur.md +3 -0
  14. package/plugins/sp/agents/super-planner.md +5 -1
  15. package/plugins/sp/agents/super-reviewer.md +43 -12
  16. package/plugins/sp/commands/dev-fixall.md +7 -2
  17. package/plugins/sp/commands/dev-idea.md +15 -6
  18. package/plugins/sp/commands/dev-plan.md +1 -1
  19. package/plugins/sp/commands/dev-pr-review.md +6 -6
  20. package/plugins/sp/commands/dev-run.md +3 -0
  21. package/plugins/sp/commands/dev-runall.md +6 -3
  22. package/plugins/sp/commands/dev-verify.md +1 -1
  23. package/plugins/sp/commands/dev-wrap.md +1 -1
  24. package/plugins/sp/commands/dev-wrapall.md +4 -4
  25. package/plugins/sp/plugin.json +1 -1
  26. package/plugins/sp/skills/code-implementation/SKILL.md +8 -8
  27. package/plugins/sp/skills/code-improvement/SKILL.md +3 -3
  28. package/plugins/sp/skills/code-review/references/verification-before-completion.md +5 -4
  29. package/plugins/sp/skills/code-verification/SKILL.md +4 -4
  30. package/plugins/sp/skills/code-verification/references/verdict-schema.md +27 -11
  31. package/plugins/sp/skills/functional-review/SKILL.md +1 -1
  32. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  33. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  34. package/plugins/sp/skills/pr-reviewing/SKILL.md +36 -18
  35. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +2 -1
  36. package/plugins/sp/skills/spur-check/SKILL.md +1 -1
  37. package/plugins/sp/skills/spur-cli/SKILL.md +23 -2
  38. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  39. package/plugins/sp/skills/spur-cli/references/builder.md +2 -2
  40. package/plugins/sp/skills/spur-cli/references/decision/lifecycle.md +166 -0
  41. package/plugins/sp/skills/spur-cli/references/decision.md +146 -16
  42. package/plugins/sp/skills/spur-cli/references/features.md +19 -0
  43. package/plugins/sp/skills/spur-cli/references/rules.md +2 -0
  44. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -1
  45. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  46. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +3 -2
  47. package/plugins/sp/skills/spur-cli/references/workflows.md +9 -0
  48. package/plugins/sp/skills/spur-composer/SKILL.md +20 -1
  49. package/plugins/sp/skills/spur-dev/SKILL.md +16 -16
  50. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +2 -1
  51. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +2 -2
  52. package/plugins/sp/skills/spur-dev/references/decision-points.md +97 -0
  53. package/plugins/sp/skills/spur-dev/references/dev-operations.md +18 -18
  54. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  55. package/plugins/sp/skills/spur-dev/references/execution-batch.md +19 -10
  56. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -4
  57. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +41 -29
  58. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +34 -2
  59. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
  60. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -3
  61. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  62. package/plugins/sp/skills/spur-dev/references/glossary.md +12 -3
  63. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +7 -6
  64. package/plugins/sp/skills/spur-doctor/SKILL.md +23 -0
  65. package/plugins/sp/skills/wayfinder/SKILL.md +11 -1
  66. package/spur.js +25617 -24290
  67. package/web/_astro/{BoardApp.Bjdp3XWV.js → BoardApp.CC7Mwswv.js} +65 -65
  68. package/web/_astro/{BoardApp.DCDZxLlQ.js → BoardApp.mWyrWCAR.js} +1 -1
  69. package/web/_astro/{TaskDetail.B3vBxynO.js → TaskDetail.CtAXuiv8.js} +1 -1
  70. package/web/_astro/{arc.DcuHTB_T.js → arc.Duf4u60i.js} +1 -1
  71. package/web/_astro/{architectureDiagram-3BPJPVTR.ClOFWBON.js → architectureDiagram-3BPJPVTR.JoymtqxN.js} +1 -1
  72. package/web/_astro/{blockDiagram-GPEHLZMM.otYyr7Zb.js → blockDiagram-GPEHLZMM.BvmhcioC.js} +1 -1
  73. package/web/_astro/{c4Diagram-AAUBKEIU.BCjpc9LP.js → c4Diagram-AAUBKEIU.CfaWkgoQ.js} +1 -1
  74. package/web/_astro/channel.DtEPXXcH.js +1 -0
  75. package/web/_astro/{chunk-2J33WTMH.D5Wpnpt_.js → chunk-2J33WTMH.B7uR02cD.js} +1 -1
  76. package/web/_astro/{chunk-4BX2VUAB.DkQGtWNz.js → chunk-4BX2VUAB.SwiEauYR.js} +1 -1
  77. package/web/_astro/{chunk-55IACEB6.B7dqjAip.js → chunk-55IACEB6.6ZRCK8PO.js} +1 -1
  78. package/web/_astro/{chunk-727SXJPM.BDQvnKE2.js → chunk-727SXJPM.Cxra_G3N.js} +1 -1
  79. package/web/_astro/{chunk-AQP2D5EJ.D92doBX5.js → chunk-AQP2D5EJ.Cxd7gZeX.js} +1 -1
  80. package/web/_astro/{chunk-FMBD7UC4.C968oI5G.js → chunk-FMBD7UC4.DCLhFkVc.js} +1 -1
  81. package/web/_astro/{chunk-ND2GUHAM.AkkYC1bY.js → chunk-ND2GUHAM.B9AmKcv6.js} +1 -1
  82. package/web/_astro/{chunk-QZHKN3VN.sCc-rKKo.js → chunk-QZHKN3VN.DOjCvl6S.js} +1 -1
  83. package/web/_astro/{classDiagram-4FO5ZUOK.pOQ9u0mY.js → classDiagram-4FO5ZUOK.BMiT_mNf.js} +1 -1
  84. package/web/_astro/{classDiagram-v2-Q7XG4LA2.pOQ9u0mY.js → classDiagram-v2-Q7XG4LA2.BMiT_mNf.js} +1 -1
  85. package/web/_astro/{cose-bilkent-S5V4N54A.CRaeD6FY.js → cose-bilkent-S5V4N54A.mLBCEGjI.js} +1 -1
  86. package/web/_astro/{cynefin-OW5HDTMX.BiCPBu54.js → cynefin-OW5HDTMX.CjDAY0qn.js} +1 -1
  87. package/web/_astro/{dagre-BM42HDAG.C1-q3vkT.js → dagre-BM42HDAG._G_sP8fP.js} +1 -1
  88. package/web/_astro/{diagram-2AECGRRQ.BSVOv30O.js → diagram-2AECGRRQ.39OcfSuz.js} +1 -1
  89. package/web/_astro/{diagram-5GNKFQAL.CHMkjJ-V.js → diagram-5GNKFQAL.D7v_eNC4.js} +1 -1
  90. package/web/_astro/{diagram-KO2AKTUF.CKY9M564.js → diagram-KO2AKTUF.Da1x-lhg.js} +1 -1
  91. package/web/_astro/{diagram-LMA3HP47.B9yNJiTP.js → diagram-LMA3HP47.C8EnzbEb.js} +1 -1
  92. package/web/_astro/{diagram-OG6HWLK6.Dk8AUGsk.js → diagram-OG6HWLK6.DPnBj69F.js} +1 -1
  93. package/web/_astro/{erDiagram-TEJ5UH35.BQHR89TZ.js → erDiagram-TEJ5UH35.CXMu2TIh.js} +1 -1
  94. package/web/_astro/{flowDiagram-I6XJVG4X.oNkWVgoK.js → flowDiagram-I6XJVG4X.ZklePVtQ.js} +1 -1
  95. package/web/_astro/{ganttDiagram-6RSMTGT7.Kz2U2nu9.js → ganttDiagram-6RSMTGT7.BhYOkdqk.js} +1 -1
  96. package/web/_astro/{gitGraphDiagram-PVQCEYII.DHIZ_wPq.js → gitGraphDiagram-PVQCEYII.CADyX8a1.js} +1 -1
  97. package/web/_astro/{infoDiagram-5YYISTIA.D0NJLcv9.js → infoDiagram-5YYISTIA.BsdmwjHt.js} +1 -1
  98. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CFEfTWRG.js → ishikawaDiagram-YF4QCWOH.CzjZ3Crd.js} +1 -1
  99. package/web/_astro/{journeyDiagram-JHISSGLW.Dc7IE7I4.js → journeyDiagram-JHISSGLW.Bxj-ZH_t.js} +1 -1
  100. package/web/_astro/{kanban-definition-UN3LZRKU.D5v0xO9k.js → kanban-definition-UN3LZRKU.Cbg3wVvX.js} +1 -1
  101. package/web/_astro/{linear.wR5KzXnE.js → linear.DJNUrBI9.js} +1 -1
  102. package/web/_astro/{mermaid.core.BDQ2FoPd.js → mermaid.core.DVHQV3SF.js} +4 -4
  103. package/web/_astro/{mindmap-definition-RKZ34NQL.W6_INsia.js → mindmap-definition-RKZ34NQL.DyomosRU.js} +1 -1
  104. package/web/_astro/{pieDiagram-4H26LBE5.61ihbgkK.js → pieDiagram-4H26LBE5.DajtFknN.js} +1 -1
  105. package/web/_astro/{quadrantDiagram-W4KKPZXB.B5lEpFLo.js → quadrantDiagram-W4KKPZXB.Bjq6SjGe.js} +1 -1
  106. package/web/_astro/{requirementDiagram-4Y6WPE33.CmhVBVX0.js → requirementDiagram-4Y6WPE33.ubBVqGLP.js} +1 -1
  107. package/web/_astro/{sankeyDiagram-5OEKKPKP.D7Vvj9Wf.js → sankeyDiagram-5OEKKPKP.I4q46xd-.js} +1 -1
  108. package/web/_astro/{sequenceDiagram-3UESZ5HK.B1vPLixM.js → sequenceDiagram-3UESZ5HK.DsCgZIa8.js} +1 -1
  109. package/web/_astro/{stateDiagram-AJRCARHV.BnACmrGi.js → stateDiagram-AJRCARHV.DcYjYSqT.js} +1 -1
  110. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CW4pnmc3.js → stateDiagram-v2-BHNVJYJU.Dt8yPqiS.js} +1 -1
  111. package/web/_astro/{timeline-definition-PNZ67QCA.CkzMCPnC.js → timeline-definition-PNZ67QCA.0YTkoa5F.js} +1 -1
  112. package/web/_astro/{vennDiagram-CIIHVFJN.CY4aNdAR.js → vennDiagram-CIIHVFJN.BNl0l1hs.js} +1 -1
  113. package/web/_astro/{wardleyDiagram-YWT4CUSO.BtoU9UFk.js → wardleyDiagram-YWT4CUSO.BYSX6cfn.js} +1 -1
  114. package/web/_astro/{xychartDiagram-2RQKCTM6._9ijUUAs.js → xychartDiagram-2RQKCTM6.DzxxpixK.js} +1 -1
  115. package/web/index.html +1 -1
  116. package/worker/laya_worker.py +219 -0
  117. package/config/workflows/feature-verification.yaml +0 -98
  118. package/config/workflows/pr-review.yaml +0 -209
  119. package/config/workflows/wayfinder-resolution.yaml +0 -302
  120. package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +0 -30
  121. package/web/_astro/channel.qgOLXjI_.js +0 -1
@@ -1,67 +1,48 @@
1
- # Idea-to-feature pipeline — unified entry from a vague idea to a feature, AC, and task batch
2
- # (design §idea-pipeline).
1
+ # Idea-to-feature pipeline — unified entry from a vague idea to a feature, AC, and a task batch
2
+ # (design docs/design/workflow-consolidation.md §3.4). YAML over the dual-workflow engine; zero new
3
+ # engine code. The pipeline STOPS at handoff — tasks are created but NOT executed; use
4
+ # task-pipeline.yaml for execution. No state inlines another pipeline's state graph.
3
5
  #
4
- # Orchestration is configuration (ADR-022 / §3.2): this is YAML over the existing
5
- # dual-workflow engine — zero new engine code. The pipeline STOPS at handoff — tasks are
6
- # created but NOT executed. Use task-pipeline.yaml or feature-dev.yaml for execution.
7
- # No state in this pipeline inlines another pipeline's state graph (no-nesting principle,
8
- # design §System Principles 4).
6
+ # Shape: start -> discovery -> idea-eval -> feature-create -> ac-generate
7
+ # -> system-design (conditional: needs_design signal) -> design-approval (taste)
8
+ # -> decompose -> batch-create-run -> ready-prepare -> handoff-finalize -> handoff.
9
+ # idea-eval rejection (or auto + `drop`) -> cancelled; ac-generate check failure and
10
+ # batch-create failure -> their own capped retry loops -> failed.
9
11
  #
10
- # Shape: start -> discovery -> idea-eval -> feature-create -> ac-generate -> feature-check
11
- # -> system-design (conditional: needs_design signal)
12
- # -> design-approval (taste HITL gate; bypassed only when design_approved=true + design check PASS)
13
- # -> decompose -> batch-create -> handoff-finalize -> handoff
14
- # (idea-eval rejection, or auto + `drop` recommendation -> cancelled)
15
- # (feature-check failure routes back to ac-generate; batch-create failure to decompose).
12
+ # D66 1250 (design §3.4): the objective checks carry no operator pause. `feature-check` and
13
+ # `batch-create` are DELETED — their recorded results are deterministic PASS/FAIL, which every
14
+ # profile now routes on directly, so the former `profile=auto` predicates and the always fallbacks
15
+ # into those states are gone. The two discovery rescues are typed `decide` actions over the catalog
16
+ # ids in config/decisions/idea-pipeline.yaml (rescue-only, design §4): the deterministic parse runs
17
+ # first and the decide is consulted only for output the parse cannot classify. `idea-eval` and
18
+ # `design-approval` stay operator gates (taste). The start-state entry check is EXECUTOR-TYPED: a
19
+ # host run's executor is the session itself (no subprocess doctor), and a fleet run is ready exactly
20
+ # when this host wired fleet dispatch (ADR-126).
16
21
  #
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.
22
+ # Evidence instead of ceremony (task 0769): the feature check is MEASURED once at each author/revise
23
+ # boundary — `idea-ac-check` at the end of ac-generate, `idea-design-check` at the end of
24
+ # system-design — by a deterministic `command.gate` writing a run-scoped PASS/FAIL result file.
25
+ # Guards only CONSUME the recorded result; no guard re-runs the CLI.
23
26
  #
24
- # Vars (passed as a JSON object via `--vars`):
25
- # idea — the idea text (required), e.g. --vars '{"idea":"add a --dry-run flag to dev-wrap"}'
26
- # profile — set --vars '{"profile":"auto"}' to skip objective HITL gates (feature-check, batch-create)
27
- # design — "auto" (default, signal-driven) or "skip" (--skip-design). No force path.
28
- # Unified: skip also means omit per-task Design in batch-create (scaffold only;
29
- # refine fills later). Default: decompose must author batch item `design`.
30
- # design_approved — "true" lets profile=auto route around the design-approval taste gate when
31
- # the recorded design check PASSes (default "false")
32
- # idea_approved — "true" lets profile=auto accept the idea-eval report's recommendation
33
- # (proceed/reshape → continue, drop → cancelled; missing → pause) (default "false")
34
- # CLI --auto on dev-idea / dev-plan sets profile=auto AND both pre-clear vars to true.
35
- # spurBin — PATH-independent spur invocation (overridden by CLI at run start)
36
- # agent — agent for agent.run steps (default: auto → `agent.default` in config)
37
- #
38
- # Reliability (aligned with task-pipeline / ADR-043):
39
- # - Soft agent doctor at start → failed via transitions (not raw lifecycle abort)
40
- # - expectFile on feature-create / ac-generate / decompose artifacts (no silent no-ops)
41
- # - Caped AC and batch-create retry loops with fail-closed escalation
42
- # - HITL taste gates (idea-eval, design-approval) exhaustive yes/no/cancel
43
- # - Free-form skill pointers remain only where no pure slash surface exists yet
44
- # (discovery/sys-architecture/decompose internals — residual until dedicated commands)
45
- #
46
- # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
27
+ # Vars (`--vars '{"idea":"…"}'`): idea (required); profile ("auto" skips the taste gates' questions —
28
+ # they answer from recorded evidence — while the objective stages route identically under every
29
+ # profile); design ("auto" default signal-driven | "skip", which also omits per-task Design);
30
+ # design_approved / idea_approved ("true" lets profile=auto route around that taste gate, default
31
+ # "false"); spurBin; agent (agent.run steps; default auto → `agent.default`); executor ("host" |
32
+ # "fleet" | "" for the tier ladder — read by the entry check and every agent.run stage).
33
+ # CLI --auto on dev-idea / dev-plan sets profile=auto AND both pre-clear vars to true.
47
34
 
48
35
  "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
49
- version: "2"
50
36
  kind: state-machine
51
37
  name: idea-pipeline
52
- description: "Idea to feature + AC + task batch: discovery, idea-eval, feature-create, ac-generate, feature-check, system-design, decompose, batch-create, handoff"
53
- # Worst legal auto path with advertised caps: start/discovery/idea-eval/feature/ac plus 2 AC
54
- # retries, design, one design rejection, decompose plus 2 batch retries, handoff ≈24 transitions.
55
- # Keep a small cushion so the explicit retry caps fail first, not the engine bound.
38
+ version: "3"
39
+ description: "Idea to feature + AC + task batch: discovery, idea-eval, feature-create, ac-generate, system-design, decompose, ready-prepare, handoff"
40
+ # Worst legal path with advertised caps ≈20 transitions; the cushion keeps the explicit retry caps
41
+ # failing first, not the engine bound.
56
42
  iterationBound: 25
57
43
  initialState: start
58
- terminalStates:
59
- - handoff
60
- - cancelled
61
- - failed
62
- failureStates:
63
- - failed
64
- - cancelled
44
+ terminalStates: [handoff, cancelled, failed]
45
+ failureStates: [failed, cancelled]
65
46
  vars:
66
47
  idea: ""
67
48
  profile: "standard"
@@ -76,26 +57,16 @@ vars:
76
57
  agent: "auto"
77
58
  planningAgent: ""
78
59
  stepTimeoutMs: "1800000"
79
- # Executor for every agent.run stage (D65 R1): "host" pauses the run with a work item
80
- # (`spur workflow run|continue --json`), "fleet" dispatches through ADR-126, and empty
81
- # resolves the subprocess surface through the existing tier ladder.
60
+ # Executor for every agent.run stage (D65 R1): "host" pauses the run with a work item, "fleet"
61
+ # dispatches through ADR-126, empty resolves the subprocess surface through the tier ladder.
82
62
  executor: ""
83
63
  # Accepted/deferred status each operator gate's decision binding writes (1236 R2).
84
64
  ideaEvalStatus: ""
85
- featureCheckStatus: ""
86
65
  designApprovalStatus: ""
87
- batchCreateStatus: ""
88
66
 
89
67
  states:
90
68
  - id: start
91
- display:
92
- phase: discover
93
- phaseTitle: Discover
94
- title: Check inputs
95
- description: >
96
- Pipeline start. The idea text arrives as vars.idea. The pipeline stops at
97
- handoff — no task execution. Soft agent doctor (status file) so doctor red
98
- routes to `failed` via transitions instead of a raw lifecycle abort.
69
+ description: "One executor-typed entry check (D66 1250 R1): the executor-aware doctor.probe writes the executor verdict and the idea-text check writes its own named reason to the SAME status file, so a single token routes."
99
70
  onEnter:
100
71
  - kind: note
101
72
  options:
@@ -107,8 +78,8 @@ states:
107
78
  agent: "${vars.agent}"
108
79
  role: planner
109
80
  resolvedAgentVar: planningAgent
110
- # B7 R1 (0894): also pin the role once — __executor.planner feeds stage
111
- # dispatch so no later idea stage re-walks the doctor ladder.
81
+ # B7 R1 (0894): also pin the role once — __executor.planner feeds stage dispatch so no
82
+ # later idea stage re-walks the doctor ladder.
112
83
  roles:
113
84
  planner: "${vars.agent}"
114
85
  - kind: shell
@@ -117,101 +88,81 @@ states:
117
88
  mkdir -p .spur/run &&
118
89
  printf '%s\n' "$idea" > ".spur/run/$__runId-idea-input.md" &&
119
90
  awk 'NF' ".spur/run/$__runId-idea-input.md" | grep -q . ||
120
- printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
91
+ printf 'FAIL: idea text is empty\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
121
92
 
122
93
  - id: discovery
123
- display:
124
- phase: discover
125
- phaseTitle: Discover
126
- title: Discovery interview
127
- description: >
128
- Dispatch sp:brainstorm to explore the idea, generate approaches with trade-offs,
129
- and record a design summary. The brainstorm ALWAYS records a design summary
130
- ("nothing is too simple" pattern). The brainstorm also emits a needs_design
131
- boolean signal written to .spur/run/${vars.__runId}-idea-needs-design.json — this determines
132
- whether the system-design state runs. As its terminal artifact for the idea path,
133
- brainstorm also emits the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md
134
- (template: the `sp:spur-dev` skill's `idea-evaluation` reference — named by skill, not by
135
- repo path, because `spur init` never scaffolds `plugins/sp/` into a seeded project).
136
- The operator's verbatim idea argument is persisted at .spur/run/${vars.__runId}-idea-input.md
137
- (written by start; the authoritative ask every model-bearing stage reads).
138
- expectFile fails a silent no-op discovery (no eval report).
94
+ description: "Dispatch sp:brainstorm: approaches with trade-offs, a design summary, the needs_design signal, and the idea-evaluation report (.spur/run/${vars.__runId}-idea-eval-report.md, per the `sp:spur-dev` `idea-evaluation` reference — named by skill, not repo path). The operator's verbatim idea is persisted at .spur/run/${vars.__runId}-idea-input.md by start. expectFile fails a silent no-op discovery. D66 1250 R3: the two rescues are typed `decide` actions whose projections keep the pre-1250 fallback semantics."
139
95
  onEnter:
140
96
  - kind: agent.run
141
97
  options:
142
98
  agent: ${vars.planningAgent}
143
99
  input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative, read it first (${vars.idea} is a convenience echo). Run sp:brainstorm. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) plus the design summary per its `Design Approval Gate` / `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation, plus mandatory `## Requirement inventory`: numbered I<n> items quoting/paraphrasing idea-input lines; `[unclear: ...]` marks ambiguity, `[deferred: <reason>]` marks out-of-scope). End with this exact footer: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339>\\n---` (omit if run_id is empty)."
144
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
100
+ # Declared Layer-1 role (0538 R2) + declared fresh policy (B7 R6 / 0894).
145
101
  role: planner
146
- # B7 R6 (0894): planner stages declare fresh — the role default is stated
147
- # explicitly so the definition is self-documenting.
148
102
  session: fresh
149
103
  expectFile: .spur/run/${vars.__runId}-idea-eval-report.md
150
- # The signal is this stage's own output; the plural form declares it to the
151
- # work item (host/fleet) while the singular expectFile stays the subprocess
152
- # post-exit guard, so a missing signal still falls back to the catalog decision.
104
+ # The signal is this stage's own output; the plural form declares it to the work item
105
+ # (host/fleet) while the singular expectFile stays the subprocess post-exit guard.
153
106
  expectFiles:
154
107
  - .spur/run/${vars.__runId}-idea-needs-design.json
155
108
  timeoutMs: ${vars.stepTimeoutMs}
156
109
  # Derive the eval recommendation ONCE (0769 pattern): the first non-blank line under
157
- # `## Recommendation` → proceed | reshape | drop | unknown. The --auto guards out of
158
- # discovery / idea-eval read this file; `unknown` routes to the idea-eval pause.
110
+ # `## Recommendation` → proceed | reshape | drop | unknown. `unknown` routes to idea-eval.
159
111
  - kind: shell
160
112
  options:
161
113
  command: >-
162
114
  awk '/^## Recommendation/{f=1;next} f&&NF{r=tolower($0);exit}
163
115
  END{print (match(r,/^[*_ ]*(proceed|reshape|drop)/) ? substr(r,RSTART,RLENGTH) : "unknown")}'
164
116
  ".spur/run/$__runId-idea-eval-report.md" | tr -d '*_ ' > ".spur/run/$__runId-idea-recommendation.txt"
165
- # 1097 R2 (design §4 rescue-only): the awk parse stays first; this rescue runs ONLY when
166
- # the derived file is `unknown`. A model answer is written only when `source == "model"`
167
- # and it lands in the closed vocabulary (proceed|reshape|drop) — every other outcome
168
- # (spur missing/non-zero, unparseable output, model `unknown`) leaves `unknown` in place,
169
- # which is today's idea-eval pause route. Exits 0 on every path (default onError: fail).
117
+ # D66 1250 R3 (design §4 rescue-only): the awk parse above stays FIRST; this typed decide
118
+ # replaces the former `spur decision run` shell and is consulted only for unclassifiable
119
+ # output. It writes the frozen schemaVersion-1 row.
120
+ - kind: decide
121
+ options:
122
+ id: idea-recommendation
123
+ decision: idea-recommendation
124
+ evidence:
125
+ - .spur/run/${vars.__runId}-idea-eval-report.md
126
+ resultFile: .spur/run/${vars.__runId}-idea-recommendation.decision
127
+ # Projection (1250 R3): a non-model source (switch off, backend failure, low confidence) reads
128
+ # `unknown`, so no default can auto-create a feature — it routes to the idea-eval pause.
170
129
  - kind: shell
171
130
  options:
172
131
  command: >-
173
132
  f=".spur/run/$__runId-idea-recommendation.txt";
174
133
  [ "$(cat "$f" 2>/dev/null)" = unknown ] || exit 0;
175
- v="$($spurBin decision run idea-recommendation --evidence ".spur/run/$__runId-idea-eval-report.md" --json 2>/dev/null | jq -r 'if .source=="model" then .value else "unknown" end' 2>/dev/null)";
134
+ v="$(jq -r 'if .source=="model" then .value else "unknown" end' ".spur/run/$__runId-idea-recommendation.decision" 2>/dev/null)";
176
135
  case "$v" in proceed|reshape|drop) printf '%s\n' "$v" > "$f" ;; esac
177
- # 1097 R3 (same rescue-only pattern): a valid needs_design boolean short-circuits — no
178
- # decision is called. On a missing/corrupt/non-boolean file the catalog decision decides:
179
- # `{"needs_design": false}` only when `source == "model"` AND `value == "skip"`; otherwise
180
- # `{"needs_design": true}` — today's behavior for a missing or corrupt file. The
181
- # ac-generate route writer (:274) consumes this file unchanged. Exits 0 on every path.
136
+ # D66 1250 R3: the same shape for the design signal — a valid boolean short-circuits in the
137
+ # projection; otherwise it falls back to `design` unless the model said `skip`.
138
+ - kind: decide
139
+ options:
140
+ id: needs-design
141
+ decision: needs-design
142
+ evidence:
143
+ - .spur/run/${vars.__runId}-idea-eval-report.md
144
+ resultFile: .spur/run/${vars.__runId}-idea-needs-design.decision
182
145
  - kind: shell
183
146
  options:
184
147
  command: >-
185
148
  f=".spur/run/$__runId-idea-needs-design.json";
186
149
  jq -e '.needs_design|type=="boolean"' "$f" >/dev/null 2>&1 && exit 0;
187
- $spurBin decision run needs-design --evidence ".spur/run/$__runId-idea-eval-report.md" --json 2>/dev/null | jq -e '.source=="model" and .value=="skip"' >/dev/null 2>&1 && printf '{"needs_design": false}\n' > "$f" || printf '{"needs_design": true}\n' > "$f"
150
+ jq -e '.source=="model" and .value=="skip"' ".spur/run/$__runId-idea-needs-design.decision" >/dev/null 2>&1 &&
151
+ printf '{"needs_design": false}\n' > "$f" || printf '{"needs_design": true}\n' > "$f"
188
152
 
189
153
  - id: idea-eval
190
- display:
191
- phase: discover
192
- phaseTitle: Discover
193
- title: Evaluate idea
194
- description: >
195
- HITL taste gate. Discovery has produced the idea-evaluation report
196
- (.spur/run/${vars.__runId}-idea-eval-report.md) with urgency/necessity scores, premises,
197
- pros/cons, and a recommendation. The operator reviews and approves or
198
- rejects. Routed around only when profile=auto AND vars.idea_approved=true
199
- (CLI --auto) AND the report's recommendation is proceed/reshape; a drop
200
- recommendation cancels, a missing one asks the gate, whose evidence binding then
201
- answers under profile=auto (1236/1241).
154
+ description: "HITL taste gate over the idea-evaluation report. Routed around only when profile=auto AND vars.idea_approved=true AND the recommendation is proceed/reshape; a drop recommendation cancels and a missing one asks the gate, whose evidence binding then answers under profile=auto (1236/1241)."
202
155
  onEnter:
203
156
  - kind: hitl.confirm
204
157
  options:
205
158
  prompt: "Review the idea evaluation report (.spur/run/${vars.__runId}-idea-eval-report.md). Approve to create a feature, or reject to cancel? (Reject creates no feature.)"
206
- # Interactive always prompts the operator; profile=auto answers from the discovery
207
- # stage's recorded evidence, deferring when it cannot decide (1236 R1/R2).
208
159
  decision:
209
160
  mode: evidence
210
161
  statusVar: ideaEvalStatus
211
162
  evidenceNodes:
212
163
  - discovery
213
- # A deferred/absent answer is the denial today's headless responder gave, so the
214
- # guards keep routing on yes | no | cancel with no new edge.
164
+ # A deferred/absent answer is the denial today's headless responder gave, so the guards keep
165
+ # routing on yes | no | cancel with no new edge.
215
166
  - kind: shell
216
167
  options:
217
168
  command: >-
@@ -223,33 +174,17 @@ states:
223
174
  var: __hitlAnswer
224
175
 
225
176
  - id: feature-create
226
- display:
227
- phase: define
228
- phaseTitle: "Define feature"
229
- title: Create feature
230
- description: >
231
- Create a feature via spur feature create, or select an existing feature id.
232
- The agent writes the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt and body-only
233
- intent artifacts .spur/run/${vars.__runId}-idea-goal.md / -idea-scope.md. Goal carries
234
- concise intent only; Scope carries explicit in/out boundaries — task breakdowns and
235
- checklists never enter Goal. Shell actions then persist both sections through
236
- `spur feature update --section Goal|Scope --from-file ...`; a missing/empty artifact
237
- stops the state. Prefer the enhanced idea from .spur/run/${vars.__runId}-idea-eval-report.md as
238
- context; do not overwrite vars.idea. The operator's verbatim ask of record is
239
- .spur/run/${vars.__runId}-idea-input.md (persisted at start; authoritative over any
240
- paraphrase).
177
+ description: "Create a feature via `spur feature create` (or select an existing id). The agent writes the feature id plus body-only Goal/Scope intent artifacts; the shell persists both sections through `spur feature update --from-file` and fails closed on an empty artifact. Goal carries intent only — task breakdowns and checklists never enter it. The operator's verbatim ask of record stays .spur/run/${vars.__runId}-idea-input.md."
241
178
  onEnter:
242
179
  - kind: agent.run
243
180
  options:
244
181
  agent: ${vars.planningAgent}
245
182
  input: 'Create a feature for the idea. The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md — the idea argument persisted verbatim at start; treat it as the authoritative ask. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it; with --json the feature id is returned under the .ref.id envelope (n), not a top-level .id — read it from there. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
246
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
247
183
  role: planner
248
- # B7 R6 (0894): declared fresh policy (planner default).
249
184
  session: fresh
250
185
  expectFile: .spur/run/${vars.__runId}-idea-feature-id.txt
251
- # Intent artifacts this stage writes; the shell below fails closed when either is
252
- # missing, so declaring them adds the work-item contract, not a new precondition.
186
+ # Intent artifacts this stage writes; the shell below fails closed when either is missing,
187
+ # so declaring them adds the work-item contract, not a new precondition.
253
188
  expectFiles:
254
189
  - .spur/run/${vars.__runId}-idea-goal.md
255
190
  - .spur/run/${vars.__runId}-idea-scope.md
@@ -258,9 +193,8 @@ states:
258
193
  options:
259
194
  path: .spur/run/${vars.__runId}-idea-feature-id.txt
260
195
  var: featureId
261
- # 1007 R2: one shell persists both sections — the Goal and Scope updates share the
262
- # artifact precondition, so the two update shells merged (original commands in order,
263
- # same && fail-fast semantics).
196
+ # 1007 R2: one shell persists both sections — they share the artifact precondition, so the two
197
+ # update shells merged (original commands in order, same && fail-fast semantics).
264
198
  - kind: shell
265
199
  options:
266
200
  command: >-
@@ -271,25 +205,7 @@ states:
271
205
  $spurBin feature update "$featureId" --section Scope --from-file .spur/run/$__runId-idea-scope.md
272
206
 
273
207
  - id: ac-generate
274
- display:
275
- phase: define
276
- phaseTitle: "Define feature"
277
- title: Generate AC
278
- description: >
279
- Generate acceptance criteria per ac-style-guide.md. Write AC scenarios to the
280
- feature file via `spur feature update --section "Acceptance Criteria"
281
- --from-file`. The agent authors R-numbered Gherkin scenarios tied to the
282
- design summary into a captured file; the shell step verifies the file is
283
- non-empty, writes it through the CLI, and writes a completion sentinel
284
- (.spur/run/${vars.__runId}-idea-ac-done.txt). AC quality is MEASURED once at this
285
- boundary by the deterministic `idea-ac-check` command.gate below (task 0769): a failing
286
- check records FAIL and routes through the capped retry loop via guards that consume the
287
- recorded result — it never fails the run (the engine's default onError policy is `fail`,
288
- so an in-action check failure would kill the run before the retry edges are evaluated).
289
- Requirement coverage is MEASURED by the same gate (1004 R2): the `idea-ac-check` args
290
- carry `--inventory .spur/run/${vars.__runId}-idea-eval-report.md`, so uncovered
291
- inventory items record FAIL on `idea-ac-check.status` and route through the same capped
292
- retry loop via the guards below.
208
+ description: "Author R-numbered BDD scenarios tied to the requirement inventory and persist them through `spur feature update --section \"Acceptance Criteria\" --from-file`. AC quality and requirement coverage are MEASURED once at this boundary by the `idea-ac-check` command.gate (0769; 1004 R2 folds coverage in via --inventory): a FAIL is recorded, never a run abort, so the capped retry edges can route it."
293
209
  onEnter:
294
210
  - kind: shell
295
211
  options:
@@ -298,20 +214,14 @@ states:
298
214
  options:
299
215
  agent: ${vars.planningAgent}
300
216
  input: "Generate acceptance criteria for feature ${vars.featureId}. The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Tie every scenario to the `## Requirement inventory` of .spur/run/${vars.__runId}-idea-eval-report.md: under each `Scenario:` heading add a comment `# covers: I1, I3` listing covered ids — every non-`[deferred: ...]` item needs at least one covering scenario. Two `spur task check` rules bind AC text: (1) AC bullets copy scenario titles verbatim — the title is the byte-identical identity key of its bullet; (2) gate-language words (HITL, approval/approved, merged/merge event, content-gate, GATED, capstone standalone) are forbidden in titles, bodies, and enum values (L4.gate-language) — rephrase around them. Output only the Acceptance Criteria section body (gherkin fence included), no commentary, no heading."
301
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
302
217
  role: planner
303
- # B7 R6 (0894): declared fresh policy (planner default).
304
218
  session: fresh
305
219
  answerFile: .spur/run/${vars.__runId}-idea-ac-content.md
306
220
  expectFile: .spur/run/${vars.__runId}-idea-ac-content.md
307
221
  timeoutMs: ${vars.stepTimeoutMs}
308
- # Empty capture is a RETRYABLE outcome, not a run abort. `answerFile` always
309
- # creates the file (so `expectFile` above cannot catch an empty answer), and the
310
- # old `test -s … && …` chain exited 1 on empty content — killing the run under
311
- # the default `fail` policy before the capped retry edges were ever evaluated,
312
- # contradicting this state's own contract. Empty now falls through with exit 0
313
- # so the recorded FAIL routes it back here (cap: 3). A failing CLI *write* still
314
- # exits non-zero and aborts — that one is not retryable.
222
+ # Empty capture is a RETRYABLE outcome, not a run abort: `answerFile` always creates the file,
223
+ # so an empty answer falls through with exit 0 and the recorded FAIL routes it back here (cap
224
+ # 3). A failing CLI *write* still exits non-zero and aborts — that one is not retryable.
315
225
  - kind: shell
316
226
  options:
317
227
  command: >-
@@ -320,9 +230,8 @@ states:
320
230
  else
321
231
  exit 0;
322
232
  fi
323
- # 0769: ONE measured check at the AC author/revise boundary. `softFail: true` keeps a
324
- # FAIL routable (retry loop / failed) instead of aborting; sibling guards consume the
325
- # recorded result from the run-scoped status file and never re-run the CLI.
233
+ # 0769: ONE measured check at the AC author/revise boundary. `softFail: true` keeps a FAIL
234
+ # routable (retry loop / failed) instead of aborting.
326
235
  - kind: command.gate
327
236
  options:
328
237
  id: idea-ac-check
@@ -331,14 +240,11 @@ states:
331
240
  resultFile: .spur/run/${vars.__runId}-idea-ac-check.status
332
241
  softFail: true
333
242
  timeoutMs: 120000
334
- # 0945 R2: derive the route facts ONCE at this boundary (0769 pattern) — guards read these
335
- # named files instead of re-deriving design×needs_design inline. The same command is
336
- # duplicated in feature-check's onEnter (YAML has no include); byte-equality is
337
- # test-pinned (idea-pipeline-routing.test.ts). Missing/corrupt needs-design JSON fails
338
- # safe to `design`; readiness is PASS only when the recorded check is PASS (1004 R2:
339
- # coverage folds into idea-ac-check via --inventory).
340
- # (warn) 7 commands: two three-way `test && printf || printf` derivations over captured
341
- # signal files (ADR-115); no model hop, no CLI re-run.
243
+ # 0945 R2: derive the route facts ONCE at this boundary (0769 pattern) so guards read these
244
+ # named files instead of re-deriving design×needs_design inline. Missing/corrupt needs-design
245
+ # JSON fails safe to `design`; readiness is PASS only when the recorded check is PASS (1004 R2:
246
+ # coverage folds into idea-ac-check via --inventory). (warn) 7 commands: two three-way
247
+ # `test && printf || printf` derivations over captured signal files (ADR-115); no model hop.
342
248
  - kind: shell
343
249
  options:
344
250
  command: >-
@@ -350,70 +256,8 @@ states:
350
256
  printf 'PASS\n' > .spur/run/$__runId-idea-ac-ready.status ||
351
257
  printf 'FAIL\n' > .spur/run/$__runId-idea-ac-ready.status; }
352
258
 
353
- - id: feature-check
354
- display:
355
- phase: define
356
- phaseTitle: "Define feature"
357
- title: Check feature
358
- description: >
359
- HITL gate over the recorded `idea-ac-check` result (measured at the ac-generate
360
- boundary — this gate never re-runs the CLI). Deliberately NOT --strict: before
361
- decompose/batch-create a feature has scenarios but zero linked tasks, which
362
- emits L4.orphan-scenarios at severity `warning`; --strict would elevate that
363
- to a failure, making the success edges unreachable and looping ac-generate
364
- into `failed`. Malformed AC still blocks via the error-severity L3 BDD
365
- checks. Strict coverage belongs at the shippable gate, not here. The recorded
366
- result is objective (schema/AC validation) and auto-routable — under profile=auto
367
- the transition guards route directly from ac-generate to the appropriate next
368
- state, so this state is only entered in interactive mode. On failure, the retry
369
- cap routes back to ac-generate (≤3 retries) or escalates to failed.
370
- Requirement coverage (1004 R2) folds into the recorded `idea-ac-check` result via the
371
- gate's `--inventory` flag: a FAIL there can mean some inventory items have no covering
372
- scenario — answer no to route back to ac-generate for revision if needed.
373
- onEnter:
374
- - kind: hitl.confirm
375
- options:
376
- prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? Recorded check status: $(cat .spur/run/${vars.__runId}-idea-ac-check.status 2>/dev/null || echo unknown) (.spur/run/${vars.__runId}-idea-ac-check.status — includes inventory-coverage findings, 1004 R2). (Failures route back to ac-generate for revision, capped at 3 retries.)"
377
- # Interactive always prompts the operator; profile=auto answers from the AC
378
- # authoring stage's recorded evidence (1236 R1/R2). No `pause: true` — an
379
- # evidence binding cannot live in a paused state (0911 D2), and the responder
380
- # is what asks the operator now.
381
- decision:
382
- mode: evidence
383
- statusVar: featureCheckStatus
384
- evidenceNodes:
385
- - ac-generate
386
- # A deferred/absent answer is the denial today's headless responder gave.
387
- - kind: shell
388
- options:
389
- command: >-
390
- mkdir -p .spur/run; a="$__hitlAnswer"; [ -n "$a" ] || a=no;
391
- printf '%s\n' "$a" > ".spur/run/$__runId-idea-feature-check-answer.txt"
392
- - kind: file.read.into-var
393
- options:
394
- path: .spur/run/${vars.__runId}-idea-feature-check-answer.txt
395
- var: __hitlAnswer
396
- # 1007 R2: the former post-confirm route-writer duplicate is deleted — ac-generate's
397
- # writer is the single writer (0945 R2 pin updated); its inputs (design var, needs-design
398
- # JSON, recorded ac-check status) cannot change across hitl.confirm.
399
-
400
259
  - id: system-design
401
- display:
402
- phase: design
403
- phaseTitle: Design
404
- title: System design
405
- description: >
406
- Dispatch sp:sys-architecture to produce the system design. This state runs
407
- only when needs_design=true (design=auto signal path). The agent creates ADR
408
- entries, architecture updates, and design satellites through constitution rules.
409
- A run-scoped design-review artifact (.spur/run/${vars.__runId}-idea-design-review.md)
410
- with fixed headings `## Proposed design`, `## Operator feedback`, and `## Reconciliation`
411
- carries operator rejection feedback back into this state: on the first pass the agent
412
- fills `## Proposed design`; on retry (operator feedback present) it revises the design,
413
- records the reconciliation, and updates invalidated Acceptance Criteria through
414
- `spur feature update` before design can exit. Design quality is then MEASURED once at
415
- this boundary by the deterministic `idea-design-check` command.gate (task 0769). The
416
- design-approval state follows (taste gate).
260
+ description: "Dispatch sp:sys-architecture (runs only on the needs_design signal) to produce ADR entries, architecture updates and design satellites. The run-scoped design-review artifact (.spur/run/${vars.__runId}-idea-design-review.md) with fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation` carries operator rejection feedback into a revision pass, which reconciles invalidated AC through `spur feature update`. Design quality is MEASURED once at this boundary by the `idea-design-check` command.gate (0769)."
417
261
  onEnter:
418
262
  - kind: shell
419
263
  options:
@@ -425,23 +269,18 @@ states:
425
269
  options:
426
270
  agent: ${vars.planningAgent}
427
271
  input: 'The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md (idea persisted verbatim at start; authoritative). Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) per the constitution edit rules; never write task or feature corpus files directly. Design-review contract (fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): first pass — write the proposed summary under `## Proposed design`, leave `## Operator feedback` empty; retry after operator feedback — revise the design/ADR artifacts, document changes under `## Reconciliation`, and when feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file, persist via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>`.'
428
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
429
272
  role: planner
430
- # B7 R6 (0894): declared fresh policy (planner default).
431
273
  session: fresh
432
274
  expectFile: .spur/run/${vars.__runId}-idea-design-review.md
433
275
  timeoutMs: ${vars.stepTimeoutMs}
434
- # expectFile proves existence only, and the onEnter skeleton pre-creates the file — so an
435
- # agent no-op would still pass it (0515 P3-2). Fail closed: the `## Proposed design` section
436
- # must carry non-whitespace content before design can exit to approval/decompose.
276
+ # expectFile proves existence only and the skeleton above pre-creates the file, so an agent
277
+ # no-op would pass it (0515 P3-2). Fail closed on the section's content.
437
278
  - kind: shell
438
279
  options:
439
280
  command: >-
440
281
  REVIEW=".spur/run/$__runId-idea-design-review.md" &&
441
282
  awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' "$REVIEW" | grep -q '[^[:space:]]'
442
- # 0769: ONE measured check at the design author/revise boundary — after this pass's
443
- # design writes (including AC reconciliation). softFail keeps FAIL routable; sibling
444
- # guards consume the recorded result and never re-run the CLI.
283
+ # 0769: ONE measured check at the design author/revise boundary — after this pass's writes.
445
284
  - kind: command.gate
446
285
  options:
447
286
  id: idea-design-check
@@ -452,25 +291,7 @@ states:
452
291
  timeoutMs: 120000
453
292
 
454
293
  - id: design-approval
455
- display:
456
- phase: design
457
- phaseTitle: Design
458
- title: Approve design
459
- description: >
460
- HITL taste gate. The operator reviews the system design and approves or
461
- rejects it. Routed around only when profile=auto AND vars.design_approved=true
462
- (CLI --auto) AND the recorded design check PASSes; a FAIL still pauses here.
463
-
464
- R7 (0433): a headless `no` persisted by DefaultHitlResponder would loop
465
- design-approval -> system-design -> design-approval, burning unbounded
466
- agent passes. The onEnter shell increments a reject counter; the `no`
467
- edge to system-design is capped at 1 revise, after which the run
468
- terminates as `failed` naming this gate.
469
-
470
- Rejection must carry operator feedback: before answering `no`, the operator
471
- records the concrete issue(s) in the run-scoped design-review artifact
472
- (.spur/run/${vars.__runId}-idea-design-review.md) under `## Operator feedback` —
473
- the revision pass reads it to reconcile invalidated AC before re-approval.
294
+ description: "HITL taste gate. Routed around only when profile=auto AND vars.design_approved=true AND the recorded design check PASSes; a FAIL still pauses here. R7 (0433): the `no` edge back to system-design is capped at 1 revise (the onEnter counter), after which the run ends failed naming this gate. Rejection must carry operator feedback in the review artifact under `## Operator feedback`. D66 1250 R2: an approved design whose recorded check FAILS routes to ac-generate (the AC revision state) — the former feature-check hop is deleted."
474
295
  onEnter:
475
296
  - kind: shell
476
297
  options:
@@ -478,8 +299,6 @@ states:
478
299
  - kind: hitl.confirm
479
300
  options:
480
301
  prompt: "Review the system design for feature ${vars.featureId}. Approve to proceed to decomposition? To reject, first record your feedback in .spur/run/${vars.__runId}-idea-design-review.md under the `## Operator feedback` heading, then answer no."
481
- # Interactive always prompts the operator; profile=auto answers from the
482
- # system-design stage's recorded evidence (1236 R1/R2).
483
302
  decision:
484
303
  mode: evidence
485
304
  statusVar: designApprovalStatus
@@ -497,18 +316,7 @@ states:
497
316
  var: __hitlAnswer
498
317
 
499
318
  - id: decompose
500
- display:
501
- phase: decompose
502
- phaseTitle: Decompose
503
- title: Decompose into tasks
504
- description: >
505
- Dispatch sp:spec-decomposition with the brainstorm artifact, feature AC, and
506
- design doc as input. The agent produces a task-batch JSON file at
507
- .spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json,
508
- plus the private task-order sidecar .spur/run/${vars.__runId}-idea-task-order.json
509
- (a JSON array of { name, depends_on_names[] }; `[]` when no ordering exists).
510
- A post-agent shell action validates the sidecar shape before decomposition
511
- can exit — missing or ambiguous title-to-WBS resolution fails before handoff.
319
+ description: "Dispatch sp:spec-decomposition with the brainstorm artifact, feature AC and design doc. The agent produces the task-batch JSON (.spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json) plus the private task-order sidecar (.spur/run/${vars.__runId}-idea-task-order.json — an array of { name, depends_on_names[] }; `[]` when no ordering exists). The post-agent shell validates the sidecar shape fail-closed."
512
320
  onEnter:
513
321
  - kind: shell
514
322
  options:
@@ -517,19 +325,14 @@ states:
517
325
  options:
518
326
  agent: ${vars.planningAgent}
519
327
  input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea argument persisted verbatim at start; authoritative). Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
520
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
521
328
  role: planner
522
- # B7 R6 (0894): declared fresh policy (planner default).
523
329
  session: fresh
524
330
  expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
525
331
  timeoutMs: ${vars.stepTimeoutMs}
526
- # R1 (0518): the task-order sidecar is the ordering contract for handoff-finalize.
527
- # Validate it fails closed: sidecar must be an array, batch names unique, sidecar names
528
- # unique, every sidecar name/dependency must refer to exactly one batch name, and — the
529
- # converse (F2, 0518 verify) — every batch name must appear in the sidecar, so a partial
530
- # sidecar can never silently skip `task deps` for an unlisted item (`[]` is valid).
531
- # (warn) stays inline by design: one jq predicate over two run-scoped files; splitting
532
- # it into a script would hide the exact fail-closed contract this guard pins (0824).
332
+ # R1 (0518): the sidecar is the ordering contract for handoff-finalize. Fail closed on the
333
+ # array shape, unique names on both sides, and BIDIRECTIONAL coverage (F2, 0518 verify) — a
334
+ # partial sidecar must never silently skip `task deps` for an unlisted item (`[]` is valid).
335
+ # (warn) stays inline by design: one jq predicate over two run-scoped files (0824).
533
336
  - kind: shell
534
337
  options:
535
338
  command: >-
@@ -544,58 +347,10 @@ states:
544
347
  (((map(.depends_on_names // []) | flatten | unique) - ($b[0] | map(.name))) | length == 0)
545
348
  ' "$ORDER" >/dev/null
546
349
 
547
- - id: batch-create
548
- display:
549
- phase: decompose
550
- phaseTitle: Decompose
551
- title: Approve task batch
552
- description: >
553
- HITL gate: runs spur task batch-create with the batch JSON from the
554
- decompose state. The check is objective (schema validation) and
555
- auto-routable — under profile=auto the transition guards route directly
556
- from decompose to the appropriate next state, so this state is only
557
- entered in interactive mode. On failure, the retry cap routes back to
558
- decompose (≤3 retries) or escalates to failed.
559
- onEnter:
560
- - kind: hitl.confirm
561
- options:
562
- prompt: "Batch-create for feature ${vars.featureId}. Review the task batch and confirm to proceed? (Failures route back to decompose for revision, capped at 3 retries.)"
563
- # Interactive always prompts the operator; profile=auto answers from the
564
- # decompose stage's recorded evidence (1236 R1/R2).
565
- decision:
566
- mode: evidence
567
- statusVar: batchCreateStatus
568
- evidenceNodes:
569
- - decompose
570
- # A deferred/absent answer is the denial today's headless responder gave.
571
- - kind: shell
572
- options:
573
- command: >-
574
- mkdir -p .spur/run; a="$__hitlAnswer"; [ -n "$a" ] || a=no;
575
- printf '%s\n' "$a" > ".spur/run/$__runId-idea-batch-create-answer.txt"
576
- - kind: file.read.into-var
577
- options:
578
- path: .spur/run/${vars.__runId}-idea-batch-create-answer.txt
579
- var: __hitlAnswer
580
-
581
350
  - id: batch-create-run
582
- display:
583
- phase: decompose
584
- phaseTitle: Decompose
585
- title: Create tasks
586
- description: >
587
- Executes task batch creation exactly once per decomposition attempt. The
588
- side effect lives in onEnter and records a sentinel; transition guards only
589
- inspect sentinel/retry state. The `--json` result is captured atomically in
590
- .spur/run/${vars.__runId}-idea-batch-create-result.json (temp file + mv); the done
591
- sentinel is written only after the JSON parses and `created == (.wbs | length)`,
592
- so handoff-finalize can zip batch names to the returned WBS list. Any failure
593
- (CLI error or malformed result) writes the failed sentinel, preserving the
594
- existing retry behavior.
351
+ description: "Executes task batch creation exactly once per decomposition attempt. The side effect records a sentinel; guards only inspect sentinel/retry state. The `--json` result is captured atomically (.spur/run/${vars.__runId}-idea-batch-create-result.json, temp file + mv) and the done sentinel is written only after the JSON parses and `created == (.wbs | length)`, so handoff-finalize can zip batch names to the returned WBS list. Any failure writes the failed sentinel, preserving the retry behavior. D66 1250 R2: every profile reaches this state directly from decompose — the former `batch-create` approval state is deleted."
595
352
  onEnter:
596
- # (e) idempotent batch creation: sentinel + temp/mv atomicity stay inline (0824);
597
- # the jq verdict check keeps the done sentinel truthful so handoff-finalize can
598
- # zip batch names to the returned WBS list.
353
+ # (e) idempotent batch creation: sentinel + temp/mv atomicity stay inline (0824).
599
354
  - kind: shell
600
355
  options:
601
356
  command: >-
@@ -612,34 +367,17 @@ states:
612
367
  fi
613
368
 
614
369
  - id: ready-prepare
615
- display:
616
- phase: handoff
617
- phaseTitle: "Hand off"
618
- title: Prepare ready set
619
- description: >
620
- Ready-by-default preparation (0788): the planning owner applies the
621
- ready-refinement checklist to each created task (requirements, design,
622
- plan, ac, decisions, dependencies, premises) so the deterministic task
623
- check passes, then writes the run-scoped ready evidence sidecar
624
- (.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs,
625
- status (ready|failed|skipped), planningDigest, and seven check rows,
626
- with the premises row carrying tree-verified path:line citations
627
- (0947). Handoff-finalize verifies presence, digest, feature binding and
628
- checklist evidence before recommending auto runall; missing or failing
629
- evidence never fails the run — it degrades the recommendation to
630
- ready-depth refineall.
370
+ description: "Ready-by-default preparation (0788): the planning owner applies the ready-refinement checklist to each created task (requirements, design, plan, ac, decisions, dependencies, premises) so the deterministic task check passes, then writes the run-scoped ready evidence sidecar (.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs, status (ready|failed|skipped), planningDigest, and seven check rows, the premises row carrying tree-verified path:line citations (0947). Missing or failing evidence never fails the run — it degrades the handoff recommendation to ready-depth refineall."
631
371
  onEnter:
632
372
  - kind: agent.run
633
373
  options:
634
374
  agent: ${vars.planningAgent}
635
- # Declared Layer-1 role (0538 R2), same planner executor as decompose.
636
375
  role: planner
637
- # B7 R6 (0894): declared fresh policy (planner default).
638
376
  session: fresh
639
377
  timeoutMs: ${vars.stepTimeoutMs}
640
- # (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the
641
- # project's own computePlanningDigest), so the bounded prompt pins the skill
642
- # reference instead of a command; artifacts are gated by answerFile/expectFile.
378
+ # (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the project's
379
+ # own computePlanningDigest), so the prompt pins the skill reference; artifacts are gated
380
+ # by answerFile/expectFile.
643
381
  input: >-
644
382
  The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea
645
383
  argument persisted verbatim at start; authoritative). Run the ready-prepare stage
@@ -651,12 +389,10 @@ states:
651
389
  cites at least one verified path:line (handoff-finalize lints it; task 0947).
652
390
  answerFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
653
391
  expectFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
654
- # Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
655
- # normalized to an empty sidecar: finalize and the seeded fallback then degrade
656
- # the recommendation to refineall instead of failing the run. A PRESENT but
657
- # malformed sidecar fails the run here — it must not masquerade as evidence.
658
- # (warn) stays inline by design: single jq shape predicate over the run-scoped
659
- # sidecar; the failed/skipped rows are what degrade the recommendation (0824).
392
+ # Fail-closed shape validation (mirrors the order-sidecar guard). Absence is normalized to an
393
+ # empty sidecar so finalize degrades the recommendation to refineall instead of failing the
394
+ # run; a PRESENT but malformed sidecar fails here — it must not masquerade as evidence.
395
+ # (warn) stays inline by design: single jq shape predicate over the run-scoped sidecar (0824).
660
396
  - kind: shell
661
397
  options:
662
398
  command: >-
@@ -665,387 +401,69 @@ states:
665
401
  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
666
402
 
667
403
  - id: handoff-finalize
668
- display:
669
- phase: handoff
670
- phaseTitle: "Hand off"
671
- title: Finalize handoff
672
- description: >
673
- Post-create finalization (0518): zip batch item names to the created WBS
674
- values from the captured batch-create result (equal-length/unique-name
675
- checks), apply every non-empty depends_on_names list through
676
- `spur task deps`, refresh the feature roster, check every created task, and
677
- write the run-scoped handoff report
678
- (.spur/run/${vars.__runId}-idea-handoff.md) with exactly one next command.
679
- A mapping or CLI error fails the run before terminal handoff; an unready
680
- task is a successful planning outcome recorded as a refineall recommendation.
404
+ description: "Post-create finalization (0518): zip batch item names to the created WBS values (equal-length / unique-name checks), apply every non-empty depends_on_names list through `spur task deps`, refresh the feature roster, check every created task, and write the run-scoped handoff report (.spur/run/${vars.__runId}-idea-handoff.md) with exactly one next command. A mapping or CLI error fails the run before terminal handoff; an unready task is a successful planning outcome recorded as a refineall recommendation."
681
405
  onEnter:
682
- # (d) `spur feature handoff` owns finalization (0824; script-absorption PR-5
683
- # retired the idea-handoff plugin script and its bundle). A nonzero exit fails
684
- # the run (fail closed) instead of silently skipping finalization. The
685
- # zip/deps/refresh/check/report contract itself is pinned by
406
+ # (d) `spur feature handoff` owns finalization (0824; script-absorption PR-5 retired the
407
+ # idea-handoff plugin script). A nonzero exit fails the run (fail closed) instead of silently
408
+ # skipping finalization. The zip/deps/refresh/check/report contract is pinned by
686
409
  # finalizeIdeaHandoff unit tests, not by this file.
687
410
  - kind: shell
688
411
  options:
689
412
  command: '$spurBin feature handoff "$featureId" --run-id "$__runId" --no-logo'
413
+
690
414
  - id: handoff
691
- description: >
692
- Terminal — idea pipeline complete. Ordering applied, roster refreshed, and
693
- the handoff report written at .spur/run/${vars.__runId}-idea-handoff.md:
694
- feature id, task WBS list, per-task readiness, and exactly one recommended
695
- next command (ready-depth refineall when any task is unready, else auto
696
- runall). No task execution — the pipeline stops here.
415
+ description: "Terminal — idea pipeline complete. Ordering applied, roster refreshed, and the handoff report written at .spur/run/${vars.__runId}-idea-handoff.md: feature id, task WBS list, per-task readiness, and exactly one recommended next command (ready-depth refineall when any task is unready, else auto runall). No task execution — the pipeline stops here."
697
416
  onEnter:
698
- # 1007 R2: the handoff note is deleted — the run.artifact below carries the handoff
699
- # (same path, the recommended-next-command report).
417
+ # 1007 R2: the run.artifact carries the handoff (the recommended-next-command report).
700
418
  - kind: run.artifact
701
419
  options:
702
420
  path: .spur/run/${vars.__runId}-idea-handoff.md
703
421
  artifactKind: idea-handoff
704
422
 
705
423
  - id: cancelled
706
- description: Terminal — pipeline cancelled by operator or error.
424
+ description: "Terminal — pipeline cancelled by operator or error."
707
425
 
708
426
  - id: failed
709
- description: >
710
- Terminal — pipeline failed after exhausting retry caps on cyclic edges
711
- (feature-check → ac-generate or batch-create → decompose). The error
712
- message in the run trace identifies which edge exceeded the cap.
427
+ description: "Terminal — the entry check failed, or a capped retry loop exhausted (ac-generate → ac-generate or batch-create-run → decompose). The recorded status file's first line names the reason; the terminal reason identifies the exhausted edge."
713
428
 
429
+ # ── Routing table ──
430
+ # Declaration order is load-bearing: the engine takes the first passing guard. start's PASS edge
431
+ # precedes its reporting failure edge; discovery/idea-eval's auto-accept edges precede their gates
432
+ # (0366 R4/R5) and every gate's discriminating edge precedes its always fallback. ac-generate routes
433
+ # on the derived route facts under EVERY profile (1250 R2) — no guard re-runs the CLI (0769).
714
434
  transitions:
715
- # ── start -> discovery ──
716
- - from: start
717
- to: discovery
718
- description: Agent doctor PASS — begin discovery.
719
- guard:
720
- kind: shell
721
- options:
722
- command: 'test "$(cat .spur/run/$__runId-idea-precheck-doctor.status 2>/dev/null)" = PASS'
723
- - from: start
724
- to: failed
725
- terminalReason: failed-check
726
- description: Agent doctor FAIL — stop before discovery.
727
- guard:
728
- kind: always
729
-
730
- # ── discovery: auto-accept the eval recommendation (profile=auto + idea_approved) OR enter idea-eval ──
731
- # Declaration order: auto edges FIRST (same pattern as system-design → design-approval).
732
- # Without them, discovery always enters the idea-eval gate, rendering the prompt and
733
- # persisting an avoidable operator question before the gate's own auto answer can fire (0366 R4/R5).
734
- # --auto accepts the recommendation derived at the end of discovery (-idea-recommendation.txt):
735
- # drop → cancelled, proceed/reshape → feature-create; unknown/missing falls through to the
736
- # idea-eval gate, whose evidence binding then answers (fail-safe: never auto-create a feature
737
- # without an explicit proceed).
738
- - from: discovery
739
- to: cancelled
740
- terminalReason: cancelled
741
- description: profile=auto AND idea_approved=true AND eval recommends drop — accept it, no feature.
742
- guard:
743
- kind: shell
744
- options:
745
- command: >-
746
- test "$profile" = auto && test "$idea_approved" = true &&
747
- grep -qx drop .spur/run/$__runId-idea-recommendation.txt
748
- - from: discovery
749
- to: feature-create
750
- description: profile=auto AND idea_approved=true AND eval recommends proceed/reshape — skip idea-eval taste gate.
751
- guard:
752
- kind: shell
753
- options:
754
- command: >-
755
- test "$profile" = auto && test "$idea_approved" = true &&
756
- grep -qxE 'proceed|reshape' .spur/run/$__runId-idea-recommendation.txt
757
- - from: discovery
758
- to: idea-eval
759
- description: Discovery complete — gate on idea evaluation taste decision.
760
- guard:
761
- kind: always
762
-
763
- # ── idea-eval: auto-skip (profile=auto + idea_approved) OR approve/reject HITL ──
764
- # Declaration order: auto-skip FIRST (same pattern as system-design → design-approval).
765
- - from: idea-eval
766
- to: feature-create
767
- description: profile=auto AND idea_approved=true AND eval recommends proceed/reshape — skip idea-eval taste gate.
768
- guard:
769
- kind: shell
770
- options:
771
- command: >-
772
- test "$profile" = auto && test "$idea_approved" = true &&
773
- grep -qxE 'proceed|reshape' .spur/run/$__runId-idea-recommendation.txt
774
- - from: idea-eval
775
- to: feature-create
776
- description: Idea evaluation approved — create or select a feature.
777
- guard:
778
- kind: shell
779
- options:
780
- command: 'test "$__hitlAnswer" = yes'
781
- - from: idea-eval
782
- to: cancelled
783
- terminalReason: cancelled
784
- description: Idea evaluation rejected or cancelled — no feature created.
785
- guard:
786
- kind: shell
787
- options:
788
- command: 'test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'
789
-
790
- # ── feature-create -> ac-generate ──
791
- - from: feature-create
792
- to: ac-generate
793
- description: Feature created — generate acceptance criteria.
794
- guard:
795
- kind: always
796
-
797
- # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
798
- # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
799
- # and design-gen→handoff). Under profile=auto, the derived route facts — the ready file (both
800
- # recorded checks PASS, 0887 R4) and the design route file (0945 R2 writer at the end of both
801
- # ac-generate and feature-check onEnter) — route directly to the appropriate next state:
802
- # guards never re-run the CLI or the checker and never re-derive the signals (task 0769).
803
- # Under interactive, the
804
- # always fallback enters the feature-check state whose onEnter hitl.confirm pauses for
805
- # operator confirmation (coverage is surfaced in that prompt; the operator's answer governs).
806
- #
807
- # Auto-skip 1: pass + coverage + design route → system-design (design=auto + needs_design != false)
808
- - from: ac-generate
809
- to: system-design
810
- description: "profile=auto, check passed, requirements covered, design route — run system design."
811
- # 0945 R3: three predicates over derived facts — the ready file folds both recorded check
812
- # statuses (PASS only when ac-check AND coverage are PASS), the route file folds
813
- # design×needs_design (fail-safe design); nothing re-runs, nothing re-derives (0769).
814
- guard:
815
- kind: shell
816
- options:
817
- command: >-
818
- test "$profile" = auto &&
819
- test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
820
- test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
821
- # Auto-skip 2: pass + coverage + skip-design route → decompose
822
- - from: ac-generate
823
- to: decompose
824
- description: "profile=auto, check passed, requirements covered, skip-design route — go directly to decompose."
825
- # 0945 R3: mirror of the design-route guard over the same derived facts with route=skip;
826
- # one captured signal file per fact keeps both routes symmetric (0769).
827
- guard:
828
- kind: shell
829
- options:
830
- command: >-
831
- test "$profile" = auto &&
832
- test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
833
- test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
834
- # Auto-skip 3: check failed (incl. inventory coverage), retry < 3 → loop back to ac-generate (self-loop)
835
- - from: ac-generate
836
- to: ac-generate
837
- description: "profile=auto, check failed (incl. inventory coverage), retry cap not reached — re-run ac-generate."
838
- # (warn) 4 commands: one persistent multi-assignment line captures the check status and the retry
839
- # count (with its 0 fallback); the profile gate folds into the retry test via `test -a`; the retry
840
- # loop is the smallest honest formulation of cap<3 routing (0769).
841
- guard:
842
- kind: shell
843
- options:
844
- command: >-
845
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
846
- test "$ac_status" != PASS && test "$profile" = auto -a "$retry" -lt 3
847
- # Auto-skip 4: check failed (incl. inventory coverage), retry cap reached → escalate to failed
848
- - from: ac-generate
849
- to: failed
850
- terminalReason: retry-exhausted
851
- description: "profile=auto, check failed (incl. inventory coverage) after 3 retries — escalate to failed."
852
- # (warn) 4 commands: mirror of the retry guard with cap>=3; keeping escalation and retry as one
853
- # test-chain pair makes the cap boundary auditable in the diff (0769).
854
- guard:
855
- kind: shell
856
- options:
857
- command: >-
858
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
859
- test "$ac_status" != PASS && test "$profile" = auto -a "$retry" -ge 3
860
- # Interactive fallback: enter feature-check HITL gate
861
- - from: ac-generate
862
- to: feature-check
863
- description: Interactive — enter feature-check HITL gate for operator confirmation.
864
- guard:
865
- kind: always
866
-
867
- # ── feature-check: pass + design route -> system-design, pass + skip route -> decompose, fail -> ac-generate ──
868
- # Declaration order matters: system-design route tried first, then skip route, then fail.
869
- - from: feature-check
870
- to: system-design
871
- description: Feature check passed, design route — run system design.
872
- # 0945 R3: three predicates over derived facts — the recorded ac-check status stays the
873
- # conjunct here (parity: the operator's answer governs the interactive gate, 0887), the
874
- # route file folds design×needs_design (fail-safe design); nothing re-runs (0769).
875
- guard:
876
- kind: shell
877
- options:
878
- command: >-
879
- test "$__hitlAnswer" = yes &&
880
- test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
881
- test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
882
- - from: feature-check
883
- to: decompose
884
- description: Feature check passed, skip-design route — go directly to decompose.
885
- # 0945 R3: mirror of the design-route guard over the same derived facts with route=skip.
886
- guard:
887
- kind: shell
888
- options:
889
- command: >-
890
- test "$__hitlAnswer" = yes &&
891
- test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
892
- test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
893
- - from: feature-check
894
- to: ac-generate
895
- description: "Feature check failed — revise AC (retry cap: 3)."
896
- # (warn) 5 commands (named status capture + 4 conditions): HITL reject/failed-status OR-pair + captured retry count; the
897
- # interactive retry mirror of the ac-generate cap<3 guard (0769).
898
- guard:
899
- kind: shell
900
- options:
901
- command: >-
902
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
903
- (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
904
- - from: feature-check
905
- to: failed
906
- terminalReason: retry-exhausted
907
- description: Feature check failed after 3 retries — escalate to failed.
908
- # (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
909
- guard:
910
- kind: shell
911
- options:
912
- command: >-
913
- ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
914
- (test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
915
- - from: feature-check
916
- to: cancelled
917
- terminalReason: cancelled
918
- description: Operator cancelled the feature-check gate.
919
- guard:
920
- kind: shell
921
- options:
922
- command: 'test "$__hitlAnswer" = cancel'
923
-
924
- # ── system-design: -> design-approval (normal) OR -> decompose (auto + prior approval) ──
925
- # Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
926
- - from: system-design
927
- to: decompose
928
- description: profile=auto AND design_approved=true AND recorded design check passes — skip design approval gate.
929
- guard:
930
- kind: shell
931
- options:
932
- command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
933
- - from: system-design
934
- to: design-approval
935
- description: System design done — gate on design approval.
936
- guard:
937
- kind: always
938
-
939
- # ── design-approval -> decompose (after operator approval), AC-failure -> feature-check ──
940
- - from: design-approval
941
- to: decompose
942
- description: Design approved and recorded design check passes — proceed to decomposition.
943
- guard:
944
- kind: shell
945
- options:
946
- command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
947
- - from: design-approval
948
- to: feature-check
949
- description: "Design approved but recorded design check fails — route back through the AC gate (revise AC, retry cap: 3)."
950
- guard:
951
- kind: shell
952
- options:
953
- command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'
954
- - from: design-approval
955
- to: system-design
956
- description: "Design rejected - revise system design (cap: 1 revise)."
957
- guard:
958
- kind: shell
959
- options:
960
- command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -le 1'
961
- - from: design-approval
962
- to: failed
963
- terminalReason: retry-exhausted
964
- description: "Design rejected after 1 revise - design-approval gate exhausted (R7 0433)."
965
- guard:
966
- kind: shell
967
- options:
968
- command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -gt 1'
969
- - from: design-approval
970
- to: cancelled
971
- terminalReason: cancelled
972
- description: Operator cancelled design approval.
973
- guard:
974
- kind: shell
975
- options:
976
- command: 'test "$__hitlAnswer" = cancel'
977
-
978
- # ── decompose: auto-skip (profile=auto) OR enter batch-create HITL gate (interactive) ──
979
- - from: decompose
980
- to: batch-create-run
981
- description: "profile=auto — execute batch-create side effect."
982
- guard:
983
- kind: shell
984
- options:
985
- command: 'test "$profile" = auto'
986
- # Interactive fallback: enter batch-create HITL gate
987
- - from: decompose
988
- to: batch-create
989
- description: Interactive — enter batch-create HITL gate for operator confirmation.
990
- guard:
991
- kind: always
992
-
993
- - from: batch-create
994
- to: batch-create-run
995
- description: Operator approved batch creation — execute side effect.
996
- guard:
997
- kind: shell
998
- options:
999
- command: 'test "$__hitlAnswer" = yes'
1000
- - from: batch-create
1001
- to: decompose
1002
- description: Operator requested decomposition revisions.
1003
- guard:
1004
- kind: shell
1005
- options:
1006
- command: 'test "$__hitlAnswer" = no'
1007
- - from: batch-create
1008
- to: cancelled
1009
- terminalReason: cancelled
1010
- description: Operator cancelled batch creation.
1011
- guard:
1012
- kind: shell
1013
- options:
1014
- command: 'test "$__hitlAnswer" = cancel'
1015
-
1016
- # ── batch-create-run: success -> ready-prepare, failure -> decompose (retry cap: 3), failure+cap -> failed ──
1017
- - from: batch-create-run
1018
- to: ready-prepare
1019
- description: Batch created and result captured — apply ready-by-default preparation, then finalize the handoff.
1020
- guard:
1021
- kind: shell
1022
- options:
1023
- command: "test -f .spur/run/$__runId-idea-batch-create.done"
1024
- # ── ready-prepare: -> handoff-finalize (always; evidence absence degrades to refineall, never fails the run) ──
1025
- - from: ready-prepare
1026
- to: handoff-finalize
1027
- description: Ready evidence sidecar written (or normalized empty) — finalize the handoff report.
1028
- guard:
1029
- kind: always
1030
- - from: batch-create-run
1031
- to: decompose
1032
- description: "Batch-create failed — revise decomposition (retry cap: 3)."
1033
- guard:
1034
- kind: shell
1035
- options:
1036
- command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'
1037
- - from: batch-create-run
1038
- to: failed
1039
- terminalReason: retry-exhausted
1040
- description: Batch-create failed after 3 retries — escalate to failed.
1041
- guard:
1042
- kind: shell
1043
- options:
1044
- command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'
1045
-
1046
- # ── handoff-finalize: -> handoff (terminal) ──
1047
- - from: handoff-finalize
1048
- to: handoff
1049
- description: Ordering applied, roster refreshed, handoff report written — terminal handoff.
1050
- guard:
1051
- kind: always
435
+ - {from: start, to: discovery, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-precheck-doctor.status 2>/dev/null)" = PASS'}}}
436
+ # The entry check's named reason (`FAIL: idea text is empty`, or the executor verdict) is the
437
+ # operator's one-place explanation for a run that never left start. `>&2` precedes the stderr
438
+ # suppression: redirections apply left to right, so the reverse order would send the line to
439
+ # /dev/null with it.
440
+ - {from: start, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'head -1 .spur/run/$__runId-idea-precheck-doctor.status >&2 2>/dev/null; exit 0'}}}
441
+ - {from: discovery, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qx drop .spur/run/$__runId-idea-recommendation.txt'}}}
442
+ - {from: discovery, to: feature-create, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qxE "proceed|reshape" .spur/run/$__runId-idea-recommendation.txt'}}}
443
+ - {from: discovery, to: idea-eval, guard: {kind: always}}
444
+ - {from: idea-eval, to: feature-create, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qxE "proceed|reshape" .spur/run/$__runId-idea-recommendation.txt'}}}
445
+ - {from: idea-eval, to: feature-create, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes'}}}
446
+ - {from: idea-eval, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'}}}
447
+ - {from: feature-create, to: ac-generate, guard: {kind: always}}
448
+ - {from: ac-generate, to: system-design, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design'}}}
449
+ - {from: ac-generate, to: decompose, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip'}}}
450
+ # (warn) 4 commands each: one persistent multi-assignment line captures the check status and the
451
+ # retry count (with its 0 fallback); retry and escalation stay one test-chain pair so the cap
452
+ # boundary is auditable in the diff (0769). The former `test "$profile" = auto` conjunct is gone.
453
+ - {from: ac-generate, to: ac-generate, guard: {kind: shell, options: {command: 'ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)"; test "$ac_status" != PASS && test "$retry" -lt 3'}}}
454
+ - {from: ac-generate, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)"; test "$ac_status" != PASS && test "$retry" -ge 3'}}}
455
+ - {from: system-design, to: decompose, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'}}}
456
+ - {from: system-design, to: design-approval, guard: {kind: always}}
457
+ - {from: design-approval, to: decompose, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'}}}
458
+ - {from: design-approval, to: ac-generate, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'}}}
459
+ - {from: design-approval, to: system-design, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -le 1'}}}
460
+ - {from: design-approval, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -gt 1'}}}
461
+ - {from: design-approval, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = cancel'}}}
462
+ # D66 1250 R2: the sidecar guard above validates the batch deterministically, so the deleted
463
+ # `batch-create` approval state had no decision left to settle — every profile takes this edge.
464
+ - {from: decompose, to: batch-create-run, guard: {kind: always}}
465
+ - {from: batch-create-run, to: ready-prepare, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.done'}}}
466
+ - {from: batch-create-run, to: decompose, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'}}}
467
+ - {from: batch-create-run, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'}}}
468
+ - {from: ready-prepare, to: handoff-finalize, guard: {kind: always}}
469
+ - {from: handoff-finalize, to: handoff, guard: {kind: always}}