@gobing-ai/spur 0.3.80 → 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 (158) 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/transition-shims.json +7 -7
  33. package/config/workflows/basic.yaml +4 -0
  34. package/config/workflows/docs-pipeline.yaml +13 -14
  35. package/config/workflows/feature-dev.yaml +20 -65
  36. package/config/workflows/history-anatomy.yaml +22 -1
  37. package/config/workflows/idea-pipeline.yaml +53 -97
  38. package/config/workflows/pr-review.yaml +21 -33
  39. package/config/workflows/task-pipeline.yaml +87 -330
  40. package/config/workflows/wayfinder-resolution.yaml +12 -26
  41. package/config/workflows/wrapup-pipeline.yaml +48 -189
  42. package/package.json +9 -9
  43. package/plugins/sp/README.md +10 -1
  44. package/plugins/sp/agents/expert-spur.md +41 -19
  45. package/plugins/sp/lib/idea-handoff.generated.d.mts +17 -0
  46. package/plugins/sp/lib/idea-handoff.generated.mjs +1301 -0
  47. package/plugins/sp/plugin.json +1 -1
  48. package/plugins/sp/scripts/feature-dev-precheck.mjs +146 -0
  49. package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
  50. package/plugins/sp/scripts/idea-handoff.mjs +27 -0
  51. package/plugins/sp/scripts/idea-handoff.ts +44 -0
  52. package/plugins/sp/scripts/quality-gate.mjs +165 -0
  53. package/plugins/sp/scripts/quality-gate.ts +217 -0
  54. package/plugins/sp/scripts/workflow-step-profile.mjs +319 -0
  55. package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
  56. package/plugins/sp/scripts/wrapup-steps.mjs +350 -0
  57. package/plugins/sp/scripts/wrapup-steps.ts +466 -0
  58. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  59. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +29 -0
  60. package/plugins/sp/skills/spur-cli/references/agent.md +56 -14
  61. package/plugins/sp/skills/spur-cli/references/message.md +30 -3
  62. package/plugins/sp/skills/spur-cli/references/projects.md +45 -1
  63. package/plugins/sp/skills/spur-cli/references/self.md +5 -4
  64. package/plugins/sp/skills/spur-cli/references/serve.md +5 -4
  65. package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
  66. package/plugins/sp/skills/spur-cli/references/team.md +21 -1
  67. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +6 -3
  68. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +57 -18
  69. package/plugins/sp/skills/spur-composer/SKILL.md +145 -0
  70. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +24 -0
  71. package/plugins/sp/skills/spur-doctor/SKILL.md +138 -0
  72. package/plugins/sp/skills/taste-refactoring-api/README.md +43 -0
  73. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +334 -0
  74. package/plugins/sp/skills/taste-refactoring-api/checklists/daily-api-review.md +71 -0
  75. package/plugins/sp/skills/taste-refactoring-api/examples/refactor-example.md +72 -0
  76. package/plugins/sp/skills/taste-refactoring-api/examples/review-template.md +93 -0
  77. package/plugins/sp/skills/taste-refactoring-api/references/api-refactoring-playbook.md +253 -0
  78. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +79 -0
  79. package/plugins/sp/skills/taste-refactoring-api/references/research-basis.md +58 -0
  80. package/plugins/sp/skills/taste-refactoring-architect/README.md +26 -0
  81. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +471 -0
  82. package/plugins/sp/skills/taste-refactoring-architect/checklists/daily-architecture-review.md +48 -0
  83. package/plugins/sp/skills/taste-refactoring-architect/examples/refactor-example.md +55 -0
  84. package/plugins/sp/skills/taste-refactoring-architect/examples/review-template.md +51 -0
  85. package/plugins/sp/skills/taste-refactoring-architect/references/architecture-refactoring-playbook.md +173 -0
  86. package/plugins/sp/skills/taste-refactoring-architect/references/research-basis.md +28 -0
  87. package/plugins/sp/skills/taste-refactoring-tests/README.md +28 -0
  88. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +482 -0
  89. package/plugins/sp/skills/taste-refactoring-tests/checklists/daily-test-review.md +39 -0
  90. package/plugins/sp/skills/taste-refactoring-tests/examples/refactor-example.md +85 -0
  91. package/plugins/sp/skills/taste-refactoring-tests/examples/review-template.md +59 -0
  92. package/plugins/sp/skills/taste-refactoring-tests/references/research-basis.md +47 -0
  93. package/plugins/sp/skills/taste-refactoring-tests/references/test-refactoring-playbook.md +222 -0
  94. package/plugins/sp/skills/taste-refactoring-ui/README.md +12 -0
  95. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +290 -0
  96. package/plugins/sp/skills/taste-refactoring-ui/checklists/daily-ui-review.md +72 -0
  97. package/plugins/sp/skills/taste-refactoring-ui/examples/review-template.md +51 -0
  98. package/plugins/sp/skills/taste-refactoring-ui/references/refactoring-ui-playbook.md +170 -0
  99. package/plugins/sp/skills/wayfinder/SKILL.md +2 -2
  100. package/plugins/sp/skills/wayfinder/references/pipeline-resolution.md +30 -0
  101. package/schemas/spur-config.schema.json +49 -0
  102. package/spur.js +46754 -44121
  103. package/web/_astro/{BoardApp.CHQ1lycZ.js → BoardApp.B1U26g3I.js} +97 -95
  104. package/web/_astro/BoardApp.Csgyg-lS.js +1 -0
  105. package/web/_astro/{TaskDetail.GKfQJ60c.js → TaskDetail.DwPqpq7v.js} +1 -1
  106. package/web/_astro/{arc.DWEtA3Tx.js → arc.CweZEjN2.js} +1 -1
  107. package/web/_astro/{architectureDiagram-3BPJPVTR.DB42oWmP.js → architectureDiagram-3BPJPVTR.D89pbDuv.js} +1 -1
  108. package/web/_astro/{blockDiagram-GPEHLZMM.rhv-zNQV.js → blockDiagram-GPEHLZMM.BOuTeEpX.js} +1 -1
  109. package/web/_astro/{c4Diagram-AAUBKEIU.Ci4-4VvY.js → c4Diagram-AAUBKEIU.CASbkWZF.js} +1 -1
  110. package/web/_astro/channel.Cx6sXxhq.js +1 -0
  111. package/web/_astro/{chunk-2J33WTMH.Cc9veUgf.js → chunk-2J33WTMH.BKQYtOvY.js} +1 -1
  112. package/web/_astro/{chunk-4BX2VUAB.Bec9c4eI.js → chunk-4BX2VUAB.9sHLdMtG.js} +1 -1
  113. package/web/_astro/{chunk-55IACEB6.DoV8S1iB.js → chunk-55IACEB6.wOLXWlPs.js} +1 -1
  114. package/web/_astro/{chunk-727SXJPM.DwR-Qlyj.js → chunk-727SXJPM.DovFbwg3.js} +1 -1
  115. package/web/_astro/{chunk-AQP2D5EJ.ND_a81WY.js → chunk-AQP2D5EJ.B1Weod1X.js} +1 -1
  116. package/web/_astro/{chunk-FMBD7UC4.Wv_jwG48.js → chunk-FMBD7UC4.TEMS04st.js} +1 -1
  117. package/web/_astro/{chunk-ND2GUHAM.CXKXCMmp.js → chunk-ND2GUHAM.Cp8VT1wQ.js} +1 -1
  118. package/web/_astro/{chunk-QZHKN3VN.nkaoNYQq.js → chunk-QZHKN3VN.BzATdEcP.js} +1 -1
  119. package/web/_astro/{classDiagram-4FO5ZUOK.cMQcVlQu.js → classDiagram-4FO5ZUOK.C9BOCfAO.js} +1 -1
  120. package/web/_astro/{classDiagram-v2-Q7XG4LA2.cMQcVlQu.js → classDiagram-v2-Q7XG4LA2.C9BOCfAO.js} +1 -1
  121. package/web/_astro/{cose-bilkent-S5V4N54A.OaDJ7Mr2.js → cose-bilkent-S5V4N54A.DUnr4UAw.js} +1 -1
  122. package/web/_astro/{cynefin-OW5HDTMX.Chi8IphF.js → cynefin-OW5HDTMX.rYq5uM3D.js} +1 -1
  123. package/web/_astro/{cytoscape.esm.DzSz-X2X.js → cytoscape.esm.BB4DxJjf.js} +1 -1
  124. package/web/_astro/{dagre-BM42HDAG.CzK2t_Fp.js → dagre-BM42HDAG.CWeNKe3I.js} +1 -1
  125. package/web/_astro/{diagram-2AECGRRQ.DRvxlVS7.js → diagram-2AECGRRQ.DCkfls10.js} +1 -1
  126. package/web/_astro/{diagram-5GNKFQAL.CnYvNdwA.js → diagram-5GNKFQAL.D5U4JCka.js} +1 -1
  127. package/web/_astro/{diagram-KO2AKTUF.CpLpMw5R.js → diagram-KO2AKTUF.BZJgqaqG.js} +1 -1
  128. package/web/_astro/{diagram-LMA3HP47.JTb78qUA.js → diagram-LMA3HP47.DoMeHvPR.js} +1 -1
  129. package/web/_astro/{diagram-OG6HWLK6.Bk-1jDIb.js → diagram-OG6HWLK6.B50qwwWX.js} +1 -1
  130. package/web/_astro/{erDiagram-TEJ5UH35.D8hN9GZq.js → erDiagram-TEJ5UH35.DdGPG6LK.js} +1 -1
  131. package/web/_astro/{flowDiagram-I6XJVG4X.-6zQr6m5.js → flowDiagram-I6XJVG4X.QP2MJ12u.js} +1 -1
  132. package/web/_astro/{ganttDiagram-6RSMTGT7.DboLQ9ca.js → ganttDiagram-6RSMTGT7.BI6LgKSy.js} +1 -1
  133. package/web/_astro/{gitGraphDiagram-PVQCEYII.4tYvJKGR.js → gitGraphDiagram-PVQCEYII.npPZiC2G.js} +1 -1
  134. package/web/_astro/index.DayyIngm.css +1 -0
  135. package/web/_astro/{infoDiagram-5YYISTIA.Bd9rXpsB.js → infoDiagram-5YYISTIA.DCJCBVbp.js} +1 -1
  136. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CvMoaf67.js → ishikawaDiagram-YF4QCWOH.BMLV-3I1.js} +1 -1
  137. package/web/_astro/{journeyDiagram-JHISSGLW.Ccy1CA7y.js → journeyDiagram-JHISSGLW.LE58crde.js} +1 -1
  138. package/web/_astro/{kanban-definition-UN3LZRKU.0MaMqHNS.js → kanban-definition-UN3LZRKU.BPbz8rH9.js} +1 -1
  139. package/web/_astro/{linear.CHXgcIbN.js → linear.DhZaBtYh.js} +1 -1
  140. package/web/_astro/{mermaid.core.Ca-kcelG.js → mermaid.core.BD5-jXum.js} +6 -6
  141. package/web/_astro/{mindmap-definition-RKZ34NQL.BUIDlHa0.js → mindmap-definition-RKZ34NQL.MTJyrQ65.js} +1 -1
  142. package/web/_astro/ordinal.BYWQX77i.js +1 -0
  143. package/web/_astro/{pieDiagram-4H26LBE5.2dX3CU1s.js → pieDiagram-4H26LBE5.BrDhDvIS.js} +1 -1
  144. package/web/_astro/{quadrantDiagram-W4KKPZXB.B3LBlRiv.js → quadrantDiagram-W4KKPZXB.71d73_5N.js} +1 -1
  145. package/web/_astro/{requirementDiagram-4Y6WPE33.X12I2uNx.js → requirementDiagram-4Y6WPE33.Bga6UF-z.js} +1 -1
  146. package/web/_astro/{sankeyDiagram-5OEKKPKP.BXohIHqx.js → sankeyDiagram-5OEKKPKP.BnHs4K82.js} +1 -1
  147. package/web/_astro/{sequenceDiagram-3UESZ5HK.C37ZIUzg.js → sequenceDiagram-3UESZ5HK.DsfY2gnj.js} +1 -1
  148. package/web/_astro/{stateDiagram-AJRCARHV.BRgz317z.js → stateDiagram-AJRCARHV.DvsTSc9a.js} +1 -1
  149. package/web/_astro/{stateDiagram-v2-BHNVJYJU.7VYSXN9-.js → stateDiagram-v2-BHNVJYJU.DxzzmHUR.js} +1 -1
  150. package/web/_astro/{timeline-definition-PNZ67QCA.BVNz_HiN.js → timeline-definition-PNZ67QCA.4ZuQmOTt.js} +1 -1
  151. package/web/_astro/{vennDiagram-CIIHVFJN.CHVDkPX4.js → vennDiagram-CIIHVFJN.Ck5Q86SG.js} +1 -1
  152. package/web/_astro/{wardleyDiagram-YWT4CUSO.EQQ_qT9v.js → wardleyDiagram-YWT4CUSO.BK7k2hXr.js} +1 -1
  153. package/web/_astro/{xychartDiagram-2RQKCTM6.DrAT9WoP.js → xychartDiagram-2RQKCTM6.DfCrgauK.js} +1 -1
  154. package/web/index.html +2 -2
  155. package/web/_astro/BoardApp.DV9kx0wo.js +0 -1
  156. package/web/_astro/channel.BAI6xLeV.js +0 -1
  157. package/web/_astro/index.Dcr_8fiK.css +0 -1
  158. 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/**"
@@ -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: >-