@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 +1 -1
- package/spur-cli/config/workflows/idea-pipeline.yaml +178 -37
- package/spur-cli/config/workflows/planning-pipeline.yaml +31 -10
- package/spur-cli/config/workflows/task-pipeline.yaml +49 -5
- package/spur-cli/config/workflows/wrapup-pipeline.yaml +34 -19
- package/spur.js +476 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.
|
|
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
|
-
|
|
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:
|
|
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}.
|
|
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
|
|
86
|
-
agent authors R-numbered Gherkin scenarios tied to the
|
|
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
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
ac-generate
|
|
99
|
-
.
|
|
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:
|
|
134
|
+
- kind: hitl.confirm
|
|
102
135
|
options:
|
|
103
|
-
|
|
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
|
|
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 $
|
|
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
|
|
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
|
-
|
|
145
|
-
decompose state.
|
|
146
|
-
|
|
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:
|
|
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
|
-
|
|
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: $
|
|
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=$
|
|
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
|
|
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:
|
|
288
|
+
description: Interactive — enter 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 $
|
|
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 $
|
|
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 $
|
|
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 $
|
|
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:
|
|
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
|
|
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:
|
|
379
|
+
description: Interactive — enter 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: '
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
the
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
74
|
-
|
|
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:
|
|
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
|