@gobing-ai/spur 0.3.69 → 0.3.70

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 (67) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +0 -18
  3. package/config/plugin-scripts.json +8 -0
  4. package/config/workflow-composition-baseline.json +123 -377
  5. package/config/workflows/task-pipeline.yaml +32 -3
  6. package/package.json +9 -9
  7. package/plugins/sp/plugin.json +1 -1
  8. package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
  9. package/plugins/sp/scripts/verify-answer-lint.ts +386 -0
  10. package/plugins/sp/skills/code-verification/SKILL.md +11 -10
  11. package/plugins/sp/skills/code-verification/references/verdict-schema.md +10 -0
  12. package/plugins/sp/skills/spur-cli/references/history.md +1 -0
  13. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +16 -0
  14. package/spur.js +1262 -260
  15. package/web/_astro/BoardApp.DHj-03Dp.js +1 -0
  16. package/web/_astro/{BoardApp.BQFbkeqq.js → BoardApp.DOadeEJV.js} +77 -77
  17. package/web/_astro/{TaskDetail.Dl2Eaj1w.js → TaskDetail.DKzpkDj5.js} +1 -1
  18. package/web/_astro/{arc.uG14rp8A.js → arc.Bsa0gprH.js} +1 -1
  19. package/web/_astro/{architectureDiagram-3BPJPVTR.Dye6uD_x.js → architectureDiagram-3BPJPVTR.LOUZCDBC.js} +1 -1
  20. package/web/_astro/{blockDiagram-GPEHLZMM.B9Pkh7Hb.js → blockDiagram-GPEHLZMM.CGee4isG.js} +1 -1
  21. package/web/_astro/{c4Diagram-AAUBKEIU.C2x7SC_X.js → c4Diagram-AAUBKEIU.B2CrQJ_O.js} +1 -1
  22. package/web/_astro/channel.BGL7KSHC.js +1 -0
  23. package/web/_astro/{chunk-2J33WTMH.D2p4-nWk.js → chunk-2J33WTMH.DJvf8RiP.js} +1 -1
  24. package/web/_astro/{chunk-4BX2VUAB.S-6jf33o.js → chunk-4BX2VUAB.B_l35O-Y.js} +1 -1
  25. package/web/_astro/{chunk-55IACEB6.DVXj4Fdh.js → chunk-55IACEB6.DqbxMswU.js} +1 -1
  26. package/web/_astro/{chunk-727SXJPM.Dz689FMN.js → chunk-727SXJPM.D_7ZGNjE.js} +1 -1
  27. package/web/_astro/{chunk-AQP2D5EJ.KxYj5TnI.js → chunk-AQP2D5EJ.Cq6yZ0s8.js} +1 -1
  28. package/web/_astro/{chunk-FMBD7UC4.itTQyHQB.js → chunk-FMBD7UC4.BWrlUPYi.js} +1 -1
  29. package/web/_astro/{chunk-ND2GUHAM.euSrbJf5.js → chunk-ND2GUHAM.BPLaXFZH.js} +1 -1
  30. package/web/_astro/{chunk-QZHKN3VN.OWASJRQy.js → chunk-QZHKN3VN.BtmAEAwZ.js} +1 -1
  31. package/web/_astro/{classDiagram-4FO5ZUOK.BLvrlpNO.js → classDiagram-4FO5ZUOK.CWycT7Ia.js} +1 -1
  32. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BLvrlpNO.js → classDiagram-v2-Q7XG4LA2.CWycT7Ia.js} +1 -1
  33. package/web/_astro/{cose-bilkent-S5V4N54A.XBF-rmyD.js → cose-bilkent-S5V4N54A.DyxjSWon.js} +1 -1
  34. package/web/_astro/{cynefin-OW5HDTMX.DlCx762Z.js → cynefin-OW5HDTMX.DnNfLBpX.js} +1 -1
  35. package/web/_astro/{dagre-BM42HDAG.D17Rshxv.js → dagre-BM42HDAG.BFAvnjKD.js} +1 -1
  36. package/web/_astro/{diagram-2AECGRRQ.AhBIVJC8.js → diagram-2AECGRRQ.BAoNCFeB.js} +1 -1
  37. package/web/_astro/{diagram-5GNKFQAL.C9ximjyC.js → diagram-5GNKFQAL.DiiKe7BP.js} +1 -1
  38. package/web/_astro/{diagram-KO2AKTUF.CZb7Ru_9.js → diagram-KO2AKTUF.t_uOQWVP.js} +1 -1
  39. package/web/_astro/{diagram-LMA3HP47.BW7LwqoS.js → diagram-LMA3HP47.SB9997cP.js} +1 -1
  40. package/web/_astro/{diagram-OG6HWLK6.XC025W0V.js → diagram-OG6HWLK6.Bgsxgf5G.js} +1 -1
  41. package/web/_astro/{erDiagram-TEJ5UH35.CpMXmBDP.js → erDiagram-TEJ5UH35.DO9OAOAc.js} +1 -1
  42. package/web/_astro/{flowDiagram-I6XJVG4X.D2ednJWg.js → flowDiagram-I6XJVG4X.CIb41ZyL.js} +1 -1
  43. package/web/_astro/{ganttDiagram-6RSMTGT7.BjL9FGKO.js → ganttDiagram-6RSMTGT7.Dc8Lk0fV.js} +1 -1
  44. package/web/_astro/{gitGraphDiagram-PVQCEYII.B90g1VGk.js → gitGraphDiagram-PVQCEYII.CpPmMWrl.js} +1 -1
  45. package/web/_astro/index.C-t8kB0T.css +1 -0
  46. package/web/_astro/{infoDiagram-5YYISTIA.RqgycKtQ.js → infoDiagram-5YYISTIA.DxBXfQSf.js} +1 -1
  47. package/web/_astro/{ishikawaDiagram-YF4QCWOH.Ctn-zt6a.js → ishikawaDiagram-YF4QCWOH.gA6OKtXq.js} +1 -1
  48. package/web/_astro/{journeyDiagram-JHISSGLW.DJhT8Ctp.js → journeyDiagram-JHISSGLW.CMEyLMmm.js} +1 -1
  49. package/web/_astro/{kanban-definition-UN3LZRKU.BY1QdejI.js → kanban-definition-UN3LZRKU.CKXlUNCr.js} +1 -1
  50. package/web/_astro/{linear.Di7YObSt.js → linear.CpTGdVx3.js} +1 -1
  51. package/web/_astro/{mermaid.core.CbxtJS3Q.js → mermaid.core.c8SSCsE5.js} +4 -4
  52. package/web/_astro/{mindmap-definition-RKZ34NQL.CxvR4g_J.js → mindmap-definition-RKZ34NQL.BH59HDw6.js} +1 -1
  53. package/web/_astro/{pieDiagram-4H26LBE5.jNWqnBHH.js → pieDiagram-4H26LBE5.DmgFXkZR.js} +1 -1
  54. package/web/_astro/{quadrantDiagram-W4KKPZXB.BCp12MbA.js → quadrantDiagram-W4KKPZXB.DyfB7N4J.js} +1 -1
  55. package/web/_astro/{requirementDiagram-4Y6WPE33.Dxhm4TyR.js → requirementDiagram-4Y6WPE33.DosszoFo.js} +1 -1
  56. package/web/_astro/{sankeyDiagram-5OEKKPKP.BtQXp4J9.js → sankeyDiagram-5OEKKPKP.pBkHhvVN.js} +1 -1
  57. package/web/_astro/{sequenceDiagram-3UESZ5HK.BWEM1R_Q.js → sequenceDiagram-3UESZ5HK.BE8tmkNR.js} +1 -1
  58. package/web/_astro/{stateDiagram-AJRCARHV.BG3wUkWB.js → stateDiagram-AJRCARHV.C4n7vDiG.js} +1 -1
  59. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BLtMeFVP.js → stateDiagram-v2-BHNVJYJU.BpgwYlRy.js} +1 -1
  60. package/web/_astro/{timeline-definition-PNZ67QCA.D5fHo0az.js → timeline-definition-PNZ67QCA.DvcVKc-W.js} +1 -1
  61. package/web/_astro/{vennDiagram-CIIHVFJN.0DcuMluU.js → vennDiagram-CIIHVFJN.BLOk-UOl.js} +1 -1
  62. package/web/_astro/{wardleyDiagram-YWT4CUSO.BZ-dxgHm.js → wardleyDiagram-YWT4CUSO.CIzB7M2o.js} +1 -1
  63. package/web/_astro/{xychartDiagram-2RQKCTM6.Bg-XWF7z.js → xychartDiagram-2RQKCTM6.CGC_lZ19.js} +1 -1
  64. package/web/index.html +2 -2
  65. package/web/_astro/BoardApp.CTkqrhWd.js +0 -1
  66. package/web/_astro/channel.Dsvulp7W.js +0 -1
  67. package/web/_astro/index.BVXdIsZV.css +0 -1
@@ -21,65 +21,43 @@
21
21
  "modelQueries": ["draft", "verify"],
22
22
  "actions": {
23
23
  "precheck:onEnter:0": {
24
- "kind": "command.gate",
25
- "stateEffect": "read",
26
- "evidenceEffect": "write"
24
+ "kind": "command.gate"
27
25
  },
28
26
  "precheck:onEnter:1": {
29
- "kind": "command.gate",
30
- "stateEffect": "read",
31
- "evidenceEffect": "write"
27
+ "kind": "command.gate"
32
28
  },
33
29
  "precheck:onEnter:2": {
34
30
  "kind": "shell",
35
- "invocation": "if test \"$(cat .spur/run/$wbs-docs-precheck-task.status 2>/dev/null)\" = PASS &&\n test \"$(cat .spur/run/$wbs-docs-precheck-agent.status 2>/dev/null)\" = PASS;\nthen printf 'PASS\\n' > \".spur/run/$wbs-docs-precheck.status\"; else printf 'FAIL\\n' > \".spur/run/$wbs-docs-precheck.status\"; fi",
36
- "stateEffect": "read",
37
- "evidenceEffect": "write"
31
+ "invocation": "if test \"$(cat .spur/run/$wbs-docs-precheck-task.status 2>/dev/null)\" = PASS &&\n test \"$(cat .spur/run/$wbs-docs-precheck-agent.status 2>/dev/null)\" = PASS;\nthen printf 'PASS\\n' > \".spur/run/$wbs-docs-precheck.status\"; else printf 'FAIL\\n' > \".spur/run/$wbs-docs-precheck.status\"; fi"
38
32
  },
39
33
  "precheck:onEnter:3": {
40
- "kind": "note",
41
- "stateEffect": "read",
42
- "evidenceEffect": "none"
34
+ "kind": "note"
43
35
  },
44
36
  "draft:onEnter:0": {
45
37
  "kind": "agent.run",
46
- "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto",
47
- "stateEffect": "write",
48
- "evidenceEffect": "none"
38
+ "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto"
49
39
  },
50
40
  "draft:onEnter:1": {
51
41
  "kind": "shell",
52
- "invocation": "$spurBin task update $wbs wip --no-lifecycle",
53
- "stateEffect": "write",
54
- "evidenceEffect": "none"
42
+ "invocation": "$spurBin task update $wbs wip --no-lifecycle"
55
43
  },
56
44
  "docs-review:onEnter:0": {
57
- "kind": "hitl.confirm",
58
- "stateEffect": "read",
59
- "evidenceEffect": "none"
45
+ "kind": "hitl.confirm"
60
46
  },
61
47
  "record:onEnter:0": {
62
48
  "kind": "shell",
63
- "invocation": "$spurBin task record $wbs --solution-from-diff --transition testing",
64
- "stateEffect": "write",
65
- "evidenceEffect": "write"
49
+ "invocation": "$spurBin task record $wbs --solution-from-diff --transition testing"
66
50
  },
67
51
  "done:onEnter:0": {
68
52
  "kind": "shell",
69
- "invocation": "$spurBin task update $wbs done --no-lifecycle",
70
- "stateEffect": "read",
71
- "evidenceEffect": "write"
53
+ "invocation": "$spurBin task update $wbs done --no-lifecycle"
72
54
  },
73
55
  "done:onEnter:1": {
74
- "kind": "note",
75
- "stateEffect": "read",
76
- "evidenceEffect": "write"
56
+ "kind": "note"
77
57
  },
78
58
  "verify:onEnter:0": {
79
59
  "kind": "shell",
80
- "invocation": "$spurBin task path $wbs --json 2>/dev/null | jq -r \".path // .filePath // empty\" > \".spur/run/$wbs-docs-taskpath.txt\" || true; exit 0",
81
- "stateEffect": "write",
82
- "evidenceEffect": "none"
60
+ "invocation": "$spurBin task path $wbs --json 2>/dev/null | jq -r \".path // .filePath // empty\" > \".spur/run/$wbs-docs-taskpath.txt\" || true; exit 0"
83
61
  },
84
62
  "verify:onEnter:1": {
85
63
  "kind": "file.read.into-var"
@@ -90,26 +68,18 @@
90
68
  },
91
69
  "verify:onEnter:3": {
92
70
  "kind": "agent.run",
93
- "invocation": "/sp:dev-verify ${vars.wbs} --auto --fix none --focus all",
94
- "stateEffect": "write",
95
- "evidenceEffect": "none"
71
+ "invocation": "/sp:dev-verify ${vars.wbs} --auto --fix none --focus all"
96
72
  },
97
73
  "verify:onEnter:4": {
98
74
  "kind": "shell",
99
- "invocation": "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt",
100
- "stateEffect": "write",
101
- "evidenceEffect": "none"
75
+ "invocation": "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
102
76
  },
103
77
  "verify:onEnter:5": {
104
78
  "kind": "shell",
105
- "invocation": "V=\".spur/run/$wbs-verdict.json\"; if [ -f \"$V\" ] && [ -n \"$proofDigest\" ]; then\n jq --arg d \"$proofDigest\" '. + {proof: {digest: $d, capturePoint: \"docs-verify-entry\"}}' \"$V\" > \"$V.tmp\" && mv \"$V.tmp\" \"$V\";\nfi; exit 0",
106
- "stateEffect": "write",
107
- "evidenceEffect": "none"
79
+ "invocation": "V=\".spur/run/$wbs-verdict.json\"; if [ -f \"$V\" ] && [ -n \"$proofDigest\" ]; then\n jq --arg d \"$proofDigest\" '. + {proof: {digest: $d, capturePoint: \"docs-verify-entry\"}}' \"$V\" > \"$V.tmp\" && mv \"$V.tmp\" \"$V\";\nfi; exit 0"
108
80
  },
109
81
  "verify:onEnter:6": {
110
- "kind": "run.artifact",
111
- "stateEffect": "read",
112
- "evidenceEffect": "write"
82
+ "kind": "run.artifact"
113
83
  },
114
84
  "verify:onEnter:7": {
115
85
  "kind": "proof.fingerprint",
@@ -136,162 +106,103 @@
136
106
  "modelQueries": ["discovery", "feature-create", "ac-generate", "system-design", "decompose"],
137
107
  "actions": {
138
108
  "start:onEnter:0": {
139
- "kind": "note",
140
- "stateEffect": "read",
141
- "evidenceEffect": "none"
109
+ "kind": "note"
142
110
  },
143
111
  "start:onEnter:1": {
144
- "kind": "doctor.probe",
145
- "stateEffect": "read",
146
- "evidenceEffect": "write"
112
+ "kind": "doctor.probe"
147
113
  },
148
114
  "start:onEnter:2": {
149
115
  "kind": "shell",
150
- "invocation": "test -n \"$idea\" || printf 'FAIL\\n' > \".spur/run/$__runId-idea-precheck-doctor.status\"",
151
- "stateEffect": "read",
152
- "evidenceEffect": "write",
153
- "disposition": "GLUE"
116
+ "invocation": "test -n \"$idea\" || printf 'FAIL\\n' > \".spur/run/$__runId-idea-precheck-doctor.status\""
154
117
  },
155
118
  "discovery:onEnter:0": {
156
119
  "kind": "agent.run",
157
- "invocation": "Run sp:brainstorm for the idea: ${vars.idea}. 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}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per plugins/sp/skills/spur-dev/references/idea-evaluation.md (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation). At the END of the report, append a provenance footer block of the exact form: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339 timestamp>\\n---` (omit the footer only if run_id is empty).",
158
- "stateEffect": "read",
159
- "evidenceEffect": "write"
120
+ "invocation": "Run sp:brainstorm for the idea: ${vars.idea}. 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}) and the design summary per the skill's `Design Approval Gate` and `The needs_design signal` sections. Also emit the idea-evaluation report to .spur/run/${vars.__runId}-idea-eval-report.md per plugins/sp/skills/spur-dev/references/idea-evaluation.md (urgency/necessity 0–5, premises, pros/cons, alternatives, enhanced idea, recommendation). At the END of the report, append a provenance footer block of the exact form: `---\\nrun_id: ${vars.__runId}\\ngenerated_at: <RFC3339 timestamp>\\n---` (omit the footer only if run_id is empty)."
160
121
  },
161
122
  "idea-eval:onEnter:0": {
162
- "kind": "hitl.confirm",
163
- "stateEffect": "read",
164
- "evidenceEffect": "none"
123
+ "kind": "hitl.confirm"
165
124
  },
166
125
  "feature-create:onEnter:0": {
167
126
  "kind": "agent.run",
168
- "invocation": "Create a feature for the idea: ${vars.idea}. 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. 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.",
169
- "stateEffect": "write",
170
- "evidenceEffect": "write"
127
+ "invocation": "Create a feature for the idea: ${vars.idea}. 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. 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."
171
128
  },
172
129
  "feature-create:onEnter:1": {
173
- "kind": "file.read.into-var",
174
- "stateEffect": "read",
175
- "evidenceEffect": "none"
130
+ "kind": "file.read.into-var"
176
131
  },
177
132
  "feature-create:onEnter:2": {
178
133
  "kind": "shell",
179
- "invocation": "mkdir -p .spur/run && test -s .spur/run/$__runId-idea-goal.md && $spurBin feature update \"$featureId\" --section Goal --from-file .spur/run/$__runId-idea-goal.md",
180
- "stateEffect": "write",
181
- "evidenceEffect": "none"
134
+ "invocation": "mkdir -p .spur/run && test -s .spur/run/$__runId-idea-goal.md && $spurBin feature update \"$featureId\" --section Goal --from-file .spur/run/$__runId-idea-goal.md"
182
135
  },
183
136
  "feature-create:onEnter:3": {
184
137
  "kind": "shell",
185
- "invocation": "mkdir -p .spur/run && test -s .spur/run/$__runId-idea-scope.md && $spurBin feature update \"$featureId\" --section Scope --from-file .spur/run/$__runId-idea-scope.md",
186
- "stateEffect": "write",
187
- "evidenceEffect": "none"
138
+ "invocation": "mkdir -p .spur/run && test -s .spur/run/$__runId-idea-scope.md && $spurBin feature update \"$featureId\" --section Scope --from-file .spur/run/$__runId-idea-scope.md"
188
139
  },
189
140
  "ac-generate:onEnter:0": {
190
141
  "kind": "shell",
191
- "invocation": "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-ac-retry-count; rm -f .spur/run/$__runId-idea-ac-content.md .spur/run/$__runId-idea-ac-done.txt",
192
- "stateEffect": "read",
193
- "evidenceEffect": "write"
142
+ "invocation": "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-ac-retry-count; rm -f .spur/run/$__runId-idea-ac-content.md .spur/run/$__runId-idea-ac-done.txt"
194
143
  },
195
144
  "ac-generate:onEnter:1": {
196
145
  "kind": "agent.run",
197
- "invocation": "Generate acceptance criteria for feature ${vars.featureId}. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Output only the complete Acceptance Criteria section body, including the gherkin code fence, with no surrounding commentary and no section heading.",
198
- "stateEffect": "write",
199
- "evidenceEffect": "write"
146
+ "invocation": "Generate acceptance criteria for feature ${vars.featureId}. Read the feature file and author R-numbered BDD Gherkin scenarios per ac-style-guide.md. Output only the complete Acceptance Criteria section body, including the gherkin code fence, with no surrounding commentary and no section heading."
200
147
  },
201
148
  "ac-generate:onEnter:2": {
202
149
  "kind": "shell",
203
- "invocation": "if test -s .spur/run/$__runId-idea-ac-content.md; then\n $spurBin feature update \"$featureId\" --section \"Acceptance Criteria\" --from-file .spur/run/$__runId-idea-ac-content.md && date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-ac-done.txt;\nelse\n exit 0;\nfi",
204
- "stateEffect": "write",
205
- "evidenceEffect": "none",
206
- "disposition": "GLUE"
150
+ "invocation": "if test -s .spur/run/$__runId-idea-ac-content.md; then\n $spurBin feature update \"$featureId\" --section \"Acceptance Criteria\" --from-file .spur/run/$__runId-idea-ac-content.md && date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-ac-done.txt;\nelse\n exit 0;\nfi"
207
151
  },
208
152
  "feature-check:onEnter:0": {
209
- "kind": "hitl.confirm",
210
- "stateEffect": "read",
211
- "evidenceEffect": "none"
153
+ "kind": "hitl.confirm"
212
154
  },
213
155
  "system-design:onEnter:0": {
214
156
  "kind": "shell",
215
- "invocation": "mkdir -p .spur/run && REVIEW=\".spur/run/$__runId-idea-design-review.md\" && test -f \"$REVIEW\" || printf '## Proposed design\\n\\n## Operator feedback\\n\\n## Reconciliation\\n' > \"$REVIEW\"",
216
- "stateEffect": "read",
217
- "evidenceEffect": "write"
157
+ "invocation": "mkdir -p .spur/run && REVIEW=\".spur/run/$__runId-idea-design-review.md\" && test -f \"$REVIEW\" || printf '## Proposed design\\n\\n## Operator feedback\\n\\n## Reconciliation\\n' > \"$REVIEW\""
218
158
  },
219
159
  "system-design:onEnter:1": {
220
160
  "kind": "agent.run",
221
- "invocation": "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) following the constitution edit rules. Do not write task or feature corpus files directly. Design-review contract (.spur/run/${vars.__runId}-idea-design-review.md, fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): on the first pass write the proposed design summary under `## Proposed design` and leave `## Operator feedback` empty; on retry with operator feedback present, revise the design/ADR artifacts, document the changes under `## Reconciliation`, and when the feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file and persist it via `$spurBin feature update \"$featureId\" --section \"Acceptance Criteria\" --from-file <file>` — never edit feature corpus files directly.",
222
- "stateEffect": "write",
223
- "evidenceEffect": "write"
161
+ "invocation": "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) following the constitution edit rules. Do not write task or feature corpus files directly. Design-review contract (.spur/run/${vars.__runId}-idea-design-review.md, fixed headings `## Proposed design`, `## Operator feedback`, `## Reconciliation`): on the first pass write the proposed design summary under `## Proposed design` and leave `## Operator feedback` empty; on retry with operator feedback present, revise the design/ADR artifacts, document the changes under `## Reconciliation`, and when the feedback invalidates an Acceptance Criteria scenario write the revised AC section body to a file and persist it via `$spurBin feature update \"$featureId\" --section \"Acceptance Criteria\" --from-file <file>` — never edit feature corpus files directly."
224
162
  },
225
163
  "system-design:onEnter:2": {
226
164
  "kind": "shell",
227
- "invocation": "REVIEW=\".spur/run/$__runId-idea-design-review.md\" && awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' \"$REVIEW\" | grep -q '[^[:space:]]'",
228
- "stateEffect": "read",
229
- "evidenceEffect": "none"
165
+ "invocation": "REVIEW=\".spur/run/$__runId-idea-design-review.md\" && awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' \"$REVIEW\" | grep -q '[^[:space:]]'"
230
166
  },
231
167
  "design-approval:onEnter:0": {
232
168
  "kind": "shell",
233
- "invocation": "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-design-reject-count",
234
- "stateEffect": "read",
235
- "evidenceEffect": "write"
169
+ "invocation": "mkdir -p .spur/run && count=$(cat .spur/run/$__runId-idea-design-reject-count 2>/dev/null || echo 0); echo $((count + 1)) > .spur/run/$__runId-idea-design-reject-count"
236
170
  },
237
171
  "design-approval:onEnter:1": {
238
- "kind": "hitl.confirm",
239
- "stateEffect": "read",
240
- "evidenceEffect": "none"
172
+ "kind": "hitl.confirm"
241
173
  },
242
174
  "decompose:onEnter:0": {
243
175
  "kind": "shell",
244
- "invocation": "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",
245
- "stateEffect": "read",
246
- "evidenceEffect": "write"
176
+ "invocation": "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"
247
177
  },
248
178
  "decompose:onEnter:1": {
249
179
  "kind": "agent.run",
250
- "invocation": "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against apps/cli/schemas/task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. Acceptance Criteria, Design, and Plan sections are filled in by the per-task refine step after batch-create, NOT at decompose time. Validate locally against the schema before emitting. Also emit the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json: a JSON array (one entry per batch item) of `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring ordering/dependencies between the batch items; use `[]` when no ordering exists. Every `name` and every dependency must match exactly one batch item `name` — it is private workflow data, not part of task-batch.schema.json.",
251
- "stateEffect": "write",
252
- "evidenceEffect": "write"
180
+ "invocation": "Run sp:spec-decomposition for feature ${vars.featureId}. Read the brainstorm artifact, feature AC, and design doc. SIZING FIRST, before any JSON: apply the skill's `Default to NOT decomposing` rubric to the whole unit of work — if it scores 0-2 the correct output is a ONE-entry batch, not many. Scenario count is not task count: merge scenarios that one task delivers (same file surface, same subsystem, or unreadable apart in review), and list every scenario a task covers in its background. Merging never costs AC coverage — one task may carry several scenarios. Do not emit one entry per scenario or per requirement by reflex. Then produce a task-batch JSON array at .spur/run/${vars.__runId}-idea-task-batch.json, validated against apps/cli/schemas/task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. Acceptance Criteria, Design, and Plan sections are filled in by the per-task refine step after batch-create, NOT at decompose time. Validate locally against the schema before emitting. Also emit the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json: a JSON array (one entry per batch item) of `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring ordering/dependencies between the batch items; use `[]` when no ordering exists. Every `name` and every dependency must match exactly one batch item `name` — it is private workflow data, not part of task-batch.schema.json."
253
181
  },
254
182
  "decompose:onEnter:2": {
255
183
  "kind": "shell",
256
- "invocation": "BATCH=\".spur/run/$__runId-idea-task-batch.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && jq -e --slurpfile b \"$BATCH\" '\n (type == \"array\") and\n (($b[0] | map(.name) | length) == ($b[0] | map(.name) | unique | length)) and\n ((map(.name) | length) == (map(.name) | unique | length)) and\n ((map(.name) - ($b[0] | map(.name))) | length == 0) and\n ((($b[0] | map(.name)) - map(.name)) | length == 0) and\n (((map(.depends_on_names // []) | flatten | unique) - ($b[0] | map(.name))) | length == 0)\n' \"$ORDER\" >/dev/null",
257
- "stateEffect": "read",
258
- "evidenceEffect": "none",
259
- "disposition": "GLUE"
184
+ "invocation": "BATCH=\".spur/run/$__runId-idea-task-batch.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && jq -e --slurpfile b \"$BATCH\" '\n (type == \"array\") and\n (($b[0] | map(.name) | length) == ($b[0] | map(.name) | unique | length)) and\n ((map(.name) | length) == (map(.name) | unique | length)) and\n ((map(.name) - ($b[0] | map(.name))) | length == 0) and\n ((($b[0] | map(.name)) - map(.name)) | length == 0) and\n (((map(.depends_on_names // []) | flatten | unique) - ($b[0] | map(.name))) | length == 0)\n' \"$ORDER\" >/dev/null"
260
185
  },
261
186
  "batch-create:onEnter:0": {
262
- "kind": "hitl.confirm",
263
- "stateEffect": "read",
264
- "evidenceEffect": "none"
187
+ "kind": "hitl.confirm"
265
188
  },
266
189
  "batch-create-run:onEnter:0": {
267
190
  "kind": "shell",
268
- "invocation": "if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi; 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; 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\n mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;\nelse\n rm -f .spur/run/$__runId-idea-batch-create-result.json.tmp;\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;\nfi",
269
- "stateEffect": "write",
270
- "evidenceEffect": "write",
271
- "disposition": "GLUE"
191
+ "invocation": "if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi; 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; 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\n mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;\nelse\n rm -f .spur/run/$__runId-idea-batch-create-result.json.tmp;\n date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;\nfi"
272
192
  },
273
193
  "handoff-finalize:onEnter:0": {
274
194
  "kind": "shell",
275
- "invocation": "if [ -f packages/app/src/workflow/idea-handoff-cli.ts ]; then\n bun packages/app/src/workflow/idea-handoff-cli.ts;\n exit $?;\nfi; BATCH=\".spur/run/$__runId-idea-task-batch.json\" && RESULT=\".spur/run/$__runId-idea-batch-create-result.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && REPORT=\".spur/run/$__runId-idea-handoff.md\" && DEPMAP=\".spur/run/$__runId-idea-dep-map.tsv\" && CHECKS=\".spur/run/$__runId-idea-check-results.jsonl\" && rm -f \"$DEPMAP\" \"$CHECKS\" && jq -e --slurpfile b \"$BATCH\" '(.wbs | length) == ($b[0] | length) and (($b[0] | map(.name) | unique | length) == ($b[0] | length))' \"$RESULT\" >/dev/null && jq -r --slurpfile b \"$BATCH\" --slurpfile r \"$RESULT\" --slurpfile o \"$ORDER\" '\n ($b[0] | map(.name)) as $names | ($r[0].wbs) as $wbs |\n $o[0] | .[] |\n (.name as $n | $names | index($n)) as $i |\n (if $i == null or $wbs[$i] == null then \"MISSING\" else $wbs[$i] end) as $own |\n ((.depends_on_names // []) | map(. as $dep | $names | index($dep) as $d | if $d == null then \"MISSING\" else $wbs[$d] end) | join(\" \")) as $deps |\n [.name, $own, $deps] | @tsv\n' \"$ORDER\" > \"$DEPMAP\" && while IFS=\"$(printf '\\t')\" read -r name own deps; do\n test \"$own\" != MISSING || exit 1;\n if test -n \"$deps\"; then\n for d in $deps; do test \"$d\" != MISSING || exit 1; done;\n $spurBin task deps \"$own\" set $deps --json >/dev/null || exit 1;\n fi;\ndone < \"$DEPMAP\" && $spurBin feature refresh --feature \"$featureId\" --json >/dev/null && WBS_LIST=$(jq -r '.wbs | join(\" \")' \"$RESULT\") && for wbs in $WBS_LIST; do\n TMP=\".spur/run/$__runId-idea-check-$wbs.tmp\";\n if $spurBin task check \"$wbs\" --json > \"$TMP\" 2>&1; then P=true; else P=false; fi;\n jq -c --arg w \"$wbs\" --argjson p \"$P\" 'if (type == \"array\" and length > 0) then {wbs: $w, pass: $p, status: (.[0].status // \"unknown\")} else {wbs: $w, pass: $p, status: \"missing\"} end' \"$TMP\" >> \"$CHECKS\" || exit 1;\n rm -f \"$TMP\";\ndone && CHECK_ROWS=$(wc -l < \"$CHECKS\" | tr -d ' ') && WBS_COUNT=$(printf '%s\\n' $WBS_LIST | wc -l | tr -d ' ') && test \"$CHECK_ROWS\" = \"$WBS_COUNT\" && NEXT=$(jq -r --arg feature \"$featureId\" --slurpfile c \"$CHECKS\" 'if ($c | any(.[]; .pass == false)) then \"/sp:dev-refineall --feature \\($feature) --auto --depth ready\" else \"/sp:dev-runall --feature \\($feature) --auto\" end' \"$RESULT\") && {\n echo \"# Idea pipeline handoff report\";\n echo;\n echo \"Feature: $featureId\";\n echo \"Run ID: $__runId\";\n echo;\n echo \"## Created tasks\";\n printf '%s\\n' $WBS_LIST | sed 's/^/ - /';\n echo;\n echo \"## Per-task readiness (spur task check)\";\n echo;\n echo \"| WBS | Outcome |\";\n echo \"|-----|---------|\";\n while IFS= read -r line; do\n w=$(printf '%s' \"$line\" | jq -r '.wbs');\n o=$(printf '%s' \"$line\" | jq -r 'if .pass then \"PASS\" else \"FAIL\" end');\n printf '| %s | %s |\\n' \"$w\" \"$o\";\n done < \"$CHECKS\";\n echo;\n echo \"## Next command\";\n echo;\n echo \"$NEXT\";\n} > \"$REPORT\"",
276
- "stateEffect": "write",
277
- "evidenceEffect": "write",
278
- "disposition": "DUAL"
195
+ "invocation": "if [ -f packages/app/src/workflow/idea-handoff-cli.ts ]; then\n bun packages/app/src/workflow/idea-handoff-cli.ts;\n exit $?;\nfi; BATCH=\".spur/run/$__runId-idea-task-batch.json\" && RESULT=\".spur/run/$__runId-idea-batch-create-result.json\" && ORDER=\".spur/run/$__runId-idea-task-order.json\" && REPORT=\".spur/run/$__runId-idea-handoff.md\" && DEPMAP=\".spur/run/$__runId-idea-dep-map.tsv\" && CHECKS=\".spur/run/$__runId-idea-check-results.jsonl\" && rm -f \"$DEPMAP\" \"$CHECKS\" && jq -e --slurpfile b \"$BATCH\" '(.wbs | length) == ($b[0] | length) and (($b[0] | map(.name) | unique | length) == ($b[0] | length))' \"$RESULT\" >/dev/null && jq -r --slurpfile b \"$BATCH\" --slurpfile r \"$RESULT\" --slurpfile o \"$ORDER\" '\n ($b[0] | map(.name)) as $names | ($r[0].wbs) as $wbs |\n $o[0] | .[] |\n (.name as $n | $names | index($n)) as $i |\n (if $i == null or $wbs[$i] == null then \"MISSING\" else $wbs[$i] end) as $own |\n ((.depends_on_names // []) | map(. as $dep | $names | index($dep) as $d | if $d == null then \"MISSING\" else $wbs[$d] end) | join(\" \")) as $deps |\n [.name, $own, $deps] | @tsv\n' \"$ORDER\" > \"$DEPMAP\" && while IFS=\"$(printf '\\t')\" read -r name own deps; do\n test \"$own\" != MISSING || exit 1;\n if test -n \"$deps\"; then\n for d in $deps; do test \"$d\" != MISSING || exit 1; done;\n $spurBin task deps \"$own\" set $deps --json >/dev/null || exit 1;\n fi;\ndone < \"$DEPMAP\" && $spurBin feature refresh --feature \"$featureId\" --json >/dev/null && WBS_LIST=$(jq -r '.wbs | join(\" \")' \"$RESULT\") && for wbs in $WBS_LIST; do\n TMP=\".spur/run/$__runId-idea-check-$wbs.tmp\";\n if $spurBin task check \"$wbs\" --json > \"$TMP\" 2>&1; then P=true; else P=false; fi;\n jq -c --arg w \"$wbs\" --argjson p \"$P\" 'if (type == \"array\" and length > 0) then {wbs: $w, pass: $p, status: (.[0].status // \"unknown\")} else {wbs: $w, pass: $p, status: \"missing\"} end' \"$TMP\" >> \"$CHECKS\" || exit 1;\n rm -f \"$TMP\";\ndone && CHECK_ROWS=$(wc -l < \"$CHECKS\" | tr -d ' ') && WBS_COUNT=$(printf '%s\\n' $WBS_LIST | wc -l | tr -d ' ') && test \"$CHECK_ROWS\" = \"$WBS_COUNT\" && NEXT=$(jq -r --arg feature \"$featureId\" --slurpfile c \"$CHECKS\" 'if ($c | any(.[]; .pass == false)) then \"/sp:dev-refineall --feature \\($feature) --auto --depth ready\" else \"/sp:dev-runall --feature \\($feature) --auto\" end' \"$RESULT\") && {\n echo \"# Idea pipeline handoff report\";\n echo;\n echo \"Feature: $featureId\";\n echo \"Run ID: $__runId\";\n echo;\n echo \"## Created tasks\";\n printf '%s\\n' $WBS_LIST | sed 's/^/ - /';\n echo;\n echo \"## Per-task readiness (spur task check)\";\n echo;\n echo \"| WBS | Outcome |\";\n echo \"|-----|---------|\";\n while IFS= read -r line; do\n w=$(printf '%s' \"$line\" | jq -r '.wbs');\n o=$(printf '%s' \"$line\" | jq -r 'if .pass then \"PASS\" else \"FAIL\" end');\n printf '| %s | %s |\\n' \"$w\" \"$o\";\n done < \"$CHECKS\";\n echo;\n echo \"## Next command\";\n echo;\n echo \"$NEXT\";\n} > \"$REPORT\""
279
196
  },
280
197
  "handoff:onEnter:0": {
281
- "kind": "note",
282
- "stateEffect": "read",
283
- "evidenceEffect": "none"
198
+ "kind": "note"
284
199
  },
285
200
  "handoff:onEnter:1": {
286
- "kind": "run.artifact",
287
- "stateEffect": "read",
288
- "evidenceEffect": "write"
201
+ "kind": "run.artifact"
289
202
  },
290
203
  "handoff:onEnter:2": {
291
204
  "kind": "shell",
292
- "invocation": "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",
293
- "stateEffect": "write",
294
- "evidenceEffect": "write"
205
+ "invocation": "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"
295
206
  }
296
207
  },
297
208
  "digest": "sha256:acc878217ffc7e31d31600b59d5b240588a079638d2eb0caff8c7a78524220b9"
@@ -308,70 +219,47 @@
308
219
  "actions": {
309
220
  "precheck:onEnter:0": {
310
221
  "kind": "shell",
311
- "invocation": "DIRTY=$(git status --porcelain -- . ':(exclude)docs/tasks*' ':(exclude)docs/features' 2>/dev/null); if [ -n \"$DIRTY\" ]; then\n echo \"precheck: WARNING - working tree has uncommitted non-corpus changes; commit or stash before starting a new task:\";\n echo \"$DIRTY\";\nfi; CORPUS_DIRTY=$(git status --porcelain -- ':(glob)docs/tasks*/**' 2>/dev/null); if [ -n \"$CORPUS_DIRTY\" ]; then\n echo \"precheck: NOTE - task corpus has uncommitted changes; review before staging with this task:\";\n echo \"$CORPUS_DIRTY\";\nfi; exit 0",
312
- "stateEffect": "read",
313
- "evidenceEffect": "none",
314
- "disposition": "GLUE"
222
+ "invocation": "DIRTY=$(git status --porcelain -- . ':(exclude)docs/tasks*' ':(exclude)docs/features' 2>/dev/null); if [ -n \"$DIRTY\" ]; then\n echo \"precheck: WARNING - working tree has uncommitted non-corpus changes; commit or stash before starting a new task:\";\n echo \"$DIRTY\";\nfi; CORPUS_DIRTY=$(git status --porcelain -- ':(glob)docs/tasks*/**' 2>/dev/null); if [ -n \"$CORPUS_DIRTY\" ]; then\n echo \"precheck: NOTE - task corpus has uncommitted changes; review before staging with this task:\";\n echo \"$CORPUS_DIRTY\";\nfi; exit 0"
315
223
  },
316
224
  "precheck:onEnter:1": {
317
- "kind": "note",
318
- "stateEffect": "read",
319
- "evidenceEffect": "none"
225
+ "kind": "note"
320
226
  },
321
227
  "precheck:onEnter:2": {
322
228
  "kind": "shell",
323
- "invocation": "if [ \"$profile\" = auto ]; then\n FID=$($spurBin task show $wbs --json 2>/dev/null |\n jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);\n if [ -n \"$FID\" ]; then\n if ! $spurBin feature sync \"$FID\" --force 2>/dev/null; then\n if ! $spurBin feature update \"$FID\" active 2>/dev/null; then\n echo \"precheck: FAIL - feature reactivation $FID failed;\" >&2;\n echo \"precheck: feature sync + feature update both errored\" >&2;\n exit 1;\n fi;\n fi;\n fi;\nfi; exit 0",
324
- "stateEffect": "write",
325
- "evidenceEffect": "none",
326
- "disposition": "GLUE"
229
+ "invocation": "if [ \"$profile\" = auto ]; then\n FID=$($spurBin task show $wbs --json 2>/dev/null |\n jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);\n if [ -n \"$FID\" ]; then\n if ! $spurBin feature sync \"$FID\" --force 2>/dev/null; then\n if ! $spurBin feature update \"$FID\" active 2>/dev/null; then\n echo \"precheck: FAIL - feature reactivation $FID failed;\" >&2;\n echo \"precheck: feature sync + feature update both errored\" >&2;\n exit 1;\n fi;\n fi;\n fi;\nfi; exit 0"
327
230
  },
328
231
  "precheck:onEnter:3": {
329
232
  "kind": "shell",
330
- "invocation": "SIZE_FILE=\".spur/run/$wbs-precheck-size.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then bun plugins/sp/scripts/task-size-precheck.ts \"$wbs\" --spur-bin \"$spurBin\" --max-reqs \"$maxImplementReqs\" --max-plan-items \"$maxImplementPlanItems\"; else\n echo \"task-size-precheck failed closed — checker script\" >&2 &&\n echo \"plugins/sp/scripts/task-size-precheck.ts absent.\" >&2 &&\n echo \"FAIL\" > \"$SIZE_FILE\";\nfi && exit 0",
331
- "stateEffect": "read",
332
- "evidenceEffect": "write",
333
- "disposition": "EXT"
233
+ "invocation": "SIZE_FILE=\".spur/run/$wbs-precheck-size.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-size-precheck.ts ]; then bun plugins/sp/scripts/task-size-precheck.ts \"$wbs\" --spur-bin \"$spurBin\" --max-reqs \"$maxImplementReqs\" --max-plan-items \"$maxImplementPlanItems\"; else\n echo \"task-size-precheck failed closed — checker script\" >&2 &&\n echo \"plugins/sp/scripts/task-size-precheck.ts absent.\" >&2 &&\n echo \"FAIL\" > \"$SIZE_FILE\";\nfi && exit 0"
234
+ },
235
+ "precheck:onEnter:4": {
236
+ "kind": "shell",
237
+ "invocation": "EVID_FILE=\".spur/run/$wbs-precheck-evidence.status\" && mkdir -p .spur/run && if [ -f plugins/sp/scripts/task-evidence-precheck.ts ]; then bun plugins/sp/scripts/task-evidence-precheck.ts \"$wbs\" --spur-bin \"$spurBin\"; else\n echo \"task-evidence-precheck failed closed —\" >&2 &&\n echo \"plugins/sp/scripts/task-evidence-precheck.ts absent.\" >&2 &&\n echo \"FAIL\" > \"$EVID_FILE\";\nfi && exit 0"
334
238
  },
335
239
  "implement:onEnter:0": {
336
240
  "kind": "agent.run",
337
- "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto",
338
- "stateEffect": "write",
339
- "evidenceEffect": "none"
241
+ "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto"
340
242
  },
341
243
  "implement:onEnter:1": {
342
244
  "kind": "shell",
343
- "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" wip --no-lifecycle",
344
- "stateEffect": "write",
345
- "evidenceEffect": "none",
346
- "disposition": "GLUE"
245
+ "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" wip --no-lifecycle"
347
246
  },
348
247
  "implement:onEnter:2": {
349
248
  "kind": "shell",
350
- "invocation": "$formatCmd ; exit 0",
351
- "stateEffect": "write",
352
- "evidenceEffect": "none"
249
+ "invocation": "$formatCmd ; exit 0"
353
250
  },
354
251
  "test:onEnter:0": {
355
252
  "kind": "shell",
356
- "invocation": "$spurBin task path $wbs --json 2>/dev/null | jq -r \".path // .filePath // empty\" > \".spur/run/$wbs-taskpath.txt\" || true; sed -n \"s/^priority:[[:space:]]*//p\" \"$(cat \".spur/run/$wbs-taskpath.txt\" 2>/dev/null)\" 2>/dev/null | head -1 | tr -d \"[:space:]\" | tr \"[:lower:]\" \"[:upper:]\" > \".spur/run/$wbs-priority.txt\"; exit 0",
357
- "stateEffect": "write",
358
- "evidenceEffect": "write",
359
- "disposition": "POLICY"
253
+ "invocation": "$spurBin task path $wbs --json 2>/dev/null | jq -r \".path // .filePath // empty\" > \".spur/run/$wbs-taskpath.txt\" || true; sed -n \"s/^priority:[[:space:]]*//p\" \"$(cat \".spur/run/$wbs-taskpath.txt\" 2>/dev/null)\" 2>/dev/null | head -1 | tr -d \"[:space:]\" | tr \"[:lower:]\" \"[:upper:]\" > \".spur/run/$wbs-priority.txt\"; exit 0"
360
254
  },
361
255
  "test:onEnter:1": {
362
- "kind": "file.read.into-var",
363
- "stateEffect": "read",
364
- "evidenceEffect": "none"
256
+ "kind": "file.read.into-var"
365
257
  },
366
258
  "test:onEnter:2": {
367
- "kind": "file.read.into-var",
368
- "stateEffect": "read",
369
- "evidenceEffect": "none"
259
+ "kind": "file.read.into-var"
370
260
  },
371
261
  "test:onEnter:3": {
372
- "kind": "proof.fingerprint",
373
- "stateEffect": "write",
374
- "evidenceEffect": "write"
262
+ "kind": "proof.fingerprint"
375
263
  },
376
264
  "test:onEnter:4": {
377
265
  "kind": "shell",
@@ -379,111 +267,75 @@
379
267
  },
380
268
  "test-fix:onEnter:0": {
381
269
  "kind": "shell",
382
- "invocation": "mkdir -p .spur/run && ATTEMPT_FILE=\".spur/run/$wbs-test-fix-attempt\" && n=$(cat \"$ATTEMPT_FILE\" 2>/dev/null || echo 0) && printf '%s\\n' \"$((n + 1))\" > \"$ATTEMPT_FILE\" && if [ -f \".spur/run/$wbs-verdict.json\" ]; then\n { echo '--- verify verdict (remediation input, task 0703 R4) ---';\n cat \".spur/run/$wbs-verdict.json\";\n } >> \".spur/run/$wbs-test-gate.log\";\nfi; exit 0",
383
- "stateEffect": "write",
384
- "evidenceEffect": "write"
270
+ "invocation": "mkdir -p .spur/run && ATTEMPT_FILE=\".spur/run/$wbs-test-fix-attempt\" && n=$(cat \"$ATTEMPT_FILE\" 2>/dev/null || echo 0) && printf '%s\\n' \"$((n + 1))\" > \"$ATTEMPT_FILE\" && if [ -f \".spur/run/$wbs-verdict.json\" ]; then\n { echo '--- verify verdict (remediation input, task 0703 R4) ---';\n cat \".spur/run/$wbs-verdict.json\";\n } >> \".spur/run/$wbs-test-gate.log\";\nfi; exit 0"
385
271
  },
386
272
  "test-fix:onEnter:1": {
387
- "kind": "file.read.into-var",
388
- "stateEffect": "read",
389
- "evidenceEffect": "none"
273
+ "kind": "file.read.into-var"
390
274
  },
391
275
  "test-fix:onEnter:2": {
392
276
  "kind": "agent.run",
393
- "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings \"${vars.gateFindings}\"",
394
- "stateEffect": "write",
395
- "evidenceEffect": "none"
277
+ "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings \"${vars.gateFindings}\""
396
278
  },
397
279
  "test-recheck:onEnter:0": {
398
- "kind": "proof.fingerprint",
399
- "stateEffect": "write",
400
- "evidenceEffect": "write",
401
- "disposition": "POLICY"
280
+ "kind": "proof.fingerprint"
402
281
  },
403
282
  "test-recheck:onEnter:1": {
404
283
  "kind": "shell",
405
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$wbs-test-gate.status\" && LOG_FILE=\".spur/run/$wbs-test-gate.log\" && FINDINGS_FILE=\".spur/run/$wbs-test-gate.findings\" && : > \"$LOG_FILE\" && probe_rc=0; if [ -n \"$gateProbeCmd\" ]; then\n sh -c \"$gateProbeCmd\" > \"$LOG_FILE.probe\" 2>&1; probe_rc=$?;\n cat \"$LOG_FILE.probe\" >> \"$LOG_FILE\";\n rm -f \"$LOG_FILE.probe\";\nfi; if [ \"$probe_rc\" -ne 0 ]; then\n gate_rc=$probe_rc;\nelse\n gate_attempt=1;\n while [ \"$gate_attempt\" -le 5 ]; do\n ATTEMPT_LOG=\"$LOG_FILE.attempt-$gate_attempt\";\n sh -c \"$qualityGateCmd\" > \"$ATTEMPT_LOG\" 2>&1; gate_rc=$?;\n gate_locked=0;\n grep -q 'SQLiteError: database is locked' \"$ATTEMPT_LOG\" && gate_locked=1;\n cat \"$ATTEMPT_LOG\" >> \"$LOG_FILE\";\n rm -f \"$ATTEMPT_LOG\";\n if [ \"$gate_rc\" -eq 0 ] || [ \"$gate_locked\" -ne 1 ] || [ \"$gate_attempt\" -ge 5 ]; then break; fi;\n printf 'quality gate: database is locked; retrying (%s/5) in 10s\\n' \"$gate_attempt\" | tee -a \"$LOG_FILE\";\n sleep 10;\n gate_attempt=$((gate_attempt + 1));\n done;\nfi && cat \"$LOG_FILE\" && set +e; grep -oE '[A-Za-z0-9_./-]+\\.[A-Za-z]+:[0-9]+' \"$LOG_FILE\" | sort -u | head -20 | tr '\\n' ' ' > \"$FINDINGS_FILE\"; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && printf 'proof-digest: %s\\n' \"$proofDigest\" >> \"$LOG_FILE\" && exit 0",
406
- "stateEffect": "write",
407
- "evidenceEffect": "write"
284
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$wbs-test-gate.status\" && LOG_FILE=\".spur/run/$wbs-test-gate.log\" && FINDINGS_FILE=\".spur/run/$wbs-test-gate.findings\" && : > \"$LOG_FILE\" && probe_rc=0; if [ -n \"$gateProbeCmd\" ]; then\n sh -c \"$gateProbeCmd\" > \"$LOG_FILE.probe\" 2>&1; probe_rc=$?;\n cat \"$LOG_FILE.probe\" >> \"$LOG_FILE\";\n rm -f \"$LOG_FILE.probe\";\nfi; if [ \"$probe_rc\" -ne 0 ]; then\n gate_rc=$probe_rc;\nelse\n gate_attempt=1;\n while [ \"$gate_attempt\" -le 5 ]; do\n ATTEMPT_LOG=\"$LOG_FILE.attempt-$gate_attempt\";\n sh -c \"$qualityGateCmd\" > \"$ATTEMPT_LOG\" 2>&1; gate_rc=$?;\n gate_locked=0;\n grep -q 'SQLiteError: database is locked' \"$ATTEMPT_LOG\" && gate_locked=1;\n cat \"$ATTEMPT_LOG\" >> \"$LOG_FILE\";\n rm -f \"$ATTEMPT_LOG\";\n if [ \"$gate_rc\" -eq 0 ] || [ \"$gate_locked\" -ne 1 ] || [ \"$gate_attempt\" -ge 5 ]; then break; fi;\n printf 'quality gate: database is locked; retrying (%s/5) in 10s\\n' \"$gate_attempt\" | tee -a \"$LOG_FILE\";\n sleep 10;\n gate_attempt=$((gate_attempt + 1));\n done;\nfi && cat \"$LOG_FILE\" && set +e; grep -oE '[A-Za-z0-9_./-]+\\.[A-Za-z]+:[0-9]+' \"$LOG_FILE\" | sort -u | head -20 | tr '\\n' ' ' > \"$FINDINGS_FILE\"; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && printf 'proof-digest: %s\\n' \"$proofDigest\" >> \"$LOG_FILE\" && exit 0"
408
285
  },
409
286
  "review:onEnter:0": {
410
287
  "kind": "agent.run",
411
- "invocation": "/sp:dev-review ${vars.wbs} --auto",
412
- "stateEffect": "may-write",
413
- "evidenceEffect": "write"
288
+ "invocation": "/sp:dev-review ${vars.wbs} --auto"
414
289
  },
415
290
  "approve:onEnter:0": {
416
- "kind": "hitl.confirm",
417
- "stateEffect": "read",
418
- "evidenceEffect": "none"
291
+ "kind": "hitl.confirm"
419
292
  },
420
293
  "verify:onEnter:0": {
421
- "kind": "proof.fingerprint",
422
- "stateEffect": "may-write",
423
- "evidenceEffect": "write"
294
+ "kind": "proof.fingerprint"
424
295
  },
425
296
  "verify:onEnter:1": {
426
297
  "kind": "agent.run",
427
- "invocation": "/sp:dev-verify ${vars.wbs} --auto --fix none --focus all",
428
- "stateEffect": "read",
429
- "evidenceEffect": "write"
298
+ "invocation": "/sp:dev-verify ${vars.wbs} --auto --fix none --focus all"
430
299
  },
431
300
  "verify:onEnter:2": {
432
301
  "kind": "shell",
433
- "invocation": "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt",
434
- "stateEffect": "read",
435
- "evidenceEffect": "write"
302
+ "invocation": "bun plugins/sp/scripts/verify-answer-lint.ts \"$wbs\" --answer \".spur/run/$wbs-verify-answer.txt\" --spur-bin \"$spurBin\""
436
303
  },
437
304
  "verify:onEnter:3": {
438
305
  "kind": "shell",
439
- "invocation": "V=\".spur/run/$wbs-verdict.json\"; if [ -f \"$V\" ] && [ -n \"$proofDigest\" ]; then\n jq --arg d \"$proofDigest\" --arg g \"$(cat .spur/run/$wbs-test-gate.status 2>/dev/null || echo UNKNOWN)\"\n '. + {proof: {digest: $d, capturePoint: \"quality-gate-entry\", stages: {\n qualityGate: {status: $g, digest: $d},\n review: {status: \"completed\", digest: $d},\n verification: {status: .verdict, digest: $d}}}}\n | .checks += [{name: \"proof-input-digest\", status: \"pass\", evidence: $d}]'\n \"$V\" > \"$V.tmp\" && mv \"$V.tmp\" \"$V\";\nfi; exit 0",
440
- "stateEffect": "read",
441
- "evidenceEffect": "none"
306
+ "invocation": "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
307
+ },
308
+ "verify:onEnter:4": {
309
+ "kind": "shell",
310
+ "invocation": "V=\".spur/run/$wbs-verdict.json\"; if [ -f \"$V\" ] && [ -n \"$proofDigest\" ]; then\n jq --arg d \"$proofDigest\" --arg g \"$(cat .spur/run/$wbs-test-gate.status 2>/dev/null || echo UNKNOWN)\"\n '. + {proof: {digest: $d, capturePoint: \"quality-gate-entry\", stages: {\n qualityGate: {status: $g, digest: $d},\n review: {status: \"completed\", digest: $d},\n verification: {status: .verdict, digest: $d}}}}\n | .checks += [{name: \"proof-input-digest\", status: \"pass\", evidence: $d}]'\n \"$V\" > \"$V.tmp\" && mv \"$V.tmp\" \"$V\";\nfi; exit 0"
442
311
  },
443
312
  "record:onEnter:0": {
444
- "kind": "proof.fingerprint",
445
- "stateEffect": "read",
446
- "evidenceEffect": "none"
313
+ "kind": "proof.fingerprint"
447
314
  },
448
315
  "record:onEnter:1": {
449
316
  "kind": "shell",
450
- "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task record \"$wbs\" --solution-from-diff --transition testing",
451
- "stateEffect": "write",
452
- "evidenceEffect": "write",
453
- "disposition": "GLUE"
317
+ "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task record \"$wbs\" --solution-from-diff --transition testing"
454
318
  },
455
319
  "record:onEnter:2": {
456
320
  "kind": "shell",
457
- "invocation": "FID=$($spurBin task show $wbs --json 2>/dev/null | jq -r \".feature_id // .frontmatter.feature_id // empty\" 2>/dev/null); if [ -n \"$FID\" ]; then\n if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$FID\" --spur-bin \"$spurBin\" --json;\n else\n $spurBin feature sync \"$FID\" --json;\n fi;\nelse\n echo \"Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature.\" >> \".spur/run/$wbs-report.txt\";\nfi; exit 0",
458
- "stateEffect": "write",
459
- "evidenceEffect": "none",
460
- "disposition": "EXT"
321
+ "invocation": "FID=$($spurBin task show $wbs --json 2>/dev/null | jq -r \".feature_id // .frontmatter.feature_id // empty\" 2>/dev/null); if [ -n \"$FID\" ]; then\n if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$FID\" --spur-bin \"$spurBin\" --json;\n else\n $spurBin feature sync \"$FID\" --json;\n fi;\nelse\n echo \"Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature.\" >> \".spur/run/$wbs-report.txt\";\nfi; exit 0"
461
322
  },
462
323
  "done:onEnter:0": {
463
324
  "kind": "shell",
464
- "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" done --no-lifecycle",
465
- "stateEffect": "write",
466
- "evidenceEffect": "none",
467
- "disposition": "GLUE"
325
+ "invocation": "retry_transient() {\n RETRY_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_OUTPUT\";\n if [ \"$RETRY_RC\" -eq 0 ]; then return 0; fi;\n if ! printf '%s' \"$RETRY_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return \"$RETRY_RC\"; fi;\n sleep 2;\n RETRY_SECOND_OUTPUT=$(\"$@\" 2>&1); RETRY_RC=$?;\n printf '%s\\n' \"$RETRY_SECOND_OUTPUT\";\n if [ \"$RETRY_RC\" -ne 0 ] && printf '%s\\n%s' \"$RETRY_OUTPUT\" \"$RETRY_SECOND_OUTPUT\" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then\n echo \"node_modules link/dependency broken — run bun install and retry\";\n fi;\n return \"$RETRY_RC\";\n}; retry_transient $spurBin task update \"$wbs\" done --no-lifecycle"
468
326
  },
469
327
  "done:onEnter:1": {
470
- "kind": "run.artifact",
471
- "stateEffect": "read",
472
- "evidenceEffect": "write"
328
+ "kind": "run.artifact"
473
329
  },
474
330
  "done:onEnter:2": {
475
- "kind": "note",
476
- "stateEffect": "read",
477
- "evidenceEffect": "none"
331
+ "kind": "note"
478
332
  },
479
333
  "done:onEnter:3": {
480
334
  "kind": "shell",
481
- "invocation": "mkdir -p .spur/memory/sessions; CP_TS=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"; CP_COMMIT=\"$(git rev-parse HEAD 2>/dev/null || echo unknown)\"; CP_DIGEST=\"$(cut -d= -f2 .spur/run/$wbs-proofdigest.txt 2>/dev/null || echo '')\"; CP_RUN=\"$SPUR_RUN_ID\"; [ -z \"$CP_RUN\" ] && CP_RUN=\"$RUN_ID\"; printf '%s\\n' '---' 'schema_version: 1' \"session_id: $(date -u +%Y-%m-%d)-$wbs\" 'workflow: task-pipeline' \"run_id: $CP_RUN\" \"task_wbs: $wbs\" 'feature_id: \"\"' 'phase: done' 'status: done' 'last_gate: record' \"source_commit: $CP_COMMIT\" \"digest: $CP_DIGEST\" \"generated_at: $CP_TS\" \"updated_at: $CP_TS\" \"next_action: none - task $wbs complete (terminal; advisory only)\" 'artifacts:' ' - .spur/run/$wbs-verdict.json' ' - .spur/run/$wbs-test-gate.log' '---' '' '## Session Notes' '' \"Terminal checkpoint for task $wbs (task-pipeline done).\" 'Advisory only; the task file is authoritative.' > .spur/memory/sessions/$wbs-checkpoint.md; exit 0",
482
- "stateEffect": "write",
483
- "evidenceEffect": "write"
335
+ "invocation": "mkdir -p .spur/memory/sessions; CP_TS=\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"; CP_COMMIT=\"$(git rev-parse HEAD 2>/dev/null || echo unknown)\"; CP_DIGEST=\"$(cut -d= -f2 .spur/run/$wbs-proofdigest.txt 2>/dev/null || echo '')\"; CP_RUN=\"$SPUR_RUN_ID\"; [ -z \"$CP_RUN\" ] && CP_RUN=\"$RUN_ID\"; printf '%s\\n' '---' 'schema_version: 1' \"session_id: $(date -u +%Y-%m-%d)-$wbs\" 'workflow: task-pipeline' \"run_id: $CP_RUN\" \"task_wbs: $wbs\" 'feature_id: \"\"' 'phase: done' 'status: done' 'last_gate: record' \"source_commit: $CP_COMMIT\" \"digest: $CP_DIGEST\" \"generated_at: $CP_TS\" \"updated_at: $CP_TS\" \"next_action: none - task $wbs complete (terminal; advisory only)\" 'artifacts:' ' - .spur/run/$wbs-verdict.json' ' - .spur/run/$wbs-test-gate.log' '---' '' '## Session Notes' '' \"Terminal checkpoint for task $wbs (task-pipeline done).\" 'Advisory only; the task file is authoritative.' > .spur/memory/sessions/$wbs-checkpoint.md; exit 0"
484
336
  }
485
337
  },
486
- "digest": "sha256:4098c91f865e9879987bb6049be911f021fe6a26843eced1085f631f33f1e47e"
338
+ "digest": "sha256:455fcf48f685809780187b7345562f3d377c5aa66d2fa3130c078b168bebf436"
487
339
  },
488
340
  "wrapup-pipeline": {
489
341
  "definition": "config/workflows/wrapup-pipeline.yaml",
@@ -496,57 +348,36 @@
496
348
  "modelQueries": ["doc-sync"],
497
349
  "actions": {
498
350
  "start:onEnter:0": {
499
- "kind": "note",
500
- "stateEffect": "read",
501
- "evidenceEffect": "none"
351
+ "kind": "note"
502
352
  },
503
353
  "task-resolve:onEnter:0": {
504
- "kind": "note",
505
- "stateEffect": "read",
506
- "evidenceEffect": "none"
354
+ "kind": "note"
507
355
  },
508
356
  "doc-sync:onEnter:0": {
509
357
  "kind": "agent.run",
510
- "invocation": "First run Skill(skill=\"sp:doc-evolve\", args=\"wrapup tasks=${vars.tasks}\") to repair drift in docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit rules; do not write task/feature corpus. THEN extract working learnings from tasks ${vars.tasks} — conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown (no fences) and END your final message with that markdown (it is captured to .spur/run/wrapup-learnings.md).",
511
- "stateEffect": "write",
512
- "evidenceEffect": "write"
358
+ "invocation": "First run Skill(skill=\"sp:doc-evolve\", args=\"wrapup tasks=${vars.tasks}\") to repair drift in docs/00_ADR.md, docs/03_ARCHITECTURE.md, docs/04_DESIGN.md, docs/design/* per constitution edit rules; do not write task/feature corpus. THEN extract working learnings from tasks ${vars.tasks} — conventions, errors fixed, patterns, gotchas, grouped by date and task WBS — as raw markdown (no fences) and END your final message with that markdown (it is captured to .spur/run/wrapup-learnings.md)."
513
359
  },
514
360
  "doc-sync:onEnter:1": {
515
361
  "kind": "shell",
516
- "invocation": "mkdir -p .spur/memory && if test -s .spur/run/wrapup-learnings.md; then\n cat .spur/run/wrapup-learnings.md >> .spur/memory/learnings.md &&\n printf '\\n' >> .spur/memory/learnings.md;\nelse\n echo \"wrapup: learning capture missing or empty at $PWD/.spur/run/wrapup-learnings.md - append skipped\" >&2;\nfi && exit 0",
517
- "stateEffect": "write",
518
- "evidenceEffect": "write",
519
- "disposition": "GLUE"
362
+ "invocation": "mkdir -p .spur/memory && if test -s .spur/run/wrapup-learnings.md; then\n cat .spur/run/wrapup-learnings.md >> .spur/memory/learnings.md &&\n printf '\\n' >> .spur/memory/learnings.md;\nelse\n echo \"wrapup: learning capture missing or empty at $PWD/.spur/run/wrapup-learnings.md - append skipped\" >&2;\nfi && exit 0"
520
363
  },
521
364
  "metrics-record:onEnter:0": {
522
365
  "kind": "shell",
523
- "invocation": "mkdir -p .spur/memory && printf '%s' \"$tasks\" | jq -r '(if type == \"string\" then (try fromjson catch .) else . end) | if type == \"array\" then .[] else empty end' | while IFS= read -r wbs; do\n if [ -n \"$wbs\" ]; then\n task_json=$($spurBin task show \"$wbs\" --json 2>/dev/null || true);\n if [ -n \"$task_json\" ]; then\n feat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.feature_id // .feature_id // empty' 2>/dev/null || true);\n stat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.status // .status // \"unknown\"' 2>/dev/null || true);\n verdict=\"UNKNOWN\";\n if [ -f \".spur/run/$wbs-verdict.json\" ]; then\n v=$(jq -r '.verdict // \"UNKNOWN\"' \".spur/run/$wbs-verdict.json\" 2>/dev/null || true);\n if [ -n \"$v\" ]; then verdict=\"$v\"; fi;\n fi;\n ts=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\");\n printf '{\"wbs\":\"%s\",\"feature_id\":\"%s\",\"status\":\"%s\",\"verdict\":\"%s\",\"timestamp\":\"%s\"}\\n' \"$wbs\" \"$feat\" \"$stat\" \"$verdict\" \"$ts\" >> .spur/memory/wrapup-metrics.jsonl;\n fi;\n fi;\ndone && exit 0",
524
- "stateEffect": "write",
525
- "evidenceEffect": "write",
526
- "disposition": "GLUE"
366
+ "invocation": "mkdir -p .spur/memory && printf '%s' \"$tasks\" | jq -r '(if type == \"string\" then (try fromjson catch .) else . end) | if type == \"array\" then .[] else empty end' | while IFS= read -r wbs; do\n if [ -n \"$wbs\" ]; then\n task_json=$($spurBin task show \"$wbs\" --json 2>/dev/null || true);\n if [ -n \"$task_json\" ]; then\n feat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.feature_id // .feature_id // empty' 2>/dev/null || true);\n stat=$(printf '%s' \"$task_json\" | jq -r '.frontmatter.status // .status // \"unknown\"' 2>/dev/null || true);\n verdict=\"UNKNOWN\";\n if [ -f \".spur/run/$wbs-verdict.json\" ]; then\n v=$(jq -r '.verdict // \"UNKNOWN\"' \".spur/run/$wbs-verdict.json\" 2>/dev/null || true);\n if [ -n \"$v\" ]; then verdict=\"$v\"; fi;\n fi;\n ts=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\");\n printf '{\"wbs\":\"%s\",\"feature_id\":\"%s\",\"status\":\"%s\",\"verdict\":\"%s\",\"timestamp\":\"%s\"}\\n' \"$wbs\" \"$feat\" \"$stat\" \"$verdict\" \"$ts\" >> .spur/memory/wrapup-metrics.jsonl;\n fi;\n fi;\ndone && exit 0"
527
367
  },
528
368
  "feature-transition:onEnter:0": {
529
369
  "kind": "shell",
530
- "invocation": "if [ -z \"$feature\" ]; then\n echo \"feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)\" >&2;\n exit 1;\nfi; SYNC_OUTPUT=$(if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$feature\" --spur-bin \"$spurBin\" --json;\nelse\n $spurBin feature sync \"$feature\" --json;\nfi); SYNC_RC=$?; printf '%s\\n' \"$SYNC_OUTPUT\"; APPLIED=$(printf '%s' \"$SYNC_OUTPUT\" | jq -r '.applied // false' 2>/dev/null || echo false); if [ \"$APPLIED\" = \"true\" ] || [ \"$SYNC_RC\" -ne 0 ]; then\n echo \"feature-transition: sync applied or failed after a possible partial transition for $feature — running corpus-aware gate: $featureGateCmd\";\n if sh -c \"$featureGateCmd\"; then\n echo \"feature-transition: corpus-aware gate PASS for feature $feature\";\n else\n echo \"feature-transition: corpus-aware gate FAIL for feature $feature — inspect findings before reporting the transition complete\" >&2;\n fi;\nelse\n echo \"feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — corpus-aware gate skipped\";\nfi; exit 0",
531
- "stateEffect": "write",
532
- "evidenceEffect": "none",
533
- "disposition": "EXT"
370
+ "invocation": "if [ -z \"$feature\" ]; then\n echo \"feature-transition: vars.feature is empty — refusing no-op feature sync (mis-invocation, not a blocked sync)\" >&2;\n exit 1;\nfi; SYNC_OUTPUT=$(if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then\n bun plugins/sp/scripts/feature-sync-bounded.ts \"$feature\" --spur-bin \"$spurBin\" --json;\nelse\n $spurBin feature sync \"$feature\" --json;\nfi); SYNC_RC=$?; printf '%s\\n' \"$SYNC_OUTPUT\"; APPLIED=$(printf '%s' \"$SYNC_OUTPUT\" | jq -r '.applied // false' 2>/dev/null || echo false); if [ \"$APPLIED\" = \"true\" ] || [ \"$SYNC_RC\" -ne 0 ]; then\n echo \"feature-transition: sync applied or failed after a possible partial transition for $feature — running corpus-aware gate: $featureGateCmd\";\n if sh -c \"$featureGateCmd\"; then\n echo \"feature-transition: corpus-aware gate PASS for feature $feature\";\n else\n echo \"feature-transition: corpus-aware gate FAIL for feature $feature — inspect findings before reporting the transition complete\" >&2;\n fi;\nelse\n echo \"feature-transition: sync did not apply a transition (rc=$SYNC_RC, applied=$APPLIED) — corpus-aware gate skipped\";\nfi; exit 0"
534
371
  },
535
372
  "branch-cleanup:onEnter:0": {
536
- "kind": "hitl.confirm",
537
- "stateEffect": "read",
538
- "evidenceEffect": "none"
373
+ "kind": "hitl.confirm"
539
374
  },
540
375
  "done:onEnter:0": {
541
- "kind": "note",
542
- "stateEffect": "read",
543
- "evidenceEffect": "none"
376
+ "kind": "note"
544
377
  },
545
378
  "done:onEnter:1": {
546
379
  "kind": "shell",
547
- "invocation": "mkdir -p .spur/memory/sessions && echo \"checkpoint: wrapup-pipeline done tasks=$tasks ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > .spur/memory/sessions/wrapup-checkpoint.md",
548
- "stateEffect": "write",
549
- "evidenceEffect": "write"
380
+ "invocation": "mkdir -p .spur/memory/sessions && echo \"checkpoint: wrapup-pipeline done tasks=$tasks ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > .spur/memory/sessions/wrapup-checkpoint.md"
550
381
  }
551
382
  },
552
383
  "digest": "sha256:390621dd339709682065e8954f2d1b49a195111c8372ec83e094fe7e2e35927f"
@@ -572,56 +403,35 @@
572
403
  "actions": {
573
404
  "preflight:onEnter:0": {
574
405
  "kind": "shell",
575
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-preflight.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" preflight --base \"$baseBranch\" --json > \".spur/run/$__runId-pr-context.json\"; rc=$?; set -e && if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
576
- "stateEffect": "read",
577
- "evidenceEffect": "write",
578
- "disposition": "EXT"
406
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-preflight.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" preflight --base \"$baseBranch\" --json > \".spur/run/$__runId-pr-context.json\"; rc=$?; set -e && if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
579
407
  },
580
408
  "hygiene:onEnter:0": {
581
409
  "kind": "shell",
582
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-hygiene.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" hygiene --base \"$baseBranch\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-hygiene.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
583
- "stateEffect": "read",
584
- "evidenceEffect": "write"
410
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-hygiene.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" hygiene --base \"$baseBranch\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-hygiene.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
585
411
  },
586
412
  "precheck:onEnter:0": {
587
413
  "kind": "shell",
588
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-precheck.status\" && if [ -z \"$preReviewCmd\" ]; then\n printf 'SKIP\\n' > \"$STATUS_FILE\";\nelse\n set +e; sh -c \"$preReviewCmd\"; rc=$?; set -e;\n if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi;\nfi && exit 0",
589
- "stateEffect": "read",
590
- "evidenceEffect": "write",
591
- "disposition": "POLICY"
414
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-precheck.status\" && if [ -z \"$preReviewCmd\" ]; then\n printf 'SKIP\\n' > \"$STATUS_FILE\";\nelse\n set +e; sh -c \"$preReviewCmd\"; rc=$?; set -e;\n if [ \"$rc\" -eq 0 ]; then printf 'PASS\\n' > \"$STATUS_FILE\"; else printf 'FAIL\\n' > \"$STATUS_FILE\"; fi;\nfi && exit 0"
592
415
  },
593
416
  "push:onEnter:0": {
594
417
  "kind": "shell",
595
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-push.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" push --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-push.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
596
- "stateEffect": "write",
597
- "evidenceEffect": "write"
418
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-push.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" push --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-push.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
598
419
  },
599
420
  "ensure-pr:onEnter:0": {
600
421
  "kind": "shell",
601
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-ensure.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" ensure-pr --base \"$baseBranch\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
602
- "stateEffect": "write",
603
- "evidenceEffect": "write"
422
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-ensure.status\" && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" ensure-pr --base \"$baseBranch\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
604
423
  },
605
424
  "request:onEnter:0": {
606
425
  "kind": "shell",
607
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-request.status\" && FORCE_FLAG=\"\" && if [ \"$mode\" = \"rerun\" ]; then FORCE_FLAG=\"--force\"; fi && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" request --focus \"$focus\" $FORCE_FLAG --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-request.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
608
- "stateEffect": "write",
609
- "evidenceEffect": "write",
610
- "disposition": "EXT"
426
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-request.status\" && FORCE_FLAG=\"\" && if [ \"$mode\" = \"rerun\" ]; then FORCE_FLAG=\"--force\"; fi && set +e && bun \"$(superskill script path sp pr-reviewing.ts)\" request --focus \"$focus\" $FORCE_FLAG --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-request.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
611
427
  },
612
428
  "wait:onEnter:0": {
613
429
  "kind": "shell",
614
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-wait.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" wait --since \"$SINCE\" --head \"$REQUEST_HEAD\" --timeout \"$waitTimeoutSec\" --interval \"$waitIntervalSec\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-wait.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0",
615
- "stateEffect": "read",
616
- "evidenceEffect": "write",
617
- "disposition": "EXT"
430
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-wait.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" wait --since \"$SINCE\" --head \"$REQUEST_HEAD\" --timeout \"$waitTimeoutSec\" --interval \"$waitIntervalSec\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-wait.json\"; rc=$?; set -e && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && exit 0"
618
431
  },
619
432
  "collect:onEnter:0": {
620
433
  "kind": "shell",
621
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-collect.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" collect --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-findings.json\"; collect_rc=$?; bun \"$(superskill script path sp pr-reviewing.ts)\" status --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json > \".spur/run/$__runId-pr-status.json\"; status_rc=$?; set -e && if [ \"$collect_rc\" -ne 0 ] || [ \"$status_rc\" -ne 0 ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && { cat \".spur/run/$__runId-pr-findings.json\" 2>/dev/null || true; } && exit 0",
622
- "stateEffect": "read",
623
- "evidenceEffect": "write",
624
- "disposition": "EXT"
434
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-pr-collect.status\" && set +e && SINCE=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).requestedAt;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && REQUEST_HEAD=\"$(bun -e 'const fs=require(\"node:fs\");const value=JSON.parse(fs.readFileSync(process.argv[1],\"utf8\")).head;process.stdout.write(value ?? \"\")' \".spur/run/$__runId-pr-request.json\")\" && bun \"$(superskill script path sp pr-reviewing.ts)\" collect --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json --status-file \"$STATUS_FILE\" > \".spur/run/$__runId-pr-findings.json\"; collect_rc=$?; bun \"$(superskill script path sp pr-reviewing.ts)\" status --since \"$SINCE\" --head \"$REQUEST_HEAD\" --json > \".spur/run/$__runId-pr-status.json\"; status_rc=$?; set -e && if [ \"$collect_rc\" -ne 0 ] || [ \"$status_rc\" -ne 0 ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && if [ ! -f \"$STATUS_FILE\" ]; then printf 'FAIL\\n' > \"$STATUS_FILE\"; fi && { cat \".spur/run/$__runId-pr-findings.json\" 2>/dev/null || true; } && exit 0"
625
435
  }
626
436
  },
627
437
  "digest": "sha256:eb3f51877647f25907ed6b660b79e4d22b3c7462ceccacc4fc251eb31bac06f0"
@@ -637,36 +447,23 @@
637
447
  "modelQueries": ["fix"],
638
448
  "actions": {
639
449
  "implement:onEnter:0": {
640
- "kind": "note",
641
- "stateEffect": "read",
642
- "evidenceEffect": "none"
450
+ "kind": "note"
643
451
  },
644
452
  "implement:onEnter:1": {
645
453
  "kind": "shell",
646
- "invocation": "mkdir -p .spur/run && printf '0\\n' > .spur/run/$__runId-basic-fix-attempt",
647
- "stateEffect": "write",
648
- "evidenceEffect": "write",
649
- "disposition": "GLUE"
454
+ "invocation": "mkdir -p .spur/run && printf '0\\n' > .spur/run/$__runId-basic-fix-attempt"
650
455
  },
651
456
  "check:onEnter:0": {
652
457
  "kind": "shell",
653
- "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-basic-gate.status\" && set +e && ( $qualityGateCmd ); gate_rc=$?; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && exit 0",
654
- "stateEffect": "write",
655
- "evidenceEffect": "write",
656
- "disposition": "POLICY"
458
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$__runId-basic-gate.status\" && set +e && ( $qualityGateCmd ); gate_rc=$?; set -e && if [ \"$gate_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \"$STATUS_FILE\";\nelse\n printf 'FAIL\\n' > \"$STATUS_FILE\";\nfi && exit 0"
657
459
  },
658
460
  "fix:onEnter:0": {
659
461
  "kind": "shell",
660
- "invocation": "mkdir -p .spur/run && ATTEMPT_FILE=\".spur/run/$__runId-basic-fix-attempt\" && n=$(cat \"$ATTEMPT_FILE\" 2>/dev/null || echo 0) && printf '%s\\n' \"$((n + 1))\" > \"$ATTEMPT_FILE\"",
661
- "stateEffect": "write",
662
- "evidenceEffect": "write",
663
- "disposition": "GLUE"
462
+ "invocation": "mkdir -p .spur/run && ATTEMPT_FILE=\".spur/run/$__runId-basic-fix-attempt\" && n=$(cat \"$ATTEMPT_FILE\" 2>/dev/null || echo 0) && printf '%s\\n' \"$((n + 1))\" > \"$ATTEMPT_FILE\""
664
463
  },
665
464
  "fix:onEnter:1": {
666
465
  "kind": "agent.run",
667
- "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\"",
668
- "stateEffect": "read",
669
- "evidenceEffect": "none"
466
+ "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\""
670
467
  }
671
468
  },
672
469
  "digest": "sha256:fb8ab828b9527f225368d20b08e3fc7ca4b92e6e22642a57868da193d5ebef2c"
@@ -683,56 +480,36 @@
683
480
  "actions": {
684
481
  "precheck:onEnter:0": {
685
482
  "kind": "shell",
686
- "invocation": "mkdir -p .spur/run && set +e && test -n \"$featureId\"; id_rc=$?; $spurBin agent doctor $agent; doctor_rc=$?; set -e && if [ \"$id_rc\" -eq 0 ] && [ \"$doctor_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nelse\n printf 'FAIL\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nfi && exit 0",
687
- "stateEffect": "write",
688
- "evidenceEffect": "write",
689
- "disposition": "GLUE"
483
+ "invocation": "mkdir -p .spur/run && set +e && test -n \"$featureId\"; id_rc=$?; $spurBin agent doctor $agent; doctor_rc=$?; set -e && if [ \"$id_rc\" -eq 0 ] && [ \"$doctor_rc\" -eq 0 ]; then\n printf 'PASS\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nelse\n printf 'FAIL\\n' > \".spur/run/$__runId-feature-dev-precheck.status\";\nfi && exit 0"
690
484
  },
691
485
  "precheck:onEnter:1": {
692
- "kind": "note",
693
- "stateEffect": "read",
694
- "evidenceEffect": "none"
486
+ "kind": "note"
695
487
  },
696
488
  "brainstorm:onEnter:0": {
697
489
  "kind": "agent.run",
698
- "invocation": "/sp:dev-brainstorm --feature ${vars.featureId}",
699
- "stateEffect": "read",
700
- "evidenceEffect": "none"
490
+ "invocation": "/sp:dev-brainstorm --feature ${vars.featureId}"
701
491
  },
702
492
  "plan:onEnter:0": {
703
493
  "kind": "agent.run",
704
- "invocation": "/sp:dev-plan --feature ${vars.featureId}",
705
- "stateEffect": "read",
706
- "evidenceEffect": "none"
494
+ "invocation": "/sp:dev-plan --feature ${vars.featureId}"
707
495
  },
708
496
  "execute-tasks-auto:onEnter:0": {
709
497
  "kind": "agent.run",
710
- "invocation": "/sp:dev-runall --feature ${vars.featureId} --auto",
711
- "stateEffect": "read",
712
- "evidenceEffect": "none"
498
+ "invocation": "/sp:dev-runall --feature ${vars.featureId} --auto"
713
499
  },
714
500
  "execute-tasks:onEnter:0": {
715
501
  "kind": "agent.run",
716
- "invocation": "/sp:dev-runall --feature ${vars.featureId}",
717
- "stateEffect": "read",
718
- "evidenceEffect": "none"
502
+ "invocation": "/sp:dev-runall --feature ${vars.featureId}"
719
503
  },
720
504
  "integration-review:onEnter:0": {
721
- "kind": "command.gate",
722
- "stateEffect": "read",
723
- "evidenceEffect": "none"
505
+ "kind": "command.gate"
724
506
  },
725
507
  "integration-review:onEnter:1": {
726
- "kind": "note",
727
- "stateEffect": "read",
728
- "evidenceEffect": "none"
508
+ "kind": "note"
729
509
  },
730
510
  "done:onEnter:0": {
731
511
  "kind": "shell",
732
- "invocation": "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",
733
- "stateEffect": "write",
734
- "evidenceEffect": "write",
735
- "disposition": "GLUE"
512
+ "invocation": "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"
736
513
  }
737
514
  },
738
515
  "digest": "sha256:e1fa6b86cb7bcad0f927cf73336996ee8ce0088b6685cebba8c8849e26029529"
@@ -749,79 +526,48 @@
749
526
  "actions": {
750
527
  "precheck:onEnter:0": {
751
528
  "kind": "shell",
752
- "invocation": "mkdir -p .spur/run/wayfinder && set +e && $spurBin task check $wbs; check_rc=$?; $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-show.json; show_rc=$?; set -e && if [ \"$check_rc\" -eq 0 ] && [ \"$show_rc\" -eq 0 ]; then\n printf 'PASS\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nelse\n printf 'FAIL\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nfi && exit 0",
753
- "stateEffect": "write",
754
- "evidenceEffect": "write",
755
- "disposition": "GLUE"
529
+ "invocation": "mkdir -p .spur/run/wayfinder && set +e && $spurBin task check $wbs; check_rc=$?; $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-show.json; show_rc=$?; set -e && if [ \"$check_rc\" -eq 0 ] && [ \"$show_rc\" -eq 0 ]; then\n printf 'PASS\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nelse\n printf 'FAIL\\n' > .spur/run/wayfinder/$wbs-precheck.status;\nfi && exit 0"
756
530
  },
757
531
  "precheck:onEnter:1": {
758
- "kind": "note",
759
- "stateEffect": "read",
760
- "evidenceEffect": "none"
532
+ "kind": "note"
761
533
  },
762
534
  "collect:onEnter:0": {
763
535
  "kind": "shell",
764
- "invocation": "mkdir -p .spur/run/wayfinder && $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-input.json",
765
- "stateEffect": "read",
766
- "evidenceEffect": "write",
767
- "disposition": "GLUE"
536
+ "invocation": "mkdir -p .spur/run/wayfinder && $spurBin task show $wbs --json > .spur/run/wayfinder/$wbs-input.json"
768
537
  },
769
538
  "collect:onEnter:1": {
770
539
  "kind": "shell",
771
- "invocation": "$spurBin workflow validate wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt",
772
- "stateEffect": "read",
773
- "evidenceEffect": "write",
774
- "disposition": "SIMPLE"
540
+ "invocation": "$spurBin workflow validate wayfinder-resolution.yaml > .spur/run/wayfinder/$wbs-workflow-check.txt"
775
541
  },
776
542
  "collect:onEnter:2": {
777
543
  "kind": "shell",
778
- "invocation": "$spurBin task check $wbs > .spur/run/wayfinder/$wbs-precheck.txt",
779
- "stateEffect": "read",
780
- "evidenceEffect": "write",
781
- "disposition": "SIMPLE"
544
+ "invocation": "$spurBin task check $wbs > .spur/run/wayfinder/$wbs-precheck.txt"
782
545
  },
783
546
  "investigate:onEnter:0": {
784
547
  "kind": "agent.run",
785
- "invocation": "Resolve task ${vars.wbs} in resolution mode ${vars.resolutionMode} using the prepared local bundle `.spur/run/wayfinder/${vars.wbs}-input.json` and only local shell/file reads plus the `spur` CLI; do not use network search or MCP tools. Do not invoke task-pipeline.yaml, /sp:dev-run, /sp:dev-runall, or code implementation. Read the task file, map its existing Requirements and Acceptance Criteria to the existing Design/Plan/References, then author concise Solution, Testing, and Review sections through `spur task update --section --from-file`. Preserve the research boundary. Finish with a short summary and stop. Evidence policy: ${vars.evidencePolicy}.\nEvidence-anchor and content contract (mandatory — 0300 R1/R2): when the task's evidence lives in a shared evidence file (e.g. `.spur/run/wayfinder-O/implementation-evidence.md`), the `## <wbs>` section you author MUST (a) be substantive — more than 5 lines AND more than 60 words, covering the task's own requirements, not a generic 2–3 sentence placeholder; and (b) be anchored truthfully — after writing the section, resolve its real start line with `grep -n \"^## <wbs> \" <evidence-file>` and use that exact line number in every Solution/Testing `file:line` citation. NEVER copy a line number from another ticket's example (e.g. `:5` from an earlier WBS); each WBS anchors to its own section. Before finishing, re-read each cited line and confirm it names the requirement's subject (0299 R1 line-anchor rule).",
786
- "stateEffect": "read",
787
- "evidenceEffect": "none"
548
+ "invocation": "Resolve task ${vars.wbs} in resolution mode ${vars.resolutionMode} using the prepared local bundle `.spur/run/wayfinder/${vars.wbs}-input.json` and only local shell/file reads plus the `spur` CLI; do not use network search or MCP tools. Do not invoke task-pipeline.yaml, /sp:dev-run, /sp:dev-runall, or code implementation. Read the task file, map its existing Requirements and Acceptance Criteria to the existing Design/Plan/References, then author concise Solution, Testing, and Review sections through `spur task update --section --from-file`. Preserve the research boundary. Finish with a short summary and stop. Evidence policy: ${vars.evidencePolicy}.\nEvidence-anchor and content contract (mandatory — 0300 R1/R2): when the task's evidence lives in a shared evidence file (e.g. `.spur/run/wayfinder-O/implementation-evidence.md`), the `## <wbs>` section you author MUST (a) be substantive — more than 5 lines AND more than 60 words, covering the task's own requirements, not a generic 2–3 sentence placeholder; and (b) be anchored truthfully — after writing the section, resolve its real start line with `grep -n \"^## <wbs> \" <evidence-file>` and use that exact line number in every Solution/Testing `file:line` citation. NEVER copy a line number from another ticket's example (e.g. `:5` from an earlier WBS); each WBS anchors to its own section. Before finishing, re-read each cited line and confirm it names the requirement's subject (0299 R1 line-anchor rule)."
788
549
  },
789
550
  "verify:onEnter:0": {
790
551
  "kind": "shell",
791
- "invocation": "rm -f .spur/run/wayfinder/$wbs-resolution-verdict.txt",
792
- "stateEffect": "read",
793
- "evidenceEffect": "write",
794
- "disposition": "GLUE"
552
+ "invocation": "rm -f .spur/run/wayfinder/$wbs-resolution-verdict.txt"
795
553
  },
796
554
  "verify:onEnter:1": {
797
555
  "kind": "agent.run",
798
- "invocation": "Verify resolved task ${vars.wbs} using only local reads and `spur` CLI. Check its requirements, acceptance scenarios, Solution, Testing, and Review. Confirm workflow orchestration uses `spur workflow` and execution uses `spur` CLI by default. Run `spur task check ${vars.wbs}` and update Testing/Review via the CLI only if needed. Do not implement plugin code. Write the final verdict word (exactly PASS, PARTIAL, or FAIL, nothing else) to `.spur/run/wayfinder/${vars.wbs}-resolution-verdict.txt`, then finish with the same verdict and stop.",
799
- "stateEffect": "read",
800
- "evidenceEffect": "none"
556
+ "invocation": "Verify resolved task ${vars.wbs} using only local reads and `spur` CLI. Check its requirements, acceptance scenarios, Solution, Testing, and Review. Confirm workflow orchestration uses `spur workflow` and execution uses `spur` CLI by default. Run `spur task check ${vars.wbs}` and update Testing/Review via the CLI only if needed. Do not implement plugin code. Write the final verdict word (exactly PASS, PARTIAL, or FAIL, nothing else) to `.spur/run/wayfinder/${vars.wbs}-resolution-verdict.txt`, then finish with the same verdict and stop."
801
557
  },
802
558
  "approve:onEnter:0": {
803
- "kind": "hitl.confirm",
804
- "stateEffect": "read",
805
- "evidenceEffect": "none"
559
+ "kind": "hitl.confirm"
806
560
  },
807
561
  "record:onEnter:0": {
808
562
  "kind": "shell",
809
- "invocation": "$spurBin task check $wbs ; exit 0",
810
- "stateEffect": "read",
811
- "evidenceEffect": "none",
812
- "disposition": "GLUE"
563
+ "invocation": "$spurBin task check $wbs ; exit 0"
813
564
  },
814
565
  "record:onEnter:1": {
815
566
  "kind": "shell",
816
- "invocation": "$spurBin task update $wbs done ; exit 0",
817
- "stateEffect": "write",
818
- "evidenceEffect": "none",
819
- "disposition": "GLUE"
567
+ "invocation": "$spurBin task update $wbs done ; exit 0"
820
568
  },
821
569
  "done:onEnter:0": {
822
- "kind": "note",
823
- "stateEffect": "read",
824
- "evidenceEffect": "none"
570
+ "kind": "note"
825
571
  }
826
572
  },
827
573
  "digest": "sha256:ef75f70a09b67ea5905ac68aba7c3eec2b805530312ca3173fa4b8ee7275059e"