@gobing-ai/spur 0.3.80 → 0.3.82

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 +31 -18
  3. package/config/config.global.yaml +13 -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/docs/99_PROJECT_CONSTITUTION.md +75 -13
  33. package/config/transition-shims.json +0 -7
  34. package/config/workflows/basic.yaml +4 -0
  35. package/config/workflows/docs-pipeline.yaml +13 -14
  36. package/config/workflows/feature-dev.yaml +20 -65
  37. package/config/workflows/history-anatomy.yaml +22 -1
  38. package/config/workflows/idea-pipeline.yaml +53 -97
  39. package/config/workflows/pr-review.yaml +21 -33
  40. package/config/workflows/task-pipeline.yaml +87 -330
  41. package/config/workflows/wayfinder-resolution.yaml +12 -26
  42. package/config/workflows/wrapup-pipeline.yaml +48 -189
  43. package/package.json +9 -9
  44. package/plugins/sp/README.md +12 -3
  45. package/plugins/sp/agents/expert-spur.md +41 -19
  46. package/plugins/sp/lib/idea-handoff.generated.d.mts +17 -0
  47. package/plugins/sp/lib/idea-handoff.generated.mjs +1301 -0
  48. package/plugins/sp/plugin.json +1 -1
  49. package/plugins/sp/scripts/feature-dev-precheck.mjs +146 -0
  50. package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
  51. package/plugins/sp/scripts/idea-handoff.mjs +27 -0
  52. package/plugins/sp/scripts/idea-handoff.ts +44 -0
  53. package/plugins/sp/scripts/inline-run-setup.ts +69 -1
  54. package/plugins/sp/scripts/quality-gate.mjs +216 -0
  55. package/plugins/sp/scripts/quality-gate.ts +287 -0
  56. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -2
  57. package/plugins/sp/scripts/task-size-precheck.ts +44 -16
  58. package/plugins/sp/scripts/verify-answer-lint.ts +17 -1
  59. package/plugins/sp/scripts/workflow-step-profile.mjs +319 -0
  60. package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
  61. package/plugins/sp/scripts/wrapup-steps.mjs +350 -0
  62. package/plugins/sp/scripts/wrapup-steps.ts +466 -0
  63. package/plugins/sp/skills/code-review/references/review-lenses.md +3 -0
  64. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  65. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +29 -0
  66. package/plugins/sp/skills/spur-cli/SKILL.md +4 -8
  67. package/plugins/sp/skills/spur-cli/references/agent.md +44 -69
  68. package/plugins/sp/skills/spur-cli/references/message.md +30 -3
  69. package/plugins/sp/skills/spur-cli/references/projects.md +25 -1
  70. package/plugins/sp/skills/spur-cli/references/self.md +6 -5
  71. package/plugins/sp/skills/spur-cli/references/serve.md +8 -7
  72. package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
  73. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +6 -3
  74. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +57 -18
  75. package/plugins/sp/skills/spur-composer/SKILL.md +145 -0
  76. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +10 -0
  77. package/plugins/sp/skills/spur-dev/references/execution-batch.md +1 -1
  78. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +4 -6
  79. package/plugins/sp/skills/spur-dev/references/glossary.md +1 -1
  80. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +11 -1
  81. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +24 -0
  82. package/plugins/sp/skills/spur-doctor/SKILL.md +138 -0
  83. package/plugins/sp/skills/taste-refactoring-api/README.md +43 -0
  84. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +334 -0
  85. package/plugins/sp/skills/taste-refactoring-api/checklists/daily-api-review.md +71 -0
  86. package/plugins/sp/skills/taste-refactoring-api/examples/refactor-example.md +72 -0
  87. package/plugins/sp/skills/taste-refactoring-api/examples/review-template.md +93 -0
  88. package/plugins/sp/skills/taste-refactoring-api/references/api-refactoring-playbook.md +253 -0
  89. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +79 -0
  90. package/plugins/sp/skills/taste-refactoring-api/references/research-basis.md +58 -0
  91. package/plugins/sp/skills/taste-refactoring-architect/README.md +26 -0
  92. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +471 -0
  93. package/plugins/sp/skills/taste-refactoring-architect/checklists/daily-architecture-review.md +48 -0
  94. package/plugins/sp/skills/taste-refactoring-architect/examples/refactor-example.md +55 -0
  95. package/plugins/sp/skills/taste-refactoring-architect/examples/review-template.md +51 -0
  96. package/plugins/sp/skills/taste-refactoring-architect/references/architecture-refactoring-playbook.md +173 -0
  97. package/plugins/sp/skills/taste-refactoring-architect/references/research-basis.md +28 -0
  98. package/plugins/sp/skills/taste-refactoring-tests/README.md +28 -0
  99. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +482 -0
  100. package/plugins/sp/skills/taste-refactoring-tests/checklists/daily-test-review.md +39 -0
  101. package/plugins/sp/skills/taste-refactoring-tests/examples/refactor-example.md +85 -0
  102. package/plugins/sp/skills/taste-refactoring-tests/examples/review-template.md +59 -0
  103. package/plugins/sp/skills/taste-refactoring-tests/references/research-basis.md +47 -0
  104. package/plugins/sp/skills/taste-refactoring-tests/references/test-refactoring-playbook.md +222 -0
  105. package/plugins/sp/skills/taste-refactoring-ui/README.md +12 -0
  106. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +290 -0
  107. package/plugins/sp/skills/taste-refactoring-ui/checklists/daily-ui-review.md +72 -0
  108. package/plugins/sp/skills/taste-refactoring-ui/examples/review-template.md +51 -0
  109. package/plugins/sp/skills/taste-refactoring-ui/references/refactoring-ui-playbook.md +170 -0
  110. package/plugins/sp/skills/wayfinder/SKILL.md +2 -2
  111. package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +30 -0
  112. package/schemas/spur-config.schema.json +105 -85
  113. package/spur.js +44616 -43320
  114. package/web/_astro/BoardApp.D-WlxiN2.js +1 -0
  115. package/web/_astro/{BoardApp.CHQ1lycZ.js → BoardApp.D8bM9pKL.js} +97 -95
  116. package/web/_astro/{TaskDetail.GKfQJ60c.js → TaskDetail.BPRqgVUE.js} +1 -1
  117. package/web/_astro/{arc.DWEtA3Tx.js → arc.BPrPES3z.js} +1 -1
  118. package/web/_astro/{architectureDiagram-3BPJPVTR.DB42oWmP.js → architectureDiagram-3BPJPVTR.qX_7q02P.js} +1 -1
  119. package/web/_astro/{blockDiagram-GPEHLZMM.rhv-zNQV.js → blockDiagram-GPEHLZMM.CUZfj5V7.js} +1 -1
  120. package/web/_astro/{c4Diagram-AAUBKEIU.Ci4-4VvY.js → c4Diagram-AAUBKEIU.CTaOr8hH.js} +1 -1
  121. package/web/_astro/channel.DGZaFHZx.js +1 -0
  122. package/web/_astro/{chunk-2J33WTMH.Cc9veUgf.js → chunk-2J33WTMH.Dt-9wf3h.js} +1 -1
  123. package/web/_astro/{chunk-4BX2VUAB.Bec9c4eI.js → chunk-4BX2VUAB.CTC2sdoN.js} +1 -1
  124. package/web/_astro/{chunk-55IACEB6.DoV8S1iB.js → chunk-55IACEB6.DQcxt2_g.js} +1 -1
  125. package/web/_astro/{chunk-727SXJPM.DwR-Qlyj.js → chunk-727SXJPM.DXFPSn-a.js} +1 -1
  126. package/web/_astro/{chunk-AQP2D5EJ.ND_a81WY.js → chunk-AQP2D5EJ.BCx3U4bT.js} +1 -1
  127. package/web/_astro/{chunk-FMBD7UC4.Wv_jwG48.js → chunk-FMBD7UC4.DL2tJkdO.js} +1 -1
  128. package/web/_astro/{chunk-ND2GUHAM.CXKXCMmp.js → chunk-ND2GUHAM.DZyflMro.js} +1 -1
  129. package/web/_astro/{chunk-QZHKN3VN.nkaoNYQq.js → chunk-QZHKN3VN.CUI2mT09.js} +1 -1
  130. package/web/_astro/{classDiagram-4FO5ZUOK.cMQcVlQu.js → classDiagram-4FO5ZUOK.g4rX4Fr1.js} +1 -1
  131. package/web/_astro/{classDiagram-v2-Q7XG4LA2.cMQcVlQu.js → classDiagram-v2-Q7XG4LA2.g4rX4Fr1.js} +1 -1
  132. package/web/_astro/{cose-bilkent-S5V4N54A.OaDJ7Mr2.js → cose-bilkent-S5V4N54A.CzWJLqp0.js} +1 -1
  133. package/web/_astro/{cynefin-OW5HDTMX.Chi8IphF.js → cynefin-OW5HDTMX.WgsvQeCR.js} +1 -1
  134. package/web/_astro/{cytoscape.esm.DzSz-X2X.js → cytoscape.esm.BB4DxJjf.js} +1 -1
  135. package/web/_astro/{dagre-BM42HDAG.CzK2t_Fp.js → dagre-BM42HDAG.Dzv6ngql.js} +1 -1
  136. package/web/_astro/{diagram-2AECGRRQ.DRvxlVS7.js → diagram-2AECGRRQ.CpJ4a9rU.js} +1 -1
  137. package/web/_astro/{diagram-5GNKFQAL.CnYvNdwA.js → diagram-5GNKFQAL.CzPlF_dq.js} +1 -1
  138. package/web/_astro/{diagram-KO2AKTUF.CpLpMw5R.js → diagram-KO2AKTUF.TAkZNTcQ.js} +1 -1
  139. package/web/_astro/{diagram-LMA3HP47.JTb78qUA.js → diagram-LMA3HP47.uCjoKSag.js} +1 -1
  140. package/web/_astro/{diagram-OG6HWLK6.Bk-1jDIb.js → diagram-OG6HWLK6.eMplIjoK.js} +1 -1
  141. package/web/_astro/{erDiagram-TEJ5UH35.D8hN9GZq.js → erDiagram-TEJ5UH35.Bf7zoXGz.js} +1 -1
  142. package/web/_astro/{flowDiagram-I6XJVG4X.-6zQr6m5.js → flowDiagram-I6XJVG4X.B_bHj3gN.js} +1 -1
  143. package/web/_astro/{ganttDiagram-6RSMTGT7.DboLQ9ca.js → ganttDiagram-6RSMTGT7.BasrHRMj.js} +1 -1
  144. package/web/_astro/{gitGraphDiagram-PVQCEYII.4tYvJKGR.js → gitGraphDiagram-PVQCEYII.C6iphq1x.js} +1 -1
  145. package/web/_astro/index.DayyIngm.css +1 -0
  146. package/web/_astro/{infoDiagram-5YYISTIA.Bd9rXpsB.js → infoDiagram-5YYISTIA.HXmDMhW4.js} +1 -1
  147. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CvMoaf67.js → ishikawaDiagram-YF4QCWOH.BSmW8NiU.js} +1 -1
  148. package/web/_astro/{journeyDiagram-JHISSGLW.Ccy1CA7y.js → journeyDiagram-JHISSGLW.DEQow5fo.js} +1 -1
  149. package/web/_astro/{kanban-definition-UN3LZRKU.0MaMqHNS.js → kanban-definition-UN3LZRKU.IVm9cTdc.js} +1 -1
  150. package/web/_astro/{linear.CHXgcIbN.js → linear.CrsM73_9.js} +1 -1
  151. package/web/_astro/{mermaid.core.Ca-kcelG.js → mermaid.core.CfBeDJls.js} +6 -6
  152. package/web/_astro/{mindmap-definition-RKZ34NQL.BUIDlHa0.js → mindmap-definition-RKZ34NQL.C3j60Y-0.js} +1 -1
  153. package/web/_astro/ordinal.BYWQX77i.js +1 -0
  154. package/web/_astro/{pieDiagram-4H26LBE5.2dX3CU1s.js → pieDiagram-4H26LBE5.B-aCMeEA.js} +1 -1
  155. package/web/_astro/{quadrantDiagram-W4KKPZXB.B3LBlRiv.js → quadrantDiagram-W4KKPZXB.Cib965yq.js} +1 -1
  156. package/web/_astro/{requirementDiagram-4Y6WPE33.X12I2uNx.js → requirementDiagram-4Y6WPE33.D61cS4O-.js} +1 -1
  157. package/web/_astro/{sankeyDiagram-5OEKKPKP.BXohIHqx.js → sankeyDiagram-5OEKKPKP.GKF2qVPy.js} +1 -1
  158. package/web/_astro/{sequenceDiagram-3UESZ5HK.C37ZIUzg.js → sequenceDiagram-3UESZ5HK.DZnq8F2h.js} +1 -1
  159. package/web/_astro/{stateDiagram-AJRCARHV.BRgz317z.js → stateDiagram-AJRCARHV.DXUFmdgM.js} +1 -1
  160. package/web/_astro/{stateDiagram-v2-BHNVJYJU.7VYSXN9-.js → stateDiagram-v2-BHNVJYJU.BtHmhLEz.js} +1 -1
  161. package/web/_astro/{timeline-definition-PNZ67QCA.BVNz_HiN.js → timeline-definition-PNZ67QCA.Cy-WW2ln.js} +1 -1
  162. package/web/_astro/{vennDiagram-CIIHVFJN.CHVDkPX4.js → vennDiagram-CIIHVFJN.SLp5b9KI.js} +1 -1
  163. package/web/_astro/{wardleyDiagram-YWT4CUSO.EQQ_qT9v.js → wardleyDiagram-YWT4CUSO.Bww45mWV.js} +1 -1
  164. package/web/_astro/{xychartDiagram-2RQKCTM6.DrAT9WoP.js → xychartDiagram-2RQKCTM6.DR4swI6a.js} +1 -1
  165. package/web/apple-touch-icon.png +0 -0
  166. package/web/favicon.ico +0 -0
  167. package/web/favicon.svg +17 -4
  168. package/web/icon-192.png +0 -0
  169. package/web/icon-512.png +0 -0
  170. package/web/index.html +2 -2
  171. package/web/site.webmanifest +31 -0
  172. package/web/spur_logo.svg +1 -0
  173. package/plugins/sp/skills/spur-cli/references/team.md +0 -145
  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
@@ -33,22 +33,15 @@ include:
33
33
  - "packages/**/tests/**/*.test.ts"
34
34
  - "packages/**/tests/**/*.test.tsx"
35
35
  exclude:
36
- - "**/node_modules/**"
37
- - "**/dist/**"
38
- # Shared full-surface mock helpers are the CORRECT pattern — they ensure every
39
- # test file registers the same compatible surface, neutralizing "last mock wins."
40
- - "apps/web/tests/test-helpers/rpc-client-mock.ts"
41
- # These files use beforeEach restoreMock() atop the shared baseline — the mock.module
42
- # call is in a runtime function, not at module scope, and always registers the full surface.
36
+ # These three files mock.module() first-party modules and keep the mock inside a
37
+ # runtime function guarded by beforeEach restoreMock() atop the shared full-surface
38
+ # baseline (apps/web/tests/test-helpers/rpc-client-mock.ts, itself unflagged: it
39
+ # only documents the surface in comments). Remaining mock.module() hits in the
40
+ # test suite were verified stale or third-party-only and have been de-listed.
43
41
  - "apps/web/tests/modules/task-kanban/board.test.tsx"
44
- - "apps/web/tests/modules/task-kanban/components.test.tsx"
45
42
  - "apps/web/tests/modules/task-kanban/new-task-panel.test.tsx"
46
- - "apps/web/tests/modules/features/components.test.tsx"
47
43
  - "apps/web/tests/modules/task-kanban/task-detail.test.tsx"
48
- - "apps/web/tests/modules/task-kanban/index.test.tsx"
49
- # index.test.tsx no longer mocks useTasks/useTaskParams/TaskDetail; useTasks.test.ts
50
- # uses custom listFn (isolated store), not mock.module for first-party modules.
51
- - "apps/web/tests/modules/task-kanban/useTasks.test.ts"
44
+ - "apps/web/tests/modules/projects/WorkView.test.tsx"
52
45
  rules:
53
46
  - id: no-leaky-mock-module
54
47
  description: >
@@ -35,8 +35,6 @@ include:
35
35
  - "packages/**/src/**/*.ts"
36
36
  - "packages/**/src/**/*.tsx"
37
37
  exclude:
38
- - "**/node_modules/**"
39
- - "**/dist/**"
40
38
  - "**/tests/**"
41
39
  - "**/*.test.ts"
42
40
  - "**/*.test.tsx"
@@ -29,9 +29,6 @@ include:
29
29
  - "packages/**/tests/**/*.test.tsx"
30
30
  - "plugins/**/tests/**/*.test.ts"
31
31
  - "plugins/**/tests/**/*.test.tsx"
32
- exclude:
33
- - "**/node_modules/**"
34
- - "**/dist/**"
35
32
 
36
33
  rules:
37
34
  - id: no-syscall-emulation-in-boundary-mock
@@ -34,9 +34,6 @@ include:
34
34
  - "apps/**/tests/**/*.test.tsx"
35
35
  - "packages/**/tests/**/*.test.ts"
36
36
  - "packages/**/tests/**/*.test.tsx"
37
- exclude:
38
- - "**/node_modules/**"
39
- - "**/dist/**"
40
37
  # All known side-effect modules are resolved. When a new one is discovered,
41
38
  # exclude files that already mock the dependency before importing it.
42
39
 
@@ -7,9 +7,6 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
7
7
  include:
8
8
  - "apps/**/src/**/*.ts"
9
9
  - "packages/**/src/**/*.ts"
10
- exclude:
11
- - "**/node_modules/**"
12
- - "**/dist/**"
13
10
 
14
11
  rules:
15
12
  - id: no-console-output
@@ -13,9 +13,6 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
13
13
  include:
14
14
  - "apps/web/tests/**/*.test.ts"
15
15
  - "apps/web/tests/**/*.test.tsx"
16
- exclude:
17
- - "**/node_modules/**"
18
- - "**/dist/**"
19
16
 
20
17
  rules:
21
18
  - id: prefer-accessible-role-for-button-queries
@@ -27,8 +27,6 @@ rules:
27
27
  - "apps/web/src/**/*.tsx"
28
28
  exclude:
29
29
  - "apps/web/src/ui.ts"
30
- - "**/tests/**"
31
- - "**/node_modules/**"
32
30
 
33
31
  - id: no-daisyui-class-leak
34
32
  description: >
@@ -48,7 +46,5 @@ rules:
48
46
  include:
49
47
  - "apps/web/src/**/*.tsx"
50
48
  exclude:
49
+ # Sanctioned class-authoring site (Rule B seam).
51
50
  - "apps/web/src/components/ui/**"
52
- - "apps/web/src/ui.ts"
53
- - "**/tests/**"
54
- - "**/node_modules/**"
@@ -3,7 +3,7 @@ name: Project Constitution
3
3
  doc: 99_PROJECT_CONSTITUTION
4
4
  owns: PROCESS — how the key files are maintained
5
5
  authority: authoritative-on-process
6
- version: 1.6.0
6
+ version: 1.7.0
7
7
  created_at: {{init-date}}
8
8
  updated_at: {{init-date}}
9
9
  edit_rules: 99 §6.8
@@ -35,10 +35,12 @@ Within project documentation, authority is scoped to the question:
35
35
  | Visual and interaction design | Root `DESIGN.md`, when present |
36
36
  | Delivery sequence, current mechanisms, non-UI contracts, feature state | `02`–`05`, derived within their respective responsibilities |
37
37
 
38
- Lower numbers win content conflicts within the same subject; `99` governs maintenance across
39
- all key files. Neither an ADR nor a task can silently change the constitution. An ADR is not
40
- a universal override for unrelated scope or UI facts. Fix the authoritative statement first,
41
- then affected projections; never average conflicting statements. Keep document numbers stable.
38
+ The designated subject owner governs; document numbers do not confer authority over another
39
+ owner's subject. `99` governs maintenance across all key files. Neither an ADR nor a task can
40
+ silently change the constitution. Identify the governing statement first; correct it only if
41
+ defective, otherwise repair the conflicting projection. If authoritative statements conflict and
42
+ existing authorization does not resolve them, report the conflict rather than invent a decision.
43
+ Never average conflicting statements. Keep document numbers stable.
42
44
 
43
45
  ## 3. Shared tools
44
46
 
@@ -118,7 +120,7 @@ it does not require touching every key file for each feature or task.
118
120
  | T1 | New architectural choice passing §6.1, or reversal | Record `00` before divergence; update affected `03`/`04`; `01` only if scope changes |
119
121
  | T2 | Implementation would contradict an existing ADR | Record the decision amendment or superseding ADR before implementing the contradiction |
120
122
  | T3 | CLI/API/config/schema/DTO or non-UI behavior changes | Update the owning `04` satellite; index and `AGENTS.md` only if their own facts change |
121
- | T4 | Feature lifecycle or acceptance changes | Update through the feature tool and refresh its generated projection; `01` only for scope changes |
123
+ | T4 | Feature hierarchy, scope, lifecycle or acceptance changes | Apply §6.6 authorization; update through the feature tool and refresh its generated projection; `01` only for product scope changes |
122
124
  | T5 | Phase goal, order, dependency, or exit changes | Update `02`; task completion alone does not trigger a roadmap entry |
123
125
  | T6 | Product scope added, removed, or deferred | Update `01`; `02` if sequencing changes |
124
126
  | T7 | Authorized change to document responsibilities, authority, or maintenance rules | Update `99`, affected entry/routing instructions, and in-scope init templates |
@@ -141,18 +143,23 @@ Do not append to a key file merely to prove work happened.
141
143
 
142
144
  ### 6.1 `docs/00_ADR.md`
143
145
 
144
- Admit a decision only if it selects among meaningful alternatives and establishes or changes
146
+ Every entry and amendment MUST select among meaningful alternatives and establish or change
145
147
  a lasting architecture boundary or invariant across features/modules: dependency direction,
146
148
  persistence ownership, trust model, protocol, runtime or shared execution model. Record the
147
149
  choice, context/reason, material tradeoff/consequence, status/date, and a detail pointer.
148
150
 
151
+ Entries and amendments MUST remain precise and concise. State the decision and its rationale
152
+ once; link detailed contracts and implementation guidance to their owners. Naming a feature,
153
+ task, technology or technical concern alone does not establish architectural significance.
154
+
149
155
  Single-feature design, public-surface consent, task progress, test results and implementation
150
156
  receipts go to their design/task records. Bug fixes that restore an existing contract need no ADR.
151
157
  Doc-map and maintenance changes belong in `99`, not an ADR certifying a constitution edit.
152
158
 
153
159
  - One architectural decision per entry. New choices append; reversals name the superseded ADR.
154
160
  - Never renumber, reuse or delete an ADR ID. Preserve original titles/anchors and dates.
155
- - Corrections to the decision use dated amendments; amendments record a decision delta only.
161
+ - Corrections to the decision use dated amendments stating only what changed and why. Amendments
162
+ must pass the same admission test; editorial corrections update wording in place without an addendum.
156
163
  - Editorial condensation may remove repetition and misplaced detail while preserving the choice,
157
164
  rationale, material alternatives, consequences and amendment history. It must not silently
158
165
  reverse a decision or present a historical choice as current.
@@ -195,6 +202,26 @@ parallel hand-written status table. Requirements, acceptance and decomposition l
195
202
  records; execution evidence lives in task records. Verify status and acceptance against evidence
196
203
  before relying on them. Never raw-edit satellites or generated regions.
197
204
 
205
+ The project is the logical tree root; its direct children are top-level features, without requiring
206
+ an extra persisted project node. Each top-level feature MUST represent a durable project module,
207
+ product capability or project-wide concern. Its scope and top-level placement MUST be supported
208
+ by an explicit requirement or approval from the operator or designated product owner.
209
+
210
+ Record the supporting requirement or approval reference in the feature record through its tool.
211
+ Existing explicit authorization is sufficient. Implementation, silence, task approval or an agent's
212
+ recommendation does not establish approval for top-level placement.
213
+
214
+ Prefer an existing owner. Children decompose their parent's approved scope; sibling boundaries
215
+ must be clear and avoid duplicate ownership. A task, delivery wave, incident, technical detail or
216
+ child-count limit alone does not justify a new top-level feature.
217
+
218
+ Creating or promoting a top-level feature, materially redefining its scope, or merging or removing
219
+ top-level boundaries requires explicit authorization covering that change. Routine decomposition
220
+ within approved scope needs no repeated approval.
221
+
222
+ For legacy roots without recorded provenance, report the gap without inventing approval or
223
+ automatically restructuring the tree. Preserve history and references during authorized migration.
224
+
198
225
  ### 6.7 `AGENTS.md`
199
226
 
200
227
  Keep essential orientation, commands, boundaries and document routing. Verify facts from the
@@ -213,20 +240,54 @@ is not a reason to edit this file or bump its metadata. Keep implementation-spec
213
240
  and tool bindings in their owners. Do not automatically promote lessons into constitutional rules.
214
241
  Update authorized templates/copies; do not mutate unrelated repositories without authorization.
215
242
 
243
+ ### 6.9 `DESIGN.md` (root, when present)
244
+
245
+ Keep shared visual and interaction rules, including accessibility and responsive behavior. Link
246
+ detailed UI references; distinguish current conventions from proposed changes. Preserve required
247
+ accessibility behavior when simplifying. Non-UI contracts belong in `04`; delivery status stays
248
+ in feature/task records. Update this owner when shared UI rules change, not for every screen edit.
249
+
216
250
  ## 7. Drift control
217
251
 
218
- Audit the affected owners, using source/help, manifests, Git diffs, generated indexes and links:
252
+ ### 7.1 Detection
253
+
254
+ During relevant document edits and task wrap-up, check affected owners and their references using
255
+ source/help, manifests, Git diffs, generated indexes and links. A comprehensive audit is a separate
256
+ scope; routine work does not require a repository-wide sweep.
219
257
 
220
258
  - Responsibility: content belongs to the file's §4.1 row; no competing ledger or rule owner.
221
259
  - Reality: current contracts/mechanisms match source; proposed/historical content is labeled.
222
260
  - Scope/status: `01` covers capabilities; phase/feature claims have current evidence.
261
+ - Feature structure: top-level boundaries have §6.6 provenance; decomposition stays within approved scope.
223
262
  - References: IDs, heading anchors, index pointers and aliases still resolve.
224
263
  - Synchronization: apply §5 to changed facts; unchanged owners need no edit.
225
264
  - Metadata/templates: contracts and dates match actual edits; portable guidance agrees.
226
265
 
227
- Repair authority first, then affected detail/index/entry files. Record findings and verification
228
- in the task or a dated report, including unverified claims. Do not claim a repository-wide audit
229
- from focused checks. An ADR content reversal follows §6.1; editorial cleanup preserves history.
266
+ ### 7.2 Automatic repair
267
+
268
+ Automatically repair confirmed documentation defects when authoritative evidence supports the
269
+ correction, approved meaning is preserved, and the repair falls within authorized work. Examples
270
+ include broken references with a verified destination, duplicate prose, misplaced detail, stale
271
+ factual descriptions, and generated projections refreshed through their owning tools.
272
+
273
+ Identify the governing owner first. Correct it only if its statement is defective; otherwise repair
274
+ the conflicting projection. Observed implementation is evidence of current behavior, not authorization
275
+ to change accepted scope, decisions or contracts.
276
+
277
+ Auto-healing MUST NOT invent requirements or approvals, reverse architectural decisions, weaken
278
+ acceptance criteria or gates, fabricate completion evidence, or amend this constitution outside §6.8
279
+ authorization. Apply an already-authorized change without requesting approval again; otherwise report
280
+ the conflict and proposed resolution. An authorized ADR reversal follows §6.1.
281
+
282
+ Preserve decision history, stable references and unrelated edits. Regenerate tool-owned content
283
+ through its tool. Synchronize affected detail/index/entry files and in-scope templates under §5.
284
+
285
+ ### 7.3 Verification and unresolved findings
286
+
287
+ After repair, verify affected contracts, links, metadata and template parity with the applicable
288
+ checks. Record findings, verification and unresolved or unverified claims in the task or a dated
289
+ report. Do not claim a repository-wide audit from focused checks. If evidence remains ambiguous
290
+ or a repair fails verification, stop that repair and report the cause; do not declare it healed.
230
291
 
231
292
  ## 8. Lessons learned per file
232
293
 
@@ -243,6 +304,7 @@ this routing rule, not an instruction to append lessons here.
243
304
  Seed `00`–`05`, this constitution and `AGENTS.md` through the project initializer. Fill project
244
305
  facts from actual requirements and manifests; leave unknowns explicit. Do not pre-accept an
245
306
  ADR, invent a completed phase, or create fictitious feature/status rows in a fresh project.
246
- Use the feature tool for its index. Keep `DESIGN.md` optional; author it when UI work needs a
307
+ Start feature decomposition from approved requirements and apply §6.6 before creating top-level
308
+ features. Use the feature tool for its index. Keep `DESIGN.md` optional; author it when UI work needs a
247
309
  shared design language. Preserve existing customized docs and supported entry symlinks.
248
310
  Read `AGENTS.md` first, then the owners relevant to the work.
@@ -15,13 +15,6 @@
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",
@@ -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: >-