@gobing-ai/spur 0.4.11 → 0.4.13
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.example.yaml +22 -0
- package/config/rules/boundary/config-loading-ownership.yaml +1 -1
- package/config/rules/quality/coverage-gate.yaml +2 -2
- package/config/rules/strict/runtime-boundaries.yaml +2 -2
- package/config/rules/surface/check-cli-surface.yaml +4 -0
- package/config/rules/typescript/output-boundaries.yaml +4 -10
- package/config/workflows/feature-verification.yaml +14 -5
- package/config/workflows/idea-pipeline.yaml +92 -12
- package/config/workflows/task-pipeline.yaml +456 -830
- package/config/workflows/wayfinder-resolution.yaml +20 -3
- package/config/workflows/wrapup-pipeline.yaml +22 -9
- package/package.json +1 -1
- package/plugins/sp/README.md +2 -2
- package/plugins/sp/agents/super-planner.md +2 -2
- package/plugins/sp/agents/super-reviewer.md +17 -8
- package/plugins/sp/bunfig.toml +3 -3
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-gitmsg.md +2 -2
- package/plugins/sp/commands/dev-handover.md +1 -1
- package/plugins/sp/commands/dev-idea.md +23 -13
- package/plugins/sp/commands/dev-job-dump.md +1 -1
- package/plugins/sp/commands/dev-plan.md +17 -6
- package/plugins/sp/commands/dev-review.md +1 -1
- package/plugins/sp/commands/dev-run.md +8 -7
- package/plugins/sp/commands/dev-runall.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -7
- package/plugins/sp/skills/code-refactoring/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/SKILL.md +1 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +1 -1
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +23 -10
- package/plugins/sp/skills/doc-evolve/SKILL.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +7 -7
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +4 -4
- package/plugins/sp/skills/spur-check/SKILL.md +9 -8
- package/plugins/sp/skills/spur-cli/SKILL.md +2 -2
- package/plugins/sp/skills/spur-cli/references/self.md +37 -2
- package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +5 -3
- package/plugins/sp/skills/spur-dev/SKILL.md +3 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +12 -11
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +15 -15
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +4 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +4 -4
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -11
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +32 -23
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +5 -5
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +4 -4
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +1 -1
- package/schemas/spur-config.schema.json +19 -1
- package/spur.js +31459 -31868
- package/web/_astro/{BoardApp.BMprmPRc.js → BoardApp.Bjdp3XWV.js} +36 -36
- package/web/_astro/{BoardApp.B5rTVNd_.js → BoardApp.DCDZxLlQ.js} +1 -1
- package/web/_astro/{TaskDetail.Ddsur8qd.js → TaskDetail.B3vBxynO.js} +1 -1
- package/web/_astro/{arc.DLqVVBxP.js → arc.DcuHTB_T.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.C-Pk3uAi.js → architectureDiagram-3BPJPVTR.ClOFWBON.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BRO1i_Nm.js → blockDiagram-GPEHLZMM.otYyr7Zb.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BU_cWu6e.js → c4Diagram-AAUBKEIU.BCjpc9LP.js} +1 -1
- package/web/_astro/channel.qgOLXjI_.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.D2VXiQ2t.js → chunk-2J33WTMH.D5Wpnpt_.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BcHT772J.js → chunk-4BX2VUAB.DkQGtWNz.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DhDtICmo.js → chunk-55IACEB6.B7dqjAip.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BoKHp7gV.js → chunk-727SXJPM.BDQvnKE2.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.Chu4JRpE.js → chunk-AQP2D5EJ.D92doBX5.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.wHdwKPlq.js → chunk-FMBD7UC4.C968oI5G.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.ffN-M1JB.js → chunk-ND2GUHAM.AkkYC1bY.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.FRubwg4S.js → chunk-QZHKN3VN.sCc-rKKo.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.c22tQsc_.js → classDiagram-4FO5ZUOK.pOQ9u0mY.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.c22tQsc_.js → classDiagram-v2-Q7XG4LA2.pOQ9u0mY.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.B9c6fiSj.js → cose-bilkent-S5V4N54A.CRaeD6FY.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.DrZsrUHl.js → cynefin-OW5HDTMX.BiCPBu54.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.DG1U4b3y.js → dagre-BM42HDAG.C1-q3vkT.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CH15b-K3.js → diagram-2AECGRRQ.BSVOv30O.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BH1j7twR.js → diagram-5GNKFQAL.CHMkjJ-V.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.q2zCkbAj.js → diagram-KO2AKTUF.CKY9M564.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BLaaZnHV.js → diagram-LMA3HP47.B9yNJiTP.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BcpRdQwI.js → diagram-OG6HWLK6.Dk8AUGsk.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.D7KBlqfM.js → erDiagram-TEJ5UH35.BQHR89TZ.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.D_gmuRqg.js → flowDiagram-I6XJVG4X.oNkWVgoK.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DzgWYniD.js → ganttDiagram-6RSMTGT7.Kz2U2nu9.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.576i3hm3.js → gitGraphDiagram-PVQCEYII.DHIZ_wPq.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DMCclBiz.js → infoDiagram-5YYISTIA.D0NJLcv9.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.9Kyca4SH.js → ishikawaDiagram-YF4QCWOH.CFEfTWRG.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.chbdG-8q.js → journeyDiagram-JHISSGLW.Dc7IE7I4.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.Bds54-e0.js → kanban-definition-UN3LZRKU.D5v0xO9k.js} +1 -1
- package/web/_astro/{linear.D5ND9GN_.js → linear.wR5KzXnE.js} +1 -1
- package/web/_astro/{mermaid.core.D9N_PGbj.js → mermaid.core.BDQ2FoPd.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.t0xop5vp.js → mindmap-definition-RKZ34NQL.W6_INsia.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.DqgKh6hM.js → pieDiagram-4H26LBE5.61ihbgkK.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DeuHVi2t.js → quadrantDiagram-W4KKPZXB.B5lEpFLo.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BdYFSG4W.js → requirementDiagram-4Y6WPE33.CmhVBVX0.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.k1LPPETJ.js → sankeyDiagram-5OEKKPKP.D7Vvj9Wf.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DdyfpzoG.js → sequenceDiagram-3UESZ5HK.B1vPLixM.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DSEI8il0.js → stateDiagram-AJRCARHV.BnACmrGi.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CoMnQRKo.js → stateDiagram-v2-BHNVJYJU.CW4pnmc3.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DM1AHNm-.js → timeline-definition-PNZ67QCA.CkzMCPnC.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.C-cJmVtl.js → vennDiagram-CIIHVFJN.CY4aNdAR.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.D0xMzKPb.js → wardleyDiagram-YWT4CUSO.BtoU9UFk.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.BH5xJcEq.js → xychartDiagram-2RQKCTM6._9ijUUAs.js} +1 -1
- package/web/index.html +1 -1
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +0 -997
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +0 -178
- package/web/_astro/channel.Bs3Illhy.js +0 -1
|
@@ -1,38 +1,45 @@
|
|
|
1
1
|
# Task execution pipeline — one run per execution attempt of a task (design §6).
|
|
2
2
|
#
|
|
3
|
-
# Orchestration is configuration (ADR-022 / §3.2): this is YAML over the
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
3
|
+
# Orchestration is configuration (ADR-022 / §3.2): this is YAML over the engine — zero new
|
|
4
|
+
# engine code. The pipeline NEVER touches files directly: task-file writes go through
|
|
5
|
+
# `spur task update --section` / `spur task record`, and status moves go through the normal
|
|
6
|
+
# `spur task update <wbs> <status>` verb so the lifecycle guards (0055) apply identically.
|
|
7
|
+
# Run linkage is written to `task_run_links` (kind=pipeline).
|
|
8
8
|
#
|
|
9
|
-
# Shape: precheck → implement[
|
|
10
|
-
# →
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
9
|
+
# Shape (D65 1239, ADR-140): precheck → implement[⇄ escalate] → gate[⇄ remediate] → review
|
|
10
|
+
# → done | failed.
|
|
11
|
+
# Six working states — `precheck`, `implement`, `escalate`, `gate`, `remediate`,
|
|
12
|
+
# `review` — and two terminals. There is no `test`/`test-recheck` pair (`gate` owns
|
|
13
|
+
# the quality gate, its mode chosen per visit), no `triage`/`test-fail-triage`/
|
|
14
|
+
# `review-fail-triage` states (the same catalog decisions run inside the state that
|
|
15
|
+
# produced the failure and their rows drive the transitions), no `approve` state (the
|
|
16
|
+
# operator approval is a decision point at the end of `review`, 1236 R1/R2), no
|
|
17
|
+
# `verify` state (`review` certifies: `spur task verdict --from-answer` derives the
|
|
18
|
+
# verdict from the reviewer's own requirement traceability), and no `record` state
|
|
19
|
+
# (`done`'s onEnter performs the deterministic close).
|
|
15
20
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
+
# Evidence model (D65 R3/R11): the run's evidence is the tree-hash gate receipt
|
|
22
|
+
# (`.spur/run/<wbs>-check-receipt.json`, keyed by the git tree the gate captures itself),
|
|
23
|
+
# the review verdict artifact written by `spur task verdict`, and the gate/review status
|
|
24
|
+
# tokens the transitions read. The former proof chain — `proofDigest`, `proof.fingerprint`
|
|
25
|
+
# captures and compares, digest-bound receipt preconditions, `run.artifact` verdict
|
|
26
|
+
# registration and the `verifiedOutcome` it fed — is gone; a PASS receipt is reused while
|
|
27
|
+
# the tree hash holds and invalidated when the tree moves. `done` closes on the verb's own
|
|
28
|
+
# target-aware `task check` plus the residual sweep, not on a re-asserted digest.
|
|
21
29
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# into the SAME bounded fix budget, so remediation stays on-graph and bounded.
|
|
30
|
+
# No operator pause lives in this file (D65 R10): the only pause is the host executor's
|
|
31
|
+
# `agent.run` transform (1232). `escalate` asks its question through the responder and the
|
|
32
|
+
# approval gate answers through the decision-point responder, so an interactive run is
|
|
33
|
+
# answered in-session and a headless run without `--auto` answers with the responder default
|
|
34
|
+
# (deny / empty) instead of pausing for a later `spur workflow continue`.
|
|
28
35
|
#
|
|
29
36
|
# Vars (passed as a JSON object via `--vars`):
|
|
30
37
|
# wbs — task WBS (required)
|
|
31
|
-
# profile — "auto"
|
|
38
|
+
# profile — "auto" answers the approval gate from its declared binding (R4)
|
|
32
39
|
# agent / spurBin — executor + spur binary (CLI overrides spurBin)
|
|
33
|
-
# stepTimeoutMs — agent.run budget for review/
|
|
40
|
+
# stepTimeoutMs — agent.run budget for review/remediate (ms)
|
|
34
41
|
# implementTimeoutMs — implement agent.run budget (ms)
|
|
35
|
-
# qualityGateCmd — project gate (default:
|
|
42
|
+
# qualityGateCmd — project gate (default: ${spurBin:-spur} self exec check)
|
|
36
43
|
# qualityGateMaxFixAttempts — max /sp:dev-fixall hops after a red gate (default: 2)
|
|
37
44
|
# maxEscalations — max operator-question pauses per implement chain (default: 2)
|
|
38
45
|
#
|
|
@@ -41,34 +48,29 @@
|
|
|
41
48
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
42
49
|
kind: state-machine
|
|
43
50
|
name: task-pipeline
|
|
44
|
-
# 0772 R2: explicit identity tag. Set only after the
|
|
45
|
-
#
|
|
46
|
-
# v4 (session finding after 1088): the graph routes a non-PASS review into bounded remediation
|
|
47
|
-
# a contract change, so the identity moves with it (the confidence half of v4 was retired at v7).
|
|
51
|
+
# 0772 R2: explicit identity tag. Set only after the failure-path tests passed; the engine
|
|
52
|
+
# treats unversioned external definitions as supported.
|
|
53
|
+
# v4 (session finding after 1088): the graph routes a non-PASS review into bounded remediation.
|
|
48
54
|
# v5 (task 1077's inline run, 2026-10-06): the verify verdict action is explicitly `onError:
|
|
49
|
-
# continue
|
|
50
|
-
#
|
|
51
|
-
# (
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# `ackLowConfidence` var, and the guards' LOW/absent clauses are gone. The derived
|
|
62
|
-
# feature-receipt confidence (1196) followed in task 1221 (ADR-138 amendment).
|
|
63
|
-
version: "7"
|
|
64
|
-
description: "Standard task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → record → done"
|
|
55
|
+
# continue` — a non-PASS verdict is data for the guards, not a state failure.
|
|
56
|
+
# v7 (task 1207, F511): the asserted `Confidence: HIGH|MEDIUM|LOW` machinery is retired.
|
|
57
|
+
# v9 (task 1239, D65 R10): the seven-state rewrite. `test`/`test-recheck` collapse into `gate`
|
|
58
|
+
# (one typed `command.gate` over `spur task gate`, mode by visit, tree-hash receipt reuse under
|
|
59
|
+
# the host-wide full-gate lock); `verify` merges into `review`, which certifies through
|
|
60
|
+
# `spur task verdict --from-answer` over the reviewer's requirement/AC traceability; the three
|
|
61
|
+
# routing states become catalog decision calls inside `gate` and `review`; `approve` becomes the
|
|
62
|
+
# review's operator decision point; `record` becomes `done`'s deterministic close; and the whole
|
|
63
|
+
# proof chain (proofDigest, `proof.fingerprint`, receipt preconditions, `run.artifact` verdict
|
|
64
|
+
# registration) is removed with the ADR-071 machinery (ADR-140 item 7). A contract change.
|
|
65
|
+
version: "9"
|
|
66
|
+
description: "Standard task pipeline: precheck → implement → gate (typed quality gate, tree-hash receipt) → review (verdict + R/AC traceability + operator decision) → done | failed"
|
|
65
67
|
# Task entity lifecycle (owned by task-lifecycle.yaml):
|
|
66
68
|
# normal: backlog → todo → wip → testing → done
|
|
67
69
|
# blocked is bidirectional with todo/wip/testing; done may reopen to wip.
|
|
68
70
|
# any non-terminal state may transition to cancelled.
|
|
69
71
|
# Guards:
|
|
70
72
|
# wip → testing: spur task check <wbs>
|
|
71
|
-
# testing → done: spur task check <wbs> --as done
|
|
73
|
+
# testing → done: spur task check <wbs> --as done
|
|
72
74
|
# Invalid: todo → testing, todo → done, and wip → done.
|
|
73
75
|
# Pipeline states below are orchestration states, not task statuses.
|
|
74
76
|
iterationBound: 20
|
|
@@ -76,16 +78,13 @@ initialState: precheck
|
|
|
76
78
|
terminalStates:
|
|
77
79
|
- done
|
|
78
80
|
- failed
|
|
79
|
-
- cancelled
|
|
80
81
|
failureStates:
|
|
81
82
|
- failed
|
|
82
|
-
- cancelled
|
|
83
83
|
vars:
|
|
84
84
|
wbs: "0000"
|
|
85
85
|
profile: "standard"
|
|
86
86
|
mode: ""
|
|
87
87
|
__runId: ""
|
|
88
|
-
__definitionDigest: ""
|
|
89
88
|
# PATH-independent spur invocation for shell guards/actions. The CLI overrides this
|
|
90
89
|
# at run start (resolveSpurBin); the literal default is a safe fallback so direct/dry
|
|
91
90
|
# runs and `workflow validate` resolve the template. (See AGENTS.md / ADR-026.)
|
|
@@ -106,8 +105,8 @@ vars:
|
|
|
106
105
|
# Remediation can mutate code only when BOTH this cap and the task's declared
|
|
107
106
|
# mutationPolicy permit code. none/tests require scoped manual repair, not fixall.
|
|
108
107
|
mutationPolicy: "code"
|
|
109
|
-
# Step-level timeout for agentic hops (review /
|
|
110
|
-
#
|
|
108
|
+
# Step-level timeout for agentic hops (review / remediate) in ms. The quality gate is a
|
|
109
|
+
# typed command.gate (its own budget, unbounded like the former shell).
|
|
111
110
|
# Raised 600s → 1800s (task 0398 R4 / H6 dogfood). Override:
|
|
112
111
|
# `--vars '{"stepTimeoutMs":"120000"}'`. 30 min is headroom, not a licence to
|
|
113
112
|
# run unbounded — if a hop still hits this wall, STOP and record it.
|
|
@@ -115,71 +114,36 @@ vars:
|
|
|
115
114
|
# Timeout for the `implement` step specifically (R2a). Implementation is the
|
|
116
115
|
# heaviest agent.run step (full read/write/test-probe loop); the old 600s default
|
|
117
116
|
# failed five consecutive dogfood runs (bugs 742/744/746/748). Default is 45 min
|
|
118
|
-
# (task 1108)
|
|
119
|
-
#
|
|
120
|
-
# timeout (inline-pipeline-driver.md § Timeout boundary). 45 min is a reasoned
|
|
121
|
-
# default, not a proven bound — in P1 one implement still hit 45 min and resumed
|
|
122
|
-
# (docs/reports/i31/1107-runall-p1-pilot.md § Timeout attribution).
|
|
117
|
+
# (task 1108); the host executor forwards it only when the host's dispatch tool
|
|
118
|
+
# accepts a timeout.
|
|
123
119
|
# Override per run: `--vars '{"implementTimeoutMs":"3600000"}'`.
|
|
124
120
|
implementTimeoutMs: "2700000"
|
|
125
|
-
# Answer captured by the
|
|
126
|
-
#
|
|
121
|
+
# Answer captured by the review's approval decision point: "yes" | "no" | "cancel"
|
|
122
|
+
# (the responder vocabulary) or "" when the declared binding deferred (no maker, or
|
|
123
|
+
# `profile=auto` with the decision maker disabled). Only meaningful once `review` ran.
|
|
127
124
|
__hitlAnswer: ""
|
|
125
|
+
# Set beside it by the same gate when `decision.mode` is `evidence`: "accepted" when the
|
|
126
|
+
# evidence answered, "deferred" when it could not (1236). Read by the review → done guard.
|
|
127
|
+
approvalStatus: ""
|
|
128
128
|
# Operator answer captured by the escalate hop's hitl.input (0933). Empty by
|
|
129
|
-
# default;
|
|
130
|
-
# by the escalate→implement / escalate→failed guards. Non-empty = answered.
|
|
129
|
+
# default; read by the escalate→implement / escalate→failed guards. Non-empty = answered.
|
|
131
130
|
__hitlInput: ""
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# evidence-producing final chain (quality → review → verify) — and re-captured at `test-recheck`
|
|
135
|
-
# when bounded remediation mutated the tree, so every evidence stage names one fresh digest
|
|
136
|
-
# (R2/R4). `proofDigestNow` is the live re-capture compared against `proofDigest` at review,
|
|
137
|
-
# verify and record entry; a mismatch means a proof input changed after evidence was established,
|
|
138
|
-
# so the run routes to `failed` instead of spending a review dispatch or crossing the completion
|
|
139
|
-
# boundary (R5). Task 1135 adds the review-entry compare and the gate-entry tree snapshot
|
|
140
|
-
# (`.spur/run/<run-id>-gate-paths.txt`, same exclusions as the git-tree half) so a mismatch also
|
|
141
|
-
# names the drifted paths.
|
|
142
|
-
# `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
|
|
143
|
-
# git-tree half — spec content is folded in explicitly or a task-file edit would go undetected.
|
|
144
|
-
# The fingerprint scopes task content to the proof-input sections only (Background, Requirements,
|
|
145
|
-
# Acceptance Criteria, Design, Plan), so record-time Solution/Testing/Review evidence writes do
|
|
146
|
-
# not retroactively invalidate the certified input set (R6).
|
|
147
|
-
proofDigest: ""
|
|
148
|
-
proofDigestNow: ""
|
|
149
|
-
taskSpecPath: ""
|
|
150
|
-
# 0785 R2: linked feature spec path, resolved at `test` entry from the task frontmatter.
|
|
151
|
-
# Empty (orphan task) means the feature spec is legitimately omitted from the digest inputs
|
|
152
|
-
# (empty-string compatibility); a declared feature whose path fails to resolve fails closed.
|
|
153
|
-
featureSpecPath: ""
|
|
154
|
-
# 0710 R4: task priority tier (P0..P4) extracted from the task frontmatter at the
|
|
155
|
-
# quality-gate stage. P0/P1 make the review/verify distinct-executor policy apply;
|
|
156
|
-
# unknown/empty priority means fresh-context-only (executor reuse allowed).
|
|
157
|
-
taskPriority: ""
|
|
158
|
-
# Project quality gate for the `test` hop (probe + fixall + recheck). Override per project
|
|
159
|
-
# with the same package-manager surface (this monorepo is Bun-only):
|
|
160
|
-
# `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
|
|
161
|
-
# and the fixall slash input all use this same var so the command stays single-sourced.
|
|
131
|
+
# Project quality gate for the `gate` hop, overridable per project:
|
|
132
|
+
# `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`.
|
|
162
133
|
# INVARIANT (ADR-124, clarified 2026-10-07 by task 1111 R5): only the FULL tier writes PASS.
|
|
163
134
|
# Under the opt-in `deferQualityGate` policy the per-task token is DEFERRED (light tier) and
|
|
164
135
|
# the "review runs only after a green full gate" invariant moves to BATCH scope — nothing is
|
|
165
|
-
# reported PASS without the integrated full gate on the base ref.
|
|
166
|
-
#
|
|
167
|
-
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
|
|
136
|
+
# reported PASS without the integrated full gate on the base ref.
|
|
137
|
+
# TRUSTED CONFIG ONLY — `spur task gate` passes this to the project's gate command. Never
|
|
168
138
|
# interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
# should override this too. TRUSTED CONFIG ONLY — executed via `sh -c` (same surface as
|
|
178
|
-
# qualityGateCmd). Invariant: `review` is only ever entered through a full green
|
|
179
|
-
# qualityGateCmd — only the full gate writes PASS to <wbs>-test-gate.status.
|
|
180
|
-
gateProbeCmd: "bun run lint"
|
|
181
|
-
# Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
|
|
182
|
-
# Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
|
|
139
|
+
# The gate observes, it does not mutate: `implement` already ran `$formatCmd` on its way out,
|
|
140
|
+
# and a formatter inside the gate would rewrite the tree the receipt is keyed by.
|
|
141
|
+
qualityGateCmd: "${spurBin:-spur} self exec check"
|
|
142
|
+
# Cheap red-detector run before the full gate on **recheck visits only**; empty ⇒ no probe.
|
|
143
|
+
# A project overriding qualityGateCmd should override this too. TRUSTED CONFIG ONLY.
|
|
144
|
+
gateProbeCmd: "${spurBin:-spur} self exec lint"
|
|
145
|
+
# Max /sp:dev-fixall attempts for one run (bounded; no thrash). Counter:
|
|
146
|
+
# .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
|
|
183
147
|
qualityGateMaxFixAttempts: "2"
|
|
184
148
|
# Max operator-question pauses (0933) before the implement chain routes to failed.
|
|
185
149
|
# Escalation counter: .spur/run/<wbs>-escalation-count. Default 2 = two answered
|
|
@@ -192,32 +156,46 @@ vars:
|
|
|
192
156
|
# Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
|
|
193
157
|
# project can point it at its own formatter; invoked best-effort (a missing or
|
|
194
158
|
# failing formatter must never abort a run — the quality gate is the real gate).
|
|
195
|
-
formatCmd: "
|
|
196
|
-
# Bounded `file:line` anchors extracted from the failing quality-gate log by
|
|
197
|
-
# `
|
|
198
|
-
# `
|
|
159
|
+
formatCmd: "${spurBin:-spur} self exec format"
|
|
160
|
+
# Bounded `file:line` anchors extracted from the failing quality-gate log by
|
|
161
|
+
# `spur task gate` (deduped, max 20, space-separated on one line).
|
|
162
|
+
# `remediate` reads .spur/run/<wbs>-test-gate.findings into this var and names the
|
|
199
163
|
# anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
|
|
200
164
|
# file:line instead of re-deriving it from a fresh gate run (0482 R3).
|
|
201
165
|
gateFindings: ""
|
|
166
|
+
# The quality-gate mode this visit runs — `run` on the first gate visit of the run,
|
|
167
|
+
# `recheck` after a remediation hop, `deferred` under the opt-in batch policy. Written by
|
|
168
|
+
# the gate's own shell and projected into this var (args cannot branch) — D65 1239 R2.
|
|
169
|
+
gateMode: ""
|
|
170
|
+
# Review depth resolved from the lane decision (0943 R1/R2): `functional` for the fast
|
|
171
|
+
# lane (requirement/AC traceability only — the reviewer's R → file:line table is what
|
|
172
|
+
# certifies, so the fast lane never drops the evidence), `all` for every other lane.
|
|
173
|
+
reviewFocus: ""
|
|
174
|
+
# Task spec path resolved at `gate` entry (also the decide evidence for the review-lane
|
|
175
|
+
# routing). `docs/tasks*` is a versioned input, not a run artifact.
|
|
176
|
+
taskSpecPath: ""
|
|
177
|
+
# 0710 R4: task priority tier (P0..P4) extracted from the task frontmatter at `gate`
|
|
178
|
+
# entry. P0/P1 make the review distinct-executor policy apply; unknown/empty priority
|
|
179
|
+
# means fresh-context-only (executor reuse allowed).
|
|
180
|
+
taskPriority: ""
|
|
202
181
|
# Diff-scope guard on the implement hop (R1, task 0487). When the target task
|
|
203
182
|
# body backticks at least one path, non-corpus changes outside those paths
|
|
204
183
|
# fail the step by name. New files beside a declared file are allowed. Empty
|
|
205
184
|
# (default) = on; set to "off" to bypass:
|
|
206
185
|
# `--vars '{"implementScopeGuard":"off"}'`.
|
|
207
186
|
implementScopeGuard: ""
|
|
208
|
-
# 1111 R1: opt-in parallel-batch gate policy. When "true", `
|
|
209
|
-
#
|
|
187
|
+
# 1111 R1: opt-in parallel-batch gate policy. When "true", `gate` runs the LIGHT tier for
|
|
188
|
+
# per-task early feedback and writes `DEFERRED` (light PASS) or `FAIL` to
|
|
210
189
|
# `.spur/run/<wbs>-test-gate.status` — never `PASS`, because that token means the full
|
|
211
190
|
# project gate ran green on this tree. The batch orchestrator owes ONE integrated full gate
|
|
212
191
|
# on the base ref over the merged slices (1111 R3). Default "false" = unchanged behavior,
|
|
213
|
-
# and a stray `DEFERRED` file fails the guards.
|
|
214
|
-
# `--defer-gate` requires `--mode parallel`.
|
|
192
|
+
# and a stray `DEFERRED` file fails the guards.
|
|
215
193
|
deferQualityGate: "false"
|
|
216
194
|
# 0931 R5: parallel batches defer the per-task feature sync so a task branch never
|
|
217
|
-
# touches feature files or docs/features/INDEX.md. When "true", the
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
#
|
|
195
|
+
# touches feature files or docs/features/INDEX.md. When "true", the close's post-record
|
|
196
|
+
# shell appends a deferral note and skips the sync; the parallel batch orchestrator runs
|
|
197
|
+
# the sync + `spur feature refresh` once per touched feature on the base ref after
|
|
198
|
+
# integration. Sequential/inline keep the default "false" (unchanged).
|
|
221
199
|
deferFeatureSync: "false"
|
|
222
200
|
|
|
223
201
|
states:
|
|
@@ -275,7 +253,7 @@ states:
|
|
|
275
253
|
fails or times out, so a halted implement never leaves the task at `wip` with an
|
|
276
254
|
empty Solution (the F3 half-state). The `--no-lifecycle` flag is correct here: the
|
|
277
255
|
pipeline owns this transition; the FSM guard would otherwise re-run `spur task
|
|
278
|
-
check`, which
|
|
256
|
+
check`, which precheck's job.
|
|
279
257
|
NOTE: this step must NOT call /sp:dev-run in full mode (the default) —
|
|
280
258
|
that command DRIVES this pipeline, so calling it here recurses.
|
|
281
259
|
--mode implement is the single-step implement entry.
|
|
@@ -298,16 +276,14 @@ states:
|
|
|
298
276
|
options:
|
|
299
277
|
agent: ${vars.implementAgent}
|
|
300
278
|
# Pure slash command only (ADR-043). Anti-recursion / implement discipline
|
|
301
|
-
# The worker's brief is OWNED BY
|
|
302
|
-
# see `plugins/sp/skills/code-implementation/SKILL.md
|
|
303
|
-
#
|
|
304
|
-
# anchor form in § Red Flags). The YAML only names the entry command.
|
|
279
|
+
# The worker's brief is OWNED BY THE SKILL, not restated here (task 1110 R3):
|
|
280
|
+
# see `plugins/sp/skills/code-implementation/SKILL.md`. The YAML only names the
|
|
281
|
+
# entry command.
|
|
305
282
|
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
306
283
|
role: coder
|
|
307
|
-
# B7 R3/R6 (0894): coder stages reuse the role session — the
|
|
284
|
+
# B7 R3/R6 (0894): coder stages reuse the role session — the remediation hop
|
|
308
285
|
# resumes the implement session instead of re-reading the task cold.
|
|
309
286
|
session: reuse
|
|
310
|
-
# lives in /sp:dev-run --mode implement → sp:code-implementation, not YAML prose.
|
|
311
287
|
# 0933 R28: --escalation-file names the Q/A transcript the implement
|
|
312
288
|
# agent appends to when pausing and reads back on re-dispatch; absent
|
|
313
289
|
# on a first attempt means "no prior escalations".
|
|
@@ -316,7 +292,7 @@ states:
|
|
|
316
292
|
# R3 (task 0424): empty-implement no-op guard — the agent.run action
|
|
317
293
|
# fails the step when exit 0 produced zero non-corpus file changes, so
|
|
318
294
|
# a silent no-op routes the run to `failed` here instead of drifting
|
|
319
|
-
# into
|
|
295
|
+
# into gate/review and being caught a full pass later.
|
|
320
296
|
requireDiff: true
|
|
321
297
|
# Verification-only re-run admission (task 0976): when requireDiff
|
|
322
298
|
# sees zero non-corpus changes because the implementation already
|
|
@@ -328,15 +304,12 @@ states:
|
|
|
328
304
|
# 0933 R26: escalation contract — the agent.run option names the QUESTION
|
|
329
305
|
# file (the pause signal, deleted pre-dispatch for freshness); a paused
|
|
330
306
|
# attempt (agent wrote its question there and exited 0) succeeds with
|
|
331
|
-
# data.escalated=true and skips requireDiff for THAT attempt only.
|
|
332
|
-
# Q/A transcript (.spur/run/<wbs>-escalation.md) is separate: it reaches
|
|
333
|
-
# the agent via --escalation-file in the input below.
|
|
307
|
+
# data.escalated=true and skips requireDiff for THAT attempt only.
|
|
334
308
|
escalationFile: .spur/run/${vars.wbs}-question.md
|
|
335
309
|
# 0706 R6: this stage mutates the working tree unattended under the
|
|
336
310
|
# auto profile, so it declares minimum execution-capability
|
|
337
311
|
# requirements. Dispatch fails closed (before spawn) when the
|
|
338
|
-
# resolved executor's attestation cannot satisfy them
|
|
339
|
-
# executor in agent config.
|
|
312
|
+
# resolved executor's attestation cannot satisfy them.
|
|
340
313
|
requiresCapabilities:
|
|
341
314
|
fsWrite: available
|
|
342
315
|
processSpawn: available
|
|
@@ -355,39 +328,35 @@ states:
|
|
|
355
328
|
timeoutMs: 120000
|
|
356
329
|
# Post-implement cleanup: auto-format any unformatted output the agent produced.
|
|
357
330
|
# The agent may leave files that don't pass `biome check`, which then fails
|
|
358
|
-
# `bun run lint` downstream
|
|
359
|
-
# the test stage from tripping on a purely mechanical formatting defect (dogfood
|
|
360
|
-
# bug-733 — omp left agent-run.test.ts unformatted, causing a spurious lint gate
|
|
361
|
-
# failure that the ## Testing section mis-attributed to "pre-existing gaps").
|
|
331
|
+
# `bun run lint` downstream (dogfood bug-733).
|
|
362
332
|
# Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
|
|
363
|
-
# non-Bun project cannot abort a run mid-implement.
|
|
364
|
-
#
|
|
333
|
+
# non-Bun project cannot abort a run mid-implement. `${vars.qualityGateCmd}` at
|
|
334
|
+
# `gate` is the gate that actually decides.
|
|
365
335
|
- kind: shell
|
|
366
336
|
options:
|
|
367
|
-
command: "$formatCmd ; exit 0
|
|
337
|
+
command: 'sh -c "$formatCmd" ; exit 0'
|
|
368
338
|
# 1109 R3: qualify this task's unique basename anchors to repo-relative paths.
|
|
369
339
|
# Placement is load-bearing: it runs AFTER implement's mutations (and the format
|
|
370
|
-
# above)
|
|
371
|
-
#
|
|
372
|
-
#
|
|
373
|
-
#
|
|
374
|
-
# deliberately does NOT address line drift or `L4.anchor-subject-mismatch`
|
|
375
|
-
# (those need the implementer to re-anchor). Scope contract: task 1109 R5.
|
|
340
|
+
# above) and BEFORE the gate reads the tree, so the receipt is keyed to the final
|
|
341
|
+
# tree. `--wbs` scopes the pass to this task's resolved file.
|
|
342
|
+
# Non-fatal (`; exit 0`): the done gate still reports anything left. Scope
|
|
343
|
+
# contract: task 1109 R5.
|
|
376
344
|
- kind: shell
|
|
377
345
|
options:
|
|
378
346
|
command: "$spurBin task migrate-anchors --wbs $wbs --json ; exit 0"
|
|
379
347
|
|
|
380
348
|
# ── escalate hop (operator-question pause, 0933) ──────────────────────────
|
|
381
349
|
# The implement agent paused on an operator question: it wrote
|
|
382
|
-
# .spur/run/<wbs>-question.md and exited 0 (agent.run reported
|
|
383
|
-
#
|
|
384
|
-
# the
|
|
385
|
-
#
|
|
386
|
-
#
|
|
387
|
-
#
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
350
|
+
# .spur/run/<wbs>-question.md and exited 0 (agent.run reported data.escalated=true,
|
|
351
|
+
# skipping requireDiff for that attempt). This hop bounds the asks (counter), surfaces
|
|
352
|
+
# the question verbatim through the HITL responder, and — once the answer lands in
|
|
353
|
+
# __hitlInput — resumes implement with the Q/A transcript
|
|
354
|
+
# (.spur/run/<wbs>-escalation.md) named in the implement input via --escalation-file.
|
|
355
|
+
# The transcript append + question-file consumption happen in implement's onEnter so a
|
|
356
|
+
# stale question can never re-trigger the hop after the answered attempt.
|
|
357
|
+
# D65 1239: the ask is answered by the responder in-session (no `pause: true` remains in
|
|
358
|
+
# this file); a headless run without an answer routes to failed(cancelled) with the
|
|
359
|
+
# question preserved, exactly as an empty answer always did.
|
|
391
360
|
- id: escalate
|
|
392
361
|
display:
|
|
393
362
|
phase: implement
|
|
@@ -398,135 +367,201 @@ states:
|
|
|
398
367
|
Operator-question pause (0933): the implement agent asked a question
|
|
399
368
|
headlessly; surface it to the operator and resume implement with the answer.
|
|
400
369
|
onEnter:
|
|
401
|
-
# Bounded asks: increment the per-task escalation counter before
|
|
370
|
+
# Bounded asks: increment the per-task escalation counter before asking.
|
|
402
371
|
- kind: shell
|
|
403
372
|
options:
|
|
404
373
|
command: >-
|
|
405
374
|
mkdir -p .spur/run; n="$(cat .spur/run/$wbs-escalation-count 2>/dev/null || echo 0)"; echo $((n + 1)) > .spur/run/$wbs-escalation-count; exit 0
|
|
406
|
-
# The agent's question, verbatim, becomes the
|
|
375
|
+
# The agent's question, verbatim, becomes the prompt.
|
|
407
376
|
- kind: file.read.into-var
|
|
408
377
|
options:
|
|
409
378
|
file: .spur/run/${vars.wbs}-question.md
|
|
410
379
|
var: escalationQuestion
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
# __hitlInput and re-evaluates the escalate guards).
|
|
380
|
+
# Ask the operator (no pause declaration: the responder answers, and the answer is
|
|
381
|
+
# written to __hitlInput for the escalate guards).
|
|
414
382
|
- kind: hitl.input
|
|
415
383
|
options:
|
|
416
384
|
prompt: "${vars.escalationQuestion}"
|
|
417
385
|
|
|
418
|
-
# ──
|
|
386
|
+
# ── gate hop (typed quality gate, one state for run/recheck visits) ───────────
|
|
419
387
|
# NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
|
|
420
388
|
# a coverage target; it is not the project quality gate. Coverage gap-fill remains
|
|
421
389
|
# available via router C3/C5 (`/sp:dev-unit`) or inside implement — not this hop.
|
|
422
390
|
#
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
|
|
391
|
+
# D65 1239 R2: `test` and `test-recheck` are one state. `spur task gate` owns the tier,
|
|
392
|
+
# the probe, the receipt and the host-wide lock; the typed `command.gate` owns the exit
|
|
393
|
+
# code (PASS/FAIL/BLOCKED) so a blocked gate routes to `failed` instead of reading as
|
|
394
|
+
# red. The mode is chosen per visit: `run` on the first visit of this run (it resets the
|
|
395
|
+
# attempt counter and consults the tree-hash receipt), `recheck` after a remediation hop
|
|
396
|
+
# (probe-then-full), `deferred` under the opt-in batch policy.
|
|
397
|
+
#
|
|
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
|
|
400
|
+
# gate's own failure class (catalog `failure-class`) drives the red-gate transitions.
|
|
401
|
+
# Both run unconditionally — the engine has no onEnter conditionals — and both degrade to
|
|
402
|
+
# their catalog fallback with no model call while `workflows.decideDecisionMaker` is off.
|
|
403
|
+
- id: gate
|
|
430
404
|
display:
|
|
431
405
|
phase: test
|
|
432
406
|
phaseTitle: Test
|
|
433
407
|
title: Run quality gate
|
|
434
408
|
description: >
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
`.spur/run/<
|
|
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
|
|
411
|
+
first), run `${vars.qualityGateCmd}` through `spur task gate <wbs> <mode>` under the
|
|
412
|
+
host-wide lock (tree-hash receipt reuse on the same tree), then classify a red gate
|
|
413
|
+
through `failure-class`. Writes the typed outcome to
|
|
414
|
+
`.spur/run/<runId>-gate.status` and the gate's own token to
|
|
415
|
+
`.spur/run/<wbs>-test-gate.status`; always a recorded outcome, never a state failure.
|
|
441
416
|
onEnter:
|
|
442
|
-
#
|
|
443
|
-
#
|
|
444
|
-
#
|
|
417
|
+
# (e) task path + priority resolution; fail-closed on an unresolvable spec (0751 R2,
|
|
418
|
+
# 0710 R4). The path is the decide evidence for the review lane and the priority is
|
|
419
|
+
# the review/executor distinctness input (0710 R4) — both are consumed downstream, so
|
|
420
|
+
# an unresolved path fails here instead of degrading the run silently.
|
|
445
421
|
- kind: shell
|
|
446
422
|
options:
|
|
447
|
-
|
|
448
|
-
# path listing); normalize to upper so requiresDistinctExecutor's exact 'P0'/'P1' match hits.
|
|
449
|
-
# 0751 R2: the task path is NOT optional - an unresolved lookup fails
|
|
450
|
-
# the step (no `|| true`, no forced `exit 0`, no stderr suppression)
|
|
451
|
-
# instead of degrading the proof to whole-tree-only. The priority read
|
|
452
|
-
# stays tolerant: a missing line is genuinely optional.
|
|
453
|
-
command: 'mkdir -p .spur/run; $spurBin task path $wbs --json | jq -r ".path // .filePath // empty" > ".spur/run/$wbs-taskpath.txt"; task_path="$(cat ".spur/run/$wbs-taskpath.txt")"; if [ -z "$task_path" ]; then echo "fail-closed proof chain (0751 R2): task path for $wbs did not resolve - the task spec cannot be folded into the proof digest" >&2; exit 1; fi; sed -n "s/^priority:[[:space:]]*//p" "$task_path" | head -1 | tr -d "[:space:]" | tr "[:lower:]" "[:upper:]" > ".spur/run/$wbs-priority.txt"'
|
|
423
|
+
command: 'mkdir -p .spur/run; $spurBin task path $wbs --json | jq -r ".path // .filePath // empty" > ".spur/run/$wbs-taskpath.txt"; task_path="$(cat ".spur/run/$wbs-taskpath.txt")"; if [ -z "$task_path" ]; then echo "gate: task path for $wbs did not resolve - the review evidence has no task spec" >&2; exit 1; fi; sed -n "s/^priority:[[:space:]]*//p" "$task_path" | head -1 | tr -d "[:space:]" | tr "[:lower:]" "[:upper:]" > ".spur/run/$wbs-priority.txt"'
|
|
454
424
|
- kind: file.read.into-var
|
|
455
425
|
options:
|
|
456
426
|
path: .spur/run/${vars.wbs}-taskpath.txt
|
|
457
427
|
var: taskSpecPath
|
|
458
|
-
# 0710 R4: carry the task's priority tier into the review
|
|
428
|
+
# 0710 R4: carry the task's priority tier into the review's distinctness policy.
|
|
459
429
|
- kind: file.read.into-var
|
|
460
430
|
options:
|
|
461
431
|
path: .spur/run/${vars.wbs}-priority.txt
|
|
462
432
|
var: taskPriority
|
|
463
|
-
#
|
|
464
|
-
#
|
|
465
|
-
#
|
|
466
|
-
# of silently folding a stale spec into the proof.
|
|
467
|
-
# (e) linked feature-spec path resolution; fail-closed when a declared feature does not resolve (0785 R2).
|
|
433
|
+
# (a) diffstat producer: `spur task diffstat` owns git reading and sensitive-path
|
|
434
|
+
# classification; shell only fails closed on a non-zero exit (writes sensitive:true,
|
|
435
|
+
# which the pre-guard below maps to the safety lane).
|
|
468
436
|
- kind: shell
|
|
469
437
|
options:
|
|
470
|
-
command:
|
|
471
|
-
|
|
438
|
+
command: >-
|
|
439
|
+
$spurBin task diffstat "$wbs" --no-logo || { echo "task-diffstat failed closed — spur task diffstat exited $?" >&2; printf '{"files":0,"insertions":0,"deletions":0,"paths":[],"sensitive":true}\n' > ".spur/run/$wbs-diffstat.json"; }; exit 0
|
|
440
|
+
# (b)+(c) lane pre-guard BEFORE the decide: a caller-set mode is projected verbatim
|
|
441
|
+
# (R2b); a sensitive path or >400 changed lines pins safety (R2c) and the decide
|
|
442
|
+
# result is never consulted. A missing/malformed diffstat row pins safety HERE: an
|
|
443
|
+
# empty mode file would let the low decide row project the fast lane downstream, so
|
|
444
|
+
# the "never to fast" fail-safe is enforced at the guard, not by the projection.
|
|
445
|
+
- kind: shell
|
|
446
|
+
options:
|
|
447
|
+
command: >-
|
|
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).
|
|
452
|
+
- kind: decide
|
|
472
453
|
options:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
454
|
+
id: task-triage
|
|
455
|
+
decision: task-triage
|
|
456
|
+
params:
|
|
457
|
+
wbs: ${vars.wbs}
|
|
458
|
+
evidence:
|
|
459
|
+
- .spur/run/${vars.wbs}-diffstat.json
|
|
460
|
+
- ${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.
|
|
480
466
|
- kind: shell
|
|
481
467
|
options:
|
|
482
468
|
command: >-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
|
470
|
+
# (d) review depth from the resolved lane (D65 1239 R2): the fast lane reviews the
|
|
471
|
+
# requirement/AC traceability only — that table is the certification input, so the
|
|
472
|
+
# cheap lane never drops the evidence, it drops the SECUA/architecture passes.
|
|
473
|
+
- kind: shell
|
|
474
|
+
options:
|
|
475
|
+
command: >-
|
|
476
|
+
m="$(cat ".spur/run/$wbs-mode.txt" 2>/dev/null)"; if [ "$m" = fast ]; then printf 'functional\n' > ".spur/run/$wbs-review-focus.txt"; else printf 'all\n' > ".spur/run/$wbs-review-focus.txt"; fi; exit 0
|
|
477
|
+
# (d) the resolved lane becomes the `mode` var for the review-depth projection.
|
|
478
|
+
- kind: file.read.into-var
|
|
479
|
+
options:
|
|
480
|
+
path: .spur/run/${vars.wbs}-mode.txt
|
|
481
|
+
var: mode
|
|
482
|
+
- kind: file.read.into-var
|
|
483
|
+
options:
|
|
484
|
+
path: .spur/run/${vars.wbs}-review-focus.txt
|
|
485
|
+
var: reviewFocus
|
|
486
|
+
# D65 1239 R2 (mode by visit): the first gate visit of this run runs `run` — which
|
|
487
|
+
# resets the fix-attempt counter and consults the tree-hash receipt, so a tree that
|
|
488
|
+
# already passed is not re-gated (R11) — and every later visit after a remediation
|
|
489
|
+
# hop runs `recheck` (probe-then-full, same host-wide lock). The visit counter is
|
|
490
|
+
# run-scoped, so a stale attempt counter from an earlier run cannot start in recheck.
|
|
491
|
+
# Two shells: bump the visit counter, then select the mode from the bumped value
|
|
492
|
+
# (one concern each, ADR-115).
|
|
493
|
+
- kind: shell
|
|
485
494
|
options:
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
featureFile: ${vars.featureSpecPath}
|
|
489
|
-
# (d) `spur task gate` owns the gate; shell only picks the mode and fails closed
|
|
490
|
-
# (a non-0/1 exit — spur missing, usage error, crash — writes FAIL).
|
|
495
|
+
command: >-
|
|
496
|
+
mkdir -p .spur/run; V=".spur/run/$__runId-gate-visits"; n="$(cat "$V" 2>/dev/null || echo 0)"; printf '%s\n' "$n" > ".spur/run/$wbs-gate-visit.txt"; echo $((n + 1)) > "$V"; exit 0
|
|
491
497
|
- kind: shell
|
|
492
498
|
options:
|
|
493
499
|
command: >-
|
|
494
|
-
M=run; [ "$
|
|
500
|
+
n="$(cat ".spur/run/$wbs-gate-visit.txt" 2>/dev/null)"; M=run; [ "$n" = 0 ] || M=recheck; [ "$deferQualityGate" = "true" ] && M=deferred; printf '%s\n' "$M" > ".spur/run/$wbs-gate-mode.txt"; exit 0
|
|
501
|
+
- kind: file.read.into-var
|
|
502
|
+
options:
|
|
503
|
+
path: .spur/run/${vars.wbs}-gate-mode.txt
|
|
504
|
+
var: gateMode
|
|
505
|
+
# (d) `spur task gate` owns the gate (tier, probe, host-wide lock, tree-hash
|
|
506
|
+
# receipt); the typed outcome owns the routing. `softFail` keeps a red or blocked
|
|
507
|
+
# gate a RECORDED outcome — the run never dies mid-gate, and guards read
|
|
508
|
+
# `.spur/run/<runId>-gate.status` (PASS | FAIL | BLOCKED). Exit 2 (usage, lock
|
|
509
|
+
# timeout) is BLOCKED, never a red gate. The receipt reuse short-circuit inside
|
|
510
|
+
# `run` is what makes a second visit on an unchanged tree free (R11).
|
|
511
|
+
- kind: command.gate
|
|
512
|
+
options:
|
|
513
|
+
id: quality-gate
|
|
514
|
+
executable: "${vars.spurBin}"
|
|
515
|
+
args: ["task", "gate", "${vars.wbs}", "${vars.gateMode}", "--no-logo"]
|
|
516
|
+
resultFile: .spur/run/${vars.__runId}-gate.status
|
|
517
|
+
softFail: true
|
|
518
|
+
# (d) failure class of the gate this visit produced (0943 R3): stop → failed
|
|
519
|
+
# before the cap can mislabel it, fix → the bounded remediation hop. A missing or
|
|
520
|
+
# corrupt row fails closed to failed-check. Runs unconditionally (no onEnter
|
|
521
|
+
# conditionals) and degrades to the catalog `fix` fallback with no model call while
|
|
522
|
+
# the decision switch is off — the green path ignores it.
|
|
523
|
+
- kind: decide
|
|
524
|
+
options:
|
|
525
|
+
id: failure-class
|
|
526
|
+
decision: failure-class
|
|
527
|
+
params:
|
|
528
|
+
wbs: ${vars.wbs}
|
|
529
|
+
evidence:
|
|
530
|
+
- .spur/run/${vars.wbs}-test-gate.findings
|
|
531
|
+
resultFile: .spur/run/${vars.wbs}-failure-class.decision
|
|
495
532
|
|
|
496
|
-
|
|
533
|
+
# ── remediate hop (bounded repair; former `test-fix`) ─────────────────────
|
|
534
|
+
- id: remediate
|
|
497
535
|
display:
|
|
498
536
|
phase: test
|
|
499
537
|
phaseTitle: Test
|
|
500
538
|
title: Fix failures
|
|
501
539
|
show: on-entry
|
|
502
540
|
description: >
|
|
503
|
-
Bounded auto-fix hop when the quality gate is red OR
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
541
|
+
Bounded auto-fix hop when the quality gate is red OR the review returned a repairable
|
|
542
|
+
non-PASS: increments `.spur/run/<wbs>-test-fix-attempt` (the shared bound, so a review
|
|
543
|
+
FAIL cannot buy extra attempts), projects the review verdict into the gate log when one
|
|
544
|
+
exists so the repair hop sees it, then pure slash (ADR-043) `/sp:dev-fixall` against
|
|
545
|
+
`${vars.qualityGateCmd}`. The next state is `gate`, whose visit-counter mode is
|
|
546
|
+
`recheck`, so the chain reruns gate → review on the remediated tree.
|
|
509
547
|
onEnter:
|
|
510
548
|
- kind: shell
|
|
511
549
|
options:
|
|
512
550
|
command: >-
|
|
513
|
-
TASK_POLICY=$($spurBin task show "$wbs" --json | jq -er '.frontmatter.mutationPolicy // ([.content | scan("(?m)^mutationPolicy:[ \t]*([^ \r\n]+)[ \t]*$") | .[0]] | if length == 0 then "code" elif length == 1 then .[0] else "ambiguous" end)') || exit 1; [ "$mutationPolicy:$TASK_POLICY" = "code:code" ] || { echo "
|
|
514
|
-
# (e)
|
|
515
|
-
#
|
|
551
|
+
TASK_POLICY=$($spurBin task show "$wbs" --json | jq -er '.frontmatter.mutationPolicy // ([.content | scan("(?m)^mutationPolicy:[ \t]*([^ \r\n]+)[ \t]*$") | .[0]] | if length == 0 then "code" elif length == 1 then .[0] else "ambiguous" end)') || exit 1; [ "$mutationPolicy:$TASK_POLICY" = "code:code" ] || { echo "remediate: mutation policy forbids automatic code repair (run=$mutationPolicy, task=$TASK_POLICY) — gate failure preserved, no agent dispatched" >&2; exit 1; }
|
|
552
|
+
# (e) attempt counter + residual artifact projection in one log append
|
|
553
|
+
# (1007 R3: the two appends merged — original commands in order, exit 0).
|
|
516
554
|
# (d) F96 R3 (0950): dev-fixall treats residual items as fix targets and may defer
|
|
517
|
-
# P3/markers to residual-deferrals.json
|
|
518
|
-
# anchors into <wbs>-test-gate.findings, this carries the structured artifact too.
|
|
555
|
+
# P3/markers to residual-deferrals.json; this carries the structured artifact too.
|
|
519
556
|
- kind: shell
|
|
520
557
|
options:
|
|
521
558
|
command: >-
|
|
522
|
-
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-
|
|
523
|
-
# Review-lane input
|
|
524
|
-
#
|
|
525
|
-
#
|
|
526
|
-
#
|
|
527
|
-
#
|
|
528
|
-
# `review-fail-triage`). Own action, not an append to the one above: ADR-115's 800-char
|
|
529
|
-
# shell cap is already 602 chars there.
|
|
559
|
+
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).
|
|
530
565
|
- kind: shell
|
|
531
566
|
options:
|
|
532
567
|
command: >-
|
|
@@ -542,17 +577,16 @@ states:
|
|
|
542
577
|
options:
|
|
543
578
|
agent: ${vars.agent}
|
|
544
579
|
# R3 (0482): `--findings` names the failing anchors inline; `--gate-log` remains
|
|
580
|
+
# the full-context escape hatch.
|
|
545
581
|
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
546
582
|
role: coder
|
|
547
583
|
# B7 R3/R6 (0894): coder stages reuse the role session — the fix hop
|
|
548
584
|
# continues the implement session it is repairing.
|
|
549
585
|
session: reuse
|
|
550
|
-
# the full-context escape hatch when the digest is not enough.
|
|
551
586
|
input: /sp:dev-fixall "${vars.qualityGateCmd}" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings "${vars.gateFindings}"
|
|
552
587
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
553
588
|
# The captured remediation answer is the run's dispatch evidence (0823 R1):
|
|
554
|
-
# expectFile fails the step when no answer was captured.
|
|
555
|
-
# the review proof digest and the verify verdict.
|
|
589
|
+
# expectFile fails the step when no answer was captured.
|
|
556
590
|
answerFile: .spur/run/${vars.__runId}-test-fix-answer.txt
|
|
557
591
|
expectFile: .spur/run/${vars.__runId}-test-fix-answer.txt
|
|
558
592
|
# 0706 R6: bounded remediation hop — unattended and tree-mutating,
|
|
@@ -561,162 +595,30 @@ states:
|
|
|
561
595
|
fsWrite: available
|
|
562
596
|
processSpawn: available
|
|
563
597
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
onEnter:
|
|
576
|
-
# R4 (0703, ADR-071): bounded remediation mutated the tree by design, so the fresh evidence
|
|
577
|
-
# chain (recheck gate → review → verify) must start from a NEWLY captured digest. Capture-only;
|
|
578
|
-
# the guards and `record` compare against this value.
|
|
579
|
-
# 1135 R5: re-snapshot the tree state after the remediation, so the refresh does not compare
|
|
580
|
-
# against a pre-remediation snapshot (a legitimate test-fix edit must not read as drift).
|
|
581
|
-
- kind: shell
|
|
582
|
-
options:
|
|
583
|
-
command: >-
|
|
584
|
-
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
|
|
585
|
-
- kind: proof.fingerprint
|
|
586
|
-
options:
|
|
587
|
-
var: proofDigest
|
|
588
|
-
taskFile: ${vars.taskSpecPath}
|
|
589
|
-
featureFile: ${vars.featureSpecPath}
|
|
590
|
-
# 0587 R3: probe-then-full recheck. A red gateProbeCmd records FAIL and skips the full
|
|
591
|
-
# gate (the measured waste is re-running a 110–140s gate to learn the tree is still red);
|
|
592
|
-
# a green probe (or empty gateProbeCmd) falls through to the full-gate loop unchanged.
|
|
593
|
-
# Only the full gate writes PASS, so the `test-recheck → review` guard (reads PASS)
|
|
594
|
-
# still means a full green qualityGateCmd ran — invariant preserved by construction.
|
|
595
|
-
# (d) `spur task gate` owns the recheck; shell only picks the mode and fails closed
|
|
596
|
-
# (0587 R3 probe-then-full: a red gateProbeCmd is the recheck verdict, the full gate is skipped).
|
|
597
|
-
- kind: shell
|
|
598
|
-
options:
|
|
599
|
-
command: >-
|
|
600
|
-
M=recheck; [ "$deferQualityGate" = "true" ] && M=deferred; $spurBin task gate "$wbs" "$M" --no-logo; rc=$?; if [ "$rc" -gt 1 ]; then echo "quality gate failed closed — spur task gate exited $rc" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
|
|
601
|
-
|
|
602
|
-
- id: triage
|
|
603
|
-
display:
|
|
604
|
-
phase: review
|
|
605
|
-
phaseTitle: Review
|
|
606
|
-
title: Choose review depth
|
|
607
|
-
description: >-
|
|
608
|
-
Deterministic lane router between a green gate and the review/verify fork (0943 R1).
|
|
609
|
-
Writes the diffstat evidence file, honors a caller-set `mode` (never overridden), pins
|
|
610
|
-
the safety lane on sensitive paths or >400 changed lines without a model call (R2c),
|
|
611
|
-
otherwise consults `decide task-triage` (low → fast lane; anything else stays empty →
|
|
612
|
-
standard review path), and appends the triage route reason to
|
|
613
|
-
.spur/memory/task-pipeline-routes.log (R5). The `decide` action itself runs
|
|
614
|
-
unconditionally (the engine has no onEnter conditionals); its result is consulted only
|
|
615
|
-
when neither a caller mode nor the deterministic guard resolved the lane. The resolved
|
|
616
|
-
lane lands in the `mode` var for the triage → verify/review guards.
|
|
617
|
-
onEnter:
|
|
618
|
-
# (a) diffstat producer: `spur task diffstat` owns git reading and sensitive-path
|
|
619
|
-
# classification; shell only fails closed on a non-zero exit (writes sensitive:true,
|
|
620
|
-
# which the pre-guard below maps to the safety lane).
|
|
621
|
-
- kind: shell
|
|
622
|
-
options:
|
|
623
|
-
command: >-
|
|
624
|
-
$spurBin task diffstat "$wbs" --no-logo || { echo "task-diffstat failed closed — spur task diffstat exited $?" >&2; printf '{"files":0,"insertions":0,"deletions":0,"paths":[],"sensitive":true}\n' > ".spur/run/$wbs-diffstat.json"; }; exit 0
|
|
625
|
-
# (b)+(c) lane pre-guard BEFORE decide: a caller-set mode is projected verbatim (R2b);
|
|
626
|
-
# a sensitive path or >400 changed lines pins safety (R2c) and the decide result below
|
|
627
|
-
# is never consulted. A missing/malformed diffstat row (producer crashed or never ran,
|
|
628
|
-
# disk error) pins safety HERE: an empty mode file would let the low decide row project
|
|
629
|
-
# the fast lane downstream, so the "never to fast" fail-safe is enforced at the guard,
|
|
630
|
-
# not by the projection (the full-suite gate run exposed this fail-open path).
|
|
631
|
-
- kind: shell
|
|
632
|
-
options:
|
|
633
|
-
command: >-
|
|
634
|
-
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
|
|
635
|
-
# (d) lane decision — degrades to the standard default whenever workflows.decideDecisionMaker
|
|
636
|
-
# is off (R4), so with the flag off this state is pure shell: no model call is made.
|
|
637
|
-
# 1114: the inline decide moved to the bundled catalog (`config/decisions/task-pipeline.yaml`
|
|
638
|
-
# → `task-triage`), whose R0 samples measured this exact prompt before the migration.
|
|
639
|
-
# Routing contract is unchanged: same evidence, same resultFile, same guards, same
|
|
640
|
-
# `standard` fallback when the switch is off (field-identical to this form's default).
|
|
641
|
-
- kind: decide
|
|
642
|
-
options:
|
|
643
|
-
id: task-triage
|
|
644
|
-
decision: task-triage
|
|
645
|
-
params:
|
|
646
|
-
wbs: ${vars.wbs}
|
|
647
|
-
evidence:
|
|
648
|
-
- .spur/run/${vars.wbs}-diffstat.json
|
|
649
|
-
- ${vars.taskSpecPath}
|
|
650
|
-
resultFile: .spur/run/${vars.wbs}-triage.decision
|
|
651
|
-
# (d) project the decision, then append the triage route reason (1007 R3: the
|
|
652
|
-
# mode-fallback shell merged into the memory-append shell — original commands in
|
|
653
|
-
# order, the non-empty mode file is still never overridden; the append keeps the
|
|
654
|
-
# failure signal of the original pair). low → fast lane; anything else (standard/high,
|
|
655
|
-
# degraded default, missing row) leaves the mode empty → standard review path.
|
|
656
|
-
- kind: shell
|
|
657
|
-
options:
|
|
658
|
-
command: >-
|
|
659
|
-
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
|
|
660
|
-
# (d) the resolved lane becomes the `mode` var for the triage → verify/review guards.
|
|
661
|
-
- kind: file.read.into-var
|
|
662
|
-
options:
|
|
663
|
-
path: .spur/run/${vars.wbs}-mode.txt
|
|
664
|
-
var: mode
|
|
665
|
-
|
|
666
|
-
- id: test-fail-triage
|
|
667
|
-
display:
|
|
668
|
-
phase: test
|
|
669
|
-
phaseTitle: Test
|
|
670
|
-
title: Triage test failure
|
|
671
|
-
show: on-entry
|
|
672
|
-
description: >-
|
|
673
|
-
Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
|
|
674
|
-
conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
|
|
675
|
-
deterministic state (the sanctioned alternative), while the green branch goes to
|
|
676
|
-
`triage`. `decide failure-class` reads the bounded gate findings and routes: fix →
|
|
677
|
-
test-fix (the bounded repair hop; its default, recorded as `source: default` when no
|
|
678
|
-
backend decides — 0976 R2), stop → failed with terminalReason failed-check. A
|
|
679
|
-
missing/corrupt decision fails closed to failed-check. (0976 R2 collapsed the former
|
|
680
|
-
`retryable` lane: the declared default made it unreachable on the inline path.)
|
|
681
|
-
onEnter:
|
|
682
|
-
# 1115: catalog-reference decide (task 1094 form) — question/choices/fallback live in
|
|
683
|
-
# the bundled catalog (`config/decisions/task-pipeline.yaml` → `failure-class`), whose
|
|
684
|
-
# R0 samples measured this exact prompt before the migration. Routing contract is
|
|
685
|
-
# unchanged: same resultFile, same guards, same `fix` fallback when the switch is off.
|
|
686
|
-
- kind: decide
|
|
687
|
-
options:
|
|
688
|
-
id: failure-class
|
|
689
|
-
decision: failure-class
|
|
690
|
-
params:
|
|
691
|
-
wbs: ${vars.wbs}
|
|
692
|
-
evidence:
|
|
693
|
-
- .spur/run/${vars.wbs}-test-gate.findings
|
|
694
|
-
resultFile: .spur/run/${vars.wbs}-failure-class.decision
|
|
695
|
-
|
|
598
|
+
# ── review hop (certification: verdict + R/AC traceability + approval decision) ──
|
|
599
|
+
# D65 1239 R2: `verify` merged into `review`. The reviewer's answer must carry the
|
|
600
|
+
# requirement/AC traceability table (`| Req | Status | Evidence |`, evidence = `file:line`
|
|
601
|
+
# or a named test/command) — sp:functional-review produces it and the answer contract owns
|
|
602
|
+
# the shape. `spur task verdict --from-answer` lints that answer, derives PASS/PARTIAL/FAIL
|
|
603
|
+
# from the rows and writes the canonical `.spur/run/<wbs>-verdict.json` that `spur task
|
|
604
|
+
# record` consumes for box flips and `## Testing`; a review whose rows do not resolve (an
|
|
605
|
+
# UNMET requirement, a missing row, a bad AC identity) cannot derive PASS. The review-gate
|
|
606
|
+
# residual check (1122) still blocks an open P1-P3 finding. Everything is folded into one
|
|
607
|
+
# status token the transitions read, so a non-PASS review routes into the bounded repair
|
|
608
|
+
# hop BEFORE `done` spends the close.
|
|
696
609
|
- id: review
|
|
697
610
|
display:
|
|
698
611
|
phase: review
|
|
699
612
|
phaseTitle: Review
|
|
700
613
|
title: Review changes
|
|
701
614
|
description: >-
|
|
702
|
-
Three-dimensional code review via /sp:dev-review (functional requirements traceability
|
|
703
|
-
SECUA framework
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
quality → review → verify on a fresh digest; only P4 rows and DEFER-ed P3 rows are wrap
|
|
709
|
-
residuals.
|
|
615
|
+
Three-dimensional code review via /sp:dev-review (functional requirements traceability
|
|
616
|
+
+ SECUA framework + architecture depth), findings written to `## Review`, then
|
|
617
|
+
deterministic certification: `spur task verdict --from-answer` over the reviewer's
|
|
618
|
+
own traceability table, the review-gate residual check, and the operator/maker
|
|
619
|
+
approval decision point. Cost at this decision point (1122): open P1-P3 review
|
|
620
|
+
findings block `done`; only P4 rows and DEFER-ed P3 rows are wrap residuals.
|
|
710
621
|
onEnter:
|
|
711
|
-
# 1135 R4: review joins the proof bracket. A pre-review compare refuses to spend a review
|
|
712
|
-
# dispatch on a state that has drifted from the certified digest; the mismatch routes
|
|
713
|
-
# through the existing tripwire to `failed` (no new re-certify path).
|
|
714
|
-
- kind: proof.fingerprint
|
|
715
|
-
options:
|
|
716
|
-
var: proofDigestNow
|
|
717
|
-
taskFile: ${vars.taskSpecPath}
|
|
718
|
-
featureFile: ${vars.featureSpecPath}
|
|
719
|
-
expect: ${vars.proofDigest}
|
|
720
622
|
- kind: agent.run
|
|
721
623
|
options:
|
|
722
624
|
# 0710 R2: review always runs on a fresh session — no implementation-session
|
|
@@ -726,10 +628,25 @@ states:
|
|
|
726
628
|
# through the executor registry, and the runner enforces (pre-dispatch,
|
|
727
629
|
# fail-closed) that a P0/P1 task's review resolves a DIFFERENT executor
|
|
728
630
|
# spec than the implement stage recorded in __agentRouting_implement.
|
|
729
|
-
|
|
730
|
-
#
|
|
731
|
-
#
|
|
732
|
-
# the
|
|
631
|
+
# 0943 R1/R2: `--focus` carries the resolved lane depth — `functional` for the
|
|
632
|
+
# fast lane (traceability, which is the certification input), `all` otherwise.
|
|
633
|
+
# D65 1240: this answer IS the certification input — `spur task verdict --from-answer`
|
|
634
|
+
# derives the run's verdict from its requirement rows, so the answer contract is part
|
|
635
|
+
# of the prompt. A report body ending with a `| WBS | Verdict | P1 | … |` summary and
|
|
636
|
+
# no per-requirement rows derives UNKNOWN and can never certify, which looped every
|
|
637
|
+
# run through review -> remediate.
|
|
638
|
+
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.
|
|
648
|
+
# The captured review answer is the run's certification input (and the
|
|
649
|
+
# `task verdict` input below): expectFile fails the step when no answer landed.
|
|
733
650
|
answerFile: .spur/run/${vars.__runId}-review-answer.txt
|
|
734
651
|
expectFile: .spur/run/${vars.__runId}-review-answer.txt
|
|
735
652
|
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
@@ -742,45 +659,33 @@ states:
|
|
|
742
659
|
priority: ${vars.taskPriority}
|
|
743
660
|
compareExecutorWith: implement
|
|
744
661
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
745
|
-
#
|
|
746
|
-
#
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
#
|
|
750
|
-
#
|
|
751
|
-
#
|
|
752
|
-
|
|
662
|
+
# (d) `spur task verdict` owns the answer lint and the verdict derivation (task 1003
|
|
663
|
+
# R2): one complete requirement row per declared requirement, AC identities resolved,
|
|
664
|
+
# status/evidence vocabularies accepted, hedged or uncited evidence rejected. It
|
|
665
|
+
# writes `.spur/run/<wbs>-verdict.json` — the review verdict artifact `spur task
|
|
666
|
+
# record` consumes — and exits 1 on a non-PASS verdict BY DESIGN, which `softFail`
|
|
667
|
+
# records as the typed outcome the guards route. A lint failure (exit 1) is a failed
|
|
668
|
+
# certification, never a warning, and there is no grep fallback.
|
|
669
|
+
- kind: command.gate
|
|
670
|
+
options:
|
|
671
|
+
id: review-verdict
|
|
672
|
+
executable: "${vars.spurBin}"
|
|
673
|
+
args: ["task", "verdict", "${vars.wbs}", "--from-answer", ".spur/run/${vars.__runId}-review-answer.txt"]
|
|
674
|
+
resultFile: .spur/run/${vars.__runId}-review-verdict.status
|
|
675
|
+
softFail: true
|
|
676
|
+
# (e) one fold, one token: PASS requires the typed verdict PASS, the artifact's own
|
|
677
|
+
# PASS, and a clean review gate (open P1-P3 finding or an uncited claim blocks it).
|
|
678
|
+
# Fail-closed: any missing/corrupt input leaves FAIL, so the transitions can route a
|
|
679
|
+
# non-PASS review into remediation instead of dead-ending the run.
|
|
753
680
|
- kind: shell
|
|
754
681
|
options:
|
|
755
|
-
command:
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
# (`.spur/run/<wbs>-test-fix-attempt` against `qualityGateMaxFixAttempts`), and the fix
|
|
763
|
-
# re-enters quality → review → verify on a freshly captured digest. The `decide` action
|
|
764
|
-
# itself always runs (the engine has no onEnter conditionals); with
|
|
765
|
-
# `workflows.decideDecisionMaker` off it degrades to its declared default.
|
|
766
|
-
- id: review-fail-triage
|
|
767
|
-
display:
|
|
768
|
-
phase: review
|
|
769
|
-
phaseTitle: Review
|
|
770
|
-
title: Triage review findings
|
|
771
|
-
show: on-entry
|
|
772
|
-
description: >-
|
|
773
|
-
Deterministic router for a review that did NOT certify PASS (session finding after 1088).
|
|
774
|
-
`decide review-failure-class` reads the reviewer's answer and the task spec and routes:
|
|
775
|
-
fix → the shared test-fix hop, stop → failed with terminalReason failed-check. A
|
|
776
|
-
missing/corrupt decision fails closed to failed-check. Cost either way (1122): open
|
|
777
|
-
P1-P3 review findings block `done`; the fix lane re-enters quality → review → verify on a
|
|
778
|
-
fresh digest; only P4 rows and DEFER-ed P3 rows are wrap residuals.
|
|
779
|
-
onEnter:
|
|
780
|
-
# 1116: the inline decide moved to the bundled catalog (`config/decisions/task-pipeline.yaml`
|
|
781
|
-
# → `review-failure-class`), whose R0 samples measured this exact prompt before the
|
|
782
|
-
# migration. Routing contract is unchanged: same evidence, same resultFile, same guards,
|
|
783
|
-
# same `fix` fallback when the switch is off.
|
|
682
|
+
command: >-
|
|
683
|
+
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.
|
|
784
689
|
- kind: decide
|
|
785
690
|
options:
|
|
786
691
|
id: review-failure-class
|
|
@@ -791,170 +696,68 @@ states:
|
|
|
791
696
|
- .spur/run/${vars.__runId}-review-answer.txt
|
|
792
697
|
- ${vars.taskSpecPath}
|
|
793
698
|
resultFile: .spur/run/${vars.wbs}-review-failure-class.decision
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
description: >
|
|
802
|
-
Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
|
|
803
|
-
(review routes around it straight to verify). In interactive mode this state pauses
|
|
804
|
-
the run for `spur workflow continue` (E3), making approval an explicit operator action.
|
|
805
|
-
The operator's answer is routed (R1, bug-750): yes -> verify; no -> failed (rejection
|
|
806
|
-
recorded, not silently approved); cancel -> cancelled (a distinct terminal state).
|
|
807
|
-
pause: true
|
|
808
|
-
onEnter:
|
|
699
|
+
# (d) Operator approval as a DECISION POINT, not a state (D65 1239 R1/R5, ADR-140
|
|
700
|
+
# item 5): interactively the operator responder answers; under `profile=auto` the
|
|
701
|
+
# declared binding answers (evidence status var, catalog fallback when the maker
|
|
702
|
+
# degrades), so `--auto` never prompts a human and can still deny. A `no`/`cancel`
|
|
703
|
+
# (or an unanswered interactive gate) routes to failed with terminalReason
|
|
704
|
+
# `cancelled` — a recorded denial, distinct from a gate or review failure. The trace
|
|
705
|
+
# records the answering source (operator / maker / catalog-fallback, 1236).
|
|
809
706
|
- kind: hitl.confirm
|
|
810
707
|
options:
|
|
811
|
-
prompt: "Approve task ${vars.wbs} to proceed to
|
|
708
|
+
prompt: "Approve task ${vars.wbs} to proceed to completion?"
|
|
812
709
|
decision:
|
|
813
|
-
mode:
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
Observe-only functional verification (BDD + traceability) via /sp:dev-verify --fix none
|
|
822
|
-
(task 0703 R1, ADR-071): the verifier certifies the state, it never repairs its own subject.
|
|
823
|
-
The answer-shape contract this stage enforces is OWNED BY the skill, not restated here
|
|
824
|
-
(task 1110 R3): see `plugins/sp/skills/code-verification/SKILL.md` § Answer-File Schema
|
|
825
|
-
Contract (`:340-375`), with the row-id/evidence vocabularies in
|
|
826
|
-
`plugins/sp/skills/code-verification/references/verdict-schema.md`. The YAML only names
|
|
827
|
-
the lint that consumes it (`task verdict --from-answer`).
|
|
828
|
-
A live digest compare BEFORE the agent refuses to certify a state that drifted after the
|
|
829
|
-
quality/review evidence was produced (R2). The agent's
|
|
830
|
-
captured answer is written to `.spur/run/<wbs>-verify-answer.txt` and MUST follow
|
|
831
|
-
the answer-file schema contract (explicit `Verdict: PASS|PARTIAL|FAIL` line plus
|
|
832
|
-
`| Req | Status | Evidence |` and `| AC | Status | Evidence Type | Evidence |` tables);
|
|
833
|
-
a deterministic shell step then derives the verdict and writes the gate artifact
|
|
834
|
-
`.spur/run/<wbs>-verdict.json` (so the completion gate reads a real verdict, not
|
|
835
|
-
agent discretion — R9) with a proof block naming the digest and the per-stage results (R3).
|
|
836
|
-
Repairable non-PASS routes once through the bounded remediation hop (verify → test-fix,
|
|
837
|
-
R4); the chain reruns on a fresh digest.
|
|
838
|
-
onEnter:
|
|
839
|
-
# R2 (0703): midpoint bracket compare — refuse to spend verification on a state that no
|
|
840
|
-
# longer matches the digest the quality/review evidence names. Reuses `proofDigestNow`:
|
|
841
|
-
# set here and re-set by the bound `run.artifact` registration at `record` entry (0785 R3).
|
|
842
|
-
- kind: proof.fingerprint
|
|
843
|
-
options:
|
|
844
|
-
var: proofDigestNow
|
|
845
|
-
taskFile: ${vars.taskSpecPath}
|
|
846
|
-
featureFile: ${vars.featureSpecPath}
|
|
847
|
-
expect: ${vars.proofDigest}
|
|
848
|
-
- kind: agent.run
|
|
849
|
-
options:
|
|
850
|
-
# 0710 R2: verify is a separate fresh-session execution — never the review
|
|
851
|
-
# session, never the implement session (R6).
|
|
852
|
-
# 0710 R4/R5: role-routed like review; P0/P1 demands a distinct executor.
|
|
853
|
-
# R1 (0703, ADR-071): `--fix none` — final verification is observe-only. Remediation
|
|
854
|
-
# belongs to the bounded test-fix hop, never to the certifying pass.
|
|
855
|
-
input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
|
|
856
|
-
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
857
|
-
role: reviewer
|
|
858
|
-
# B7 R3/R6 (0894): declared fresh policy (reviewer default).
|
|
859
|
-
# `freshSession: true` kept as the action-level hard guarantee (0710 R2).
|
|
860
|
-
freshSession: true
|
|
861
|
-
session: fresh
|
|
862
|
-
priority: ${vars.taskPriority}
|
|
863
|
-
compareExecutorWith: implement
|
|
864
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
865
|
-
expectFile: .spur/run/${vars.wbs}-verify-answer.txt
|
|
866
|
-
# (d) `spur task verdict` owns the answer lint (task 1003 R2) — the shell below
|
|
867
|
-
# fails closed when the verifier's answer is malformed, before any verdict artifact.
|
|
868
|
-
- kind: shell
|
|
869
|
-
# `task verdict` exits 1 on a non-PASS verdict by design (1003 R2). The `continue`
|
|
870
|
-
# policy keeps that exit code as recorded evidence (the action row is still `failed`)
|
|
871
|
-
# while letting the state finish its remaining actions and reach the guards below;
|
|
872
|
-
# without it the default 'fail' policy halts the run here and `verify → test-fix`
|
|
873
|
-
# (the declared bounded remediation lane) can never fire (v5, task 1077 run).
|
|
874
|
-
onError: continue
|
|
875
|
-
options:
|
|
876
|
-
command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
|
|
877
|
-
# (e) one jq mutation binds the verdict to the proof digest (0703 R3; runId 0730 §B.2 /
|
|
878
|
-
# 0757 R4; definitionDigest 0759 R5; honest review stamp 0785 R4; soft action + hard guard).
|
|
710
|
+
mode: evidence
|
|
711
|
+
statusVar: approvalStatus
|
|
712
|
+
evidenceNodes:
|
|
713
|
+
- review
|
|
714
|
+
# (e) The denial is EVIDENCE, not just a variable: fold the answer into one recorded
|
|
715
|
+
# approval token the transitions read. `profile=auto` proceeds unless the maker said
|
|
716
|
+
# `no` (a deferred answer is not a denial — no maker means no objection under auto);
|
|
717
|
+
# interactively only an explicit `yes` approves. Fail-closed to DENIED.
|
|
879
718
|
- kind: shell
|
|
880
719
|
options:
|
|
881
720
|
command: >-
|
|
882
|
-
|
|
721
|
+
mkdir -p .spur/run; A=APPROVED; if [ "$profile" = auto ]; then [ "$__hitlAnswer" = no ] && A=DENIED; else [ "$__hitlAnswer" = yes ] || A=DENIED; fi; printf '%s\n' "$A" > ".spur/run/$wbs-approval.status"; exit 0
|
|
883
722
|
|
|
884
|
-
- id:
|
|
885
|
-
display:
|
|
886
|
-
phase: verify
|
|
887
|
-
phaseTitle: "Verify & record"
|
|
888
|
-
title: Record evidence
|
|
723
|
+
- id: done
|
|
889
724
|
description: >
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
`
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
conditionally syncs feature status via `spur feature sync`
|
|
897
|
-
(task 0411 retry-suppression) if `feature_id` is present, or appends an orphan
|
|
898
|
-
link proposal to the run report if absent (task 0328 / ADR-0322). The post-record
|
|
899
|
-
residual sweep (0983) re-scans the task file after the verdict-proven box flips and
|
|
900
|
-
folds blocking leftovers into the verdict before `record → done`; a downgrade
|
|
901
|
-
re-records Testing from the final artifact so a failed task never keeps a stale
|
|
902
|
-
PASS narrative.
|
|
725
|
+
Terminal — the task is closed deterministically. onEnter performs the close the
|
|
726
|
+
former `record` state owned: `spur task record` writes Testing/Review from the review
|
|
727
|
+
verdict and flips the requirement/AC boxes it proves, the residual sweep folds any
|
|
728
|
+
leftover, and `spur task update <wbs> done --no-lifecycle` runs the verb's own
|
|
729
|
+
target-aware `task check` as the completion gate — a task whose boxes or evidence do
|
|
730
|
+
not certify cannot reach the stored `done` status.
|
|
903
731
|
onEnter:
|
|
904
|
-
#
|
|
905
|
-
#
|
|
906
|
-
#
|
|
907
|
-
# — before the ledger write AND before `task record` — unless: the run's declared digest
|
|
908
|
-
# equals a fresh capture over the canonical task spec (+ linked feature when linked);
|
|
909
|
-
# the verdict artifact is canonical-valid PASS for this wbs; its raw proof block names
|
|
910
|
-
# the fresh digest, the certifying run id, and the run row's resume/definition digest;
|
|
911
|
-
# qualityGate and verification stages are PASS with that digest; review is completed and
|
|
912
|
-
# independently evidenced by the run-scoped marker `<runId>-review-proof.digest`
|
|
913
|
-
# (0785 R4). Task-spec evidence writes (Testing/Review/Solution, R6) happen only in the
|
|
914
|
-
# actions AFTER this registration, and the fingerprint scopes task content to the
|
|
915
|
-
# proof-input sections, so they cannot invalidate the certified input set.
|
|
916
|
-
- kind: run.artifact
|
|
917
|
-
options:
|
|
918
|
-
path: .spur/run/${vars.wbs}-verdict.json
|
|
919
|
-
artifactKind: verify-verdict
|
|
920
|
-
proofBinding: current
|
|
921
|
-
taskFile: ${vars.taskSpecPath}
|
|
922
|
-
featureFile: ${vars.featureSpecPath}
|
|
923
|
-
# (c) command.gate owns the record write with classified transient retry.
|
|
732
|
+
# (c) command.gate owns the record write with classified transient retry. 0980:
|
|
733
|
+
# --no-lifecycle mirrors the wip/done hops — the pipeline run is already the lifecycle
|
|
734
|
+
# record, so a record-stage transition must not spawn a nested task-lifecycle run.
|
|
924
735
|
- kind: command.gate
|
|
925
736
|
options:
|
|
926
|
-
id: task-record-
|
|
737
|
+
id: task-record-close
|
|
927
738
|
executable: "${vars.spurBin}"
|
|
928
|
-
# 0980: --no-lifecycle mirrors the wip/done hops — the pipeline run is
|
|
929
|
-
# already the lifecycle record, so a record-stage transition must not
|
|
930
|
-
# spawn a nested task-lifecycle run that outlives the pipeline as a
|
|
931
|
-
# `running` orphan. The target-aware check gate still runs (inline).
|
|
932
739
|
args: ["task", "record", "${vars.wbs}", "--solution-from-diff", "--transition", "testing", "--no-lifecycle"]
|
|
933
740
|
retry:
|
|
934
741
|
maxAttempts: 2
|
|
935
742
|
delayMs: 2000
|
|
936
743
|
on: ["sqlite-busy", "ENOENT", "EBUSY", "ENOTEMPTY"]
|
|
937
|
-
resultFile: .spur/run/${vars.__runId}-record
|
|
744
|
+
resultFile: .spur/run/${vars.__runId}-record.status
|
|
938
745
|
softFail: false
|
|
939
746
|
timeoutMs: 120000
|
|
940
|
-
# (d) Best-effort `exit 0` — feature status sync is a follow-up, not a completion gate
|
|
941
|
-
#
|
|
942
|
-
#
|
|
943
|
-
#
|
|
944
|
-
#
|
|
945
|
-
# BLOCKED suppression (0411) now lives in the feature sync service itself, so the shell
|
|
946
|
-
# resolves feature_id via jq and delegates without a helper script.
|
|
747
|
+
# (d) Best-effort `exit 0` — feature status sync is a follow-up, not a completion gate.
|
|
748
|
+
# 0931 R5: with deferFeatureSync "true" (parallel mode) the deferral note lands in the
|
|
749
|
+
# task report and the sync is skipped entirely, so task branches never touch feature
|
|
750
|
+
# corpus files. 1004 R4: the sync is a bare `feature sync` — repeated-BLOCKED
|
|
751
|
+
# suppression (0411) lives in the feature sync service itself.
|
|
947
752
|
- kind: shell
|
|
948
753
|
options:
|
|
949
754
|
command: >-
|
|
950
755
|
if [ "$deferFeatureSync" = "true" ]; then echo "feature sync deferred to batch integration" >> ".spur/run/$wbs-report.txt"; else FID="$($spurBin task show $wbs --json 2>/dev/null | jq -r '(.feature_id // .frontmatter.feature_id) // empty' 2>/dev/null)"; if [ -n "$FID" ]; then $spurBin feature sync "$FID" --json || echo "feature sync failed — re-run: $spurBin feature sync $FID" >> ".spur/run/$wbs-report.txt"; else echo "Orphan task $wbs — no feature_id linked — proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt"; fi; fi; exit 0
|
|
951
756
|
# (d) F96 (0983): `spur task residual` owns the sweep; shell only sequences it. The
|
|
952
|
-
# scan
|
|
953
|
-
#
|
|
954
|
-
#
|
|
955
|
-
#
|
|
956
|
-
# unfinished Plan) stays blocking and fails the record → done guard closed.
|
|
957
|
-
# ONE hard action (scan+fold): a scanner crash fails record closed.
|
|
757
|
+
# scan runs AFTER `task record` flipped the verdict-proven boxes, or a proven-but-
|
|
758
|
+
# unticked box folds every good run to PARTIAL and the done gate is unreachable
|
|
759
|
+
# (0967). Any box still unchecked post-record stays blocking.
|
|
760
|
+
# ONE hard action (scan+fold): a scanner crash fails the close closed.
|
|
958
761
|
- kind: shell
|
|
959
762
|
options:
|
|
960
763
|
command: >-
|
|
@@ -962,22 +765,19 @@ states:
|
|
|
962
765
|
# (e) F96 R5 (0983): a sweep downgrade must reach the task record — re-record from
|
|
963
766
|
# the FINAL (post-fold) artifact so Testing carries the downgraded verdict; a failed
|
|
964
767
|
# task must not retain a stale PASS Testing narrative. Soft (exit 0): the downgrade
|
|
965
|
-
# itself
|
|
966
|
-
#
|
|
967
|
-
# 0980: --no-lifecycle for the same orphan reason as the main record command —
|
|
968
|
-
# inert today (the task is already at testing, so this is a same-status no-op),
|
|
969
|
-
# but it keeps the no-nested-lifecycle invariant if the flow shifts.
|
|
768
|
+
# itself fails the re-assert below.
|
|
769
|
+
# 0980: --no-lifecycle for the same orphan reason as the main record command.
|
|
970
770
|
- kind: shell
|
|
971
771
|
options:
|
|
972
772
|
command: >-
|
|
973
773
|
verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)"; if [ -n "$verdict" ] && [ "$verdict" != PASS ]; then $spurBin task record $wbs --solution-from-diff --transition testing --no-lifecycle || echo "post-downgrade re-record failed — re-run: $spurBin task record $wbs" >&2; fi; exit 0
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
774
|
+
# (e) The completion contract, re-asserted after the sweep: a post-record residual
|
|
775
|
+
# downgrade must block the close (hard action, no `; exit 0`). The verdict artifact is
|
|
776
|
+
# the review's own evidence, not a re-captured digest — the old proof re-assertion is
|
|
777
|
+
# gone, the fail-closed check on the verdict it certifies is not.
|
|
778
|
+
- kind: shell
|
|
779
|
+
options:
|
|
780
|
+
command: 'jq -e ".verdict == \"PASS\"" .spur/run/$wbs-verdict.json >/dev/null'
|
|
981
781
|
# (d) F96 R4 (0950): `spur task residual` owns the settle.
|
|
982
782
|
# Soft best-effort (exit 0) — runs after certification and must never change
|
|
983
783
|
# the outcome; failure prints the re-run command.
|
|
@@ -985,7 +785,9 @@ states:
|
|
|
985
785
|
options:
|
|
986
786
|
command: >-
|
|
987
787
|
$spurBin task residual "$wbs" settle --no-logo || echo "residual-settle failed — re-run: spur task residual $wbs settle" >&2; exit 0
|
|
988
|
-
# (c) command.gate owns the done transition with classified transient retry.
|
|
788
|
+
# (c) command.gate owns the done transition with classified transient retry. The verb
|
|
789
|
+
# runs its own target-aware `task check --as done` (the completion gate) before the
|
|
790
|
+
# status write, so a task that does not certify a `done` target never stores `done`.
|
|
989
791
|
- kind: command.gate
|
|
990
792
|
options:
|
|
991
793
|
id: task-done-transition
|
|
@@ -999,18 +801,19 @@ states:
|
|
|
999
801
|
softFail: false
|
|
1000
802
|
timeoutMs: 120000
|
|
1001
803
|
# (e) advisory terminal checkpoint, one printf (0711 R1–R3; binding corrected 0784 R4).
|
|
1002
|
-
#
|
|
1003
|
-
#
|
|
804
|
+
# The `digest` field now carries the gate receipt's tree hash — the run's evidence
|
|
805
|
+
# identity since D65 1239 — instead of the retired proof digest (empty when no receipt
|
|
806
|
+
# bound a tree, which the contract treats as "not reported").
|
|
1004
807
|
- kind: shell
|
|
1005
808
|
options:
|
|
1006
809
|
command: >-
|
|
1007
|
-
mkdir -p .spur/memory/sessions; CP_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"; CP_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)"; printf '%s\n' '---' 'schema_version: 1' "session_id: $(date -u +%Y-%m-%d)-$wbs" 'workflow: task-pipeline' "run_id: $__runId" "task_wbs: $wbs" 'feature_id: ""' 'phase: done' 'status: done' 'last_gate:
|
|
810
|
+
mkdir -p .spur/memory/sessions; CP_TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"; CP_COMMIT="$(git rev-parse HEAD 2>/dev/null || echo unknown)"; CP_TREE="$(jq -r .inputDigest ".spur/run/$wbs-check-receipt.json" 2>/dev/null)"; printf '%s\n' '---' 'schema_version: 1' "session_id: $(date -u +%Y-%m-%d)-$wbs" 'workflow: task-pipeline' "run_id: $__runId" "task_wbs: $wbs" 'feature_id: ""' 'phase: done' 'status: done' 'last_gate: gate' "source_commit: $CP_COMMIT" "digest: $CP_TREE" "generated_at: $CP_TS" "updated_at: $CP_TS" "next_action: none - task $wbs complete" 'artifacts:' " - .spur/run/$wbs-verdict.json" " - .spur/run/$wbs-check-receipt.json" '---' '' '## Session Notes' '' "Terminal checkpoint for task $wbs (task-pipeline done)." 'Advisory only.' > .spur/memory/sessions/$wbs-checkpoint.md; exit 0
|
|
1008
811
|
|
|
1009
812
|
- id: failed
|
|
1010
813
|
description: >
|
|
1011
|
-
Terminal — precheck, quality-gate exhaustion,
|
|
1012
|
-
failure,
|
|
1013
|
-
|
|
814
|
+
Terminal — precheck, quality-gate exhaustion, a review that did not certify, a
|
|
815
|
+
completion check failure, operator rejection/cancel at the approval decision point,
|
|
816
|
+
or an escalation give-up; reported, not advanced.
|
|
1014
817
|
onEnter:
|
|
1015
818
|
# 0933 R30 (guard-lines compression): the escalation-bound report note lives
|
|
1016
819
|
# here as a conditional — no-op unless the run reached failed with a pending
|
|
@@ -1032,9 +835,6 @@ states:
|
|
|
1032
835
|
command: >-
|
|
1033
836
|
$spurBin task residual "$wbs" report --no-logo || echo "residual-report failed — re-run: spur task residual $wbs report" >&2; exit 0
|
|
1034
837
|
|
|
1035
|
-
- id: cancelled
|
|
1036
|
-
description: Terminal — pipeline cancelled by operator at the approval gate (R1).
|
|
1037
|
-
|
|
1038
838
|
transitions:
|
|
1039
839
|
# ── precheck: task check --precheck (size + evidence + four-layer, 1002 R4) → implement; ──
|
|
1040
840
|
# else → failed. The flag folds the two deleted precheck scripts into one fail-closed
|
|
@@ -1062,7 +862,7 @@ transitions:
|
|
|
1062
862
|
# attempt that already exhausted its maxEscalations asks routes to failed (with a
|
|
1063
863
|
# report note appended in the same guard) instead of asking forever; then the
|
|
1064
864
|
# question-presence hop; then the normal always edge. The bound guard requires a
|
|
1065
|
-
# fresh question file so a COMPLETED implement after max asks still routes to
|
|
865
|
+
# fresh question file so a COMPLETED implement after max asks still routes to gate.
|
|
1066
866
|
- from: implement
|
|
1067
867
|
to: failed
|
|
1068
868
|
terminalReason: retry-exhausted
|
|
@@ -1077,15 +877,15 @@ transitions:
|
|
|
1077
877
|
test "$n" -ge "$maxEscalations" -a -s .spur/run/$wbs-question.md
|
|
1078
878
|
- from: implement
|
|
1079
879
|
to: escalate
|
|
1080
|
-
description: Implement agent paused on an operator question (non-empty question file) —
|
|
880
|
+
description: Implement agent paused on an operator question (non-empty question file) — ask for an answer.
|
|
1081
881
|
guard:
|
|
1082
882
|
kind: shell
|
|
1083
883
|
options:
|
|
1084
884
|
command: 'test -s .spur/run/$wbs-question.md'
|
|
1085
885
|
# ── linear body ──
|
|
1086
886
|
- from: implement
|
|
1087
|
-
to:
|
|
1088
|
-
description: Implementation done — quality
|
|
887
|
+
to: gate
|
|
888
|
+
description: Implementation done — run the typed quality gate.
|
|
1089
889
|
guard:
|
|
1090
890
|
kind: always
|
|
1091
891
|
# escalate branching (0933 R27): an answered pause resumes implement — the transcript
|
|
@@ -1107,91 +907,46 @@ transitions:
|
|
|
1107
907
|
kind: shell
|
|
1108
908
|
options:
|
|
1109
909
|
command: 'test -z "$__hitlInput"'
|
|
1110
|
-
|
|
1111
|
-
# 0943 R1/R3
|
|
1112
|
-
#
|
|
1113
|
-
#
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
options:
|
|
1120
|
-
# 1111 R2: `DEFERRED` is accepted ONLY when the opt-in var is "true" — with the
|
|
1121
|
-
# default "false" a stray DEFERRED file fails the guard exactly like a corrupt one.
|
|
1122
|
-
command: >-
|
|
1123
|
-
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
1124
|
-
test "$gate_status" = PASS || { test "$gate_status" = DEFERRED && test "$deferQualityGate" = "true"; }
|
|
1125
|
-
- from: test
|
|
1126
|
-
to: test-fail-triage
|
|
1127
|
-
description: Quality gate red — classify the failure before the repair hop (0943 R3).
|
|
1128
|
-
guard:
|
|
1129
|
-
kind: shell
|
|
1130
|
-
options:
|
|
1131
|
-
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
|
|
1132
|
-
# 0943 review P3#2: the former `test → test-fix` FAIL edge is removed — it was dead (the
|
|
1133
|
-
# identical guard on test → test-fail-triage fires first) and a silent spec-bypass trap:
|
|
1134
|
-
# restoring it would skip failure-class on every red gate. The always-defense below keeps
|
|
1135
|
-
# pre-0943 corrupt-status behavior (fixall then recheck).
|
|
1136
|
-
- from: test
|
|
1137
|
-
to: test-fix
|
|
1138
|
-
description: Probe status missing/corrupt — attempt fixall then recheck.
|
|
1139
|
-
guard:
|
|
1140
|
-
kind: always
|
|
1141
|
-
- from: test-fix
|
|
1142
|
-
to: test-recheck
|
|
1143
|
-
description: Fixall finished — soft recheck the same quality gate.
|
|
1144
|
-
guard:
|
|
1145
|
-
kind: always
|
|
1146
|
-
# Recheck branching (PASS → triage; red → test-fail-triage; defense). The two former
|
|
1147
|
-
# static FAIL edges (under-max → test-fix, exhausted → failed retry-exhausted) moved into
|
|
1148
|
-
# test-fail-triage's decision routing (0943 R3).
|
|
1149
|
-
- from: test-recheck
|
|
1150
|
-
to: triage
|
|
1151
|
-
description: Quality gate green after fixall — route the lane deterministically (triage, 0943 R1).
|
|
910
|
+
|
|
911
|
+
# ── gate routing (0943 R1/R3 collapsed into one state, D65 1239 R2) ──
|
|
912
|
+
# Declaration order is the routing contract. PASS first (the green path), then a BLOCKED
|
|
913
|
+
# typed outcome (the gate could not decide — never read as red), then the failure class:
|
|
914
|
+
# stop before the cap (never mislabeled), the shared attempt cap, the fix lane, and the
|
|
915
|
+
# fail-closed defense so a missing/corrupt status or decision always terminates.
|
|
916
|
+
- from: gate
|
|
917
|
+
to: review
|
|
918
|
+
description: Quality gate green — the typed outcome is PASS and the gate wrote its own full-tier PASS token.
|
|
1152
919
|
guard:
|
|
1153
920
|
kind: shell
|
|
1154
921
|
options:
|
|
1155
|
-
#
|
|
922
|
+
# Two predicates over two result files (ADR-115): the typed `command.gate` outcome
|
|
923
|
+
# says the step ran and succeeded, and the gate's own token says the FULL tier wrote
|
|
924
|
+
# PASS (only the full tier writes PASS, ADR-124). A stale status file cannot ride a
|
|
925
|
+
# blocked or crashed visit into review because the typed outcome must be PASS too.
|
|
1156
926
|
command: >-
|
|
1157
|
-
|
|
1158
|
-
test "$
|
|
1159
|
-
- from:
|
|
1160
|
-
to:
|
|
1161
|
-
description:
|
|
927
|
+
test "$(cat ".spur/run/$__runId-gate.status" 2>/dev/null)" = PASS &&
|
|
928
|
+
test "$(cat ".spur/run/$wbs-test-gate.status" 2>/dev/null)" = PASS
|
|
929
|
+
- from: gate
|
|
930
|
+
to: review
|
|
931
|
+
description: Opt-in batch policy — the typed outcome is PASS and the gate wrote the DEFERRED (light-tier) token.
|
|
1162
932
|
guard:
|
|
1163
933
|
kind: shell
|
|
1164
934
|
options:
|
|
935
|
+
# 1111 R2: `DEFERRED` is accepted ONLY when the opt-in var is "true" — with the
|
|
936
|
+
# default "false" a stray DEFERRED file fails the guard exactly like a corrupt one.
|
|
1165
937
|
command: >-
|
|
1166
|
-
|
|
1167
|
-
test "$
|
|
1168
|
-
|
|
1169
|
-
- from:
|
|
938
|
+
test "$deferQualityGate" = "true" &&
|
|
939
|
+
test "$(cat ".spur/run/$__runId-gate.status" 2>/dev/null)" = PASS &&
|
|
940
|
+
test "$(cat ".spur/run/$wbs-test-gate.status" 2>/dev/null)" = DEFERRED
|
|
941
|
+
- from: gate
|
|
1170
942
|
to: failed
|
|
1171
943
|
terminalReason: failed-check
|
|
1172
|
-
description:
|
|
1173
|
-
guard:
|
|
1174
|
-
kind: always
|
|
1175
|
-
# ── triage routing (0943 R1/R2): the lane producer replaced the four gate-PASS edges.
|
|
1176
|
-
# mode=fast → verify; anything else (standard/high, deterministic-high, degraded default,
|
|
1177
|
-
# caller-set non-fast) → review. Together the two guards are exhaustive, so triage never hangs.
|
|
1178
|
-
- from: triage
|
|
1179
|
-
to: verify
|
|
1180
|
-
description: Triage resolved the fast lane (caller-set fast or task-triage low) — skip review.
|
|
944
|
+
description: The gate could not decide (BLOCKED — usage error, lock timeout, killed process) — stop instead of reading it as red.
|
|
1181
945
|
guard:
|
|
1182
946
|
kind: shell
|
|
1183
947
|
options:
|
|
1184
|
-
command: 'test "$
|
|
1185
|
-
- from:
|
|
1186
|
-
to: review
|
|
1187
|
-
description: Triage resolved a review lane (standard/high, deterministic-high, or caller-set) — review.
|
|
1188
|
-
guard:
|
|
1189
|
-
kind: shell
|
|
1190
|
-
options:
|
|
1191
|
-
command: 'test "$mode" != fast'
|
|
1192
|
-
# ── failure-class routing (0943 R3): stop first (never mislabeled by the cap), then the
|
|
1193
|
-
# attempt cap, then the fix lane, then the fail-closed defense. Declaration order matters.
|
|
1194
|
-
- from: test-fail-triage
|
|
948
|
+
command: 'test "$(cat ".spur/run/$__runId-gate.status" 2>/dev/null)" = BLOCKED'
|
|
949
|
+
- from: gate
|
|
1195
950
|
to: failed
|
|
1196
951
|
terminalReason: failed-check
|
|
1197
952
|
description: Failure class stop — the run terminates at failed, never bypassed.
|
|
@@ -1200,222 +955,93 @@ transitions:
|
|
|
1200
955
|
options:
|
|
1201
956
|
command: >-
|
|
1202
957
|
jq -e '.value == "stop"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
|
|
1203
|
-
- from:
|
|
958
|
+
- from: gate
|
|
1204
959
|
to: failed
|
|
1205
960
|
terminalReason: retry-exhausted
|
|
1206
|
-
description: Attempt cap reached — qualityGateMaxFixAttempts bounds the
|
|
1207
|
-
# (warn) 5 commands: named fix attempts (legibility, 0874).
|
|
961
|
+
description: Attempt cap reached — qualityGateMaxFixAttempts bounds the remediation lane.
|
|
1208
962
|
guard:
|
|
1209
963
|
kind: shell
|
|
1210
964
|
options:
|
|
1211
965
|
command: >-
|
|
1212
966
|
fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
|
|
1213
967
|
test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
|
|
1214
|
-
- from:
|
|
1215
|
-
to:
|
|
1216
|
-
description: Failure class fix and under the cap — the bounded repair hop,
|
|
968
|
+
- from: gate
|
|
969
|
+
to: remediate
|
|
970
|
+
description: Failure class fix and under the cap — the bounded repair hop, then a recheck visit of this same gate.
|
|
1217
971
|
guard:
|
|
1218
972
|
kind: shell
|
|
1219
973
|
options:
|
|
1220
974
|
command: >-
|
|
1221
975
|
jq -e '.value == "fix"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
|
|
1222
|
-
# Defense: missing/corrupt failure-class decision — fail closed
|
|
1223
|
-
- from:
|
|
976
|
+
# Defense: missing/corrupt gate status or failure-class decision — fail closed.
|
|
977
|
+
- from: gate
|
|
1224
978
|
to: failed
|
|
1225
979
|
terminalReason: failed-check
|
|
1226
|
-
description:
|
|
980
|
+
description: Gate status or failure-class decision missing/corrupt — stop at failed.
|
|
1227
981
|
guard:
|
|
1228
982
|
kind: always
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
# review FAIL cannot buy extra attempts and the iteration bound stays finite.
|
|
1233
|
-
- from: review-fail-triage
|
|
1234
|
-
to: failed
|
|
1235
|
-
terminalReason: failed-check
|
|
1236
|
-
description: Review failure class stop — the run terminates at failed, never bypassed.
|
|
1237
|
-
guard:
|
|
1238
|
-
kind: shell
|
|
1239
|
-
options:
|
|
1240
|
-
command: >-
|
|
1241
|
-
jq -e '.value == "stop"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
|
|
1242
|
-
- from: review-fail-triage
|
|
1243
|
-
to: failed
|
|
1244
|
-
terminalReason: retry-exhausted
|
|
1245
|
-
description: Attempt cap reached — qualityGateMaxFixAttempts bounds the review repair lane.
|
|
1246
|
-
guard:
|
|
1247
|
-
kind: shell
|
|
1248
|
-
options:
|
|
1249
|
-
command: >-
|
|
1250
|
-
fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
|
|
1251
|
-
test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
|
|
1252
|
-
- from: review-fail-triage
|
|
1253
|
-
to: test-fix
|
|
1254
|
-
description: Review failure class fix and under the cap — the shared bounded repair hop.
|
|
1255
|
-
guard:
|
|
1256
|
-
kind: shell
|
|
1257
|
-
options:
|
|
1258
|
-
command: >-
|
|
1259
|
-
jq -e '.value == "fix"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
|
|
1260
|
-
# Defense: missing/corrupt review-failure decision — fail closed instead of silently repairing.
|
|
1261
|
-
- from: review-fail-triage
|
|
1262
|
-
to: failed
|
|
1263
|
-
terminalReason: failed-check
|
|
1264
|
-
description: Review-failure decision missing/corrupt — stop at failed.
|
|
983
|
+
- from: remediate
|
|
984
|
+
to: gate
|
|
985
|
+
description: Repair finished — re-enter the gate, whose visit counter selects the recheck mode.
|
|
1265
986
|
guard:
|
|
1266
987
|
kind: always
|
|
1267
|
-
|
|
1268
|
-
#
|
|
1269
|
-
#
|
|
1270
|
-
#
|
|
1271
|
-
#
|
|
1272
|
-
#
|
|
1273
|
-
# review finding (record would terminally fail it after a full verify + record) — enters
|
|
1274
|
-
# `review-fail-triage` and its bounded repair hop BEFORE verify spends the cycle, then
|
|
1275
|
-
# re-enters quality → review → verify on a fresh digest. Before 1088 a FAIL review had no
|
|
1276
|
-
# outgoing edge at all; before 1122 a PASS-with-findings cost a full re-certification cycle per
|
|
1277
|
-
# attempt (knowledge-kit 0210/0213). The reviewer writes `Verdict: PASS|FAIL` in its answer;
|
|
1278
|
-
# both plain and bold forms are accepted. (F511: the v4-era PASS confidence grading — the
|
|
1279
|
-
# reviewer's self-stated HIGH/MEDIUM/LOW level and its `ackLowConfidence` escape hatch — was
|
|
1280
|
-
# retired; a PASS with no open P1-P3 finding is what the gate reads.) Review stays advisory:
|
|
1281
|
-
# verify certifies.
|
|
1282
|
-
# Under `profile=auto` the approve gate is skipped, so the PASS+auto edge is tried first and the
|
|
1283
|
-
# interactive PASS edge second.
|
|
1284
|
-
- from: review
|
|
1285
|
-
to: verify
|
|
1286
|
-
description: profile=auto, the review certified a PASS with no open P1-P3 finding — skip the human approval gate.
|
|
1287
|
-
guard:
|
|
1288
|
-
kind: shell
|
|
1289
|
-
options:
|
|
1290
|
-
command: >-
|
|
1291
|
-
test "$profile" = auto &&
|
|
1292
|
-
grep -qiE '^[[:space:]]*\**Verdict:\** *PASS' ".spur/run/$__runId-review-answer.txt" &&
|
|
1293
|
-
$spurBin task residual "$wbs" review-gate --no-logo >&2
|
|
988
|
+
|
|
989
|
+
# ── review routing (D65 1239 R2/R3): certification, approval, bounded repair ──
|
|
990
|
+
# Declaration order is the contract. `done` demands the certified review status AND the
|
|
991
|
+
# approval answer: under `profile=auto` a recorded maker denial (`__hitlAnswer = no`) blocks
|
|
992
|
+
# it, while a deferred answer (no maker configured) is not a denial — `--auto` runs do not
|
|
993
|
+
# gain a human gate they never had. Interactively only an explicit yes certifies.
|
|
1294
994
|
- from: review
|
|
1295
|
-
to:
|
|
1296
|
-
description: Review certified
|
|
995
|
+
to: done
|
|
996
|
+
description: Review certified PASS (verdict + traceability + clean review gate) and the approval decision point approved — close the task.
|
|
1297
997
|
guard:
|
|
1298
998
|
kind: shell
|
|
1299
999
|
options:
|
|
1000
|
+
# Two recorded tokens, two predicates (ADR-115): the certification status and the
|
|
1001
|
+
# approval status the decision point folded. Nothing is inferred from variables.
|
|
1300
1002
|
command: >-
|
|
1301
|
-
|
|
1302
|
-
$
|
|
1003
|
+
test "$(cat ".spur/run/$wbs-review.status" 2>/dev/null)" = PASS &&
|
|
1004
|
+
test "$(cat ".spur/run/$wbs-approval.status" 2>/dev/null)" = APPROVED
|
|
1303
1005
|
- from: review
|
|
1304
|
-
to: review-fail-triage
|
|
1305
|
-
description: >-
|
|
1306
|
-
No certified PASS without an open P1-P3 finding (FAIL, PARTIAL, missing, unparseable, or
|
|
1307
|
-
a PASS whose review-gate check found an open P1-P3 row; fail closed) —
|
|
1308
|
-
classify the failure and route it into the bounded repair hop instead of dead-ending the run.
|
|
1309
|
-
guard:
|
|
1310
|
-
kind: always
|
|
1311
|
-
|
|
1312
|
-
# ── approve: HITL gate (reached only when profile != auto). Routed by the
|
|
1313
|
-
# operator's captured answer (R1, bug-750) — three ordered guards, declaration
|
|
1314
|
-
# order matters (yes first, then no, then cancel), mirroring idea-pipeline's
|
|
1315
|
-
# design-approval gate (.spur/workflows/idea-pipeline.yaml:352-366). No
|
|
1316
|
-
# `always` edge remains out of `approve`. ──
|
|
1317
|
-
- from: approve
|
|
1318
|
-
to: verify
|
|
1319
|
-
description: Approved — proceed to verification.
|
|
1320
|
-
guard:
|
|
1321
|
-
kind: shell
|
|
1322
|
-
options:
|
|
1323
|
-
command: 'test "$__hitlAnswer" = yes'
|
|
1324
|
-
- from: approve
|
|
1325
1006
|
to: failed
|
|
1326
1007
|
terminalReason: cancelled
|
|
1327
|
-
description:
|
|
1008
|
+
description: The approval decision point denied (no/cancel interactively, a maker denial under auto, or no operator answer) — a recorded denial, not a check failure.
|
|
1328
1009
|
guard:
|
|
1329
1010
|
kind: shell
|
|
1330
1011
|
options:
|
|
1331
|
-
command: 'test "$
|
|
1332
|
-
- from:
|
|
1333
|
-
to:
|
|
1334
|
-
terminalReason:
|
|
1335
|
-
description:
|
|
1336
|
-
guard:
|
|
1337
|
-
kind: shell
|
|
1338
|
-
options:
|
|
1339
|
-
command: 'test "$__hitlAnswer" = cancel'
|
|
1340
|
-
|
|
1341
|
-
# ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
|
|
1342
|
-
# The verify step emits .spur/run/<wbs>-verdict.json with a required proof block (task 0703 R3/R5).
|
|
1343
|
-
# Only `verdict: PASS` PLUS a proof block whose digest — top level and every named stage — equals
|
|
1344
|
-
# the captured `proofDigest` clears the gate to `record`; any non-PASS, a missing file, malformed
|
|
1345
|
-
# JSON, or missing/mismatched proof evidence does not. (F511: the v4-era LOW/absent-confidence
|
|
1346
|
-
# clause and its `ackLowConfidence` escape hatch were retired.) Declaration order: PASS+proof
|
|
1347
|
-
# guard FIRST, then the bounded remediation route (R4), then the always catch-all so a PASS
|
|
1348
|
-
# verdict with a missing/malformed proof block still terminates at `failed` instead of hanging
|
|
1349
|
-
# the state.
|
|
1350
|
-
- from: verify
|
|
1351
|
-
to: record
|
|
1352
|
-
description: Verification verdict is PASS and its proof block names the captured digest on every stage — record results and proceed to done.
|
|
1012
|
+
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.
|
|
1353
1017
|
guard:
|
|
1354
|
-
# (one jq -e predicate) PASS verdict; proof, quality-gate, review and verification digests
|
|
1355
|
-
# equal to proofDigest; review completed; matching run id and definition digest (0703 R5, 0785 R4).
|
|
1356
1018
|
kind: shell
|
|
1357
1019
|
options:
|
|
1358
1020
|
command: >-
|
|
1359
|
-
jq -e
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
and (.proof.stages.review.status // "") == "completed"
|
|
1365
|
-
and (.proof.stages.verification.digest // "") == $d
|
|
1366
|
-
and (.proof.runId // "") == $r
|
|
1367
|
-
and (.proof.definitionDigest // "") == $dd
|
|
1368
|
-
' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
|
|
1369
|
-
- from: verify
|
|
1370
|
-
to: test-fix
|
|
1371
|
-
description: >-
|
|
1372
|
-
Verification returned a repairable non-PASS and the shared fix budget is not exhausted —
|
|
1373
|
-
bounded remediation hop (task 0703 R4); the chain re-enters quality → review → verify on a
|
|
1374
|
-
freshly captured digest. Never reached on PASS: remediation cannot follow certification.
|
|
1021
|
+
jq -e '.value == "stop"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
|
|
1022
|
+
- from: review
|
|
1023
|
+
to: failed
|
|
1024
|
+
terminalReason: retry-exhausted
|
|
1025
|
+
description: Attempt cap reached — the shared counter bounds the review repair lane too.
|
|
1375
1026
|
guard:
|
|
1376
|
-
# (e) remediation route: verdict not PASS and fix attempts below qualityGateMaxFixAttempts (0703 R4).
|
|
1377
1027
|
kind: shell
|
|
1378
1028
|
options:
|
|
1379
1029
|
command: >-
|
|
1380
|
-
|
|
1381
|
-
test
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
terminalReason: retry-exhausted
|
|
1386
|
-
description: >-
|
|
1387
|
-
Non-PASS with the fix budget exhausted, or a PASS/missing/malformed verdict whose proof
|
|
1388
|
-
block is absent or mismatched (task 0703 R5) — block before done; defense catch-all so the
|
|
1389
|
-
state always has a viable outgoing edge.
|
|
1390
|
-
guard:
|
|
1391
|
-
kind: always
|
|
1392
|
-
# ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23)
|
|
1393
|
-
# PLUS the proof-block re-assertion (task 0703 R5): the verdict artifact must still be PASS and
|
|
1394
|
-
# still name the captured digest — a forged or mutated completion artifact fails closed here.
|
|
1395
|
-
# Declaration order: pass guard first.
|
|
1396
|
-
- from: record
|
|
1397
|
-
to: done
|
|
1398
|
-
description: Task check passed and the verdict proof block still names the captured digest — certify done.
|
|
1030
|
+
fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
|
|
1031
|
+
test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
|
|
1032
|
+
- from: review
|
|
1033
|
+
to: remediate
|
|
1034
|
+
description: Review failure class fix and under the cap — the shared bounded repair hop, then gate → review on the remediated tree.
|
|
1399
1035
|
guard:
|
|
1400
1036
|
kind: shell
|
|
1401
1037
|
options:
|
|
1402
|
-
# 0804 R6: `--as done` projects the check onto the done target so open
|
|
1403
|
-
# Plan/AC checkboxes fail HERE — before the done action runs — mirroring
|
|
1404
|
-
# the CLI's own target-aware `--no-lifecycle` backstop (F92 R3). No `#`
|
|
1405
|
-
# comments inside the folded scalar: `>-` joins same-indent lines into
|
|
1406
|
-
# one shell line.
|
|
1407
|
-
# Two logical commands, deliberately (ADR-115): the `task check` projection plus
|
|
1408
|
-
# ONE jq predicate re-asserting the whole completion contract — PASS verdict and the
|
|
1409
|
-
# captured proof digest.
|
|
1410
1038
|
command: >-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
|
|
1416
|
-
- from: record
|
|
1039
|
+
jq -e '.value == "fix"' ".spur/run/$wbs-review-failure-class.decision" >/dev/null 2>&1
|
|
1040
|
+
# Defense: a review that did not certify and no clearer route — fail closed instead of
|
|
1041
|
+
# dead-ending the run (a FAIL, PARTIAL, missing or unparseable certification lands here).
|
|
1042
|
+
- from: review
|
|
1417
1043
|
to: failed
|
|
1418
1044
|
terminalReason: failed-check
|
|
1419
|
-
description:
|
|
1045
|
+
description: Review did not certify and no route classified it — stop at failed.
|
|
1420
1046
|
guard:
|
|
1421
1047
|
kind: always
|