@gobing-ai/spur 0.4.14 → 0.4.15
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/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +0 -14
- package/config/workflow-candidates.json +18 -0
- package/config/workflows/feature-lifecycle.yaml +9 -16
- package/config/workflows/idea-pipeline.yaml +158 -740
- package/config/workflows/wrapup-pipeline.yaml +91 -623
- package/package.json +9 -9
- package/plugins/sp/README.md +8 -4
- package/plugins/sp/commands/dev-idea.md +9 -5
- package/plugins/sp/commands/dev-plan.md +1 -1
- package/plugins/sp/commands/dev-pr-review.md +6 -6
- package/plugins/sp/commands/dev-wrap.md +1 -1
- package/plugins/sp/commands/dev-wrapall.md +4 -4
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/pr-reviewing/SKILL.md +36 -18
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +2 -1
- package/plugins/sp/skills/spur-cli/references/builder.md +2 -2
- package/plugins/sp/skills/spur-cli/references/features.md +7 -0
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +2 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +10 -10
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +9 -10
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +1 -2
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +7 -6
- package/plugins/sp/skills/wayfinder/SKILL.md +11 -1
- package/spur.js +800 -799
- package/config/workflows/feature-verification.yaml +0 -98
- package/config/workflows/pr-review.yaml +0 -209
- package/config/workflows/wayfinder-resolution.yaml +0 -302
- package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +0 -30
|
@@ -1,67 +1,48 @@
|
|
|
1
|
-
# Idea-to-feature pipeline — unified entry from a vague idea to a feature, AC, and task batch
|
|
2
|
-
# (design §
|
|
1
|
+
# Idea-to-feature pipeline — unified entry from a vague idea to a feature, AC, and a task batch
|
|
2
|
+
# (design docs/design/workflow-consolidation.md §3.4). YAML over the dual-workflow engine; zero new
|
|
3
|
+
# engine code. The pipeline STOPS at handoff — tasks are created but NOT executed; use
|
|
4
|
+
# task-pipeline.yaml for execution. No state inlines another pipeline's state graph.
|
|
3
5
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# Shape: start -> discovery -> idea-eval -> feature-create -> ac-generate
|
|
7
|
+
# -> system-design (conditional: needs_design signal) -> design-approval (taste)
|
|
8
|
+
# -> decompose -> batch-create-run -> ready-prepare -> handoff-finalize -> handoff.
|
|
9
|
+
# idea-eval rejection (or auto + `drop`) -> cancelled; ac-generate check failure and
|
|
10
|
+
# batch-create failure -> their own capped retry loops -> failed.
|
|
9
11
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
12
|
+
# D66 1250 (design §3.4): the objective checks carry no operator pause. `feature-check` and
|
|
13
|
+
# `batch-create` are DELETED — their recorded results are deterministic PASS/FAIL, which every
|
|
14
|
+
# profile now routes on directly, so the former `profile=auto` predicates and the always fallbacks
|
|
15
|
+
# into those states are gone. The two discovery rescues are typed `decide` actions over the catalog
|
|
16
|
+
# ids in config/decisions/idea-pipeline.yaml (rescue-only, design §4): the deterministic parse runs
|
|
17
|
+
# first and the decide is consulted only for output the parse cannot classify. `idea-eval` and
|
|
18
|
+
# `design-approval` stay operator gates (taste). The start-state entry check is EXECUTOR-TYPED: a
|
|
19
|
+
# host run's executor is the session itself (no subprocess doctor), and a fleet run is ready exactly
|
|
20
|
+
# when this host wired fleet dispatch (ADR-126).
|
|
16
21
|
#
|
|
17
|
-
# Evidence instead of ceremony (task 0769): the feature check is MEASURED
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# no CLI re-run per guard); a gate re-runs only after a relevant write (revision pass) or on
|
|
22
|
-
# the next boundary.
|
|
22
|
+
# Evidence instead of ceremony (task 0769): the feature check is MEASURED once at each author/revise
|
|
23
|
+
# boundary — `idea-ac-check` at the end of ac-generate, `idea-design-check` at the end of
|
|
24
|
+
# system-design — by a deterministic `command.gate` writing a run-scoped PASS/FAIL result file.
|
|
25
|
+
# Guards only CONSUME the recorded result; no guard re-runs the CLI.
|
|
23
26
|
#
|
|
24
|
-
# Vars (
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# the recorded design check PASSes (default "false")
|
|
32
|
-
# idea_approved — "true" lets profile=auto accept the idea-eval report's recommendation
|
|
33
|
-
# (proceed/reshape → continue, drop → cancelled; missing → pause) (default "false")
|
|
34
|
-
# CLI --auto on dev-idea / dev-plan sets profile=auto AND both pre-clear vars to true.
|
|
35
|
-
# spurBin — PATH-independent spur invocation (overridden by CLI at run start)
|
|
36
|
-
# agent — agent for agent.run steps (default: auto → `agent.default` in config)
|
|
37
|
-
#
|
|
38
|
-
# Reliability (aligned with task-pipeline / ADR-043):
|
|
39
|
-
# - Soft agent doctor at start → failed via transitions (not raw lifecycle abort)
|
|
40
|
-
# - expectFile on feature-create / ac-generate / decompose artifacts (no silent no-ops)
|
|
41
|
-
# - Caped AC and batch-create retry loops with fail-closed escalation
|
|
42
|
-
# - HITL taste gates (idea-eval, design-approval) exhaustive yes/no/cancel
|
|
43
|
-
# - Free-form skill pointers remain only where no pure slash surface exists yet
|
|
44
|
-
# (discovery/sys-architecture/decompose internals — residual until dedicated commands)
|
|
45
|
-
#
|
|
46
|
-
# Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
|
|
27
|
+
# Vars (`--vars '{"idea":"…"}'`): idea (required); profile ("auto" skips the taste gates' questions —
|
|
28
|
+
# they answer from recorded evidence — while the objective stages route identically under every
|
|
29
|
+
# profile); design ("auto" default signal-driven | "skip", which also omits per-task Design);
|
|
30
|
+
# design_approved / idea_approved ("true" lets profile=auto route around that taste gate, default
|
|
31
|
+
# "false"); spurBin; agent (agent.run steps; default auto → `agent.default`); executor ("host" |
|
|
32
|
+
# "fleet" | "" for the tier ladder — read by the entry check and every agent.run stage).
|
|
33
|
+
# CLI --auto on dev-idea / dev-plan sets profile=auto AND both pre-clear vars to true.
|
|
47
34
|
|
|
48
35
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
49
|
-
version: "2"
|
|
50
36
|
kind: state-machine
|
|
51
37
|
name: idea-pipeline
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
#
|
|
38
|
+
version: "3"
|
|
39
|
+
description: "Idea to feature + AC + task batch: discovery, idea-eval, feature-create, ac-generate, system-design, decompose, ready-prepare, handoff"
|
|
40
|
+
# Worst legal path with advertised caps ≈20 transitions; the cushion keeps the explicit retry caps
|
|
41
|
+
# failing first, not the engine bound.
|
|
56
42
|
iterationBound: 25
|
|
57
43
|
initialState: start
|
|
58
|
-
terminalStates:
|
|
59
|
-
|
|
60
|
-
- cancelled
|
|
61
|
-
- failed
|
|
62
|
-
failureStates:
|
|
63
|
-
- failed
|
|
64
|
-
- cancelled
|
|
44
|
+
terminalStates: [handoff, cancelled, failed]
|
|
45
|
+
failureStates: [failed, cancelled]
|
|
65
46
|
vars:
|
|
66
47
|
idea: ""
|
|
67
48
|
profile: "standard"
|
|
@@ -76,26 +57,16 @@ vars:
|
|
|
76
57
|
agent: "auto"
|
|
77
58
|
planningAgent: ""
|
|
78
59
|
stepTimeoutMs: "1800000"
|
|
79
|
-
# Executor for every agent.run stage (D65 R1): "host" pauses the run with a work item
|
|
80
|
-
#
|
|
81
|
-
# resolves the subprocess surface through the existing tier ladder.
|
|
60
|
+
# Executor for every agent.run stage (D65 R1): "host" pauses the run with a work item, "fleet"
|
|
61
|
+
# dispatches through ADR-126, empty resolves the subprocess surface through the tier ladder.
|
|
82
62
|
executor: ""
|
|
83
63
|
# Accepted/deferred status each operator gate's decision binding writes (1236 R2).
|
|
84
64
|
ideaEvalStatus: ""
|
|
85
|
-
featureCheckStatus: ""
|
|
86
65
|
designApprovalStatus: ""
|
|
87
|
-
batchCreateStatus: ""
|
|
88
66
|
|
|
89
67
|
states:
|
|
90
68
|
- id: start
|
|
91
|
-
|
|
92
|
-
phase: discover
|
|
93
|
-
phaseTitle: Discover
|
|
94
|
-
title: Check inputs
|
|
95
|
-
description: >
|
|
96
|
-
Pipeline start. The idea text arrives as vars.idea. The pipeline stops at
|
|
97
|
-
handoff — no task execution. Soft agent doctor (status file) so doctor red
|
|
98
|
-
routes to `failed` via transitions instead of a raw lifecycle abort.
|
|
69
|
+
description: "One executor-typed entry check (D66 1250 R1): the executor-aware doctor.probe writes the executor verdict and the idea-text check writes its own named reason to the SAME status file, so a single token routes."
|
|
99
70
|
onEnter:
|
|
100
71
|
- kind: note
|
|
101
72
|
options:
|
|
@@ -107,8 +78,8 @@ states:
|
|
|
107
78
|
agent: "${vars.agent}"
|
|
108
79
|
role: planner
|
|
109
80
|
resolvedAgentVar: planningAgent
|
|
110
|
-
# B7 R1 (0894): also pin the role once — __executor.planner feeds stage
|
|
111
|
-
#
|
|
81
|
+
# B7 R1 (0894): also pin the role once — __executor.planner feeds stage dispatch so no
|
|
82
|
+
# later idea stage re-walks the doctor ladder.
|
|
112
83
|
roles:
|
|
113
84
|
planner: "${vars.agent}"
|
|
114
85
|
- kind: shell
|
|
@@ -117,101 +88,81 @@ states:
|
|
|
117
88
|
mkdir -p .spur/run &&
|
|
118
89
|
printf '%s\n' "$idea" > ".spur/run/$__runId-idea-input.md" &&
|
|
119
90
|
awk 'NF' ".spur/run/$__runId-idea-input.md" | grep -q . ||
|
|
120
|
-
printf 'FAIL\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
|
|
91
|
+
printf 'FAIL: idea text is empty\n' > ".spur/run/$__runId-idea-precheck-doctor.status"
|
|
121
92
|
|
|
122
93
|
- id: discovery
|
|
123
|
-
|
|
124
|
-
phase: discover
|
|
125
|
-
phaseTitle: Discover
|
|
126
|
-
title: Discovery interview
|
|
127
|
-
description: >
|
|
128
|
-
Dispatch sp:brainstorm to explore the idea, generate approaches with trade-offs,
|
|
129
|
-
and record a design summary. The brainstorm ALWAYS records a design summary
|
|
130
|
-
("nothing is too simple" pattern). The brainstorm also emits a needs_design
|
|
131
|
-
boolean signal written to .spur/run/${vars.__runId}-idea-needs-design.json — this determines
|
|
132
|
-
whether the system-design state runs. As its terminal artifact for the idea path,
|
|
133
|
-
brainstorm also emits the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md
|
|
134
|
-
(template: the `sp:spur-dev` skill's `idea-evaluation` reference — named by skill, not by
|
|
135
|
-
repo path, because `spur init` never scaffolds `plugins/sp/` into a seeded project).
|
|
136
|
-
The operator's verbatim idea argument is persisted at .spur/run/${vars.__runId}-idea-input.md
|
|
137
|
-
(written by start; the authoritative ask every model-bearing stage reads).
|
|
138
|
-
expectFile fails a silent no-op discovery (no eval report).
|
|
94
|
+
description: "Dispatch sp:brainstorm: approaches with trade-offs, a design summary, the needs_design signal, and the idea-evaluation report (.spur/run/${vars.__runId}-idea-eval-report.md, per the `sp:spur-dev` `idea-evaluation` reference — named by skill, not repo path). The operator's verbatim idea is persisted at .spur/run/${vars.__runId}-idea-input.md by start. expectFile fails a silent no-op discovery. D66 1250 R3: the two rescues are typed `decide` actions whose projections keep the pre-1250 fallback semantics."
|
|
139
95
|
onEnter:
|
|
140
96
|
- kind: agent.run
|
|
141
97
|
options:
|
|
142
98
|
agent: ${vars.planningAgent}
|
|
143
99
|
input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative, read it first (${vars.idea} is a convenience echo). Run sp:brainstorm. The skill owns the approach-generation, design summary, and `needs_design` signal criteria; emit .spur/run/${vars.__runId}-idea-needs-design.json ({\"needs_design\": true|false}) plus the design summary per its `Design Approval Gate` / `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per the `sp:spur-dev` skill's `idea-evaluation` reference (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation, plus mandatory `## Requirement inventory`: numbered I<n> items quoting/paraphrasing idea-input lines; `[unclear: ...]` marks ambiguity, `[deferred: <reason>]` marks out-of-scope). End with this exact footer: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339>\\n---` (omit if run_id is empty)."
|
|
144
|
-
# Declared Layer-1 role (0538 R2)
|
|
100
|
+
# Declared Layer-1 role (0538 R2) + declared fresh policy (B7 R6 / 0894).
|
|
145
101
|
role: planner
|
|
146
|
-
# B7 R6 (0894): planner stages declare fresh — the role default is stated
|
|
147
|
-
# explicitly so the definition is self-documenting.
|
|
148
102
|
session: fresh
|
|
149
103
|
expectFile: .spur/run/${vars.__runId}-idea-eval-report.md
|
|
150
|
-
# The signal is this stage's own output; the plural form declares it to the
|
|
151
|
-
#
|
|
152
|
-
# post-exit guard, so a missing signal still falls back to the catalog decision.
|
|
104
|
+
# The signal is this stage's own output; the plural form declares it to the work item
|
|
105
|
+
# (host/fleet) while the singular expectFile stays the subprocess post-exit guard.
|
|
153
106
|
expectFiles:
|
|
154
107
|
- .spur/run/${vars.__runId}-idea-needs-design.json
|
|
155
108
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
156
109
|
# Derive the eval recommendation ONCE (0769 pattern): the first non-blank line under
|
|
157
|
-
# `## Recommendation` → proceed | reshape | drop | unknown.
|
|
158
|
-
# discovery / idea-eval read this file; `unknown` routes to the idea-eval pause.
|
|
110
|
+
# `## Recommendation` → proceed | reshape | drop | unknown. `unknown` routes to idea-eval.
|
|
159
111
|
- kind: shell
|
|
160
112
|
options:
|
|
161
113
|
command: >-
|
|
162
114
|
awk '/^## Recommendation/{f=1;next} f&&NF{r=tolower($0);exit}
|
|
163
115
|
END{print (match(r,/^[*_ ]*(proceed|reshape|drop)/) ? substr(r,RSTART,RLENGTH) : "unknown")}'
|
|
164
116
|
".spur/run/$__runId-idea-eval-report.md" | tr -d '*_ ' > ".spur/run/$__runId-idea-recommendation.txt"
|
|
165
|
-
#
|
|
166
|
-
# the
|
|
167
|
-
#
|
|
168
|
-
|
|
169
|
-
|
|
117
|
+
# D66 1250 R3 (design §4 rescue-only): the awk parse above stays FIRST; this typed decide
|
|
118
|
+
# replaces the former `spur decision run` shell and is consulted only for unclassifiable
|
|
119
|
+
# output. It writes the frozen schemaVersion-1 row.
|
|
120
|
+
- kind: decide
|
|
121
|
+
options:
|
|
122
|
+
id: idea-recommendation
|
|
123
|
+
decision: idea-recommendation
|
|
124
|
+
evidence:
|
|
125
|
+
- .spur/run/${vars.__runId}-idea-eval-report.md
|
|
126
|
+
resultFile: .spur/run/${vars.__runId}-idea-recommendation.decision
|
|
127
|
+
# Projection (1250 R3): a non-model source (switch off, backend failure, low confidence) reads
|
|
128
|
+
# `unknown`, so no default can auto-create a feature — it routes to the idea-eval pause.
|
|
170
129
|
- kind: shell
|
|
171
130
|
options:
|
|
172
131
|
command: >-
|
|
173
132
|
f=".spur/run/$__runId-idea-recommendation.txt";
|
|
174
133
|
[ "$(cat "$f" 2>/dev/null)" = unknown ] || exit 0;
|
|
175
|
-
v="$(
|
|
134
|
+
v="$(jq -r 'if .source=="model" then .value else "unknown" end' ".spur/run/$__runId-idea-recommendation.decision" 2>/dev/null)";
|
|
176
135
|
case "$v" in proceed|reshape|drop) printf '%s\n' "$v" > "$f" ;; esac
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
136
|
+
# D66 1250 R3: the same shape for the design signal — a valid boolean short-circuits in the
|
|
137
|
+
# projection; otherwise it falls back to `design` unless the model said `skip`.
|
|
138
|
+
- kind: decide
|
|
139
|
+
options:
|
|
140
|
+
id: needs-design
|
|
141
|
+
decision: needs-design
|
|
142
|
+
evidence:
|
|
143
|
+
- .spur/run/${vars.__runId}-idea-eval-report.md
|
|
144
|
+
resultFile: .spur/run/${vars.__runId}-idea-needs-design.decision
|
|
182
145
|
- kind: shell
|
|
183
146
|
options:
|
|
184
147
|
command: >-
|
|
185
148
|
f=".spur/run/$__runId-idea-needs-design.json";
|
|
186
149
|
jq -e '.needs_design|type=="boolean"' "$f" >/dev/null 2>&1 && exit 0;
|
|
187
|
-
|
|
150
|
+
jq -e '.source=="model" and .value=="skip"' ".spur/run/$__runId-idea-needs-design.decision" >/dev/null 2>&1 &&
|
|
151
|
+
printf '{"needs_design": false}\n' > "$f" || printf '{"needs_design": true}\n' > "$f"
|
|
188
152
|
|
|
189
153
|
- id: idea-eval
|
|
190
|
-
|
|
191
|
-
phase: discover
|
|
192
|
-
phaseTitle: Discover
|
|
193
|
-
title: Evaluate idea
|
|
194
|
-
description: >
|
|
195
|
-
HITL taste gate. Discovery has produced the idea-evaluation report
|
|
196
|
-
(.spur/run/${vars.__runId}-idea-eval-report.md) with urgency/necessity scores, premises,
|
|
197
|
-
pros/cons, and a recommendation. The operator reviews and approves or
|
|
198
|
-
rejects. Routed around only when profile=auto AND vars.idea_approved=true
|
|
199
|
-
(CLI --auto) AND the report's recommendation is proceed/reshape; a drop
|
|
200
|
-
recommendation cancels, a missing one asks the gate, whose evidence binding then
|
|
201
|
-
answers under profile=auto (1236/1241).
|
|
154
|
+
description: "HITL taste gate over the idea-evaluation report. Routed around only when profile=auto AND vars.idea_approved=true AND the recommendation is proceed/reshape; a drop recommendation cancels and a missing one asks the gate, whose evidence binding then answers under profile=auto (1236/1241)."
|
|
202
155
|
onEnter:
|
|
203
156
|
- kind: hitl.confirm
|
|
204
157
|
options:
|
|
205
158
|
prompt: "Review the idea evaluation report (.spur/run/${vars.__runId}-idea-eval-report.md). Approve to create a feature, or reject to cancel? (Reject creates no feature.)"
|
|
206
|
-
# Interactive always prompts the operator; profile=auto answers from the discovery
|
|
207
|
-
# stage's recorded evidence, deferring when it cannot decide (1236 R1/R2).
|
|
208
159
|
decision:
|
|
209
160
|
mode: evidence
|
|
210
161
|
statusVar: ideaEvalStatus
|
|
211
162
|
evidenceNodes:
|
|
212
163
|
- discovery
|
|
213
|
-
# A deferred/absent answer is the denial today's headless responder gave, so the
|
|
214
|
-
#
|
|
164
|
+
# A deferred/absent answer is the denial today's headless responder gave, so the guards keep
|
|
165
|
+
# routing on yes | no | cancel with no new edge.
|
|
215
166
|
- kind: shell
|
|
216
167
|
options:
|
|
217
168
|
command: >-
|
|
@@ -223,33 +174,17 @@ states:
|
|
|
223
174
|
var: __hitlAnswer
|
|
224
175
|
|
|
225
176
|
- id: feature-create
|
|
226
|
-
|
|
227
|
-
phase: define
|
|
228
|
-
phaseTitle: "Define feature"
|
|
229
|
-
title: Create feature
|
|
230
|
-
description: >
|
|
231
|
-
Create a feature via spur feature create, or select an existing feature id.
|
|
232
|
-
The agent writes the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt and body-only
|
|
233
|
-
intent artifacts .spur/run/${vars.__runId}-idea-goal.md / -idea-scope.md. Goal carries
|
|
234
|
-
concise intent only; Scope carries explicit in/out boundaries — task breakdowns and
|
|
235
|
-
checklists never enter Goal. Shell actions then persist both sections through
|
|
236
|
-
`spur feature update --section Goal|Scope --from-file ...`; a missing/empty artifact
|
|
237
|
-
stops the state. Prefer the enhanced idea from .spur/run/${vars.__runId}-idea-eval-report.md as
|
|
238
|
-
context; do not overwrite vars.idea. The operator's verbatim ask of record is
|
|
239
|
-
.spur/run/${vars.__runId}-idea-input.md (persisted at start; authoritative over any
|
|
240
|
-
paraphrase).
|
|
177
|
+
description: "Create a feature via `spur feature create` (or select an existing id). The agent writes the feature id plus body-only Goal/Scope intent artifacts; the shell persists both sections through `spur feature update --from-file` and fails closed on an empty artifact. Goal carries intent only — task breakdowns and checklists never enter it. The operator's verbatim ask of record stays .spur/run/${vars.__runId}-idea-input.md."
|
|
241
178
|
onEnter:
|
|
242
179
|
- kind: agent.run
|
|
243
180
|
options:
|
|
244
181
|
agent: ${vars.planningAgent}
|
|
245
182
|
input: 'Create a feature for the idea. The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md — the idea argument persisted verbatim at start; treat it as the authoritative ask. Read .spur/run/${vars.__runId}-idea-eval-report.md if present for the enhanced idea and scores. Use ''spur feature create "<name>" --json'' to create it; with --json the feature id is returned under the .ref.id envelope (n), not a top-level .id — read it from there. Write the feature id to .spur/run/${vars.__runId}-idea-feature-id.txt. If an existing feature is appropriate, use its id instead. Also write two body-only intent artifacts: .spur/run/${vars.__runId}-idea-goal.md with concise Goal intent only (a short statement of what the feature achieves; never task breakdowns, checklists, or how-to steps), and .spur/run/${vars.__runId}-idea-scope.md with explicit in-scope and out-of-scope boundary bullets.'
|
|
246
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
247
183
|
role: planner
|
|
248
|
-
# B7 R6 (0894): declared fresh policy (planner default).
|
|
249
184
|
session: fresh
|
|
250
185
|
expectFile: .spur/run/${vars.__runId}-idea-feature-id.txt
|
|
251
|
-
# Intent artifacts this stage writes; the shell below fails closed when either is
|
|
252
|
-
#
|
|
186
|
+
# Intent artifacts this stage writes; the shell below fails closed when either is missing,
|
|
187
|
+
# so declaring them adds the work-item contract, not a new precondition.
|
|
253
188
|
expectFiles:
|
|
254
189
|
- .spur/run/${vars.__runId}-idea-goal.md
|
|
255
190
|
- .spur/run/${vars.__runId}-idea-scope.md
|
|
@@ -258,9 +193,8 @@ states:
|
|
|
258
193
|
options:
|
|
259
194
|
path: .spur/run/${vars.__runId}-idea-feature-id.txt
|
|
260
195
|
var: featureId
|
|
261
|
-
# 1007 R2: one shell persists both sections —
|
|
262
|
-
#
|
|
263
|
-
# same && fail-fast semantics).
|
|
196
|
+
# 1007 R2: one shell persists both sections — they share the artifact precondition, so the two
|
|
197
|
+
# update shells merged (original commands in order, same && fail-fast semantics).
|
|
264
198
|
- kind: shell
|
|
265
199
|
options:
|
|
266
200
|
command: >-
|
|
@@ -271,25 +205,7 @@ states:
|
|
|
271
205
|
$spurBin feature update "$featureId" --section Scope --from-file .spur/run/$__runId-idea-scope.md
|
|
272
206
|
|
|
273
207
|
- id: ac-generate
|
|
274
|
-
|
|
275
|
-
phase: define
|
|
276
|
-
phaseTitle: "Define feature"
|
|
277
|
-
title: Generate AC
|
|
278
|
-
description: >
|
|
279
|
-
Generate acceptance criteria per ac-style-guide.md. Write AC scenarios to the
|
|
280
|
-
feature file via `spur feature update --section "Acceptance Criteria"
|
|
281
|
-
--from-file`. The agent authors R-numbered Gherkin scenarios tied to the
|
|
282
|
-
design summary into a captured file; the shell step verifies the file is
|
|
283
|
-
non-empty, writes it through the CLI, and writes a completion sentinel
|
|
284
|
-
(.spur/run/${vars.__runId}-idea-ac-done.txt). AC quality is MEASURED once at this
|
|
285
|
-
boundary by the deterministic `idea-ac-check` command.gate below (task 0769): a failing
|
|
286
|
-
check records FAIL and routes through the capped retry loop via guards that consume the
|
|
287
|
-
recorded result — it never fails the run (the engine's default onError policy is `fail`,
|
|
288
|
-
so an in-action check failure would kill the run before the retry edges are evaluated).
|
|
289
|
-
Requirement coverage is MEASURED by the same gate (1004 R2): the `idea-ac-check` args
|
|
290
|
-
carry `--inventory .spur/run/${vars.__runId}-idea-eval-report.md`, so uncovered
|
|
291
|
-
inventory items record FAIL on `idea-ac-check.status` and route through the same capped
|
|
292
|
-
retry loop via the guards below.
|
|
208
|
+
description: "Author R-numbered BDD scenarios tied to the requirement inventory and persist them through `spur feature update --section \"Acceptance Criteria\" --from-file`. AC quality and requirement coverage are MEASURED once at this boundary by the `idea-ac-check` command.gate (0769; 1004 R2 folds coverage in via --inventory): a FAIL is recorded, never a run abort, so the capped retry edges can route it."
|
|
293
209
|
onEnter:
|
|
294
210
|
- kind: shell
|
|
295
211
|
options:
|
|
@@ -298,20 +214,14 @@ states:
|
|
|
298
214
|
options:
|
|
299
215
|
agent: ${vars.planningAgent}
|
|
300
216
|
input: "Generate acceptance criteria for feature ${vars.featureId}. The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md — idea persisted verbatim at start; authoritative. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Tie every scenario to the `## Requirement inventory` of .spur/run/${vars.__runId}-idea-eval-report.md: under each `Scenario:` heading add a comment `# covers: I1, I3` listing covered ids — every non-`[deferred: ...]` item needs at least one covering scenario. Two `spur task check` rules bind AC text: (1) AC bullets copy scenario titles verbatim — the title is the byte-identical identity key of its bullet; (2) gate-language words (HITL, approval/approved, merged/merge event, content-gate, GATED, capstone standalone) are forbidden in titles, bodies, and enum values (L4.gate-language) — rephrase around them. Output only the Acceptance Criteria section body (gherkin fence included), no commentary, no heading."
|
|
301
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
302
217
|
role: planner
|
|
303
|
-
# B7 R6 (0894): declared fresh policy (planner default).
|
|
304
218
|
session: fresh
|
|
305
219
|
answerFile: .spur/run/${vars.__runId}-idea-ac-content.md
|
|
306
220
|
expectFile: .spur/run/${vars.__runId}-idea-ac-content.md
|
|
307
221
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
308
|
-
# Empty capture is a RETRYABLE outcome, not a run abort
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
# the default `fail` policy before the capped retry edges were ever evaluated,
|
|
312
|
-
# contradicting this state's own contract. Empty now falls through with exit 0
|
|
313
|
-
# so the recorded FAIL routes it back here (cap: 3). A failing CLI *write* still
|
|
314
|
-
# exits non-zero and aborts — that one is not retryable.
|
|
222
|
+
# Empty capture is a RETRYABLE outcome, not a run abort: `answerFile` always creates the file,
|
|
223
|
+
# so an empty answer falls through with exit 0 and the recorded FAIL routes it back here (cap
|
|
224
|
+
# 3). A failing CLI *write* still exits non-zero and aborts — that one is not retryable.
|
|
315
225
|
- kind: shell
|
|
316
226
|
options:
|
|
317
227
|
command: >-
|
|
@@ -320,9 +230,8 @@ states:
|
|
|
320
230
|
else
|
|
321
231
|
exit 0;
|
|
322
232
|
fi
|
|
323
|
-
# 0769: ONE measured check at the AC author/revise boundary. `softFail: true` keeps a
|
|
324
|
-
#
|
|
325
|
-
# recorded result from the run-scoped status file and never re-run the CLI.
|
|
233
|
+
# 0769: ONE measured check at the AC author/revise boundary. `softFail: true` keeps a FAIL
|
|
234
|
+
# routable (retry loop / failed) instead of aborting.
|
|
326
235
|
- kind: command.gate
|
|
327
236
|
options:
|
|
328
237
|
id: idea-ac-check
|
|
@@ -331,14 +240,11 @@ states:
|
|
|
331
240
|
resultFile: .spur/run/${vars.__runId}-idea-ac-check.status
|
|
332
241
|
softFail: true
|
|
333
242
|
timeoutMs: 120000
|
|
334
|
-
# 0945 R2: derive the route facts ONCE at this boundary (0769 pattern)
|
|
335
|
-
# named files instead of re-deriving design×needs_design inline.
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
# coverage folds into idea-ac-check via --inventory).
|
|
340
|
-
# (warn) 7 commands: two three-way `test && printf || printf` derivations over captured
|
|
341
|
-
# signal files (ADR-115); no model hop, no CLI re-run.
|
|
243
|
+
# 0945 R2: derive the route facts ONCE at this boundary (0769 pattern) so guards read these
|
|
244
|
+
# named files instead of re-deriving design×needs_design inline. Missing/corrupt needs-design
|
|
245
|
+
# JSON fails safe to `design`; readiness is PASS only when the recorded check is PASS (1004 R2:
|
|
246
|
+
# coverage folds into idea-ac-check via --inventory). (warn) 7 commands: two three-way
|
|
247
|
+
# `test && printf || printf` derivations over captured signal files (ADR-115); no model hop.
|
|
342
248
|
- kind: shell
|
|
343
249
|
options:
|
|
344
250
|
command: >-
|
|
@@ -350,70 +256,8 @@ states:
|
|
|
350
256
|
printf 'PASS\n' > .spur/run/$__runId-idea-ac-ready.status ||
|
|
351
257
|
printf 'FAIL\n' > .spur/run/$__runId-idea-ac-ready.status; }
|
|
352
258
|
|
|
353
|
-
- id: feature-check
|
|
354
|
-
display:
|
|
355
|
-
phase: define
|
|
356
|
-
phaseTitle: "Define feature"
|
|
357
|
-
title: Check feature
|
|
358
|
-
description: >
|
|
359
|
-
HITL gate over the recorded `idea-ac-check` result (measured at the ac-generate
|
|
360
|
-
boundary — this gate never re-runs the CLI). Deliberately NOT --strict: before
|
|
361
|
-
decompose/batch-create a feature has scenarios but zero linked tasks, which
|
|
362
|
-
emits L4.orphan-scenarios at severity `warning`; --strict would elevate that
|
|
363
|
-
to a failure, making the success edges unreachable and looping ac-generate
|
|
364
|
-
into `failed`. Malformed AC still blocks via the error-severity L3 BDD
|
|
365
|
-
checks. Strict coverage belongs at the shippable gate, not here. The recorded
|
|
366
|
-
result is objective (schema/AC validation) and auto-routable — under profile=auto
|
|
367
|
-
the transition guards route directly from ac-generate to the appropriate next
|
|
368
|
-
state, so this state is only entered in interactive mode. On failure, the retry
|
|
369
|
-
cap routes back to ac-generate (≤3 retries) or escalates to failed.
|
|
370
|
-
Requirement coverage (1004 R2) folds into the recorded `idea-ac-check` result via the
|
|
371
|
-
gate's `--inventory` flag: a FAIL there can mean some inventory items have no covering
|
|
372
|
-
scenario — answer no to route back to ac-generate for revision if needed.
|
|
373
|
-
onEnter:
|
|
374
|
-
- kind: hitl.confirm
|
|
375
|
-
options:
|
|
376
|
-
prompt: "Feature check for ${vars.featureId}. Review the AC and confirm to proceed? Recorded check status: $(cat .spur/run/${vars.__runId}-idea-ac-check.status 2>/dev/null || echo unknown) (.spur/run/${vars.__runId}-idea-ac-check.status — includes inventory-coverage findings, 1004 R2). (Failures route back to ac-generate for revision, capped at 3 retries.)"
|
|
377
|
-
# Interactive always prompts the operator; profile=auto answers from the AC
|
|
378
|
-
# authoring stage's recorded evidence (1236 R1/R2). No `pause: true` — an
|
|
379
|
-
# evidence binding cannot live in a paused state (0911 D2), and the responder
|
|
380
|
-
# is what asks the operator now.
|
|
381
|
-
decision:
|
|
382
|
-
mode: evidence
|
|
383
|
-
statusVar: featureCheckStatus
|
|
384
|
-
evidenceNodes:
|
|
385
|
-
- ac-generate
|
|
386
|
-
# A deferred/absent answer is the denial today's headless responder gave.
|
|
387
|
-
- kind: shell
|
|
388
|
-
options:
|
|
389
|
-
command: >-
|
|
390
|
-
mkdir -p .spur/run; a="$__hitlAnswer"; [ -n "$a" ] || a=no;
|
|
391
|
-
printf '%s\n' "$a" > ".spur/run/$__runId-idea-feature-check-answer.txt"
|
|
392
|
-
- kind: file.read.into-var
|
|
393
|
-
options:
|
|
394
|
-
path: .spur/run/${vars.__runId}-idea-feature-check-answer.txt
|
|
395
|
-
var: __hitlAnswer
|
|
396
|
-
# 1007 R2: the former post-confirm route-writer duplicate is deleted — ac-generate's
|
|
397
|
-
# writer is the single writer (0945 R2 pin updated); its inputs (design var, needs-design
|
|
398
|
-
# JSON, recorded ac-check status) cannot change across hitl.confirm.
|
|
399
|
-
|
|
400
259
|
- id: system-design
|
|
401
|
-
|
|
402
|
-
phase: design
|
|
403
|
-
phaseTitle: Design
|
|
404
|
-
title: System design
|
|
405
|
-
description: >
|
|
406
|
-
Dispatch sp:sys-architecture to produce the system design. This state runs
|
|
407
|
-
only when needs_design=true (design=auto signal path). The agent creates ADR
|
|
408
|
-
entries, architecture updates, and design satellites through constitution rules.
|
|
409
|
-
A run-scoped design-review artifact (.spur/run/${vars.__runId}-idea-design-review.md)
|
|
410
|
-
with fixed headings `## Proposed design`, `## Operator feedback`, and `## Reconciliation`
|
|
411
|
-
carries operator rejection feedback back into this state: on the first pass the agent
|
|
412
|
-
fills `## Proposed design`; on retry (operator feedback present) it revises the design,
|
|
413
|
-
records the reconciliation, and updates invalidated Acceptance Criteria through
|
|
414
|
-
`spur feature update` before design can exit. Design quality is then MEASURED once at
|
|
415
|
-
this boundary by the deterministic `idea-design-check` command.gate (task 0769). The
|
|
416
|
-
design-approval state follows (taste gate).
|
|
260
|
+
description: "Dispatch sp:sys-architecture (runs only on the needs_design signal) to produce ADR entries, architecture updates and design satellites. The run-scoped design-review artifact (.spur/run/${vars.__runId}-idea-design-review.md) with fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation` carries operator rejection feedback into a revision pass, which reconciles invalidated AC through `spur feature update`. Design quality is MEASURED once at this boundary by the `idea-design-check` command.gate (0769)."
|
|
417
261
|
onEnter:
|
|
418
262
|
- kind: shell
|
|
419
263
|
options:
|
|
@@ -425,23 +269,18 @@ states:
|
|
|
425
269
|
options:
|
|
426
270
|
agent: ${vars.planningAgent}
|
|
427
271
|
input: 'The operator''s ask of record is .spur/run/${vars.__runId}-idea-input.md (idea persisted verbatim at start; authoritative). Run sp:sys-architecture for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and .spur/run/${vars.__runId}-idea-design-review.md. Produce ADR entries, architecture updates, and design satellites (docs/design/<slug>.md) per the constitution edit rules; never write task or feature corpus files directly. Design-review contract (fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): first pass — write the proposed summary under `## Proposed design`, leave `## Operator feedback` empty; retry after operator feedback — revise the design/ADR artifacts, document changes under `## Reconciliation`, and when feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file, persist via `$spurBin feature update "$featureId" --section "Acceptance Criteria" --from-file <file>`.'
|
|
428
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
429
272
|
role: planner
|
|
430
|
-
# B7 R6 (0894): declared fresh policy (planner default).
|
|
431
273
|
session: fresh
|
|
432
274
|
expectFile: .spur/run/${vars.__runId}-idea-design-review.md
|
|
433
275
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
434
|
-
# expectFile proves existence only
|
|
435
|
-
#
|
|
436
|
-
# must carry non-whitespace content before design can exit to approval/decompose.
|
|
276
|
+
# expectFile proves existence only and the skeleton above pre-creates the file, so an agent
|
|
277
|
+
# no-op would pass it (0515 P3-2). Fail closed on the section's content.
|
|
437
278
|
- kind: shell
|
|
438
279
|
options:
|
|
439
280
|
command: >-
|
|
440
281
|
REVIEW=".spur/run/$__runId-idea-design-review.md" &&
|
|
441
282
|
awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' "$REVIEW" | grep -q '[^[:space:]]'
|
|
442
|
-
# 0769: ONE measured check at the design author/revise boundary — after this pass's
|
|
443
|
-
# design writes (including AC reconciliation). softFail keeps FAIL routable; sibling
|
|
444
|
-
# guards consume the recorded result and never re-run the CLI.
|
|
283
|
+
# 0769: ONE measured check at the design author/revise boundary — after this pass's writes.
|
|
445
284
|
- kind: command.gate
|
|
446
285
|
options:
|
|
447
286
|
id: idea-design-check
|
|
@@ -452,25 +291,7 @@ states:
|
|
|
452
291
|
timeoutMs: 120000
|
|
453
292
|
|
|
454
293
|
- id: design-approval
|
|
455
|
-
|
|
456
|
-
phase: design
|
|
457
|
-
phaseTitle: Design
|
|
458
|
-
title: Approve design
|
|
459
|
-
description: >
|
|
460
|
-
HITL taste gate. The operator reviews the system design and approves or
|
|
461
|
-
rejects it. Routed around only when profile=auto AND vars.design_approved=true
|
|
462
|
-
(CLI --auto) AND the recorded design check PASSes; a FAIL still pauses here.
|
|
463
|
-
|
|
464
|
-
R7 (0433): a headless `no` persisted by DefaultHitlResponder would loop
|
|
465
|
-
design-approval -> system-design -> design-approval, burning unbounded
|
|
466
|
-
agent passes. The onEnter shell increments a reject counter; the `no`
|
|
467
|
-
edge to system-design is capped at 1 revise, after which the run
|
|
468
|
-
terminates as `failed` naming this gate.
|
|
469
|
-
|
|
470
|
-
Rejection must carry operator feedback: before answering `no`, the operator
|
|
471
|
-
records the concrete issue(s) in the run-scoped design-review artifact
|
|
472
|
-
(.spur/run/${vars.__runId}-idea-design-review.md) under `## Operator feedback` —
|
|
473
|
-
the revision pass reads it to reconcile invalidated AC before re-approval.
|
|
294
|
+
description: "HITL taste gate. Routed around only when profile=auto AND vars.design_approved=true AND the recorded design check PASSes; a FAIL still pauses here. R7 (0433): the `no` edge back to system-design is capped at 1 revise (the onEnter counter), after which the run ends failed naming this gate. Rejection must carry operator feedback in the review artifact under `## Operator feedback`. D66 1250 R2: an approved design whose recorded check FAILS routes to ac-generate (the AC revision state) — the former feature-check hop is deleted."
|
|
474
295
|
onEnter:
|
|
475
296
|
- kind: shell
|
|
476
297
|
options:
|
|
@@ -478,8 +299,6 @@ states:
|
|
|
478
299
|
- kind: hitl.confirm
|
|
479
300
|
options:
|
|
480
301
|
prompt: "Review the system design for feature ${vars.featureId}. Approve to proceed to decomposition? To reject, first record your feedback in .spur/run/${vars.__runId}-idea-design-review.md under the `## Operator feedback` heading, then answer no."
|
|
481
|
-
# Interactive always prompts the operator; profile=auto answers from the
|
|
482
|
-
# system-design stage's recorded evidence (1236 R1/R2).
|
|
483
302
|
decision:
|
|
484
303
|
mode: evidence
|
|
485
304
|
statusVar: designApprovalStatus
|
|
@@ -497,18 +316,7 @@ states:
|
|
|
497
316
|
var: __hitlAnswer
|
|
498
317
|
|
|
499
318
|
- id: decompose
|
|
500
|
-
|
|
501
|
-
phase: decompose
|
|
502
|
-
phaseTitle: Decompose
|
|
503
|
-
title: Decompose into tasks
|
|
504
|
-
description: >
|
|
505
|
-
Dispatch sp:spec-decomposition with the brainstorm artifact, feature AC, and
|
|
506
|
-
design doc as input. The agent produces a task-batch JSON file at
|
|
507
|
-
.spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json,
|
|
508
|
-
plus the private task-order sidecar .spur/run/${vars.__runId}-idea-task-order.json
|
|
509
|
-
(a JSON array of { name, depends_on_names[] }; `[]` when no ordering exists).
|
|
510
|
-
A post-agent shell action validates the sidecar shape before decomposition
|
|
511
|
-
can exit — missing or ambiguous title-to-WBS resolution fails before handoff.
|
|
319
|
+
description: "Dispatch sp:spec-decomposition with the brainstorm artifact, feature AC and design doc. The agent produces the task-batch JSON (.spur/run/${vars.__runId}-idea-task-batch.json, validated against task-batch.schema.json) plus the private task-order sidecar (.spur/run/${vars.__runId}-idea-task-order.json — an array of { name, depends_on_names[] }; `[]` when no ordering exists). The post-agent shell validates the sidecar shape fail-closed."
|
|
512
320
|
onEnter:
|
|
513
321
|
- kind: shell
|
|
514
322
|
options:
|
|
@@ -517,19 +325,14 @@ states:
|
|
|
517
325
|
options:
|
|
518
326
|
agent: ${vars.planningAgent}
|
|
519
327
|
input: "The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea argument persisted verbatim at start; authoritative). Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
|
|
520
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
521
328
|
role: planner
|
|
522
|
-
# B7 R6 (0894): declared fresh policy (planner default).
|
|
523
329
|
session: fresh
|
|
524
330
|
expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
|
|
525
331
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
526
|
-
# R1 (0518): the
|
|
527
|
-
#
|
|
528
|
-
#
|
|
529
|
-
#
|
|
530
|
-
# sidecar can never silently skip `task deps` for an unlisted item (`[]` is valid).
|
|
531
|
-
# (warn) stays inline by design: one jq predicate over two run-scoped files; splitting
|
|
532
|
-
# it into a script would hide the exact fail-closed contract this guard pins (0824).
|
|
332
|
+
# R1 (0518): the sidecar is the ordering contract for handoff-finalize. Fail closed on the
|
|
333
|
+
# array shape, unique names on both sides, and BIDIRECTIONAL coverage (F2, 0518 verify) — a
|
|
334
|
+
# partial sidecar must never silently skip `task deps` for an unlisted item (`[]` is valid).
|
|
335
|
+
# (warn) stays inline by design: one jq predicate over two run-scoped files (0824).
|
|
533
336
|
- kind: shell
|
|
534
337
|
options:
|
|
535
338
|
command: >-
|
|
@@ -544,58 +347,10 @@ states:
|
|
|
544
347
|
(((map(.depends_on_names // []) | flatten | unique) - ($b[0] | map(.name))) | length == 0)
|
|
545
348
|
' "$ORDER" >/dev/null
|
|
546
349
|
|
|
547
|
-
- id: batch-create
|
|
548
|
-
display:
|
|
549
|
-
phase: decompose
|
|
550
|
-
phaseTitle: Decompose
|
|
551
|
-
title: Approve task batch
|
|
552
|
-
description: >
|
|
553
|
-
HITL gate: runs spur task batch-create with the batch JSON from the
|
|
554
|
-
decompose state. The check is objective (schema validation) and
|
|
555
|
-
auto-routable — under profile=auto the transition guards route directly
|
|
556
|
-
from decompose to the appropriate next state, so this state is only
|
|
557
|
-
entered in interactive mode. On failure, the retry cap routes back to
|
|
558
|
-
decompose (≤3 retries) or escalates to failed.
|
|
559
|
-
onEnter:
|
|
560
|
-
- kind: hitl.confirm
|
|
561
|
-
options:
|
|
562
|
-
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.)"
|
|
563
|
-
# Interactive always prompts the operator; profile=auto answers from the
|
|
564
|
-
# decompose stage's recorded evidence (1236 R1/R2).
|
|
565
|
-
decision:
|
|
566
|
-
mode: evidence
|
|
567
|
-
statusVar: batchCreateStatus
|
|
568
|
-
evidenceNodes:
|
|
569
|
-
- decompose
|
|
570
|
-
# A deferred/absent answer is the denial today's headless responder gave.
|
|
571
|
-
- kind: shell
|
|
572
|
-
options:
|
|
573
|
-
command: >-
|
|
574
|
-
mkdir -p .spur/run; a="$__hitlAnswer"; [ -n "$a" ] || a=no;
|
|
575
|
-
printf '%s\n' "$a" > ".spur/run/$__runId-idea-batch-create-answer.txt"
|
|
576
|
-
- kind: file.read.into-var
|
|
577
|
-
options:
|
|
578
|
-
path: .spur/run/${vars.__runId}-idea-batch-create-answer.txt
|
|
579
|
-
var: __hitlAnswer
|
|
580
|
-
|
|
581
350
|
- id: batch-create-run
|
|
582
|
-
|
|
583
|
-
phase: decompose
|
|
584
|
-
phaseTitle: Decompose
|
|
585
|
-
title: Create tasks
|
|
586
|
-
description: >
|
|
587
|
-
Executes task batch creation exactly once per decomposition attempt. The
|
|
588
|
-
side effect lives in onEnter and records a sentinel; transition guards only
|
|
589
|
-
inspect sentinel/retry state. The `--json` result is captured atomically in
|
|
590
|
-
.spur/run/${vars.__runId}-idea-batch-create-result.json (temp file + mv); the done
|
|
591
|
-
sentinel is written only after the JSON parses and `created == (.wbs | length)`,
|
|
592
|
-
so handoff-finalize can zip batch names to the returned WBS list. Any failure
|
|
593
|
-
(CLI error or malformed result) writes the failed sentinel, preserving the
|
|
594
|
-
existing retry behavior.
|
|
351
|
+
description: "Executes task batch creation exactly once per decomposition attempt. The side effect records a sentinel; guards only inspect sentinel/retry state. The `--json` result is captured atomically (.spur/run/${vars.__runId}-idea-batch-create-result.json, temp file + mv) and the done sentinel is written only after the JSON parses and `created == (.wbs | length)`, so handoff-finalize can zip batch names to the returned WBS list. Any failure writes the failed sentinel, preserving the retry behavior. D66 1250 R2: every profile reaches this state directly from decompose — the former `batch-create` approval state is deleted."
|
|
595
352
|
onEnter:
|
|
596
|
-
# (e) idempotent batch creation: sentinel + temp/mv atomicity stay inline (0824)
|
|
597
|
-
# the jq verdict check keeps the done sentinel truthful so handoff-finalize can
|
|
598
|
-
# zip batch names to the returned WBS list.
|
|
353
|
+
# (e) idempotent batch creation: sentinel + temp/mv atomicity stay inline (0824).
|
|
599
354
|
- kind: shell
|
|
600
355
|
options:
|
|
601
356
|
command: >-
|
|
@@ -612,34 +367,17 @@ states:
|
|
|
612
367
|
fi
|
|
613
368
|
|
|
614
369
|
- id: ready-prepare
|
|
615
|
-
|
|
616
|
-
phase: handoff
|
|
617
|
-
phaseTitle: "Hand off"
|
|
618
|
-
title: Prepare ready set
|
|
619
|
-
description: >
|
|
620
|
-
Ready-by-default preparation (0788): the planning owner applies the
|
|
621
|
-
ready-refinement checklist to each created task (requirements, design,
|
|
622
|
-
plan, ac, decisions, dependencies, premises) so the deterministic task
|
|
623
|
-
check passes, then writes the run-scoped ready evidence sidecar
|
|
624
|
-
(.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs,
|
|
625
|
-
status (ready|failed|skipped), planningDigest, and seven check rows,
|
|
626
|
-
with the premises row carrying tree-verified path:line citations
|
|
627
|
-
(0947). Handoff-finalize verifies presence, digest, feature binding and
|
|
628
|
-
checklist evidence before recommending auto runall; missing or failing
|
|
629
|
-
evidence never fails the run — it degrades the recommendation to
|
|
630
|
-
ready-depth refineall.
|
|
370
|
+
description: "Ready-by-default preparation (0788): the planning owner applies the ready-refinement checklist to each created task (requirements, design, plan, ac, decisions, dependencies, premises) so the deterministic task check passes, then writes the run-scoped ready evidence sidecar (.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs, status (ready|failed|skipped), planningDigest, and seven check rows, the premises row carrying tree-verified path:line citations (0947). Missing or failing evidence never fails the run — it degrades the handoff recommendation to ready-depth refineall."
|
|
631
371
|
onEnter:
|
|
632
372
|
- kind: agent.run
|
|
633
373
|
options:
|
|
634
374
|
agent: ${vars.planningAgent}
|
|
635
|
-
# Declared Layer-1 role (0538 R2), same planner executor as decompose.
|
|
636
375
|
role: planner
|
|
637
|
-
# B7 R6 (0894): declared fresh policy (planner default).
|
|
638
376
|
session: fresh
|
|
639
377
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
640
|
-
# (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the
|
|
641
|
-
#
|
|
642
|
-
#
|
|
378
|
+
# (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the project's
|
|
379
|
+
# own computePlanningDigest), so the prompt pins the skill reference; artifacts are gated
|
|
380
|
+
# by answerFile/expectFile.
|
|
643
381
|
input: >-
|
|
644
382
|
The operator's ask of record is .spur/run/${vars.__runId}-idea-input.md (the idea
|
|
645
383
|
argument persisted verbatim at start; authoritative). Run the ready-prepare stage
|
|
@@ -651,12 +389,10 @@ states:
|
|
|
651
389
|
cites at least one verified path:line (handoff-finalize lints it; task 0947).
|
|
652
390
|
answerFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
|
|
653
391
|
expectFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
|
|
654
|
-
# Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
|
|
655
|
-
#
|
|
656
|
-
#
|
|
657
|
-
#
|
|
658
|
-
# (warn) stays inline by design: single jq shape predicate over the run-scoped
|
|
659
|
-
# sidecar; the failed/skipped rows are what degrade the recommendation (0824).
|
|
392
|
+
# Fail-closed shape validation (mirrors the order-sidecar guard). Absence is normalized to an
|
|
393
|
+
# empty sidecar so finalize degrades the recommendation to refineall instead of failing the
|
|
394
|
+
# run; a PRESENT but malformed sidecar fails here — it must not masquerade as evidence.
|
|
395
|
+
# (warn) stays inline by design: single jq shape predicate over the run-scoped sidecar (0824).
|
|
660
396
|
- kind: shell
|
|
661
397
|
options:
|
|
662
398
|
command: >-
|
|
@@ -665,387 +401,69 @@ states:
|
|
|
665
401
|
jq -e 'type == "object" and (.tasks | type == "array") and (all(.tasks[]; (.wbs | type == "string") and (.status == "ready" or .status == "failed" or .status == "skipped") and (.planningDigest | type == "string") and (.checks | type == "array") and (all(.checks[]; (.id | type == "string") and (.pass | type == "boolean") and (.evidence | type == "string")))))' "$READY" >/dev/null
|
|
666
402
|
|
|
667
403
|
- id: handoff-finalize
|
|
668
|
-
|
|
669
|
-
phase: handoff
|
|
670
|
-
phaseTitle: "Hand off"
|
|
671
|
-
title: Finalize handoff
|
|
672
|
-
description: >
|
|
673
|
-
Post-create finalization (0518): zip batch item names to the created WBS
|
|
674
|
-
values from the captured batch-create result (equal-length/unique-name
|
|
675
|
-
checks), apply every non-empty depends_on_names list through
|
|
676
|
-
`spur task deps`, refresh the feature roster, check every created task, and
|
|
677
|
-
write the run-scoped handoff report
|
|
678
|
-
(.spur/run/${vars.__runId}-idea-handoff.md) with exactly one next command.
|
|
679
|
-
A mapping or CLI error fails the run before terminal handoff; an unready
|
|
680
|
-
task is a successful planning outcome recorded as a refineall recommendation.
|
|
404
|
+
description: "Post-create finalization (0518): zip batch item names to the created WBS values (equal-length / unique-name checks), apply every non-empty depends_on_names list through `spur task deps`, refresh the feature roster, check every created task, and write the run-scoped handoff report (.spur/run/${vars.__runId}-idea-handoff.md) with exactly one next command. A mapping or CLI error fails the run before terminal handoff; an unready task is a successful planning outcome recorded as a refineall recommendation."
|
|
681
405
|
onEnter:
|
|
682
|
-
# (d) `spur feature handoff` owns finalization (0824; script-absorption PR-5
|
|
683
|
-
#
|
|
684
|
-
#
|
|
685
|
-
# zip/deps/refresh/check/report contract itself is pinned by
|
|
406
|
+
# (d) `spur feature handoff` owns finalization (0824; script-absorption PR-5 retired the
|
|
407
|
+
# idea-handoff plugin script). A nonzero exit fails the run (fail closed) instead of silently
|
|
408
|
+
# skipping finalization. The zip/deps/refresh/check/report contract is pinned by
|
|
686
409
|
# finalizeIdeaHandoff unit tests, not by this file.
|
|
687
410
|
- kind: shell
|
|
688
411
|
options:
|
|
689
412
|
command: '$spurBin feature handoff "$featureId" --run-id "$__runId" --no-logo'
|
|
413
|
+
|
|
690
414
|
- id: handoff
|
|
691
|
-
description:
|
|
692
|
-
Terminal — idea pipeline complete. Ordering applied, roster refreshed, and
|
|
693
|
-
the handoff report written at .spur/run/${vars.__runId}-idea-handoff.md:
|
|
694
|
-
feature id, task WBS list, per-task readiness, and exactly one recommended
|
|
695
|
-
next command (ready-depth refineall when any task is unready, else auto
|
|
696
|
-
runall). No task execution — the pipeline stops here.
|
|
415
|
+
description: "Terminal — idea pipeline complete. Ordering applied, roster refreshed, and the handoff report written at .spur/run/${vars.__runId}-idea-handoff.md: feature id, task WBS list, per-task readiness, and exactly one recommended next command (ready-depth refineall when any task is unready, else auto runall). No task execution — the pipeline stops here."
|
|
697
416
|
onEnter:
|
|
698
|
-
# 1007 R2: the
|
|
699
|
-
# (same path, the recommended-next-command report).
|
|
417
|
+
# 1007 R2: the run.artifact carries the handoff (the recommended-next-command report).
|
|
700
418
|
- kind: run.artifact
|
|
701
419
|
options:
|
|
702
420
|
path: .spur/run/${vars.__runId}-idea-handoff.md
|
|
703
421
|
artifactKind: idea-handoff
|
|
704
422
|
|
|
705
423
|
- id: cancelled
|
|
706
|
-
description: Terminal — pipeline cancelled by operator or error.
|
|
424
|
+
description: "Terminal — pipeline cancelled by operator or error."
|
|
707
425
|
|
|
708
426
|
- id: failed
|
|
709
|
-
description:
|
|
710
|
-
Terminal — pipeline failed after exhausting retry caps on cyclic edges
|
|
711
|
-
(feature-check → ac-generate or batch-create → decompose). The error
|
|
712
|
-
message in the run trace identifies which edge exceeded the cap.
|
|
427
|
+
description: "Terminal — the entry check failed, or a capped retry loop exhausted (ac-generate → ac-generate or batch-create-run → decompose). The recorded status file's first line names the reason; the terminal reason identifies the exhausted edge."
|
|
713
428
|
|
|
429
|
+
# ── Routing table ──
|
|
430
|
+
# Declaration order is load-bearing: the engine takes the first passing guard. start's PASS edge
|
|
431
|
+
# precedes its reporting failure edge; discovery/idea-eval's auto-accept edges precede their gates
|
|
432
|
+
# (0366 R4/R5) and every gate's discriminating edge precedes its always fallback. ac-generate routes
|
|
433
|
+
# on the derived route facts under EVERY profile (1250 R2) — no guard re-runs the CLI (0769).
|
|
714
434
|
transitions:
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
- from:
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
#
|
|
731
|
-
#
|
|
732
|
-
#
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
- from:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
- from:
|
|
749
|
-
|
|
750
|
-
description: profile=auto AND idea_approved=true AND eval recommends proceed/reshape — skip idea-eval taste gate.
|
|
751
|
-
guard:
|
|
752
|
-
kind: shell
|
|
753
|
-
options:
|
|
754
|
-
command: >-
|
|
755
|
-
test "$profile" = auto && test "$idea_approved" = true &&
|
|
756
|
-
grep -qxE 'proceed|reshape' .spur/run/$__runId-idea-recommendation.txt
|
|
757
|
-
- from: discovery
|
|
758
|
-
to: idea-eval
|
|
759
|
-
description: Discovery complete — gate on idea evaluation taste decision.
|
|
760
|
-
guard:
|
|
761
|
-
kind: always
|
|
762
|
-
|
|
763
|
-
# ── idea-eval: auto-skip (profile=auto + idea_approved) OR approve/reject HITL ──
|
|
764
|
-
# Declaration order: auto-skip FIRST (same pattern as system-design → design-approval).
|
|
765
|
-
- from: idea-eval
|
|
766
|
-
to: feature-create
|
|
767
|
-
description: profile=auto AND idea_approved=true AND eval recommends proceed/reshape — skip idea-eval taste gate.
|
|
768
|
-
guard:
|
|
769
|
-
kind: shell
|
|
770
|
-
options:
|
|
771
|
-
command: >-
|
|
772
|
-
test "$profile" = auto && test "$idea_approved" = true &&
|
|
773
|
-
grep -qxE 'proceed|reshape' .spur/run/$__runId-idea-recommendation.txt
|
|
774
|
-
- from: idea-eval
|
|
775
|
-
to: feature-create
|
|
776
|
-
description: Idea evaluation approved — create or select a feature.
|
|
777
|
-
guard:
|
|
778
|
-
kind: shell
|
|
779
|
-
options:
|
|
780
|
-
command: 'test "$__hitlAnswer" = yes'
|
|
781
|
-
- from: idea-eval
|
|
782
|
-
to: cancelled
|
|
783
|
-
terminalReason: cancelled
|
|
784
|
-
description: Idea evaluation rejected or cancelled — no feature created.
|
|
785
|
-
guard:
|
|
786
|
-
kind: shell
|
|
787
|
-
options:
|
|
788
|
-
command: 'test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'
|
|
789
|
-
|
|
790
|
-
# ── feature-create -> ac-generate ──
|
|
791
|
-
- from: feature-create
|
|
792
|
-
to: ac-generate
|
|
793
|
-
description: Feature created — generate acceptance criteria.
|
|
794
|
-
guard:
|
|
795
|
-
kind: always
|
|
796
|
-
|
|
797
|
-
# ── ac-generate: auto-skip (profile=auto) OR enter feature-check HITL gate (interactive) ──
|
|
798
|
-
# Declaration order: auto-skip guards tried FIRST (same pattern as task-pipeline review→verify
|
|
799
|
-
# and design-gen→handoff). Under profile=auto, the derived route facts — the ready file (both
|
|
800
|
-
# recorded checks PASS, 0887 R4) and the design route file (0945 R2 writer at the end of both
|
|
801
|
-
# ac-generate and feature-check onEnter) — route directly to the appropriate next state:
|
|
802
|
-
# guards never re-run the CLI or the checker and never re-derive the signals (task 0769).
|
|
803
|
-
# Under interactive, the
|
|
804
|
-
# always fallback enters the feature-check state whose onEnter hitl.confirm pauses for
|
|
805
|
-
# operator confirmation (coverage is surfaced in that prompt; the operator's answer governs).
|
|
806
|
-
#
|
|
807
|
-
# Auto-skip 1: pass + coverage + design route → system-design (design=auto + needs_design != false)
|
|
808
|
-
- from: ac-generate
|
|
809
|
-
to: system-design
|
|
810
|
-
description: "profile=auto, check passed, requirements covered, design route — run system design."
|
|
811
|
-
# 0945 R3: three predicates over derived facts — the ready file folds both recorded check
|
|
812
|
-
# statuses (PASS only when ac-check AND coverage are PASS), the route file folds
|
|
813
|
-
# design×needs_design (fail-safe design); nothing re-runs, nothing re-derives (0769).
|
|
814
|
-
guard:
|
|
815
|
-
kind: shell
|
|
816
|
-
options:
|
|
817
|
-
command: >-
|
|
818
|
-
test "$profile" = auto &&
|
|
819
|
-
test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
|
|
820
|
-
test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
|
|
821
|
-
# Auto-skip 2: pass + coverage + skip-design route → decompose
|
|
822
|
-
- from: ac-generate
|
|
823
|
-
to: decompose
|
|
824
|
-
description: "profile=auto, check passed, requirements covered, skip-design route — go directly to decompose."
|
|
825
|
-
# 0945 R3: mirror of the design-route guard over the same derived facts with route=skip;
|
|
826
|
-
# one captured signal file per fact keeps both routes symmetric (0769).
|
|
827
|
-
guard:
|
|
828
|
-
kind: shell
|
|
829
|
-
options:
|
|
830
|
-
command: >-
|
|
831
|
-
test "$profile" = auto &&
|
|
832
|
-
test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS &&
|
|
833
|
-
test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
|
|
834
|
-
# Auto-skip 3: check failed (incl. inventory coverage), retry < 3 → loop back to ac-generate (self-loop)
|
|
835
|
-
- from: ac-generate
|
|
836
|
-
to: ac-generate
|
|
837
|
-
description: "profile=auto, check failed (incl. inventory coverage), retry cap not reached — re-run ac-generate."
|
|
838
|
-
# (warn) 4 commands: one persistent multi-assignment line captures the check status and the retry
|
|
839
|
-
# count (with its 0 fallback); the profile gate folds into the retry test via `test -a`; the retry
|
|
840
|
-
# loop is the smallest honest formulation of cap<3 routing (0769).
|
|
841
|
-
guard:
|
|
842
|
-
kind: shell
|
|
843
|
-
options:
|
|
844
|
-
command: >-
|
|
845
|
-
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
|
|
846
|
-
test "$ac_status" != PASS && test "$profile" = auto -a "$retry" -lt 3
|
|
847
|
-
# Auto-skip 4: check failed (incl. inventory coverage), retry cap reached → escalate to failed
|
|
848
|
-
- from: ac-generate
|
|
849
|
-
to: failed
|
|
850
|
-
terminalReason: retry-exhausted
|
|
851
|
-
description: "profile=auto, check failed (incl. inventory coverage) after 3 retries — escalate to failed."
|
|
852
|
-
# (warn) 4 commands: mirror of the retry guard with cap>=3; keeping escalation and retry as one
|
|
853
|
-
# test-chain pair makes the cap boundary auditable in the diff (0769).
|
|
854
|
-
guard:
|
|
855
|
-
kind: shell
|
|
856
|
-
options:
|
|
857
|
-
command: >-
|
|
858
|
-
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)";
|
|
859
|
-
test "$ac_status" != PASS && test "$profile" = auto -a "$retry" -ge 3
|
|
860
|
-
# Interactive fallback: enter feature-check HITL gate
|
|
861
|
-
- from: ac-generate
|
|
862
|
-
to: feature-check
|
|
863
|
-
description: Interactive — enter feature-check HITL gate for operator confirmation.
|
|
864
|
-
guard:
|
|
865
|
-
kind: always
|
|
866
|
-
|
|
867
|
-
# ── feature-check: pass + design route -> system-design, pass + skip route -> decompose, fail -> ac-generate ──
|
|
868
|
-
# Declaration order matters: system-design route tried first, then skip route, then fail.
|
|
869
|
-
- from: feature-check
|
|
870
|
-
to: system-design
|
|
871
|
-
description: Feature check passed, design route — run system design.
|
|
872
|
-
# 0945 R3: three predicates over derived facts — the recorded ac-check status stays the
|
|
873
|
-
# conjunct here (parity: the operator's answer governs the interactive gate, 0887), the
|
|
874
|
-
# route file folds design×needs_design (fail-safe design); nothing re-runs (0769).
|
|
875
|
-
guard:
|
|
876
|
-
kind: shell
|
|
877
|
-
options:
|
|
878
|
-
command: >-
|
|
879
|
-
test "$__hitlAnswer" = yes &&
|
|
880
|
-
test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
|
|
881
|
-
test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design
|
|
882
|
-
- from: feature-check
|
|
883
|
-
to: decompose
|
|
884
|
-
description: Feature check passed, skip-design route — go directly to decompose.
|
|
885
|
-
# 0945 R3: mirror of the design-route guard over the same derived facts with route=skip.
|
|
886
|
-
guard:
|
|
887
|
-
kind: shell
|
|
888
|
-
options:
|
|
889
|
-
command: >-
|
|
890
|
-
test "$__hitlAnswer" = yes &&
|
|
891
|
-
test "$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" = PASS &&
|
|
892
|
-
test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip
|
|
893
|
-
- from: feature-check
|
|
894
|
-
to: ac-generate
|
|
895
|
-
description: "Feature check failed — revise AC (retry cap: 3)."
|
|
896
|
-
# (warn) 5 commands (named status capture + 4 conditions): HITL reject/failed-status OR-pair + captured retry count; the
|
|
897
|
-
# interactive retry mirror of the ac-generate cap<3 guard (0769).
|
|
898
|
-
guard:
|
|
899
|
-
kind: shell
|
|
900
|
-
options:
|
|
901
|
-
command: >-
|
|
902
|
-
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
903
|
-
(test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
|
|
904
|
-
- from: feature-check
|
|
905
|
-
to: failed
|
|
906
|
-
terminalReason: retry-exhausted
|
|
907
|
-
description: Feature check failed after 3 retries — escalate to failed.
|
|
908
|
-
# (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
|
|
909
|
-
guard:
|
|
910
|
-
kind: shell
|
|
911
|
-
options:
|
|
912
|
-
command: >-
|
|
913
|
-
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
914
|
-
(test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
|
|
915
|
-
- from: feature-check
|
|
916
|
-
to: cancelled
|
|
917
|
-
terminalReason: cancelled
|
|
918
|
-
description: Operator cancelled the feature-check gate.
|
|
919
|
-
guard:
|
|
920
|
-
kind: shell
|
|
921
|
-
options:
|
|
922
|
-
command: 'test "$__hitlAnswer" = cancel'
|
|
923
|
-
|
|
924
|
-
# ── system-design: -> design-approval (normal) OR -> decompose (auto + prior approval) ──
|
|
925
|
-
# Declaration order: auto-skip guard tried FIRST (like task-pipeline's review->verify pattern).
|
|
926
|
-
- from: system-design
|
|
927
|
-
to: decompose
|
|
928
|
-
description: profile=auto AND design_approved=true AND recorded design check passes — skip design approval gate.
|
|
929
|
-
guard:
|
|
930
|
-
kind: shell
|
|
931
|
-
options:
|
|
932
|
-
command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
|
|
933
|
-
- from: system-design
|
|
934
|
-
to: design-approval
|
|
935
|
-
description: System design done — gate on design approval.
|
|
936
|
-
guard:
|
|
937
|
-
kind: always
|
|
938
|
-
|
|
939
|
-
# ── design-approval -> decompose (after operator approval), AC-failure -> feature-check ──
|
|
940
|
-
- from: design-approval
|
|
941
|
-
to: decompose
|
|
942
|
-
description: Design approved and recorded design check passes — proceed to decomposition.
|
|
943
|
-
guard:
|
|
944
|
-
kind: shell
|
|
945
|
-
options:
|
|
946
|
-
command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'
|
|
947
|
-
- from: design-approval
|
|
948
|
-
to: feature-check
|
|
949
|
-
description: "Design approved but recorded design check fails — route back through the AC gate (revise AC, retry cap: 3)."
|
|
950
|
-
guard:
|
|
951
|
-
kind: shell
|
|
952
|
-
options:
|
|
953
|
-
command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'
|
|
954
|
-
- from: design-approval
|
|
955
|
-
to: system-design
|
|
956
|
-
description: "Design rejected - revise system design (cap: 1 revise)."
|
|
957
|
-
guard:
|
|
958
|
-
kind: shell
|
|
959
|
-
options:
|
|
960
|
-
command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -le 1'
|
|
961
|
-
- from: design-approval
|
|
962
|
-
to: failed
|
|
963
|
-
terminalReason: retry-exhausted
|
|
964
|
-
description: "Design rejected after 1 revise - design-approval gate exhausted (R7 0433)."
|
|
965
|
-
guard:
|
|
966
|
-
kind: shell
|
|
967
|
-
options:
|
|
968
|
-
command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -gt 1'
|
|
969
|
-
- from: design-approval
|
|
970
|
-
to: cancelled
|
|
971
|
-
terminalReason: cancelled
|
|
972
|
-
description: Operator cancelled design approval.
|
|
973
|
-
guard:
|
|
974
|
-
kind: shell
|
|
975
|
-
options:
|
|
976
|
-
command: 'test "$__hitlAnswer" = cancel'
|
|
977
|
-
|
|
978
|
-
# ── decompose: auto-skip (profile=auto) OR enter batch-create HITL gate (interactive) ──
|
|
979
|
-
- from: decompose
|
|
980
|
-
to: batch-create-run
|
|
981
|
-
description: "profile=auto — execute batch-create side effect."
|
|
982
|
-
guard:
|
|
983
|
-
kind: shell
|
|
984
|
-
options:
|
|
985
|
-
command: 'test "$profile" = auto'
|
|
986
|
-
# Interactive fallback: enter batch-create HITL gate
|
|
987
|
-
- from: decompose
|
|
988
|
-
to: batch-create
|
|
989
|
-
description: Interactive — enter batch-create HITL gate for operator confirmation.
|
|
990
|
-
guard:
|
|
991
|
-
kind: always
|
|
992
|
-
|
|
993
|
-
- from: batch-create
|
|
994
|
-
to: batch-create-run
|
|
995
|
-
description: Operator approved batch creation — execute side effect.
|
|
996
|
-
guard:
|
|
997
|
-
kind: shell
|
|
998
|
-
options:
|
|
999
|
-
command: 'test "$__hitlAnswer" = yes'
|
|
1000
|
-
- from: batch-create
|
|
1001
|
-
to: decompose
|
|
1002
|
-
description: Operator requested decomposition revisions.
|
|
1003
|
-
guard:
|
|
1004
|
-
kind: shell
|
|
1005
|
-
options:
|
|
1006
|
-
command: 'test "$__hitlAnswer" = no'
|
|
1007
|
-
- from: batch-create
|
|
1008
|
-
to: cancelled
|
|
1009
|
-
terminalReason: cancelled
|
|
1010
|
-
description: Operator cancelled batch creation.
|
|
1011
|
-
guard:
|
|
1012
|
-
kind: shell
|
|
1013
|
-
options:
|
|
1014
|
-
command: 'test "$__hitlAnswer" = cancel'
|
|
1015
|
-
|
|
1016
|
-
# ── batch-create-run: success -> ready-prepare, failure -> decompose (retry cap: 3), failure+cap -> failed ──
|
|
1017
|
-
- from: batch-create-run
|
|
1018
|
-
to: ready-prepare
|
|
1019
|
-
description: Batch created and result captured — apply ready-by-default preparation, then finalize the handoff.
|
|
1020
|
-
guard:
|
|
1021
|
-
kind: shell
|
|
1022
|
-
options:
|
|
1023
|
-
command: "test -f .spur/run/$__runId-idea-batch-create.done"
|
|
1024
|
-
# ── ready-prepare: -> handoff-finalize (always; evidence absence degrades to refineall, never fails the run) ──
|
|
1025
|
-
- from: ready-prepare
|
|
1026
|
-
to: handoff-finalize
|
|
1027
|
-
description: Ready evidence sidecar written (or normalized empty) — finalize the handoff report.
|
|
1028
|
-
guard:
|
|
1029
|
-
kind: always
|
|
1030
|
-
- from: batch-create-run
|
|
1031
|
-
to: decompose
|
|
1032
|
-
description: "Batch-create failed — revise decomposition (retry cap: 3)."
|
|
1033
|
-
guard:
|
|
1034
|
-
kind: shell
|
|
1035
|
-
options:
|
|
1036
|
-
command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'
|
|
1037
|
-
- from: batch-create-run
|
|
1038
|
-
to: failed
|
|
1039
|
-
terminalReason: retry-exhausted
|
|
1040
|
-
description: Batch-create failed after 3 retries — escalate to failed.
|
|
1041
|
-
guard:
|
|
1042
|
-
kind: shell
|
|
1043
|
-
options:
|
|
1044
|
-
command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'
|
|
1045
|
-
|
|
1046
|
-
# ── handoff-finalize: -> handoff (terminal) ──
|
|
1047
|
-
- from: handoff-finalize
|
|
1048
|
-
to: handoff
|
|
1049
|
-
description: Ordering applied, roster refreshed, handoff report written — terminal handoff.
|
|
1050
|
-
guard:
|
|
1051
|
-
kind: always
|
|
435
|
+
- {from: start, to: discovery, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-precheck-doctor.status 2>/dev/null)" = PASS'}}}
|
|
436
|
+
# The entry check's named reason (`FAIL: idea text is empty`, or the executor verdict) is the
|
|
437
|
+
# operator's one-place explanation for a run that never left start. `>&2` precedes the stderr
|
|
438
|
+
# suppression: redirections apply left to right, so the reverse order would send the line to
|
|
439
|
+
# /dev/null with it.
|
|
440
|
+
- {from: start, to: failed, terminalReason: failed-check, guard: {kind: shell, options: {command: 'head -1 .spur/run/$__runId-idea-precheck-doctor.status >&2 2>/dev/null; exit 0'}}}
|
|
441
|
+
- {from: discovery, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qx drop .spur/run/$__runId-idea-recommendation.txt'}}}
|
|
442
|
+
- {from: discovery, to: feature-create, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qxE "proceed|reshape" .spur/run/$__runId-idea-recommendation.txt'}}}
|
|
443
|
+
- {from: discovery, to: idea-eval, guard: {kind: always}}
|
|
444
|
+
- {from: idea-eval, to: feature-create, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$idea_approved" = true && grep -qxE "proceed|reshape" .spur/run/$__runId-idea-recommendation.txt'}}}
|
|
445
|
+
- {from: idea-eval, to: feature-create, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes'}}}
|
|
446
|
+
- {from: idea-eval, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no || test "$__hitlAnswer" = cancel'}}}
|
|
447
|
+
- {from: feature-create, to: ac-generate, guard: {kind: always}}
|
|
448
|
+
- {from: ac-generate, to: system-design, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = design'}}}
|
|
449
|
+
- {from: ac-generate, to: decompose, guard: {kind: shell, options: {command: 'test "$(cat .spur/run/$__runId-idea-ac-ready.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$__runId-idea-design-route.txt 2>/dev/null)" = skip'}}}
|
|
450
|
+
# (warn) 4 commands each: one persistent multi-assignment line captures the check status and the
|
|
451
|
+
# retry count (with its 0 fallback); retry and escalation stay one test-chain pair so the cap
|
|
452
|
+
# boundary is auditable in the diff (0769). The former `test "$profile" = auto` conjunct is gone.
|
|
453
|
+
- {from: ac-generate, to: ac-generate, guard: {kind: shell, options: {command: 'ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)"; test "$ac_status" != PASS && test "$retry" -lt 3'}}}
|
|
454
|
+
- {from: ac-generate, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)" retry="$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)"; test "$ac_status" != PASS && test "$retry" -ge 3'}}}
|
|
455
|
+
- {from: system-design, to: decompose, guard: {kind: shell, options: {command: 'test "$profile" = auto && test "$design_approved" = true && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'}}}
|
|
456
|
+
- {from: system-design, to: design-approval, guard: {kind: always}}
|
|
457
|
+
- {from: design-approval, to: decompose, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" = PASS'}}}
|
|
458
|
+
- {from: design-approval, to: ac-generate, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = yes && test "$(cat .spur/run/$__runId-idea-design-check.status 2>/dev/null)" != PASS'}}}
|
|
459
|
+
- {from: design-approval, to: system-design, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -le 1'}}}
|
|
460
|
+
- {from: design-approval, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = no && test "$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0)" -gt 1'}}}
|
|
461
|
+
- {from: design-approval, to: cancelled, terminalReason: cancelled, guard: {kind: shell, options: {command: 'test "$__hitlAnswer" = cancel'}}}
|
|
462
|
+
# D66 1250 R2: the sidecar guard above validates the batch deterministically, so the deleted
|
|
463
|
+
# `batch-create` approval state had no decision left to settle — every profile takes this edge.
|
|
464
|
+
- {from: decompose, to: batch-create-run, guard: {kind: always}}
|
|
465
|
+
- {from: batch-create-run, to: ready-prepare, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.done'}}}
|
|
466
|
+
- {from: batch-create-run, to: decompose, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -lt 3'}}}
|
|
467
|
+
- {from: batch-create-run, to: failed, terminalReason: retry-exhausted, guard: {kind: shell, options: {command: 'test -f .spur/run/$__runId-idea-batch-create.failed && test "$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0)" -ge 3'}}}
|
|
468
|
+
- {from: ready-prepare, to: handoff-finalize, guard: {kind: always}}
|
|
469
|
+
- {from: handoff-finalize, to: handoff, guard: {kind: always}}
|