@gobing-ai/spur 0.2.11 → 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/feature-dev.yaml +5 -0
- package/spur-cli/config/workflows/idea-pipeline.yaml +426 -0
- package/spur-cli/config/workflows/planning-pipeline.yaml +50 -18
- package/spur-cli/config/workflows/task-pipeline.yaml +69 -5
- package/spur-cli/config/workflows/wrapup-pipeline.yaml +240 -0
- package/spur.js +515 -78
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",
|
|
@@ -86,6 +86,11 @@ states:
|
|
|
86
86
|
|
|
87
87
|
- id: done
|
|
88
88
|
description: Terminal — feature planned, all tasks executed, and the feature verified.
|
|
89
|
+
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
90
|
+
onEnter:
|
|
91
|
+
- kind: shell
|
|
92
|
+
options:
|
|
93
|
+
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: feature-dev done featureId=${vars.featureId} ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/${vars.featureId}-checkpoint.md'
|
|
89
94
|
|
|
90
95
|
- id: failed
|
|
91
96
|
description: Terminal — plan rejected, a task failed verification, or the feature check did not pass.
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Idea-to-feature pipeline — unified entry from a vague idea to a feature, AC, and task batch
|
|
2
|
+
# (design §idea-pipeline).
|
|
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 STOPS at handoff — tasks are
|
|
6
|
+
# created but NOT executed. Use task-pipeline.yaml or feature-dev.yaml for execution.
|
|
7
|
+
# No state in this pipeline inlines another pipeline's state graph (no-nesting principle,
|
|
8
|
+
# design §System Principles 4).
|
|
9
|
+
#
|
|
10
|
+
# Shape: start -> discovery -> feature-create -> ac-generate -> feature-check
|
|
11
|
+
# -> system-design (conditional: needs_design signal or --design)
|
|
12
|
+
# -> design-approval (taste HITL gate; not auto-clicked by --auto)
|
|
13
|
+
# -> decompose -> batch-create -> handoff
|
|
14
|
+
# (feature-check failure routes back to ac-generate; batch-create failure to decompose).
|
|
15
|
+
#
|
|
16
|
+
# Vars (passed as a JSON object via `--vars`):
|
|
17
|
+
# idea — the idea text (required), e.g. --vars '{"idea":"add a --dry-run flag to dev-wrap"}'
|
|
18
|
+
# profile — set --vars '{"profile":"auto"}' to skip objective HITL gates (feature-check, batch-create)
|
|
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")
|
|
22
|
+
# spurBin — PATH-independent spur invocation (overridden by CLI at run start)
|
|
23
|
+
# agent — agent for agent.run steps (default: omp)
|
|
24
|
+
#
|
|
25
|
+
# Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
|
|
26
|
+
|
|
27
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
28
|
+
kind: state-machine
|
|
29
|
+
name: idea-pipeline
|
|
30
|
+
description: "Idea to feature + AC + task batch: discovery, feature-create, ac-generate, feature-check, system-design, decompose, batch-create, handoff"
|
|
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
|
|
35
|
+
initialState: start
|
|
36
|
+
terminalStates:
|
|
37
|
+
- handoff
|
|
38
|
+
- cancelled
|
|
39
|
+
- failed
|
|
40
|
+
vars:
|
|
41
|
+
idea: ""
|
|
42
|
+
profile: "standard"
|
|
43
|
+
design: "auto"
|
|
44
|
+
design_approved: "false"
|
|
45
|
+
featureId: ""
|
|
46
|
+
__hitlAnswer: ""
|
|
47
|
+
spurBin: "spur"
|
|
48
|
+
agent: "omp"
|
|
49
|
+
stepTimeoutMs: "600000"
|
|
50
|
+
|
|
51
|
+
states:
|
|
52
|
+
- id: start
|
|
53
|
+
description: >
|
|
54
|
+
Pipeline start. The idea text arrives as vars.idea. The pipeline stops at
|
|
55
|
+
handoff — no task execution.
|
|
56
|
+
onEnter:
|
|
57
|
+
- kind: note
|
|
58
|
+
options:
|
|
59
|
+
message: "Idea pipeline start for idea: ${vars.idea}"
|
|
60
|
+
- kind: shell
|
|
61
|
+
options:
|
|
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'
|
|
66
|
+
|
|
67
|
+
- id: discovery
|
|
68
|
+
description: >
|
|
69
|
+
Dispatch sp:brainstorm to explore the idea, generate approaches with trade-offs,
|
|
70
|
+
and record a design summary. The brainstorm ALWAYS records a design summary
|
|
71
|
+
("nothing is too simple" pattern). The brainstorm also emits a needs_design
|
|
72
|
+
boolean signal written to .spur/run/idea-needs-design.json — this determines
|
|
73
|
+
whether the system-design state runs.
|
|
74
|
+
onEnter:
|
|
75
|
+
- kind: agent.run
|
|
76
|
+
options:
|
|
77
|
+
agent: ${vars.agent}
|
|
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."
|
|
79
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
80
|
+
|
|
81
|
+
- id: feature-create
|
|
82
|
+
description: >
|
|
83
|
+
Create a feature via spur feature create, or select an existing feature id.
|
|
84
|
+
The agent writes the feature id to .spur/run/idea-feature-id.txt for subsequent
|
|
85
|
+
states to read.
|
|
86
|
+
onEnter:
|
|
87
|
+
- kind: agent.run
|
|
88
|
+
options:
|
|
89
|
+
agent: ${vars.agent}
|
|
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
|
|
92
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
93
|
+
- kind: file.read.into-var
|
|
94
|
+
options:
|
|
95
|
+
path: .spur/run/idea-feature-id.txt
|
|
96
|
+
var: featureId
|
|
97
|
+
|
|
98
|
+
- id: ac-generate
|
|
99
|
+
description: >
|
|
100
|
+
Generate acceptance criteria per ac-style-guide.md. Write AC scenarios 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).
|
|
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'
|
|
114
|
+
- kind: agent.run
|
|
115
|
+
options:
|
|
116
|
+
agent: ${vars.agent}
|
|
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
|
|
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'
|
|
124
|
+
|
|
125
|
+
- id: feature-check
|
|
126
|
+
description: >
|
|
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
|
|
133
|
+
onEnter:
|
|
134
|
+
- kind: hitl.confirm
|
|
135
|
+
options:
|
|
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.)"
|
|
137
|
+
|
|
138
|
+
- id: system-design
|
|
139
|
+
description: >
|
|
140
|
+
Dispatch sp:sys-architecture to produce the system design. This state runs
|
|
141
|
+
only when needs_design=true (or --design forces it). The agent creates ADR
|
|
142
|
+
entries, architecture updates, and design satellites through constitution rules.
|
|
143
|
+
The design-approval state follows (taste gate).
|
|
144
|
+
onEnter:
|
|
145
|
+
- kind: agent.run
|
|
146
|
+
options:
|
|
147
|
+
agent: ${vars.agent}
|
|
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."
|
|
149
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
150
|
+
|
|
151
|
+
- id: design-approval
|
|
152
|
+
description: >
|
|
153
|
+
HITL taste gate. The operator reviews the system design and approves or
|
|
154
|
+
rejects it. This gate is NOT auto-clicked by --auto (Auto-Decision Principle
|
|
155
|
+
#5: taste-decision -> surface to human). Only routes around when
|
|
156
|
+
vars.design_approved=true (explicit prior approval).
|
|
157
|
+
pause: true
|
|
158
|
+
onEnter:
|
|
159
|
+
- kind: hitl.confirm
|
|
160
|
+
options:
|
|
161
|
+
prompt: "Review the system design for feature ${vars.featureId}. Approve to proceed to decomposition?"
|
|
162
|
+
|
|
163
|
+
- id: decompose
|
|
164
|
+
description: >
|
|
165
|
+
Dispatch sp:spec-decomposition with the brainstorm artifact, feature AC, and
|
|
166
|
+
design doc as input. The agent produces a task-batch JSON file at
|
|
167
|
+
.spur/run/idea-task-batch.json, validated against task-batch.schema.json.
|
|
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'
|
|
172
|
+
- kind: agent.run
|
|
173
|
+
options:
|
|
174
|
+
agent: ${vars.agent}
|
|
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."
|
|
176
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
177
|
+
|
|
178
|
+
- id: batch-create
|
|
179
|
+
description: >
|
|
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
|
|
187
|
+
onEnter:
|
|
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
|
|
199
|
+
options:
|
|
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'
|
|
201
|
+
|
|
202
|
+
- id: handoff
|
|
203
|
+
description: >
|
|
204
|
+
Terminal — idea pipeline complete. Output: feature id, task WBS list, and
|
|
205
|
+
the next command to run (/sp:dev-run <first-wbs> or /sp:dev-runall --tasks feature:<id>).
|
|
206
|
+
No task execution — the pipeline stops here.
|
|
207
|
+
onEnter:
|
|
208
|
+
- kind: note
|
|
209
|
+
options:
|
|
210
|
+
message: "Idea pipeline handoff. Feature: ${vars.featureId}. Tasks created. Next: /sp:dev-runall --tasks feature:${vars.featureId}"
|
|
211
|
+
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
212
|
+
- kind: shell
|
|
213
|
+
options:
|
|
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'
|
|
215
|
+
|
|
216
|
+
- id: cancelled
|
|
217
|
+
description: Terminal — pipeline cancelled by operator or error.
|
|
218
|
+
|
|
219
|
+
- id: failed
|
|
220
|
+
description: >
|
|
221
|
+
Terminal — pipeline failed after exhausting retry caps on cyclic edges
|
|
222
|
+
(feature-check → ac-generate or batch-create → decompose). The error
|
|
223
|
+
message in the run trace identifies which edge exceeded the cap.
|
|
224
|
+
|
|
225
|
+
transitions:
|
|
226
|
+
# ── start -> discovery ──
|
|
227
|
+
- from: start
|
|
228
|
+
to: discovery
|
|
229
|
+
description: Begin discovery — brainstorm the idea.
|
|
230
|
+
guard:
|
|
231
|
+
kind: always
|
|
232
|
+
|
|
233
|
+
# ── discovery -> feature-create ──
|
|
234
|
+
- from: discovery
|
|
235
|
+
to: feature-create
|
|
236
|
+
description: Discovery complete — create or select a feature.
|
|
237
|
+
guard:
|
|
238
|
+
kind: always
|
|
239
|
+
|
|
240
|
+
# ── feature-create -> ac-generate ──
|
|
241
|
+
- from: feature-create
|
|
242
|
+
to: ac-generate
|
|
243
|
+
description: Feature created — generate acceptance criteria.
|
|
244
|
+
guard:
|
|
245
|
+
kind: always
|
|
246
|
+
|
|
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
|
|
286
|
+
- from: ac-generate
|
|
287
|
+
to: feature-check
|
|
288
|
+
description: Interactive — enter feature-check HITL gate for operator confirmation.
|
|
289
|
+
guard:
|
|
290
|
+
kind: always
|
|
291
|
+
|
|
292
|
+
# ── feature-check: pass + design route -> system-design, pass + skip route -> decompose, fail -> ac-generate ──
|
|
293
|
+
# Declaration order matters: system-design route tried first, then skip route, then fail.
|
|
294
|
+
- from: feature-check
|
|
295
|
+
to: system-design
|
|
296
|
+
description: Feature check passed, design route — run system design.
|
|
297
|
+
guard:
|
|
298
|
+
kind: shell
|
|
299
|
+
options:
|
|
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))'
|
|
301
|
+
- from: feature-check
|
|
302
|
+
to: decompose
|
|
303
|
+
description: Feature check passed, skip-design route — go directly to decompose.
|
|
304
|
+
guard:
|
|
305
|
+
kind: shell
|
|
306
|
+
options:
|
|
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))'
|
|
308
|
+
- from: feature-check
|
|
309
|
+
to: ac-generate
|
|
310
|
+
description: "Feature check failed — revise AC (retry cap: 3)."
|
|
311
|
+
guard:
|
|
312
|
+
kind: shell
|
|
313
|
+
options:
|
|
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'
|
|
315
|
+
- from: feature-check
|
|
316
|
+
to: failed
|
|
317
|
+
description: Feature check failed after 3 retries — escalate to failed.
|
|
318
|
+
guard:
|
|
319
|
+
kind: shell
|
|
320
|
+
options:
|
|
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'
|
|
329
|
+
|
|
330
|
+
# ── system-design: -> design-approval (normal) OR -> decompose (auto + prior approval) ──
|
|
331
|
+
# Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
|
|
332
|
+
- from: system-design
|
|
333
|
+
to: decompose
|
|
334
|
+
description: profile=auto AND design_approved=true — skip design approval gate.
|
|
335
|
+
guard:
|
|
336
|
+
kind: shell
|
|
337
|
+
options:
|
|
338
|
+
command: 'test "${vars.profile}" = auto && test "${vars.design_approved}" = true'
|
|
339
|
+
- from: system-design
|
|
340
|
+
to: design-approval
|
|
341
|
+
description: System design done — gate on design approval.
|
|
342
|
+
guard:
|
|
343
|
+
kind: always
|
|
344
|
+
|
|
345
|
+
# ── design-approval -> decompose (after operator approval) ──
|
|
346
|
+
- from: design-approval
|
|
347
|
+
to: decompose
|
|
348
|
+
description: Design approved — proceed to decomposition.
|
|
349
|
+
guard:
|
|
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'
|
|
367
|
+
|
|
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
|
|
377
|
+
- from: decompose
|
|
378
|
+
to: batch-create
|
|
379
|
+
description: Interactive — enter batch-create HITL gate for operator confirmation.
|
|
380
|
+
guard:
|
|
381
|
+
kind: always
|
|
382
|
+
|
|
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
|
|
407
|
+
to: handoff
|
|
408
|
+
description: Batch created — handoff.
|
|
409
|
+
guard:
|
|
410
|
+
kind: shell
|
|
411
|
+
options:
|
|
412
|
+
command: 'test -f .spur/run/idea-batch-create.done'
|
|
413
|
+
- from: batch-create-run
|
|
414
|
+
to: decompose
|
|
415
|
+
description: "Batch-create failed — revise decomposition (retry cap: 3)."
|
|
416
|
+
guard:
|
|
417
|
+
kind: shell
|
|
418
|
+
options:
|
|
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
|
|
421
|
+
to: failed
|
|
422
|
+
description: Batch-create failed after 3 retries — escalate to failed.
|
|
423
|
+
guard:
|
|
424
|
+
kind: shell
|
|
425
|
+
options:
|
|
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'
|
|
@@ -12,12 +12,16 @@
|
|
|
12
12
|
# commit (Q3 — hybrid by risk).
|
|
13
13
|
#
|
|
14
14
|
# Shape: phasing(HITL) → feature-id → design-gen → design-approval(HITL) → handoff
|
|
15
|
-
# (phasing
|
|
15
|
+
# (phasing skipped when profile=auto; design-approval skipped when profile=auto
|
|
16
|
+
# AND design_approved=true — taste gates pause even in auto unless prior approval
|
|
17
|
+
# is encoded, per design doc HITL taxonomy).
|
|
16
18
|
#
|
|
17
19
|
# Vars (passed as a JSON object via `--vars`):
|
|
18
20
|
# slug — the design-doc slug, e.g. `--vars '{"slug":"auth-migration"}'`
|
|
19
21
|
# profile — set `--vars '{"profile":"auto"}'` to skip the HITL gates (Q4)
|
|
20
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")
|
|
21
25
|
#
|
|
22
26
|
# Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
|
|
23
27
|
|
|
@@ -34,6 +38,11 @@ vars:
|
|
|
34
38
|
slug: "draft"
|
|
35
39
|
profile: "interactive"
|
|
36
40
|
feature: ""
|
|
41
|
+
design_approved: "false"
|
|
42
|
+
__hitlAnswer: ""
|
|
43
|
+
spurBin: "spur"
|
|
44
|
+
agent: "omp"
|
|
45
|
+
stepTimeoutMs: "600000"
|
|
37
46
|
|
|
38
47
|
states:
|
|
39
48
|
- id: start
|
|
@@ -55,14 +64,16 @@ states:
|
|
|
55
64
|
|
|
56
65
|
- id: feature-id
|
|
57
66
|
description: >
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
the
|
|
61
|
-
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.
|
|
62
70
|
onEnter:
|
|
63
71
|
- kind: agent.run
|
|
64
72
|
options:
|
|
65
|
-
|
|
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}
|
|
66
77
|
|
|
67
78
|
- id: design-gen
|
|
68
79
|
description: >
|
|
@@ -73,13 +84,18 @@ states:
|
|
|
73
84
|
onEnter:
|
|
74
85
|
- kind: agent.run
|
|
75
86
|
options:
|
|
87
|
+
agent: ${vars.agent}
|
|
76
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}
|
|
77
90
|
|
|
78
91
|
- id: design-approval
|
|
79
92
|
description: >
|
|
80
93
|
HITL gate — the highest-leverage gate in the pipeline. Loop until the operator
|
|
81
|
-
approves the design doc. Under profile=auto this state is
|
|
82
|
-
|
|
94
|
+
approves the design doc. Under profile=auto this state is skipped ONLY when
|
|
95
|
+
vars.design_approved=true (explicit prior approval); otherwise the taste gate
|
|
96
|
+
pauses even in auto mode (design doc HITL taxonomy). Loop back to design-gen
|
|
97
|
+
for revisions.
|
|
98
|
+
pause: true
|
|
83
99
|
onEnter:
|
|
84
100
|
- kind: hitl.confirm
|
|
85
101
|
options:
|
|
@@ -90,6 +106,11 @@ states:
|
|
|
90
106
|
Terminal — design doc approved; the drafted feature list is ready. Hand off to
|
|
91
107
|
sp:spur-dev (steps 7–12), which starts at `spur feature create`. The seam is the
|
|
92
108
|
drafted-feature-list file at docs/plans/${vars.slug}-drafted.md.
|
|
109
|
+
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
110
|
+
onEnter:
|
|
111
|
+
- kind: shell
|
|
112
|
+
options:
|
|
113
|
+
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: planning-pipeline handoff slug=${vars.slug} ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/${vars.slug}-checkpoint.md'
|
|
93
114
|
|
|
94
115
|
- id: cancelled
|
|
95
116
|
description: Terminal — planning abandoned by the operator.
|
|
@@ -117,12 +138,16 @@ transitions:
|
|
|
117
138
|
to: feature-id
|
|
118
139
|
description: Phasing decided — proceed to feature-ID derivation.
|
|
119
140
|
guard:
|
|
120
|
-
kind:
|
|
141
|
+
kind: shell
|
|
142
|
+
options:
|
|
143
|
+
command: 'test "${vars.__hitlAnswer}" = yes || test "${vars.__hitlAnswer}" = no'
|
|
121
144
|
- from: phasing
|
|
122
145
|
to: cancelled
|
|
123
146
|
description: Operator cancelled planning.
|
|
124
147
|
guard:
|
|
125
|
-
kind:
|
|
148
|
+
kind: shell
|
|
149
|
+
options:
|
|
150
|
+
command: 'test "${vars.__hitlAnswer}" = cancel'
|
|
126
151
|
|
|
127
152
|
# ── linear body ──
|
|
128
153
|
- from: feature-id
|
|
@@ -131,16 +156,17 @@ transitions:
|
|
|
131
156
|
guard:
|
|
132
157
|
kind: always
|
|
133
158
|
|
|
134
|
-
# ── design-gen → design-approval, OR skip the HITL gate when profile=auto ──
|
|
135
|
-
# Auto-skip guard tried FIRST: under profile=auto
|
|
136
|
-
# without entering design-approval
|
|
159
|
+
# ── design-gen → design-approval, OR skip the HITL gate when profile=auto AND design_approved=true ──
|
|
160
|
+
# Auto-skip guard tried FIRST: under profile=auto with explicit prior approval,
|
|
161
|
+
# route straight to handoff without entering design-approval. Without prior
|
|
162
|
+
# approval, the taste gate pauses even in auto (design doc HITL taxonomy).
|
|
137
163
|
- from: design-gen
|
|
138
164
|
to: handoff
|
|
139
|
-
description: profile=auto — skip the design-approval HITL gate
|
|
165
|
+
description: profile=auto AND design_approved=true — skip the design-approval HITL gate.
|
|
140
166
|
guard:
|
|
141
167
|
kind: shell
|
|
142
168
|
options:
|
|
143
|
-
command: 'test "${vars.profile}" = auto'
|
|
169
|
+
command: 'test "${vars.profile}" = auto && test "${vars.design_approved}" = true'
|
|
144
170
|
- from: design-gen
|
|
145
171
|
to: design-approval
|
|
146
172
|
description: Design doc drafted — gate on human approval.
|
|
@@ -152,14 +178,20 @@ transitions:
|
|
|
152
178
|
to: handoff
|
|
153
179
|
description: Design approved — hand off to sp:spur-dev.
|
|
154
180
|
guard:
|
|
155
|
-
kind:
|
|
181
|
+
kind: shell
|
|
182
|
+
options:
|
|
183
|
+
command: 'test "${vars.__hitlAnswer}" = yes'
|
|
156
184
|
- from: design-approval
|
|
157
185
|
to: design-gen
|
|
158
186
|
description: Rework — operator requested design revisions.
|
|
159
187
|
guard:
|
|
160
|
-
kind:
|
|
188
|
+
kind: shell
|
|
189
|
+
options:
|
|
190
|
+
command: 'test "${vars.__hitlAnswer}" = no'
|
|
161
191
|
- from: design-approval
|
|
162
192
|
to: cancelled
|
|
163
193
|
description: Operator cancelled planning.
|
|
164
194
|
guard:
|
|
165
|
-
kind:
|
|
195
|
+
kind: shell
|
|
196
|
+
options:
|
|
197
|
+
command: 'test "${vars.__hitlAnswer}" = cancel'
|