@gobing-ai/spur 0.2.5 → 0.2.9

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 (42) hide show
  1. package/package.json +8 -11
  2. package/schemas/section-matrix.schema.json +68 -0
  3. package/schemas/spur-config.schema.json +71 -1
  4. package/schemas/state-machine-workflow.schema.json +4 -0
  5. package/schemas/task-batch.schema.json +53 -0
  6. package/spur-cli/config/config.example.yaml +25 -1
  7. package/spur-cli/config/rules/boundary/config-loading-ownership.yaml +54 -0
  8. package/spur-cli/config/rules/boundary/planning-folder-hardcode.yaml +46 -0
  9. package/spur-cli/config/rules/recommended-pre-check.yaml +3 -2
  10. package/spur-cli/config/rules/structure/test-location.yaml +10 -0
  11. package/spur-cli/config/rules/typescript/happy-dom-teardown.yaml +43 -0
  12. package/spur-cli/config/rules/typescript/no-leaky-module-mocks.yaml +71 -0
  13. package/spur-cli/config/rules/ui/ui-import-boundary.yaml +51 -0
  14. package/spur-cli/config/tasks/section-matrix.yaml +190 -0
  15. package/spur-cli/config/tasks/templates/brainstorm.md +29 -0
  16. package/spur-cli/config/tasks/templates/feature-impl.md +50 -0
  17. package/spur-cli/config/tasks/templates/issue.md +33 -0
  18. package/spur-cli/config/tasks/templates/meta.md +31 -0
  19. package/spur-cli/config/tasks/templates/review.md +53 -0
  20. package/spur-cli/config/tasks/templates/standard.md +48 -0
  21. package/spur-cli/config/templates/bdd/checklist.md +1 -0
  22. package/spur-cli/config/templates/bdd/gherkin.md +8 -0
  23. package/spur-cli/config/templates/docs/00_ADR.md +27 -0
  24. package/spur-cli/config/templates/docs/01_PRD.md +37 -0
  25. package/spur-cli/config/templates/docs/02_ROADMAP.md +22 -0
  26. package/spur-cli/config/templates/docs/03_ARCHITECTURE.md +27 -0
  27. package/spur-cli/config/templates/docs/04_DESIGN.md +30 -0
  28. package/spur-cli/config/templates/docs/05_FEATURES.md +23 -0
  29. package/spur-cli/config/templates/docs/99_PROJECT_CONSTITUTION.md +65 -0
  30. package/spur-cli/config/templates/feature/default.md +41 -0
  31. package/spur-cli/config/templates/task/brainstorm.md +29 -0
  32. package/spur-cli/config/templates/task/feature-impl.md +50 -0
  33. package/spur-cli/config/templates/task/issue.md +33 -0
  34. package/spur-cli/config/templates/task/meta.md +31 -0
  35. package/spur-cli/config/templates/task/review.md +52 -0
  36. package/spur-cli/config/templates/task/standard.md +48 -0
  37. package/spur-cli/config/workflows/feature-dev.yaml +92 -80
  38. package/spur-cli/config/workflows/feature-lifecycle.yaml +100 -0
  39. package/spur-cli/config/workflows/planning-pipeline.yaml +166 -0
  40. package/spur-cli/config/workflows/task-lifecycle.yaml +128 -0
  41. package/spur-cli/config/workflows/task-pipeline.yaml +244 -0
  42. package/spur.js +69485 -52365
@@ -0,0 +1,166 @@
1
+ $schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
2
+ # Planning pipeline — front-half of the development workflow (task 0088, design §6).
3
+ #
4
+ # Orchestration is configuration (ADR-022 / §3.2): this is YAML over the existing
5
+ # dual-workflow engine — zero new engine code. The pipeline automates steps 3–6 of the
6
+ # ideation→implementation chain: phasing → feature-ID derivation → design-doc generation →
7
+ # design-doc approval. It terminates at a drafted feature list and hands off to sp:spur-dev
8
+ # (steps 7–12), which is already shipped and unchanged.
9
+ #
10
+ # Every authoritative-doc touch invokes sp:doc-evolve to honor the constitution §5 sync triggers.
11
+ # Derived docs (docs/design/*, docs/features/*, 05_FEATURES index) are auto-written; high-risk
12
+ # authoritative edits (02_ROADMAP phasing, 04_DESIGN index) are STAGED to docs/plans/ for human
13
+ # commit (Q3 — hybrid by risk).
14
+ #
15
+ # Shape: phasing(HITL) → feature-id → design-gen → design-approval(HITL) → handoff
16
+ # (phasing/design-approval skipped when profile=auto).
17
+ #
18
+ # Vars (passed as a JSON object via `--vars`):
19
+ # slug — the design-doc slug, e.g. `--vars '{"slug":"auth-migration"}'`
20
+ # profile — set `--vars '{"profile":"auto"}'` to skip the HITL gates (Q4)
21
+ # feature — optional parent feature ID for derivation context
22
+ #
23
+ # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
24
+
25
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
26
+ kind: state-machine
27
+ name: planning-pipeline
28
+ description: "Front-half planning pipeline (phasing → feature-ID → design-gen → design-approval → handoff)"
29
+ iterationBound: 20
30
+ initialState: start
31
+ terminalStates:
32
+ - handoff
33
+ - cancelled
34
+ vars:
35
+ slug: "draft"
36
+ profile: "interactive"
37
+ feature: ""
38
+
39
+ states:
40
+ - id: start
41
+ description: >
42
+ Entry pass-through. Routes to the phasing HITL gate (interactive) or straight to
43
+ feature-id (profile=auto), so the phasing onEnter hitl.confirm only fires when a
44
+ human is in the loop. No actions — a pure routing node.
45
+
46
+ - id: phasing
47
+ description: >
48
+ Decide whether this requirement needs a new phase in 02_ROADMAP. The HITL gate asks
49
+ the operator; if a new phase is needed, the edit is STAGED to docs/plans/ (never
50
+ auto-written — Q3). Under profile=auto this state is never entered (start routes
51
+ around it straight to feature-id).
52
+ onEnter:
53
+ - kind: hitl.confirm
54
+ options:
55
+ prompt: "Does this requirement need a new phase in 02_ROADMAP? (Stage the edit if yes.)"
56
+
57
+ - id: feature-id
58
+ description: >
59
+ Derive the feature ID by scanning docs/features + 05_FEATURES for the parent and
60
+ allocating the child id (same rule as `spur feature create`). Result is recorded for
61
+ the design-doc generation step. Invokes sp:doc-evolve if 05_FEATURES index needs a
62
+ placeholder row.
63
+ onEnter:
64
+ - kind: agent.run
65
+ options:
66
+ input: "Derive the feature ID for ${vars.slug} (scan docs/features + docs/05_FEATURES.md; allocate child id under parent ${vars.feature}). Record the derived id."
67
+
68
+ - id: design-gen
69
+ description: >
70
+ Author the design doc at docs/design/${vars.slug}.md from the brainstormed feature
71
+ draft (docs/plans/${vars.slug}-drafted.md). ADVISORY: use the highest-quality coding
72
+ agent available (Q8 — tier rule is guidance, not enforced). Invokes sp:doc-evolve to
73
+ stage the 04_DESIGN index edit (never auto-write the authoritative index — Q3).
74
+ onEnter:
75
+ - kind: agent.run
76
+ options:
77
+ input: "Author the design doc at docs/design/${vars.slug}.md from docs/plans/${vars.slug}-drafted.md. Invoke sp:doc-evolve to stage the 04_DESIGN index edit."
78
+
79
+ - id: design-approval
80
+ description: >
81
+ HITL gate — the highest-leverage gate in the pipeline. Loop until the operator
82
+ approves the design doc. Under profile=auto this state is never entered (design-gen
83
+ routes around it straight to handoff).
84
+ onEnter:
85
+ - kind: hitl.confirm
86
+ options:
87
+ prompt: "Approve the design doc at docs/design/${vars.slug}.md? (Loop back to design-gen for revisions.)"
88
+
89
+ - id: handoff
90
+ description: >
91
+ Terminal — design doc approved; the drafted feature list is ready. Hand off to
92
+ sp:spur-dev (steps 7–12), which starts at `spur feature create`. The seam is the
93
+ drafted-feature-list file at docs/plans/${vars.slug}-drafted.md.
94
+
95
+ - id: cancelled
96
+ description: Terminal — planning abandoned by the operator.
97
+
98
+ transitions:
99
+ # ── start: route AROUND the phasing HITL gate when profile=auto ──
100
+ # Auto-skip guard tried FIRST: under profile=auto the run goes straight to
101
+ # feature-id and never ENTERS phasing (whose onEnter hitl.confirm would block).
102
+ # Otherwise fall through to phasing for the human gate.
103
+ - from: start
104
+ to: feature-id
105
+ description: profile=auto — skip the phasing HITL gate.
106
+ guard:
107
+ kind: shell
108
+ options:
109
+ command: 'test "${vars.profile}" = auto'
110
+ - from: start
111
+ to: phasing
112
+ description: Interactive — enter the phasing HITL gate.
113
+ guard:
114
+ kind: always
115
+
116
+ # ── phasing: HITL gate (reached only when profile != auto) ──
117
+ - from: phasing
118
+ to: feature-id
119
+ description: Phasing decided — proceed to feature-ID derivation.
120
+ guard:
121
+ kind: always
122
+ - from: phasing
123
+ to: cancelled
124
+ description: Operator cancelled planning.
125
+ guard:
126
+ kind: always
127
+
128
+ # ── linear body ──
129
+ - from: feature-id
130
+ to: design-gen
131
+ description: Feature ID derived — generate the design doc.
132
+ guard:
133
+ kind: always
134
+
135
+ # ── design-gen → design-approval, OR skip the HITL gate when profile=auto ──
136
+ # Auto-skip guard tried FIRST: under profile=auto, route straight to handoff
137
+ # without entering design-approval (whose onEnter hitl.confirm would block).
138
+ - from: design-gen
139
+ to: handoff
140
+ description: profile=auto — skip the design-approval HITL gate, hand off directly.
141
+ guard:
142
+ kind: shell
143
+ options:
144
+ command: 'test "${vars.profile}" = auto'
145
+ - from: design-gen
146
+ to: design-approval
147
+ description: Design doc drafted — gate on human approval.
148
+ guard:
149
+ kind: always
150
+
151
+ # ── design-approval: HITL gate (reached only when profile != auto) ──
152
+ - from: design-approval
153
+ to: handoff
154
+ description: Design approved — hand off to sp:spur-dev.
155
+ guard:
156
+ kind: always
157
+ - from: design-approval
158
+ to: design-gen
159
+ description: Rework — operator requested design revisions.
160
+ guard:
161
+ kind: always
162
+ - from: design-approval
163
+ to: cancelled
164
+ description: Operator cancelled planning.
165
+ guard:
166
+ kind: always
@@ -0,0 +1,128 @@
1
+ $schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
2
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
3
+ kind: state-machine
4
+ name: task-lifecycle
5
+ description: >
6
+ Task lifecycle FSM (design §2.3, §5.1). States are the canonical TaskStatus
7
+ vocabulary (DD-01); transitions encode the §2.3 graph; guards invoke
8
+ `${vars.spurBin} task check` at the wip→testing and testing→done placements.
9
+ `done` is re-enterable (reopen with a warning); `cancelled` is terminal.
10
+ Guard commands reference the check verb (0051) — structural validation passes
11
+ today, behavioral wiring activates as the verb ships. Unconditional transitions
12
+ use the `always` guard (externally-driven via `requestTransition`, not
13
+ auto-advance).
14
+ initialState: backlog
15
+ terminalStates:
16
+ - cancelled
17
+ vars:
18
+ spurBin: 'spur'
19
+ wbs: '0000'
20
+
21
+ states:
22
+ - id: backlog
23
+ description: Initial state — task exists but has not been triaged.
24
+ - id: todo
25
+ description: Triaged and ready to start.
26
+ - id: wip
27
+ description: Actively being worked on.
28
+ - id: testing
29
+ description: Implementation complete; under test/verification.
30
+ - id: blocked
31
+ description: Impediment; work suspended.
32
+ - id: done
33
+ description: Completed. Re-enterable (reopen with warning + mandatory History entry).
34
+ - id: cancelled
35
+ description: Terminal — abandoned.
36
+
37
+ transitions:
38
+ # Forward path: backlog → todo → wip → testing → done
39
+ - from: backlog
40
+ to: todo
41
+ description: Triage — task is ready to start
42
+ guard:
43
+ kind: always
44
+ - from: todo
45
+ to: wip
46
+ description: Start work
47
+ guard:
48
+ kind: always
49
+ - from: wip
50
+ to: testing
51
+ description: Implementation complete; enter testing
52
+ guard:
53
+ kind: shell
54
+ options:
55
+ command: '${vars.spurBin} task check ${vars.wbs}'
56
+ - from: testing
57
+ to: done
58
+ description: Testing passed — task complete
59
+ guard:
60
+ kind: shell
61
+ options:
62
+ command: '${vars.spurBin} task check ${vars.wbs} --strict-core'
63
+
64
+ # Reopen: done → wip (warned, mandatory History entry)
65
+ - from: done
66
+ to: wip
67
+ description: Reopen — work resumed (warning + mandatory History entry)
68
+ guard:
69
+ kind: always
70
+
71
+ # Blocked ↔ (todo, wip, testing) — bidirectional from/to active states
72
+ - from: todo
73
+ to: blocked
74
+ description: Blocked by an impediment
75
+ guard:
76
+ kind: always
77
+ - from: blocked
78
+ to: todo
79
+ description: Impediment resolved — back to todo
80
+ guard:
81
+ kind: always
82
+ - from: wip
83
+ to: blocked
84
+ description: Blocked by an impediment
85
+ guard:
86
+ kind: always
87
+ - from: blocked
88
+ to: wip
89
+ description: Impediment resolved — back to wip
90
+ guard:
91
+ kind: always
92
+ - from: testing
93
+ to: blocked
94
+ description: Blocked by an impediment
95
+ guard:
96
+ kind: always
97
+ - from: blocked
98
+ to: testing
99
+ description: Impediment resolved — back to testing
100
+ guard:
101
+ kind: always
102
+
103
+ # Cancel: any non-terminal → cancelled (terminal)
104
+ - from: backlog
105
+ to: cancelled
106
+ description: Cancel task
107
+ guard:
108
+ kind: always
109
+ - from: todo
110
+ to: cancelled
111
+ description: Cancel task
112
+ guard:
113
+ kind: always
114
+ - from: wip
115
+ to: cancelled
116
+ description: Cancel task
117
+ guard:
118
+ kind: always
119
+ - from: testing
120
+ to: cancelled
121
+ description: Cancel task
122
+ guard:
123
+ kind: always
124
+ - from: blocked
125
+ to: cancelled
126
+ description: Cancel task
127
+ guard:
128
+ kind: always
@@ -0,0 +1,244 @@
1
+ $schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
2
+ # Task execution pipeline — one run per execution attempt of a task (design §6).
3
+ #
4
+ # Orchestration is configuration (ADR-022 / §3.2): this is YAML over the existing
5
+ # dual-workflow engine — zero new engine code. The pipeline NEVER touches files
6
+ # directly — `record` writes via `spur task update --section`, and status moves go
7
+ # through the normal `spur task update <wbs> <status>` verb so the lifecycle guards
8
+ # (0055) apply identically. Run linkage is written to `task_run_links` (kind=pipeline).
9
+ #
10
+ # Shape: precheck → implement → test → review → approve(HITL) → verify → record → done
11
+ # (precheck failure short-circuits to `failed`).
12
+ #
13
+ # Vars (passed as a JSON object via `--vars`):
14
+ # wbs — the task WBS (required), e.g. `--vars '{"wbs":"0042"}'`
15
+ # profile — set `--vars '{"profile":"auto"}'` to skip the HITL approve gate (R4)
16
+ #
17
+ # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
18
+
19
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
20
+ kind: state-machine
21
+ name: task-pipeline
22
+ description: "Standard task execution pipeline (precheck → implement → test → review → approve → verify → record → done)"
23
+ iterationBound: 20
24
+ initialState: precheck
25
+ terminalStates:
26
+ - done
27
+ - failed
28
+ vars:
29
+ wbs: "0000"
30
+ profile: "standard"
31
+ # PATH-independent spur invocation for shell guards/actions. The CLI overrides this
32
+ # at run start (resolveSpurBin); the literal default is a safe fallback so direct/dry
33
+ # runs and `workflow validate` resolve the template. (See AGENTS.md / ADR-026.)
34
+ spurBin: "spur"
35
+ # Agent the pipeline's agent.run steps invoke. Override per run with
36
+ # `--vars '{"agent":"claude"}'`. Pinned (not left to the AiRunner's <default>
37
+ # selection) so a broken/misconfigured agent on the box can't silently capture the run.
38
+ agent: "omp"
39
+ # Step-level timeout for agent.run steps in milliseconds. A timed-out agent
40
+ # subprocess is killed (not abandoned) and the step fails → pipeline routes to
41
+ # `failed`. Override per run with `--vars '{"stepTimeoutMs":120000}'`.
42
+ stepTimeoutMs: "600000"
43
+
44
+ states:
45
+ - id: precheck
46
+ description: >
47
+ Pre-flight structural validation. The precheck→implement guard runs
48
+ `spur task check <wbs>`; a failure routes to `failed` (blocked report).
49
+ onEnter:
50
+ - kind: note
51
+ options:
52
+ # R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
53
+ # hook at run start — see the 0062 follow-up note; there is no link-writing
54
+ # CLI verb to call from a shell step, so it cannot live in pure YAML.
55
+ message: "Pipeline start for task ${vars.wbs}."
56
+
57
+ - id: implement
58
+ description: >
59
+ Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
60
+ task to `wip` via the normal verb. The lifecycle transition fires AFTER the
61
+ implement agent.run succeeds — onEnter runs in declaration order and the default
62
+ 'fail' policy halts the sequence (routing the run to `failed`) if the agent.run
63
+ fails or times out, so a halted implement never leaves the task at `wip` with an
64
+ empty Solution (the F3 half-state). The `--no-lifecycle` flag is correct here: the
65
+ pipeline owns this transition; the FSM guard would otherwise re-run `spur task
66
+ check`, which is precheck's job.
67
+ NOTE: this step must NOT call /sp:dev-run in full mode (the default) —
68
+ that command DRIVES this pipeline, so calling it here recurses.
69
+ --mode implement is the single-step implement entry.
70
+ onEnter:
71
+ - kind: agent.run
72
+ options:
73
+ agent: ${vars.agent}
74
+ input: /sp:dev-run --mode implement ${vars.wbs} --auto
75
+ timeoutMs: ${vars.stepTimeoutMs}
76
+ - kind: shell
77
+ options:
78
+ command: "${vars.spurBin} task update ${vars.wbs} wip --no-lifecycle"
79
+
80
+ - id: test
81
+ description: Test execution + coverage via /sp:dev-unit.
82
+ onEnter:
83
+ - kind: agent.run
84
+ options:
85
+ agent: ${vars.agent}
86
+ input: /sp:dev-unit ${vars.wbs} --auto
87
+ timeoutMs: ${vars.stepTimeoutMs}
88
+
89
+ - id: review
90
+ description: SECUA-framework code review via /sp:dev-review (Security, Efficiency, Correctness, Usability, Architecture).
91
+ onEnter:
92
+ - kind: agent.run
93
+ options:
94
+ agent: ${vars.agent}
95
+ input: /sp:dev-review ${vars.wbs} --auto
96
+ timeoutMs: ${vars.stepTimeoutMs}
97
+
98
+ - id: approve
99
+ description: >
100
+ Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
101
+ (review routes around it straight to verify). NOTE: to make this state PAUSE for `spur workflow continue`
102
+ (E3), add `pause: true` here — deferred until the globally-installed `@gobing-ai/spur`
103
+ schema (currently a stale 0.2.5 that lacks the `pause` field) is refreshed, so
104
+ full `spur workflow validate` stays green. The workspace schema already supports it.
105
+ onEnter:
106
+ - kind: hitl.confirm
107
+ options:
108
+ prompt: "Approve task ${vars.wbs} to proceed to verification?"
109
+
110
+ - id: verify
111
+ description: >
112
+ Functional verification (BDD + traceability) via /sp:dev-verify. The agent's
113
+ captured answer is written to `.spur/run/<wbs>-verify-answer.txt`; a deterministic
114
+ shell step then derives the verdict and writes the gate artifact
115
+ `.spur/run/<wbs>-verdict.json` (so the completion gate reads a real verdict, not
116
+ agent discretion — R9). The verdict is PASS only if the agent both reported PASS
117
+ AND `spur task check` passes; otherwise FAIL.
118
+ onEnter:
119
+ - kind: agent.run
120
+ options:
121
+ agent: ${vars.agent}
122
+ input: /sp:dev-verify ${vars.wbs} --auto --fix all
123
+ timeoutMs: ${vars.stepTimeoutMs}
124
+ answerFile: .spur/run/${vars.wbs}-verify-answer.txt
125
+ - kind: shell
126
+ options:
127
+ command: "${vars.spurBin} task verdict ${vars.wbs} --from-answer .spur/run/${vars.wbs}-verify-answer.txt"
128
+
129
+ - id: record
130
+ description: >
131
+ Record pipeline results into the task file via `spur task record` —
132
+ Testing/Review from the verdict, Solution backfilled from git diff as a
133
+ safety net, optional transition to testing. A single verb replaces the
134
+ previous ~50 lines of embedded shell (0108; ADR-022).
135
+ onEnter:
136
+ - kind: shell
137
+ options:
138
+ command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
139
+
140
+ - id: done
141
+ description: >
142
+ Terminal — task executed, verified, and recorded. The `record → done`
143
+ guard runs `spur task check` before certifying; a genuinely non-compliant
144
+ task routes to `failed` instead of a silent bad `done`.
145
+ onEnter:
146
+ - kind: shell
147
+ options:
148
+ command: "${vars.spurBin} task update ${vars.wbs} done --no-lifecycle"
149
+ - kind: note
150
+ options:
151
+ message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
152
+
153
+ - id: failed
154
+ description: Terminal — precheck (or a gated step) failed; reported, not advanced.
155
+
156
+ transitions:
157
+ # ── precheck: shell guard runs `spur task check`; pass → implement, fail → failed ──
158
+ - from: precheck
159
+ to: implement
160
+ description: Precheck passed — begin implementation.
161
+ guard:
162
+ kind: shell
163
+ options:
164
+ command: "${vars.spurBin} task check ${vars.wbs}"
165
+ - from: precheck
166
+ to: failed
167
+ description: Precheck failed — report and stop (no fall-through to implement).
168
+ guard:
169
+ kind: shell
170
+ options:
171
+ command: '! ${vars.spurBin} task check ${vars.wbs}'
172
+
173
+ # ── linear body ──
174
+ - from: implement
175
+ to: test
176
+ description: Implementation done — run tests.
177
+ guard:
178
+ kind: always
179
+ - from: test
180
+ to: review
181
+ description: Tests done — run review.
182
+ guard:
183
+ kind: always
184
+ # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
185
+ # Declaration order matters: the auto-skip guard is tried FIRST, so under
186
+ # profile=auto the run routes straight to verify and never ENTERS approve
187
+ # (whose onEnter hitl.confirm would otherwise block). Otherwise fall through
188
+ # to approve for the human gate.
189
+ - from: review
190
+ to: verify
191
+ description: profile=auto — skip the human approval gate, go straight to verify.
192
+ guard:
193
+ kind: shell
194
+ options:
195
+ command: 'test "${vars.profile}" = auto'
196
+ - from: review
197
+ to: approve
198
+ description: Review done — gate on human approval.
199
+ guard:
200
+ kind: always
201
+
202
+ # ── approve: HITL gate (reached only when profile != auto) ──
203
+ - from: approve
204
+ to: verify
205
+ description: Approved — proceed to verification.
206
+ guard:
207
+ kind: always
208
+ # ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
209
+ # The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
210
+ # the gate to `record`; any non-PASS (PARTIAL/FAIL), a missing file, or malformed
211
+ # JSON routes to `failed`. Declaration order: PASS guard tried FIRST.
212
+ - from: verify
213
+ to: record
214
+ description: Verification verdict is PASS — record results and proceed to done.
215
+ guard:
216
+ kind: shell
217
+ options:
218
+ command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" = PASS'
219
+ - from: verify
220
+ to: failed
221
+ description: Verification verdict is not PASS (PARTIAL/FAIL/missing) — block before done.
222
+ guard:
223
+ kind: shell
224
+ options:
225
+ command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" != PASS'
226
+ # ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23) ──
227
+ # The record step guarantees every done-required section ([Solution, Testing, Review])
228
+ # has real content (each owned by its pipeline step). This guard is defense-in-depth —
229
+ # it certifies the matrix before done; a genuinely non-compliant task routes to failed.
230
+ # Declaration order: pass guard first.
231
+ - from: record
232
+ to: done
233
+ description: Task check passed — certify done.
234
+ guard:
235
+ kind: shell
236
+ options:
237
+ command: "${vars.spurBin} task check ${vars.wbs}"
238
+ - from: record
239
+ to: failed
240
+ description: Task check failed — block before done; investigate missing sections.
241
+ guard:
242
+ kind: shell
243
+ options:
244
+ command: '! ${vars.spurBin} task check ${vars.wbs}'