@gobing-ai/spur 0.3.53 → 0.3.55

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 (96) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +2783 -2783
  3. package/config/pipeline-budgets.json +42 -0
  4. package/config/plugin-scripts.json +73 -0
  5. package/config/workflow-composition-baseline.json +561 -0
  6. package/config/workflows/docs-pipeline.yaml +29 -13
  7. package/config/workflows/feature-dev.yaml +54 -2
  8. package/config/workflows/idea-pipeline.yaml +15 -0
  9. package/config/workflows/task-pipeline.yaml +77 -65
  10. package/config/workflows/wrapup-pipeline.yaml +40 -45
  11. package/package.json +9 -9
  12. package/plugins/README.md +1 -1
  13. package/plugins/sp/README.md +3 -3
  14. package/plugins/sp/agents/super-planner.md +1 -1
  15. package/plugins/sp/commands/dev-daily.md +1 -1
  16. package/plugins/sp/commands/dev-history-load.md +1 -1
  17. package/plugins/sp/commands/dev-idea.md +10 -2
  18. package/plugins/sp/plugin.json +1 -1
  19. package/plugins/sp/scripts/batch-preflight.mjs +160 -0
  20. package/plugins/sp/scripts/batch-preflight.ts +1 -1
  21. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +479 -0
  22. package/plugins/sp/scripts/daily-summary/daily-summary.ts +75 -37
  23. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +204 -0
  24. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -1
  25. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +141 -0
  26. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +1 -1
  27. package/plugins/sp/scripts/feature-sync-bounded.mjs +290 -0
  28. package/plugins/sp/scripts/feature-sync-bounded.ts +9 -8
  29. package/plugins/sp/scripts/history-load.mjs +268 -0
  30. package/plugins/sp/scripts/pr-reviewing.mjs +685 -0
  31. package/plugins/sp/scripts/pr-reviewing.ts +1 -1
  32. package/plugins/sp/scripts/script-contract-check.ts +319 -0
  33. package/plugins/sp/skills/daily-summary/SKILL.md +3 -3
  34. package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -5
  35. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  36. package/plugins/sp/skills/pr-reviewing/SKILL.md +3 -3
  37. package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
  38. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +3 -4
  39. package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
  40. package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
  41. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
  42. package/spur.js +4270 -1323
  43. package/web/_astro/{BoardApp.plVXYkKo.js → BoardApp.DCLSB3Zs.js} +63 -63
  44. package/web/_astro/BoardApp.Dx5gzAhb.js +1 -0
  45. package/web/_astro/{TaskDetail.5DIyVQ_N.js → TaskDetail.DN_RxS-2.js} +1 -1
  46. package/web/_astro/{arc.BnUgL7ho.js → arc.CldTHRg-.js} +1 -1
  47. package/web/_astro/{architectureDiagram-3BPJPVTR.B9YMJau-.js → architectureDiagram-3BPJPVTR.DQ4T9oeU.js} +1 -1
  48. package/web/_astro/{blockDiagram-GPEHLZMM.Dw2qgvWJ.js → blockDiagram-GPEHLZMM.CGDaXH8J.js} +1 -1
  49. package/web/_astro/{c4Diagram-AAUBKEIU.jTbU0-Nk.js → c4Diagram-AAUBKEIU.Cqsc2iub.js} +1 -1
  50. package/web/_astro/channel.CrBJYpxo.js +1 -0
  51. package/web/_astro/{chunk-2J33WTMH.Di46EtkU.js → chunk-2J33WTMH.CQSKsRmq.js} +1 -1
  52. package/web/_astro/{chunk-4BX2VUAB.DweHBwqQ.js → chunk-4BX2VUAB.Pv1HCtp8.js} +1 -1
  53. package/web/_astro/{chunk-55IACEB6.rRjTKaa7.js → chunk-55IACEB6.-9MseOIk.js} +1 -1
  54. package/web/_astro/{chunk-727SXJPM.BkEQIkSV.js → chunk-727SXJPM.M3OI1DF7.js} +1 -1
  55. package/web/_astro/{chunk-AQP2D5EJ.Cfe9IvAd.js → chunk-AQP2D5EJ.NfHka5Ca.js} +1 -1
  56. package/web/_astro/{chunk-FMBD7UC4.fI6tLbeN.js → chunk-FMBD7UC4.B2suYe3s.js} +1 -1
  57. package/web/_astro/{chunk-ND2GUHAM.BIjgpq0O.js → chunk-ND2GUHAM.BfJ7aucP.js} +1 -1
  58. package/web/_astro/{chunk-QZHKN3VN.ZwLnVxyz.js → chunk-QZHKN3VN._582hZVc.js} +1 -1
  59. package/web/_astro/{classDiagram-4FO5ZUOK.BJAGYidE.js → classDiagram-4FO5ZUOK._tttU_jk.js} +1 -1
  60. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BJAGYidE.js → classDiagram-v2-Q7XG4LA2._tttU_jk.js} +1 -1
  61. package/web/_astro/{cose-bilkent-S5V4N54A.B5y3nz26.js → cose-bilkent-S5V4N54A.C4rVATLQ.js} +1 -1
  62. package/web/_astro/{dagre-BM42HDAG.DHmw0ZBC.js → dagre-BM42HDAG.8BLRi7f9.js} +1 -1
  63. package/web/_astro/{diagram-2AECGRRQ.10gapJfY.js → diagram-2AECGRRQ.D6lwUZos.js} +1 -1
  64. package/web/_astro/{diagram-5GNKFQAL.Gh33_drM.js → diagram-5GNKFQAL.Cl_rMiVT.js} +1 -1
  65. package/web/_astro/{diagram-KO2AKTUF.8ROWlfcX.js → diagram-KO2AKTUF.DhMExHLp.js} +1 -1
  66. package/web/_astro/{diagram-LMA3HP47.BsP3tKmg.js → diagram-LMA3HP47.3OAxzH_o.js} +1 -1
  67. package/web/_astro/{diagram-OG6HWLK6.DM2WFDhU.js → diagram-OG6HWLK6.Uh9RLucG.js} +1 -1
  68. package/web/_astro/{erDiagram-TEJ5UH35.royaC_lH.js → erDiagram-TEJ5UH35.izzElG0c.js} +1 -1
  69. package/web/_astro/{flowDiagram-I6XJVG4X.C9GEvIie.js → flowDiagram-I6XJVG4X.BlGwp5qM.js} +1 -1
  70. package/web/_astro/{ganttDiagram-6RSMTGT7.CXO4T2U0.js → ganttDiagram-6RSMTGT7.je7Vf8dN.js} +1 -1
  71. package/web/_astro/{gitGraphDiagram-PVQCEYII.Q02KzYH7.js → gitGraphDiagram-PVQCEYII.DNk0Ycop.js} +1 -1
  72. package/web/_astro/{index.DAxu50UF.css → index.V6Q7nhed.css} +1 -1
  73. package/web/_astro/{infoDiagram-5YYISTIA.DNfSDKJt.js → infoDiagram-5YYISTIA.K2HhGyWH.js} +1 -1
  74. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BFttvtgJ.js → ishikawaDiagram-YF4QCWOH.BbbOW4IN.js} +1 -1
  75. package/web/_astro/{journeyDiagram-JHISSGLW.Cfm-2SbG.js → journeyDiagram-JHISSGLW.B1UaZKqN.js} +1 -1
  76. package/web/_astro/{kanban-definition-UN3LZRKU.BWGNW6fV.js → kanban-definition-UN3LZRKU.9tsw5QFd.js} +1 -1
  77. package/web/_astro/{linear.BprdUk2Y.js → linear.I9dvtu-j.js} +1 -1
  78. package/web/_astro/{mermaid.core.B2zRNxtX.js → mermaid.core.5bKJsMgf.js} +4 -4
  79. package/web/_astro/{mindmap-definition-RKZ34NQL.g9jij8sw.js → mindmap-definition-RKZ34NQL.BC5MqSn6.js} +1 -1
  80. package/web/_astro/{pieDiagram-4H26LBE5.CbzR4n5b.js → pieDiagram-4H26LBE5.B8GIMyxD.js} +1 -1
  81. package/web/_astro/{quadrantDiagram-W4KKPZXB.DxV3viWt.js → quadrantDiagram-W4KKPZXB.K_aZSM_u.js} +1 -1
  82. package/web/_astro/{requirementDiagram-4Y6WPE33.DvAb90JS.js → requirementDiagram-4Y6WPE33.B_V5kzfT.js} +1 -1
  83. package/web/_astro/{sankeyDiagram-5OEKKPKP.CIFz5t4D.js → sankeyDiagram-5OEKKPKP.DyV2quLS.js} +1 -1
  84. package/web/_astro/{sequenceDiagram-3UESZ5HK.DzlEQynd.js → sequenceDiagram-3UESZ5HK.BlKvPnBv.js} +1 -1
  85. package/web/_astro/{stateDiagram-AJRCARHV.Dph2V-ts.js → stateDiagram-AJRCARHV.C1lYUMdS.js} +1 -1
  86. package/web/_astro/{stateDiagram-v2-BHNVJYJU.wJhfrLAy.js → stateDiagram-v2-BHNVJYJU.DJWjr3QV.js} +1 -1
  87. package/web/_astro/{timeline-definition-PNZ67QCA.BrSiyubX.js → timeline-definition-PNZ67QCA.CYYiXVlv.js} +1 -1
  88. package/web/_astro/{vennDiagram-CIIHVFJN.COobPrUX.js → vennDiagram-CIIHVFJN.zYjQKa_S.js} +1 -1
  89. package/web/_astro/{wardley-L42UT6IY.CrjeTx8A.js → wardley-L42UT6IY.BGrWTY3D.js} +1 -1
  90. package/web/_astro/{wardleyDiagram-YWT4CUSO.B8_fTQ8E.js → wardleyDiagram-YWT4CUSO.CkXxtcI1.js} +1 -1
  91. package/web/_astro/{xychartDiagram-2RQKCTM6.BPpokgVv.js → xychartDiagram-2RQKCTM6.BhKH3KG3.js} +1 -1
  92. package/web/index.html +2 -2
  93. package/config/workflows/planning-pipeline.yaml +0 -249
  94. package/config/workflows/task-pipeline2.yaml +0 -778
  95. package/web/_astro/BoardApp.pdtYu-yW.js +0 -1
  96. package/web/_astro/channel.DEqvaz-I.js +0 -1
@@ -0,0 +1,561 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "proofInputs": {
4
+ "repository": {
5
+ "excludeConfiguredCorpusFolders": true
6
+ },
7
+ "taskFields": ["wbs", "name", "feature_id", "depends_on"],
8
+ "taskSections": ["Background", "Requirements", "Acceptance Criteria", "Design", "Plan"],
9
+ "featureFields": ["id", "name"],
10
+ "featureSections": ["Goal", "Scope", "Acceptance Criteria"]
11
+ },
12
+ "workflows": {
13
+ "docs-pipeline": {
14
+ "definition": "config/workflows/docs-pipeline.yaml",
15
+ "boundary": "docs-evolution",
16
+ "disposition": "keep",
17
+ "callers": ["sp:spur-dev", "/sp:dev-run"],
18
+ "terminalStates": ["done", "failed", "cancelled"],
19
+ "artifacts": ["docs-precheck", "docs-verdict"],
20
+ "failurePolicy": "fail-closed",
21
+ "modelQueries": ["draft"],
22
+ "actions": {
23
+ "precheck:onEnter:0": {
24
+ "kind": "command.gate",
25
+ "stateEffect": "read",
26
+ "evidenceEffect": "write"
27
+ },
28
+ "precheck:onEnter:1": {
29
+ "kind": "command.gate",
30
+ "stateEffect": "read",
31
+ "evidenceEffect": "write"
32
+ },
33
+ "precheck:onEnter:2": {
34
+ "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"
38
+ },
39
+ "precheck:onEnter:3": {
40
+ "kind": "note",
41
+ "stateEffect": "read",
42
+ "evidenceEffect": "none"
43
+ },
44
+ "draft:onEnter:0": {
45
+ "kind": "agent.run",
46
+ "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto",
47
+ "stateEffect": "write",
48
+ "evidenceEffect": "none"
49
+ },
50
+ "draft:onEnter:1": {
51
+ "kind": "shell",
52
+ "invocation": "$spurBin task update $wbs wip --no-lifecycle",
53
+ "stateEffect": "write",
54
+ "evidenceEffect": "none"
55
+ },
56
+ "docs-review:onEnter:0": {
57
+ "kind": "hitl.confirm",
58
+ "stateEffect": "read",
59
+ "evidenceEffect": "none"
60
+ },
61
+ "record:onEnter:0": {
62
+ "kind": "shell",
63
+ "invocation": "$spurBin task record $wbs --solution-from-diff --transition testing",
64
+ "stateEffect": "write",
65
+ "evidenceEffect": "write"
66
+ },
67
+ "done:onEnter:0": {
68
+ "kind": "shell",
69
+ "invocation": "mkdir -p .spur/run && printf '%s\\n' \"{\\\"wbs\\\":\\\"$wbs\\\",\\\"verdict\\\":\\\"PASS\\\",\\\"source\\\":\\\"docs-pipeline\\\",\\\"requirements\\\":[{\\\"id\\\":\\\"docs-deliverable\\\",\\\"status\\\":\\\"MET\\\",\\\"evidence\\\":\\\"docs-pipeline record step\\\"}],\\\"acceptanceCriteria\\\":[],\\\"checks\\\":[{\\\"name\\\":\\\"docs-only\\\",\\\"status\\\":\\\"pass\\\",\\\"evidence\\\":\\\"docs-pipeline\\\"}]}\" > \".spur/run/$wbs-verdict.json\"\n",
70
+ "stateEffect": "read",
71
+ "evidenceEffect": "write"
72
+ },
73
+ "done:onEnter:1": {
74
+ "kind": "run.artifact",
75
+ "stateEffect": "read",
76
+ "evidenceEffect": "write"
77
+ },
78
+ "done:onEnter:2": {
79
+ "kind": "shell",
80
+ "invocation": "$spurBin task update $wbs done --no-lifecycle",
81
+ "stateEffect": "write",
82
+ "evidenceEffect": "none"
83
+ },
84
+ "done:onEnter:3": {
85
+ "kind": "note",
86
+ "stateEffect": "read",
87
+ "evidenceEffect": "none"
88
+ }
89
+ }
90
+ },
91
+ "idea-pipeline": {
92
+ "definition": "config/workflows/idea-pipeline.yaml",
93
+ "boundary": "idea-decomposition",
94
+ "disposition": "keep",
95
+ "callers": ["sp:spur-dev", "/sp:dev-idea"],
96
+ "terminalStates": ["handoff", "cancelled", "failed"],
97
+ "artifacts": [
98
+ "idea-eval-report",
99
+ "idea-needs-design",
100
+ "idea-design-review",
101
+ "idea-task-batch",
102
+ "idea-task-order",
103
+ "idea-handoff"
104
+ ],
105
+ "failurePolicy": "fail-closed",
106
+ "modelQueries": ["discovery", "feature-create", "ac-generate", "system-design", "decompose"],
107
+ "actions": {
108
+ "start:onEnter:0": {
109
+ "kind": "note",
110
+ "stateEffect": "read",
111
+ "evidenceEffect": "none"
112
+ },
113
+ "start:onEnter:1": {
114
+ "kind": "shell",
115
+ "invocation": "mkdir -p .spur/run && DOCTOR_FILE=\".spur/run/$__runId-idea-precheck-doctor.status\" && set +e && $spurBin agent doctor $agent; doctor_rc=$?; set -e && if [ \"$doctor_rc\" -eq 0 ] && test -n \"$idea\"; then\n printf 'PASS\\n' > \"$DOCTOR_FILE\";\nelse\n printf 'FAIL\\n' > \"$DOCTOR_FILE\";\nfi && exit 0",
116
+ "stateEffect": "read",
117
+ "evidenceEffect": "write"
118
+ },
119
+ "discovery:onEnter:0": {
120
+ "kind": "agent.run",
121
+ "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).",
122
+ "stateEffect": "read",
123
+ "evidenceEffect": "write"
124
+ },
125
+ "idea-eval:onEnter:0": {
126
+ "kind": "hitl.confirm",
127
+ "stateEffect": "read",
128
+ "evidenceEffect": "none"
129
+ },
130
+ "feature-create:onEnter:0": {
131
+ "kind": "agent.run",
132
+ "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.",
133
+ "stateEffect": "write",
134
+ "evidenceEffect": "write"
135
+ },
136
+ "feature-create:onEnter:1": {
137
+ "kind": "file.read.into-var",
138
+ "stateEffect": "read",
139
+ "evidenceEffect": "none"
140
+ },
141
+ "feature-create:onEnter:2": {
142
+ "kind": "shell",
143
+ "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",
144
+ "stateEffect": "write",
145
+ "evidenceEffect": "none"
146
+ },
147
+ "feature-create:onEnter:3": {
148
+ "kind": "shell",
149
+ "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",
150
+ "stateEffect": "write",
151
+ "evidenceEffect": "none"
152
+ },
153
+ "ac-generate:onEnter:0": {
154
+ "kind": "shell",
155
+ "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",
156
+ "stateEffect": "read",
157
+ "evidenceEffect": "write"
158
+ },
159
+ "ac-generate:onEnter:1": {
160
+ "kind": "agent.run",
161
+ "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.",
162
+ "stateEffect": "write",
163
+ "evidenceEffect": "write"
164
+ },
165
+ "ac-generate:onEnter:2": {
166
+ "kind": "shell",
167
+ "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",
168
+ "stateEffect": "write",
169
+ "evidenceEffect": "none"
170
+ },
171
+ "feature-check:onEnter:0": {
172
+ "kind": "hitl.confirm",
173
+ "stateEffect": "read",
174
+ "evidenceEffect": "none"
175
+ },
176
+ "system-design:onEnter:0": {
177
+ "kind": "shell",
178
+ "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\"",
179
+ "stateEffect": "read",
180
+ "evidenceEffect": "write"
181
+ },
182
+ "system-design:onEnter:1": {
183
+ "kind": "agent.run",
184
+ "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.",
185
+ "stateEffect": "write",
186
+ "evidenceEffect": "write"
187
+ },
188
+ "system-design:onEnter:2": {
189
+ "kind": "shell",
190
+ "invocation": "REVIEW=\".spur/run/$__runId-idea-design-review.md\" && awk '/^## Proposed design/{f=1; next} /^## Operator feedback/{f=0} f' \"$REVIEW\" | grep -q '[^[:space:]]'",
191
+ "stateEffect": "read",
192
+ "evidenceEffect": "none"
193
+ },
194
+ "design-approval:onEnter:0": {
195
+ "kind": "shell",
196
+ "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",
197
+ "stateEffect": "read",
198
+ "evidenceEffect": "write"
199
+ },
200
+ "design-approval:onEnter:1": {
201
+ "kind": "hitl.confirm",
202
+ "stateEffect": "read",
203
+ "evidenceEffect": "none"
204
+ },
205
+ "decompose:onEnter:0": {
206
+ "kind": "shell",
207
+ "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",
208
+ "stateEffect": "read",
209
+ "evidenceEffect": "write"
210
+ },
211
+ "decompose:onEnter:1": {
212
+ "kind": "agent.run",
213
+ "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.",
214
+ "stateEffect": "write",
215
+ "evidenceEffect": "write"
216
+ },
217
+ "decompose:onEnter:2": {
218
+ "kind": "shell",
219
+ "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",
220
+ "stateEffect": "read",
221
+ "evidenceEffect": "none"
222
+ },
223
+ "batch-create:onEnter:0": {
224
+ "kind": "hitl.confirm",
225
+ "stateEffect": "read",
226
+ "evidenceEffect": "none"
227
+ },
228
+ "batch-create-run:onEnter:0": {
229
+ "kind": "shell",
230
+ "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",
231
+ "stateEffect": "write",
232
+ "evidenceEffect": "write"
233
+ },
234
+ "handoff-finalize:onEnter:0": {
235
+ "kind": "shell",
236
+ "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\"",
237
+ "stateEffect": "write",
238
+ "evidenceEffect": "write"
239
+ },
240
+ "handoff:onEnter:0": {
241
+ "kind": "note",
242
+ "stateEffect": "read",
243
+ "evidenceEffect": "none"
244
+ },
245
+ "handoff:onEnter:1": {
246
+ "kind": "run.artifact",
247
+ "stateEffect": "read",
248
+ "evidenceEffect": "write"
249
+ },
250
+ "handoff:onEnter:2": {
251
+ "kind": "shell",
252
+ "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",
253
+ "stateEffect": "write",
254
+ "evidenceEffect": "write"
255
+ }
256
+ }
257
+ },
258
+ "task-pipeline": {
259
+ "definition": "config/workflows/task-pipeline.yaml",
260
+ "boundary": "task-execution",
261
+ "disposition": "canonical",
262
+ "callers": ["sp:spur-dev", "sp:super-planner"],
263
+ "terminalStates": ["done", "failed", "cancelled"],
264
+ "artifacts": ["verify-answer", "verify-verdict", "testing-section", "review-section"],
265
+ "failurePolicy": "fail-closed",
266
+ "modelQueries": ["implement", "test-fix", "review", "verify"],
267
+ "actions": {
268
+ "precheck:onEnter:0": {
269
+ "kind": "doctor.probe",
270
+ "stateEffect": "read",
271
+ "evidenceEffect": "write"
272
+ },
273
+ "precheck:onEnter:1": {
274
+ "kind": "shell",
275
+ "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",
276
+ "stateEffect": "read",
277
+ "evidenceEffect": "none"
278
+ },
279
+ "precheck:onEnter:2": {
280
+ "kind": "note",
281
+ "stateEffect": "read",
282
+ "evidenceEffect": "none"
283
+ },
284
+ "precheck:onEnter:3": {
285
+ "kind": "shell",
286
+ "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 $spurBin feature sync \"$FID\" --force --json 2>/dev/null ||\n $spurBin feature update \"$FID\" active 2>/dev/null || true;\n fi;\nfi; exit 0",
287
+ "stateEffect": "write",
288
+ "evidenceEffect": "none"
289
+ },
290
+ "precheck:onEnter:4": {
291
+ "kind": "shell",
292
+ "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\" --executor \"$implementAgent\"; else\n echo \"task-size-precheck skipped — plugins/sp/scripts/task-size-precheck.ts not present in project.\" >&2 &&\n echo \"PASS\" > \"$SIZE_FILE\";\nfi && exit 0",
293
+ "stateEffect": "read",
294
+ "evidenceEffect": "write"
295
+ },
296
+ "implement:onEnter:0": {
297
+ "kind": "agent.run",
298
+ "invocation": "/sp:dev-run --mode implement ${vars.wbs} --auto",
299
+ "stateEffect": "write",
300
+ "evidenceEffect": "none"
301
+ },
302
+ "implement:onEnter:1": {
303
+ "kind": "shell",
304
+ "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",
305
+ "stateEffect": "write",
306
+ "evidenceEffect": "none"
307
+ },
308
+ "implement:onEnter:2": {
309
+ "kind": "shell",
310
+ "invocation": "$formatCmd ; exit 0",
311
+ "stateEffect": "write",
312
+ "evidenceEffect": "none"
313
+ },
314
+ "test:onEnter:0": {
315
+ "kind": "shell",
316
+ "invocation": "mkdir -p .spur/run && STATUS_FILE=\".spur/run/$wbs-test-gate.status\" && LOG_FILE=\".spur/run/$wbs-test-gate.log\" && ATTEMPT_FILE=\".spur/run/$wbs-test-fix-attempt\" && echo 0 > \"$ATTEMPT_FILE\" && : > \"$LOG_FILE\" && gate_attempt=1; 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));\ndone && cat \"$LOG_FILE\" && FINDINGS_FILE=\".spur/run/$wbs-test-gate.findings\" && 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 && exit 0",
317
+ "stateEffect": "write",
318
+ "evidenceEffect": "write"
319
+ },
320
+ "test-fix:onEnter:0": {
321
+ "kind": "shell",
322
+ "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\"",
323
+ "stateEffect": "write",
324
+ "evidenceEffect": "write"
325
+ },
326
+ "test-fix:onEnter:1": {
327
+ "kind": "file.read.into-var",
328
+ "stateEffect": "read",
329
+ "evidenceEffect": "none"
330
+ },
331
+ "test-fix:onEnter:2": {
332
+ "kind": "agent.run",
333
+ "invocation": "/sp:dev-fixall \"${vars.qualityGateCmd}\" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings \"${vars.gateFindings}\"",
334
+ "stateEffect": "write",
335
+ "evidenceEffect": "none"
336
+ },
337
+ "test-recheck:onEnter:0": {
338
+ "kind": "shell",
339
+ "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 && exit 0",
340
+ "stateEffect": "write",
341
+ "evidenceEffect": "write"
342
+ },
343
+ "review:onEnter:0": {
344
+ "kind": "agent.run",
345
+ "invocation": "/sp:dev-review ${vars.wbs} --auto",
346
+ "stateEffect": "may-write",
347
+ "evidenceEffect": "write"
348
+ },
349
+ "approve:onEnter:0": {
350
+ "kind": "hitl.confirm",
351
+ "stateEffect": "read",
352
+ "evidenceEffect": "none"
353
+ },
354
+ "verify:onEnter:0": {
355
+ "kind": "agent.run",
356
+ "invocation": "/sp:dev-verify ${vars.wbs} --auto --fix all --focus all",
357
+ "stateEffect": "may-write",
358
+ "evidenceEffect": "write"
359
+ },
360
+ "verify:onEnter:1": {
361
+ "kind": "shell",
362
+ "invocation": "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt",
363
+ "stateEffect": "read",
364
+ "evidenceEffect": "write"
365
+ },
366
+ "verify:onEnter:2": {
367
+ "kind": "shell",
368
+ "invocation": "$spurBin task path $wbs --json 2>/dev/null | jq -r \".path // empty\" > \".spur/run/$wbs-taskpath.txt\" || true; exit 0",
369
+ "stateEffect": "read",
370
+ "evidenceEffect": "write"
371
+ },
372
+ "verify:onEnter:3": {
373
+ "kind": "file.read.into-var",
374
+ "stateEffect": "read",
375
+ "evidenceEffect": "none"
376
+ },
377
+ "verify:onEnter:4": {
378
+ "kind": "proof.fingerprint",
379
+ "stateEffect": "read",
380
+ "evidenceEffect": "none"
381
+ },
382
+ "verify:onEnter:5": {
383
+ "kind": "shell",
384
+ "invocation": "V=\".spur/run/$wbs-verdict.json\"; if [ -f \"$V\" ] && [ -n \"$proofDigest\" ]; then jq --arg d \"$proofDigest\" \".checks += [{\\\"name\\\":\\\"proof-input-digest\\\",\\\"status\\\":\\\"pass\\\",\\\"evidence\\\":\\$d}]\" \"$V\" > \"$V.tmp\" && mv \"$V.tmp\" \"$V\"; fi; exit 0",
385
+ "stateEffect": "read",
386
+ "evidenceEffect": "write"
387
+ },
388
+ "record:onEnter:0": {
389
+ "kind": "proof.fingerprint",
390
+ "stateEffect": "read",
391
+ "evidenceEffect": "none"
392
+ },
393
+ "record:onEnter:1": {
394
+ "kind": "shell",
395
+ "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",
396
+ "stateEffect": "write",
397
+ "evidenceEffect": "write"
398
+ },
399
+ "record:onEnter:2": {
400
+ "kind": "shell",
401
+ "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",
402
+ "stateEffect": "write",
403
+ "evidenceEffect": "none"
404
+ },
405
+ "done:onEnter:0": {
406
+ "kind": "shell",
407
+ "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",
408
+ "stateEffect": "write",
409
+ "evidenceEffect": "none"
410
+ },
411
+ "done:onEnter:1": {
412
+ "kind": "run.artifact",
413
+ "stateEffect": "read",
414
+ "evidenceEffect": "write"
415
+ },
416
+ "done:onEnter:2": {
417
+ "kind": "note",
418
+ "stateEffect": "read",
419
+ "evidenceEffect": "none"
420
+ },
421
+ "done:onEnter:3": {
422
+ "kind": "shell",
423
+ "invocation": "mkdir -p .spur/memory/sessions && echo \"checkpoint: task-pipeline done wbs=$wbs ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)\" > .spur/memory/sessions/$wbs-checkpoint.md",
424
+ "stateEffect": "write",
425
+ "evidenceEffect": "write"
426
+ }
427
+ }
428
+ },
429
+ "wrapup-pipeline": {
430
+ "definition": "config/workflows/wrapup-pipeline.yaml",
431
+ "boundary": "task-wrapup",
432
+ "disposition": "keep",
433
+ "callers": ["sp:spur-dev", "/sp:dev-wrap", "/sp:dev-wrapall"],
434
+ "terminalStates": ["done", "skipped"],
435
+ "artifacts": ["wrapup-learnings", "wrapup-metrics"],
436
+ "failurePolicy": "fail-closed",
437
+ "modelQueries": ["doc-sync"],
438
+ "actions": {
439
+ "start:onEnter:0": {
440
+ "kind": "note",
441
+ "stateEffect": "read",
442
+ "evidenceEffect": "none"
443
+ },
444
+ "task-resolve:onEnter:0": {
445
+ "kind": "note",
446
+ "stateEffect": "read",
447
+ "evidenceEffect": "none"
448
+ },
449
+ "doc-sync:onEnter:0": {
450
+ "kind": "agent.run",
451
+ "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).",
452
+ "stateEffect": "write",
453
+ "evidenceEffect": "write"
454
+ },
455
+ "doc-sync:onEnter:1": {
456
+ "kind": "shell",
457
+ "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",
458
+ "stateEffect": "write",
459
+ "evidenceEffect": "write"
460
+ },
461
+ "metrics-record:onEnter:0": {
462
+ "kind": "shell",
463
+ "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",
464
+ "stateEffect": "write",
465
+ "evidenceEffect": "write"
466
+ },
467
+ "feature-transition:onEnter:0": {
468
+ "kind": "shell",
469
+ "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; 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; exit 0",
470
+ "stateEffect": "write",
471
+ "evidenceEffect": "none"
472
+ },
473
+ "branch-cleanup:onEnter:0": {
474
+ "kind": "hitl.confirm",
475
+ "stateEffect": "read",
476
+ "evidenceEffect": "none"
477
+ },
478
+ "done:onEnter:0": {
479
+ "kind": "note",
480
+ "stateEffect": "read",
481
+ "evidenceEffect": "none"
482
+ },
483
+ "done:onEnter:1": {
484
+ "kind": "shell",
485
+ "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",
486
+ "stateEffect": "write",
487
+ "evidenceEffect": "write"
488
+ }
489
+ }
490
+ },
491
+ "pr-review": {
492
+ "definition": "config/workflows/pr-review.yaml",
493
+ "boundary": "pr-review",
494
+ "disposition": "keep",
495
+ "callers": ["sp:spur-dev", "/sp:dev-pr-review"],
496
+ "terminalStates": ["done", "pending", "failed"],
497
+ "artifacts": [
498
+ "pr-context",
499
+ "pr-hygiene",
500
+ "pr-push",
501
+ "pr",
502
+ "pr-request",
503
+ "pr-wait",
504
+ "pr-findings",
505
+ "pr-status"
506
+ ],
507
+ "failurePolicy": "fail-closed",
508
+ "modelQueries": [],
509
+ "actions": {
510
+ "preflight:onEnter:0": {
511
+ "kind": "shell",
512
+ "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",
513
+ "stateEffect": "read",
514
+ "evidenceEffect": "write"
515
+ },
516
+ "hygiene:onEnter:0": {
517
+ "kind": "shell",
518
+ "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",
519
+ "stateEffect": "read",
520
+ "evidenceEffect": "write"
521
+ },
522
+ "precheck:onEnter:0": {
523
+ "kind": "shell",
524
+ "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",
525
+ "stateEffect": "read",
526
+ "evidenceEffect": "write"
527
+ },
528
+ "push:onEnter:0": {
529
+ "kind": "shell",
530
+ "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",
531
+ "stateEffect": "write",
532
+ "evidenceEffect": "write"
533
+ },
534
+ "ensure-pr:onEnter:0": {
535
+ "kind": "shell",
536
+ "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",
537
+ "stateEffect": "write",
538
+ "evidenceEffect": "write"
539
+ },
540
+ "request:onEnter:0": {
541
+ "kind": "shell",
542
+ "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",
543
+ "stateEffect": "write",
544
+ "evidenceEffect": "write"
545
+ },
546
+ "wait:onEnter:0": {
547
+ "kind": "shell",
548
+ "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",
549
+ "stateEffect": "read",
550
+ "evidenceEffect": "write"
551
+ },
552
+ "collect:onEnter:0": {
553
+ "kind": "shell",
554
+ "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",
555
+ "stateEffect": "read",
556
+ "evidenceEffect": "write"
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }