@gobing-ai/spur 0.3.99 → 0.4.1

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 +17 -0
  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/decisions/task-pipeline.yaml +55 -0
  7. package/config/plugin-scripts.json +25 -0
  8. package/config/rules/boundary/sp-runtime-path.yaml +2 -0
  9. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  10. package/config/rules/quality/coverage-gate.yaml +5 -3
  11. package/config/rules/recommended-pre-check.yaml +8 -3
  12. package/config/rules/strict/runtime-boundaries.yaml +11 -0
  13. package/config/rules/structure/protected-files.yaml +3 -0
  14. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  15. package/config/script-placement-baseline.json +2 -2
  16. package/config/workflows/feature-lifecycle.yaml +9 -2
  17. package/config/workflows/history-anatomy.yaml +22 -0
  18. package/config/workflows/idea-pipeline.yaml +75 -0
  19. package/config/workflows/task-pipeline.yaml +368 -49
  20. package/config/workflows/wrapup-pipeline.yaml +106 -13
  21. package/package.json +9 -9
  22. package/plugins/sp/README.md +25 -2
  23. package/plugins/sp/agents/super-planner.md +11 -0
  24. package/plugins/sp/agents/super-reviewer.md +39 -11
  25. package/plugins/sp/bunfig.toml +8 -0
  26. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  27. package/plugins/sp/commands/dev-changelog.md +5 -4
  28. package/plugins/sp/commands/dev-find-way.md +80 -0
  29. package/plugins/sp/commands/dev-idea.md +6 -0
  30. package/plugins/sp/commands/dev-parallel.md +8 -2
  31. package/plugins/sp/commands/dev-plan.md +6 -0
  32. package/plugins/sp/commands/dev-review-session.md +14 -7
  33. package/plugins/sp/commands/dev-run.md +17 -4
  34. package/plugins/sp/commands/dev-runall.md +21 -10
  35. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  36. package/plugins/sp/hooks/context-session-start.ts +1 -1
  37. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  38. package/plugins/sp/hooks/hooks.json +45 -1
  39. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  40. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  41. package/plugins/sp/lib/idea-handoff.generated.mjs +72 -11
  42. package/plugins/sp/lib/inline-run-app.ts +28 -0
  43. package/plugins/sp/lib/inline-run.generated.d.mts +6 -0
  44. package/plugins/sp/lib/inline-run.generated.mjs +364 -46
  45. package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
  46. package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
  47. package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
  48. package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
  49. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  50. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  51. package/plugins/sp/lib/run-summary-core.ts +561 -0
  52. package/plugins/sp/lib/transcript.ts +275 -0
  53. package/plugins/sp/plugin.json +1 -1
  54. package/plugins/sp/references/roles.md +1 -1
  55. package/plugins/sp/scripts/batch-plan.mjs +335 -0
  56. package/plugins/sp/scripts/batch-plan.ts +100 -0
  57. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  58. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  59. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  60. package/plugins/sp/scripts/commit-guard.ts +249 -0
  61. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  62. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  63. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  64. package/plugins/sp/scripts/inline-run-setup.mjs +563 -91
  65. package/plugins/sp/scripts/inline-run-setup.ts +65 -97
  66. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  67. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  68. package/plugins/sp/scripts/quality-gate.mjs +30407 -73
  69. package/plugins/sp/scripts/quality-gate.ts +21 -5
  70. package/plugins/sp/scripts/residual-scan.mjs +88 -38
  71. package/plugins/sp/scripts/residual-scan.ts +35 -38
  72. package/plugins/sp/scripts/run-summary.mjs +693 -0
  73. package/plugins/sp/scripts/run-summary.ts +105 -0
  74. package/plugins/sp/scripts/session-timeline.mjs +331 -0
  75. package/plugins/sp/scripts/session-timeline.ts +204 -0
  76. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  77. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  78. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  79. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  80. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  81. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  82. package/plugins/sp/skills/code-verification/SKILL.md +8 -4
  83. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  84. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  85. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  86. package/plugins/sp/skills/session-review/SKILL.md +77 -24
  87. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  88. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  89. package/plugins/sp/skills/spur-cli/SKILL.md +6 -2
  90. package/plugins/sp/skills/spur-cli/references/agent.md +124 -17
  91. package/plugins/sp/skills/spur-cli/references/decision.md +98 -0
  92. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  93. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +41 -3
  94. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  95. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  96. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  97. package/plugins/sp/skills/spur-composer/SKILL.md +28 -2
  98. package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
  99. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
  100. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  101. package/plugins/sp/skills/spur-dev/references/dev-operations.md +51 -18
  102. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  103. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
  104. package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -963
  105. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +20 -0
  107. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  109. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +51 -9
  110. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  111. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +361 -156
  112. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  113. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  114. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  115. package/schemas/decision-catalog.schema.json +226 -0
  116. package/schemas/spur-config.schema.json +25 -0
  117. package/schemas/state-machine-workflow.schema.json +23 -0
  118. package/spur.js +28165 -19051
  119. package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.D66k_s_T.js} +1 -1
  120. package/web/_astro/BoardApp.D6sjAJr_.js +324 -0
  121. package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.BynLnMNc.js} +1 -1
  122. package/web/_astro/{arc.Bu5XpBEz.js → arc._BUadrdH.js} +1 -1
  123. package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
  124. package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
  125. package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
  126. package/web/_astro/channel.DZVfto-r.js +1 -0
  127. package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
  128. package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
  129. package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
  130. package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
  131. package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
  132. package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
  133. package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
  134. package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
  135. package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
  136. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
  137. package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
  138. package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
  139. package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
  140. package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
  141. package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
  142. package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
  143. package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
  144. package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
  145. package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
  146. package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
  147. package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
  148. package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
  149. package/web/_astro/index.COh_kBxN.css +1 -0
  150. package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
  151. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
  152. package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
  153. package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
  154. package/web/_astro/{linear.Dxw00fGQ.js → linear.MzmRrNER.js} +1 -1
  155. package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.fK-WNswS.js} +4 -4
  156. package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
  157. package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
  158. package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
  159. package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
  160. package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
  161. package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
  162. package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
  163. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
  164. package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
  165. package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
  166. package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
  167. package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
  168. package/web/board-runtime.json +1 -1
  169. package/web/index.html +2 -2
  170. package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
  171. package/web/_astro/channel.6IFIKBhu.js +0 -1
  172. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -19,6 +19,21 @@
19
19
  # done guard closed, and re-records Testing before the failed edge); done settles
20
20
  # deferrals; failed renders the recovery report. No new state, edge, or model query.
21
21
  #
22
+ # Confidence gate (session finding after 1088, task 1068 follow-up): the verify answer has
23
+ # carried a `Confidence: HIGH|MEDIUM|LOW` line since 1068, and nothing read it — a PASS at
24
+ # LOW confidence certified exactly like HIGH. The verify stamp now records a `confidence`
25
+ # check row, and BOTH completion guards (verify → record, record → done) refuse a LOW (or
26
+ # absent) level unless the run was launched with `--vars '{"ackLowConfidence":"true"}'`.
27
+ # The acknowledgement is an operator decision, not a model one: it is the only way to
28
+ # certify a verdict the verifier itself would not stand behind.
29
+ #
30
+ # Review-failure routing (session finding after 1088): `review` had no outgoing edge for a
31
+ # FAIL verdict, so a failing review could only be remediated by driving the graph by hand
32
+ # (re-entering `test` and hand-emitting rows). The review stage now derives
33
+ # `.spur/run/<wbs>-review.status` from its own answer file (fail-closed to FAIL), and a FAIL
34
+ # routes through `review-fail-triage` — the review-side twin of `test-fail-triage` (0943) —
35
+ # into the SAME bounded fix budget, so remediation stays on-graph and bounded.
36
+ #
22
37
  # Vars (passed as a JSON object via `--vars`):
23
38
  # wbs — task WBS (required)
24
39
  # profile — "auto" skips HITL approve (R4)
@@ -36,7 +51,26 @@ kind: state-machine
36
51
  name: task-pipeline
37
52
  # 0772 R2: explicit identity tag. Set only after the proof failure-path tests
38
53
  # passed; the engine treats unversioned external definitions as supported.
39
- version: "3"
54
+ # v4 (session finding after 1088): the completion gate reads the verifier's confidence level and
55
+ # the graph routes a non-PASS review into bounded remediation — both are contract changes, so the
56
+ # identity moves with them.
57
+ # v5 (task 1077's inline run, 2026-10-06): the verify verdict action is explicitly `onError:
58
+ # continue`. `task verdict` exits non-zero on any non-PASS verdict BY DESIGN (1003 R2) — that is
59
+ # data for the verify guards, not a state failure. Under the engine's default 'fail' policy
60
+ # (`resolveOnErrorPolicy: action ?? workflowDefault ?? runOption ?? 'fail'`) the state sequence
61
+ # halted on that exit code and `lifecycle.fail('verify')` finalized the run before any transition
62
+ # guard was evaluated, which made the declared `verify → test-fix` "repairable non-PASS" lane
63
+ # UNREACHABLE: a non-PASS verify could only end the run failed. `test-fix` also gained the
64
+ # review-lane evidence projection (see its onEnter) so the repair hop is not fed an empty findings
65
+ # list when the failing hop was `review-fail-triage` rather than the quality gate.
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"
40
74
  description: "Standard task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → record → done"
41
75
  # Task entity lifecycle (owned by task-lifecycle.yaml):
42
76
  # normal: backlog → todo → wip → testing → done
@@ -89,11 +123,15 @@ vars:
89
123
  # run unbounded — if a hop still hits this wall, STOP and record it.
90
124
  stepTimeoutMs: "1800000"
91
125
  # Timeout for the `implement` step specifically (R2a). Implementation is the
92
- # heaviest agent.run step (full read/write/test-probe loop) and has timed out
93
- # at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
94
- # a 100% failure rate at that budget. 30 min gives real headroom; STOP and
95
- # record honestly rather than raising it further without operator sign-off.
96
- 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"
97
135
  # Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
98
136
  # Empty by default; only meaningful once the approve state has been entered.
99
137
  __hitlAnswer: ""
@@ -101,13 +139,16 @@ vars:
101
139
  # default; set by `spur workflow continue --answer-text` after the pause and read
102
140
  # by the escalate→implement / escalate→failed guards. Non-empty = answered.
103
141
  __hitlInput: ""
104
- # Proof-state bracket (task 0612, ADR-071; restructured by task 0703). `proofDigest` is the
105
- # canonical capture taken at quality-gate ENTRY — immediately before the evidence-producing
106
- # final chain (quality → review → verify) — and re-captured at `test-recheck` when bounded
107
- # remediation mutated the tree, so every evidence stage names one fresh digest (R2/R4).
108
- # `proofDigestNow` is the live re-capture compared against `proofDigest` at verify entry and
109
- # immediately before `record`; a mismatch means a proof input changed after evidence was
110
- # 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.
111
152
  # `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
112
153
  # git-tree half — spec content is folded in explicitly or a task-file edit would go undetected.
113
154
  # The fingerprint scopes task content to the proof-input sections only (Background, Requirements,
@@ -128,6 +169,11 @@ vars:
128
169
  # with the same package-manager surface (this monorepo is Bun-only):
129
170
  # `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
130
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.
131
177
  # TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
132
178
  # interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
133
179
  # NO `format` PREFIX: `implement` already ran `$formatCmd` on its way out, and `test`
@@ -149,6 +195,14 @@ vars:
149
195
  # Escalation counter: .spur/run/<wbs>-escalation-count. Default 2 = two answered
150
196
  # questions; a third pause routes to failed with a report note.
151
197
  maxEscalations: "2"
198
+ # Operator acknowledgement of a LOW-confidence certification (session finding after 1088).
199
+ # The verify answer's `Confidence:` line is captured into the verdict artifact; a LOW (or
200
+ # absent) level fails the completion guards unless this is "true", because a PASS the
201
+ # verifier would not stand behind is an operator risk decision, not a mechanical one.
202
+ # Set per run: `--vars '{"ackLowConfidence":"true"}'` (and export it on the inline path).
203
+ # Deliberately NOT settable from the task file or the verifier's own answer: the point is
204
+ # that a second party, not the producer, accepts the residual uncertainty.
205
+ ackLowConfidence: ""
152
206
  # Runtime-written, 0933: the escalate hop's file.read.into-var fills this from
153
207
  # .spur/run/<wbs>-question.md; the hitl.input prompt below references it. Declared
154
208
  # here (empty) so the static var-reference validator sees the template source.
@@ -169,6 +223,14 @@ vars:
169
223
  # (default) = on; set to "off" to bypass:
170
224
  # `--vars '{"implementScopeGuard":"off"}'`.
171
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"
172
234
  # 0931 R5: parallel batches defer the per-task feature sync so a task branch never
173
235
  # touches feature files or docs/features/INDEX.md. When "true", the record step's
174
236
  # post-record shell appends a deferral note and skips the sync; the parallel batch
@@ -178,6 +240,10 @@ vars:
178
240
 
179
241
  states:
180
242
  - id: precheck
243
+ display:
244
+ phase: implement
245
+ phaseTitle: Implement
246
+ title: Check task readiness
181
247
  description: >
182
248
  Fast deterministic task readiness and size checks. Failures route to the
183
249
  `failed` terminal state (not a raw lifecycle abort mid-enter).
@@ -221,6 +287,10 @@ states:
221
287
  reviewer: "${vars.agent}"
222
288
 
223
289
  - id: implement
290
+ display:
291
+ phase: implement
292
+ phaseTitle: Implement
293
+ title: Implement
224
294
  description: >
225
295
  Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
226
296
  task to `wip` via the normal verb. The lifecycle transition fires AFTER the
@@ -252,6 +322,10 @@ states:
252
322
  options:
253
323
  agent: ${vars.implementAgent}
254
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.
255
329
  # Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
256
330
  role: coder
257
331
  # B7 R3/R6 (0894): coder stages reuse the role session — the test-fix hop
@@ -315,6 +389,17 @@ states:
315
389
  - kind: shell
316
390
  options:
317
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"
318
403
 
319
404
  # ── escalate hop (operator-question pause, 0933) ──────────────────────────
320
405
  # The implement agent paused on an operator question: it wrote
@@ -328,6 +413,11 @@ states:
328
413
  # the escalate→implement guard so a stale question can never re-trigger the hop
329
414
  # after the answered attempt.
330
415
  - id: escalate
416
+ display:
417
+ phase: implement
418
+ phaseTitle: Implement
419
+ title: Ask operator
420
+ show: on-entry
331
421
  description: >-
332
422
  Operator-question pause (0933): the implement agent asked a question
333
423
  headlessly; surface it to the operator and resume implement with the answer.
@@ -361,6 +451,10 @@ states:
361
451
  # test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
362
452
  # test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
363
453
  - id: test
454
+ display:
455
+ phase: test
456
+ phaseTitle: Test
457
+ title: Run quality gate
364
458
  description: >
365
459
  Proof-chain entry + soft quality-gate probe (single logical gate on the green path; bounded retries only
366
460
  for SQLite lock contention). Capture of the canonical proof-input digest happens HERE — before any
@@ -405,6 +499,12 @@ states:
405
499
  # R2 (0703, ADR-071): THE canonical proof capture. Placement is load-bearing: immediately before
406
500
  # the final evidence chain, after every implement mutation (including the post-implement format).
407
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
408
508
  - kind: proof.fingerprint
409
509
  options:
410
510
  var: proofDigest
@@ -414,9 +514,14 @@ states:
414
514
  - kind: shell
415
515
  options:
416
516
  command: >-
417
- 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
418
518
 
419
519
  - id: test-fix
520
+ display:
521
+ phase: test
522
+ phaseTitle: Test
523
+ title: Fix failures
524
+ show: on-entry
420
525
  description: >
421
526
  Bounded auto-fix hop when the quality gate is red OR final verification returned a
422
527
  repairable non-PASS (task 0703 R4: remediation never happens inside verify — it loops
@@ -438,6 +543,17 @@ states:
438
543
  options:
439
544
  command: >-
440
545
  printf '%s\n' "$(( $(cat ".spur/run/$wbs-test-fix-attempt" 2>/dev/null || echo 0) + 1 ))" > ".spur/run/$wbs-test-fix-attempt"; [ -f ".spur/run/$wbs-verdict.json" ] && echo '--- verify verdict (remediation input, task 0703 R4) ---' >> ".spur/run/$wbs-test-gate.log" && cat ".spur/run/$wbs-verdict.json" >> ".spur/run/$wbs-test-gate.log"; [ -f ".spur/run/$wbs-residuals.json" ] && echo '--- residual artifact (F96 fix targets; deferrals go to residual-deferrals.json, P3/markers only) ---' >> ".spur/run/$wbs-test-gate.log" && cat ".spur/run/$wbs-residuals.json" >> ".spur/run/$wbs-test-gate.log"; exit 0
546
+ # Review-lane input (v5, task 1077 run): `review-fail-triage` routes a non-PASS review
547
+ # into this same bounded repair hop, but it stages no review evidence — the fixer then
548
+ # received a GREEN gate log and an empty findings list, so the review finding it exists
549
+ # to repair was invisible to it. Project the reviewer's answer into the same log the
550
+ # other lanes use, and only on the review lane (the decision file exists only after
551
+ # `review-fail-triage`). Own action, not an append to the one above: ADR-115's 800-char
552
+ # shell cap is already 602 chars there.
553
+ - kind: shell
554
+ options:
555
+ command: >-
556
+ [ -f ".spur/run/$wbs-review-failure-class.decision" ] && [ -f ".spur/run/$__runId-review-answer.txt" ] && echo '--- review verdict (remediation input, review lane) ---' >> ".spur/run/$wbs-test-gate.log" && cat ".spur/run/$__runId-review-answer.txt" >> ".spur/run/$wbs-test-gate.log"; exit 0
441
557
  # R3 (0482): project the extracted gate anchors into a var so the dispatch input
442
558
  # can NAME the failing file:line, not merely point at a log. A vars template cannot
443
559
  # run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
@@ -469,6 +585,11 @@ states:
469
585
  processSpawn: available
470
586
 
471
587
  - id: test-recheck
588
+ display:
589
+ phase: test
590
+ phaseTitle: Test
591
+ title: Re-run quality gate
592
+ show: on-entry
472
593
  description: >
473
594
  Soft recheck after fixall with bounded SQLite-lock retry. Writes PASS|FAIL (always exit 0). Transitions
474
595
  branch to `triage` (PASS — the 0943 lane router), `test-fail-triage` (FAIL — the 0943
@@ -478,6 +599,12 @@ states:
478
599
  # R4 (0703, ADR-071): bounded remediation mutated the tree by design, so the fresh evidence
479
600
  # chain (recheck gate → review → verify) must start from a NEWLY captured digest. Capture-only;
480
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
481
608
  - kind: proof.fingerprint
482
609
  options:
483
610
  var: proofDigest
@@ -493,9 +620,13 @@ states:
493
620
  - kind: shell
494
621
  options:
495
622
  command: >-
496
- 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
497
624
 
498
625
  - id: triage
626
+ display:
627
+ phase: review
628
+ phaseTitle: Review
629
+ title: Choose review depth
499
630
  description: >-
500
631
  Deterministic lane router between a green gate and the review/verify fork (0943 R1).
501
632
  Writes the diffstat evidence file, honors a caller-set `mode` (never overridden), pins
@@ -524,17 +655,18 @@ states:
524
655
  options:
525
656
  command: >-
526
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
527
- # (d) lane decision — degrades to the standard default whenever workflow.decideDecisionMaker
658
+ # (d) lane decision — degrades to the standard default whenever workflows.decideDecisionMaker
528
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).
529
664
  - kind: decide
530
665
  options:
531
666
  id: task-triage
532
- method: choice
533
- question: >-
534
- The quality gate is green. Which verification lane does this diff deserve —
535
- low (small, contained diff — fast lane), standard, or high caution?
536
- choices: [low, standard, high]
537
- default: standard
667
+ decision: task-triage
668
+ params:
669
+ wbs: ${vars.wbs}
538
670
  evidence:
539
671
  - .spur/run/${vars.wbs}-diffstat.json
540
672
  - ${vars.taskSpecPath}
@@ -555,6 +687,11 @@ states:
555
687
  var: mode
556
688
 
557
689
  - id: test-fail-triage
690
+ display:
691
+ phase: test
692
+ phaseTitle: Test
693
+ title: Triage test failure
694
+ show: on-entry
558
695
  description: >-
559
696
  Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
560
697
  conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
@@ -565,22 +702,44 @@ states:
565
702
  missing/corrupt decision fails closed to failed-check. (0976 R2 collapsed the former
566
703
  `retryable` lane: the declared default made it unreachable on the inline path.)
567
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.
568
709
  - kind: decide
569
710
  options:
570
711
  id: failure-class
571
- method: choice
572
- question: >-
573
- The quality gate failed. Is the failure fixable by the repair agent, or should
574
- the run stop here?
575
- choices: [fix, stop]
576
- default: fix
712
+ decision: failure-class
713
+ params:
714
+ wbs: ${vars.wbs}
577
715
  evidence:
578
716
  - .spur/run/${vars.wbs}-test-gate.findings
579
717
  resultFile: .spur/run/${vars.wbs}-failure-class.decision
580
718
 
581
719
  - id: review
582
- 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.
583
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}
584
743
  - kind: agent.run
585
744
  options:
586
745
  # 0710 R2: review always runs on a fresh session — no implementation-session
@@ -611,11 +770,57 @@ states:
611
770
  # verify stamp marks review `completed` only when this marker equals the current digest;
612
771
  # otherwise `skipped` (an honest downgrade, never a fake completed), and the bound
613
772
  # `record` registration refuses a skipped review.
773
+ # Session finding after 1088: the review VERDICT is read straight out of the reviewer's
774
+ # own answer file by the review → * guards below, so this stage needs no extra artifact
775
+ # and no extra action (ADR-115 legibility cap).
614
776
  - kind: shell
615
777
  options:
616
778
  command: 'mkdir -p .spur/run; printf "%s" "$proofDigest" > ".spur/run/$__runId-review-proof.digest"; exit 0'
617
779
 
780
+ # ── review-fail-triage (session finding after 1088) ────────────────────────
781
+ # A review that reports FAIL used to have no outgoing edge: the graph could not see the
782
+ # verdict and nothing consumed it, so remediating a failing review meant driving the
783
+ # states by hand. This state is the review-side twin of `test-fail-triage` (0943 R3):
784
+ # the deterministic classifier routes the failure into the SAME bounded fix budget
785
+ # (`.spur/run/<wbs>-test-fix-attempt` against `qualityGateMaxFixAttempts`), and the fix
786
+ # re-enters quality → review → verify on a freshly captured digest. The `decide` action
787
+ # itself always runs (the engine has no onEnter conditionals); with
788
+ # `workflows.decideDecisionMaker` off it degrades to its declared default.
789
+ - id: review-fail-triage
790
+ display:
791
+ phase: review
792
+ phaseTitle: Review
793
+ title: Triage review findings
794
+ show: on-entry
795
+ description: >-
796
+ Deterministic router for a review that did NOT certify PASS (session finding after 1088).
797
+ `decide review-failure-class` reads the reviewer's answer and the task spec and routes:
798
+ fix → the shared test-fix hop, stop → failed with terminalReason failed-check. A
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.
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.
807
+ - kind: decide
808
+ options:
809
+ id: review-failure-class
810
+ decision: review-failure-class
811
+ params:
812
+ wbs: ${vars.wbs}
813
+ evidence:
814
+ - .spur/run/${vars.__runId}-review-answer.txt
815
+ - ${vars.taskSpecPath}
816
+ resultFile: .spur/run/${vars.wbs}-review-failure-class.decision
817
+
618
818
  - id: approve
819
+ display:
820
+ phase: review
821
+ phaseTitle: Review
822
+ title: Operator approval
823
+ show: on-entry
619
824
  description: >
620
825
  Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
621
826
  (review routes around it straight to verify). In interactive mode this state pauses
@@ -631,9 +836,18 @@ states:
631
836
  mode: never
632
837
 
633
838
  - id: verify
839
+ display:
840
+ phase: verify
841
+ phaseTitle: "Verify & record"
842
+ title: Verify requirements and AC
634
843
  description: >
635
844
  Observe-only functional verification (BDD + traceability) via /sp:dev-verify --fix none
636
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`).
637
851
  A live digest compare BEFORE the agent refuses to certify a state that drifted after the
638
852
  quality/review evidence was produced (R2). The agent's
639
853
  captured answer is written to `.spur/run/<wbs>-verify-answer.txt` and MUST follow
@@ -675,8 +889,28 @@ states:
675
889
  # (d) `spur task verdict` owns the answer lint (task 1003 R2) — the shell below
676
890
  # fails closed when the verifier's answer is malformed, before any verdict artifact.
677
891
  - kind: shell
892
+ # `task verdict` exits 1 on a non-PASS verdict by design (1003 R2). The `continue`
893
+ # policy keeps that exit code as recorded evidence (the action row is still `failed`)
894
+ # while letting the state finish its remaining actions and reach the guards below;
895
+ # without it the default 'fail' policy halts the run here and `verify → test-fix`
896
+ # (the declared bounded remediation lane) can never fire (v5, task 1077 run).
897
+ onError: continue
678
898
  options:
679
899
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
900
+ # Session finding after 1088: the verifier's confidence level becomes a `confidence` check
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.
907
+ - kind: shell
908
+ options:
909
+ command: >-
910
+ V=".spur/run/$wbs-verdict.json"; [ -f "$V" ] || exit 0;
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";
912
+ printf 'confidence: %s (ack=%s)\n' "$(jq -r '.confidence // "LOW (absent)"' "$V")" "$ackLowConfidence" >> ".spur/run/$wbs-test-gate.log";
913
+ exit 0
680
914
  # (e) one jq mutation binds the verdict to the proof digest (0703 R3; runId 0730 §B.2 /
681
915
  # 0757 R4; definitionDigest 0759 R5; honest review stamp 0785 R4; soft action + hard guard).
682
916
  - kind: shell
@@ -685,6 +919,10 @@ states:
685
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
686
920
 
687
921
  - id: record
922
+ display:
923
+ phase: verify
924
+ phaseTitle: "Verify & record"
925
+ title: Record evidence
688
926
  description: >
689
927
  Record pipeline results into the task file via `spur task record` —
690
928
  Testing/Review from the verdict, Solution backfilled from git diff as a
@@ -844,7 +1082,11 @@ transitions:
844
1082
  guard:
845
1083
  kind: shell
846
1084
  options:
847
- 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'
848
1090
  - from: precheck
849
1091
  to: failed
850
1092
  terminalReason: failed-check
@@ -912,9 +1154,11 @@ transitions:
912
1154
  guard:
913
1155
  kind: shell
914
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.
915
1159
  command: >-
916
1160
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
917
- test "$gate_status" = PASS
1161
+ test "$gate_status" = PASS || { test "$gate_status" = DEFERRED && test "$deferQualityGate" = "true"; }
918
1162
  - from: test
919
1163
  to: test-fail-triage
920
1164
  description: Quality gate red — classify the failure before the repair hop (0943 R3).
@@ -945,9 +1189,10 @@ transitions:
945
1189
  guard:
946
1190
  kind: shell
947
1191
  options:
1192
+ # 1111 R2: same var-gated acceptance of the deferred token as `test → triage`.
948
1193
  command: >-
949
1194
  gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
950
- test "$gate_status" = PASS
1195
+ test "$gate_status" = PASS || { test "$gate_status" = DEFERRED && test "$deferQualityGate" = "true"; }
951
1196
  - from: test-recheck
952
1197
  to: test-fail-triage
953
1198
  description: Still red after fixall — classify the failure (0943 R3).
@@ -1018,21 +1263,84 @@ transitions:
1018
1263
  description: Failure-class decision missing/corrupt — stop at failed.
1019
1264
  guard:
1020
1265
  kind: always
1021
- # ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
1022
- # Declaration order matters: the auto-skip guard is tried FIRST, so under
1023
- # profile=auto the run routes straight to verify and never ENTERS approve
1024
- # (whose onEnter hitl.confirm would otherwise block). Otherwise fall through
1025
- # to approve for the human gate.
1266
+ # ── review-failure routing (session finding after 1088): the review-side twin of the
1267
+ # failure-class router above — stop first, then the shared attempt cap, then the fix lane,
1268
+ # then the fail-closed defense. The counter is the SAME one the quality-gate loop uses, so a
1269
+ # review FAIL cannot buy extra attempts and the iteration bound stays finite.
1270
+ - from: review-fail-triage
1271
+ to: failed
1272
+ terminalReason: failed-check
1273
+ description: Review failure class stop — the run terminates at failed, never bypassed.
1274
+ guard:
1275
+ kind: shell
1276
+ options:
1277
+ command: >-
1278
+ jq -e '.value == "stop"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
1279
+ - from: review-fail-triage
1280
+ to: failed
1281
+ terminalReason: retry-exhausted
1282
+ description: Attempt cap reached — qualityGateMaxFixAttempts bounds the review repair lane.
1283
+ guard:
1284
+ kind: shell
1285
+ options:
1286
+ command: >-
1287
+ fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
1288
+ test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
1289
+ - from: review-fail-triage
1290
+ to: test-fix
1291
+ description: Review failure class fix and under the cap — the shared bounded repair hop.
1292
+ guard:
1293
+ kind: shell
1294
+ options:
1295
+ command: >-
1296
+ jq -e '.value == "fix"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
1297
+ # Defense: missing/corrupt review-failure decision — fail closed instead of silently repairing.
1298
+ - from: review-fail-triage
1299
+ to: failed
1300
+ terminalReason: failed-check
1301
+ description: Review-failure decision missing/corrupt — stop at failed.
1302
+ guard:
1303
+ kind: always
1304
+ # ── review routing: FAIL → bounded remediation; PASS → approve or the auto-skip (R4) ──
1305
+ # Declaration order matters and is FAIL-CLOSED: the two PASS edges each demand the reviewer's
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.
1026
1317
  - from: review
1027
1318
  to: verify
1028
- description: profile=auto — skip the human approval gate, go straight to verify.
1319
+ description: profile=auto, the review certified PASS, and no open P1-P3 review finding — skip the human approval gate.
1029
1320
  guard:
1030
1321
  kind: shell
1031
1322
  options:
1032
- command: 'test "$profile" = auto'
1323
+ command: >-
1324
+ test "$profile" = auto &&
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
1033
1328
  - from: review
1034
1329
  to: approve
1035
- description: Review done — gate on human approval.
1330
+ description: Review certified PASS with no open P1-P3 review finding — gate on human approval before verification.
1331
+ guard:
1332
+ kind: shell
1333
+ options:
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
1338
+ - from: review
1339
+ to: review-fail-triage
1340
+ description: >-
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.
1036
1344
  guard:
1037
1345
  kind: always
1038
1346
 
@@ -1069,19 +1377,23 @@ transitions:
1069
1377
  # The verify step emits .spur/run/<wbs>-verdict.json with a required proof block (task 0703 R3/R5).
1070
1378
  # Only `verdict: PASS` PLUS a proof block whose digest — top level and every named stage — equals
1071
1379
  # the captured `proofDigest` clears the gate to `record`; any non-PASS, a missing file, malformed
1072
- # JSON, or missing/mismatched proof evidence does not. Declaration order: PASS+proof guard FIRST,
1380
+ # JSON, or missing/mismatched proof evidence does not. The verifier's confidence level is part of
1381
+ # that certification (session finding after 1088): a PASS the verifier itself marked LOW — or left
1382
+ # unstated — clears the gate only when the operator accepted it with
1383
+ # `--vars '{"ackLowConfidence":"true"}'`. Declaration order: PASS+proof guard FIRST,
1073
1384
  # then the bounded remediation route (R4), then the always catch-all so a PASS verdict with a
1074
1385
  # missing/malformed proof block still terminates at `failed` instead of hanging the state.
1075
1386
  - from: verify
1076
1387
  to: record
1077
- description: Verification verdict is PASS and its proof block names the captured digest on every stage — record results and proceed to done.
1388
+ description: Verification verdict is PASS, its proof block names the captured digest on every stage, and any LOW confidence was operator-acknowledged — record results and proceed to done.
1078
1389
  guard:
1079
1390
  # (one jq -e predicate) PASS verdict; proof, quality-gate, review and verification digests
1080
- # equal to proofDigest; review completed; matching run id and definition digest (0703 R5, 0785 R4).
1391
+ # equal to proofDigest; review completed; matching run id and definition digest (0703 R5, 0785 R4);
1392
+ # confidence HIGH/MEDIUM, or LOW/absent with the operator's ack (session finding after 1088).
1081
1393
  kind: shell
1082
1394
  options:
1083
1395
  command: >-
1084
- jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '
1396
+ jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" --arg ack "$ackLowConfidence" '
1085
1397
  .verdict == "PASS"
1086
1398
  and (.proof.digest // "") == $d
1087
1399
  and (.proof.stages.qualityGate.digest // "") == $d
@@ -1090,6 +1402,7 @@ transitions:
1090
1402
  and (.proof.stages.verification.digest // "") == $d
1091
1403
  and (.proof.runId // "") == $r
1092
1404
  and (.proof.definitionDigest // "") == $dd
1405
+ and ((.confidence // "LOW") != "LOW" or $ack == "true")
1093
1406
  ' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
1094
1407
  - from: verify
1095
1408
  to: test-fix
@@ -1117,11 +1430,12 @@ transitions:
1117
1430
  # ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23)
1118
1431
  # PLUS the proof-block re-assertion (task 0703 R5): the verdict artifact must still be PASS and
1119
1432
  # still name the captured digest — a forged or mutated completion artifact fails closed here.
1433
+ # PLUS the confidence re-assertion (session finding after 1088): the same ack rule as verify →
1434
+ # record, re-read from the artifact so a mutation between the two boundaries cannot slip through.
1120
1435
  # Declaration order: pass guard first.
1121
1436
  - from: record
1122
1437
  to: done
1123
- description: Task check passed and the verdict proof block still names the captured digest — certify done.
1124
- # (warn) 5 commands: named verdict + proof digest reads after the task check (legibility, 0874).
1438
+ description: Task check passed, the verdict proof block still names the captured digest, and any LOW confidence was acknowledged — certify done.
1125
1439
  guard:
1126
1440
  kind: shell
1127
1441
  options:
@@ -1130,11 +1444,16 @@ transitions:
1130
1444
  # the CLI's own target-aware `--no-lifecycle` backstop (F92 R3). No `#`
1131
1445
  # comments inside the folded scalar: `>-` joins same-indent lines into
1132
1446
  # one shell line.
1447
+ # Two logical commands, deliberately (ADR-115): the `task check` projection plus
1448
+ # ONE jq predicate re-asserting the whole completion contract — PASS verdict, the
1449
+ # captured proof digest, and the confidence rule (session finding after 1088).
1133
1450
  command: >-
1134
1451
  $spurBin task check $wbs --as done &&
1135
- verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" &&
1136
- proof_digest="$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
1137
- test "$verdict" = PASS && test "$proof_digest" = "$proofDigest"
1452
+ jq -e --arg p "$proofDigest" --arg ack "$ackLowConfidence" '
1453
+ .verdict == "PASS"
1454
+ and ((.proof.digest // "") == $p)
1455
+ and ((.confidence // "LOW") != "LOW" or $ack == "true")
1456
+ ' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
1138
1457
  - from: record
1139
1458
  to: failed
1140
1459
  terminalReason: failed-check