@gobing-ai/spur 0.2.4 → 0.2.8

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 +9 -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 +9 -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 +188 -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 +48 -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 +88 -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 +238 -0
  42. package/spur.js +69233 -52218
@@ -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,238 @@
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
+ Move the task to `wip` (via the normal verb — lifecycle guards apply, R3),
60
+ then run agent-driven implementation via /sp:dev-run --mode implement.
61
+ NOTE: this step must NOT call /sp:dev-run in full mode (the default) —
62
+ that command DRIVES this pipeline, so calling it here recurses.
63
+ --mode implement is the single-step implement entry.
64
+ onEnter:
65
+ - kind: shell
66
+ options:
67
+ command: "${vars.spurBin} task update ${vars.wbs} wip --no-lifecycle"
68
+ - kind: agent.run
69
+ options:
70
+ agent: ${vars.agent}
71
+ input: /sp:dev-run --mode implement ${vars.wbs} --auto
72
+ timeoutMs: ${vars.stepTimeoutMs}
73
+
74
+ - id: test
75
+ description: Test execution + coverage via /sp:dev-unit.
76
+ onEnter:
77
+ - kind: agent.run
78
+ options:
79
+ agent: ${vars.agent}
80
+ input: /sp:dev-unit ${vars.wbs} --auto
81
+ timeoutMs: ${vars.stepTimeoutMs}
82
+
83
+ - id: review
84
+ description: SECU code review via /sp:dev-review.
85
+ onEnter:
86
+ - kind: agent.run
87
+ options:
88
+ agent: ${vars.agent}
89
+ input: /sp:dev-review ${vars.wbs} --auto
90
+ timeoutMs: ${vars.stepTimeoutMs}
91
+
92
+ - id: approve
93
+ description: >
94
+ Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
95
+ (review routes around it straight to verify). NOTE: to make this state PAUSE for `spur workflow continue`
96
+ (E3), add `pause: true` here — deferred until the globally-installed `@gobing-ai/spur`
97
+ schema (currently a stale 0.2.5 that lacks the `pause` field) is refreshed, so
98
+ full `spur workflow validate` stays green. The workspace schema already supports it.
99
+ onEnter:
100
+ - kind: hitl.confirm
101
+ options:
102
+ prompt: "Approve task ${vars.wbs} to proceed to verification?"
103
+
104
+ - id: verify
105
+ description: >
106
+ Functional verification (BDD + traceability) via /sp:dev-verify. The agent's
107
+ captured answer is written to `.spur/run/<wbs>-verify-answer.txt`; a deterministic
108
+ shell step then derives the verdict and writes the gate artifact
109
+ `.spur/run/<wbs>-verdict.json` (so the completion gate reads a real verdict, not
110
+ agent discretion — R9). The verdict is PASS only if the agent both reported PASS
111
+ AND `spur task check` passes; otherwise FAIL.
112
+ onEnter:
113
+ - kind: agent.run
114
+ options:
115
+ agent: ${vars.agent}
116
+ input: /sp:dev-verify ${vars.wbs} --auto --fix all
117
+ timeoutMs: ${vars.stepTimeoutMs}
118
+ answerFile: .spur/run/${vars.wbs}-verify-answer.txt
119
+ - kind: shell
120
+ options:
121
+ command: "${vars.spurBin} task verdict ${vars.wbs} --from-answer .spur/run/${vars.wbs}-verify-answer.txt"
122
+
123
+ - id: record
124
+ description: >
125
+ Record pipeline results into the task file via `spur task record` —
126
+ Testing/Review from the verdict, Solution backfilled from git diff as a
127
+ safety net, optional transition to testing. A single verb replaces the
128
+ previous ~50 lines of embedded shell (0108; ADR-022).
129
+ onEnter:
130
+ - kind: shell
131
+ options:
132
+ command: "${vars.spurBin} task record ${vars.wbs} --solution-from-diff --transition testing"
133
+
134
+ - id: done
135
+ description: >
136
+ Terminal — task executed, verified, and recorded. The `record → done`
137
+ guard runs `spur task check` before certifying; a genuinely non-compliant
138
+ task routes to `failed` instead of a silent bad `done`.
139
+ onEnter:
140
+ - kind: shell
141
+ options:
142
+ command: "${vars.spurBin} task update ${vars.wbs} done --no-lifecycle"
143
+ - kind: note
144
+ options:
145
+ message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
146
+
147
+ - id: failed
148
+ description: Terminal — precheck (or a gated step) failed; reported, not advanced.
149
+
150
+ transitions:
151
+ # ── precheck: shell guard runs `spur task check`; pass → implement, fail → failed ──
152
+ - from: precheck
153
+ to: implement
154
+ description: Precheck passed — begin implementation.
155
+ guard:
156
+ kind: shell
157
+ options:
158
+ command: "${vars.spurBin} task check ${vars.wbs}"
159
+ - from: precheck
160
+ to: failed
161
+ description: Precheck failed — report and stop (no fall-through to implement).
162
+ guard:
163
+ kind: shell
164
+ options:
165
+ command: '! ${vars.spurBin} task check ${vars.wbs}'
166
+
167
+ # ── linear body ──
168
+ - from: implement
169
+ to: test
170
+ description: Implementation done — run tests.
171
+ guard:
172
+ kind: always
173
+ - from: test
174
+ to: review
175
+ description: Tests done — run review.
176
+ guard:
177
+ kind: always
178
+ # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
179
+ # Declaration order matters: the auto-skip guard is tried FIRST, so under
180
+ # profile=auto the run routes straight to verify and never ENTERS approve
181
+ # (whose onEnter hitl.confirm would otherwise block). Otherwise fall through
182
+ # to approve for the human gate.
183
+ - from: review
184
+ to: verify
185
+ description: profile=auto — skip the human approval gate, go straight to verify.
186
+ guard:
187
+ kind: shell
188
+ options:
189
+ command: 'test "${vars.profile}" = auto'
190
+ - from: review
191
+ to: approve
192
+ description: Review done — gate on human approval.
193
+ guard:
194
+ kind: always
195
+
196
+ # ── approve: HITL gate (reached only when profile != auto) ──
197
+ - from: approve
198
+ to: verify
199
+ description: Approved — proceed to verification.
200
+ guard:
201
+ kind: always
202
+ # ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
203
+ # The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
204
+ # the gate to `record`; any non-PASS (PARTIAL/FAIL), a missing file, or malformed
205
+ # JSON routes to `failed`. Declaration order: PASS guard tried FIRST.
206
+ - from: verify
207
+ to: record
208
+ description: Verification verdict is PASS — record results and proceed to done.
209
+ guard:
210
+ kind: shell
211
+ options:
212
+ command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" = PASS'
213
+ - from: verify
214
+ to: failed
215
+ description: Verification verdict is not PASS (PARTIAL/FAIL/missing) — block before done.
216
+ guard:
217
+ kind: shell
218
+ options:
219
+ command: 'test "$(jq -r .verdict .spur/run/${vars.wbs}-verdict.json 2>/dev/null)" != PASS'
220
+ # ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23) ──
221
+ # The record step guarantees every done-required section ([Solution, Testing, Review])
222
+ # has real content (each owned by its pipeline step). This guard is defense-in-depth —
223
+ # it certifies the matrix before done; a genuinely non-compliant task routes to failed.
224
+ # Declaration order: pass guard first.
225
+ - from: record
226
+ to: done
227
+ description: Task check passed — certify done.
228
+ guard:
229
+ kind: shell
230
+ options:
231
+ command: "${vars.spurBin} task check ${vars.wbs}"
232
+ - from: record
233
+ to: failed
234
+ description: Task check failed — block before done; investigate missing sections.
235
+ guard:
236
+ kind: shell
237
+ options:
238
+ command: '! ${vars.spurBin} task check ${vars.wbs}'