@gobing-ai/spur 0.2.12 → 0.3.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.2.12",
3
+ "version": "0.3.0",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -17,6 +17,8 @@
17
17
  # idea — the idea text (required), e.g. --vars '{"idea":"add a --dry-run flag to dev-wrap"}'
18
18
  # profile — set --vars '{"profile":"auto"}' to skip objective HITL gates (feature-check, batch-create)
19
19
  # design — "auto" (default, signal-driven), "force" (--design), "skip" (--skip-design)
20
+ # design_approved — "true" only when the operator explicitly approved the design in-session;
21
+ # lets profile=auto route around the design-approval taste gate (default "false")
20
22
  # spurBin — PATH-independent spur invocation (overridden by CLI at run start)
21
23
  # agent — agent for agent.run steps (default: omp)
22
24
  #
@@ -26,7 +28,10 @@
26
28
  kind: state-machine
27
29
  name: idea-pipeline
28
30
  description: "Idea to feature + AC + task batch: discovery, feature-create, ac-generate, feature-check, system-design, decompose, batch-create, handoff"
29
- iterationBound: 15
31
+ # Worst legal auto path with advertised caps: start/discovery/feature/ac plus 2 AC retries,
32
+ # design, one design rejection, decompose plus 2 batch retries, handoff ≈22 transitions.
33
+ # Keep a small cushion so the explicit retry caps fail first, not the engine bound.
34
+ iterationBound: 25
30
35
  initialState: start
31
36
  terminalStates:
32
37
  - handoff
@@ -36,6 +41,9 @@ vars:
36
41
  idea: ""
37
42
  profile: "standard"
38
43
  design: "auto"
44
+ design_approved: "false"
45
+ featureId: ""
46
+ __hitlAnswer: ""
39
47
  spurBin: "spur"
40
48
  agent: "omp"
41
49
  stepTimeoutMs: "600000"
@@ -51,7 +59,10 @@ states:
51
59
  message: "Idea pipeline start for idea: ${vars.idea}"
52
60
  - kind: shell
53
61
  options:
54
- command: 'rm -f .spur/run/idea-ac-retry-count .spur/run/idea-decompose-retry-count'
62
+ command: "${vars.spurBin} agent doctor ${vars.agent}"
63
+ - kind: shell
64
+ options:
65
+ command: 'mkdir -p .spur/run && rm -f .spur/run/idea-ac-retry-count .spur/run/idea-decompose-retry-count .spur/run/idea-feature-id.txt .spur/run/idea-ac-content.md .spur/run/idea-ac-done.txt .spur/run/idea-task-batch.json .spur/run/idea-batch-create.done .spur/run/idea-batch-create.failed'
55
66
 
56
67
  - id: discovery
57
68
  description: >
@@ -64,7 +75,7 @@ states:
64
75
  - kind: agent.run
65
76
  options:
66
77
  agent: ${vars.agent}
67
- input: "Run sp:brainstorm for the idea: ${vars.idea}. Generate 2-3 approaches with trade-offs and a design summary. Perform the scope decomposition check and write the needs_design boolean to .spur/run/idea-needs-design.json as {\"needs_design\": true|false}. Criteria: true if multiple subsystems/schema/transport/dependency changes; false if single-module/bug-fix/pattern-following. Ties lean true. ALWAYS record a design summary — no idea is too simple."
78
+ input: "Run sp:brainstorm for the idea: ${vars.idea}. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/idea-needs-design.json ({\"needs_design\": true|false}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections."
68
79
  timeoutMs: ${vars.stepTimeoutMs}
69
80
 
70
81
  - id: feature-create
@@ -77,30 +88,52 @@ states:
77
88
  options:
78
89
  agent: ${vars.agent}
79
90
  input: "Create a feature for the idea: ${vars.idea}. Use 'spur feature create \"<name>\" --json' to create it. Write the feature id to .spur/run/idea-feature-id.txt. If an existing feature is appropriate, use its id instead."
91
+ expectFile: .spur/run/idea-feature-id.txt
80
92
  timeoutMs: ${vars.stepTimeoutMs}
93
+ - kind: file.read.into-var
94
+ options:
95
+ path: .spur/run/idea-feature-id.txt
96
+ var: featureId
81
97
 
82
98
  - id: ac-generate
83
99
  description: >
84
100
  Generate acceptance criteria per ac-style-guide.md. Write AC scenarios to the
85
- feature file via spur feature update (direct body edit, not --section). The
86
- agent authors R-numbered Gherkin scenarios tied to the design summary.
101
+ feature file via `spur feature update --section "Acceptance Criteria"
102
+ --from-file`. The agent authors R-numbered Gherkin scenarios tied to the
103
+ design summary into a captured file; the shell step verifies the file is
104
+ non-empty, writes it through the CLI, and writes a completion sentinel
105
+ (.spur/run/idea-ac-done.txt). AC quality checking lives in the transition
106
+ guards (`spur feature check --strict`), NOT in this action chain — a failing
107
+ check must route through the capped retry loop, not fail the run (the engine's
108
+ default onError policy is `fail`, so an in-action check failure would kill the
109
+ run before the retry edges are ever evaluated).
87
110
  onEnter:
111
+ - kind: shell
112
+ options:
113
+ command: 'mkdir -p .spur/run && count=$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/idea-ac-retry-count; rm -f .spur/run/idea-ac-content.md .spur/run/idea-ac-done.txt'
88
114
  - kind: agent.run
89
115
  options:
90
116
  agent: ${vars.agent}
91
- input: "Generate acceptance criteria for the feature whose id is in .spur/run/idea-feature-id.txt. Read the feature file, author R-numbered BDD Gherkin scenarios per ac-style-guide.md, and write them to the feature body via direct file edit (spur feature update does not support --section for features). Run 'spur feature check <id>' to validate."
117
+ input: "Generate acceptance criteria for feature ${vars.featureId}. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Output only the complete Acceptance Criteria section body, including the gherkin code fence, with no surrounding commentary and no section heading."
118
+ answerFile: .spur/run/idea-ac-content.md
119
+ expectFile: .spur/run/idea-ac-content.md
92
120
  timeoutMs: ${vars.stepTimeoutMs}
121
+ - kind: shell
122
+ options:
123
+ command: 'test -s .spur/run/idea-ac-content.md && ${vars.spurBin} feature update ${vars.featureId} --section "Acceptance Criteria" --from-file .spur/run/idea-ac-content.md && date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/idea-ac-done.txt'
93
124
 
94
125
  - id: feature-check
95
126
  description: >
96
- Objective gate: run spur feature check <id> --strict. Under --auto, routes
97
- around the HITL gate when the check passes. On failure, routes back to
98
- ac-generate for AC revision. The feature id is read from
99
- .spur/run/idea-feature-id.txt.
127
+ HITL gate: runs spur feature check <id> --strict. The check is objective
128
+ (schema/AC validation) and auto-routable under profile=auto the transition
129
+ guards route directly from ac-generate to the appropriate next state, so this
130
+ state is only entered in interactive mode. On failure, the retry cap routes
131
+ back to ac-generate (≤3 retries) or escalates to failed.
132
+ pause: true
100
133
  onEnter:
101
- - kind: note
134
+ - kind: hitl.confirm
102
135
  options:
103
- message: "Running feature check for feature $(cat .spur/run/idea-feature-id.txt)"
136
+ prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? (Failures route back to ac-generate for revision, capped at 3 retries.)"
104
137
 
105
138
  - id: system-design
106
139
  description: >
@@ -112,7 +145,7 @@ states:
112
145
  - kind: agent.run
113
146
  options:
114
147
  agent: ${vars.agent}
115
- input: "Run sp:sys-architecture for the feature whose id is in .spur/run/idea-feature-id.txt. Read the brainstorm artifact and feature AC. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) following the constitution edit rules. Do not write task or feature corpus files directly."
148
+ input: "Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact and feature AC. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) following the constitution edit rules. Do not write task or feature corpus files directly."
116
149
  timeoutMs: ${vars.stepTimeoutMs}
117
150
 
118
151
  - id: design-approval
@@ -125,7 +158,7 @@ states:
125
158
  onEnter:
126
159
  - kind: hitl.confirm
127
160
  options:
128
- prompt: "Review the system design for feature $(cat .spur/run/idea-feature-id.txt). Approve to proceed to decomposition?"
161
+ prompt: "Review the system design for feature ${vars.featureId}. Approve to proceed to decomposition?"
129
162
 
130
163
  - id: decompose
131
164
  description: >
@@ -133,21 +166,38 @@ states:
133
166
  design doc as input. The agent produces a task-batch JSON file at
134
167
  .spur/run/idea-task-batch.json, validated against task-batch.schema.json.
135
168
  onEnter:
169
+ - kind: shell
170
+ options:
171
+ command: 'mkdir -p .spur/run && count=$(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/idea-decompose-retry-count; rm -f .spur/run/idea-task-batch.json .spur/run/idea-batch-create.done .spur/run/idea-batch-create.failed'
136
172
  - kind: agent.run
137
173
  options:
138
174
  agent: ${vars.agent}
139
- input: "Run sp:spec-decomposition for the feature whose id is in .spur/run/idea-feature-id.txt. Read the brainstorm artifact, feature AC, and design doc. Produce a task-batch JSON array at .spur/run/idea-task-batch.json, validated against apps/cli/schemas/task-batch.schema.json. Each entry must have filled Requirements, AC, Design, and Plan sections."
175
+ input: "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. Produce a task-batch JSON array at .spur/run/idea-task-batch.json, validated against apps/cli/schemas/task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. Acceptance Criteria, Design, and Plan sections are filled in by the per-task refine step after batch-create, NOT at decompose time. Validate locally against the schema before emitting."
140
176
  timeoutMs: ${vars.stepTimeoutMs}
141
177
 
142
178
  - id: batch-create
143
179
  description: >
144
- Objective gate: run spur task batch-create with the batch JSON from the
145
- decompose state. Under --auto, routes around the HITL gate when the batch
146
- validates. On failure, routes back to decompose.
180
+ HITL gate: runs spur task batch-create with the batch JSON from the
181
+ decompose state. The check is objective (schema validation) and
182
+ auto-routable under profile=auto the transition guards route directly
183
+ from decompose to the appropriate next state, so this state is only
184
+ entered in interactive mode. On failure, the retry cap routes back to
185
+ decompose (≤3 retries) or escalates to failed.
186
+ pause: true
147
187
  onEnter:
148
- - kind: note
188
+ - kind: hitl.confirm
189
+ options:
190
+ 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.)"
191
+
192
+ - id: batch-create-run
193
+ description: >
194
+ Executes task batch creation exactly once per decomposition attempt. The
195
+ side effect lives in onEnter and records a sentinel; transition guards only
196
+ inspect sentinel/retry state.
197
+ onEnter:
198
+ - kind: shell
149
199
  options:
150
- message: "Running batch-create for feature $(cat .spur/run/idea-feature-id.txt)"
200
+ command: 'if test -f .spur/run/idea-batch-create.done; then exit 0; fi; rm -f .spur/run/idea-batch-create.failed; if ${vars.spurBin} task batch-create --file .spur/run/idea-task-batch.json; then date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/idea-batch-create.done; else date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/idea-batch-create.failed; fi'
151
201
 
152
202
  - id: handoff
153
203
  description: >
@@ -157,11 +207,11 @@ states:
157
207
  onEnter:
158
208
  - kind: note
159
209
  options:
160
- message: "Idea pipeline handoff. Feature: $(cat .spur/run/idea-feature-id.txt). Tasks created. Next: /sp:dev-runall --tasks feature:$(cat .spur/run/idea-feature-id.txt)"
210
+ message: "Idea pipeline handoff. Feature: ${vars.featureId}. Tasks created. Next: /sp:dev-runall --tasks feature:${vars.featureId}"
161
211
  # Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
162
212
  - kind: shell
163
213
  options:
164
- command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: idea-pipeline handoff feature=$(cat .spur/run/idea-feature-id.txt) ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/idea-checkpoint.md'
214
+ command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: idea-pipeline handoff feature=${vars.featureId} ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/idea-checkpoint.md'
165
215
 
166
216
  - id: cancelled
167
217
  description: Terminal — pipeline cancelled by operator or error.
@@ -194,10 +244,48 @@ transitions:
194
244
  guard:
195
245
  kind: always
196
246
 
197
- # ── ac-generate -> feature-check ──
247
+ # ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
248
+ # Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
249
+ # and design-gen→handoff). Under profile=auto, the feature check runs as a transition guard and
250
+ # routes directly to the appropriate next state. Under interactive, the always fallback enters
251
+ # the feature-check state whose onEnter hitl.confirm pauses for operator confirmation.
252
+ #
253
+ # Auto-skip 1: pass + design route → system-design
254
+ - from: ac-generate
255
+ to: system-design
256
+ description: "profile=auto, check passed, design route — run system design."
257
+ guard:
258
+ kind: shell
259
+ options:
260
+ command: 'test "${vars.profile}" = auto && ${vars.spurBin} feature check ${vars.featureId} --strict && (test "${vars.design}" = force || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" != false))'
261
+ # Auto-skip 2: pass + skip-design route → decompose
262
+ - from: ac-generate
263
+ to: decompose
264
+ description: "profile=auto, check passed, skip-design route — go directly to decompose."
265
+ guard:
266
+ kind: shell
267
+ options:
268
+ command: 'test "${vars.profile}" = auto && ${vars.spurBin} feature check ${vars.featureId} --strict && (test "${vars.design}" = skip || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" = false))'
269
+ # Auto-skip 3: check failed, retry < 3 → loop back to ac-generate (self-loop)
270
+ - from: ac-generate
271
+ to: ac-generate
272
+ description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
273
+ guard:
274
+ kind: shell
275
+ options:
276
+ command: 'test "${vars.profile}" = auto && ! ${vars.spurBin} feature check ${vars.featureId} --strict && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
277
+ # Auto-skip 4: check failed, retry cap reached → escalate to failed
278
+ - from: ac-generate
279
+ to: failed
280
+ description: "profile=auto, check failed after 3 retries — escalate to failed."
281
+ guard:
282
+ kind: shell
283
+ options:
284
+ command: 'test "${vars.profile}" = auto && ! ${vars.spurBin} feature check ${vars.featureId} --strict && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
285
+ # Interactive fallback: enter feature-check HITL gate
198
286
  - from: ac-generate
199
287
  to: feature-check
200
- description: AC generated run feature check.
288
+ description: Interactiveenter feature-check HITL gate for operator confirmation.
201
289
  guard:
202
290
  kind: always
203
291
 
@@ -209,28 +297,35 @@ transitions:
209
297
  guard:
210
298
  kind: shell
211
299
  options:
212
- command: '${vars.spurBin} feature check $(cat .spur/run/idea-feature-id.txt) --strict && (test "${vars.design}" = force || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" != false))'
300
+ command: 'test "${vars.__hitlAnswer}" = yes && ${vars.spurBin} feature check ${vars.featureId} --strict && (test "${vars.design}" = force || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" != false))'
213
301
  - from: feature-check
214
302
  to: decompose
215
303
  description: Feature check passed, skip-design route — go directly to decompose.
216
304
  guard:
217
305
  kind: shell
218
306
  options:
219
- command: '${vars.spurBin} feature check $(cat .spur/run/idea-feature-id.txt) --strict && (test "${vars.design}" = skip || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" = false))'
307
+ command: 'test "${vars.__hitlAnswer}" = yes && ${vars.spurBin} feature check ${vars.featureId} --strict && (test "${vars.design}" = skip || (test "${vars.design}" = auto && test "$(jq -r .needs_design .spur/run/idea-needs-design.json 2>/dev/null)" = false))'
220
308
  - from: feature-check
221
309
  to: ac-generate
222
310
  description: "Feature check failed — revise AC (retry cap: 3)."
223
311
  guard:
224
312
  kind: shell
225
313
  options:
226
- command: '! ${vars.spurBin} feature check $(cat .spur/run/idea-feature-id.txt) --strict && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3 && echo $(( $(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0) + 1 )) > .spur/run/idea-ac-retry-count'
314
+ command: '(test "${vars.__hitlAnswer}" = no || ! ${vars.spurBin} feature check ${vars.featureId} --strict) && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3'
227
315
  - from: feature-check
228
316
  to: failed
229
317
  description: Feature check failed after 3 retries — escalate to failed.
230
318
  guard:
231
319
  kind: shell
232
320
  options:
233
- command: '! ${vars.spurBin} feature check $(cat .spur/run/idea-feature-id.txt) --strict && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
321
+ command: '(test "${vars.__hitlAnswer}" = no || ! ${vars.spurBin} feature check ${vars.featureId} --strict) && test "$(cat .spur/run/idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3'
322
+ - from: feature-check
323
+ to: cancelled
324
+ description: Operator cancelled the feature-check gate.
325
+ guard:
326
+ kind: shell
327
+ options:
328
+ command: 'test "${vars.__hitlAnswer}" = cancel'
234
329
 
235
330
  # ── system-design: -> design-approval (normal) OR -> decompose (auto + prior approval) ──
236
331
  # Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
@@ -252,34 +347,80 @@ transitions:
252
347
  to: decompose
253
348
  description: Design approved — proceed to decomposition.
254
349
  guard:
255
- kind: always
350
+ kind: shell
351
+ options:
352
+ command: 'test "${vars.__hitlAnswer}" = yes'
353
+ - from: design-approval
354
+ to: system-design
355
+ description: Design rejected — revise system design.
356
+ guard:
357
+ kind: shell
358
+ options:
359
+ command: 'test "${vars.__hitlAnswer}" = no'
360
+ - from: design-approval
361
+ to: cancelled
362
+ description: Operator cancelled design approval.
363
+ guard:
364
+ kind: shell
365
+ options:
366
+ command: 'test "${vars.__hitlAnswer}" = cancel'
256
367
 
257
- # ── decompose -> batch-create ──
368
+ # ── decompose: auto-skip (profile=auto) OR enter batch-create HITL gate (interactive) ──
369
+ - from: decompose
370
+ to: batch-create-run
371
+ description: "profile=auto — execute batch-create side effect."
372
+ guard:
373
+ kind: shell
374
+ options:
375
+ command: 'test "${vars.profile}" = auto'
376
+ # Interactive fallback: enter batch-create HITL gate
258
377
  - from: decompose
259
378
  to: batch-create
260
- description: Decomposition complete — create task batch.
379
+ description: Interactiveenter batch-create HITL gate for operator confirmation.
261
380
  guard:
262
381
  kind: always
263
382
 
264
- # ── batch-create: success -> handoff, failure -> decompose (retry cap: 3), failure+cap -> failed ──
265
383
  - from: batch-create
384
+ to: batch-create-run
385
+ description: Operator approved batch creation — execute side effect.
386
+ guard:
387
+ kind: shell
388
+ options:
389
+ command: 'test "${vars.__hitlAnswer}" = yes'
390
+ - from: batch-create
391
+ to: decompose
392
+ description: Operator requested decomposition revisions.
393
+ guard:
394
+ kind: shell
395
+ options:
396
+ command: 'test "${vars.__hitlAnswer}" = no'
397
+ - from: batch-create
398
+ to: cancelled
399
+ description: Operator cancelled batch creation.
400
+ guard:
401
+ kind: shell
402
+ options:
403
+ command: 'test "${vars.__hitlAnswer}" = cancel'
404
+
405
+ # ── batch-create-run: success -> handoff, failure -> decompose (retry cap: 3), failure+cap -> failed ──
406
+ - from: batch-create-run
266
407
  to: handoff
267
408
  description: Batch created — handoff.
268
409
  guard:
269
410
  kind: shell
270
411
  options:
271
- command: '${vars.spurBin} task batch-create --file .spur/run/idea-task-batch.json'
272
- - from: batch-create
412
+ command: 'test -f .spur/run/idea-batch-create.done'
413
+ - from: batch-create-run
273
414
  to: decompose
274
415
  description: "Batch-create failed — revise decomposition (retry cap: 3)."
275
416
  guard:
276
417
  kind: shell
277
418
  options:
278
- command: '! ${vars.spurBin} task batch-create --file .spur/run/idea-task-batch.json && test "$(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3 && echo $(( $(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0) + 1 )) > .spur/run/idea-decompose-retry-count'
279
- - from: batch-create
419
+ command: 'test -f .spur/run/idea-batch-create.failed && test "$(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'
420
+ - from: batch-create-run
280
421
  to: failed
281
422
  description: Batch-create failed after 3 retries — escalate to failed.
282
423
  guard:
283
424
  kind: shell
284
425
  options:
285
- command: '! ${vars.spurBin} task batch-create --file .spur/run/idea-task-batch.json && test "$(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'
426
+ command: 'test -f .spur/run/idea-batch-create.failed && test "$(cat .spur/run/idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'
@@ -20,6 +20,8 @@
20
20
  # slug — the design-doc slug, e.g. `--vars '{"slug":"auth-migration"}'`
21
21
  # profile — set `--vars '{"profile":"auto"}'` to skip the HITL gates (Q4)
22
22
  # feature — optional parent feature ID for derivation context
23
+ # design_approved — "true" only when the operator explicitly approved the design in-session;
24
+ # lets profile=auto route around the design-approval taste gate (default "false")
23
25
  #
24
26
  # Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
25
27
 
@@ -36,6 +38,11 @@ vars:
36
38
  slug: "draft"
37
39
  profile: "interactive"
38
40
  feature: ""
41
+ design_approved: "false"
42
+ __hitlAnswer: ""
43
+ spurBin: "spur"
44
+ agent: "omp"
45
+ stepTimeoutMs: "600000"
39
46
 
40
47
  states:
41
48
  - id: start
@@ -57,14 +64,16 @@ states:
57
64
 
58
65
  - id: feature-id
59
66
  description: >
60
- Derive the feature ID by scanning docs/features + 05_FEATURES for the parent and
61
- allocating the child id (same rule as `spur feature create`). Result is recorded for
62
- the design-doc generation step. Invokes sp:doc-evolve if 05_FEATURES index needs a
63
- placeholder row.
67
+ Allocate the feature id via the canonical `spur feature create` verb (it owns the
68
+ hierarchical ID rule per DD-14). The agent derives a feature name from ${vars.slug}
69
+ and invokes the verb; the new id is captured for the design-doc generation step.
64
70
  onEnter:
65
71
  - kind: agent.run
66
72
  options:
67
- 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."
73
+ agent: ${vars.agent}
74
+ input: "Allocate a feature id for slug ${vars.slug} via the canonical verb. Run `${vars.spurBin} feature create \"<human title derived from ${vars.slug}>\" --parent ${vars.feature} --json` and capture the new feature id from the JSON output into .spur/run/plan-feature-id.txt for the design-gen step. If an existing feature for ${vars.slug} is appropriate, use its id instead and write it to the same file. Do not re-implement id-allocation rules in prose; the verb owns them."
75
+ expectFile: .spur/run/plan-feature-id.txt
76
+ timeoutMs: ${vars.stepTimeoutMs}
68
77
 
69
78
  - id: design-gen
70
79
  description: >
@@ -75,7 +84,9 @@ states:
75
84
  onEnter:
76
85
  - kind: agent.run
77
86
  options:
87
+ agent: ${vars.agent}
78
88
  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."
89
+ timeoutMs: ${vars.stepTimeoutMs}
79
90
 
80
91
  - id: design-approval
81
92
  description: >
@@ -127,12 +138,16 @@ transitions:
127
138
  to: feature-id
128
139
  description: Phasing decided — proceed to feature-ID derivation.
129
140
  guard:
130
- kind: always
141
+ kind: shell
142
+ options:
143
+ command: 'test "${vars.__hitlAnswer}" = yes || test "${vars.__hitlAnswer}" = no'
131
144
  - from: phasing
132
145
  to: cancelled
133
146
  description: Operator cancelled planning.
134
147
  guard:
135
- kind: always
148
+ kind: shell
149
+ options:
150
+ command: 'test "${vars.__hitlAnswer}" = cancel'
136
151
 
137
152
  # ── linear body ──
138
153
  - from: feature-id
@@ -163,14 +178,20 @@ transitions:
163
178
  to: handoff
164
179
  description: Design approved — hand off to sp:spur-dev.
165
180
  guard:
166
- kind: always
181
+ kind: shell
182
+ options:
183
+ command: 'test "${vars.__hitlAnswer}" = yes'
167
184
  - from: design-approval
168
185
  to: design-gen
169
186
  description: Rework — operator requested design revisions.
170
187
  guard:
171
- kind: always
188
+ kind: shell
189
+ options:
190
+ command: 'test "${vars.__hitlAnswer}" = no'
172
191
  - from: design-approval
173
192
  to: cancelled
174
193
  description: Operator cancelled planning.
175
194
  guard:
176
- kind: always
195
+ kind: shell
196
+ options:
197
+ command: 'test "${vars.__hitlAnswer}" = cancel'
@@ -7,7 +7,8 @@
7
7
  # (0055) apply identically. Run linkage is written to `task_run_links` (kind=pipeline).
8
8
  #
9
9
  # Shape: precheck → implement → test → review → approve(HITL) → verify → record → done
10
- # (precheck failure short-circuits to `failed`).
10
+ # (precheck failure short-circuits to `failed`; approve routes to `failed` on
11
+ # operator rejection or `cancelled` on operator cancel — R1, bug-750).
11
12
  #
12
13
  # Vars (passed as a JSON object via `--vars`):
13
14
  # wbs — the task WBS (required), e.g. `--vars '{"wbs":"0042"}'`
@@ -24,6 +25,7 @@ initialState: precheck
24
25
  terminalStates:
25
26
  - done
26
27
  - failed
28
+ - cancelled
27
29
  vars:
28
30
  wbs: "0000"
29
31
  profile: "standard"
@@ -39,6 +41,15 @@ vars:
39
41
  # subprocess is killed (not abandoned) and the step fails → pipeline routes to
40
42
  # `failed`. Override per run with `--vars '{"stepTimeoutMs":120000}'`.
41
43
  stepTimeoutMs: "600000"
44
+ # Timeout for the `implement` step specifically (R2a). Implementation is the
45
+ # heaviest agent.run step (full read/write/test-probe loop) and has timed out
46
+ # at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
47
+ # a 100% failure rate at that budget. 30 min gives real headroom; STOP and
48
+ # record honestly rather than raising it further without operator sign-off.
49
+ implementTimeoutMs: "1800000"
50
+ # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
51
+ # Empty by default; only meaningful once the approve state has been entered.
52
+ __hitlAnswer: ""
42
53
 
43
54
  states:
44
55
  - id: precheck
@@ -46,6 +57,9 @@ states:
46
57
  Pre-flight structural validation. The precheck→implement guard runs
47
58
  `spur task check <wbs>`; a failure routes to `failed` (blocked report).
48
59
  onEnter:
60
+ - kind: shell
61
+ options:
62
+ command: "${vars.spurBin} agent doctor ${vars.agent}"
49
63
  - kind: note
50
64
  options:
51
65
  # R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
@@ -70,8 +84,12 @@ states:
70
84
  - kind: agent.run
71
85
  options:
72
86
  agent: ${vars.agent}
73
- input: /sp:dev-run --mode implement ${vars.wbs} --auto
74
- timeoutMs: ${vars.stepTimeoutMs}
87
+ input: >-
88
+ /sp:dev-run --mode implement ${vars.wbs} --auto.
89
+ Work only in this working tree on task ${vars.wbs}; NEVER invoke
90
+ `spur workflow run` or `/sp:dev-run` without `--mode implement` —
91
+ this step IS the pipeline (bug-742).
92
+ timeoutMs: ${vars.implementTimeoutMs}
75
93
  - kind: shell
76
94
  options:
77
95
  command: "${vars.spurBin} task update ${vars.wbs} wip --no-lifecycle"
@@ -115,6 +133,8 @@ states:
115
133
  Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
116
134
  (review routes around it straight to verify). In interactive mode this state pauses
117
135
  the run for `spur workflow continue` (E3), making approval an explicit operator action.
136
+ The operator's answer is routed (R1, bug-750): yes -> verify; no -> failed (rejection
137
+ recorded, not silently approved); cancel -> cancelled (a distinct terminal state).
118
138
  pause: true
119
139
  onEnter:
120
140
  - kind: hitl.confirm
@@ -171,6 +191,9 @@ states:
171
191
  - id: failed
172
192
  description: Terminal — precheck (or a gated step) failed; reported, not advanced.
173
193
 
194
+ - id: cancelled
195
+ description: Terminal — pipeline cancelled by operator at the approval gate (R1).
196
+
174
197
  transitions:
175
198
  # ── precheck: shell guard runs `spur task check`; pass → implement, fail → failed ──
176
199
  - from: precheck
@@ -217,12 +240,33 @@ transitions:
217
240
  guard:
218
241
  kind: always
219
242
 
220
- # ── approve: HITL gate (reached only when profile != auto) ──
243
+ # ── approve: HITL gate (reached only when profile != auto). Routed by the
244
+ # operator's captured answer (R1, bug-750) — three ordered guards, declaration
245
+ # order matters (yes first, then no, then cancel), mirroring idea-pipeline's
246
+ # design-approval gate (config/workflows/idea-pipeline.yaml:352-366). No
247
+ # `always` edge remains out of `approve`. ──
221
248
  - from: approve
222
249
  to: verify
223
250
  description: Approved — proceed to verification.
224
251
  guard:
225
- kind: always
252
+ kind: shell
253
+ options:
254
+ command: 'test "${vars.__hitlAnswer}" = yes'
255
+ - from: approve
256
+ to: failed
257
+ description: Operator rejected at the approval gate — report and stop.
258
+ guard:
259
+ kind: shell
260
+ options:
261
+ command: 'test "${vars.__hitlAnswer}" = no'
262
+ - from: approve
263
+ to: cancelled
264
+ description: Operator cancelled at the approval gate.
265
+ guard:
266
+ kind: shell
267
+ options:
268
+ command: 'test "${vars.__hitlAnswer}" = cancel'
269
+
226
270
  # ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
227
271
  # The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
228
272
  # the gate to `record`; any non-PASS (PARTIAL/FAIL), a missing file, or malformed