@gobing-ai/spur 0.4.0 → 0.4.2

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 (172) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +4 -2
  3. package/config/decisions/gates.yaml +36 -0
  4. package/config/decisions/history-anatomy.yaml +35 -0
  5. package/config/decisions/idea-pipeline.yaml +38 -0
  6. package/config/plugin-scripts.json +15 -0
  7. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  8. package/config/rules/recommended-pre-check.yaml +8 -3
  9. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  10. package/config/script-placement-baseline.json +2 -2
  11. package/config/templates/AGENTS.md +1 -1
  12. package/config/workflows/feature-lifecycle.yaml +9 -2
  13. package/config/workflows/history-anatomy.yaml +22 -0
  14. package/config/workflows/idea-pipeline.yaml +75 -0
  15. package/config/workflows/task-pipeline.yaml +216 -57
  16. package/config/workflows/wrapup-pipeline.yaml +107 -14
  17. package/package.json +10 -10
  18. package/plugins/sp/README.md +12 -3
  19. package/plugins/sp/agents/super-planner.md +16 -6
  20. package/plugins/sp/agents/super-reviewer.md +14 -0
  21. package/plugins/sp/bunfig.toml +8 -0
  22. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  23. package/plugins/sp/commands/dev-find-way.md +80 -0
  24. package/plugins/sp/commands/dev-idea.md +6 -0
  25. package/plugins/sp/commands/dev-plan.md +6 -0
  26. package/plugins/sp/commands/dev-review-session.md +4 -1
  27. package/plugins/sp/commands/dev-run.md +12 -2
  28. package/plugins/sp/commands/dev-runall.md +32 -15
  29. package/plugins/sp/hooks/context-owner.ts +56 -0
  30. package/plugins/sp/hooks/context-post-tool.ts +23 -0
  31. package/plugins/sp/hooks/context-session-start.ts +49 -27
  32. package/plugins/sp/hooks/context-session-stop.ts +131 -30
  33. package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
  34. package/plugins/sp/lib/idea-handoff.generated.mjs +43 -5
  35. package/plugins/sp/lib/inline-run-app.ts +28 -0
  36. package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
  37. package/plugins/sp/lib/inline-run.generated.mjs +351 -46
  38. package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
  39. package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
  40. package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
  41. package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
  42. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  43. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  44. package/plugins/sp/lib/run-summary-core.ts +561 -0
  45. package/plugins/sp/lib/transcript.ts +150 -7
  46. package/plugins/sp/plugin.json +1 -1
  47. package/plugins/sp/references/roles.md +1 -1
  48. package/plugins/sp/scripts/batch-plan.mjs +488 -0
  49. package/plugins/sp/scripts/batch-plan.ts +145 -0
  50. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  51. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  52. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  53. package/plugins/sp/scripts/commit-guard.ts +249 -0
  54. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  55. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  56. package/plugins/sp/scripts/inline-run-setup.mjs +562 -80
  57. package/plugins/sp/scripts/inline-run-setup.ts +52 -78
  58. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  59. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  60. package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
  61. package/plugins/sp/scripts/pr-reviewing.ts +34 -9
  62. package/plugins/sp/scripts/quality-gate.mjs +30213 -61
  63. package/plugins/sp/scripts/quality-gate.ts +21 -5
  64. package/plugins/sp/scripts/residual-scan.mjs +86 -31
  65. package/plugins/sp/scripts/residual-scan.ts +28 -30
  66. package/plugins/sp/scripts/run-summary.mjs +432 -14
  67. package/plugins/sp/scripts/run-summary.ts +41 -177
  68. package/plugins/sp/scripts/session-timeline.mjs +109 -11
  69. package/plugins/sp/scripts/session-timeline.ts +57 -19
  70. package/plugins/sp/scripts/task-diffstat.mjs +32 -29
  71. package/plugins/sp/scripts/task-diffstat.ts +34 -34
  72. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  73. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  74. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  75. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  78. package/plugins/sp/skills/code-verification/SKILL.md +3 -4
  79. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  80. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  81. package/plugins/sp/skills/fleet-join/SKILL.md +1 -1
  82. package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
  83. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  84. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  85. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
  86. package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
  87. package/plugins/sp/skills/session-review/SKILL.md +40 -7
  88. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  89. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  90. package/plugins/sp/skills/spur-cli/SKILL.md +5 -2
  91. package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
  92. package/plugins/sp/skills/spur-cli/references/decision.md +16 -8
  93. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  94. package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
  95. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -3
  96. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  97. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  98. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  99. package/plugins/sp/skills/spur-composer/SKILL.md +1 -1
  100. package/plugins/sp/skills/spur-dev/SKILL.md +20 -14
  101. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
  102. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  103. package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
  104. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  105. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
  107. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -0
  109. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  110. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  111. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +44 -12
  112. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  113. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
  114. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  115. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  116. package/schemas/state-machine-workflow.schema.json +23 -0
  117. package/spur.js +42402 -26889
  118. package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
  119. package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
  120. package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
  121. package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
  122. package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
  123. package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
  124. package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
  125. package/web/_astro/channel.D4nG9YLf.js +1 -0
  126. package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
  127. package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
  128. package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
  129. package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
  130. package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
  131. package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
  132. package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
  133. package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
  134. package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
  135. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
  136. package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
  137. package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
  138. package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
  139. package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
  140. package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
  141. package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
  142. package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
  143. package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
  144. package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
  145. package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
  146. package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
  147. package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
  148. package/web/_astro/index.BhSwN8yY.css +1 -0
  149. package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
  150. package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
  151. package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
  152. package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
  153. package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
  154. package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
  155. package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
  156. package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
  157. package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
  158. package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
  159. package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
  160. package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
  161. package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
  162. package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
  163. package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
  164. package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
  165. package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
  166. package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
  167. package/web/index.html +2 -2
  168. package/plugins/sp/commands/dev-parallel.md +0 -31
  169. package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
  170. package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
  171. package/web/_astro/channel.CUFdtWhm.js +0 -1
  172. package/web/_astro/index.Dg_3bOaJ.css +0 -1
@@ -63,7 +63,14 @@ name: task-pipeline
63
63
  # UNREACHABLE: a non-PASS verify could only end the run failed. `test-fix` also gained the
64
64
  # review-lane evidence projection (see its onEnter) so the repair hop is not fed an empty findings
65
65
  # list when the failing hop was `review-fail-triage` rather than the quality gate.
66
- version: "5"
66
+ # v6 (task 1134 run, 2026-10-09): the `confidence` check row now encodes the SAME policy as the two
67
+ # completion guards instead of a stricter one of its own. It warned for every level below HIGH, so a
68
+ # MEDIUM certification was admitted by both guards but recomputed as PARTIAL by the CLI done gate —
69
+ # the artifact was self-inconsistent and `done` was refused with no named level to act on. The row
70
+ # now warns only for an unacknowledged LOW (or absent) level, which is exactly what the guards
71
+ # refuse; `packages/app/tests/workflow/task-pipeline-proof-chain.test.ts` pins the agreement for
72
+ # HIGH/MEDIUM/LOW/absent × ack.
73
+ version: "6"
67
74
  description: "Standard task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → record → done"
68
75
  # Task entity lifecycle (owned by task-lifecycle.yaml):
69
76
  # normal: backlog → todo → wip → testing → done
@@ -116,11 +123,15 @@ vars:
116
123
  # run unbounded — if a hop still hits this wall, STOP and record it.
117
124
  stepTimeoutMs: "1800000"
118
125
  # Timeout for the `implement` step specifically (R2a). Implementation is the
119
- # heaviest agent.run step (full read/write/test-probe loop) and has timed out
120
- # at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
121
- # a 100% failure rate at that budget. 30 min gives real headroom; STOP and
122
- # record honestly rather than raising it further without operator sign-off.
123
- implementTimeoutMs: "1800000"
126
+ # heaviest agent.run step (full read/write/test-probe loop); the old 600s default
127
+ # failed five consecutive dogfood runs (bugs 742/744/746/748). Default is 45 min
128
+ # (task 1108): it governs subprocess surfaces (`spur workflow run`, parallel,
129
+ # headless); inline hosts receive it only when their dispatch tool accepts a
130
+ # timeout (inline-pipeline-driver.md § Timeout boundary). 45 min is a reasoned
131
+ # default, not a proven bound — in P1 one implement still hit 45 min and resumed
132
+ # (docs/reports/i31/1107-runall-p1-pilot.md § Timeout attribution).
133
+ # Override per run: `--vars '{"implementTimeoutMs":"3600000"}'`.
134
+ implementTimeoutMs: "2700000"
124
135
  # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
125
136
  # Empty by default; only meaningful once the approve state has been entered.
126
137
  __hitlAnswer: ""
@@ -128,13 +139,16 @@ vars:
128
139
  # default; set by `spur workflow continue --answer-text` after the pause and read
129
140
  # by the escalate→implement / escalate→failed guards. Non-empty = answered.
130
141
  __hitlInput: ""
131
- # Proof-state bracket (task 0612, ADR-071; restructured by task 0703). `proofDigest` is the
132
- # canonical capture taken at quality-gate ENTRY — immediately before the evidence-producing
133
- # final chain (quality → review → verify) — and re-captured at `test-recheck` when bounded
134
- # remediation mutated the tree, so every evidence stage names one fresh digest (R2/R4).
135
- # `proofDigestNow` is the live re-capture compared against `proofDigest` at verify entry and
136
- # immediately before `record`; a mismatch means a proof input changed after evidence was
137
- # established, so the run routes to `failed` instead of crossing the completion boundary (R5).
142
+ # Proof-state bracket (task 0612, ADR-071; restructured by task 0703; review joins by task 1135).
143
+ # `proofDigest` is the canonical capture taken at quality-gate ENTRY — immediately before the
144
+ # evidence-producing final chain (quality → review → verify) — and re-captured at `test-recheck`
145
+ # when bounded remediation mutated the tree, so every evidence stage names one fresh digest
146
+ # (R2/R4). `proofDigestNow` is the live re-capture compared against `proofDigest` at review,
147
+ # verify and record entry; a mismatch means a proof input changed after evidence was established,
148
+ # so the run routes to `failed` instead of spending a review dispatch or crossing the completion
149
+ # boundary (R5). Task 1135 adds the review-entry compare and the gate-entry tree snapshot
150
+ # (`.spur/run/<run-id>-gate-paths.txt`, same exclusions as the git-tree half) so a mismatch also
151
+ # names the drifted paths.
138
152
  # `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
139
153
  # git-tree half — spec content is folded in explicitly or a task-file edit would go undetected.
140
154
  # The fingerprint scopes task content to the proof-input sections only (Background, Requirements,
@@ -155,6 +169,11 @@ vars:
155
169
  # with the same package-manager surface (this monorepo is Bun-only):
156
170
  # `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
157
171
  # and the fixall slash input all use this same var so the command stays single-sourced.
172
+ # INVARIANT (ADR-124, clarified 2026-10-07 by task 1111 R5): only the FULL tier writes PASS.
173
+ # Under the opt-in `deferQualityGate` policy the per-task token is DEFERRED (light tier) and
174
+ # the "review runs only after a green full gate" invariant moves to BATCH scope — nothing is
175
+ # reported PASS without the integrated full gate on the base ref. Default "false" = per-task
176
+ # full gate, unchanged.
158
177
  # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
159
178
  # interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
160
179
  # NO `format` PREFIX: `implement` already ran `$formatCmd` on its way out, and `test`
@@ -204,6 +223,14 @@ vars:
204
223
  # (default) = on; set to "off" to bypass:
205
224
  # `--vars '{"implementScopeGuard":"off"}'`.
206
225
  implementScopeGuard: ""
226
+ # 1111 R1: opt-in parallel-batch gate policy. When "true", `test` / `test-recheck` run the
227
+ # LIGHT tier for per-task early feedback and write `DEFERRED` (light PASS) or `FAIL` to
228
+ # `.spur/run/<wbs>-test-gate.status` — never `PASS`, because that token means the full
229
+ # project gate ran green on this tree. The batch orchestrator owes ONE integrated full gate
230
+ # on the base ref over the merged slices (1111 R3). Default "false" = unchanged behavior,
231
+ # and a stray `DEFERRED` file fails the guards. Sequential/inline runs never set it:
232
+ # `--defer-gate` requires `--mode parallel`.
233
+ deferQualityGate: "false"
207
234
  # 0931 R5: parallel batches defer the per-task feature sync so a task branch never
208
235
  # touches feature files or docs/features/INDEX.md. When "true", the record step's
209
236
  # post-record shell appends a deferral note and skips the sync; the parallel batch
@@ -213,6 +240,10 @@ vars:
213
240
 
214
241
  states:
215
242
  - id: precheck
243
+ display:
244
+ phase: implement
245
+ phaseTitle: Implement
246
+ title: Check task readiness
216
247
  description: >
217
248
  Fast deterministic task readiness and size checks. Failures route to the
218
249
  `failed` terminal state (not a raw lifecycle abort mid-enter).
@@ -256,6 +287,10 @@ states:
256
287
  reviewer: "${vars.agent}"
257
288
 
258
289
  - id: implement
290
+ display:
291
+ phase: implement
292
+ phaseTitle: Implement
293
+ title: Implement
259
294
  description: >
260
295
  Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
261
296
  task to `wip` via the normal verb. The lifecycle transition fires AFTER the
@@ -287,6 +322,10 @@ states:
287
322
  options:
288
323
  agent: ${vars.implementAgent}
289
324
  # Pure slash command only (ADR-043). Anti-recursion / implement discipline
325
+ # The worker's brief is OWNED BY the skill, not restated here (task 1110 R3):
326
+ # see `plugins/sp/skills/code-implementation/SKILL.md` (targeted-probe scope
327
+ # § Implement scope, dependency-aware § Changed-path targeted checks, Solution
328
+ # anchor form in § Red Flags). The YAML only names the entry command.
290
329
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
291
330
  role: coder
292
331
  # B7 R3/R6 (0894): coder stages reuse the role session — the test-fix hop
@@ -350,6 +389,17 @@ states:
350
389
  - kind: shell
351
390
  options:
352
391
  command: "$formatCmd ; exit 0"
392
+ # 1109 R3: qualify this task's unique basename anchors to repo-relative paths.
393
+ # Placement is load-bearing: it runs AFTER implement's mutations (and the format
394
+ # above) but BEFORE the `test` stage's `proof.fingerprint` capture, so a rewrite in
395
+ # a digest section cannot invalidate the proof. `--wbs` scopes the pass to this
396
+ # task's resolved file — the unscoped form rewrites ~119 unrelated task files.
397
+ # Non-fatal (`; exit 0`): the done gate still reports anything left, and this step
398
+ # deliberately does NOT address line drift or `L4.anchor-subject-mismatch`
399
+ # (those need the implementer to re-anchor). Scope contract: task 1109 R5.
400
+ - kind: shell
401
+ options:
402
+ command: "$spurBin task migrate-anchors --wbs $wbs --json ; exit 0"
353
403
 
354
404
  # ── escalate hop (operator-question pause, 0933) ──────────────────────────
355
405
  # The implement agent paused on an operator question: it wrote
@@ -363,6 +413,11 @@ states:
363
413
  # the escalate→implement guard so a stale question can never re-trigger the hop
364
414
  # after the answered attempt.
365
415
  - id: escalate
416
+ display:
417
+ phase: implement
418
+ phaseTitle: Implement
419
+ title: Ask operator
420
+ show: on-entry
366
421
  description: >-
367
422
  Operator-question pause (0933): the implement agent asked a question
368
423
  headlessly; surface it to the operator and resume implement with the answer.
@@ -396,6 +451,10 @@ states:
396
451
  # test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
397
452
  # test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
398
453
  - id: test
454
+ display:
455
+ phase: test
456
+ phaseTitle: Test
457
+ title: Run quality gate
399
458
  description: >
400
459
  Proof-chain entry + soft quality-gate probe (single logical gate on the green path; bounded retries only
401
460
  for SQLite lock contention). Capture of the canonical proof-input digest happens HERE — before any
@@ -440,6 +499,12 @@ states:
440
499
  # R2 (0703, ADR-071): THE canonical proof capture. Placement is load-bearing: immediately before
441
500
  # the final evidence chain, after every implement mutation (including the post-implement format).
442
501
  # Capture-only here; `record` compares. A remediation pass re-captures at `test-recheck` (R4).
502
+ # 1135 R5: snapshot the non-corpus tree state FIRST, so a later proof mismatch names the
503
+ # drifted paths (not only the D1/D2 pair). Same exclusions as the digest's git-tree half.
504
+ - kind: shell
505
+ options:
506
+ command: >-
507
+ mkdir -p .spur/run; { git status --porcelain=v1 -uall -- . ':(exclude)docs/tasks*' ':(exclude)docs/features*'; git diff --name-only HEAD -- . ':(exclude)docs/tasks*' ':(exclude)docs/features*'; } > ".spur/run/$__runId-gate-paths.txt" 2>/dev/null; exit 0
443
508
  - kind: proof.fingerprint
444
509
  options:
445
510
  var: proofDigest
@@ -449,9 +514,14 @@ states:
449
514
  - kind: shell
450
515
  options:
451
516
  command: >-
452
- eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/quality-gate.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/quality-gate.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)"; if [ -f "$S" ]; then "$RUNNER" "$S" run; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
517
+ eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/quality-gate.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/quality-gate.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)"; M=run; [ "$deferQualityGate" = "true" ] && M=deferred; if [ -f "$S" ]; then "$RUNNER" "$S" "$M"; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
453
518
 
454
519
  - id: test-fix
520
+ display:
521
+ phase: test
522
+ phaseTitle: Test
523
+ title: Fix failures
524
+ show: on-entry
455
525
  description: >
456
526
  Bounded auto-fix hop when the quality gate is red OR final verification returned a
457
527
  repairable non-PASS (task 0703 R4: remediation never happens inside verify — it loops
@@ -515,6 +585,11 @@ states:
515
585
  processSpawn: available
516
586
 
517
587
  - id: test-recheck
588
+ display:
589
+ phase: test
590
+ phaseTitle: Test
591
+ title: Re-run quality gate
592
+ show: on-entry
518
593
  description: >
519
594
  Soft recheck after fixall with bounded SQLite-lock retry. Writes PASS|FAIL (always exit 0). Transitions
520
595
  branch to `triage` (PASS — the 0943 lane router), `test-fail-triage` (FAIL — the 0943
@@ -524,6 +599,12 @@ states:
524
599
  # R4 (0703, ADR-071): bounded remediation mutated the tree by design, so the fresh evidence
525
600
  # chain (recheck gate → review → verify) must start from a NEWLY captured digest. Capture-only;
526
601
  # the guards and `record` compare against this value.
602
+ # 1135 R5: re-snapshot the tree state after the remediation, so the refresh does not compare
603
+ # against a pre-remediation snapshot (a legitimate test-fix edit must not read as drift).
604
+ - kind: shell
605
+ options:
606
+ command: >-
607
+ mkdir -p .spur/run; { git status --porcelain=v1 -uall -- . ':(exclude)docs/tasks*' ':(exclude)docs/features*'; git diff --name-only HEAD -- . ':(exclude)docs/tasks*' ':(exclude)docs/features*'; } > ".spur/run/$__runId-gate-paths.txt" 2>/dev/null; exit 0
527
608
  - kind: proof.fingerprint
528
609
  options:
529
610
  var: proofDigest
@@ -539,9 +620,13 @@ states:
539
620
  - kind: shell
540
621
  options:
541
622
  command: >-
542
- eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/quality-gate.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/quality-gate.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)"; if [ -f "$S" ]; then "$RUNNER" "$S" recheck; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
623
+ eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/quality-gate.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/quality-gate.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)"; M=recheck; [ "$deferQualityGate" = "true" ] && M=deferred; if [ -f "$S" ]; then "$RUNNER" "$S" "$M"; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
543
624
 
544
625
  - id: triage
626
+ display:
627
+ phase: review
628
+ phaseTitle: Review
629
+ title: Choose review depth
545
630
  description: >-
546
631
  Deterministic lane router between a green gate and the review/verify fork (0943 R1).
547
632
  Writes the diffstat evidence file, honors a caller-set `mode` (never overridden), pins
@@ -570,17 +655,18 @@ states:
570
655
  options:
571
656
  command: >-
572
657
  mkdir -p .spur/run; if [ -n "$mode" ]; then printf '%s\n' "$mode" > ".spur/run/$wbs-mode.txt"; exit 0; fi; DF=".spur/run/$wbs-diffstat.json"; if ! jq -e 'type == "object"' "$DF" >/dev/null 2>&1; then printf 'safety\n' > ".spur/run/$wbs-mode.txt"; exit 0; fi; jq -r 'if .sensitive == true or ((.insertions // 0) + (.deletions // 0)) > 400 then "safety" else empty end' "$DF" 2>/dev/null > ".spur/run/$wbs-mode.txt"; exit 0
573
- # (d) lane decision — degrades to the standard default whenever workflow.decideDecisionMaker
658
+ # (d) lane decision — degrades to the standard default whenever workflows.decideDecisionMaker
574
659
  # is off (R4), so with the flag off this state is pure shell: no model call is made.
660
+ # 1114: the inline decide moved to the bundled catalog (`config/decisions/task-pipeline.yaml`
661
+ # → `task-triage`), whose R0 samples measured this exact prompt before the migration.
662
+ # Routing contract is unchanged: same evidence, same resultFile, same guards, same
663
+ # `standard` fallback when the switch is off (field-identical to this form's default).
575
664
  - kind: decide
576
665
  options:
577
666
  id: task-triage
578
- method: choice
579
- question: >-
580
- The quality gate is green. Which verification lane does this diff deserve —
581
- low (small, contained diff — fast lane), standard, or high caution?
582
- choices: [low, standard, high]
583
- default: standard
667
+ decision: task-triage
668
+ params:
669
+ wbs: ${vars.wbs}
584
670
  evidence:
585
671
  - .spur/run/${vars.wbs}-diffstat.json
586
672
  - ${vars.taskSpecPath}
@@ -601,6 +687,11 @@ states:
601
687
  var: mode
602
688
 
603
689
  - id: test-fail-triage
690
+ display:
691
+ phase: test
692
+ phaseTitle: Test
693
+ title: Triage test failure
694
+ show: on-entry
604
695
  description: >-
605
696
  Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
606
697
  conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
@@ -611,22 +702,44 @@ states:
611
702
  missing/corrupt decision fails closed to failed-check. (0976 R2 collapsed the former
612
703
  `retryable` lane: the declared default made it unreachable on the inline path.)
613
704
  onEnter:
705
+ # 1115: catalog-reference decide (task 1094 form) — question/choices/fallback live in
706
+ # the bundled catalog (`config/decisions/task-pipeline.yaml` → `failure-class`), whose
707
+ # R0 samples measured this exact prompt before the migration. Routing contract is
708
+ # unchanged: same resultFile, same guards, same `fix` fallback when the switch is off.
614
709
  - kind: decide
615
710
  options:
616
711
  id: failure-class
617
- method: choice
618
- question: >-
619
- The quality gate failed. Is the failure fixable by the repair agent, or should
620
- the run stop here?
621
- choices: [fix, stop]
622
- default: fix
712
+ decision: failure-class
713
+ params:
714
+ wbs: ${vars.wbs}
623
715
  evidence:
624
716
  - .spur/run/${vars.wbs}-test-gate.findings
625
717
  resultFile: .spur/run/${vars.wbs}-failure-class.decision
626
718
 
627
719
  - id: review
628
- description: Three-dimensional code review via /sp:dev-review (functional requirements traceability + SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) + architecture depth), findings written to `## Review`.
720
+ display:
721
+ phase: review
722
+ phaseTitle: Review
723
+ title: Review changes
724
+ description: >-
725
+ Three-dimensional code review via /sp:dev-review (functional requirements traceability +
726
+ SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) +
727
+ architecture depth), findings written to `## Review`. Cost at this decision point (1122):
728
+ open P1-P3 review findings block `done` — the PASS edges below also run the review-gate
729
+ residual check, and a PASS carrying an open P1-P3 row routes to review-fail-triage
730
+ before verify. Fixing a finding invalidates the certified digest and re-runs
731
+ quality → review → verify on a fresh digest; only P4 rows and DEFER-ed P3 rows are wrap
732
+ residuals.
629
733
  onEnter:
734
+ # 1135 R4: review joins the proof bracket. A pre-review compare refuses to spend a review
735
+ # dispatch on a state that has drifted from the certified digest; the mismatch routes
736
+ # through the existing tripwire to `failed` (no new re-certify path).
737
+ - kind: proof.fingerprint
738
+ options:
739
+ var: proofDigestNow
740
+ taskFile: ${vars.taskSpecPath}
741
+ featureFile: ${vars.featureSpecPath}
742
+ expect: ${vars.proofDigest}
630
743
  - kind: agent.run
631
744
  options:
632
745
  # 0710 R2: review always runs on a fresh session — no implementation-session
@@ -672,29 +785,42 @@ states:
672
785
  # (`.spur/run/<wbs>-test-fix-attempt` against `qualityGateMaxFixAttempts`), and the fix
673
786
  # re-enters quality → review → verify on a freshly captured digest. The `decide` action
674
787
  # itself always runs (the engine has no onEnter conditionals); with
675
- # `workflow.decideDecisionMaker` off it degrades to its declared default.
788
+ # `workflows.decideDecisionMaker` off it degrades to its declared default.
676
789
  - id: review-fail-triage
790
+ display:
791
+ phase: review
792
+ phaseTitle: Review
793
+ title: Triage review findings
794
+ show: on-entry
677
795
  description: >-
678
796
  Deterministic router for a review that did NOT certify PASS (session finding after 1088).
679
797
  `decide review-failure-class` reads the reviewer's answer and the task spec and routes:
680
798
  fix → the shared test-fix hop, stop → failed with terminalReason failed-check. A
681
- missing/corrupt decision fails closed to failed-check.
799
+ missing/corrupt decision fails closed to failed-check. Cost either way (1122): open
800
+ P1-P3 review findings block `done`; the fix lane re-enters quality → review → verify on a
801
+ fresh digest; only P4 rows and DEFER-ed P3 rows are wrap residuals.
682
802
  onEnter:
803
+ # 1116: the inline decide moved to the bundled catalog (`config/decisions/task-pipeline.yaml`
804
+ # → `review-failure-class`), whose R0 samples measured this exact prompt before the
805
+ # migration. Routing contract is unchanged: same evidence, same resultFile, same guards,
806
+ # same `fix` fallback when the switch is off.
683
807
  - kind: decide
684
808
  options:
685
809
  id: review-failure-class
686
- method: choice
687
- question: >-
688
- The review did not certify PASS (FAIL or PARTIAL). Is the finding fixable by the
689
- repair agent, or should the run stop here for the operator?
690
- choices: [fix, stop]
691
- default: fix
810
+ decision: review-failure-class
811
+ params:
812
+ wbs: ${vars.wbs}
692
813
  evidence:
693
814
  - .spur/run/${vars.__runId}-review-answer.txt
694
815
  - ${vars.taskSpecPath}
695
816
  resultFile: .spur/run/${vars.wbs}-review-failure-class.decision
696
817
 
697
818
  - id: approve
819
+ display:
820
+ phase: review
821
+ phaseTitle: Review
822
+ title: Operator approval
823
+ show: on-entry
698
824
  description: >
699
825
  Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
700
826
  (review routes around it straight to verify). In interactive mode this state pauses
@@ -710,9 +836,18 @@ states:
710
836
  mode: never
711
837
 
712
838
  - id: verify
839
+ display:
840
+ phase: verify
841
+ phaseTitle: "Verify & record"
842
+ title: Verify requirements and AC
713
843
  description: >
714
844
  Observe-only functional verification (BDD + traceability) via /sp:dev-verify --fix none
715
845
  (task 0703 R1, ADR-071): the verifier certifies the state, it never repairs its own subject.
846
+ The answer-shape contract this stage enforces is OWNED BY the skill, not restated here
847
+ (task 1110 R3): see `plugins/sp/skills/code-verification/SKILL.md` § Answer-File Schema
848
+ Contract (`:340-375`), with the row-id/evidence vocabularies in
849
+ `plugins/sp/skills/code-verification/references/verdict-schema.md`. The YAML only names
850
+ the lint that consumes it (`task verdict --from-answer`).
716
851
  A live digest compare BEFORE the agent refuses to certify a state that drifted after the
717
852
  quality/review evidence was produced (R2). The agent's
718
853
  captured answer is written to `.spur/run/<wbs>-verify-answer.txt` and MUST follow
@@ -763,15 +898,17 @@ states:
763
898
  options:
764
899
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
765
900
  # Session finding after 1088: the verifier's confidence level becomes a `confidence` check
766
- # row (warn unless HIGH) and a gate-log line, so the level the completion guards enforce is
767
- # visible in the artifact and the log — not only in the answer file. This is its own action
768
- # because the proof-binding stamp below must stay the state's LAST action AND under the
769
- # ADR-115 800-char shell cap.
901
+ # row and a gate-log line, so the level the completion guards enforce is visible in the
902
+ # artifact and the log — not only in the answer file. v6 (task 1134 run): the row warns for
903
+ # an unacknowledged LOW/absent level ONLY, matching the guards' ack rule; warning for
904
+ # MEDIUM too made an admitted certification aggregate to PARTIAL (self-inconsistent artifact,
905
+ # done refused). This is its own action because the proof-binding stamp below must stay the
906
+ # state's LAST action AND under the ADR-115 800-char shell cap.
770
907
  - kind: shell
771
908
  options:
772
909
  command: >-
773
910
  V=".spur/run/$wbs-verdict.json"; [ -f "$V" ] || exit 0;
774
- jq --arg ack "$ackLowConfidence" '. + {checks: ((.checks // []) | map(select(.name != "confidence"))) + [{name: "confidence", status: (if (.confidence // "LOW") == "HIGH" then "pass" else "warn" end), evidence: ((.confidence // "LOW (absent from artifact)") + (if $ack == "true" then " (operator-acknowledged)" else "" end))}]}' "$V" > "$V.tmp" && mv "$V.tmp" "$V";
911
+ jq --arg ack "$ackLowConfidence" '. + {checks: (((.checks // []) | map(select(.name != "confidence"))) + [{name: "confidence", status: (if ((.confidence // "LOW") != "LOW" or $ack == "true") then "pass" else "warn" end), evidence: ((.confidence // "LOW (absent from artifact)") + (if $ack == "true" then " (operator-acknowledged)" else "" end))}])}' "$V" > "$V.tmp" && mv "$V.tmp" "$V";
775
912
  printf 'confidence: %s (ack=%s)\n' "$(jq -r '.confidence // "LOW (absent)"' "$V")" "$ackLowConfidence" >> ".spur/run/$wbs-test-gate.log";
776
913
  exit 0
777
914
  # (e) one jq mutation binds the verdict to the proof digest (0703 R3; runId 0730 §B.2 /
@@ -782,6 +919,10 @@ states:
782
919
  V=".spur/run/$wbs-verdict.json"; [ -f "$V" ] && [ -n "$proofDigest" ] || exit 0; jq --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" --arg rp "$(cat .spur/run/$__runId-review-proof.digest 2>/dev/null)" --arg g "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null || echo UNKNOWN)" '. + {proof: {digest: $d, runId: $r, definitionDigest: $dd, capturePoint: "quality-gate-entry", stages: {qualityGate: {status: $g, digest: $d}, review: {status: (if $rp == $d then "completed" else "skipped" end), digest: $d}, verification: {status: .verdict, digest: $d}}}} | .checks += [{name: "proof-input-digest", status: "pass", evidence: $d}]' "$V" > "$V.tmp" && mv "$V.tmp" "$V"; exit 0
783
920
 
784
921
  - id: record
922
+ display:
923
+ phase: verify
924
+ phaseTitle: "Verify & record"
925
+ title: Record evidence
785
926
  description: >
786
927
  Record pipeline results into the task file via `spur task record` —
787
928
  Testing/Review from the verdict, Solution backfilled from git diff as a
@@ -941,7 +1082,11 @@ transitions:
941
1082
  guard:
942
1083
  kind: shell
943
1084
  options:
944
- command: '$spurBin task check $wbs --precheck'
1085
+ # 1132 R4 (auto-fix-first): repair mechanically first, then judge fail-closed. The
1086
+ # `--fix` output is captured to a run artifact so the batch report can carry the
1087
+ # task-level repairs under `autoRepairs` (feature-level repairs come from the
1088
+ # `feature check --fix` preflight); only `--precheck` decides this transition.
1089
+ command: '$spurBin task check $wbs --fix --json > ".spur/run/$wbs-auto-repairs.json" 2>/dev/null ; $spurBin task check $wbs --precheck'
945
1090
  - from: precheck
946
1091
  to: failed
947
1092
  terminalReason: failed-check
@@ -1009,9 +1154,11 @@ transitions:
1009
1154
  guard:
1010
1155
  kind: shell
1011
1156
  options:
1157
+ # 1111 R2: `DEFERRED` is accepted ONLY when the opt-in var is "true" — with the
1158
+ # default "false" a stray DEFERRED file fails the guard exactly like a corrupt one.
1012
1159
  command: >-
1013
1160
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
1014
- test "$gate_status" = PASS
1161
+ test "$gate_status" = PASS || { test "$gate_status" = DEFERRED && test "$deferQualityGate" = "true"; }
1015
1162
  - from: test
1016
1163
  to: test-fail-triage
1017
1164
  description: Quality gate red — classify the failure before the repair hop (0943 R3).
@@ -1042,9 +1189,10 @@ transitions:
1042
1189
  guard:
1043
1190
  kind: shell
1044
1191
  options:
1192
+ # 1111 R2: same var-gated acceptance of the deferred token as `test → triage`.
1045
1193
  command: >-
1046
1194
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
1047
- test "$gate_status" = PASS
1195
+ test "$gate_status" = PASS || { test "$gate_status" = DEFERRED && test "$deferQualityGate" = "true"; }
1048
1196
  - from: test-recheck
1049
1197
  to: test-fail-triage
1050
1198
  description: Still red after fixall — classify the failure (0943 R3).
@@ -1155,33 +1303,44 @@ transitions:
1155
1303
  kind: always
1156
1304
  # ── review routing: FAIL → bounded remediation; PASS → approve or the auto-skip (R4) ──
1157
1305
  # Declaration order matters and is FAIL-CLOSED: the two PASS edges each demand the reviewer's
1158
- # own `Verdict: PASS` line, and the catch-all sends EVERYTHING ELSE (FAIL, a missing answer, an
1159
- # unparseable one) into `review-fail-triage`. Before this, a FAIL review had no outgoing edge at
1160
- # all — the graph could not see the verdict, so remediation meant driving states by hand
1161
- # (session finding after 1088). The reviewer writes `Verdict: PASS|FAIL` in its answer; both
1162
- # plain and bold forms are accepted. Under `profile=auto` the approve gate is skipped, so the
1163
- # PASS+auto edge is tried first and the interactive PASS edge second.
1306
+ # own `Verdict: PASS` line AND a passing review-gate residual check (1122 R3) — the SAME
1307
+ # parse/classify/deferrals the record sweep enforces, resolved through the same source-repo /
1308
+ # installed scanner pair as the record step; a missing scanner fails closed into the catch-all.
1309
+ # EVERYTHING ELSE — FAIL, a missing or unparseable answer, or a PASS carrying an open P1-P3
1310
+ # review finding (record would terminally fail it after a full verify + record) — enters
1311
+ # `review-fail-triage` and its bounded repair hop BEFORE verify spends the cycle, then
1312
+ # re-enters quality → review → verify on a fresh digest. Before 1088 a FAIL review had no
1313
+ # outgoing edge at all; before 1122 a PASS-with-findings cost a full re-certification cycle per
1314
+ # attempt (knowledge-kit 0210/0213). The reviewer writes `Verdict: PASS|FAIL` in its answer;
1315
+ # both plain and bold forms are accepted. Under `profile=auto` the approve gate is skipped, so
1316
+ # the PASS+auto edge is tried first and the interactive PASS edge second.
1164
1317
  - from: review
1165
1318
  to: verify
1166
- description: profile=auto and the review certified PASS — skip the human approval gate.
1319
+ description: profile=auto, the review certified PASS, and no open P1-P3 review finding — skip the human approval gate.
1167
1320
  guard:
1168
1321
  kind: shell
1169
1322
  options:
1170
1323
  command: >-
1171
1324
  test "$profile" = auto &&
1172
- grep -qiE '^[[:space:]]*\**Verdict:\** *PASS' ".spur/run/$__runId-review-answer.txt"
1325
+ grep -qiE '^[[:space:]]*\**Verdict:\** *PASS' ".spur/run/$__runId-review-answer.txt" &&
1326
+ eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/residual-scan.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/residual-scan.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)" &&
1327
+ [ -f "$S" ] && "$RUNNER" "$S" review-gate "$wbs" --spur-bin "$spurBin" >&2
1173
1328
  - from: review
1174
1329
  to: approve
1175
- description: Review certified PASS — gate on human approval before verification.
1330
+ description: Review certified PASS with no open P1-P3 review finding — gate on human approval before verification.
1176
1331
  guard:
1177
1332
  kind: shell
1178
1333
  options:
1179
- command: grep -qiE '^[[:space:]]*\**Verdict:\** *PASS' ".spur/run/$__runId-review-answer.txt"
1334
+ command: >-
1335
+ grep -qiE '^[[:space:]]*\**Verdict:\** *PASS' ".spur/run/$__runId-review-answer.txt" &&
1336
+ eval "$(jq -r 'if .mode=="source-repo" then "S="+@sh"\(.dir)/residual-scan.ts"+" RUNNER=bun" elif .mode=="installed" then "S="+@sh"\(.dir)/residual-scan.mjs"+" RUNNER=node" else empty end' ".spur/run/$__runId-script-root.json" 2>/dev/null)" &&
1337
+ [ -f "$S" ] && "$RUNNER" "$S" review-gate "$wbs" --spur-bin "$spurBin" >&2
1180
1338
  - from: review
1181
1339
  to: review-fail-triage
1182
1340
  description: >-
1183
- No PASS in the reviewer's answer (FAIL, PARTIAL, missing, or unparseable — fail closed) —
1184
- classify the failure and route it into the bounded repair hop instead of dead-ending the run.
1341
+ No certified PASS without an open P1-P3 finding (FAIL, PARTIAL, missing, unparseable, or
1342
+ a PASS whose review-gate check found an open P1-P3 row — fail closed) — classify the
1343
+ failure and route it into the bounded repair hop instead of dead-ending the run.
1185
1344
  guard:
1186
1345
  kind: always
1187
1346