@gobing-ai/spur 0.3.84 → 0.3.86

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.
Files changed (109) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +11 -6
  3. package/config/pipeline-budgets.json +0 -25
  4. package/config/plugin-scripts.json +0 -5
  5. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  6. package/config/transition-shims.json +1 -1
  7. package/config/workflow-candidates.json +41 -0
  8. package/config/workflows/feature-lifecycle.yaml +4 -2
  9. package/config/workflows/feature-verification.yaml +70 -0
  10. package/config/workflows/idea-pipeline.yaml +25 -13
  11. package/config/workflows/task-pipeline.yaml +43 -12
  12. package/config/workflows/wrapup-pipeline.yaml +71 -8
  13. package/package.json +9 -9
  14. package/plugins/sp/README.md +0 -3
  15. package/plugins/sp/agents/super-planner.md +2 -1
  16. package/plugins/sp/hooks/agent-hint.ts +5 -4
  17. package/plugins/sp/hooks/careful-guard.ts +3 -1
  18. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  19. package/plugins/sp/hooks/context-session-start.ts +4 -3
  20. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  21. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  22. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  23. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  24. package/plugins/sp/plugin.json +1 -1
  25. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  26. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  27. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  28. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  29. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  30. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  31. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  32. package/plugins/sp/scripts/inline-run-setup.ts +185 -2
  33. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  34. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  35. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  36. package/plugins/sp/scripts/quality-gate.ts +2 -1
  37. package/plugins/sp/scripts/surface-drift-inventory.ts +2 -5
  38. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  39. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  40. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  41. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  42. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  43. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  44. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  45. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  46. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  47. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  48. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  49. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
  50. package/schemas/state-machine-workflow.schema.json +5 -0
  51. package/schemas/transition-flow-workflow.schema.json +5 -0
  52. package/spur.js +21393 -20831
  53. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  54. package/web/_astro/{BoardApp.D4mBUGLT.js → BoardApp.yBBcFXWP.js} +60 -60
  55. package/web/_astro/{TaskDetail.DhCWaOPR.js → TaskDetail.DqFJbRFc.js} +1 -1
  56. package/web/_astro/{arc.D3X0nfq-.js → arc.DL-BpHoi.js} +1 -1
  57. package/web/_astro/{architectureDiagram-3BPJPVTR.BQDoiGme.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  58. package/web/_astro/{blockDiagram-GPEHLZMM.BjHekBbM.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  59. package/web/_astro/{c4Diagram-AAUBKEIU.BvHt171l.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  60. package/web/_astro/channel.CX5453qQ.js +1 -0
  61. package/web/_astro/{chunk-2J33WTMH.CwxGXlDR.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  62. package/web/_astro/{chunk-4BX2VUAB.Dia1YtwU.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  63. package/web/_astro/{chunk-55IACEB6.Oz9epCXa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  64. package/web/_astro/{chunk-727SXJPM.DKFYJjYJ.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  65. package/web/_astro/{chunk-AQP2D5EJ.AD7w5oVz.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  66. package/web/_astro/{chunk-FMBD7UC4.D0OqTj52.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  67. package/web/_astro/{chunk-ND2GUHAM.CU5ppR7j.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  68. package/web/_astro/{chunk-QZHKN3VN.Bu1rFztw.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  69. package/web/_astro/{classDiagram-4FO5ZUOK.C0Pgm5yE.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  70. package/web/_astro/{classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  71. package/web/_astro/{cose-bilkent-S5V4N54A.C0ecLb1e.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  72. package/web/_astro/{cynefin-OW5HDTMX.CpStVmhO.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  73. package/web/_astro/{dagre-BM42HDAG.BPWN3W9f.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  74. package/web/_astro/{diagram-2AECGRRQ.DnXbEot4.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  75. package/web/_astro/{diagram-5GNKFQAL.BYJsgUiI.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  76. package/web/_astro/{diagram-KO2AKTUF.CU4Bqprx.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  77. package/web/_astro/{diagram-LMA3HP47.BQLOANIu.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  78. package/web/_astro/{diagram-OG6HWLK6.Cn1UVhqj.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  79. package/web/_astro/{erDiagram-TEJ5UH35.C8FdZyvA.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  80. package/web/_astro/{flowDiagram-I6XJVG4X.QSZCR8Uo.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  81. package/web/_astro/{ganttDiagram-6RSMTGT7.O1Py31MY.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  82. package/web/_astro/{gitGraphDiagram-PVQCEYII.DoeJRBfY.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  83. package/web/_astro/{index.TPF1FerP.css → index.CcU5weKX.css} +1 -1
  84. package/web/_astro/{infoDiagram-5YYISTIA.9npLJecW.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  85. package/web/_astro/{ishikawaDiagram-YF4QCWOH.DXanvOr3.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  86. package/web/_astro/{journeyDiagram-JHISSGLW.DxbVjxAv.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  87. package/web/_astro/{kanban-definition-UN3LZRKU.BDk6_iq0.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  88. package/web/_astro/{linear.C9cCDjT3.js → linear.Nv_xOUjP.js} +1 -1
  89. package/web/_astro/{mermaid.core.CpHRe5nH.js → mermaid.core.Bc4LqQgX.js} +4 -4
  90. package/web/_astro/{mindmap-definition-RKZ34NQL.D-6C85bn.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  91. package/web/_astro/{pieDiagram-4H26LBE5.B3Oxodty.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  92. package/web/_astro/{quadrantDiagram-W4KKPZXB.QZ_9Hshu.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  93. package/web/_astro/{requirementDiagram-4Y6WPE33.BimvuK53.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  94. package/web/_astro/{sankeyDiagram-5OEKKPKP.D5Kkpx2C.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  95. package/web/_astro/{sequenceDiagram-3UESZ5HK.B4YR6fAE.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  96. package/web/_astro/{stateDiagram-AJRCARHV.Bt2wkZok.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  97. package/web/_astro/{stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  98. package/web/_astro/{timeline-definition-PNZ67QCA.DmiTBL8w.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  99. package/web/_astro/{vennDiagram-CIIHVFJN.KNJV40mf.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  100. package/web/_astro/{wardleyDiagram-YWT4CUSO.BVAaKgWH.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  101. package/web/_astro/{xychartDiagram-2RQKCTM6.D5Jx7zB2.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  102. package/web/index.html +2 -2
  103. package/config/workflows/basic.yaml +0 -146
  104. package/config/workflows/docs-pipeline.yaml +0 -350
  105. package/config/workflows/feature-dev.yaml +0 -288
  106. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  107. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  108. package/web/_astro/BoardApp.DFPYpI5O.js +0 -1
  109. package/web/_astro/channel.BlQwxyoI.js +0 -1
@@ -1,350 +0,0 @@
1
- # Docs-only task pipeline — one run per documentation/design-doc deliverable.
2
- #
3
- # Sibling of task-pipeline.yaml for tasks whose output is a document, not product
4
- # code. Keeps docs deliverables out of the full quality gate and routes them
5
- # through an explicit docs-review HITL gate.
6
- #
7
- # Reliability (aligned with task-pipeline / ADR-043):
8
- # - Soft precheck (task check + status file) so red precheck → failed via transitions
9
- # - draft agent.run is a pure slash command (docs discipline lives in
10
- # /sp:dev-run --mode implement + sp:code-implementation, not YAML prose)
11
- # - requireDiff fails silent no-op drafts
12
- # - HITL answer routing is exhaustive (yes/no/cancel)
13
- # - verify measures the deliverable read-only (answer file + `spur task verdict` + proof
14
- # digest bracket); recording happens only AFTER measured PASS (0769 ordering:
15
- # precheck → draft → docs-review → verify → record → done) and `record → done` is
16
- # fail-closed on the captured record result + re-asserted verdict — never on a stub
17
-
18
- "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
19
- version: "2"
20
- kind: state-machine
21
- name: docs-pipeline
22
- description: "Docs-only execution pipeline (precheck -> draft -> docs-review -> verify -> record -> done)"
23
- iterationBound: 12
24
- initialState: precheck
25
- terminalStates:
26
- - done
27
- - failed
28
- - cancelled
29
- failureStates:
30
- - failed
31
- - cancelled
32
- vars:
33
- wbs: "0000"
34
- profile: "standard"
35
- spurBin: "spur"
36
- agent: "auto"
37
- stepTimeoutMs: "1800000"
38
- # Proof-state bracket (task 0704, mirroring task-pipeline 0612/0703). `proofDigest` is the
39
- # canonical capture at verify entry; `proofDigestNow` is the live re-capture compared against
40
- # it by the `verify → record` guard so verifier-time mutation fails closed (R4/R5).
41
- proofDigest: ""
42
- proofDigestNow: ""
43
- # `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
44
- # git-tree half (task 0704 R4); resolved per-run by the verify state's taskpath shell step.
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: ""
50
- __hitlAnswer: ""
51
- # R8 (0366): injected by WorkflowAppService.run(); run-scopes every temporary capture.
52
- __runId: ""
53
-
54
- states:
55
- - id: precheck
56
- description: >
57
- Soft precheck: task check + agent doctor, each owned by a deterministic `command.gate`
58
- (D5-J, ADR-069). Both gates run `softFail: true`, so a red probe records FAIL and lets
59
- the transitions route to `failed` instead of aborting the run — the shipped action
60
- schema exposes no `onError`, so soft mode is what preserves that routing.
61
- The canonical `.spur/run/$__runId-docs-precheck.status` is the AND of the two gate files.
62
- (0769: temporary captures are run-scoped — `.spur/run/<runId>-docs-*` — so concurrent
63
- runs on one task never share or clobber a capture.)
64
- onEnter:
65
- - kind: command.gate
66
- options:
67
- id: docs-precheck-task
68
- executable: "${vars.spurBin}"
69
- args: ["task", "check", "${vars.wbs}"]
70
- resultFile: .spur/run/${vars.__runId}-docs-precheck-task.status
71
- softFail: true
72
- timeoutMs: 120000
73
- - kind: command.gate
74
- options:
75
- id: docs-precheck-agent
76
- executable: "${vars.spurBin}"
77
- args: ["agent", "doctor", "${vars.agent}"]
78
- resultFile: .spur/run/${vars.__runId}-docs-precheck-agent.status
79
- softFail: true
80
- timeoutMs: 120000
81
- - kind: shell
82
- options:
83
- command: >-
84
- if test "$(cat .spur/run/$__runId-docs-precheck-task.status 2>/dev/null)" = PASS &&
85
- test "$(cat .spur/run/$__runId-docs-precheck-agent.status 2>/dev/null)" = PASS;
86
- then printf 'PASS\n' > ".spur/run/$__runId-docs-precheck.status";
87
- else printf 'FAIL\n' > ".spur/run/$__runId-docs-precheck.status"; fi
88
- - kind: note
89
- options:
90
- message: "Docs pipeline start for task ${vars.wbs}."
91
-
92
- - id: draft
93
- description: >
94
- Author or update the documentation deliverable for task ${vars.wbs}. Pure slash
95
- implement hop (ADR-043). Docs-only scope is owned by the implement competency when
96
- the task template/background is documentation — not by YAML essays.
97
- onEnter:
98
- - kind: agent.run
99
- options:
100
- agent: ${vars.agent}
101
- # Pure slash only (ADR-043). Do not bolt docs-only prose onto the input line.
102
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
103
- role: coder
104
- input: /sp:dev-run --mode implement ${vars.wbs} --auto
105
- timeoutMs: ${vars.stepTimeoutMs}
106
- # Fail a silent no-op draft (same empty-implement class as task-pipeline R3).
107
- requireDiff: true
108
- - kind: shell
109
- options:
110
- command: "$spurBin task update $wbs wip --no-lifecycle"
111
-
112
- - id: docs-review
113
- description: >
114
- Human documentation review gate. Under profile=auto this state is skipped; in
115
- interactive mode it pauses for explicit approval before recording completion.
116
- pause: true
117
- onEnter:
118
- - kind: hitl.confirm
119
- options:
120
- prompt: "Approve docs-only deliverable for task ${vars.wbs}?"
121
-
122
- - id: verify
123
- description: >
124
- Read-only measured verification (task 0704, R2/R4/R7): dispatch /sp:dev-verify --fix none
125
- with an answer file, derive the ONE standard verdict via `spur task verdict`, register the
126
- standard verify-verdict artifact, and bracket the verifier with proof-input digest capture/compare so any
127
- task-spec or document mutation during certification fails closed (R4). No docs assurance
128
- subclass — PASS semantics are identical to task-pipeline (R7). 0769: verification now
129
- PRECEDES recording; the `verify → record` guard compares the proof bracket, and the
130
- verdict artifact keeps its `.spur/run/<wbs>-verdict.json` compatibility path with
131
- runId + definitionDigest stamped through the existing proof/run-artifact contract.
132
- onEnter:
133
- # 0769: clear any stale current-run answer before the fresh-session verifier writes it.
134
- - kind: shell
135
- options:
136
- command: "mkdir -p .spur/run && rm -f .spur/run/$__runId-docs-verify-answer.txt"
137
- # R4 (0703 R6 precedent): resolve the task-spec path BEFORE capture. `docs/tasks*` is
138
- # excluded from the digest's git-tree half, so the spec is folded in via `taskFile`.
139
- - kind: shell
140
- options:
141
- # (e) 0825: 6 lines — fail-closed lookup feeds the verdict/digest file names
142
- # (0760 R1); a silent miss would degrade the proof to tree-only.
143
- command: >-
144
- set -e;
145
- task_path="$($spurBin task path $wbs --json | jq -r '.path // .filePath // empty')";
146
- if [ -z "$task_path" ]; then echo "docs-pipeline: task path did not resolve for wbs $wbs" >&2; exit 1; fi;
147
- printf '%s' "$task_path" > ".spur/run/$__runId-docs-taskpath.txt"
148
- - kind: file.read.into-var
149
- options:
150
- path: .spur/run/${vars.__runId}-docs-taskpath.txt
151
- var: taskSpecPath
152
- # 0785 R2: resolve the linked feature spec path so the digest covers it too. An orphan task
153
- # (no feature_id) resolves to empty — legitimately omitted via empty-string compatibility;
154
- # a declared feature whose spec path does not resolve fails closed instead of silently
155
- # folding a stale spec into the proof.
156
- - kind: shell
157
- options:
158
- # (e) 0825: 9 lines — per-check rc capture (task show/feature show/jq) keeps an
159
- # orphan task and a broken linked feature distinguishable (0785 R2 contract).
160
- command: >-
161
- set -e;
162
- FID="$($spurBin task show $wbs --json 2>/dev/null | jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null)";
163
- FPATH_OUT="";
164
- if [ -n "$FID" ]; then
165
- FPATH_OUT="$($spurBin feature show "$FID" --json | jq -r '.filePath // .path // empty')";
166
- 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;
167
- fi;
168
- printf '%s' "$FPATH_OUT" > ".spur/run/$__runId-docs-featurepath.txt"
169
- - kind: file.read.into-var
170
- options:
171
- path: .spur/run/${vars.__runId}-docs-featurepath.txt
172
- var: featureSpecPath
173
- # R4: canonical capture BEFORE the verifier runs. Same fingerprint input as the
174
- # re-capture below so the bracket compares like for like.
175
- - kind: proof.fingerprint
176
- options:
177
- var: proofDigest
178
- taskFile: ${vars.taskSpecPath}
179
- featureFile: ${vars.featureSpecPath}
180
- input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
181
- timeoutMs: ${vars.stepTimeoutMs}
182
- - kind: agent.run
183
- options:
184
- agent: ${vars.agent}
185
- # Observe-only (ADR-071 precedent): the verifier certifies, it never repairs.
186
- # 0710 R2: verify runs in a fresh session — never inherits the implement hop's session.
187
- freshSession: true
188
- input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
189
- role: reviewer
190
- timeoutMs: ${vars.stepTimeoutMs}
191
- answerFile: .spur/run/${vars.__runId}-docs-verify-answer.txt
192
- expectFile: .spur/run/${vars.__runId}-docs-verify-answer.txt
193
- - kind: shell
194
- options:
195
- command: "$spurBin task verdict $wbs --from-answer .spur/run/$__runId-docs-verify-answer.txt"
196
- # R7: stamp the proof block + run identity into the standard verdict artifact, then register
197
- # it. Soft action + hard guard: a missing/malformed stamp fails the `verify -> record`
198
- # guard below, not this step.
199
- - kind: shell
200
- options:
201
- # (e) 0825: 6 lines — stamp shell freezes the docs proof digest for the closing
202
- # guard; the record→done guard compares against this stamp.
203
- command: >-
204
- V=".spur/run/$wbs-verdict.json";
205
- if [ -f "$V" ] && [ -n "$proofDigest" ]; then
206
- jq --arg d "$proofDigest" --arg r "$__runId" '. + {proof: {digest: $d, capturePoint: "docs-verify-entry", runId: $r}}' "$V" > "$V.tmp" && mv "$V.tmp" "$V";
207
- fi;
208
- exit 0
209
- - kind: run.artifact
210
- options:
211
- path: .spur/run/${vars.wbs}-verdict.json
212
- artifactKind: verify-verdict
213
- # R4: re-capture AFTER the verifier. No `expect` here — an action-level abort would
214
- # bypass the `failed` state; the `verify -> done` guard compares instead (R5).
215
- - kind: proof.fingerprint
216
- options:
217
- var: proofDigestNow
218
- taskFile: ${vars.taskSpecPath}
219
- featureFile: ${vars.featureSpecPath}
220
- input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
221
- timeoutMs: ${vars.stepTimeoutMs}
222
-
223
- - id: record
224
- description: >
225
- Record the docs-only outcome AFTER measured verification (task 0769 ordering:
226
- draft/approval → verify → record → done — the pre-verification record could write
227
- UNKNOWN/old Testing evidence). The verdict is already derived and proof-bracketed in
228
- `verify`; `verify → record` only opens on measured PASS with an intact bracket. The
229
- task record verb backfills Solution from the diff and moves the task to testing; its
230
- result is captured to a run-scoped status file so a record denial reaches `failed`
231
- (record → failed) and is never converted to success by an exit 0.
232
- onEnter:
233
- - kind: shell
234
- options:
235
- command: >-
236
- if $spurBin task record $wbs --solution-from-diff --transition testing; then
237
- printf 'PASS\n' > ".spur/run/$__runId-docs-record.status";
238
- else
239
- printf 'FAIL\n' > ".spur/run/$__runId-docs-record.status";
240
- fi
241
-
242
- - id: done
243
- description: Terminal — docs deliverable measured, reviewed, and recorded.
244
- onEnter:
245
- # R1 (0704): no synthetic verdict writer. The verdict artifact is produced by measured
246
- # verification in `verify` and registered there; done only performs the guarded
247
- # transition. Override path remains: --force-done --reason.
248
- - kind: shell
249
- options:
250
- command: "$spurBin task update $wbs done --no-lifecycle"
251
- - kind: note
252
- options:
253
- message: "Docs pipeline complete for task ${vars.wbs}."
254
- - id: failed
255
- description: Terminal — docs pipeline failed or was rejected.
256
-
257
- - id: cancelled
258
- description: Terminal — docs pipeline cancelled by operator.
259
-
260
- transitions:
261
- - from: precheck
262
- to: draft
263
- description: Precheck PASS — begin docs draft.
264
- guard:
265
- kind: shell
266
- options:
267
- command: 'test "$(cat .spur/run/$__runId-docs-precheck.status 2>/dev/null)" = PASS'
268
- - from: precheck
269
- to: failed
270
- description: Precheck FAIL — stop before draft.
271
- guard:
272
- kind: always
273
-
274
- - from: draft
275
- to: verify
276
- description: "profile=auto — skip the docs-review HITL gate; verify measures BEFORE record (0769: a pre-verification record could write UNKNOWN/old Testing evidence)."
277
- guard:
278
- kind: shell
279
- options:
280
- command: 'test "$profile" = auto'
281
- - from: draft
282
- to: docs-review
283
- description: Interactive — enter docs-review.
284
- guard:
285
- kind: always
286
-
287
- - from: docs-review
288
- to: verify
289
- description: "Docs approved — measure the deliverable (0769: verification precedes recording)."
290
- guard:
291
- kind: shell
292
- options:
293
- command: 'test "$__hitlAnswer" = yes'
294
- - from: docs-review
295
- to: failed
296
- description: Docs rejected — stop for revision.
297
- guard:
298
- kind: shell
299
- options:
300
- command: 'test "$__hitlAnswer" = no'
301
- - from: docs-review
302
- to: cancelled
303
- description: Operator cancelled docs review.
304
- guard:
305
- kind: shell
306
- options:
307
- command: 'test "$__hitlAnswer" = cancel'
308
-
309
- - from: verify
310
- to: record
311
- description: Measured PASS with the proof bracket intact (R4/R5) — record only verified
312
- outcomes (0769). Structural task-check gates remain at precheck, record (wip→testing),
313
- and done (verdict-artifact guard, R3).
314
- guard:
315
- kind: shell
316
- options:
317
- # (e) 0825: 8-line operand ladder condensed to one jq select — same file, same
318
- # digest-locked PASS semantics (0769 R4/R5); empty/malformed still fail closed.
319
- command: >-
320
- test -n "$proofDigest" &&
321
- test -n "$proofDigestNow" &&
322
- test "$proofDigestNow" = "$proofDigest" &&
323
- test "$(jq -r --arg d "$proofDigest" 'select(.proof.digest == $d) | .verdict // empty' ".spur/run/$wbs-verdict.json" 2>/dev/null)" = PASS
324
- - from: verify
325
- to: failed
326
- description: Non-PASS verdict, missing/malformed answer, or digest mismatch (R5) — the run
327
- reaches failed and nothing is recorded.
328
- guard:
329
- kind: always
330
-
331
- - from: record
332
- to: done
333
- description: Recorded from a verified outcome; the persisted verdict/bracket is re-asserted
334
- so a failed `task record` (captured FAIL) never converts to done (0769).
335
- guard:
336
- kind: shell
337
- options:
338
- # (e) 0825: 5-line guard — captured record PASS + digest-locked persisted verdict
339
- # must both hold before done; a failed `task record` never converts to success.
340
- command: >-
341
- test "$(cat .spur/run/$__runId-docs-record.status 2>/dev/null)" = PASS &&
342
- test -f ".spur/run/$wbs-verdict.json" &&
343
- test "$(jq -r '.verdict // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
344
- test -n "$proofDigest" &&
345
- test "$(jq -r '.proof.digest // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
346
- - from: record
347
- to: failed
348
- description: "Task record denied or errored (captured status not PASS) — the record denial reaches failed; the done guard above never converts it to success."
349
- guard:
350
- kind: always
@@ -1,288 +0,0 @@
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).
8
- #
9
- # Reliability (aligned with task-pipeline / ADR-043):
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.
13
- # - Every agent.run input is a pure slash command (no free-form pipeline essays)
14
- # - featureId must be supplied by the caller (workflow vars are not agent-mutated)
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).
20
-
21
- "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
22
- kind: state-machine
23
- name: feature-dev
24
- version: "3"
25
- description: >
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.
31
-
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).
40
-
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).
45
-
46
- Vars (passed as a JSON object via `--vars`):
47
- featureId — the existing feature ID this loop drives (required), e.g. `--vars '{"featureId":"B3"}'`
48
- agent — agent the agent.run steps invoke; override `--vars '{"agent":"claude"}'`
49
- profile — `auto` selects execute-tasks-auto (`/sp:dev-runall --tasks … --auto`); otherwise
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)
53
- spurBin — PATH-independent spur invocation (CLI overrides at run start; literal fallback)
54
- stepTimeoutMs — agent.run budget (ms) for the runall dispatch
55
-
56
- Reliability (ADR-043): every agent.run input is a pure slash command. featureId must be
57
- supplied by the caller (workflow vars cannot be mutated by the agent). profile is wired
58
- via two execute hops (templates cannot branch a single input string).
59
- iterationBound: 20
60
- initialState: precheck
61
- terminalStates:
62
- - done
63
- - failed
64
- failureStates:
65
- - failed
66
- vars:
67
- featureId: ""
68
- agent: "auto"
69
- profile: "standard"
70
- featureTaskIds: ""
71
- spurBin: "spur"
72
- stepTimeoutMs: "1800000"
73
- # Integration review (D5-P, feature R11). Advisory by default: a pending, timed-out, or
74
- # quota-unavailable review must never block a locally-verified feature. Set
75
- # requireCleanReview=true to opt into a hard gate for a branch that must ship clean.
76
- baseBranch: ""
77
- requireCleanReview: "false"
78
- # Injected by WorkflowAppService.run(); scopes .spur/run artifacts per run (0425 R4).
79
- __runId: ""
80
-
81
- states:
82
- - id: precheck
83
- description: >
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.
91
- onEnter:
92
- - kind: shell
93
- options:
94
- # (d) 0825: the 12-command identity/roster program moved into
95
- # plugins/sp/scripts/feature-dev-precheck.ts (§1.1 owner); same messages,
96
- # artifacts and soft-fail exit-0 contract, wrapper fails closed.
97
- command: >-
98
- mkdir -p .spur/run; if [ -f plugins/sp/scripts/feature-dev-precheck.ts ]; then bun plugins/sp/scripts/feature-dev-precheck.ts; elif W="$(superskill script path sp feature-dev-precheck.mjs 2>/dev/null)" && [ -f "$W" ]; then node "$W"; else echo "feature-dev precheck failed closed — feature-dev-precheck script not found — run 'superskill install sp'" >&2; printf 'FAIL\n' > ".spur/run/$__runId-feature-dev-precheck.status"; fi; exit 0
99
- - kind: note
100
- options:
101
- 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."
102
-
103
- # Two pure-slash execute hops so vars.profile actually controls --auto (ADR-043).
104
- # Engine templates cannot branch a single input string on profile; declaration-order
105
- # transitions pick the correct hop (auto first, then interactive fallback). Each hop first
106
- # reads the FROZEN todo list produced by precheck into featureTaskIds, then dispatches the
107
- # explicit list — the runall child never re-enumerates membership (0782 R2).
108
- - id: execute-tasks-auto
109
- description: >
110
- profile=auto — dispatch the frozen todo WBS list via pure slash
111
- /sp:dev-runall --tasks <list> --auto (skips per-task HITL inside task-pipeline).
112
- onEnter:
113
- - kind: file.read.into-var
114
- options:
115
- path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
116
- var: featureTaskIds
117
- trim: true
118
- - kind: agent.run
119
- options:
120
- agent: ${vars.agent}
121
- input: /sp:dev-runall --tasks ${vars.featureTaskIds} --auto
122
- answerFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
123
- expectFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
124
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
125
- role: planner
126
- timeoutMs: ${vars.stepTimeoutMs}
127
-
128
- - id: execute-tasks
129
- description: >
130
- Interactive — dispatch the frozen todo WBS list via pure slash
131
- /sp:dev-runall --tasks <list> without --auto (per-task HITL approve gates remain
132
- available).
133
- onEnter:
134
- - kind: file.read.into-var
135
- options:
136
- path: .spur/run/${vars.__runId}-feature-dev-tasks.txt
137
- var: featureTaskIds
138
- trim: true
139
- - kind: agent.run
140
- options:
141
- agent: ${vars.agent}
142
- input: /sp:dev-runall --tasks ${vars.featureTaskIds}
143
- answerFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
144
- expectFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
145
- # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
146
- role: planner
147
- timeoutMs: ${vars.stepTimeoutMs}
148
-
149
- - id: feature-verify
150
- description: >
151
- Certify the feature is complete (0782 R3): run `spur feature check --as done --json`
152
- EXACTLY ONCE, capture the exit code and JSON result at
153
- .spur/run/<runId>-feature-dev-verify.json, and decide PASS/FAIL into the .status
154
- sibling. PASS requires exit 0, a nonempty result array, and every returned scope
155
- pass === true. The guards below only read the captured status — no sibling guard
156
- re-runs the check. No --strict elevation, no whole-corpus scan.
157
- onEnter:
158
- - kind: shell
159
- options:
160
- # (e) 0825: 9 lines — `feature check --as done` runs EXACTLY ONCE per run (0782 R3)
161
- # and owns the status write the guards read; split would re-run the check.
162
- command: >-
163
- mkdir -p .spur/run; rm -f ".spur/run/$__runId-feature-dev-verify.status"; $spurBin feature check "$featureId" --as done --json > ".spur/run/$__runId-feature-dev-verify.json" 2>&1 && jq -e 'type == "array" and length > 0 and all(.[]; .pass == true)' ".spur/run/$__runId-feature-dev-verify.json" > /dev/null 2>&1 && R=PASS || R=FAIL; printf '%s\n' "$R" > ".spur/run/$__runId-feature-dev-verify.status.tmp" && mv -f ".spur/run/$__runId-feature-dev-verify.status.tmp" ".spur/run/$__runId-feature-dev-verify.status"
164
- - kind: note
165
- options:
166
- 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)."
167
-
168
- - id: integration-review
169
- description: >
170
- Advisory integration review (D5-P, feature R11), upgraded by 0770 to a
171
- truthful collect: ONE Codex review request per HEAD, then exactly ONE
172
- `pr-reviewing.ts collect --head <captured-head>` against that same HEAD —
173
- no wait loop, no polling. The request verdict (`REQUESTED` /
174
- `ALREADY_REQUESTED` / `ALREADY_REVIEWED`) describes request state only;
175
- it is NEVER clean evidence. The ONLY clean evidence is the collected
176
- status (CLEAN / FINDINGS / PENDING) persisted run-scoped at
177
- .spur/run/<runId>-integration-review-collect.status. A missing head SHA,
178
- an invalid collect result, or head drift records collect FAIL.
179
-
180
- D8 decision D1 (0753 R3): invoking `pr-reviewing.ts request` directly —
181
- the same per-HEAD dedupe-or-request logic `config/workflows/pr-review.yaml`
182
- uses internally. Request FAIL records FAIL in
183
- .spur/run/<runId>-integration-review.status, loudly (no softFail).
184
- `requireCleanReview=true` turns a non-clean COLLECTED verdict (including
185
- collect FAIL) into the blocking edge declared first below.
186
- onEnter:
187
- # (e) 0825: one request action + one collect action (same verdicts, same files,
188
- # collect gated on the captured head SHA as before) to hold the §1.2 budgets.
189
- - kind: shell
190
- options:
191
- command: >-
192
- mkdir -p .spur/run; bun "$(superskill script path sp pr-reviewing.ts)" request --base "$baseBranch" --json --status-file ".spur/run/$__runId-integration-review-pr-request.status" > ".spur/run/$__runId-integration-review.json" 2>&1 && printf 'PASS\n' > ".spur/run/$__runId-integration-review.status" || printf 'FAIL\n' > ".spur/run/$__runId-integration-review.status"; exit 0
193
- - kind: shell
194
- options:
195
- # (e) 0825: 8 lines — collect stays head-locked to the request record and records
196
- # its verdict status file; the rc/status echo is the audible soft-fail contract.
197
- command: >-
198
- if H=$(jq -er '.head // empty | select(. != "")' ".spur/run/$__runId-integration-review.json" 2>/dev/null); then bun "$(superskill script path sp pr-reviewing.ts)" collect --head "$H" --json --status-file ".spur/run/$__runId-integration-review-collect.status" > ".spur/run/$__runId-integration-review-collect.json" 2>&1; rc=$?; echo "integration-review: collect rc=$rc status=$(cat ".spur/run/$__runId-integration-review-collect.status" 2>/dev/null || echo missing) — verdict captured to .spur/run/$__runId-integration-review-collect.json" >&2; else echo "integration-review: request result carried no head SHA — collect not run, recorded FAIL" >&2; printf 'FAIL\n' > ".spur/run/$__runId-integration-review-collect.status"; fi; exit 0
199
- - kind: note
200
- options:
201
- 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."
202
-
203
- - id: done
204
- description: Terminal — the existing feature's roster executed and the feature verified.
205
-
206
- - id: failed
207
- description: Terminal — the roster contract was rejected at precheck, a task failed verification, or the feature check did not pass.
208
-
209
- transitions:
210
- # ── precheck routing (0782 R1/R2) — declaration order matters: the all-terminal
211
- # direct-verify edge is tried FIRST so a fully-terminal roster reaches completion
212
- # verification with zero execution model calls; then the profile hops; then the
213
- # failure fallback. ──
214
- - from: precheck
215
- to: feature-verify
216
- description: Roster PASS and every linked task is already terminal — verify the feature directly, zero execution model calls.
217
- guard:
218
- kind: shell
219
- options:
220
- 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"'
221
- - from: precheck
222
- to: execute-tasks-auto
223
- description: Roster PASS with todo work; profile=auto — batch the frozen list with --auto (no per-task HITL).
224
- guard:
225
- kind: shell
226
- options:
227
- 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'
228
- - from: precheck
229
- to: execute-tasks
230
- description: Roster PASS with todo work; interactive — batch the frozen list without --auto.
231
- guard:
232
- kind: shell
233
- options:
234
- command: 'test "$(cat ".spur/run/$__runId-feature-dev-precheck.status" 2>/dev/null)" = PASS && test -s ".spur/run/$__runId-feature-dev-tasks.txt"'
235
- - from: precheck
236
- to: failed
237
- description: Precheck FAIL (missing identity, malformed/empty roster, duplicate identities, or refinement-blocking statuses) — stop before any model dispatch.
238
- guard:
239
- kind: always
240
-
241
- - from: execute-tasks-auto
242
- to: feature-verify
243
- description: Auto batch finished — verify the feature.
244
- guard:
245
- kind: always
246
- - from: execute-tasks
247
- to: feature-verify
248
- description: Interactive batch finished — verify the feature.
249
- guard:
250
- kind: always
251
-
252
- # ── feature-verify gate: the captured one-shot check decision certifies review, else
253
- # fail. Declaration order: the pass guard is tried FIRST. The check itself is NOT
254
- # re-run here (0782 R3) — the shell decision file is the only evidence. ──
255
- - from: feature-verify
256
- to: integration-review
257
- description: Captured completion check is PASS — spend one integration review on this stable HEAD.
258
- guard:
259
- kind: shell
260
- options:
261
- command: 'test "$(cat ".spur/run/$__runId-feature-dev-verify.status" 2>/dev/null)" = PASS'
262
- - from: integration-review
263
- to: failed
264
- description: >
265
- Collected verdict is not CLEAN (FINDINGS / PENDING / collect FAIL or
266
- missing) and this branch opted into a hard gate (requireCleanReview=true).
267
- Exactly one collect ran against the captured request HEAD — no wait loop —
268
- so pending or findings stop the feature here with the actual reason.
269
- Ordered before the advisory edge so the strict policy wins when both
270
- would match.
271
- guard:
272
- kind: shell
273
- options:
274
- command: 'test "$requireCleanReview" = true && test "$(cat .spur/run/$__runId-integration-review-collect.status 2>/dev/null)" != CLEAN'
275
- - from: integration-review
276
- to: done
277
- description: >
278
- Collected CLEAN, or advisory mode (requireCleanReview != true) — the
279
- feature is complete. A non-clean advisory collect never blocks a
280
- locally-verified feature (feature R11); the note above preserves the
281
- collected non-clean state in the run record instead of hiding it.
282
- guard:
283
- kind: always
284
- - from: feature-verify
285
- to: failed
286
- description: Completion check missing, malformed, or non-PASS (or its evidence failed to persist) — block BEFORE integration review; no review is requested (0782 R3).
287
- guard:
288
- kind: always