@gobing-ai/spur 0.4.15 → 0.4.16

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 (101) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +21 -8
  3. package/config/decisions/executor.yaml +53 -0
  4. package/config/decisions/task-pipeline.yaml +44 -28
  5. package/config/workflows/task-pipeline.yaml +66 -54
  6. package/package.json +11 -10
  7. package/plugins/sp/README.md +1 -1
  8. package/plugins/sp/agents/expert-spur.md +3 -0
  9. package/plugins/sp/agents/super-planner.md +5 -1
  10. package/plugins/sp/agents/super-reviewer.md +43 -12
  11. package/plugins/sp/commands/dev-fixall.md +7 -2
  12. package/plugins/sp/commands/dev-idea.md +6 -1
  13. package/plugins/sp/commands/dev-run.md +3 -0
  14. package/plugins/sp/commands/dev-runall.md +6 -3
  15. package/plugins/sp/commands/dev-verify.md +1 -1
  16. package/plugins/sp/plugin.json +1 -1
  17. package/plugins/sp/skills/code-implementation/SKILL.md +8 -8
  18. package/plugins/sp/skills/code-improvement/SKILL.md +3 -3
  19. package/plugins/sp/skills/code-review/references/verification-before-completion.md +5 -4
  20. package/plugins/sp/skills/code-verification/SKILL.md +4 -4
  21. package/plugins/sp/skills/code-verification/references/verdict-schema.md +27 -11
  22. package/plugins/sp/skills/functional-review/SKILL.md +1 -1
  23. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  24. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  25. package/plugins/sp/skills/spur-check/SKILL.md +1 -1
  26. package/plugins/sp/skills/spur-cli/SKILL.md +23 -2
  27. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  28. package/plugins/sp/skills/spur-cli/references/decision/lifecycle.md +166 -0
  29. package/plugins/sp/skills/spur-cli/references/decision.md +146 -16
  30. package/plugins/sp/skills/spur-cli/references/features.md +12 -0
  31. package/plugins/sp/skills/spur-cli/references/rules.md +2 -0
  32. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -0
  33. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  34. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +3 -2
  35. package/plugins/sp/skills/spur-cli/references/workflows.md +9 -0
  36. package/plugins/sp/skills/spur-composer/SKILL.md +20 -1
  37. package/plugins/sp/skills/spur-dev/SKILL.md +16 -16
  38. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +2 -2
  39. package/plugins/sp/skills/spur-dev/references/decision-points.md +97 -0
  40. package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -8
  41. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  42. package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -3
  43. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -4
  44. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +41 -29
  45. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +33 -0
  46. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -3
  47. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  48. package/plugins/sp/skills/spur-dev/references/glossary.md +12 -3
  49. package/plugins/sp/skills/spur-doctor/SKILL.md +23 -0
  50. package/spur.js +7378 -6052
  51. package/web/_astro/{BoardApp.Bjdp3XWV.js → BoardApp.CC7Mwswv.js} +65 -65
  52. package/web/_astro/{BoardApp.DCDZxLlQ.js → BoardApp.mWyrWCAR.js} +1 -1
  53. package/web/_astro/{TaskDetail.B3vBxynO.js → TaskDetail.CtAXuiv8.js} +1 -1
  54. package/web/_astro/{arc.DcuHTB_T.js → arc.Duf4u60i.js} +1 -1
  55. package/web/_astro/{architectureDiagram-3BPJPVTR.ClOFWBON.js → architectureDiagram-3BPJPVTR.JoymtqxN.js} +1 -1
  56. package/web/_astro/{blockDiagram-GPEHLZMM.otYyr7Zb.js → blockDiagram-GPEHLZMM.BvmhcioC.js} +1 -1
  57. package/web/_astro/{c4Diagram-AAUBKEIU.BCjpc9LP.js → c4Diagram-AAUBKEIU.CfaWkgoQ.js} +1 -1
  58. package/web/_astro/channel.DtEPXXcH.js +1 -0
  59. package/web/_astro/{chunk-2J33WTMH.D5Wpnpt_.js → chunk-2J33WTMH.B7uR02cD.js} +1 -1
  60. package/web/_astro/{chunk-4BX2VUAB.DkQGtWNz.js → chunk-4BX2VUAB.SwiEauYR.js} +1 -1
  61. package/web/_astro/{chunk-55IACEB6.B7dqjAip.js → chunk-55IACEB6.6ZRCK8PO.js} +1 -1
  62. package/web/_astro/{chunk-727SXJPM.BDQvnKE2.js → chunk-727SXJPM.Cxra_G3N.js} +1 -1
  63. package/web/_astro/{chunk-AQP2D5EJ.D92doBX5.js → chunk-AQP2D5EJ.Cxd7gZeX.js} +1 -1
  64. package/web/_astro/{chunk-FMBD7UC4.C968oI5G.js → chunk-FMBD7UC4.DCLhFkVc.js} +1 -1
  65. package/web/_astro/{chunk-ND2GUHAM.AkkYC1bY.js → chunk-ND2GUHAM.B9AmKcv6.js} +1 -1
  66. package/web/_astro/{chunk-QZHKN3VN.sCc-rKKo.js → chunk-QZHKN3VN.DOjCvl6S.js} +1 -1
  67. package/web/_astro/{classDiagram-4FO5ZUOK.pOQ9u0mY.js → classDiagram-4FO5ZUOK.BMiT_mNf.js} +1 -1
  68. package/web/_astro/{classDiagram-v2-Q7XG4LA2.pOQ9u0mY.js → classDiagram-v2-Q7XG4LA2.BMiT_mNf.js} +1 -1
  69. package/web/_astro/{cose-bilkent-S5V4N54A.CRaeD6FY.js → cose-bilkent-S5V4N54A.mLBCEGjI.js} +1 -1
  70. package/web/_astro/{cynefin-OW5HDTMX.BiCPBu54.js → cynefin-OW5HDTMX.CjDAY0qn.js} +1 -1
  71. package/web/_astro/{dagre-BM42HDAG.C1-q3vkT.js → dagre-BM42HDAG._G_sP8fP.js} +1 -1
  72. package/web/_astro/{diagram-2AECGRRQ.BSVOv30O.js → diagram-2AECGRRQ.39OcfSuz.js} +1 -1
  73. package/web/_astro/{diagram-5GNKFQAL.CHMkjJ-V.js → diagram-5GNKFQAL.D7v_eNC4.js} +1 -1
  74. package/web/_astro/{diagram-KO2AKTUF.CKY9M564.js → diagram-KO2AKTUF.Da1x-lhg.js} +1 -1
  75. package/web/_astro/{diagram-LMA3HP47.B9yNJiTP.js → diagram-LMA3HP47.C8EnzbEb.js} +1 -1
  76. package/web/_astro/{diagram-OG6HWLK6.Dk8AUGsk.js → diagram-OG6HWLK6.DPnBj69F.js} +1 -1
  77. package/web/_astro/{erDiagram-TEJ5UH35.BQHR89TZ.js → erDiagram-TEJ5UH35.CXMu2TIh.js} +1 -1
  78. package/web/_astro/{flowDiagram-I6XJVG4X.oNkWVgoK.js → flowDiagram-I6XJVG4X.ZklePVtQ.js} +1 -1
  79. package/web/_astro/{ganttDiagram-6RSMTGT7.Kz2U2nu9.js → ganttDiagram-6RSMTGT7.BhYOkdqk.js} +1 -1
  80. package/web/_astro/{gitGraphDiagram-PVQCEYII.DHIZ_wPq.js → gitGraphDiagram-PVQCEYII.CADyX8a1.js} +1 -1
  81. package/web/_astro/{infoDiagram-5YYISTIA.D0NJLcv9.js → infoDiagram-5YYISTIA.BsdmwjHt.js} +1 -1
  82. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CFEfTWRG.js → ishikawaDiagram-YF4QCWOH.CzjZ3Crd.js} +1 -1
  83. package/web/_astro/{journeyDiagram-JHISSGLW.Dc7IE7I4.js → journeyDiagram-JHISSGLW.Bxj-ZH_t.js} +1 -1
  84. package/web/_astro/{kanban-definition-UN3LZRKU.D5v0xO9k.js → kanban-definition-UN3LZRKU.Cbg3wVvX.js} +1 -1
  85. package/web/_astro/{linear.wR5KzXnE.js → linear.DJNUrBI9.js} +1 -1
  86. package/web/_astro/{mermaid.core.BDQ2FoPd.js → mermaid.core.DVHQV3SF.js} +4 -4
  87. package/web/_astro/{mindmap-definition-RKZ34NQL.W6_INsia.js → mindmap-definition-RKZ34NQL.DyomosRU.js} +1 -1
  88. package/web/_astro/{pieDiagram-4H26LBE5.61ihbgkK.js → pieDiagram-4H26LBE5.DajtFknN.js} +1 -1
  89. package/web/_astro/{quadrantDiagram-W4KKPZXB.B5lEpFLo.js → quadrantDiagram-W4KKPZXB.Bjq6SjGe.js} +1 -1
  90. package/web/_astro/{requirementDiagram-4Y6WPE33.CmhVBVX0.js → requirementDiagram-4Y6WPE33.ubBVqGLP.js} +1 -1
  91. package/web/_astro/{sankeyDiagram-5OEKKPKP.D7Vvj9Wf.js → sankeyDiagram-5OEKKPKP.I4q46xd-.js} +1 -1
  92. package/web/_astro/{sequenceDiagram-3UESZ5HK.B1vPLixM.js → sequenceDiagram-3UESZ5HK.DsCgZIa8.js} +1 -1
  93. package/web/_astro/{stateDiagram-AJRCARHV.BnACmrGi.js → stateDiagram-AJRCARHV.DcYjYSqT.js} +1 -1
  94. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CW4pnmc3.js → stateDiagram-v2-BHNVJYJU.Dt8yPqiS.js} +1 -1
  95. package/web/_astro/{timeline-definition-PNZ67QCA.CkzMCPnC.js → timeline-definition-PNZ67QCA.0YTkoa5F.js} +1 -1
  96. package/web/_astro/{vennDiagram-CIIHVFJN.CY4aNdAR.js → vennDiagram-CIIHVFJN.BNl0l1hs.js} +1 -1
  97. package/web/_astro/{wardleyDiagram-YWT4CUSO.BtoU9UFk.js → wardleyDiagram-YWT4CUSO.BYSX6cfn.js} +1 -1
  98. package/web/_astro/{xychartDiagram-2RQKCTM6._9ijUUAs.js → xychartDiagram-2RQKCTM6.DzxxpixK.js} +1 -1
  99. package/web/index.html +1 -1
  100. package/worker/laya_worker.py +219 -0
  101. package/web/_astro/channel.qgOLXjI_.js +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.4.15",
10
+ "version": "0.4.16",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -155,15 +155,28 @@ workflows:
155
155
  # catalogs. `paths` registers extra catalog folders (absolute or
156
156
  # project-relative); the first layer wins per file basename (ADR-113 style).
157
157
  # `maker` selects the default DecisionMaker for every decision point, and
158
- # `makers` overrides it per decision id. Built-in makers: typesafe, fm-local,
159
- # laya-local (laya-local needs Apple Silicon) and clef (hosted Cloudflare
160
- # Workers AI, registered only when CLOUDFLARE_ACCOUNT_ID and
161
- # CLOUDFLARE_AUTH_TOKEN are both set). An unregistered name is a config error,
162
- # never a silent fallback. Inspect the effective choice with
163
- # `spur decision status`. `init` never overwrites this file — until you add the
164
- # keys, the shipped catalog's maker applies (see `status`).
158
+ # `makers` overrides it per decision id. The value is any maker name the
159
+ # registry currently registers — the list lives in the CLI, not here, so
160
+ # `spur decision status` (registeredMakers) is the authority, together with each
161
+ # maker's own prerequisites (a local maker needs its local stack; a hosted maker
162
+ # registers only when its credentials are set). An unregistered name is a config
163
+ # error, never a silent fallback. Inspect the effective choice per decision with
164
+ # the same command. `init` never overwrites this file — until you add the keys,
165
+ # the shipped catalog's maker applies (see `status`).
165
166
  decisions:
166
167
  paths: []
167
168
  maker: typesafe
168
169
  # makers:
169
- # task-triage: laya-local
170
+ # task-semantic-risk: laya-local
171
+ # What "the decision was right" means (task 1263 R5): per decision id, the recorded outcomes
172
+ # that count as correct for each served value. `String(value)` keys, so a yes/no decision uses
173
+ # "true"/"false". The outcome verb records them (`spur decision outcome <invocation-id> ...`);
174
+ # `spur decision status --reliability` scores accuracy against this map and reports null for a
175
+ # decision with no entry, rather than guessing.
176
+ outcomeMap:
177
+ failure-class:
178
+ fix: [fixed]
179
+ stop: [not-fixed]
180
+ task-semantic-risk:
181
+ "false": [none, p4, p3]
182
+ "true": [p1, p2]
@@ -0,0 +1,53 @@
1
+ $schema: "@gobing-ai/spur/schemas/decision-catalog.schema.json"
2
+ # Executor-failure rescue catalog (feature P1, task 1264).
3
+ #
4
+ # The executor loop escalates on `FAILURE_RULES` matches only (0407 precision bias). Anything the
5
+ # rules miss — a reworded rate limit, "credits depleted", a proxy error body — ends the run as a
6
+ # task defect. This decision classifies ONLY that residue, after the rules and the terminal checks
7
+ # (timeout, permission denial, confirmed quota) have had their say, and only while
8
+ # `workflows.executorFailureDecisionMaker` is on. It ships off by default: shipping off routes
9
+ # nothing, and turning it on is an operator call backed by replay evidence in task 1264's Q&A.
10
+ #
11
+ # Resolution: shared layer, overridable per basename through `.spur/decisions/` or
12
+ # `decisions.paths` (first layer wins, ADR-113 style).
13
+ version: 1
14
+
15
+ defaults:
16
+ # Higher than the catalog floor: a false `resource-exhaustion` costs a failover attempt, and a
17
+ # false `task-defect` equals today's behaviour — so the uncertainty must resolve downward.
18
+ minConfidence: 0.85
19
+
20
+ decisions:
21
+ executor-failure-class:
22
+ type: choice
23
+ description: Is an unmatched agent-dispatch failure resource exhaustion, an auth failure, or a task defect?
24
+ instructions: |
25
+ You are classifying why one agent dispatch exited non-zero, after every deterministic
26
+ rule already declined to classify it.
27
+ Failure output: ${params.instructions}
28
+ Answer `resource-exhaustion` only when the output states a provider-side capacity or
29
+ billing limit: a quota/credit/billing message, a 429 or rate-limit body, a token or
30
+ request budget, or an "overloaded"/"capacity" error. Answer `auth` only when the
31
+ output states a credential or authorization fault: an invalid/expired key or token, a
32
+ 401/403 body, or a missing credential for the provider. Answer `task-defect` for
33
+ everything else: a compile or test failure, a missing file, a malformed prompt, a
34
+ socket/proxy error, or output that does not name a provider limit at all.
35
+ Classify only what the output says. Do not infer a vendor, and do not guess.
36
+ parameters:
37
+ wbs:
38
+ type: string
39
+ description: Task WBS the failing dispatch belongs to, when known.
40
+ # Optional: the executor seam knows only the run id, and a required param would
41
+ # reject every rescue before it reached a maker.
42
+ default: ""
43
+ criteria:
44
+ resource-exhaustion: >
45
+ The output names a provider-side limit — quota, credits, billing period, rate
46
+ limit, 429, token budget, or capacity.
47
+ auth: >
48
+ The output names a credential or authorization fault — invalid or expired key or
49
+ token, 401/403, or a missing credential.
50
+ task-defect: >
51
+ The output names no provider limit or credential fault — a code/test/file error, a
52
+ prompt problem, or a transport error that is not a provider quota.
53
+ fallback: task-defect
@@ -1,11 +1,11 @@
1
1
  $schema: "@gobing-ai/spur/schemas/decision-catalog.schema.json"
2
- # Decision catalogs for the task pipeline (feature P / task 1092, ADR-134).
2
+ # Decision catalogs for the task pipeline (feature P1, ADR-134).
3
3
  #
4
- # These entries mirror the inline decide actions that ship in
5
- # config/workflows/task-pipeline.yaml today — same questions, choices and
6
- # fallbacks — so their reliability can be measured with `spur decision run`
7
- # before feature P1 migrates the workflow actions onto them (design §3.5).
8
- # No workflow references this catalog yet.
4
+ # The shipped `config/workflows/task-pipeline.yaml` decide actions reference these ids
5
+ # (`failure-class`, and `task-semantic-risk` once its evidence bar is met). Each entry's
6
+ # reliability is measured from recorded `decision.*` events — `spur decision status
7
+ # --reliability` — and an adoption slice only keeps a decision point while that evidence
8
+ # shows it adds information the deterministic guards do not.
9
9
  #
10
10
  # Resolution: this file ships in the shared layer (the installed package's
11
11
  # config/decisions/). A project can override it per basename through
@@ -17,39 +17,55 @@ defaults:
17
17
  minConfidence: 0.8
18
18
 
19
19
  decisions:
20
- task-triage:
21
- type: choice
22
- description: Which verification lane does a green-gate diff deserve?
20
+ task-semantic-risk:
21
+ type: noul
22
+ description: >
23
+ Does this task's change set change behaviour a reviewer must reason about beyond what
24
+ the diffstat already shows? (false projects the fast lane, true or a fallback the
25
+ standard lane.)
26
+ instructions: |
27
+ You are classifying one Spur task's change set for review depth.
28
+ Change set: ${params.instructions}
29
+ Answer true when the change alters behaviour a reviewer must reason about: control
30
+ flow, a data shape, persistence, security, an external contract, or error handling.
31
+ Answer false only when the change is mechanical — docs, comments, formatting, renames,
32
+ test-only edits, or a dependency bump with no source edit.
33
+ Judge the change set only; do not score its quality or estimate its size.
23
34
  parameters:
24
35
  wbs:
25
36
  type: string
26
- description: Task WBS the diff belongs to.
37
+ description: Task WBS whose change set is being classified.
27
38
  criteria:
28
- low: Small, contained diff — the fast lane is enough.
29
- standard: Ordinary diff — the standard verification lane applies.
30
- high: Broad or risky diff — verify with high caution.
31
- fallback: standard
39
+ "true": >
40
+ The change set touches executable behaviour — a branch, a boundary, a stored
41
+ schema, a public signature, or a failure path — so a reviewer must read the diff,
42
+ not only its diffstat.
43
+ "false": >
44
+ The change set is mechanical — docs, comments, formatting, renames, test-only
45
+ edits, or a version bump with no executable change — so the diffstat is enough.
46
+ fallback: true
32
47
 
33
48
  failure-class:
34
49
  type: choice
35
50
  description: Is a failed quality gate fixable by the repair agent, or should the run stop?
51
+ instructions: |
52
+ You are classifying one failed quality-gate run for Spur's task pipeline.
53
+ Gate output: ${params.instructions}
54
+ Answer `fix` when the output names a concrete defect inside this repository that the
55
+ repair agent can act on: a failing test with an expected/actual diff, a type error
56
+ naming a file and symbol, a lint rule id with a file:line, or a missing export or
57
+ import. Answer `stop` when the failure is environmental or not repairable by editing
58
+ this tree: a missing tool or binary, a permission or disk error, a network or registry
59
+ failure, a gate timeout, or output that names no in-tree file.
36
60
  parameters:
37
61
  wbs:
38
62
  type: string
39
63
  description: Task WBS whose gate failed.
40
64
  criteria:
41
- fix: The failure looks fixable — send it to the repair agent.
42
- stop: The failure is not mechanically fixable — stop the run for the operator.
43
- fallback: fix
44
-
45
- review-failure-class:
46
- type: choice
47
- description: Is a non-PASS review fixable by the repair agent, or should the run stop?
48
- parameters:
49
- wbs:
50
- type: string
51
- description: Task WBS whose review did not certify PASS.
52
- criteria:
53
- fix: The findings look fixable — send them to the repair agent.
54
- stop: The findings are not mechanically fixable — stop the run for the operator.
65
+ fix: >
66
+ The output names an in-tree defect — a failing test, a type error with a file and
67
+ symbol, a lint rule id with a file:line, or a missing import.
68
+ stop: >
69
+ The output names no in-tree defect — a missing tool, a permission or disk error,
70
+ a network or registry failure, or a timeout — so editing the tree cannot fix it.
55
71
  fallback: fix
@@ -396,7 +396,7 @@ states:
396
396
  # (probe-then-full), `deferred` under the opt-in batch policy.
397
397
  #
398
398
  # The three former routing states are decision CALLS here and in `review` (D65 1239 R1):
399
- # the lane decision (0943 R1/R2, catalog `task-triage`) picks the review depth, and the
399
+ # the lane decision (0943 R1/R2, catalog `task-semantic-risk`, 1262 R5) picks the review depth, and the
400
400
  # gate's own failure class (catalog `failure-class`) drives the red-gate transitions.
401
401
  # Both run unconditionally — the engine has no onEnter conditionals — and both degrade to
402
402
  # their catalog fallback with no model call while `workflows.decideDecisionMaker` is off.
@@ -407,7 +407,7 @@ states:
407
407
  title: Run quality gate
408
408
  description: >
409
409
  Typed quality gate: resolve the task path/priority, produce the diffstat lane
410
- evidence, consult `task-triage` for the review depth (deterministic safety floor
410
+ evidence, consult `task-semantic-risk` for the review depth (deterministic safety floor
411
411
  first), run `${vars.qualityGateCmd}` through `spur task gate <wbs> <mode>` under the
412
412
  host-wide lock (tree-hash receipt reuse on the same tree), then classify a red gate
413
413
  through `failure-class`. Writes the typed outcome to
@@ -446,27 +446,30 @@ states:
446
446
  options:
447
447
  command: >-
448
448
  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
449
- # (d) lane decision — degrades to the standard default whenever
450
- # workflows.decideDecisionMaker is off, so with the flag off the pipeline makes no
451
- # model call (1114/1116: the bundled catalog owns question, choices and fallback).
449
+ # (d) lane decision (1262 R5) — one narrow question: does this change set alter
450
+ # behaviour beyond its diffstat? `false` projects the fast lane; `true` or the catalog
451
+ # fallback projects standard. Degrades to that fallback with no model call whenever
452
+ # workflows.decideDecisionMaker is off (the bundled catalog owns question, criteria and
453
+ # fallback), and `skipWhenEvidenceEmpty` keeps a missing diffstat from costing a call.
452
454
  - kind: decide
453
455
  options:
454
- id: task-triage
455
- decision: task-triage
456
+ id: task-semantic-risk
457
+ decision: task-semantic-risk
456
458
  params:
457
459
  wbs: ${vars.wbs}
458
460
  evidence:
459
461
  - .spur/run/${vars.wbs}-diffstat.json
460
462
  - ${vars.taskSpecPath}
461
- resultFile: .spur/run/${vars.wbs}-triage.decision
462
- # (d) project the decision and append the triage route reason (1007 R3: the
463
- # non-empty mode file is never overridden; the append keeps the failure signal of the
464
- # original pair). low → fast lane; anything else (standard/high, degraded default,
465
- # missing row) stays empty → the standard review path.
463
+ skipWhenEvidenceEmpty: true
464
+ resultFile: .spur/run/${vars.wbs}-semantic-risk.decision
465
+ # (d) project the decision and append the route reason (1007 R3: the non-empty mode
466
+ # file is never overridden; the append keeps the failure signal of the original pair).
467
+ # `no` (a noul row stores the label, not a boolean) → fast lane; anything else (`yes`,
468
+ # degraded default, missing row) stays empty → the standard review path.
466
469
  - kind: shell
467
470
  options:
468
471
  command: >-
469
- MF=".spur/run/$wbs-mode.txt"; [ -s "$MF" ] || jq -r 'if .value == "low" then "fast" else empty end' ".spur/run/$wbs-triage.decision" 2>/dev/null > "$MF"; m="$(cat "$MF")"; RUN_ID="$__runId"; [ -n "$RUN_ID" ] || RUN_ID="pipeline-$wbs"; mkdir -p .spur/memory; printf '%s %s %s\n' "$RUN_ID" "$wbs" "$(jq -rn --arg m "$m" --arg c "$mode" 'if $c != "" then ($c + ":caller-set") else {"fast":"fast:triage low lane","safety":"safety:triage deterministic-high","":"safety:triage standard lane"}[$m] // "safety:triage unrecognized mode" end')" >> .spur/memory/task-pipeline-routes.log
472
+ MF=".spur/run/$wbs-mode.txt"; [ -s "$MF" ] || jq -r 'if (.value == false or .value == "no") then "fast" else empty end' ".spur/run/$wbs-semantic-risk.decision" 2>/dev/null > "$MF"; m="$(cat "$MF")"; RUN_ID="$__runId"; [ -n "$RUN_ID" ] || RUN_ID="pipeline-$wbs"; mkdir -p .spur/memory; printf '%s %s %s\n' "$RUN_ID" "$wbs" "$(jq -rn --arg m "$m" --arg c "$mode" 'if $c != "" then ($c + ":caller-set") else {"fast":"fast:semantic-risk mechanical change","safety":"safety:deterministic-high","":"safety:standard lane"}[$m] // "safety:unrecognized mode" end')" >> .spur/memory/task-pipeline-routes.log
470
473
  # (d) review depth from the resolved lane (D65 1239 R2): the fast lane reviews the
471
474
  # requirement/AC traceability only — that table is the certification input, so the
472
475
  # cheap lane never drops the evidence, it drops the SECUA/architecture passes.
@@ -515,6 +518,14 @@ states:
515
518
  args: ["task", "gate", "${vars.wbs}", "${vars.gateMode}", "--no-logo"]
516
519
  resultFile: .spur/run/${vars.__runId}-gate.status
517
520
  softFail: true
521
+ # (d) record what the PREVIOUS gate visit's failure-class answer turned out to be
522
+ # (1263 R3a): a recheck visit means the last class was `fix` and the repair hop ran, so
523
+ # this visit's gate status says whether it was right. First write wins, the step never
524
+ # changes routing, and a missing invocationId (degraded row) or a first visit is a no-op.
525
+ - kind: shell
526
+ options:
527
+ command: >-
528
+ [ "$gateMode" = recheck ] || exit 0; ID="$(jq -r '.invocationId // empty' ".spur/run/$wbs-failure-class.decision" 2>/dev/null)"; [ -n "$ID" ] || exit 0; $spurBin decision outcome "$ID" --gate-status "$__runId" --source gate-recheck --no-logo >/dev/null 2>&1; exit 0
518
529
  # (d) failure class of the gate this visit produced (0943 R3): stop → failed
519
530
  # before the cap can mislabel it, fix → the bounded remediation hop. A missing or
520
531
  # corrupt row fails closed to failed-check. Runs unconditionally (no onEnter
@@ -528,6 +539,9 @@ states:
528
539
  wbs: ${vars.wbs}
529
540
  evidence:
530
541
  - .spur/run/${vars.wbs}-test-gate.findings
542
+ # 1262 R4: a green gate writes a 0-byte findings file — there is nothing to read,
543
+ # so the row degrades to the catalog `fix` fallback with no maker call and no events.
544
+ skipWhenEvidenceEmpty: true
531
545
  resultFile: .spur/run/${vars.wbs}-failure-class.decision
532
546
 
533
547
  # ── remediate hop (bounded repair; former `test-fix`) ─────────────────────
@@ -557,15 +571,17 @@ states:
557
571
  options:
558
572
  command: >-
559
573
  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-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
560
- # Review-lane input: the review failure class routes a non-PASS review into this same
561
- # bounded repair hop, but it stages no gate evidence — the fixer would receive a GREEN
562
- # gate log and an empty findings list. Project the reviewer's answer into the same log
563
- # the other lane uses, and only on the review lane (the decision file exists only
564
- # after a review FAIL). Own action, not an append to the one above (ADR-115 cap).
574
+ # Review-lane input: a non-PASS review status routes into this same bounded repair hop
575
+ # (1262 R6), but it stages no gate evidence — the fixer would receive a GREEN gate log
576
+ # and an empty findings list. Project the reviewer's answer into the same log the other
577
+ # lane uses, and only on the review lane: remediate after a GREEN gate this run means
578
+ # review sent it. The review status file is no lane marker — it outlives its review, so
579
+ # a red recheck after a review repair would re-feed that stale answer.
580
+ # Own action, not an append to the one above (ADR-115 cap).
565
581
  - kind: shell
566
582
  options:
567
583
  command: >-
568
- [ -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
584
+ [ "$(cat ".spur/run/$__runId-gate.status" 2>/dev/null)" = PASS ] && [ -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
569
585
  # R3 (0482): project the extracted gate anchors into a var so the dispatch input
570
586
  # can NAME the failing file:line, not merely point at a log. A vars template cannot
571
587
  # run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
@@ -630,21 +646,29 @@ states:
630
646
  # spec than the implement stage recorded in __agentRouting_implement.
631
647
  # 0943 R1/R2: `--focus` carries the resolved lane depth — `functional` for the
632
648
  # fast lane (traceability, which is the certification input), `all` otherwise.
633
- # D65 1240: this answer IS the certification input — `spur task verdict --from-answer`
649
+ # D65 1240 + 1254: this answer IS the certification input — `spur task verdict --from-answer`
634
650
  # derives the run's verdict from its requirement rows, so the answer contract is part
635
651
  # of the prompt. A report body ending with a `| WBS | Verdict | P1 | … |` summary and
636
652
  # no per-requirement rows derives UNKNOWN and can never certify, which looped every
637
653
  # run through review -> remediate.
654
+ # 1254: the review emits TWO artifacts with different consumers — the `## Review`
655
+ # section (finding dispositions, deriveReviewConfidence) and this captured answer
656
+ # (requirement/AC evidence, deriveVerifyConfidence). The prompt has to teach both
657
+ # lanes and the fact that a single `Confidence:` line may only carry the second one.
658
+ # 1254: the prompt stays inside the 1000-char composition cap — the full
659
+ # contract (tables, vocabularies, the two confidence lanes, AC identity vs
660
+ # feature credit) is owned by `plugins/sp/agents/super-reviewer.md` Output Format,
661
+ # which this pointer names.
638
662
  input: >-
639
- /sp:dev-review --tasks ${vars.wbs} --auto --focus ${vars.reviewFocus}
640
- Answer contract (machine-read): end with exactly one plain `Verdict: PASS|FAIL`
641
- line, preceded by a `| Req | Status | Evidence |` table with one row per
642
- requirement (R1, R2, ...) and a `| AC | Status | Evidence Type | Evidence |` table
643
- with one row per acceptance criterion, and one `Confidence: HIGH|MEDIUM|LOW` line
644
- whose level YOUR finding dispositions derive (ADR-141: no open P1-P3 and no
645
- carried residual is HIGH; a carried residual is MEDIUM; an open P1-P3 or a
646
- non-PASS verdict is LOW) — the gate recomputes it and rejects a disagreement.
647
- Every row cites a repo-relative `path:line`; no row may be omitted or merged.
663
+ /sp:dev-review --tasks ${vars.wbs} --auto --focus ${vars.reviewFocus} Answer contract (full: super-reviewer Output
664
+ Format). Two artifacts: the `## Review` section (dispositions, deriveReviewConfidence) and your captured answer
665
+ (certification input for `spur task verdict`). The answer has one `Confidence: HIGH|MEDIUM|LOW` line, EVIDENCE-derived
666
+ (deriveVerifyConfidence), never the section's disposition level. End with one `Verdict: PASS|PARTIAL|FAIL` line
667
+ preceded by `| Req | Status | Evidence |` (one row per requirement; MET | PARTIAL | UNMET) and `| AC | Status |
668
+ Evidence Type | Evidence |` (one row per AC; MET | PARTIAL | UNMET | N/A). Evidence types (`+` compound allowed): test
669
+ | command | static-ref | manual-review | llm-judge | n/a. Evidence cells cite `path:line` or a named check. AC rows
670
+ key on the task's identities or a declared `AC-N` alias; a linked scenario covers its task and a task-local criterion
671
+ needs no feature credit. Open P1-P3 blocks completion.
648
672
  # The captured review answer is the run's certification input (and the
649
673
  # `task verdict` input below): expectFile fails the step when no answer landed.
650
674
  answerFile: .spur/run/${vars.__runId}-review-answer.txt
@@ -681,21 +705,14 @@ states:
681
705
  options:
682
706
  command: >-
683
707
  mkdir -p .spur/run; S=FAIL; if [ "$(cat ".spur/run/$__runId-review-verdict.status" 2>/dev/null)" = PASS ] && [ "$(jq -r '.verdict // empty' ".spur/run/$wbs-verdict.json" 2>/dev/null)" = PASS ] && $spurBin task residual "$wbs" review-gate --no-logo >&2; then S=PASS; fi; printf '%s\n' "$S" > ".spur/run/$wbs-review.status"; exit 0
684
- # (d) review failure class (session finding after 1088): stop → failed before the cap
685
- # can mislabel it, fix → the SAME bounded repair hop the red gate uses, so the
686
- # iteration bound stays finite across both lanes. Degrades to the catalog `fix`
687
- # fallback with no model call while the decision switch is off; a missing/corrupt row
688
- # fails closed to failed-check.
689
- - kind: decide
708
+ # (d) record the review severity as the lane decision's outcome (1263 R3b): the
709
+ # question `task-semantic-risk` answered is "is this risky?", and the Review table's
710
+ # worst finding is the answer as observed. Resolved rows count (a fixed P2 was still
711
+ # risky). First write wins, so a later review visit changes nothing.
712
+ - kind: shell
690
713
  options:
691
- id: review-failure-class
692
- decision: review-failure-class
693
- params:
694
- wbs: ${vars.wbs}
695
- evidence:
696
- - .spur/run/${vars.__runId}-review-answer.txt
697
- - ${vars.taskSpecPath}
698
- resultFile: .spur/run/${vars.wbs}-review-failure-class.decision
714
+ command: >-
715
+ [ -f ".spur/run/$wbs-semantic-risk.decision" ] || exit 0; ID="$(jq -r '.invocationId // empty' ".spur/run/$wbs-semantic-risk.decision" 2>/dev/null)"; [ -n "$ID" ] || exit 0; $spurBin decision outcome "$ID" --review-severity "$wbs" --source "task-pipeline review" --no-logo >/dev/null 2>&1; exit 0
699
716
  # (d) Operator approval as a DECISION POINT, not a state (D65 1239 R1/R5, ADR-140
700
717
  # item 5): interactively the operator responder answers; under `profile=auto` the
701
718
  # declared binding answers (evidence status var, catalog fallback when the maker
@@ -1010,15 +1027,6 @@ transitions:
1010
1027
  kind: shell
1011
1028
  options:
1012
1029
  command: 'test "$(cat ".spur/run/$wbs-approval.status" 2>/dev/null)" = DENIED'
1013
- - from: review
1014
- to: failed
1015
- terminalReason: failed-check
1016
- description: Review failure class stop — the run terminates at failed, never bypassed.
1017
- guard:
1018
- kind: shell
1019
- options:
1020
- command: >-
1021
- jq -e '.value == "stop"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
1022
1030
  - from: review
1023
1031
  to: failed
1024
1032
  terminalReason: retry-exhausted
@@ -1031,12 +1039,16 @@ transitions:
1031
1039
  test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
1032
1040
  - from: review
1033
1041
  to: remediate
1034
- description: Review failure class fix and under the cap — the shared bounded repair hop, then gate → review on the remediated tree.
1042
+ description: Review did not certify and the attempt cap still allows a repair — the shared bounded repair hop, then gate → review on the remediated tree.
1035
1043
  guard:
1036
1044
  kind: shell
1037
1045
  options:
1046
+ # 1262 R6: no decision row and no model call. The review status file is the whole
1047
+ # signal — it exists and is not PASS — and the cap guard above already bounds the
1048
+ # loop, so a non-PASS review always has exactly one forward route.
1038
1049
  command: >-
1039
- jq -e '.value == "fix"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
1050
+ test -f ".spur/run/$wbs-review.status" &&
1051
+ test "$(cat ".spur/run/$wbs-review.status" 2>/dev/null)" != PASS
1040
1052
  # Defense: a review that did not certify and no clearer route — fail closed instead of
1041
1053
  # dead-ending the run (a FAIL, PARTIAL, missing or unparseable certification lands here).
1042
1054
  - from: review
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -32,6 +32,7 @@
32
32
  "files": [
33
33
  "spur.js",
34
34
  "config",
35
+ "worker",
35
36
  "schemas",
36
37
  "board",
37
38
  "web",
@@ -49,7 +50,7 @@
49
50
  "dev": "bun run src/index.ts",
50
51
  "build": "bun run ../../scripts/spur-dev.ts build-cli",
51
52
  "build:binaries": "bun run ../../scripts/spur-dev.ts build-binaries",
52
- "build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-plugins",
53
+ "build:bundle": "bun run ../../scripts/spur-dev.ts bundle-web && bun run ../../scripts/spur-dev.ts emit-board-types && bun build src/index.ts --target=bun --outfile spur.js --external @johnhenry/backend-webgpu && bun run ../../scripts/spur-dev.ts bundle-config config && bun run ../../scripts/spur-dev.ts bundle-laya-worker && bun run ../../scripts/spur-dev.ts bundle-plugins",
53
54
  "test": "bun test tests --coverage --coverage-dir=../../.coverage --pass-with-no-tests",
54
55
  "typecheck": "tsc --noEmit",
55
56
  "prepack": "bun run build:bundle",
@@ -57,14 +58,14 @@
57
58
  },
58
59
  "devDependencies": {
59
60
  "@commander-js/extra-typings": "^14.0.0",
60
- "@gobing-ai/ts-db": "^0.5.30",
61
- "@gobing-ai/ts-ai-runner": "^0.5.30",
62
- "@gobing-ai/ts-dual-workflow-engine": "^0.5.30",
63
- "@gobing-ai/ts-infra": "^0.5.30",
64
- "@gobing-ai/ts-llm-jsonl-importer": "^0.5.30",
65
- "@gobing-ai/ts-rule-engine": "^0.5.30",
66
- "@gobing-ai/ts-runtime": "^0.5.30",
67
- "@gobing-ai/ts-utils": "^0.5.30",
61
+ "@gobing-ai/ts-db": "^0.5.32",
62
+ "@gobing-ai/ts-ai-runner": "^0.5.32",
63
+ "@gobing-ai/ts-dual-workflow-engine": "^0.5.32",
64
+ "@gobing-ai/ts-infra": "^0.5.32",
65
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.5.32",
66
+ "@gobing-ai/ts-rule-engine": "^0.5.32",
67
+ "@gobing-ai/ts-runtime": "^0.5.32",
68
+ "@gobing-ai/ts-utils": "^0.5.32",
68
69
  "@types/bun": "1.4.3",
69
70
  "@types/figlet": "^1.7.0",
70
71
  "@types/node-notifier": "8.0.5",
@@ -29,7 +29,7 @@ One feature, start to finish — see the Command index below for exact command n
29
29
  ```
30
30
  idea/plan vague description → feature + AC + decomposed task batch
31
31
  ↓
32
- run <wbs> → pipeline: precheck → implement → test → review → approve(HITL) → verify → record → done
32
+ run <wbs> → pipeline: precheck → implement → gate ⇄ remediate → review → done (decision points route repair, stop and approval)
33
33
  ↓
34
34
  verify <wbs> → traceability + AC verdict (PASS clears the gate; this also runs inside the run step)
35
35
  ↓
@@ -42,6 +42,9 @@ Use for:
42
42
  ladder, trace-driven rule tuning, and applying accepted doctor proposals.
43
43
  - Evaluation and reflection campaigns via `sp:spur-doctor`: read-only evidence, history-finding
44
44
  reflection, and a proposal table.
45
+ - Decision campaigns: catalog health and reliability audits (`spur decision status --json`,
46
+ `--reliability`, `--probe`) through `sp:spur-doctor`'s decision flags, and new or tuned decision
47
+ points through the decision lifecycle and `sp:spur-composer` (shared ids need operator consent).
45
48
 
46
49
  One bounded campaign per dispatch. Do not use for one CLI invocation. Do not use for
47
50
  planning→implementation→verification lifecycle or batch task execution; `sp:spur-dev` owns that
@@ -89,6 +89,9 @@ You own the spaces **between** task runs:
89
89
  timeout prints one checkpoint and exits 1; the run continues, never cancelled or relaunched) plus
90
90
  `.spur/run/<wbs>-verdict.json`, accepted only when the trace's `.run.runId` equals the dispatched
91
91
  run and the verdict's mtime ≥ the trace's `.run.startedAt` — otherwise `stale-evidence` (Step 3.3).
92
+ A `failed` run's cause is its decision row (`failure-class` / `review-failure-class` = `stop`, or
93
+ approval `DENIED`); report it per [reporting a run](../skills/spur-dev/references/decision-points.md#reporting-a-run)
94
+ and do not re-judge it before recovery.
92
95
  - **One-shot recovery (optional)** - on non-PASS / stuck status, consult next-router **once** for that
93
96
  WBS (`recoveryHint` / dry-run plan): print the child command, or dispatch once only when the batch
94
97
  was started with `--auto` and cardinality is 1. Never self-loop until done.
@@ -135,7 +138,8 @@ Your decision-autonomy is **at the batch level**, bounded by the global Decision
135
138
  | Continue/halt between task runs | Whether to auto-approve a HITL gate inside a task (only `--auto` does, via `vars.profile`) |
136
139
 
137
140
  Surface blockers/HITL **only at the batch boundary** (between task runs). A pipeline run that pauses
138
- on its internal HITL `approve` gate (under the default profile) is surfaced to the operator as a
141
+ on its `review`-state approval decision point (interactive profiles only; under `profile=auto` the
142
+ decision point answers itself, and a deferred answer is no objection) is surfaced to the operator as a
139
143
  batch-level event: "task 0042 is awaiting approval - `spur workflow continue <run-id> --yes` to
140
144
  approve, or provide feedback". You do not answer the gate from inside a step.
141
145
 
@@ -70,8 +70,10 @@ Only when invoked under the pipeline do blocker/major findings block a gate.
70
70
 
71
71
  Invoked by `task-pipeline.yaml`'s `review` step. The pipeline hands you the task WBS and the
72
72
  `--focus` dimensions. You run the review, write findings to the task's `## Review` section, and
73
- return a PASS/PARTIAL/FAIL verdict to the pipeline's `approve(HITL)` gate. `blocker`/`major`
74
- findings block the gate; `minor`/`advisory` are recorded but do not block.
73
+ return a PASS/PARTIAL/FAIL verdict. `spur task verdict` certifies the answer; a PASS then reaches the
74
+ approval decision point before `done`, and a non-PASS feeds the `review-failure-class` decision
75
+ (`fix` → `remediate`, `stop` → `failed`). `blocker`/`major` findings block certification;
76
+ `minor`/`advisory` are recorded but do not block.
75
77
 
76
78
  ### Multi-target coordination (task 1023)
77
79
 
@@ -181,11 +183,11 @@ with the commit) drops the row from the sweep; `DEFER(<reason>)` reclassifies a
181
183
  the cell, not the sentence. `OPEN` states "still live", and `ACCEPTED` marks a non-blocking P4
182
184
  observation you are deliberately not acting on; both behave as an ordinary open row.
183
185
 
184
- **Pipeline cost of an open finding (1122).** The review PASS edges run this same classification
185
- before verify (`spur task residual <wbs> review-gate`): a PASS whose table still carries an open P1-P3 row
186
- routes into `review-fail-triage` → the bounded repair hop instead of verify. Open P1-P3 findings
187
- block `done`; fixing one invalidates the certified digest and re-runs quality → review → verify on
188
- a fresh digest. Only P4 rows and `DEFER`-ed P3 rows are wrap residuals — disposition the finding in
186
+ **Pipeline cost of an open finding (1122).** The review certification runs this same classification
187
+ (`spur task residual <wbs> review-gate`): a PASS whose table still carries an open P1-P3 row is a
188
+ non-PASS review, so the `review-failure-class` decision routes it to `remediate` instead of `done`.
189
+ Open P1-P3 findings block `done`; fixing one invalidates the certified digest and re-runs
190
+ gate → review on a fresh digest. Only P4 rows and `DEFER`-ed P3 rows are wrap residuals — disposition the finding in
189
191
  its cell now, while you hold the context, instead of leaving the cost to be rediscovered after a
190
192
  full cycle.
191
193
 
@@ -220,9 +222,9 @@ shallower.
220
222
  ```
221
223
 
222
224
  The **Verdict line is machine-read** by the task pipeline (session finding after 1088): `review →
223
- verify|approve` requires a line matching `Verdict: PASS` (plain or bold), and ANYTHING ELSE —
224
- FAIL, PARTIAL, a missing line, a line split across two lines — routes the run into
225
- `review-fail-triage` and its bounded repair hop. Keep it on one line, in that shape, in every
225
+ done` requires a line matching `Verdict: PASS` (plain or bold), and ANYTHING ELSE —
226
+ FAIL, PARTIAL, a missing line, a line split across two lines — routes the run through the
227
+ `review-failure-class` decision into the bounded `remediate` hop. Keep it on one line, in that shape, in every
226
228
  review answer; never restate it as prose only.
227
229
 
228
230
  **State `Confidence: HIGH | MEDIUM | LOW` — the gate derives it from your dispositions (ADR-141).**
@@ -243,6 +245,35 @@ No verdict-artifact field, no receipt level, no per-finding grade, and no operat
243
245
  (ADR-138 stands for every other carrier). Evidence gaps still belong in the report body: name them,
244
246
  then let the dispositions decide the level.
245
247
 
248
+ **Two artifacts, one `Confidence:` line each (1254).** The body you write into `### Review` states
249
+ the disposition-derived level above. When the pipeline also captures your final answer as the
250
+ certification input for `spur task verdict`, that text is a *second* artifact with a *different*
251
+ consumer, and it carries the **evidence-derived** level (`deriveVerifyConfidence`): PASS with every
252
+ AC row MET on executable evidence (`test`/`command`) is HIGH; a citation-only (`static-ref`),
253
+ `manual-review`, `llm-judge` or `N/A` AC is MEDIUM; a non-PASS verdict or any PARTIAL/UNMET row is
254
+ LOW. It ends with exactly one plain `Verdict: PASS|PARTIAL|FAIL` line preceded by both tables, in
255
+ this exact shape:
256
+
257
+ | Req | Status | Evidence |
258
+ | --- | --- | --- |
259
+ | R1 | MET | `src/api/users.ts:42` |
260
+
261
+ | AC | Status | Evidence Type | Evidence |
262
+ | --- | --- | --- | --- |
263
+ | AC1 — the guard rejects an unsafe input | MET | test | `packages/app/tests/guard.test.ts:12` |
264
+
265
+ One row per declared requirement (`MET | PARTIAL | UNMET`, none omitted or merged) and one row per
266
+ acceptance criterion (`MET | PARTIAL | UNMET | N/A`), with the evidence-type token alone in its own
267
+ cell — `test | command | static-ref | manual-review | llm-judge | n/a`, or a `+` compound when more
268
+ than one kind applies. Evidence cells cite a repo-relative `path:line` or a named test/command. AC
269
+ identities stay the task's own (checklist label, title, bold head, `Scenario:` prefix, bracket tag or
270
+ declared `AC-N` alias): a linked feature's scenario title covers the task that implements it, and a
271
+ task-local criterion is legal as-is — it needs no feature credit.
272
+ Never repeat the section's disposition-derived `Confidence:` line here: a second `Confidence:` line is
273
+ a `confidence-duplicate` rejection of the whole answer, and so is a level the rows do not derive. A
274
+ PASS with executable AC and one accepted P4 therefore certifies as `Confidence: HIGH` in the answer
275
+ while the section keeps `Confidence: MEDIUM`.
276
+
246
277
  **Source citations for external claims (one claim contract, 2026-10-12).** A Finding cell or a
247
278
  Functional Traceability `Evidence` cell that asserts an external API/library claim carries a
248
279
  verifiable citation in the SAME cell — a repo `path:line(-end)`, the external named-origin form
@@ -258,8 +289,8 @@ as an explicit gap in the report body (above), so a reader can re-check it.
258
289
 
259
290
  **Machine-checked at the gate (findings table).** `spur task residual <wbs> review-gate` applies the same two
260
291
  predicates to every finding row: a Finding cell carrying one of the verify lint's claim markers with
261
- no citation form becomes a **blocking** `review-contract` item, so `review → verify` stops and the
262
- run enters `review-fail-triage`. The `Location` column is deliberately not consulted as a citation —
292
+ no citation form becomes a **blocking** `review-contract` item, so `review → done` stops and the
293
+ run enters the `review-failure-class` decision. The `Location` column is deliberately not consulted as a citation —
263
294
  that is the rule stated above, and a gate that accepted it would pass every row that has an anchor.
264
295
  Functional Traceability `Evidence` cells stay reviewer-enforced (SECUA review): they are free prose
265
296
  in a table the gate does not parse.