@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.2.11",
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/design-approval skipped when profile=auto).
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
- Derive the feature ID by scanning docs/features + 05_FEATURES for the parent and
59
- allocating the child id (same rule as `spur feature create`). Result is recorded for
60
- the design-doc generation step. Invokes sp:doc-evolve if 05_FEATURES index needs a
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
- 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}
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 never entered (design-gen
82
- routes around it straight to handoff).
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: always
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: always
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, route straight to handoff
136
- # without entering design-approval (whose onEnter hitl.confirm would block).
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, hand off directly.
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: always
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: always
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: always
195
+ kind: shell
196
+ options:
197
+ command: 'test "${vars.__hitlAnswer}" = cancel'