@gobing-ai/spur 0.3.78 → 0.3.81

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 (177) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +29 -18
  3. package/config/config.global.yaml +10 -11
  4. package/config/pipeline-budgets.json +34 -2
  5. package/config/plugin-scripts.json +25 -0
  6. package/config/rules/boundary/config-loading-ownership.yaml +0 -3
  7. package/config/rules/boundary/dao-boundary.yaml +4 -17
  8. package/config/rules/boundary/planning-folder-hardcode.yaml +0 -1
  9. package/config/rules/boundary/sp-no-vendor-refs.yaml +3 -2
  10. package/config/rules/boundary/sp-runtime-path.yaml +3 -14
  11. package/config/rules/quality/coverage-gate.yaml +3 -14
  12. package/config/rules/quality/tsdoc-exports.yaml +4 -7
  13. package/config/rules/strict/http-boundaries.yaml +5 -8
  14. package/config/rules/strict/runtime-boundaries.yaml +1 -5
  15. package/config/rules/structure/protected-files.yaml +9 -3
  16. package/config/rules/structure/test-focus-skip.yaml +0 -2
  17. package/config/rules/structure/test-location.yaml +0 -5
  18. package/config/rules/surface/check-cli-surface.yaml +3 -2
  19. package/config/rules/typescript/bun-tooling.yaml +5 -7
  20. package/config/rules/typescript/guarded-happy-dom-register.yaml +0 -2
  21. package/config/rules/typescript/happy-dom-teardown.yaml +0 -2
  22. package/config/rules/typescript/no-biome-suppressions.yaml +0 -2
  23. package/config/rules/typescript/no-debugger.yaml +0 -2
  24. package/config/rules/typescript/no-eslint-suppressions.yaml +0 -4
  25. package/config/rules/typescript/no-leaky-module-mocks.yaml +6 -13
  26. package/config/rules/typescript/no-module-scope-import-calls.yaml +0 -2
  27. package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +0 -3
  28. package/config/rules/typescript/no-unmocked-module-eval-side-effects.yaml +0 -3
  29. package/config/rules/typescript/output-boundaries.yaml +0 -3
  30. package/config/rules/typescript/prefer-accessible-role-for-button-queries.yaml +0 -3
  31. package/config/rules/ui/ui-import-boundary.yaml +1 -5
  32. package/config/templates/AGENTS.md +26 -23
  33. package/config/templates/docs/00_ADR.md +13 -23
  34. package/config/templates/docs/01_PRD.md +5 -2
  35. package/config/templates/docs/02_ROADMAP.md +9 -13
  36. package/config/templates/docs/03_ARCHITECTURE.md +2 -2
  37. package/config/templates/docs/04_DESIGN.md +12 -31
  38. package/config/templates/docs/05_FEATURES.md +6 -18
  39. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +162 -394
  40. package/config/transition-shims.json +7 -7
  41. package/config/workflows/basic.yaml +4 -0
  42. package/config/workflows/docs-pipeline.yaml +13 -14
  43. package/config/workflows/feature-dev.yaml +20 -65
  44. package/config/workflows/history-anatomy.yaml +22 -1
  45. package/config/workflows/idea-pipeline.yaml +53 -97
  46. package/config/workflows/pr-review.yaml +21 -33
  47. package/config/workflows/task-pipeline.yaml +87 -330
  48. package/config/workflows/wayfinder-resolution.yaml +12 -26
  49. package/config/workflows/wrapup-pipeline.yaml +48 -189
  50. package/package.json +9 -9
  51. package/plugins/sp/README.md +22 -8
  52. package/plugins/sp/agents/expert-spur.md +41 -19
  53. package/plugins/sp/agents/super-reviewer.md +43 -8
  54. package/plugins/sp/lib/idea-handoff.generated.d.mts +17 -0
  55. package/plugins/sp/lib/idea-handoff.generated.mjs +1301 -0
  56. package/plugins/sp/plugin.json +1 -1
  57. package/plugins/sp/scripts/feature-dev-precheck.mjs +146 -0
  58. package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
  59. package/plugins/sp/scripts/idea-handoff.mjs +27 -0
  60. package/plugins/sp/scripts/idea-handoff.ts +44 -0
  61. package/plugins/sp/scripts/quality-gate.mjs +165 -0
  62. package/plugins/sp/scripts/quality-gate.ts +217 -0
  63. package/plugins/sp/scripts/verify-answer-lint.ts +21 -3
  64. package/plugins/sp/scripts/workflow-step-profile.mjs +319 -0
  65. package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
  66. package/plugins/sp/scripts/wrapup-steps.mjs +350 -0
  67. package/plugins/sp/scripts/wrapup-steps.ts +466 -0
  68. package/plugins/sp/skills/conflict-finding/SKILL.md +6 -0
  69. package/plugins/sp/skills/daily-summary/SKILL.md +1 -1
  70. package/plugins/sp/skills/doc-evolve/SKILL.md +26 -40
  71. package/plugins/sp/skills/doc-evolve/references/operations.md +17 -30
  72. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  73. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +29 -0
  74. package/plugins/sp/skills/spur-cli/references/agent.md +56 -14
  75. package/plugins/sp/skills/spur-cli/references/message.md +30 -3
  76. package/plugins/sp/skills/spur-cli/references/projects.md +45 -1
  77. package/plugins/sp/skills/spur-cli/references/self.md +5 -4
  78. package/plugins/sp/skills/spur-cli/references/serve.md +5 -4
  79. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +17 -1
  80. package/plugins/sp/skills/spur-cli/references/tasks.md +32 -2
  81. package/plugins/sp/skills/spur-cli/references/team.md +21 -1
  82. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +6 -3
  83. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +57 -18
  84. package/plugins/sp/skills/spur-composer/SKILL.md +145 -0
  85. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +14 -0
  86. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +3 -3
  87. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +12 -0
  88. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +46 -4
  89. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +24 -0
  90. package/plugins/sp/skills/spur-doctor/SKILL.md +138 -0
  91. package/plugins/sp/skills/taste-refactoring-api/README.md +43 -0
  92. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +334 -0
  93. package/plugins/sp/skills/taste-refactoring-api/checklists/daily-api-review.md +71 -0
  94. package/plugins/sp/skills/taste-refactoring-api/examples/refactor-example.md +72 -0
  95. package/plugins/sp/skills/taste-refactoring-api/examples/review-template.md +93 -0
  96. package/plugins/sp/skills/taste-refactoring-api/references/api-refactoring-playbook.md +253 -0
  97. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +79 -0
  98. package/plugins/sp/skills/taste-refactoring-api/references/research-basis.md +58 -0
  99. package/plugins/sp/skills/taste-refactoring-architect/README.md +26 -0
  100. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +471 -0
  101. package/plugins/sp/skills/taste-refactoring-architect/checklists/daily-architecture-review.md +48 -0
  102. package/plugins/sp/skills/taste-refactoring-architect/examples/refactor-example.md +55 -0
  103. package/plugins/sp/skills/taste-refactoring-architect/examples/review-template.md +51 -0
  104. package/plugins/sp/skills/taste-refactoring-architect/references/architecture-refactoring-playbook.md +173 -0
  105. package/plugins/sp/skills/taste-refactoring-architect/references/research-basis.md +28 -0
  106. package/plugins/sp/skills/taste-refactoring-tests/README.md +28 -0
  107. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +482 -0
  108. package/plugins/sp/skills/taste-refactoring-tests/checklists/daily-test-review.md +39 -0
  109. package/plugins/sp/skills/taste-refactoring-tests/examples/refactor-example.md +85 -0
  110. package/plugins/sp/skills/taste-refactoring-tests/examples/review-template.md +59 -0
  111. package/plugins/sp/skills/taste-refactoring-tests/references/research-basis.md +47 -0
  112. package/plugins/sp/skills/taste-refactoring-tests/references/test-refactoring-playbook.md +222 -0
  113. package/plugins/sp/skills/taste-refactoring-ui/README.md +12 -0
  114. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +290 -0
  115. package/plugins/sp/skills/taste-refactoring-ui/checklists/daily-ui-review.md +72 -0
  116. package/plugins/sp/skills/taste-refactoring-ui/examples/review-template.md +51 -0
  117. package/plugins/sp/skills/taste-refactoring-ui/references/refactoring-ui-playbook.md +170 -0
  118. package/plugins/sp/skills/wayfinder/SKILL.md +2 -2
  119. package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +30 -0
  120. package/schemas/spur-config.schema.json +49 -0
  121. package/spur.js +46936 -44198
  122. package/web/_astro/{BoardApp.CHQ1lycZ.js → BoardApp.B1U26g3I.js} +97 -95
  123. package/web/_astro/BoardApp.Csgyg-lS.js +1 -0
  124. package/web/_astro/{TaskDetail.GKfQJ60c.js → TaskDetail.DwPqpq7v.js} +1 -1
  125. package/web/_astro/{arc.DWEtA3Tx.js → arc.CweZEjN2.js} +1 -1
  126. package/web/_astro/{architectureDiagram-3BPJPVTR.DB42oWmP.js → architectureDiagram-3BPJPVTR.D89pbDuv.js} +1 -1
  127. package/web/_astro/{blockDiagram-GPEHLZMM.rhv-zNQV.js → blockDiagram-GPEHLZMM.BOuTeEpX.js} +1 -1
  128. package/web/_astro/{c4Diagram-AAUBKEIU.Ci4-4VvY.js → c4Diagram-AAUBKEIU.CASbkWZF.js} +1 -1
  129. package/web/_astro/channel.Cx6sXxhq.js +1 -0
  130. package/web/_astro/{chunk-2J33WTMH.Cc9veUgf.js → chunk-2J33WTMH.BKQYtOvY.js} +1 -1
  131. package/web/_astro/{chunk-4BX2VUAB.Bec9c4eI.js → chunk-4BX2VUAB.9sHLdMtG.js} +1 -1
  132. package/web/_astro/{chunk-55IACEB6.DoV8S1iB.js → chunk-55IACEB6.wOLXWlPs.js} +1 -1
  133. package/web/_astro/{chunk-727SXJPM.DwR-Qlyj.js → chunk-727SXJPM.DovFbwg3.js} +1 -1
  134. package/web/_astro/{chunk-AQP2D5EJ.ND_a81WY.js → chunk-AQP2D5EJ.B1Weod1X.js} +1 -1
  135. package/web/_astro/{chunk-FMBD7UC4.Wv_jwG48.js → chunk-FMBD7UC4.TEMS04st.js} +1 -1
  136. package/web/_astro/{chunk-ND2GUHAM.CXKXCMmp.js → chunk-ND2GUHAM.Cp8VT1wQ.js} +1 -1
  137. package/web/_astro/{chunk-QZHKN3VN.nkaoNYQq.js → chunk-QZHKN3VN.BzATdEcP.js} +1 -1
  138. package/web/_astro/{classDiagram-4FO5ZUOK.cMQcVlQu.js → classDiagram-4FO5ZUOK.C9BOCfAO.js} +1 -1
  139. package/web/_astro/{classDiagram-v2-Q7XG4LA2.cMQcVlQu.js → classDiagram-v2-Q7XG4LA2.C9BOCfAO.js} +1 -1
  140. package/web/_astro/{cose-bilkent-S5V4N54A.OaDJ7Mr2.js → cose-bilkent-S5V4N54A.DUnr4UAw.js} +1 -1
  141. package/web/_astro/{cynefin-OW5HDTMX.Chi8IphF.js → cynefin-OW5HDTMX.rYq5uM3D.js} +1 -1
  142. package/web/_astro/{cytoscape.esm.DzSz-X2X.js → cytoscape.esm.BB4DxJjf.js} +1 -1
  143. package/web/_astro/{dagre-BM42HDAG.CzK2t_Fp.js → dagre-BM42HDAG.CWeNKe3I.js} +1 -1
  144. package/web/_astro/{diagram-2AECGRRQ.DRvxlVS7.js → diagram-2AECGRRQ.DCkfls10.js} +1 -1
  145. package/web/_astro/{diagram-5GNKFQAL.CnYvNdwA.js → diagram-5GNKFQAL.D5U4JCka.js} +1 -1
  146. package/web/_astro/{diagram-KO2AKTUF.CpLpMw5R.js → diagram-KO2AKTUF.BZJgqaqG.js} +1 -1
  147. package/web/_astro/{diagram-LMA3HP47.JTb78qUA.js → diagram-LMA3HP47.DoMeHvPR.js} +1 -1
  148. package/web/_astro/{diagram-OG6HWLK6.Bk-1jDIb.js → diagram-OG6HWLK6.B50qwwWX.js} +1 -1
  149. package/web/_astro/{erDiagram-TEJ5UH35.D8hN9GZq.js → erDiagram-TEJ5UH35.DdGPG6LK.js} +1 -1
  150. package/web/_astro/{flowDiagram-I6XJVG4X.-6zQr6m5.js → flowDiagram-I6XJVG4X.QP2MJ12u.js} +1 -1
  151. package/web/_astro/{ganttDiagram-6RSMTGT7.DboLQ9ca.js → ganttDiagram-6RSMTGT7.BI6LgKSy.js} +1 -1
  152. package/web/_astro/{gitGraphDiagram-PVQCEYII.4tYvJKGR.js → gitGraphDiagram-PVQCEYII.npPZiC2G.js} +1 -1
  153. package/web/_astro/index.DayyIngm.css +1 -0
  154. package/web/_astro/{infoDiagram-5YYISTIA.Bd9rXpsB.js → infoDiagram-5YYISTIA.DCJCBVbp.js} +1 -1
  155. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CvMoaf67.js → ishikawaDiagram-YF4QCWOH.BMLV-3I1.js} +1 -1
  156. package/web/_astro/{journeyDiagram-JHISSGLW.Ccy1CA7y.js → journeyDiagram-JHISSGLW.LE58crde.js} +1 -1
  157. package/web/_astro/{kanban-definition-UN3LZRKU.0MaMqHNS.js → kanban-definition-UN3LZRKU.BPbz8rH9.js} +1 -1
  158. package/web/_astro/{linear.CHXgcIbN.js → linear.DhZaBtYh.js} +1 -1
  159. package/web/_astro/{mermaid.core.Ca-kcelG.js → mermaid.core.BD5-jXum.js} +6 -6
  160. package/web/_astro/{mindmap-definition-RKZ34NQL.BUIDlHa0.js → mindmap-definition-RKZ34NQL.MTJyrQ65.js} +1 -1
  161. package/web/_astro/ordinal.BYWQX77i.js +1 -0
  162. package/web/_astro/{pieDiagram-4H26LBE5.2dX3CU1s.js → pieDiagram-4H26LBE5.BrDhDvIS.js} +1 -1
  163. package/web/_astro/{quadrantDiagram-W4KKPZXB.B3LBlRiv.js → quadrantDiagram-W4KKPZXB.71d73_5N.js} +1 -1
  164. package/web/_astro/{requirementDiagram-4Y6WPE33.X12I2uNx.js → requirementDiagram-4Y6WPE33.Bga6UF-z.js} +1 -1
  165. package/web/_astro/{sankeyDiagram-5OEKKPKP.BXohIHqx.js → sankeyDiagram-5OEKKPKP.BnHs4K82.js} +1 -1
  166. package/web/_astro/{sequenceDiagram-3UESZ5HK.C37ZIUzg.js → sequenceDiagram-3UESZ5HK.DsfY2gnj.js} +1 -1
  167. package/web/_astro/{stateDiagram-AJRCARHV.BRgz317z.js → stateDiagram-AJRCARHV.DvsTSc9a.js} +1 -1
  168. package/web/_astro/{stateDiagram-v2-BHNVJYJU.7VYSXN9-.js → stateDiagram-v2-BHNVJYJU.DxzzmHUR.js} +1 -1
  169. package/web/_astro/{timeline-definition-PNZ67QCA.BVNz_HiN.js → timeline-definition-PNZ67QCA.4ZuQmOTt.js} +1 -1
  170. package/web/_astro/{vennDiagram-CIIHVFJN.CHVDkPX4.js → vennDiagram-CIIHVFJN.Ck5Q86SG.js} +1 -1
  171. package/web/_astro/{wardleyDiagram-YWT4CUSO.EQQ_qT9v.js → wardleyDiagram-YWT4CUSO.BK7k2hXr.js} +1 -1
  172. package/web/_astro/{xychartDiagram-2RQKCTM6.DrAT9WoP.js → xychartDiagram-2RQKCTM6.DfCrgauK.js} +1 -1
  173. package/web/index.html +2 -2
  174. package/web/_astro/BoardApp.DV9kx0wo.js +0 -1
  175. package/web/_astro/channel.BAI6xLeV.js +0 -1
  176. package/web/_astro/index.Dcr_8fiK.css +0 -1
  177. package/web/_astro/ordinal.DBvzRdQf.js +0 -1
@@ -15,19 +15,19 @@
15
15
  "keepsWorking": "a pre-existing agent spec carrying only type (no executor field) still drains: --agent <specId> falls back to the spec's coding-agent type instead of resolving a missing executor",
16
16
  "removalCondition": "every spec under .spur/agents/ records an executor field (scan .spur/agents/*.yaml for the key; re-materialize the demo team first)"
17
17
  },
18
- {
19
- "id": "agent-flag-spec-id",
20
- "wbs": "0542",
21
- "file": "apps/cli/src/commands/agent.ts",
22
- "keepsWorking": "a team spec id passed to --agent <spec-id> still addresses the spec and drives the drain path, warned once by warnAgentSpecIdOnce; --spec <id> is the canonical carrier",
23
- "removalCondition": "no --agent <spec-id> usage remains in config/workflows/, plugins/sp/, or docs/ (scan for --agent values that match a .spur/agents/ spec id)"
24
- },
25
18
  {
26
19
  "id": "agent-default-executor",
27
20
  "wbs": "0542",
28
21
  "file": "packages/app/src/services/agent-service.ts",
29
22
  "keepsWorking": "a configured executor name in agent.default still resolves during the transition, warned once by warnAgentDefaultExecutorOnce; the value domain moved to roles (recommended default: coder)",
30
23
  "removalCondition": "no agent.default value names an agent.executors entry (scan .spur/config.yaml and config/config.example.yaml against the agent.executors names)"
24
+ },
25
+ {
26
+ "id": "team-noun-retired",
27
+ "wbs": "0848",
28
+ "file": "apps/cli/src/commands/team.ts",
29
+ "keepsWorking": "the `spur team` noun keeps working after its six verbs moved to owning nouns (0848): assign → `spur task update --assignee`, status → `spur agent list --specs`, up → fleet materialization at serve start (`spur projects list --fleet` for --check), down/start/stop → `spur agent stop|start|stop <spec-id>`; a one-time stderr warning (warnTeamNounRetiredOnce) names the replacement for the invoked verb and every verb keeps its previous exit code and output",
30
+ "removalCondition": "no `spur team` invocation remains in config/workflows/, plugins/sp/, scripts/, or docs/, and the cutover window is recorded in docs/features/G64_retire-workspace-inbox-teams-and-spur-team.md"
31
31
  }
32
32
  ]
33
33
  }
@@ -56,6 +56,8 @@ states:
56
56
  onEnter:
57
57
  - kind: shell
58
58
  options:
59
+ # (e) 0825: 10 lines — soft probe must run the trusted compound gate via `sh -c`
60
+ # and write the status file the guards read, always exit 0 (0771 R1 contract).
59
61
  command: >-
60
62
  mkdir -p .spur/run &&
61
63
  STATUS_FILE=".spur/run/$__runId-basic-gate.status" &&
@@ -83,6 +85,8 @@ states:
83
85
  options:
84
86
  agent: ${vars.agent}
85
87
  input: /sp:dev-fixall "${vars.qualityGateCmd}"
88
+ answerFile: .spur/run/${vars.__runId}-basic-fix-answer.txt
89
+ expectFile: .spur/run/${vars.__runId}-basic-fix-answer.txt
86
90
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
87
91
  role: coder
88
92
  timeoutMs: ${vars.stepTimeoutMs}
@@ -138,12 +138,8 @@ states:
138
138
  # excluded from the digest's git-tree half, so the spec is folded in via `taskFile`.
139
139
  - kind: shell
140
140
  options:
141
- # 0760 R1 (sibling of 0751 R2): task-path lookup fails closed. Drop
142
- # `2>/dev/null`, `|| true`, and the forced `exit 0` so an unresolved
143
- # wbs exits non-zero with a named message. The resolved path folds
144
- # into the proof digest via `taskFile:` below; a silent miss would
145
- # degrade the docs-pipeline proof to tree-only (same hazard 0751 R2
146
- # removed from task-pipeline). (0769: run-scoped capture path.)
141
+ # (e) 0825: 6 lines — fail-closed lookup feeds the verdict/digest file names
142
+ # (0760 R1); a silent miss would degrade the proof to tree-only.
147
143
  command: >-
148
144
  set -e;
149
145
  task_path="$($spurBin task path $wbs --json | jq -r '.path // .filePath // empty')";
@@ -159,6 +155,8 @@ states:
159
155
  # folding a stale spec into the proof.
160
156
  - kind: shell
161
157
  options:
158
+ # (e) 0825: 9 lines — per-check rc capture (task show/feature show/jq) keeps an
159
+ # orphan task and a broken linked feature distinguishable (0785 R2 contract).
162
160
  command: >-
163
161
  set -e;
164
162
  FID="$($spurBin task show $wbs --json 2>/dev/null | jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null)";
@@ -191,6 +189,7 @@ states:
191
189
  role: reviewer
192
190
  timeoutMs: ${vars.stepTimeoutMs}
193
191
  answerFile: .spur/run/${vars.__runId}-docs-verify-answer.txt
192
+ expectFile: .spur/run/${vars.__runId}-docs-verify-answer.txt
194
193
  - kind: shell
195
194
  options:
196
195
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$__runId-docs-verify-answer.txt"
@@ -199,6 +198,8 @@ states:
199
198
  # guard below, not this step.
200
199
  - kind: shell
201
200
  options:
201
+ # (e) 0825: 6 lines — stamp shell freezes the docs proof digest for the closing
202
+ # guard; the record→done guard compares against this stamp.
202
203
  command: >-
203
204
  V=".spur/run/$wbs-verdict.json";
204
205
  if [ -f "$V" ] && [ -n "$proofDigest" ]; then
@@ -263,7 +264,7 @@ transitions:
263
264
  guard:
264
265
  kind: shell
265
266
  options:
266
- command: 'test "$(cat .spur/run/$wbs-docs-precheck.status 2>/dev/null)" = PASS'
267
+ command: 'test "$(cat .spur/run/$__runId-docs-precheck.status 2>/dev/null)" = PASS'
267
268
  - from: precheck
268
269
  to: failed
269
270
  description: Precheck FAIL — stop before draft.
@@ -313,17 +314,13 @@ transitions:
313
314
  guard:
314
315
  kind: shell
315
316
  options:
316
- # Hardened (0703 P2 carry): every operand is required non-empty (`test -n`) so a
317
- # missing file or empty var fails closed instead of empty-equals-empty passing.
317
+ # (e) 0825: 8-line operand ladder condensed to one jq select — same file, same
318
+ # digest-locked PASS semantics (0769 R4/R5); empty/malformed still fail closed.
318
319
  command: >-
319
320
  test -n "$proofDigest" &&
320
321
  test -n "$proofDigestNow" &&
321
322
  test "$proofDigestNow" = "$proofDigest" &&
322
- test -f ".spur/run/$wbs-verdict.json" &&
323
- test -n "$(jq -r '.verdict // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
324
- test "$(jq -r '.verdict // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
325
- test -n "$(jq -r '.proof.digest // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
326
- test "$(jq -r '.proof.digest // empty' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
323
+ test "$(jq -r --arg d "$proofDigest" 'select(.proof.digest == $d) | .verdict // empty' ".spur/run/$wbs-verdict.json" 2>/dev/null)" = PASS
327
324
  - from: verify
328
325
  to: failed
329
326
  description: Non-PASS verdict, missing/malformed answer, or digest mismatch (R5) — the run
@@ -338,6 +335,8 @@ transitions:
338
335
  guard:
339
336
  kind: shell
340
337
  options:
338
+ # (e) 0825: 5-line guard — captured record PASS + digest-locked persisted verdict
339
+ # must both hold before done; a failed `task record` never converts to success.
341
340
  command: >-
342
341
  test "$(cat .spur/run/$__runId-docs-record.status 2>/dev/null)" = PASS &&
343
342
  test -f ".spur/run/$wbs-verdict.json" &&
@@ -91,35 +91,11 @@ states:
91
91
  onEnter:
92
92
  - kind: shell
93
93
  options:
94
+ # (d) 0825: the 12-command identity/roster program moved into
95
+ # plugins/sp/scripts/feature-dev-precheck.ts (§1.1 owner); same messages,
96
+ # artifacts and soft-fail exit-0 contract, wrapper fails closed.
94
97
  command: >-
95
- mkdir -p .spur/run;
96
- FEATURE_JSON=".spur/run/$__runId-feature-dev-feature.json";
97
- ROSTER_JSON=".spur/run/$__runId-feature-dev-roster.json";
98
- TASKS_TXT=".spur/run/$__runId-feature-dev-tasks.txt";
99
- PRECHECK_STATUS=".spur/run/$__runId-feature-dev-precheck.status";
100
- rm -f "$PRECHECK_STATUS";
101
- set +e;
102
- test -n "$featureId" && test -n "$__runId";
103
- id_rc=$?;
104
- if [ "$id_rc" -eq 0 ]; then $spurBin feature show "$featureId" --json > "$FEATURE_JSON" 2>&1; show_rc=$?; else show_rc=1; fi;
105
- if [ "$id_rc" -eq 0 ] && [ "$show_rc" -eq 0 ] && jq -e --arg id "$featureId" 'type == "object" and .id == $id' "$FEATURE_JSON" > /dev/null 2>&1; then $spurBin task list --feature "$featureId" --json > "$ROSTER_JSON" 2>&1; list_rc=$?; else list_rc=1; fi;
106
- if [ "$id_rc" -ne 0 ] || [ "$show_rc" -ne 0 ] || [ "$list_rc" -ne 0 ]; then
107
- echo "feature-dev precheck: missing featureId/runId, unknown feature '$featureId', or unreadable roster (rc $id_rc/$show_rc/$list_rc) — supply an existing planned feature via /sp:dev-plan or /sp:dev-idea; nothing was auto-created or re-planned" >&2;
108
- printf 'FAIL\n' > "$PRECHECK_STATUS";
109
- elif ! jq -e 'type == "array" and length > 0' "$ROSTER_JSON" > /dev/null 2>&1; then
110
- echo "feature-dev precheck: roster at $ROSTER_JSON is malformed, not an array, or empty — plan the feature first via /sp:dev-plan; refusing to replan or run an empty batch" >&2;
111
- printf 'FAIL\n' > "$PRECHECK_STATUS";
112
- elif ! jq -e 'all(.[]; (.wbs | type == "string" and length > 0)) and ([.[].wbs] | length == (unique | length)) and all(.[]; .status == "todo" or .status == "done" or .status == "cancelled" or .status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")' "$ROSTER_JSON" > /dev/null 2>&1; then
113
- echo "feature-dev precheck: roster has empty/duplicate/mismatched WBS identities or unknown statuses at $ROSTER_JSON — repair the task corpus; refusing to batch a broken roster" >&2;
114
- printf 'FAIL\n' > "$PRECHECK_STATUS";
115
- elif [ "$(jq '[.[] | select(.status == "backlog" or .status == "wip" or .status == "testing" or .status == "blocked")] | length' "$ROSTER_JSON")" -gt 0 ]; then
116
- echo "feature-dev precheck: linked task(s) are backlog/wip/testing/blocked — refine or resume them through their own task pipelines before batching; refusing to launch overlapping work" >&2;
117
- printf 'FAIL\n' > "$PRECHECK_STATUS";
118
- else
119
- printf '%s' "$(jq -r '[.[] | select(.status == "todo") | .wbs] | sort | join(",")' "$ROSTER_JSON")" > "$TASKS_TXT";
120
- printf 'PASS\n' > "$PRECHECK_STATUS";
121
- fi;
122
- exit 0
98
+ mkdir -p .spur/run; if [ -f plugins/sp/scripts/feature-dev-precheck.ts ]; then bun plugins/sp/scripts/feature-dev-precheck.ts; elif W="$(superskill script path sp feature-dev-precheck.mjs 2>/dev/null)" && [ -f "$W" ]; then node "$W"; else echo "feature-dev precheck failed closed — feature-dev-precheck script not found — run 'superskill install sp'" >&2; printf 'FAIL\n' > ".spur/run/$__runId-feature-dev-precheck.status"; fi; exit 0
123
99
  - kind: note
124
100
  options:
125
101
  message: "Feature-dev start for existing feature ${vars.featureId} (0782 reuse path): roster resolved under .spur/run/${vars.__runId}-feature-dev-roster.json; frozen todo list at .spur/run/${vars.__runId}-feature-dev-tasks.txt."
@@ -143,6 +119,8 @@ states:
143
119
  options:
144
120
  agent: ${vars.agent}
145
121
  input: /sp:dev-runall --tasks ${vars.featureTaskIds} --auto
122
+ answerFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
123
+ expectFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
146
124
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
147
125
  role: planner
148
126
  timeoutMs: ${vars.stepTimeoutMs}
@@ -162,6 +140,8 @@ states:
162
140
  options:
163
141
  agent: ${vars.agent}
164
142
  input: /sp:dev-runall --tasks ${vars.featureTaskIds}
143
+ answerFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
144
+ expectFile: .spur/run/${vars.__runId}-feature-dev-runall-answer.txt
165
145
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
166
146
  role: planner
167
147
  timeoutMs: ${vars.stepTimeoutMs}
@@ -177,17 +157,10 @@ states:
177
157
  onEnter:
178
158
  - kind: shell
179
159
  options:
160
+ # (e) 0825: 9 lines — `feature check --as done` runs EXACTLY ONCE per run (0782 R3)
161
+ # and owns the status write the guards read; split would re-run the check.
180
162
  command: >-
181
- mkdir -p .spur/run;
182
- VERIFY_JSON=".spur/run/$__runId-feature-dev-verify.json";
183
- VERIFY_STATUS=".spur/run/$__runId-feature-dev-verify.status";
184
- VERIFY_TMP=".spur/run/$__runId-feature-dev-verify.status.tmp";
185
- rm -f "$VERIFY_STATUS";
186
- set +e;
187
- $spurBin feature check "$featureId" --as done --json > "$VERIFY_JSON" 2>&1;
188
- check_rc=$?;
189
- set -e;
190
- if [ "$check_rc" -eq 0 ] && jq -e 'type == "array" and length > 0 and all(.[]; .pass == true)' "$VERIFY_JSON" > /dev/null 2>&1; then printf 'PASS\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; else printf 'FAIL\n' > "$VERIFY_TMP" && mv -f "$VERIFY_TMP" "$VERIFY_STATUS"; fi
163
+ mkdir -p .spur/run; rm -f ".spur/run/$__runId-feature-dev-verify.status"; $spurBin feature check "$featureId" --as done --json > ".spur/run/$__runId-feature-dev-verify.json" 2>&1 && jq -e 'type == "array" and length > 0 and all(.[]; .pass == true)' ".spur/run/$__runId-feature-dev-verify.json" > /dev/null 2>&1 && R=PASS || R=FAIL; printf '%s\n' "$R" > ".spur/run/$__runId-feature-dev-verify.status.tmp" && mv -f ".spur/run/$__runId-feature-dev-verify.status.tmp" ".spur/run/$__runId-feature-dev-verify.status"
191
164
  - kind: note
192
165
  options:
193
166
  message: "Feature verification for ${vars.featureId}: decision at .spur/run/${vars.__runId}-feature-dev-verify.status (evidence at .spur/run/${vars.__runId}-feature-dev-verify.json) — exactly one `feature check --as done --json` invocation per run (0782 R3)."
@@ -211,36 +184,18 @@ states:
211
184
  `requireCleanReview=true` turns a non-clean COLLECTED verdict (including
212
185
  collect FAIL) into the blocking edge declared first below.
213
186
  onEnter:
187
+ # (e) 0825: one request action + one collect action (same verdicts, same files,
188
+ # collect gated on the captured head SHA as before) to hold the §1.2 budgets.
214
189
  - kind: shell
215
190
  options:
216
191
  command: >-
217
- mkdir -p .spur/run &&
218
- STATUS_FILE=".spur/run/$__runId-integration-review.status" &&
219
- REQUEST_JSON=".spur/run/$__runId-integration-review.json" &&
220
- COLLECT_JSON=".spur/run/$__runId-integration-review-collect.json" &&
221
- COLLECT_STATUS=".spur/run/$__runId-integration-review-collect.status" &&
222
- PR_REQUEST_STATUS=".spur/run/$__runId-integration-review-pr-request.status" &&
223
- set +e &&
224
- bun "$(superskill script path sp pr-reviewing.ts)" request --base "$baseBranch" --json
225
- --status-file "$PR_REQUEST_STATUS" > "$REQUEST_JSON" 2>&1;
226
- request_rc=$?; set -e &&
227
- if [ "$request_rc" -eq 0 ]; then
228
- printf 'PASS\n' > "$STATUS_FILE";
229
- else
230
- printf 'FAIL\n' > "$STATUS_FILE";
231
- fi &&
232
- HEAD_SHA=$(jq -r '.head // empty' "$REQUEST_JSON" 2>/dev/null || true) &&
233
- if [ -z "$HEAD_SHA" ]; then
234
- echo "integration-review: request result carried no head SHA — collect not run, recorded FAIL" >&2;
235
- printf 'FAIL\n' > "$COLLECT_STATUS";
236
- else
237
- set +e &&
238
- bun "$(superskill script path sp pr-reviewing.ts)" collect --head "$HEAD_SHA" --json
239
- --status-file "$COLLECT_STATUS" > "$COLLECT_JSON" 2>&1;
240
- collect_rc=$?; set -e &&
241
- echo "integration-review: collect rc=$collect_rc status=$(cat "$COLLECT_STATUS" 2>/dev/null || echo missing) — verdict captured to $COLLECT_JSON" >&2;
242
- fi;
243
- exit 0
192
+ mkdir -p .spur/run; bun "$(superskill script path sp pr-reviewing.ts)" request --base "$baseBranch" --json --status-file ".spur/run/$__runId-integration-review-pr-request.status" > ".spur/run/$__runId-integration-review.json" 2>&1 && printf 'PASS\n' > ".spur/run/$__runId-integration-review.status" || printf 'FAIL\n' > ".spur/run/$__runId-integration-review.status"; exit 0
193
+ - kind: shell
194
+ options:
195
+ # (e) 0825: 8 lines — collect stays head-locked to the request record and records
196
+ # its verdict status file; the rc/status echo is the audible soft-fail contract.
197
+ command: >-
198
+ if H=$(jq -er '.head // empty | select(. != "")' ".spur/run/$__runId-integration-review.json" 2>/dev/null); then bun "$(superskill script path sp pr-reviewing.ts)" collect --head "$H" --json --status-file ".spur/run/$__runId-integration-review-collect.status" > ".spur/run/$__runId-integration-review-collect.json" 2>&1; rc=$?; echo "integration-review: collect rc=$rc status=$(cat ".spur/run/$__runId-integration-review-collect.status" 2>/dev/null || echo missing) — verdict captured to .spur/run/$__runId-integration-review-collect.json" >&2; else echo "integration-review: request result carried no head SHA — collect not run, recorded FAIL" >&2; printf 'FAIL\n' > ".spur/run/$__runId-integration-review-collect.status"; fi; exit 0
244
199
  - kind: note
245
200
  options:
246
201
  message: "Integration review for feature ${vars.featureId}: request verdict at .spur/run/${vars.__runId}-integration-review.status (request state only — REQUESTED/ALREADY_* are NEVER clean evidence); collected verdict at .spur/run/${vars.__runId}-integration-review-collect.status (CLEAN/FINDINGS/PENDING — the only clean evidence). Advisory unless requireCleanReview=true."
@@ -72,7 +72,10 @@ vars:
72
72
  agent: "claude"
73
73
  spurBin: "spur"
74
74
  __runId: ""
75
- stepTimeoutMs: "1800000"
75
+ # 2026-09-12: raised 30m -> 60m. Observed runs on zai/glm-5.3-flash butt against the
76
+ # 30m ceiling (correct exited at exactly 30m00s; a prior run passed at 29m52s) — the
77
+ # enrich/validate/correct stages legitimately need >30m for the 60KB candidate report.
78
+ stepTimeoutMs: "3600000"
76
79
  workflowFile: "config/workflows/history-anatomy.yaml"
77
80
  contractVersion: "1"
78
81
  reportDir: "docs/report"
@@ -225,6 +228,24 @@ states:
225
228
  role: reviewer
226
229
  expectFile: .spur/run/${vars.__runId}-validation.txt
227
230
  timeoutMs: ${vars.stepTimeoutMs}
231
+ - kind: shell
232
+ options:
233
+ # 2026-09-13 verdict-placement normalization: the 0771 guard reads ONLY the
234
+ # final line, but models sometimes lead with `Verdict: PASS` and close with
235
+ # prose — a substantive PASS must not die as a format phantom (observed
236
+ # 2026-09-13 run 94c4d6dc). Moves the verdict line to the end ONLY when
237
+ # exactly one exact-line verdict exists and it is PASS. Any `Verdict: FAIL`
238
+ # line anywhere (or multiple/ambiguous verdict lines) suppresses
239
+ # normalization, so the guard still fails and a real FAIL can never be
240
+ # laundered into a PASS.
241
+ command: >-
242
+ f=.spur/run/$__runId-validation.txt;
243
+ if [ -f "$f" ] && ! tail -n 1 "$f" | grep -qx 'Verdict: PASS'; then
244
+ p=$(grep -cx 'Verdict: PASS' "$f"); q=$(grep -cx 'Verdict: FAIL' "$f");
245
+ if [ "$p" = 1 ] && [ "$q" = 0 ]; then
246
+ grep -vx 'Verdict: PASS' "$f" > "$f.tmp" && printf 'Verdict: PASS\n' >> "$f.tmp" && mv "$f.tmp" "$f";
247
+ fi;
248
+ fi
228
249
  - kind: shell
229
250
  options:
230
251
  command: >-
@@ -331,7 +331,7 @@ states:
331
331
  - kind: agent.run
332
332
  options:
333
333
  agent: ${vars.planningAgent}
334
- input: "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 task-batch.schema.json. Schema-permitted fields per entry: `name`, `background`, `requirements`, `design`, `plan`, `acceptance_criteria`, `feature_id`, `parent_wbs`, `priority`, `tags`, `template` — schema validation rejects anything else. `design`, `plan`, and `acceptance_criteria` are supported batch fields and normal default planning fills them from your analysis; the per-task refine step after batch-create still deepens them when a task needs more detail. 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."
334
+ input: "Run sp:spec-decomposition for feature ${vars.featureId} per skill references/decomposition.md § Idea-pipeline emission: sizing first, then the batch JSON at .spur/run/${vars.__runId}-idea-task-batch.json and the private task-order sidecar at .spur/run/${vars.__runId}-idea-task-order.json."
335
335
  # Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
336
336
  role: planner
337
337
  expectFile: .spur/run/${vars.__runId}-idea-task-batch.json
@@ -341,6 +341,8 @@ states:
341
341
  # unique, every sidecar name/dependency must refer to exactly one batch name, and — the
342
342
  # converse (F2, 0518 verify) — every batch name must appear in the sidecar, so a partial
343
343
  # sidecar can never silently skip `task deps` for an unlisted item (`[]` is valid).
344
+ # (warn) stays inline by design: one jq predicate over two run-scoped files; splitting
345
+ # it into a script would hide the exact fail-closed contract this guard pins (0824).
344
346
  - kind: shell
345
347
  options:
346
348
  command: >-
@@ -380,17 +382,22 @@ states:
380
382
  (CLI error or malformed result) writes the failed sentinel, preserving the
381
383
  existing retry behavior.
382
384
  onEnter:
385
+ # (e) idempotent batch creation: sentinel + temp/mv atomicity stay inline (0824);
386
+ # the jq verdict check keeps the done sentinel truthful so handoff-finalize can
387
+ # zip batch names to the returned WBS list.
383
388
  - kind: shell
384
389
  options:
385
390
  command: >-
386
- if test -f .spur/run/$__runId-idea-batch-create.done; then exit 0; fi;
387
- rm -f .spur/run/$__runId-idea-batch-create.failed .spur/run/$__runId-idea-batch-create-result.json .spur/run/$__runId-idea-batch-create-result.json.tmp;
388
- if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --skip-ready --json > .spur/run/$__runId-idea-batch-create-result.json.tmp && jq -e ".created == (.wbs | length)" .spur/run/$__runId-idea-batch-create-result.json.tmp >/dev/null 2>&1; then
389
- mv .spur/run/$__runId-idea-batch-create-result.json.tmp .spur/run/$__runId-idea-batch-create-result.json &&
390
- date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.done;
391
+ P=".spur/run/$__runId-idea-batch-create" &&
392
+ test ! -f "$P.done" || exit 0 &&
393
+ rm -f "$P.failed" "$P-result.json" "$P-result.json.tmp" &&
394
+ if $spurBin task batch-create --file .spur/run/$__runId-idea-task-batch.json --skip-ready --json > "$P-result.json.tmp" &&
395
+ jq -e ".created == (.wbs | length)" "$P-result.json.tmp" >/dev/null 2>&1; then
396
+ mv "$P-result.json.tmp" "$P-result.json" &&
397
+ date -u +%Y-%m-%dT%H:%M:%SZ > "$P.done";
391
398
  else
392
- rm -f .spur/run/$__runId-idea-batch-create-result.json.tmp;
393
- date -u +%Y-%m-%dT%H:%M:%SZ > .spur/run/$__runId-idea-batch-create.failed;
399
+ rm -f "$P-result.json.tmp" &&
400
+ date -u +%Y-%m-%dT%H:%M:%SZ > "$P.failed";
394
401
  fi
395
402
 
396
403
  - id: ready-prepare
@@ -411,24 +418,22 @@ states:
411
418
  # Declared Layer-1 role (0538 R2), same planner executor as decompose.
412
419
  role: planner
413
420
  timeoutMs: ${vars.stepTimeoutMs}
421
+ # (warn) non-slash pointer: the 0788 checklist is per-checkout (digest via the
422
+ # project's own computePlanningDigest), so the bounded prompt pins the skill
423
+ # reference instead of a command; artifacts are gated by answerFile/expectFile.
414
424
  input: >-
415
- Ready-by-default preparation for feature ${vars.featureId} (0788). The created WBS
416
- values are in .spur/run/${vars.__runId}-idea-batch-create-result.json under .wbs.
417
- For EACH wbs: resolve the task file with `spur task path <wbs> --json`; open the
418
- task doc and apply the ready-refinement checklist — make requirements, design,
419
- plan, acceptance criteria, decisions, dependencies and premises present and
420
- non-placeholder so `spur task check <wbs> --json` exits 0; edit only planning
421
- sections, never Solution/Testing/Review/History. Record one checklist row per id
422
- with concrete evidence of how you verified it. Compute the planning digest with
423
- the project's own implementation when this is a monorepo checkout:
424
- `bun -e 'const m = await import("./packages/app/src/services/task-readiness"); console.log(m.computePlanningDigest(await Bun.file(process.argv[1]).text()))' <task-file>`
425
- — when that is impossible in this checkout, set status "skipped" instead of
426
- guessing a digest. Finally write .spur/run/${vars.__runId}-idea-ready.json with
427
- exactly this shape: {"runId":"${vars.__runId}","depth":"ready","tasks":[{"wbs":"<wbs>","status":"ready" or "failed" or "skipped","planningDigest":"<sha256 hex>","checks":[{"id":"requirements" or "design" or "plan" or "ac" or "decisions" or "dependencies" or "premises","pass":true or false,"evidence":"<how verified>"}]}]}. A task you cannot fully prepare gets status "failed" or "skipped" — never fabricate evidence; the handoff degrades to refineall.
425
+ Run the ready-prepare stage for feature ${vars.featureId} per sp:spur-dev
426
+ references/planning-workflow.md § Step 5.6 (Ready preparation): read
427
+ .spur/run/${vars.__runId}-idea-batch-create-result.json and write
428
+ .spur/run/${vars.__runId}-idea-ready.json.
429
+ answerFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
430
+ expectFile: .spur/run/${vars.__runId}-ready-prepare-answer.txt
428
431
  # Fail-closed shape validation (mirrors the order-sidecar guard). Absence is
429
432
  # normalized to an empty sidecar: finalize and the seeded fallback then degrade
430
433
  # the recommendation to refineall instead of failing the run. A PRESENT but
431
434
  # malformed sidecar fails the run here — it must not masquerade as evidence.
435
+ # (warn) stays inline by design: single jq shape predicate over the run-scoped
436
+ # sidecar; the failed/skipped rows are what degrade the recommendation (0824).
432
437
  - kind: shell
433
438
  options:
434
439
  command: >-
@@ -447,87 +452,23 @@ states:
447
452
  A mapping or CLI error fails the run before terminal handoff; an unready
448
453
  task is a successful planning outcome recorded as a refineall recommendation.
449
454
  onEnter:
450
- # F1 (0518 verify): the per-task check loop fails closed. A stderr-only (non-JSON)
451
- # `task check --json` exception would otherwise drop that task from the JSONL results
452
- # without failing the run (the loop's exit status is its last body command), silently
453
- # flipping the recommendation to runall — violating R3's any-fail=>refineall invariant.
454
- # Each `jq -c … >> "$CHECKS"` appends under `|| exit 1`, and a row-count assertion
455
- # (CHECKS lines == WBS count) gates the recommendation computation.
456
- # D5-O (R4): the deterministic writer owns finalization wherever it exists.
457
- # `idea-handoff-cli.ts` calls the tested `finalizeIdeaHandoff` capability and
458
- # implements this exact contract (equal-length/unique-name zip, `spur task deps`,
459
- # `spur feature refresh`, per-task check with a row-count assertion, and a single
460
- # recommended next command). `spur init` never scaffolds `packages/`, so seeded
461
- # projects fall through to the portable shell program below — the same
462
- # monorepo-writer/shell-fallback split the wrap-up metrics hop uses (0604 Q&A).
455
+ # (d) idea-handoff owns finalization (0824): the bundled finalizeIdeaHandoff
456
+ # capability runs from the monorepo checkout first; seeded projects (no
457
+ # packages/, no plugins/) resolve the registered idea-handoff.mjs twin under
458
+ # bare node; neither present fails closed (exit 1) instead of silently
459
+ # skipping finalization. The zip/deps/refresh/check/report contract itself is
460
+ # pinned by finalizeIdeaHandoff unit tests, not by this file.
463
461
  - kind: shell
464
462
  options:
465
463
  command: >-
466
464
  if [ -f packages/app/src/workflow/idea-handoff-cli.ts ]; then
467
465
  bun packages/app/src/workflow/idea-handoff-cli.ts;
468
- exit $?;
469
- fi;
470
- BATCH=".spur/run/$__runId-idea-task-batch.json" &&
471
- RESULT=".spur/run/$__runId-idea-batch-create-result.json" &&
472
- ORDER=".spur/run/$__runId-idea-task-order.json" &&
473
- REPORT=".spur/run/$__runId-idea-handoff.md" &&
474
- DEPMAP=".spur/run/$__runId-idea-dep-map.tsv" &&
475
- CHECKS=".spur/run/$__runId-idea-check-results.jsonl" &&
476
- rm -f "$DEPMAP" "$CHECKS" &&
477
- jq -e --slurpfile b "$BATCH" '(.wbs | length) == ($b[0] | length) and (($b[0] | map(.name) | unique | length) == ($b[0] | length))' "$RESULT" >/dev/null &&
478
- jq -r --slurpfile b "$BATCH" --slurpfile r "$RESULT" --slurpfile o "$ORDER" '
479
- ($b[0] | map(.name)) as $names | ($r[0].wbs) as $wbs |
480
- $o[0] | .[] |
481
- (.name as $n | $names | index($n)) as $i |
482
- (if $i == null or $wbs[$i] == null then "MISSING" else $wbs[$i] end) as $own |
483
- ((.depends_on_names // []) | map(. as $dep | $names | index($dep) as $d | if $d == null then "MISSING" else $wbs[$d] end) | join(" ")) as $deps |
484
- [.name, $own, $deps] | @tsv
485
- ' "$ORDER" > "$DEPMAP" &&
486
- while IFS="$(printf '\t')" read -r name own deps; do
487
- test "$own" != MISSING || exit 1;
488
- if test -n "$deps"; then
489
- for d in $deps; do test "$d" != MISSING || exit 1; done;
490
- $spurBin task deps "$own" set $deps --json >/dev/null || exit 1;
491
- fi;
492
- done < "$DEPMAP" &&
493
- $spurBin feature refresh --feature "$featureId" --json >/dev/null &&
494
- WBS_LIST=$(jq -r '.wbs | join(" ")' "$RESULT") &&
495
- for wbs in $WBS_LIST; do
496
- TMP=".spur/run/$__runId-idea-check-$wbs.tmp";
497
- if $spurBin task check "$wbs" --json > "$TMP" 2>&1; then P=true; else P=false; fi;
498
- 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;
499
- rm -f "$TMP";
500
- done &&
501
- CHECK_ROWS=$(wc -l < "$CHECKS" | tr -d ' ') &&
502
- WBS_COUNT=$(printf '%s\n' $WBS_LIST | wc -l | tr -d ' ') &&
503
- test "$CHECK_ROWS" = "$WBS_COUNT" &&
504
- READY=".spur/run/$__runId-idea-ready.json" &&
505
- (test -f "$READY" || printf '{"tasks":[]}\n' > "$READY") &&
506
- NEXT=$(jq -r --arg feature "$featureId" --slurpfile c "$CHECKS" --slurpfile k "$READY" 'if (($c | any(.[]; .pass == false)) or (($k[0].tasks // []) | length == 0) or (($k[0].tasks // []) | any(.[]; .status != "ready"))) then "/sp:dev-refineall --feature \($feature) --auto --depth ready" else "/sp:dev-runall --feature \($feature) --auto" end' "$RESULT") &&
507
- {
508
- echo "# Idea pipeline handoff report";
509
- echo;
510
- echo "Feature: $featureId";
511
- echo "Run ID: $__runId";
512
- echo;
513
- echo "## Created tasks";
514
- printf '%s\n' $WBS_LIST | sed 's/^/ - /';
515
- echo;
516
- echo "## Per-task readiness (spur task check)";
517
- echo;
518
- echo "| WBS | Outcome |";
519
- echo "|-----|---------|";
520
- while IFS= read -r line; do
521
- w=$(printf '%s' "$line" | jq -r '.wbs');
522
- o=$(printf '%s' "$line" | jq -r 'if .pass then "PASS" else "FAIL" end');
523
- printf '| %s | %s |\n' "$w" "$o";
524
- done < "$CHECKS";
525
- echo;
526
- echo "## Next command";
527
- echo;
528
- echo "$NEXT";
529
- } > "$REPORT"
530
-
466
+ elif H="$(superskill script path sp idea-handoff.mjs 2>/dev/null)" && [ -f "$H" ]; then
467
+ node "$H";
468
+ else
469
+ echo "idea handoff failed closed — idea-handoff script not found — run 'superskill install sp'" >&2;
470
+ exit 1;
471
+ fi
531
472
  - id: handoff
532
473
  description: >
533
474
  Terminal — idea pipeline complete. Ordering applied, roster refreshed, and
@@ -627,6 +568,8 @@ transitions:
627
568
  - from: ac-generate
628
569
  to: system-design
629
570
  description: "profile=auto, check passed, design route — run system design."
571
+ # (warn) 4 test segments: profile gate + captured status + route signal jointly own the
572
+ # route; guards never re-run the CLI (0769), so there is nothing smaller to extract.
630
573
  guard:
631
574
  kind: shell
632
575
  options:
@@ -635,6 +578,8 @@ transitions:
635
578
  - from: ac-generate
636
579
  to: decompose
637
580
  description: "profile=auto, check passed, skip-design route — go directly to decompose."
581
+ # (warn) 5 test segments: same as auto-skip 1 plus the OR'd design=auto/needs_design=false
582
+ # pair that keeps one captured signal file authoritative for both routes (0769).
638
583
  guard:
639
584
  kind: shell
640
585
  options:
@@ -643,6 +588,8 @@ transitions:
643
588
  - from: ac-generate
644
589
  to: ac-generate
645
590
  description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
591
+ # (warn) 4 test segments: profile gate + captured status + captured retry count; the
592
+ # retry loop is the smallest honest formulation of cap<3 routing (0769).
646
593
  guard:
647
594
  kind: shell
648
595
  options:
@@ -651,6 +598,8 @@ transitions:
651
598
  - from: ac-generate
652
599
  to: failed
653
600
  description: "profile=auto, check failed after 3 retries — escalate to failed."
601
+ # (warn) 4 test segments: mirror of the retry guard with cap>=3; keeping escalation and
602
+ # retry as one test-chain pair makes the cap boundary auditable in the diff (0769).
654
603
  guard:
655
604
  kind: shell
656
605
  options:
@@ -667,6 +616,8 @@ transitions:
667
616
  - from: feature-check
668
617
  to: system-design
669
618
  description: Feature check passed, design route — run system design.
619
+ # (warn) 4 test segments: HITL answer + captured status + design route; same shape as the
620
+ # ac-generate auto-skips, reading the same captured signal files (0769).
670
621
  guard:
671
622
  kind: shell
672
623
  options:
@@ -674,6 +625,8 @@ transitions:
674
625
  - from: feature-check
675
626
  to: decompose
676
627
  description: Feature check passed, skip-design route — go directly to decompose.
628
+ # (warn) 5 test segments: same as the feature-check design route plus the OR'd
629
+ # design=auto/needs_design=false pair (0769).
677
630
  guard:
678
631
  kind: shell
679
632
  options:
@@ -681,6 +634,8 @@ transitions:
681
634
  - from: feature-check
682
635
  to: ac-generate
683
636
  description: "Feature check failed — revise AC (retry cap: 3)."
637
+ # (warn) 4 test segments: HITL reject/failed-status OR-pair + captured retry count; the
638
+ # interactive retry mirror of the ac-generate cap<3 guard (0769).
684
639
  guard:
685
640
  kind: shell
686
641
  options:
@@ -688,6 +643,7 @@ transitions:
688
643
  - from: feature-check
689
644
  to: failed
690
645
  description: Feature check failed after 3 retries — escalate to failed.
646
+ # (warn) 4 test segments: mirror of the revise guard with cap>=3 (0769).
691
647
  guard:
692
648
  kind: shell
693
649
  options: