@gobing-ai/spur 0.3.74 → 0.3.76
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/config.example.yaml +6 -0
- package/config/rules/README.md +1 -1
- package/config/rules/strict/runtime-boundaries.yaml +1 -0
- package/config/rules/typescript/no-eslint-suppressions.yaml +30 -0
- package/config/workflows/docs-pipeline.yaml +26 -1
- package/config/workflows/feature-dev.yaml +143 -102
- package/config/workflows/idea-pipeline.yaml +58 -11
- package/config/workflows/task-pipeline.yaml +76 -40
- package/config/workflows/wrapup-pipeline.yaml +183 -111
- package/package.json +9 -9
- package/plugins/sp/agents/expert-spur.md +7 -4
- package/plugins/sp/commands/dev-idea.md +1 -1
- package/plugins/sp/commands/dev-refineall.md +2 -2
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/dogfood-testing/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -3
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +5 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +3 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +3 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +23 -8
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -1
- package/schemas/spur-config.schema.json +4 -0
- package/spur.js +3675 -838
- package/web/_astro/{BoardApp.C60RJZRj.js → BoardApp.CHQ1lycZ.js} +92 -92
- package/web/_astro/BoardApp.DV9kx0wo.js +1 -0
- package/web/_astro/{TaskDetail.Guk8VuNu.js → TaskDetail.GKfQJ60c.js} +1 -1
- package/web/_astro/{arc.CAZtlWJM.js → arc.DWEtA3Tx.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.9-XbF_Tz.js → architectureDiagram-3BPJPVTR.DB42oWmP.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DC4WLx3L.js → blockDiagram-GPEHLZMM.rhv-zNQV.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.JgjUQMgG.js → c4Diagram-AAUBKEIU.Ci4-4VvY.js} +1 -1
- package/web/_astro/channel.BAI6xLeV.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.D_JFiXa-.js → chunk-2J33WTMH.Cc9veUgf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Bk_RxoeT.js → chunk-4BX2VUAB.Bec9c4eI.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.a_Rj2VxL.js → chunk-55IACEB6.DoV8S1iB.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.C2NoS1U6.js → chunk-727SXJPM.DwR-Qlyj.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.D1Xn6CNa.js → chunk-AQP2D5EJ.ND_a81WY.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BhJrSBsX.js → chunk-FMBD7UC4.Wv_jwG48.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.ByVGduYQ.js → chunk-ND2GUHAM.CXKXCMmp.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.B47Paunq.js → chunk-QZHKN3VN.nkaoNYQq.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BERMSD5C.js → classDiagram-4FO5ZUOK.cMQcVlQu.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BERMSD5C.js → classDiagram-v2-Q7XG4LA2.cMQcVlQu.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.O_rHGlhU.js → cose-bilkent-S5V4N54A.OaDJ7Mr2.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.qCW2GdNp.js → cynefin-OW5HDTMX.Chi8IphF.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.C5Y4lH_u.js → dagre-BM42HDAG.CzK2t_Fp.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DxZmRXxF.js → diagram-2AECGRRQ.DRvxlVS7.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.qSxZeYSK.js → diagram-5GNKFQAL.CnYvNdwA.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.-7vl3bXs.js → diagram-KO2AKTUF.CpLpMw5R.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BHVV-UH3.js → diagram-LMA3HP47.JTb78qUA.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DHAuW9sK.js → diagram-OG6HWLK6.Bk-1jDIb.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DMRLQwPN.js → erDiagram-TEJ5UH35.D8hN9GZq.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.B3EpPp_8.js → flowDiagram-I6XJVG4X.-6zQr6m5.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BgUrExXM.js → ganttDiagram-6RSMTGT7.DboLQ9ca.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CU-9yVN4.js → gitGraphDiagram-PVQCEYII.4tYvJKGR.js} +1 -1
- package/web/_astro/index.Dcr_8fiK.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CvVTCRLe.js → infoDiagram-5YYISTIA.Bd9rXpsB.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUzOfKPf.js → ishikawaDiagram-YF4QCWOH.CvMoaf67.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.BBTb7ziz.js → journeyDiagram-JHISSGLW.Ccy1CA7y.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CTQQr70i.js → kanban-definition-UN3LZRKU.0MaMqHNS.js} +1 -1
- package/web/_astro/{linear.CLKlJPiS.js → linear.CHXgcIbN.js} +1 -1
- package/web/_astro/{mermaid.core.BA4wFhLP.js → mermaid.core.Ca-kcelG.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.t1vG2l5e.js → mindmap-definition-RKZ34NQL.BUIDlHa0.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.C8zJhyTu.js → pieDiagram-4H26LBE5.2dX3CU1s.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DqYYvb7T.js → quadrantDiagram-W4KKPZXB.B3LBlRiv.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.lzfDZ9nI.js → requirementDiagram-4Y6WPE33.X12I2uNx.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.lm6N5ORM.js → sankeyDiagram-5OEKKPKP.BXohIHqx.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.E6_IV4Dj.js → sequenceDiagram-3UESZ5HK.C37ZIUzg.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bnv6Ok2p.js → stateDiagram-AJRCARHV.BRgz317z.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js → stateDiagram-v2-BHNVJYJU.7VYSXN9-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BhRgqSLa.js → timeline-definition-PNZ67QCA.BVNz_HiN.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.pjb5DMC9.js → vennDiagram-CIIHVFJN.CHVDkPX4.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CW20KUng.js → wardleyDiagram-YWT4CUSO.EQQ_qT9v.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.vX9_yuMl.js → xychartDiagram-2RQKCTM6.DrAT9WoP.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.ymfj1EHA.js +0 -1
- package/web/_astro/channel.Bxxe2Byc.js +0 -1
- package/web/_astro/index.BhvM3djt.css +0 -1
|
@@ -130,6 +130,12 @@ agent:
|
|
|
130
130
|
# agent: omp
|
|
131
131
|
# model: minimax/MiniMax-M3
|
|
132
132
|
# tier: cheap
|
|
133
|
+
# A disabled profile stays visible to `spur agent doctor` but never routes
|
|
134
|
+
# (no role, team, stage, or explicit selection). Flip back to enabled by
|
|
135
|
+
# removing the line or setting disabled: false.
|
|
136
|
+
# - name: retired-profile
|
|
137
|
+
# agent: omp
|
|
138
|
+
# disabled: true
|
|
133
139
|
- name: pi-dsv4-flash-volc
|
|
134
140
|
agent: pi
|
|
135
141
|
# tier: standard
|
package/config/rules/README.md
CHANGED
|
@@ -9,7 +9,7 @@ install or to ts-libs.
|
|
|
9
9
|
|
|
10
10
|
| Category | Dir | Purpose |
|
|
11
11
|
|---|---|---|
|
|
12
|
-
| `typescript` | `typescript/` | TypeScript tooling, output boundaries, biome-suppression ban, no `debugger` |
|
|
12
|
+
| `typescript` | `typescript/` | TypeScript tooling, output boundaries, biome/eslint-suppression ban, no `debugger` |
|
|
13
13
|
| `strict` | `strict/` | Opt-in strict rules (runtime boundaries, HTTP boundaries, structural) |
|
|
14
14
|
| `boundary` | `boundary/` | DB/DAO boundary enforcement |
|
|
15
15
|
| `structure` | `structure/` | File layout, protected files, no focused/skipped tests |
|
|
@@ -69,6 +69,7 @@ rules:
|
|
|
69
69
|
# Synchronous bootstrap & path resolution:
|
|
70
70
|
- "packages/config/src/bundled-config.ts" # sync bundledConfigRoot() directory walk
|
|
71
71
|
- "packages/config/src/loader.ts" # sync resolveConfigFile() / schema specifier lookup
|
|
72
|
+
- "packages/config/src/executor-update.ts" # atomic single-entry config update (task 0797): O_EXCL lock, fsync'd tmp+rename commit, lstat symlink refusal, mode preservation — ts-runtime FileSystem seam has no lstat/fsync/chmod/O_EXCL
|
|
72
73
|
- "packages/app/src/services/project-start.ts" # sync CLI binary path checks before daemon spawn
|
|
73
74
|
|
|
74
75
|
# Synchronous template & workflow resolvers:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
$schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
|
|
2
|
+
# No ESLint suppression comments. Spur lints with Biome, not ESLint; leftover
|
|
3
|
+
# `eslint-disable*` comments are dead suppressions that hide the real fix and
|
|
4
|
+
# would not even silence the live gate. Sibling of `no-biome-suppressions`
|
|
5
|
+
# (AGENTS.md: never suppress a gate to force green).
|
|
6
|
+
#
|
|
7
|
+
# Tests ARE in scope: an eslint-disable in a test is the same bypass.
|
|
8
|
+
include:
|
|
9
|
+
- "apps/**/*.ts"
|
|
10
|
+
- "apps/**/*.tsx"
|
|
11
|
+
- "packages/**/*.ts"
|
|
12
|
+
- "packages/**/*.tsx"
|
|
13
|
+
- "plugins/**/*.ts"
|
|
14
|
+
- "scripts/**/*.ts"
|
|
15
|
+
exclude:
|
|
16
|
+
- "**/node_modules/**"
|
|
17
|
+
- "**/dist/**"
|
|
18
|
+
- "**/vendors/**"
|
|
19
|
+
rules:
|
|
20
|
+
- id: no-eslint-suppressions
|
|
21
|
+
description: >
|
|
22
|
+
Do not use ESLint disable comments (file, line, or next-line forms).
|
|
23
|
+
Spur lints with Biome; an ESLint suppression does not silence the live
|
|
24
|
+
gate and hides the real fix. Remove the comment and fix the underlying
|
|
25
|
+
issue (AGENTS.md: no suppressions to force green).
|
|
26
|
+
severity: error
|
|
27
|
+
evaluator:
|
|
28
|
+
type: rg
|
|
29
|
+
config:
|
|
30
|
+
pattern: "eslint-disable"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# fail-closed on the captured record result + re-asserted verdict — never on a stub
|
|
17
17
|
|
|
18
18
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
19
|
-
version: "
|
|
19
|
+
version: "2"
|
|
20
20
|
kind: state-machine
|
|
21
21
|
name: docs-pipeline
|
|
22
22
|
description: "Docs-only execution pipeline (precheck -> draft -> docs-review -> verify -> record -> done)"
|
|
@@ -43,6 +43,10 @@ vars:
|
|
|
43
43
|
# `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
|
|
44
44
|
# git-tree half (task 0704 R4); resolved per-run by the verify state's taskpath shell step.
|
|
45
45
|
taskSpecPath: ""
|
|
46
|
+
# 0785 R2: linked feature spec path, resolved at verify entry. Empty (orphan task) means the
|
|
47
|
+
# feature spec is legitimately omitted from the digest inputs (empty-string compatibility);
|
|
48
|
+
# a declared feature whose path fails to resolve fails closed.
|
|
49
|
+
featureSpecPath: ""
|
|
46
50
|
__hitlAnswer: ""
|
|
47
51
|
# R8 (0366): injected by WorkflowAppService.run(); run-scopes every temporary capture.
|
|
48
52
|
__runId: ""
|
|
@@ -149,12 +153,32 @@ states:
|
|
|
149
153
|
options:
|
|
150
154
|
path: .spur/run/${vars.__runId}-docs-taskpath.txt
|
|
151
155
|
var: taskSpecPath
|
|
156
|
+
# 0785 R2: resolve the linked feature spec path so the digest covers it too. An orphan task
|
|
157
|
+
# (no feature_id) resolves to empty — legitimately omitted via empty-string compatibility;
|
|
158
|
+
# a declared feature whose spec path does not resolve fails closed instead of silently
|
|
159
|
+
# folding a stale spec into the proof.
|
|
160
|
+
- kind: shell
|
|
161
|
+
options:
|
|
162
|
+
command: >-
|
|
163
|
+
set -e;
|
|
164
|
+
FID="$($spurBin task show $wbs --json 2>/dev/null | jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null)";
|
|
165
|
+
FPATH_OUT="";
|
|
166
|
+
if [ -n "$FID" ]; then
|
|
167
|
+
FPATH_OUT="$($spurBin feature show "$FID" --json | jq -r '.filePath // .path // empty')";
|
|
168
|
+
if [ -z "$FPATH_OUT" ]; then echo "docs-pipeline: feature $FID linked to task $wbs but its spec path did not resolve" >&2; exit 1; fi;
|
|
169
|
+
fi;
|
|
170
|
+
printf '%s' "$FPATH_OUT" > ".spur/run/$__runId-docs-featurepath.txt"
|
|
171
|
+
- kind: file.read.into-var
|
|
172
|
+
options:
|
|
173
|
+
path: .spur/run/${vars.__runId}-docs-featurepath.txt
|
|
174
|
+
var: featureSpecPath
|
|
152
175
|
# R4: canonical capture BEFORE the verifier runs. Same fingerprint input as the
|
|
153
176
|
# re-capture below so the bracket compares like for like.
|
|
154
177
|
- kind: proof.fingerprint
|
|
155
178
|
options:
|
|
156
179
|
var: proofDigest
|
|
157
180
|
taskFile: ${vars.taskSpecPath}
|
|
181
|
+
featureFile: ${vars.featureSpecPath}
|
|
158
182
|
input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
|
|
159
183
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
160
184
|
- kind: agent.run
|
|
@@ -191,6 +215,7 @@ states:
|
|
|
191
215
|
options:
|
|
192
216
|
var: proofDigestNow
|
|
193
217
|
taskFile: ${vars.taskSpecPath}
|
|
218
|
+
featureFile: ${vars.featureSpecPath}
|
|
194
219
|
input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
|
|
195
220
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
196
221
|
|
|
@@ -1,37 +1,57 @@
|
|
|
1
|
-
# Feature development loop —
|
|
1
|
+
# Feature development loop — existing-feature reuse: precheck → execute-tasks →
|
|
2
|
+
# feature-verify → integration-review → done.
|
|
3
|
+
#
|
|
4
|
+
# 0782 (feature D6): the caller supplies a featureId whose AC are already accepted and whose
|
|
5
|
+
# task roster already exists. This definition REUSES them — the brainstorm/plan states and
|
|
6
|
+
# their edges are deleted, so no model hop ever re-plans or re-decomposes an existing feature
|
|
7
|
+
# (explicit planning stays at /sp:dev-plan and /sp:dev-idea, outside this consumer).
|
|
2
8
|
#
|
|
3
9
|
# Reliability (aligned with task-pipeline / ADR-043):
|
|
4
|
-
# - Soft precheck (
|
|
10
|
+
# - Soft precheck (feature identity + roster structural contract) → failed via transitions,
|
|
11
|
+
# never a raw lifecycle abort. The doctor probe is gone: the precheck now performs the
|
|
12
|
+
# essential feature/roster validation itself; task execution owns executor availability.
|
|
5
13
|
# - Every agent.run input is a pure slash command (no free-form pipeline essays)
|
|
6
14
|
# - featureId must be supplied by the caller (workflow vars are not agent-mutated)
|
|
7
|
-
# - feature-verify
|
|
15
|
+
# - feature-verify runs `feature check --as done --json` EXACTLY ONCE (0782 R3): the shell
|
|
16
|
+
# captures the exit code and JSON result, writes the decision to a .status sibling, and
|
|
17
|
+
# the guards only read that status — no sibling guard re-runs the check.
|
|
18
|
+
# - A nonempty all-terminal roster routes straight to feature-verify with zero execution
|
|
19
|
+
# model calls (0782 R2).
|
|
8
20
|
|
|
9
21
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
10
22
|
kind: state-machine
|
|
11
23
|
name: feature-dev
|
|
12
|
-
version: "
|
|
24
|
+
version: "3"
|
|
13
25
|
description: >
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
Existing-feature execution loop: precheck → execute-tasks → feature-verify → done.
|
|
27
|
+
Drives a WHOLE feature whose accepted AC and task roster already exist — the caller
|
|
28
|
+
supplies featureId, this loop reuses the roster and runs it to verified completion.
|
|
29
|
+
For planning a NEW feature use /sp:dev-plan / /sp:dev-idea; for one deliverable use
|
|
30
|
+
task-pipeline.yaml directly.
|
|
18
31
|
|
|
19
|
-
The engine has no native for-each over a dynamic task list, so
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
The engine has no native for-each over a dynamic task list, so execution DELEGATES the
|
|
33
|
+
fan-out: the precheck resolves the linked roster via `spur feature show` + `spur task
|
|
34
|
+
list --feature` (captured once under .spur/run/<runId>-feature-dev-{feature,roster}.json),
|
|
35
|
+
validates the essential structural contract (identity, roster shape, duplicate/mismatched
|
|
36
|
+
identities, refinement-blocking statuses), and freezes the sorted todo WBS list into
|
|
37
|
+
.spur/run/<runId>-feature-dev-tasks.txt. The execute-tasks step reads that frozen list
|
|
38
|
+
into a var (file.read.into-var) and dispatches ONE pure-slash /sp:dev-runall --tasks run
|
|
39
|
+
with it — the child never re-enumerates membership (0782 R2).
|
|
24
40
|
|
|
25
|
-
Shape: precheck →
|
|
26
|
-
|
|
41
|
+
Shape: precheck → execute-tasks[-auto](delegated frozen batch) → feature-verify →
|
|
42
|
+
integration-review → done
|
|
43
|
+
(precheck failure, a non-PASS completion check, or a blocking review short-circuits
|
|
44
|
+
to `failed`; a nonempty all-terminal roster skips execution entirely).
|
|
27
45
|
|
|
28
46
|
Vars (passed as a JSON object via `--vars`):
|
|
29
|
-
featureId — the feature ID this loop drives (required), e.g. `--vars '{"featureId":"B3"}'`
|
|
47
|
+
featureId — the existing feature ID this loop drives (required), e.g. `--vars '{"featureId":"B3"}'`
|
|
30
48
|
agent — agent the agent.run steps invoke; override `--vars '{"agent":"claude"}'`
|
|
31
|
-
profile — `auto` selects execute-tasks-auto (`/sp:dev-runall … --auto`); otherwise
|
|
49
|
+
profile — `auto` selects execute-tasks-auto (`/sp:dev-runall --tasks … --auto`); otherwise
|
|
32
50
|
interactive execute-tasks (no --auto, per-task HITL remains)
|
|
51
|
+
featureTaskIds — frozen todo WBS list projected from the roster file at run time; declared
|
|
52
|
+
empty so template resolution cannot throw before the read lands (0782 design)
|
|
33
53
|
spurBin — PATH-independent spur invocation (CLI overrides at run start; literal fallback)
|
|
34
|
-
stepTimeoutMs — agent.run budget (ms) for
|
|
54
|
+
stepTimeoutMs — agent.run budget (ms) for the runall dispatch
|
|
35
55
|
|
|
36
56
|
Reliability (ADR-043): every agent.run input is a pure slash command. featureId must be
|
|
37
57
|
supplied by the caller (workflow vars cannot be mutated by the agent). profile is wired
|
|
@@ -47,6 +67,7 @@ vars:
|
|
|
47
67
|
featureId: ""
|
|
48
68
|
agent: "auto"
|
|
49
69
|
profile: "standard"
|
|
70
|
+
featureTaskIds: ""
|
|
50
71
|
spurBin: "spur"
|
|
51
72
|
stepTimeoutMs: "1800000"
|
|
52
73
|
# Integration review (D5-P, feature R11). Advisory by default: a pending, timed-out, or
|
|
@@ -60,87 +81,116 @@ vars:
|
|
|
60
81
|
states:
|
|
61
82
|
- id: precheck
|
|
62
83
|
description: >
|
|
63
|
-
Soft precheck:
|
|
64
|
-
|
|
84
|
+
Soft precheck (0782 R1): resolve the supplied feature and its linked task roster via
|
|
85
|
+
existing CLI reads, then validate the essential structural contract — non-empty
|
|
86
|
+
featureId/runId, a feature-show JSON whose identity matches, a nonempty array roster
|
|
87
|
+
with unique non-empty WBS identities and known statuses, no backlog/wip/testing/blocked
|
|
88
|
+
member (planning/refine/resume handoff), and at least one linked task. Rejects fail
|
|
89
|
+
closed with an actionable handoff BEFORE any model dispatch; nothing is auto-created
|
|
90
|
+
or re-planned. Always exit 0 so failures route to `failed` via transitions.
|
|
65
91
|
onEnter:
|
|
66
92
|
- kind: shell
|
|
67
93
|
options:
|
|
68
94
|
command: >-
|
|
69
|
-
mkdir -p .spur/run
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
mkdir -p .spur/run;
|
|
96
|
+
FEATURE_JSON=".spur/run/$__runId-feature-dev-feature.json";
|
|
97
|
+
ROSTER_JSON=".spur/run/$__runId-feature-dev-roster.json";
|
|
98
|
+
TASKS_TXT=".spur/run/$__runId-feature-dev-tasks.txt";
|
|
99
|
+
PRECHECK_STATUS=".spur/run/$__runId-feature-dev-precheck.status";
|
|
100
|
+
rm -f "$PRECHECK_STATUS";
|
|
101
|
+
set +e;
|
|
102
|
+
test -n "$featureId" && test -n "$__runId";
|
|
103
|
+
id_rc=$?;
|
|
104
|
+
if [ "$id_rc" -eq 0 ]; then $spurBin feature show "$featureId" --json > "$FEATURE_JSON" 2>&1; show_rc=$?; else show_rc=1; fi;
|
|
105
|
+
if [ "$id_rc" -eq 0 ] && [ "$show_rc" -eq 0 ] && jq -e --arg id "$featureId" 'type == "object" and .id == $id' "$FEATURE_JSON" > /dev/null 2>&1; then $spurBin task list --feature "$featureId" --json > "$ROSTER_JSON" 2>&1; list_rc=$?; else list_rc=1; fi;
|
|
106
|
+
if [ "$id_rc" -ne 0 ] || [ "$show_rc" -ne 0 ] || [ "$list_rc" -ne 0 ]; then
|
|
107
|
+
echo "feature-dev precheck: missing featureId/runId, unknown feature '$featureId', or unreadable roster (rc $id_rc/$show_rc/$list_rc) — supply an existing planned feature via /sp:dev-plan or /sp:dev-idea; nothing was auto-created or re-planned" >&2;
|
|
108
|
+
printf 'FAIL\n' > "$PRECHECK_STATUS";
|
|
109
|
+
elif ! jq -e 'type == "array" and length > 0' "$ROSTER_JSON" > /dev/null 2>&1; then
|
|
110
|
+
echo "feature-dev precheck: roster at $ROSTER_JSON is malformed, not an array, or empty — plan the feature first via /sp:dev-plan; refusing to replan or run an empty batch" >&2;
|
|
111
|
+
printf 'FAIL\n' > "$PRECHECK_STATUS";
|
|
112
|
+
elif ! jq -e 'all(.[]; (.wbs | type == "string" and length > 0)) and ([.[].wbs] | length == (unique | length)) and all(.[]; .status == "todo" or .status == "done" or .status == "cancelled" or .status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")' "$ROSTER_JSON" > /dev/null 2>&1; then
|
|
113
|
+
echo "feature-dev precheck: roster has empty/duplicate/mismatched WBS identities or unknown statuses at $ROSTER_JSON — repair the task corpus; refusing to batch a broken roster" >&2;
|
|
114
|
+
printf 'FAIL\n' > "$PRECHECK_STATUS";
|
|
115
|
+
elif [ "$(jq '[.[] | select(.status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")] | length' "$ROSTER_JSON")" -gt 0 ]; then
|
|
116
|
+
echo "feature-dev precheck: linked task(s) are backlog/wip/testing/blocked — refine or resume them through their own task pipelines before batching; refusing to launch overlapping work" >&2;
|
|
117
|
+
printf 'FAIL\n' > "$PRECHECK_STATUS";
|
|
76
118
|
else
|
|
77
|
-
printf '
|
|
78
|
-
|
|
119
|
+
printf '%s' "$(jq -r '[.[] | select(.status == "todo") | .wbs] | sort | join(",")' "$ROSTER_JSON")" > "$TASKS_TXT";
|
|
120
|
+
printf 'PASS\n' > "$PRECHECK_STATUS";
|
|
121
|
+
fi;
|
|
79
122
|
exit 0
|
|
80
123
|
- kind: note
|
|
81
124
|
options:
|
|
82
|
-
message: "Feature-dev start for feature ${vars.featureId}."
|
|
83
|
-
|
|
84
|
-
- id: brainstorm
|
|
85
|
-
description: >
|
|
86
|
-
Validate / expand the feature with BDD AC via pure slash /sp:dev-brainstorm.
|
|
87
|
-
Caller must pass vars.featureId (required for plan/runall).
|
|
88
|
-
onEnter:
|
|
89
|
-
- kind: agent.run
|
|
90
|
-
options:
|
|
91
|
-
agent: ${vars.agent}
|
|
92
|
-
input: /sp:dev-brainstorm --feature ${vars.featureId}
|
|
93
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
94
|
-
role: planner
|
|
95
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
96
|
-
|
|
97
|
-
- id: plan
|
|
98
|
-
description: >
|
|
99
|
-
Decompose the validated feature into a CLI-validated task batch via pure slash
|
|
100
|
-
/sp:dev-plan. The batch-create gate rejects a malformed decomposition.
|
|
101
|
-
onEnter:
|
|
102
|
-
- kind: agent.run
|
|
103
|
-
options:
|
|
104
|
-
agent: ${vars.agent}
|
|
105
|
-
input: /sp:dev-plan --feature ${vars.featureId}
|
|
106
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
107
|
-
role: planner
|
|
108
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
125
|
+
message: "Feature-dev start for existing feature ${vars.featureId} (0782 reuse path): roster resolved under .spur/run/${vars.__runId}-feature-dev-roster.json; frozen todo list at .spur/run/${vars.__runId}-feature-dev-tasks.txt."
|
|
109
126
|
|
|
110
127
|
# Two pure-slash execute hops so vars.profile actually controls --auto (ADR-043).
|
|
111
128
|
# Engine templates cannot branch a single input string on profile; declaration-order
|
|
112
|
-
# transitions pick the correct hop (auto first, then interactive fallback).
|
|
129
|
+
# transitions pick the correct hop (auto first, then interactive fallback). Each hop first
|
|
130
|
+
# reads the FROZEN todo list produced by precheck into featureTaskIds, then dispatches the
|
|
131
|
+
# explicit list — the runall child never re-enumerates membership (0782 R2).
|
|
113
132
|
- id: execute-tasks-auto
|
|
114
133
|
description: >
|
|
115
|
-
profile=auto —
|
|
116
|
-
(skips per-task HITL inside task-pipeline).
|
|
134
|
+
profile=auto — dispatch the frozen todo WBS list via pure slash
|
|
135
|
+
/sp:dev-runall --tasks <list> --auto (skips per-task HITL inside task-pipeline).
|
|
117
136
|
onEnter:
|
|
137
|
+
- kind: file.read.into-var
|
|
138
|
+
options:
|
|
139
|
+
path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
|
|
140
|
+
var: featureTaskIds
|
|
141
|
+
trim: true
|
|
118
142
|
- kind: agent.run
|
|
119
143
|
options:
|
|
120
144
|
agent: ${vars.agent}
|
|
121
|
-
input: /sp:dev-runall --
|
|
145
|
+
input: /sp:dev-runall --tasks ${vars.featureTaskIds} --auto
|
|
122
146
|
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
123
147
|
role: planner
|
|
124
148
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
125
149
|
|
|
126
150
|
- id: execute-tasks
|
|
127
151
|
description: >
|
|
128
|
-
Interactive —
|
|
129
|
-
--auto (per-task HITL approve gates remain
|
|
152
|
+
Interactive — dispatch the frozen todo WBS list via pure slash
|
|
153
|
+
/sp:dev-runall --tasks <list> without --auto (per-task HITL approve gates remain
|
|
154
|
+
available).
|
|
130
155
|
onEnter:
|
|
156
|
+
- kind: file.read.into-var
|
|
157
|
+
options:
|
|
158
|
+
path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
|
|
159
|
+
var: featureTaskIds
|
|
160
|
+
trim: true
|
|
131
161
|
- kind: agent.run
|
|
132
162
|
options:
|
|
133
163
|
agent: ${vars.agent}
|
|
134
|
-
input: /sp:dev-runall --
|
|
164
|
+
input: /sp:dev-runall --tasks ${vars.featureTaskIds}
|
|
135
165
|
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
136
166
|
role: planner
|
|
137
167
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
138
168
|
|
|
139
169
|
- id: feature-verify
|
|
140
170
|
description: >
|
|
141
|
-
Certify the feature is complete: `spur feature check
|
|
142
|
-
|
|
143
|
-
|
|
171
|
+
Certify the feature is complete (0782 R3): run `spur feature check --as done --json`
|
|
172
|
+
EXACTLY ONCE, capture the exit code and JSON result at
|
|
173
|
+
.spur/run/<runId>-feature-dev-verify.json, and decide PASS/FAIL into the .status
|
|
174
|
+
sibling. PASS requires exit 0, a nonempty result array, and every returned scope
|
|
175
|
+
pass === true. The guards below only read the captured status — no sibling guard
|
|
176
|
+
re-runs the check. No --strict elevation, no whole-corpus scan.
|
|
177
|
+
onEnter:
|
|
178
|
+
- kind: shell
|
|
179
|
+
options:
|
|
180
|
+
command: >-
|
|
181
|
+
mkdir -p .spur/run;
|
|
182
|
+
VERIFY_JSON=".spur/run/$__runId-feature-dev-verify.json";
|
|
183
|
+
VERIFY_STATUS=".spur/run/$__runId-feature-dev-verify.status";
|
|
184
|
+
VERIFY_TMP=".spur/run/$__runId-feature-dev-verify.status.tmp";
|
|
185
|
+
rm -f "$VERIFY_STATUS";
|
|
186
|
+
set +e;
|
|
187
|
+
$spurBin feature check "$featureId" --as done --json > "$VERIFY_JSON" 2>&1;
|
|
188
|
+
check_rc=$?;
|
|
189
|
+
set -e;
|
|
190
|
+
if [ "$check_rc" -eq 0 ] && jq -e 'type == "array" and length > 0 and all(.[]; .pass == true)' "$VERIFY_JSON" > /dev/null 2>&1; then printf 'PASS\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; else printf 'FAIL\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; fi
|
|
191
|
+
- kind: note
|
|
192
|
+
options:
|
|
193
|
+
message: "Feature verification for ${vars.featureId}: decision at .spur/run/${vars.__runId}-feature-dev-verify.status (evidence at .spur/run/${vars.__runId}-feature-dev-verify.json) — exactly one `feature check --as done --json` invocation per run (0782 R3)."
|
|
144
194
|
|
|
145
195
|
- id: integration-review
|
|
146
196
|
description: >
|
|
@@ -196,47 +246,40 @@ states:
|
|
|
196
246
|
message: "Integration review for feature ${vars.featureId}: request verdict at .spur/run/${vars.__runId}-integration-review.status (request state only — REQUESTED/ALREADY_* are NEVER clean evidence); collected verdict at .spur/run/${vars.__runId}-integration-review-collect.status (CLEAN/FINDINGS/PENDING — the only clean evidence). Advisory unless requireCleanReview=true."
|
|
197
247
|
|
|
198
248
|
- id: done
|
|
199
|
-
description: Terminal —
|
|
200
|
-
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
201
|
-
onEnter:
|
|
202
|
-
- kind: shell
|
|
203
|
-
options:
|
|
204
|
-
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: feature-dev done featureId=$featureId ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/$featureId-checkpoint.md'
|
|
249
|
+
description: Terminal — the existing feature's roster executed and the feature verified.
|
|
205
250
|
|
|
206
251
|
- id: failed
|
|
207
|
-
description: Terminal —
|
|
252
|
+
description: Terminal — the roster contract was rejected at precheck, a task failed verification, or the feature check did not pass.
|
|
208
253
|
|
|
209
254
|
transitions:
|
|
255
|
+
# ── precheck routing (0782 R1/R2) — declaration order matters: the all-terminal
|
|
256
|
+
# direct-verify edge is tried FIRST so a fully-terminal roster reaches completion
|
|
257
|
+
# verification with zero execution model calls; then the profile hops; then the
|
|
258
|
+
# failure fallback. ──
|
|
210
259
|
- from: precheck
|
|
211
|
-
to:
|
|
212
|
-
description:
|
|
260
|
+
to: feature-verify
|
|
261
|
+
description: Roster PASS and every linked task is already terminal — verify the feature directly, zero execution model calls.
|
|
213
262
|
guard:
|
|
214
263
|
kind: shell
|
|
215
264
|
options:
|
|
216
|
-
command: 'test "$(cat .spur/run/$__runId-feature-dev-precheck.status 2>/dev/null)" = PASS'
|
|
265
|
+
command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -f ".spur/run/$__runId-feature-dev-feature.json" && test ! -s ".spur/run/$__runId-feature-dev-tasks.txt"'
|
|
217
266
|
- from: precheck
|
|
218
|
-
to: failed
|
|
219
|
-
description: Precheck FAIL (missing featureId and/or doctor red) — stop.
|
|
220
|
-
guard:
|
|
221
|
-
kind: always
|
|
222
|
-
|
|
223
|
-
# Linear prefix: brainstorm → plan → execute-tasks
|
|
224
|
-
- from: brainstorm
|
|
225
|
-
to: plan
|
|
226
|
-
description: Feature validated; decompose it into tasks
|
|
227
|
-
guard:
|
|
228
|
-
kind: always
|
|
229
|
-
|
|
230
|
-
- from: plan
|
|
231
267
|
to: execute-tasks-auto
|
|
232
|
-
description:
|
|
268
|
+
description: Roster PASS with todo work; profile=auto — batch the frozen list with --auto (no per-task HITL).
|
|
233
269
|
guard:
|
|
234
270
|
kind: shell
|
|
235
271
|
options:
|
|
236
|
-
command: 'test "$profile" = auto'
|
|
237
|
-
- from:
|
|
272
|
+
command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -s ".spur/run/$__runId-feature-dev-tasks.txt" && test "$profile" = auto'
|
|
273
|
+
- from: precheck
|
|
238
274
|
to: execute-tasks
|
|
239
|
-
description:
|
|
275
|
+
description: Roster PASS with todo work; interactive — batch the frozen list without --auto.
|
|
276
|
+
guard:
|
|
277
|
+
kind: shell
|
|
278
|
+
options:
|
|
279
|
+
command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -s ".spur/run/$__runId-feature-dev-tasks.txt"'
|
|
280
|
+
- from: precheck
|
|
281
|
+
to: failed
|
|
282
|
+
description: Precheck FAIL (missing identity, malformed/empty roster, duplicate identities, or refinement-blocking statuses) — stop before any model dispatch.
|
|
240
283
|
guard:
|
|
241
284
|
kind: always
|
|
242
285
|
|
|
@@ -251,16 +294,16 @@ transitions:
|
|
|
251
294
|
guard:
|
|
252
295
|
kind: always
|
|
253
296
|
|
|
254
|
-
# ── feature-verify gate:
|
|
255
|
-
# Declaration order: the pass guard is tried FIRST.
|
|
297
|
+
# ── feature-verify gate: the captured one-shot check decision certifies review, else
|
|
298
|
+
# fail. Declaration order: the pass guard is tried FIRST. The check itself is NOT
|
|
299
|
+
# re-run here (0782 R3) — the shell decision file is the only evidence. ──
|
|
256
300
|
- from: feature-verify
|
|
257
301
|
to: integration-review
|
|
258
|
-
description:
|
|
302
|
+
description: Captured completion check is PASS — spend one integration review on this stable HEAD.
|
|
259
303
|
guard:
|
|
260
304
|
kind: shell
|
|
261
305
|
options:
|
|
262
|
-
command: "$
|
|
263
|
-
|
|
306
|
+
command: 'test "$(cat ".spur/run/$__runId-feature-dev-verify.status" 2>/dev/null)" = PASS'
|
|
264
307
|
- from: integration-review
|
|
265
308
|
to: failed
|
|
266
309
|
description: >
|
|
@@ -285,8 +328,6 @@ transitions:
|
|
|
285
328
|
kind: always
|
|
286
329
|
- from: feature-verify
|
|
287
330
|
to: failed
|
|
288
|
-
description:
|
|
331
|
+
description: Completion check missing, malformed, or non-PASS (or its evidence failed to persist) — block BEFORE integration review; no review is requested (0782 R3).
|
|
289
332
|
guard:
|
|
290
|
-
kind:
|
|
291
|
-
options:
|
|
292
|
-
command: '! $spurBin feature check $featureId --strict'
|
|
333
|
+
kind: always
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
# Seeded by `spur init`; adapt the agent.run inputs to your project's command set.
|
|
47
47
|
|
|
48
48
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
49
|
-
version: "
|
|
49
|
+
version: "2"
|
|
50
50
|
kind: state-machine
|
|
51
51
|
name: idea-pipeline
|
|
52
52
|
description: "Idea to feature + AC + task batch: discovery, idea-eval, feature-create, ac-generate, feature-check, system-design, decompose, batch-create, handoff"
|
|
@@ -327,7 +327,7 @@ states:
|
|
|
327
327
|
onEnter:
|
|
328
328
|
- kind: shell
|
|
329
329
|
options:
|
|
330
|
-
command: "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-decompose-retry-count; rm -f .spur/run/$__runId-idea-task-batch.json .spur/run/$__runId-idea-task-order.json .spur/run/$__runId-idea-batch-create.done .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-handoff.md"
|
|
330
|
+
command: "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-decompose-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-decompose-retry-count; rm -f .spur/run/$__runId-idea-task-batch.json .spur/run/$__runId-idea-task-order.json .spur/run/$__runId-idea-batch-create.done .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-handoff.md .spur/run/$__runId-idea-ready.json"
|
|
331
331
|
- kind: agent.run
|
|
332
332
|
options:
|
|
333
333
|
agent: ${vars.planningAgent}
|
|
@@ -385,7 +385,7 @@ states:
|
|
|
385
385
|
command: >-
|
|
386
386
|
if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi;
|
|
387
387
|
rm -f .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-batch-create-result.json.tmp;
|
|
388
|
-
if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e ".created == (.wbs | length)" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then
|
|
388
|
+
if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --skip-ready --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e ".created == (.wbs | length)" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then
|
|
389
389
|
mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&
|
|
390
390
|
date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;
|
|
391
391
|
else
|
|
@@ -393,6 +393,49 @@ states:
|
|
|
393
393
|
date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;
|
|
394
394
|
fi
|
|
395
395
|
|
|
396
|
+
- id: ready-prepare
|
|
397
|
+
description: >
|
|
398
|
+
Ready-by-default preparation (0788): the planning owner applies the
|
|
399
|
+
ready-refinement checklist to each created task (requirements, design,
|
|
400
|
+
plan, ac, decisions, dependencies, premises) so the deterministic task
|
|
401
|
+
check passes, then writes the run-scoped ready evidence sidecar
|
|
402
|
+
(.spur/run/${vars.__runId}-idea-ready.json) with one row per task: wbs,
|
|
403
|
+
status (ready|failed|skipped), planningDigest, and seven check rows.
|
|
404
|
+
Handoff-finalize verifies presence, digest and checklist evidence before
|
|
405
|
+
recommending auto runall; missing or failing evidence never fails the
|
|
406
|
+
run — it degrades the recommendation to ready-depth refineall.
|
|
407
|
+
onEnter:
|
|
408
|
+
- kind: agent.run
|
|
409
|
+
options:
|
|
410
|
+
agent: ${vars.planningAgent}
|
|
411
|
+
# Declared Layer-1 role (0538 R2), same planner executor as decompose.
|
|
412
|
+
role: planner
|
|
413
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
414
|
+
input: >-
|
|
415
|
+
Ready-by-default preparation for feature ${vars.featureId} (0788). The created WBS
|
|
416
|
+
values are in .spur/run/${vars.__runId}-idea-batch-create-result.json under .wbs.
|
|
417
|
+
For EACH wbs: resolve the task file with `spur task path <wbs> --json`; open the
|
|
418
|
+
task doc and apply the ready-refinement checklist — make requirements, design,
|
|
419
|
+
plan, acceptance criteria, decisions, dependencies and premises present and
|
|
420
|
+
non-placeholder so `spur task check <wbs> --json` exits 0; edit only planning
|
|
421
|
+
sections, never Solution/Testing/Review/History. Record one checklist row per id
|
|
422
|
+
with concrete evidence of how you verified it. Compute the planning digest with
|
|
423
|
+
the project's own implementation when this is a monorepo checkout:
|
|
424
|
+
`bun -e 'const m = await import("./packages/app/src/services/task-readiness"); console.log(m.computePlanningDigest(await Bun.file(process.argv[1]).text()))' <task-file>`
|
|
425
|
+
— when that is impossible in this checkout, set status "skipped" instead of
|
|
426
|
+
guessing a digest. Finally write .spur/run/${vars.__runId}-idea-ready.json with
|
|
427
|
+
exactly this shape: {"runId":"${vars.__runId}","depth":"ready","tasks":[{"wbs":"<wbs>","status":"ready" or "failed" or "skipped","planningDigest":"<sha256 hex>","checks":[{"id":"requirements" or "design" or "plan" or "ac" or "decisions" or "dependencies" or "premises","pass":true or false,"evidence":"<how verified>"}]}]}. A task you cannot fully prepare gets status "failed" or "skipped" — never fabricate evidence; the handoff degrades to refineall.
|
|
428
|
+
# Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
|
|
429
|
+
# normalized to an empty sidecar: finalize and the seeded fallback then degrade
|
|
430
|
+
# the recommendation to refineall instead of failing the run. A PRESENT but
|
|
431
|
+
# malformed sidecar fails the run here — it must not masquerade as evidence.
|
|
432
|
+
- kind: shell
|
|
433
|
+
options:
|
|
434
|
+
command: >-
|
|
435
|
+
READY=".spur/run/$__runId-idea-ready.json" &&
|
|
436
|
+
if ! test -f "$READY"; then printf '{"runId":"%s","depth":"ready","tasks":[]}\n' "$__runId" > "$READY"; fi &&
|
|
437
|
+
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
|
|
438
|
+
|
|
396
439
|
- id: handoff-finalize
|
|
397
440
|
description: >
|
|
398
441
|
Post-create finalization (0518): zip batch item names to the created WBS
|
|
@@ -458,7 +501,9 @@ states:
|
|
|
458
501
|
CHECK_ROWS=$(wc -l < "$CHECKS" | tr -d ' ') &&
|
|
459
502
|
WBS_COUNT=$(printf '%s\n' $WBS_LIST | wc -l | tr -d ' ') &&
|
|
460
503
|
test "$CHECK_ROWS" = "$WBS_COUNT" &&
|
|
461
|
-
|
|
504
|
+
READY=".spur/run/$__runId-idea-ready.json" &&
|
|
505
|
+
(test -f "$READY" || printf '{"tasks":[]}\n' > "$READY") &&
|
|
506
|
+
NEXT=$(jq -r --arg feature "$featureId" --slurpfile c "$CHECKS" --slurpfile k "$READY" 'if (($c | any(.[]; .pass == false)) or (($k[0].tasks // []) | length == 0) or (($k[0].tasks // []) | any(.[]; .status != "ready"))) then "/sp:dev-refineall --feature \($feature) --auto --depth ready" else "/sp:dev-runall --feature \($feature) --auto" end' "$RESULT") &&
|
|
462
507
|
{
|
|
463
508
|
echo "# Idea pipeline handoff report";
|
|
464
509
|
echo;
|
|
@@ -498,10 +543,6 @@ states:
|
|
|
498
543
|
options:
|
|
499
544
|
path: .spur/run/${vars.__runId}-idea-handoff.md
|
|
500
545
|
artifactKind: idea-handoff
|
|
501
|
-
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
502
|
-
- kind: shell
|
|
503
|
-
options:
|
|
504
|
-
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: idea-pipeline handoff feature=$featureId ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/idea-checkpoint.md'
|
|
505
546
|
|
|
506
547
|
- id: cancelled
|
|
507
548
|
description: Terminal — pipeline cancelled by operator or error.
|
|
@@ -748,14 +789,20 @@ transitions:
|
|
|
748
789
|
options:
|
|
749
790
|
command: 'test "$__hitlAnswer" = cancel'
|
|
750
791
|
|
|
751
|
-
# ── batch-create-run: success ->
|
|
792
|
+
# ── batch-create-run: success -> ready-prepare, failure -> decompose (retry cap: 3), failure+cap -> failed ──
|
|
752
793
|
- from: batch-create-run
|
|
753
|
-
to:
|
|
754
|
-
description: Batch created and result captured — apply
|
|
794
|
+
to: ready-prepare
|
|
795
|
+
description: Batch created and result captured — apply ready-by-default preparation, then finalize the handoff.
|
|
755
796
|
guard:
|
|
756
797
|
kind: shell
|
|
757
798
|
options:
|
|
758
799
|
command: "test -f .spur/run/$__runId-idea-batch-create.done"
|
|
800
|
+
# ── ready-prepare: -> handoff-finalize (always; evidence absence degrades to refineall, never fails the run) ──
|
|
801
|
+
- from: ready-prepare
|
|
802
|
+
to: handoff-finalize
|
|
803
|
+
description: Ready evidence sidecar written (or normalized empty) — finalize the handoff report.
|
|
804
|
+
guard:
|
|
805
|
+
kind: always
|
|
759
806
|
- from: batch-create-run
|
|
760
807
|
to: decompose
|
|
761
808
|
description: "Batch-create failed — revise decomposition (retry cap: 3)."
|