@gobing-ai/spur 0.3.73 → 0.3.74
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 +15 -4
- package/config/pipeline-budgets.json +4 -4
- package/config/rules/strict/runtime-boundaries.yaml +1 -1
- package/config/templates/AGENTS.md +5 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +2 -0
- package/config/workflows/basic.yaml +5 -2
- package/config/workflows/docs-pipeline.yaml +92 -55
- package/config/workflows/feature-dev.yaml +43 -20
- package/config/workflows/feature-lifecycle.yaml +1 -0
- package/config/workflows/history-anatomy.yaml +8 -4
- package/config/workflows/idea-pipeline.yaml +66 -33
- package/config/workflows/pr-review.yaml +31 -8
- package/config/workflows/task-lifecycle.yaml +1 -0
- package/config/workflows/task-pipeline.yaml +929 -905
- package/config/workflows/wayfinder-resolution.yaml +124 -90
- package/config/workflows/wrapup-pipeline.yaml +208 -83
- package/package.json +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +7 -1
- package/plugins/sp/scripts/history-anatomy-cache.ts +11 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +5 -1
- package/plugins/sp/scripts/pr-reviewing.ts +7 -1
- package/plugins/sp/scripts/transition-shim-check.ts +6 -6
- package/plugins/sp/skills/pr-reviewing/SKILL.md +4 -1
- package/plugins/sp/skills/spur-cli/references/self.md +17 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +5 -5
- package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
- package/plugins/sp/skills/spur-dev/SKILL.md +17 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -2
- package/plugins/sp/skills/wayfinder/SKILL.md +1 -1
- package/spur.js +1125 -784
- package/web/_astro/{BoardApp.BYCNkMOn.js → BoardApp.C60RJZRj.js} +78 -77
- package/web/_astro/BoardApp.ymfj1EHA.js +1 -0
- package/web/_astro/{TaskDetail.CgUreSP2.js → TaskDetail.Guk8VuNu.js} +1 -1
- package/web/_astro/{arc.BySSh34M.js → arc.CAZtlWJM.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.DM46TS_h.js → architectureDiagram-3BPJPVTR.9-XbF_Tz.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.tZhvNUHA.js → blockDiagram-GPEHLZMM.DC4WLx3L.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.PT4Or4Nf.js → c4Diagram-AAUBKEIU.JgjUQMgG.js} +1 -1
- package/web/_astro/channel.Bxxe2Byc.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.J9r0_Bbe.js → chunk-2J33WTMH.D_JFiXa-.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.hzyeIvhR.js → chunk-4BX2VUAB.Bk_RxoeT.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.B0rO7qVh.js → chunk-55IACEB6.a_Rj2VxL.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.wE_Uk5D4.js → chunk-727SXJPM.C2NoS1U6.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DqEEjQw7.js → chunk-AQP2D5EJ.D1Xn6CNa.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.CDoD9sBX.js → chunk-FMBD7UC4.BhJrSBsX.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CtX5nF9P.js → chunk-ND2GUHAM.ByVGduYQ.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.CK_EwfaT.js → chunk-QZHKN3VN.B47Paunq.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.DLt5a8Lh.js → classDiagram-4FO5ZUOK.BERMSD5C.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.DLt5a8Lh.js → classDiagram-v2-Q7XG4LA2.BERMSD5C.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CMCWP49h.js → cose-bilkent-S5V4N54A.O_rHGlhU.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.HyXw_vdS.js → cynefin-OW5HDTMX.qCW2GdNp.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BTuAzh01.js → dagre-BM42HDAG.C5Y4lH_u.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D9dr9wfT.js → diagram-2AECGRRQ.DxZmRXxF.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C4Rot0hj.js → diagram-5GNKFQAL.qSxZeYSK.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.B_TK5uWC.js → diagram-KO2AKTUF.-7vl3bXs.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.JkXKK7CO.js → diagram-LMA3HP47.BHVV-UH3.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BzMN8Bd6.js → diagram-OG6HWLK6.DHAuW9sK.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DVZaWGUd.js → erDiagram-TEJ5UH35.DMRLQwPN.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.rjEiWUfR.js → flowDiagram-I6XJVG4X.B3EpPp_8.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.C_EgAarK.js → ganttDiagram-6RSMTGT7.BgUrExXM.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.B-QQSDsK.js → gitGraphDiagram-PVQCEYII.CU-9yVN4.js} +1 -1
- package/web/_astro/{index.B5MTfe7k.css → index.BhvM3djt.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DlWesz7T.js → infoDiagram-5YYISTIA.CvVTCRLe.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BUMZOawi.js → ishikawaDiagram-YF4QCWOH.BUzOfKPf.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CWfkxfjY.js → journeyDiagram-JHISSGLW.BBTb7ziz.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.B-YpMwXf.js → kanban-definition-UN3LZRKU.CTQQr70i.js} +1 -1
- package/web/_astro/{linear.D7uqzENp.js → linear.CLKlJPiS.js} +1 -1
- package/web/_astro/{mermaid.core.CxrNppBD.js → mermaid.core.BA4wFhLP.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B4Qe7cM2.js → mindmap-definition-RKZ34NQL.t1vG2l5e.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.Ds-5j2ro.js → pieDiagram-4H26LBE5.C8zJhyTu.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.tBd38uNC.js → quadrantDiagram-W4KKPZXB.DqYYvb7T.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.sFENkWl3.js → requirementDiagram-4Y6WPE33.lzfDZ9nI.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BeB-Hk7C.js → sankeyDiagram-5OEKKPKP.lm6N5ORM.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DnTeaSpx.js → sequenceDiagram-3UESZ5HK.E6_IV4Dj.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.B-8Jt5EJ.js → stateDiagram-AJRCARHV.Bnv6Ok2p.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Br7xoqMW.js → stateDiagram-v2-BHNVJYJU.mJHH_Ng_.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C-3WdOyi.js → timeline-definition-PNZ67QCA.BhRgqSLa.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DCIs7Lc6.js → vennDiagram-CIIHVFJN.pjb5DMC9.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.rGAL-bbz.js → wardleyDiagram-YWT4CUSO.CW20KUng.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.hkfQKiRl.js → xychartDiagram-2RQKCTM6.vX9_yuMl.js} +1 -1
- package/web/index.html +2 -2
- package/config/corpus-baseline.json +0 -1805
- package/config/workflow-composition-baseline.json +0 -615
- package/web/_astro/BoardApp.E12MFjOS.js +0 -1
- package/web/_astro/channel.5cYKr5cs.js +0 -1
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
28
28
|
kind: state-machine
|
|
29
29
|
name: task-pipeline
|
|
30
|
+
# 0772 R2: explicit identity tag. Set only after the proof failure-path tests
|
|
31
|
+
# passed; the engine treats unversioned external definitions as supported.
|
|
32
|
+
version: "1"
|
|
30
33
|
description: "Standard task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → record → done"
|
|
31
34
|
# Task entity lifecycle (owned by task-lifecycle.yaml):
|
|
32
35
|
# normal: backlog → todo → wip → testing → done
|
|
@@ -40,928 +43,949 @@ description: "Standard task pipeline: precheck → implement → quality-gate (t
|
|
|
40
43
|
iterationBound: 20
|
|
41
44
|
initialState: precheck
|
|
42
45
|
terminalStates:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
- done
|
|
47
|
+
- failed
|
|
48
|
+
- cancelled
|
|
46
49
|
failureStates:
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
- failed
|
|
51
|
+
- cancelled
|
|
49
52
|
vars:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
53
|
+
wbs: "0000"
|
|
54
|
+
profile: "standard"
|
|
55
|
+
mode: ""
|
|
56
|
+
__runId: ""
|
|
57
|
+
__definitionDigest: ""
|
|
58
|
+
# PATH-independent spur invocation for shell guards/actions. The CLI overrides this
|
|
59
|
+
# at run start (resolveSpurBin); the literal default is a safe fallback so direct/dry
|
|
60
|
+
# runs and `workflow validate` resolve the template. (See AGENTS.md / ADR-026.)
|
|
61
|
+
spurBin: "spur"
|
|
62
|
+
# Agent the pipeline's agent.run steps invoke. Override per run with
|
|
63
|
+
# `--vars '{"agent":"claude"}'`. `auto` is the reserved config-resolving selector:
|
|
64
|
+
# `agent.default` role -> tier -> cheapest USABLE executor. A named literal here would
|
|
65
|
+
# pin a box-specific binary into tracked SSOT and escape that usability ladder.
|
|
66
|
+
# It does not inherit the host session's interactive model; required capability
|
|
67
|
+
# attestation still gates the chosen executor before dispatch (0706).
|
|
68
|
+
agent: "auto"
|
|
69
|
+
# Implement-only executor override (R1, task 0454). Resolves like `agent` unless
|
|
70
|
+
# overridden. The `--agent` flag (execution-batch.md §3.2) forwards into BOTH
|
|
71
|
+
# `agent` and `implementAgent`, so a pinned `--agent X` reaches implement too. To
|
|
72
|
+
# pin ONLY implement while other hops keep the default, pass
|
|
73
|
+
# `--vars '{"implementAgent":"pi-zai"}'`.
|
|
74
|
+
implementAgent: "auto"
|
|
75
|
+
# Remediation can mutate code only when BOTH this cap and the task's declared
|
|
76
|
+
# mutationPolicy permit code. none/tests require scoped manual repair, not fixall.
|
|
77
|
+
mutationPolicy: "code"
|
|
78
|
+
# Step-level timeout for agentic hops (review / verify / test-fix) in ms.
|
|
79
|
+
# Soft quality-gate shells are unbounded by this var (host shell only).
|
|
80
|
+
# Raised 600s → 1800s (task 0398 R4 / H6 dogfood). Override:
|
|
81
|
+
# `--vars '{"stepTimeoutMs":"120000"}'`. 30 min is headroom, not a licence to
|
|
82
|
+
# run unbounded — if a hop still hits this wall, STOP and record it.
|
|
83
|
+
stepTimeoutMs: "1800000"
|
|
84
|
+
# Timeout for the `implement` step specifically (R2a). Implementation is the
|
|
85
|
+
# heaviest agent.run step (full read/write/test-probe loop) and has timed out
|
|
86
|
+
# at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
|
|
87
|
+
# a 100% failure rate at that budget. 30 min gives real headroom; STOP and
|
|
88
|
+
# record honestly rather than raising it further without operator sign-off.
|
|
89
|
+
implementTimeoutMs: "1800000"
|
|
90
|
+
# Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
|
|
91
|
+
# Empty by default; only meaningful once the approve state has been entered.
|
|
92
|
+
__hitlAnswer: ""
|
|
93
|
+
# Proof-state bracket (task 0612, ADR-071; restructured by task 0703). `proofDigest` is the
|
|
94
|
+
# canonical capture taken at quality-gate ENTRY — immediately before the evidence-producing
|
|
95
|
+
# final chain (quality → review → verify) — and re-captured at `test-recheck` when bounded
|
|
96
|
+
# remediation mutated the tree, so every evidence stage names one fresh digest (R2/R4).
|
|
97
|
+
# `proofDigestNow` is the live re-capture compared against `proofDigest` at verify entry and
|
|
98
|
+
# immediately before `record`; a mismatch means a proof input changed after evidence was
|
|
99
|
+
# established, so the run routes to `failed` instead of crossing the completion boundary (R5).
|
|
100
|
+
# `taskSpecPath` carries the task file path because `docs/tasks*` is excluded from the digest's
|
|
101
|
+
# git-tree half — spec content is folded in explicitly or a task-file edit would go undetected.
|
|
102
|
+
# The fingerprint scopes task content to the proof-input sections only (Background, Requirements,
|
|
103
|
+
# Acceptance Criteria, Design, Plan), so record-time Solution/Testing/Review evidence writes do
|
|
104
|
+
# not retroactively invalidate the certified input set (R6).
|
|
105
|
+
proofDigest: ""
|
|
106
|
+
proofDigestNow: ""
|
|
107
|
+
taskSpecPath: ""
|
|
108
|
+
# 0710 R4: task priority tier (P0..P4) extracted from the task frontmatter at the
|
|
109
|
+
# quality-gate stage. P0/P1 make the review/verify distinct-executor policy apply;
|
|
110
|
+
# unknown/empty priority means fresh-context-only (executor reuse allowed).
|
|
111
|
+
taskPriority: ""
|
|
112
|
+
# Project quality gate for the `test` hop (probe + fixall + recheck). Override per project
|
|
113
|
+
# with the same package-manager surface (this monorepo is Bun-only):
|
|
114
|
+
# `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
|
|
115
|
+
# and the fixall slash input all use this same var so the command stays single-sourced.
|
|
116
|
+
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
|
|
117
|
+
# interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
|
|
118
|
+
# NO `format` PREFIX: `implement` already ran `$formatCmd` on its way out, and `test`
|
|
119
|
+
# captures `proofDigest` (onEnter[3]) BEFORE this command runs (onEnter[4]) — a formatter
|
|
120
|
+
# inside the gate rewrites the very tree the digest just fingerprinted (ADR-071 proof
|
|
121
|
+
# window). It was a no-op only because the implement-stage format got there first; that is
|
|
122
|
+
# an accident, not an invariant. The gate observes, it does not mutate.
|
|
123
|
+
qualityGateCmd: "bun run spur-check"
|
|
124
|
+
# Cheap red-detector run before the full gate on **recheck only**; empty ⇒ no probe
|
|
125
|
+
# (full gate every recheck — the pre-0587 behavior). A project overriding qualityGateCmd
|
|
126
|
+
# should override this too. TRUSTED CONFIG ONLY — executed via `sh -c` (same surface as
|
|
127
|
+
# qualityGateCmd). Invariant: `review` is only ever entered through a full green
|
|
128
|
+
# qualityGateCmd — only the full gate writes PASS to <wbs>-test-gate.status.
|
|
129
|
+
gateProbeCmd: "bun run lint"
|
|
130
|
+
# Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
|
|
131
|
+
# Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
|
|
132
|
+
qualityGateMaxFixAttempts: "2"
|
|
133
|
+
# Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
|
|
134
|
+
# project can point it at its own formatter; invoked best-effort (a missing or
|
|
135
|
+
# failing formatter must never abort a run — the quality gate is the real gate).
|
|
136
|
+
formatCmd: "bun run format"
|
|
137
|
+
# Bounded `file:line` anchors extracted from the failing quality-gate log by the
|
|
138
|
+
# `test` / `test-recheck` hops (deduped, max 20, space-separated on one line).
|
|
139
|
+
# `test-fix` reads `.spur/run/<wbs>-test-gate.findings` into this var and names the
|
|
140
|
+
# anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
|
|
141
|
+
# file:line instead of re-deriving it from a fresh gate run (0482 R3).
|
|
142
|
+
gateFindings: ""
|
|
143
|
+
# Max R-items in ## Requirements before size precheck fails (R2, task 0454).
|
|
144
|
+
# Override with `--vars '{"maxImplementReqs":"20"}'`.
|
|
145
|
+
maxImplementReqs: "10"
|
|
146
|
+
# Max checklist items under ## Plan before size precheck fails (R2, task 0454).
|
|
147
|
+
# Override with `--vars '{"maxImplementPlanItems":"32"}'`.
|
|
148
|
+
maxImplementPlanItems: "16"
|
|
149
|
+
# Diff-scope guard on the implement hop (R1, task 0487). When the target task
|
|
150
|
+
# body backticks at least one path, non-corpus changes outside those paths
|
|
151
|
+
# fail the step by name. New files beside a declared file are allowed. Empty
|
|
152
|
+
# (default) = on; set to "off" to bypass:
|
|
153
|
+
# `--vars '{"implementScopeGuard":"off"}'`.
|
|
154
|
+
implementScopeGuard: ""
|
|
147
155
|
|
|
148
156
|
states:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
157
|
+
- id: precheck
|
|
158
|
+
description: >
|
|
159
|
+
Fast deterministic task readiness and size checks. Failures route to the
|
|
160
|
+
`failed` terminal state (not a raw lifecycle abort mid-enter).
|
|
161
|
+
onEnter:
|
|
162
|
+
# R6 (0487): pre-launch hygiene WARNING (never a block) — starting a task on
|
|
163
|
+
# a tree already dirty with another task's implementation is how 0485's diff
|
|
164
|
+
# got swept into 0486's run. Corpus dirs are excluded: the pipeline writes
|
|
165
|
+
# those itself.
|
|
166
|
+
# R1 (0511): task-corpus dirt gets its own advisory NOTE — docs/tasks* is
|
|
167
|
+
# excluded from the non-corpus warning, so pre-existing task-corpus edits are
|
|
168
|
+
# invisible at launch yet can be accidentally staged with the current task
|
|
169
|
+
# later. Advisory only (never a block): the pipeline legitimately updates
|
|
170
|
+
# task files, so corpus dirt is launch-time visibility, not a lifecycle guard.
|
|
171
|
+
- kind: shell
|
|
172
|
+
options:
|
|
173
|
+
command: >-
|
|
174
|
+
DIRTY=$(git status --porcelain -- . ':(exclude)docs/tasks*' ':(exclude)docs/features' 2>/dev/null);
|
|
175
|
+
if [ -n "$DIRTY" ]; then
|
|
176
|
+
echo "precheck: WARNING - working tree has uncommitted non-corpus changes; commit or stash before starting a new task:";
|
|
177
|
+
echo "$DIRTY";
|
|
178
|
+
fi;
|
|
179
|
+
CORPUS_DIRTY=$(git status --porcelain -- ':(glob)docs/tasks*/**' 2>/dev/null);
|
|
180
|
+
if [ -n "$CORPUS_DIRTY" ]; then
|
|
181
|
+
echo "precheck: NOTE - task corpus has uncommitted changes; review before staging with this task:";
|
|
182
|
+
echo "$CORPUS_DIRTY";
|
|
183
|
+
fi;
|
|
184
|
+
exit 0
|
|
185
|
+
- kind: note
|
|
186
|
+
options:
|
|
187
|
+
# R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
|
|
188
|
+
# hook at run start — see the 0062 follow-up note; there is no link-writing
|
|
189
|
+
# CLI verb to call from a shell step, so it cannot live in pure YAML.
|
|
190
|
+
message: "Pipeline start for task ${vars.wbs}."
|
|
191
|
+
# R1 (0453): auto-profile precheck reopens a done feature before task check.
|
|
192
|
+
# Under profile=auto, resolve feature_id, sync (preferred) or update to active.
|
|
193
|
+
# Under non-auto, leave R4 message to guide the operator.
|
|
194
|
+
# R3 (0723): a real reactivation failure is surfaced, not swallowed —
|
|
195
|
+
# the default 'fail' onEnter policy halts the sequence and routes the
|
|
196
|
+
# run to `failed` before implementation. Verbs stay single-shot:
|
|
197
|
+
# one sync, then one update fallback, never retried in a loop.
|
|
198
|
+
- kind: shell
|
|
199
|
+
options:
|
|
200
|
+
command: >-
|
|
201
|
+
if [ "$profile" = auto ]; then
|
|
202
|
+
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
203
|
+
jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);
|
|
204
|
+
if [ -n "$FID" ]; then
|
|
205
|
+
if ! $spurBin feature sync "$FID" --force 2>/dev/null; then
|
|
206
|
+
if ! $spurBin feature update "$FID" active 2>/dev/null; then
|
|
207
|
+
echo "precheck: FAIL - feature reactivation $FID failed;" >&2;
|
|
208
|
+
echo "precheck: feature sync + feature update both errored" >&2;
|
|
209
|
+
exit 1;
|
|
210
|
+
fi;
|
|
211
|
+
fi;
|
|
212
|
+
fi;
|
|
213
|
+
fi;
|
|
214
|
+
exit 0
|
|
215
|
+
# R2 (0454, 0723): task size precheck — deterministic count-only
|
|
216
|
+
# evaluation of R-item and Plan-item counts. No executor-tier policy:
|
|
217
|
+
# dispatch-time requiresCapabilities at `agent.run` is the
|
|
218
|
+
# authoritative capability check. Writes PASS/FAIL to
|
|
219
|
+
# .spur/run/<wbs>-precheck-size.status. Always exit 0 (soft action);
|
|
220
|
+
# the precheck→implement guard reads the file, so a missing checker
|
|
221
|
+
# fails closed (writes FAIL, never PASS).
|
|
222
|
+
- kind: shell
|
|
223
|
+
options:
|
|
224
|
+
command: >-
|
|
225
|
+
SIZE_FILE=".spur/run/$wbs-precheck-size.status" &&
|
|
226
|
+
mkdir -p .spur/run &&
|
|
227
|
+
SIZE_SCRIPT="plugins/sp/scripts/task-size-precheck.ts";
|
|
228
|
+
[ -f "$SIZE_SCRIPT" ] ||
|
|
229
|
+
SIZE_SCRIPT="$(superskill script path sp task-size-precheck.ts 2>/dev/null)";
|
|
230
|
+
if [ -n "$SIZE_SCRIPT" ] && [ -f "$SIZE_SCRIPT" ]; then
|
|
231
|
+
bun "$SIZE_SCRIPT" "$wbs"
|
|
232
|
+
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs"
|
|
233
|
+
--max-plan-items "$maxImplementPlanItems";
|
|
234
|
+
else
|
|
235
|
+
echo "task-size-precheck failed closed — checker not found in" >&2 &&
|
|
236
|
+
echo "plugins/sp/scripts/ nor staged; run 'superskill install sp'." >&2 &&
|
|
237
|
+
echo "FAIL" > "$SIZE_FILE";
|
|
238
|
+
fi &&
|
|
239
|
+
exit 0
|
|
240
|
+
# 0726 R2: task evidence precheck — deterministic live-data
|
|
241
|
+
# evidence-channel proof before implement dispatch. Writes PASS/FAIL to
|
|
242
|
+
# .spur/run/<wbs>-precheck-evidence.status. Always exit 0 (soft action);
|
|
243
|
+
# the precheck→implement guard reads the file, so a missing checker
|
|
244
|
+
# fails closed (writes FAIL, never PASS).
|
|
245
|
+
- kind: shell
|
|
246
|
+
options:
|
|
247
|
+
command: >-
|
|
248
|
+
EVID_FILE=".spur/run/$wbs-precheck-evidence.status" &&
|
|
249
|
+
mkdir -p .spur/run &&
|
|
250
|
+
EVID_SCRIPT="plugins/sp/scripts/task-evidence-precheck.ts";
|
|
251
|
+
[ -f "$EVID_SCRIPT" ] ||
|
|
252
|
+
EVID_SCRIPT="$(superskill script path sp task-evidence-precheck.ts 2>/dev/null)";
|
|
253
|
+
if [ -n "$EVID_SCRIPT" ] && [ -f "$EVID_SCRIPT" ]; then
|
|
254
|
+
bun "$EVID_SCRIPT" "$wbs"
|
|
255
|
+
--spur-bin "$spurBin";
|
|
256
|
+
else
|
|
257
|
+
echo "task-evidence-precheck failed closed — checker not found" >&2 &&
|
|
258
|
+
echo "in plugins/sp/scripts/ nor staged; run 'superskill install sp'." >&2 &&
|
|
259
|
+
echo "FAIL" > "$EVID_FILE";
|
|
260
|
+
fi &&
|
|
261
|
+
exit 0
|
|
262
|
+
# Proportional route table evaluation (0759 R1/R4). The reason artifact is RUN-scoped, not
|
|
263
|
+
# wbs-scoped: ADR-107 names `.spur/run/<runId>-route-reason.txt`, and a wbs-scoped path lets
|
|
264
|
+
# a re-run of the same task overwrite the earlier run's route claim, so the artifact could
|
|
265
|
+
# not attribute a route to the run that took it (0759 R5). `__runId` is injected by
|
|
266
|
+
# WorkflowAppService.run(); the wbs fallback keeps a driver-less invocation from writing to
|
|
267
|
+
# a bare "-route-reason.txt". The log line carries the run id for the same reason — an
|
|
268
|
+
# unattributed append is log scraping, which R5 explicitly rejects as evidence.
|
|
269
|
+
- kind: shell
|
|
270
|
+
options:
|
|
271
|
+
command: >-
|
|
272
|
+
mkdir -p .spur/run .spur/memory &&
|
|
273
|
+
RUN_ID="$__runId" &&
|
|
274
|
+
if [ -z "$RUN_ID" ]; then RUN_ID="pipeline-$wbs"; fi &&
|
|
275
|
+
REASON_FILE=".spur/run/$RUN_ID-route-reason.txt" &&
|
|
276
|
+
if [ "$mode" = "fast" ]; then
|
|
277
|
+
echo "fast:evidence complete+consistent" > "$REASON_FILE";
|
|
278
|
+
elif [ -z "$mode" ]; then
|
|
279
|
+
echo "safety:standard verification" > "$REASON_FILE";
|
|
280
|
+
elif [ "$mode" = "unknown" ]; then
|
|
281
|
+
echo "safety:unknown evidence quality" > "$REASON_FILE";
|
|
282
|
+
elif [ "$mode" = "conflict" ]; then
|
|
283
|
+
echo "safety:conflicting evidence" > "$REASON_FILE";
|
|
284
|
+
else
|
|
285
|
+
echo "safety:unrecognized evidence (mode=$mode)" > "$REASON_FILE";
|
|
286
|
+
fi &&
|
|
287
|
+
printf '%s %s %s\n' "$RUN_ID" "$wbs" "$(cat "$REASON_FILE")"
|
|
288
|
+
>> .spur/memory/task-pipeline-routes.log &&
|
|
289
|
+
exit 0
|
|
282
290
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
291
|
+
- id: implement
|
|
292
|
+
description: >
|
|
293
|
+
Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
|
|
294
|
+
task to `wip` via the normal verb. The lifecycle transition fires AFTER the
|
|
295
|
+
implement agent.run succeeds — onEnter runs in declaration order and the default
|
|
296
|
+
'fail' policy halts the sequence (routing the run to `failed`) if the agent.run
|
|
297
|
+
fails or times out, so a halted implement never leaves the task at `wip` with an
|
|
298
|
+
empty Solution (the F3 half-state). The `--no-lifecycle` flag is correct here: the
|
|
299
|
+
pipeline owns this transition; the FSM guard would otherwise re-run `spur task
|
|
300
|
+
check`, which is precheck's job.
|
|
301
|
+
NOTE: this step must NOT call /sp:dev-run in full mode (the default) —
|
|
302
|
+
that command DRIVES this pipeline, so calling it here recurses.
|
|
303
|
+
--mode implement is the single-step implement entry.
|
|
304
|
+
onEnter:
|
|
305
|
+
- kind: agent.run
|
|
306
|
+
options:
|
|
307
|
+
agent: ${vars.implementAgent}
|
|
308
|
+
# Pure slash command only (ADR-043). Anti-recursion / implement discipline
|
|
309
|
+
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
310
|
+
role: coder
|
|
311
|
+
# lives in /sp:dev-run --mode implement → sp:code-implementation, not YAML prose.
|
|
312
|
+
input: /sp:dev-run --mode implement ${vars.wbs} --auto
|
|
313
|
+
timeoutMs: ${vars.implementTimeoutMs}
|
|
314
|
+
# R3 (task 0424): empty-implement no-op guard — the agent.run action
|
|
315
|
+
# fails the step when exit 0 produced zero non-corpus file changes, so
|
|
316
|
+
# a silent no-op routes the run to `failed` here instead of drifting
|
|
317
|
+
# into test/review and being caught a full pass later.
|
|
318
|
+
requireDiff: true
|
|
319
|
+
# 0706 R6: this stage mutates the working tree unattended under the
|
|
320
|
+
# auto profile, so it declares minimum execution-capability
|
|
321
|
+
# requirements. Dispatch fails closed (before spawn) when the
|
|
322
|
+
# resolved executor's attestation cannot satisfy them — attest the
|
|
323
|
+
# executor in agent config.
|
|
324
|
+
requiresCapabilities:
|
|
325
|
+
fsWrite: available
|
|
326
|
+
processSpawn: available
|
|
327
|
+
- kind: shell
|
|
328
|
+
options:
|
|
329
|
+
command: >-
|
|
330
|
+
retry_transient() {
|
|
331
|
+
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
332
|
+
printf '%s\n' "$RETRY_OUTPUT";
|
|
333
|
+
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
334
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
335
|
+
sleep 2;
|
|
336
|
+
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
337
|
+
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
338
|
+
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
339
|
+
echo "node_modules link/dependency broken — run bun install and retry";
|
|
340
|
+
fi;
|
|
341
|
+
return "$RETRY_RC";
|
|
342
|
+
};
|
|
343
|
+
retry_transient $spurBin task update "$wbs" wip --no-lifecycle
|
|
344
|
+
# Post-implement cleanup: auto-format any unformatted output the agent produced.
|
|
345
|
+
# The agent may leave files that don't pass `biome check`, which then fails
|
|
346
|
+
# `bun run lint` downstream. Running format here is cheap (< 1 s) and prevents
|
|
347
|
+
# the test stage from tripping on a purely mechanical formatting defect (dogfood
|
|
348
|
+
# bug-733 — omp left agent-run.test.ts unformatted, causing a spurious lint gate
|
|
349
|
+
# failure that the ## Testing section mis-attributed to "pre-existing gaps").
|
|
350
|
+
# Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
|
|
351
|
+
# non-Bun project cannot abort a run mid-implement. Formatting is a convenience;
|
|
352
|
+
# `${vars.qualityGateCmd}` at `test` is the gate that actually decides.
|
|
353
|
+
- kind: shell
|
|
354
|
+
options:
|
|
355
|
+
command: "$formatCmd ; exit 0"
|
|
348
356
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
357
|
+
# ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
|
|
358
|
+
# NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
|
|
359
|
+
# a coverage target; it is not the project quality gate. Coverage gap-fill remains
|
|
360
|
+
# available via router C3/C5 (`/sp:dev-unit`) or inside implement — not this hop.
|
|
361
|
+
#
|
|
362
|
+
# Shape (engine has no per-action "run next only if previous failed" without
|
|
363
|
+
# `onError`, and Spur's action schema does not expose `onError`):
|
|
364
|
+
# test — soft probe: qualityGateCmd → PASS|FAIL status; always exit 0
|
|
365
|
+
# (green path: one gate run → review; no fixall/recheck tax)
|
|
366
|
+
# test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
|
|
367
|
+
# test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
|
|
368
|
+
- id: test
|
|
369
|
+
description: >
|
|
370
|
+
Proof-chain entry + soft quality-gate probe (single logical gate on the green path; bounded retries only
|
|
371
|
+
for SQLite lock contention). Capture of the canonical proof-input digest happens HERE — before any
|
|
372
|
+
evidence-producing final check (task 0703, ADR-071) — so the gate, review, and verification evidence
|
|
373
|
+
all name one digest. Then runs
|
|
374
|
+
`${vars.qualityGateCmd}`, records PASS|FAIL under
|
|
375
|
+
`.spur/run/<wbs>-test-gate.status`, resets the fix attempt counter, always exit 0.
|
|
376
|
+
onEnter:
|
|
377
|
+
# R6/R2 (0703): resolve the task-spec path BEFORE the digest capture. `docs/tasks*` is excluded
|
|
378
|
+
# from the digest's git-tree half, so the spec is folded in explicitly via `taskSpecPath`.
|
|
379
|
+
- kind: shell
|
|
380
|
+
options:
|
|
381
|
+
# 0710 R4: resolve the spec path, then extract `priority:` from the TASK FILE itself (not the
|
|
382
|
+
# path listing); normalize to upper so requiresDistinctExecutor's exact 'P0'/'P1' match hits.
|
|
383
|
+
# 0751 R2: the task path is NOT optional - an unresolved lookup fails
|
|
384
|
+
# the step (no `|| true`, no forced `exit 0`, no stderr suppression)
|
|
385
|
+
# instead of degrading the proof to whole-tree-only. The priority read
|
|
386
|
+
# stays tolerant: a missing line is genuinely optional.
|
|
387
|
+
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"'
|
|
388
|
+
- kind: file.read.into-var
|
|
389
|
+
options:
|
|
390
|
+
path: .spur/run/${vars.wbs}-taskpath.txt
|
|
391
|
+
var: taskSpecPath
|
|
392
|
+
# 0710 R4: carry the task's priority tier into the review/verify risk policy.
|
|
393
|
+
- kind: file.read.into-var
|
|
394
|
+
options:
|
|
395
|
+
path: .spur/run/${vars.wbs}-priority.txt
|
|
396
|
+
var: taskPriority
|
|
397
|
+
# R2 (0703, ADR-071): THE canonical proof capture. Placement is load-bearing: immediately before
|
|
398
|
+
# the final evidence chain, after every implement mutation (including the post-implement format).
|
|
399
|
+
# Capture-only here; `record` compares. A remediation pass re-captures at `test-recheck` (R4).
|
|
400
|
+
- kind: proof.fingerprint
|
|
401
|
+
options:
|
|
402
|
+
var: proofDigest
|
|
403
|
+
taskFile: ${vars.taskSpecPath}
|
|
404
|
+
- kind: shell
|
|
405
|
+
options:
|
|
406
|
+
command: >-
|
|
407
|
+
mkdir -p .spur/run &&
|
|
408
|
+
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
409
|
+
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
410
|
+
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
411
|
+
echo 0 > "$ATTEMPT_FILE" &&
|
|
412
|
+
: > "$LOG_FILE" &&
|
|
413
|
+
gate_attempt=1;
|
|
414
|
+
while [ "$gate_attempt" -le 5 ]; do
|
|
415
|
+
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
416
|
+
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
417
|
+
gate_locked=0;
|
|
418
|
+
grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
|
|
419
|
+
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
420
|
+
rm -f "$ATTEMPT_LOG";
|
|
421
|
+
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
422
|
+
printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
|
|
423
|
+
sleep 10;
|
|
424
|
+
gate_attempt=$((gate_attempt + 1));
|
|
425
|
+
done &&
|
|
426
|
+
if [ "$gate_rc" -eq 0 ]; then
|
|
427
|
+
printf 'quality gate PASS (attempts: %s; log: %s; bytes: %s)\n' "$gate_attempt" "$LOG_FILE" "$(wc -c < "$LOG_FILE" | tr -d "[:space:]")";
|
|
428
|
+
else
|
|
429
|
+
printf 'quality gate FAIL — last 40 lines follow (full log: %s)\n' "$LOG_FILE";
|
|
430
|
+
tail -n 40 "$LOG_FILE";
|
|
431
|
+
fi &&
|
|
432
|
+
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
433
|
+
set +e; grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+' "$LOG_FILE" | sort -u | head -20 | tr '\n' ' ' > "$FINDINGS_FILE"; set -e &&
|
|
434
|
+
if [ "$gate_rc" -eq 0 ]; then
|
|
435
|
+
printf 'PASS\n' > "$STATUS_FILE";
|
|
436
|
+
else
|
|
437
|
+
printf 'FAIL\n' > "$STATUS_FILE";
|
|
438
|
+
fi &&
|
|
439
|
+
printf 'proof-digest: %s\n' "$proofDigest" >> "$LOG_FILE" &&
|
|
440
|
+
exit 0
|
|
428
441
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
442
|
+
- id: test-fix
|
|
443
|
+
description: >
|
|
444
|
+
Bounded auto-fix hop when the quality gate is red OR final verification returned a
|
|
445
|
+
repairable non-PASS (task 0703 R4: remediation never happens inside verify — it loops
|
|
446
|
+
through here, then re-enters quality → review → verify on a fresh digest). Increments
|
|
447
|
+
`.spur/run/<wbs>-test-fix-attempt` (the shared bound with the quality path), projects the
|
|
448
|
+
verify verdict into the gate log when one exists so the repair hop sees it, then pure
|
|
449
|
+
slash (ADR-043) `/sp:dev-fixall` against `${vars.qualityGateCmd}`.
|
|
450
|
+
onEnter:
|
|
451
|
+
- kind: shell
|
|
452
|
+
options:
|
|
453
|
+
command: >-
|
|
454
|
+
TASK_POLICY=$($spurBin task show "$wbs" --json |
|
|
455
|
+
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;
|
|
456
|
+
if [ "$mutationPolicy:$TASK_POLICY" != "code:code" ]; then
|
|
457
|
+
echo "test-fix: mutation policy forbids automatic code repair (run=$mutationPolicy, task=$TASK_POLICY); gate failure preserved, no agent dispatched" >&2;
|
|
458
|
+
exit 1;
|
|
459
|
+
fi;
|
|
460
|
+
mkdir -p .spur/run &&
|
|
461
|
+
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
462
|
+
n=$(cat "$ATTEMPT_FILE" 2>/dev/null || echo 0) &&
|
|
463
|
+
printf '%s\n' "$((n + 1))" > "$ATTEMPT_FILE" &&
|
|
464
|
+
if [ -f ".spur/run/$wbs-verdict.json" ]; then
|
|
465
|
+
{ echo '--- verify verdict (remediation input, task 0703 R4) ---';
|
|
466
|
+
cat ".spur/run/$wbs-verdict.json";
|
|
467
|
+
} >> ".spur/run/$wbs-test-gate.log";
|
|
468
|
+
fi;
|
|
469
|
+
exit 0
|
|
470
|
+
# R3 (0482): project the extracted gate anchors into a var so the dispatch input
|
|
471
|
+
# can NAME the failing file:line, not merely point at a log. A vars template cannot
|
|
472
|
+
# run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
|
|
473
|
+
- kind: file.read.into-var
|
|
474
|
+
options:
|
|
475
|
+
path: .spur/run/${vars.wbs}-test-gate.findings
|
|
476
|
+
var: gateFindings
|
|
477
|
+
- kind: agent.run
|
|
478
|
+
options:
|
|
479
|
+
agent: ${vars.agent}
|
|
480
|
+
# R3 (0482): `--findings` names the failing anchors inline; `--gate-log` remains
|
|
481
|
+
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
482
|
+
role: coder
|
|
483
|
+
# the full-context escape hatch when the digest is not enough.
|
|
484
|
+
input: /sp:dev-fixall "${vars.qualityGateCmd}" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings "${vars.gateFindings}"
|
|
485
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
486
|
+
# 0706 R6: bounded remediation hop — unattended and tree-mutating,
|
|
487
|
+
# so it declares the same minimum requirements as `implement`.
|
|
488
|
+
requiresCapabilities:
|
|
489
|
+
fsWrite: available
|
|
490
|
+
processSpawn: available
|
|
472
491
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
492
|
+
- id: test-recheck
|
|
493
|
+
description: >
|
|
494
|
+
Soft recheck after fixall with bounded SQLite-lock retry. Writes PASS|FAIL (always exit 0). Transitions
|
|
495
|
+
branch to review (PASS), another test-fix (FAIL and under max attempts),
|
|
496
|
+
or the pipeline `failed` state (FAIL and attempts exhausted) — never a
|
|
497
|
+
raw lifecycle abort that skips the terminal `failed` state.
|
|
498
|
+
onEnter:
|
|
499
|
+
# R4 (0703, ADR-071): bounded remediation mutated the tree by design, so the fresh evidence
|
|
500
|
+
# chain (recheck gate → review → verify) must start from a NEWLY captured digest. Capture-only;
|
|
501
|
+
# the guards and `record` compare against this value.
|
|
502
|
+
- kind: proof.fingerprint
|
|
503
|
+
options:
|
|
504
|
+
var: proofDigest
|
|
505
|
+
taskFile: ${vars.taskSpecPath}
|
|
506
|
+
# 0587 R3: probe-then-full recheck. A red gateProbeCmd records FAIL and skips the full
|
|
507
|
+
# gate (the measured waste is re-running a 110–140s gate to learn the tree is still red);
|
|
508
|
+
# a green probe (or empty gateProbeCmd) falls through to the full-gate loop unchanged.
|
|
509
|
+
# Only the full gate writes PASS, so the `test-recheck → review` guard (reads PASS)
|
|
510
|
+
# still means a full green qualityGateCmd ran — invariant preserved by construction.
|
|
511
|
+
- kind: shell
|
|
512
|
+
options:
|
|
513
|
+
command: >-
|
|
514
|
+
mkdir -p .spur/run &&
|
|
515
|
+
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
516
|
+
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
517
|
+
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
518
|
+
: > "$LOG_FILE" &&
|
|
519
|
+
probe_rc=0;
|
|
520
|
+
if [ -n "$gateProbeCmd" ]; then
|
|
521
|
+
sh -c "$gateProbeCmd" > "$LOG_FILE.probe" 2>&1; probe_rc=$?;
|
|
522
|
+
cat "$LOG_FILE.probe" >> "$LOG_FILE";
|
|
523
|
+
rm -f "$LOG_FILE.probe";
|
|
524
|
+
fi;
|
|
525
|
+
if [ "$probe_rc" -ne 0 ]; then
|
|
526
|
+
gate_rc=$probe_rc;
|
|
527
|
+
else
|
|
528
|
+
gate_attempt=1;
|
|
529
|
+
while [ "$gate_attempt" -le 5 ]; do
|
|
530
|
+
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
531
|
+
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
532
|
+
gate_locked=0;
|
|
533
|
+
grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
|
|
534
|
+
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
535
|
+
rm -f "$ATTEMPT_LOG";
|
|
536
|
+
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
537
|
+
printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
|
|
538
|
+
sleep 10;
|
|
539
|
+
gate_attempt=$((gate_attempt + 1));
|
|
540
|
+
done;
|
|
541
|
+
fi &&
|
|
542
|
+
if [ "$gate_rc" -eq 0 ]; then
|
|
543
|
+
printf 'quality gate PASS (attempts: %s; log: %s; bytes: %s)\n' "$gate_attempt" "$LOG_FILE" "$(wc -c < "$LOG_FILE" | tr -d "[:space:]")";
|
|
544
|
+
else
|
|
545
|
+
printf 'quality gate FAIL — last 40 lines follow (full log: %s)\n' "$LOG_FILE";
|
|
546
|
+
tail -n 40 "$LOG_FILE";
|
|
547
|
+
fi &&
|
|
548
|
+
set +e; grep -oE '[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+' "$LOG_FILE" | sort -u | head -20 | tr '\n' ' ' > "$FINDINGS_FILE"; set -e &&
|
|
549
|
+
if [ "$gate_rc" -eq 0 ]; then
|
|
550
|
+
printf 'PASS\n' > "$STATUS_FILE";
|
|
551
|
+
else
|
|
552
|
+
printf 'FAIL\n' > "$STATUS_FILE";
|
|
553
|
+
fi &&
|
|
554
|
+
printf 'proof-digest: %s\n' "$proofDigest" >> "$LOG_FILE" &&
|
|
555
|
+
exit 0
|
|
532
556
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
557
|
+
- id: review
|
|
558
|
+
description: Three-dimensional code review via /sp:dev-review (functional requirements traceability + SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) + architecture depth), findings written to `## Review`.
|
|
559
|
+
onEnter:
|
|
560
|
+
- kind: agent.run
|
|
561
|
+
options:
|
|
562
|
+
# 0710 R2: review always runs on a fresh session — no implementation-session
|
|
563
|
+
# inheritance, no latch resume; implementation context reaches the reviewer
|
|
564
|
+
# only via the persisted task spec, the recorded diff, and run artifacts.
|
|
565
|
+
# 0710 R4/R5: the agent pin is deliberately gone — role: reviewer routes
|
|
566
|
+
# through the executor registry, and the runner enforces (pre-dispatch,
|
|
567
|
+
# fail-closed) that a P0/P1 task's review resolves a DIFFERENT executor
|
|
568
|
+
# spec than the implement stage recorded in __agentRouting_implement.
|
|
569
|
+
input: /sp:dev-review ${vars.wbs} --auto
|
|
570
|
+
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
571
|
+
role: reviewer
|
|
572
|
+
freshSession: true
|
|
573
|
+
priority: ${vars.taskPriority}
|
|
574
|
+
compareExecutorWith: implement
|
|
575
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
552
576
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
577
|
+
- id: approve
|
|
578
|
+
description: >
|
|
579
|
+
Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
|
|
580
|
+
(review routes around it straight to verify). In interactive mode this state pauses
|
|
581
|
+
the run for `spur workflow continue` (E3), making approval an explicit operator action.
|
|
582
|
+
The operator's answer is routed (R1, bug-750): yes -> verify; no -> failed (rejection
|
|
583
|
+
recorded, not silently approved); cancel -> cancelled (a distinct terminal state).
|
|
584
|
+
pause: true
|
|
585
|
+
onEnter:
|
|
586
|
+
- kind: hitl.confirm
|
|
587
|
+
options:
|
|
588
|
+
prompt: "Approve task ${vars.wbs} to proceed to verification?"
|
|
565
589
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
590
|
+
- id: verify
|
|
591
|
+
description: >
|
|
592
|
+
Observe-only functional verification (BDD + traceability) via /sp:dev-verify --fix none
|
|
593
|
+
(task 0703 R1, ADR-071): the verifier certifies the state, it never repairs its own subject.
|
|
594
|
+
A live digest compare BEFORE the agent refuses to certify a state that drifted after the
|
|
595
|
+
quality/review evidence was produced (R2). The agent's
|
|
596
|
+
captured answer is written to `.spur/run/<wbs>-verify-answer.txt` and MUST follow
|
|
597
|
+
the answer-file schema contract (explicit `Verdict: PASS|PARTIAL|FAIL` line plus
|
|
598
|
+
`| Req | Status | Evidence |` and `| AC | Status | Evidence Type | Evidence |` tables);
|
|
599
|
+
a deterministic shell step then derives the verdict and writes the gate artifact
|
|
600
|
+
`.spur/run/<wbs>-verdict.json` (so the completion gate reads a real verdict, not
|
|
601
|
+
agent discretion — R9) with a proof block naming the digest and the per-stage results (R3).
|
|
602
|
+
Repairable non-PASS routes once through the bounded remediation hop (verify → test-fix,
|
|
603
|
+
R4); the chain reruns on a fresh digest.
|
|
604
|
+
onEnter:
|
|
605
|
+
# R2 (0703): midpoint bracket compare — refuse to spend verification on a state that no
|
|
606
|
+
# longer matches the digest the quality/review evidence names. Reuses `proofDigestNow`:
|
|
607
|
+
# set here and re-set by the final compare at `record` entry.
|
|
608
|
+
- kind: proof.fingerprint
|
|
609
|
+
options:
|
|
610
|
+
var: proofDigestNow
|
|
611
|
+
taskFile: ${vars.taskSpecPath}
|
|
612
|
+
expect: ${vars.proofDigest}
|
|
613
|
+
- kind: agent.run
|
|
614
|
+
options:
|
|
615
|
+
# 0710 R2: verify is a separate fresh-session execution — never the review
|
|
616
|
+
# session, never the implement session (R6).
|
|
617
|
+
# 0710 R4/R5: role-routed like review; P0/P1 demands a distinct executor.
|
|
618
|
+
# R1 (0703, ADR-071): `--fix none` — final verification is observe-only. Remediation
|
|
619
|
+
# belongs to the bounded test-fix hop, never to the certifying pass.
|
|
620
|
+
input: /sp:dev-verify ${vars.wbs} --auto --fix none --focus all
|
|
621
|
+
# Declared Layer-1 role (0538 R2/0710 R7): routing reason; no executor pin — role routing feeds the distinctness gate.
|
|
622
|
+
role: reviewer
|
|
623
|
+
freshSession: true
|
|
624
|
+
priority: ${vars.taskPriority}
|
|
625
|
+
compareExecutorWith: implement
|
|
626
|
+
timeoutMs: ${vars.stepTimeoutMs}
|
|
627
|
+
expectFile: .spur/run/${vars.wbs}-verify-answer.txt
|
|
628
|
+
# 0726 R3: hard lint gate over the verifier-owned answer — shape and
|
|
629
|
+
# evidence-row identity, before the verdict derivation reads it.
|
|
630
|
+
# Hard action: a malformed answer halts the sequence here instead of
|
|
631
|
+
# poisoning the verdict parse downstream.
|
|
632
|
+
- kind: shell
|
|
633
|
+
options:
|
|
634
|
+
command: >-
|
|
635
|
+
LINT_SCRIPT="plugins/sp/scripts/verify-answer-lint.ts";
|
|
636
|
+
[ -f "$LINT_SCRIPT" ] ||
|
|
637
|
+
LINT_SCRIPT="$(superskill script path sp verify-answer-lint.ts 2>/dev/null)";
|
|
638
|
+
if [ -z "$LINT_SCRIPT" ] || [ ! -f "$LINT_SCRIPT" ]; then
|
|
639
|
+
echo "verify-answer-lint: checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'" >&2;
|
|
640
|
+
exit 1;
|
|
641
|
+
fi;
|
|
642
|
+
bun "$LINT_SCRIPT" "$wbs"
|
|
643
|
+
--answer ".spur/run/$wbs-verify-answer.txt"
|
|
644
|
+
--spur-bin "$spurBin"
|
|
645
|
+
- kind: shell
|
|
646
|
+
options:
|
|
647
|
+
command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
|
|
648
|
+
# R3 (0703): write the required proof block into the verdict artifact — the digest, the
|
|
649
|
+
# certifying run id, the capture point, and the named per-stage results, each stage carrying
|
|
650
|
+
# the SAME digest value (prose asserting proof validity is insufficient). `runId` closes
|
|
651
|
+
# 0730 §B.2 (task 0757 R4): without it the verified-outcome fold has to accept ANY linked
|
|
652
|
+
# run as certifying, so a dry-run probe linked to the same wbs reads as proof of completion.
|
|
653
|
+
# `definitionDigest` closes 0759 R5: the record binds to the certifying run AND the exact
|
|
654
|
+
# workflow definition it executed — a stale-definition resume or a definition edited
|
|
655
|
+
# between run and record cannot certify silently. `__definitionDigest` is injected at run
|
|
656
|
+
# start (workflow-service.ts) and equals the digest stamped on the run row (task 0603).
|
|
657
|
+
# Also keeps the flat
|
|
658
|
+
# `proof-input-digest` check row for consumers that read `checks[]`. Soft action + hard
|
|
659
|
+
# guard: a missing/malformed stamp fails the `verify → record` guard below, not this step.
|
|
660
|
+
- kind: shell
|
|
661
|
+
options:
|
|
662
|
+
command: >-
|
|
663
|
+
V=".spur/run/$wbs-verdict.json";
|
|
664
|
+
if [ -f "$V" ] && [ -n "$proofDigest" ]; then
|
|
665
|
+
jq --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" --arg g "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null || echo UNKNOWN)"
|
|
666
|
+
'. + {proof: {digest: $d, runId: $r, definitionDigest: $dd, capturePoint: "quality-gate-entry", stages: {
|
|
667
|
+
qualityGate: {status: $g, digest: $d},
|
|
668
|
+
review: {status: "completed", digest: $d},
|
|
669
|
+
verification: {status: .verdict, digest: $d}}}}
|
|
670
|
+
| .checks += [{name: "proof-input-digest", status: "pass", evidence: $d}]'
|
|
671
|
+
"$V" > "$V.tmp" && mv "$V.tmp" "$V";
|
|
672
|
+
fi;
|
|
673
|
+
exit 0
|
|
650
674
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
675
|
+
- id: record
|
|
676
|
+
description: >
|
|
677
|
+
Record pipeline results into the task file via `spur task record` —
|
|
678
|
+
Testing/Review from the verdict, Solution backfilled from git diff as a
|
|
679
|
+
safety net, optional transition to testing. Post-record step conditionally syncs
|
|
680
|
+
feature status via the bounded `feature-sync-bounded` wrapper (task 0411
|
|
681
|
+
retry-suppression) if `feature_id` is present, or appends an orphan link proposal
|
|
682
|
+
to the run report if absent (task 0328 / ADR-0322).
|
|
683
|
+
onEnter:
|
|
684
|
+
# Proof-state compare (task 0612, ADR-071; bracket closed against the pre-chain
|
|
685
|
+
# capture per task 0703) — FIRST action in the state, before any record write.
|
|
686
|
+
# Re-captures the digest and asserts it equals the value the evidence chain started
|
|
687
|
+
# from. A mismatch means a proof input changed after evidence was established; the
|
|
688
|
+
# default `fail` policy halts the sequence and routes the run to `failed` rather
|
|
689
|
+
# than crossing into `record`. Task-spec evidence writes (Testing/Review/Solution,
|
|
690
|
+
# R6) happen only in the actions AFTER this comparison, and the fingerprint scopes
|
|
691
|
+
# task content to the proof-input sections, so they cannot invalidate the certified
|
|
692
|
+
# input set.
|
|
693
|
+
- kind: proof.fingerprint
|
|
694
|
+
options:
|
|
695
|
+
var: proofDigestNow
|
|
696
|
+
taskFile: ${vars.taskSpecPath}
|
|
697
|
+
expect: ${vars.proofDigest}
|
|
698
|
+
- kind: shell
|
|
699
|
+
options:
|
|
700
|
+
command: >-
|
|
701
|
+
retry_transient() {
|
|
702
|
+
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
703
|
+
printf '%s\n' "$RETRY_OUTPUT";
|
|
704
|
+
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
705
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
706
|
+
sleep 2;
|
|
707
|
+
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
708
|
+
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
709
|
+
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
710
|
+
echo "node_modules link/dependency broken — run bun install and retry";
|
|
711
|
+
fi;
|
|
712
|
+
return "$RETRY_RC";
|
|
713
|
+
};
|
|
714
|
+
retry_transient $spurBin task record "$wbs" --solution-from-diff --transition testing
|
|
715
|
+
# Best-effort, and portable across layouts. `feature-sync-bounded.ts` is a
|
|
716
|
+
# Spur-monorepo path — `spur init` never scaffolds `plugins/sp/`, so a seeded
|
|
717
|
+
# project must fall back to the plain `spur feature sync` verb rather than
|
|
718
|
+
# abort a run that already produced a PASS verdict. Trailing `exit 0` keeps the
|
|
719
|
+
# whole step advisory for the same reason (feature status sync is a follow-up,
|
|
720
|
+
# not a completion gate — `record → done` runs `spur task check`).
|
|
721
|
+
- kind: shell
|
|
722
|
+
options:
|
|
723
|
+
command: >-
|
|
724
|
+
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
725
|
+
jq -r ".feature_id // .frontmatter.feature_id // empty" 2>/dev/null);
|
|
726
|
+
if [ -n "$FID" ]; then
|
|
727
|
+
if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
|
|
728
|
+
bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json;
|
|
729
|
+
elif SYNC_MJS="$(superskill script path sp feature-sync-bounded.mjs 2>/dev/null)" && [ -f "$SYNC_MJS" ]; then
|
|
730
|
+
node "$SYNC_MJS" "$FID" --spur-bin "$spurBin" --json;
|
|
731
|
+
else
|
|
732
|
+
$spurBin feature sync "$FID" --json;
|
|
733
|
+
fi;
|
|
734
|
+
else
|
|
735
|
+
echo "Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt";
|
|
736
|
+
fi;
|
|
737
|
+
exit 0
|
|
714
738
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
739
|
+
- id: done
|
|
740
|
+
description: >
|
|
741
|
+
Terminal — task executed, verified, and recorded. The `record → done`
|
|
742
|
+
guard runs `spur task check` before certifying; a genuinely non-compliant
|
|
743
|
+
task routes to `failed` instead of a silent bad `done`.
|
|
744
|
+
onEnter:
|
|
745
|
+
- kind: shell
|
|
746
|
+
options:
|
|
747
|
+
command: >-
|
|
748
|
+
retry_transient() {
|
|
749
|
+
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
750
|
+
printf '%s\n' "$RETRY_OUTPUT";
|
|
751
|
+
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
752
|
+
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
753
|
+
sleep 2;
|
|
754
|
+
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
755
|
+
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
756
|
+
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
757
|
+
echo "node_modules link/dependency broken — run bun install and retry";
|
|
758
|
+
fi;
|
|
759
|
+
return "$RETRY_RC";
|
|
760
|
+
};
|
|
761
|
+
retry_transient $spurBin task update "$wbs" done --no-lifecycle
|
|
762
|
+
# D5-L: the verify verdict is this pipeline's proof of completion, so its ownership
|
|
763
|
+
# belongs to a deterministic primitive rather than an untracked file on disk.
|
|
764
|
+
# `run.artifact` records path + kind + runId only — never the body (ADR-069).
|
|
765
|
+
- kind: run.artifact
|
|
766
|
+
options:
|
|
767
|
+
path: .spur/run/${vars.wbs}-verdict.json
|
|
768
|
+
artifactKind: verify-verdict
|
|
769
|
+
# 0751 R4: bind the verdict to the run's captured proof digest. `record`
|
|
770
|
+
# re-captures `proofDigestNow` with expect=proofDigest, so the binding
|
|
771
|
+
# holds by construction here — making the option non-decorative.
|
|
772
|
+
proofBinding: current
|
|
773
|
+
- kind: note
|
|
774
|
+
options:
|
|
775
|
+
message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
|
|
776
|
+
# Checkpoint write: record session state for resume (0711 R1–R3) —
|
|
777
|
+
# canonical frontmatter contract; mirrors the Session Checkpoint
|
|
778
|
+
# Convention in plugins/sp/skills/spur-dev/references/cross-cutting.md.
|
|
779
|
+
# Advisory only: the task file and the persisted run row stay authoritative.
|
|
780
|
+
- kind: shell
|
|
781
|
+
options:
|
|
782
|
+
# Single logical line: the composition-baseline argument-split lint flags any
|
|
783
|
+
# command whose continuation lines look like argument lists (heredocs with
|
|
784
|
+
# `- item` entries trip it), so the checkpoint body is one printf with \n escapes.
|
|
785
|
+
command: >-
|
|
786
|
+
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_DIGEST="$(cut -d= -f2 .spur/run/$wbs-proofdigest.txt 2>/dev/null || echo '')"; CP_RUN="$SPUR_RUN_ID"; [ -z "$CP_RUN" ] && CP_RUN="$RUN_ID"; printf '%s\n' '---' 'schema_version: 1' "session_id: $(date -u +%Y-%m-%d)-$wbs" 'workflow: task-pipeline' "run_id: $CP_RUN" "task_wbs: $wbs" 'feature_id: ""' 'phase: done' 'status: done' 'last_gate: record' "source_commit: $CP_COMMIT" "digest: $CP_DIGEST" "generated_at: $CP_TS" "updated_at: $CP_TS" "next_action: none - task $wbs complete (terminal; advisory only)" 'artifacts:' ' - .spur/run/$wbs-verdict.json' ' - .spur/run/$wbs-test-gate.log' '---' '' '## Session Notes' '' "Terminal checkpoint for task $wbs (task-pipeline done)." 'Advisory only; the task file is authoritative.' > .spur/memory/sessions/$wbs-checkpoint.md; exit 0
|
|
763
787
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
788
|
+
- id: failed
|
|
789
|
+
description: >
|
|
790
|
+
Terminal — precheck, quality-gate exhaustion, verify non-PASS, record check
|
|
791
|
+
failure, or operator rejection; reported, not advanced.
|
|
768
792
|
|
|
769
|
-
|
|
770
|
-
|
|
793
|
+
- id: cancelled
|
|
794
|
+
description: Terminal — pipeline cancelled by operator at the approval gate (R1).
|
|
771
795
|
|
|
772
796
|
transitions:
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
797
|
+
# ── precheck: size PASS + task check → implement; else → failed ──
|
|
798
|
+
- from: precheck
|
|
799
|
+
to: implement
|
|
800
|
+
description: Deterministic size, evidence, and task checks are green — begin implementation.
|
|
801
|
+
guard:
|
|
802
|
+
kind: shell
|
|
803
|
+
options:
|
|
804
|
+
command: 'test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
|
|
805
|
+
- from: precheck
|
|
806
|
+
to: failed
|
|
807
|
+
description: Size and/or task check failed — stop before implement.
|
|
808
|
+
guard:
|
|
809
|
+
kind: always
|
|
786
810
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
811
|
+
# ── linear body ──
|
|
812
|
+
- from: implement
|
|
813
|
+
to: test
|
|
814
|
+
description: Implementation done — quality-gate probe.
|
|
815
|
+
guard:
|
|
816
|
+
kind: always
|
|
817
|
+
# Soft probe branching (declaration order: PASS first, then FAIL, then defense).
|
|
818
|
+
- from: test
|
|
819
|
+
to: verify
|
|
820
|
+
description: Quality gate already green and mode is fast — proportional fast path bypasses review.
|
|
821
|
+
guard:
|
|
822
|
+
kind: shell
|
|
823
|
+
options:
|
|
824
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
|
|
825
|
+
- from: test
|
|
826
|
+
to: review
|
|
827
|
+
description: Quality gate already green and safety mode — proceed to review.
|
|
828
|
+
guard:
|
|
829
|
+
kind: shell
|
|
830
|
+
options:
|
|
831
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
|
|
832
|
+
- from: test
|
|
833
|
+
to: test-fix
|
|
834
|
+
description: Quality gate red — start bounded fixall loop.
|
|
835
|
+
guard:
|
|
836
|
+
kind: shell
|
|
837
|
+
options:
|
|
838
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
|
|
839
|
+
# Defense: missing/corrupt status — treat as FAIL path, not silent PASS.
|
|
840
|
+
- from: test
|
|
841
|
+
to: test-fix
|
|
842
|
+
description: Probe status missing/corrupt — attempt fixall then recheck.
|
|
843
|
+
guard:
|
|
844
|
+
kind: always
|
|
845
|
+
- from: test-fix
|
|
846
|
+
to: test-recheck
|
|
847
|
+
description: Fixall finished — soft recheck the same quality gate.
|
|
848
|
+
guard:
|
|
849
|
+
kind: always
|
|
850
|
+
# Recheck branching (PASS first; under-max FAIL → fixall again; exhausted → failed).
|
|
851
|
+
- from: test-recheck
|
|
852
|
+
to: verify
|
|
853
|
+
description: Quality gate green after fixall and mode is fast — proportional fast path bypasses review.
|
|
854
|
+
guard:
|
|
855
|
+
kind: shell
|
|
856
|
+
options:
|
|
857
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" = fast'
|
|
858
|
+
- from: test-recheck
|
|
859
|
+
to: review
|
|
860
|
+
description: Quality gate green after fixall and safety mode — proceed to review.
|
|
861
|
+
guard:
|
|
862
|
+
kind: shell
|
|
863
|
+
options:
|
|
864
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS && test "$mode" != fast'
|
|
865
|
+
- from: test-recheck
|
|
866
|
+
to: test-fix
|
|
867
|
+
description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
|
|
868
|
+
guard:
|
|
869
|
+
kind: shell
|
|
870
|
+
options:
|
|
871
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"'
|
|
872
|
+
- from: test-recheck
|
|
873
|
+
to: failed
|
|
874
|
+
description: Still red after max fixall attempts — stop at failed (not silent abort).
|
|
875
|
+
guard:
|
|
876
|
+
kind: shell
|
|
877
|
+
options:
|
|
878
|
+
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL && test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -ge "$qualityGateMaxFixAttempts"'
|
|
879
|
+
# Defense: corrupt recheck status — failed, not review.
|
|
880
|
+
- from: test-recheck
|
|
881
|
+
to: failed
|
|
882
|
+
description: Recheck status missing/corrupt — stop at failed.
|
|
883
|
+
guard:
|
|
884
|
+
kind: always
|
|
885
|
+
# ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
|
|
886
|
+
# Declaration order matters: the auto-skip guard is tried FIRST, so under
|
|
887
|
+
# profile=auto the run routes straight to verify and never ENTERS approve
|
|
888
|
+
# (whose onEnter hitl.confirm would otherwise block). Otherwise fall through
|
|
889
|
+
# to approve for the human gate.
|
|
890
|
+
- from: review
|
|
891
|
+
to: verify
|
|
892
|
+
description: profile=auto — skip the human approval gate, go straight to verify.
|
|
893
|
+
guard:
|
|
894
|
+
kind: shell
|
|
895
|
+
options:
|
|
896
|
+
command: 'test "$profile" = auto'
|
|
897
|
+
- from: review
|
|
898
|
+
to: approve
|
|
899
|
+
description: Review done — gate on human approval.
|
|
900
|
+
guard:
|
|
901
|
+
kind: always
|
|
878
902
|
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
903
|
+
# ── approve: HITL gate (reached only when profile != auto). Routed by the
|
|
904
|
+
# operator's captured answer (R1, bug-750) — three ordered guards, declaration
|
|
905
|
+
# order matters (yes first, then no, then cancel), mirroring idea-pipeline's
|
|
906
|
+
# design-approval gate (.spur/workflows/idea-pipeline.yaml:352-366). No
|
|
907
|
+
# `always` edge remains out of `approve`. ──
|
|
908
|
+
- from: approve
|
|
909
|
+
to: verify
|
|
910
|
+
description: Approved — proceed to verification.
|
|
911
|
+
guard:
|
|
912
|
+
kind: shell
|
|
913
|
+
options:
|
|
914
|
+
command: 'test "$__hitlAnswer" = yes'
|
|
915
|
+
- from: approve
|
|
916
|
+
to: failed
|
|
917
|
+
description: Operator rejected at the approval gate — report and stop.
|
|
918
|
+
guard:
|
|
919
|
+
kind: shell
|
|
920
|
+
options:
|
|
921
|
+
command: 'test "$__hitlAnswer" = no'
|
|
922
|
+
- from: approve
|
|
923
|
+
to: cancelled
|
|
924
|
+
description: Operator cancelled at the approval gate.
|
|
925
|
+
guard:
|
|
926
|
+
kind: shell
|
|
927
|
+
options:
|
|
928
|
+
command: 'test "$__hitlAnswer" = cancel'
|
|
905
929
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
930
|
+
# ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
|
|
931
|
+
# The verify step emits .spur/run/<wbs>-verdict.json with a required proof block (task 0703 R3/R5).
|
|
932
|
+
# Only `verdict: PASS` PLUS a proof block whose digest — top level and every named stage — equals
|
|
933
|
+
# the captured `proofDigest` clears the gate to `record`; any non-PASS, a missing file, malformed
|
|
934
|
+
# JSON, or missing/mismatched proof evidence does not. Declaration order: PASS+proof guard FIRST,
|
|
935
|
+
# then the bounded remediation route (R4), then the always catch-all so a PASS verdict with a
|
|
936
|
+
# missing/malformed proof block still terminates at `failed` instead of hanging the state.
|
|
937
|
+
- from: verify
|
|
938
|
+
to: record
|
|
939
|
+
description: Verification verdict is PASS and its proof block names the captured digest on every stage — record results and proceed to done.
|
|
940
|
+
guard:
|
|
941
|
+
kind: shell
|
|
942
|
+
options:
|
|
943
|
+
command: >-
|
|
944
|
+
V=".spur/run/$wbs-verdict.json";
|
|
945
|
+
test "$(jq -r .verdict "$V" 2>/dev/null)" = PASS &&
|
|
946
|
+
test "$(jq -r '.proof.digest // ""' "$V" 2>/dev/null)" = "$proofDigest" &&
|
|
947
|
+
test "$(jq -r '.proof.stages.qualityGate.digest // ""' "$V" 2>/dev/null)" = "$proofDigest" &&
|
|
948
|
+
test "$(jq -r '.proof.stages.review.digest // ""' "$V" 2>/dev/null)" = "$proofDigest" &&
|
|
949
|
+
test "$(jq -r '.proof.stages.verification.digest // ""' "$V" 2>/dev/null)" = "$proofDigest" &&
|
|
950
|
+
test "$(jq -r '.proof.runId // ""' "$V" 2>/dev/null)" = "$__runId" &&
|
|
951
|
+
test "$(jq -r '.proof.definitionDigest // ""' "$V" 2>/dev/null)" = "$__definitionDigest"
|
|
952
|
+
- from: verify
|
|
953
|
+
to: test-fix
|
|
954
|
+
description: >-
|
|
955
|
+
Verification returned a repairable non-PASS and the shared fix budget is not exhausted —
|
|
956
|
+
bounded remediation hop (task 0703 R4); the chain re-enters quality → review → verify on a
|
|
957
|
+
freshly captured digest. Never reached on PASS: remediation cannot follow certification.
|
|
958
|
+
guard:
|
|
959
|
+
kind: shell
|
|
960
|
+
options:
|
|
961
|
+
command: >-
|
|
962
|
+
V="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
|
|
963
|
+
test -n "$V" && test "$V" != PASS &&
|
|
964
|
+
test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"
|
|
965
|
+
- from: verify
|
|
966
|
+
to: failed
|
|
967
|
+
description: >-
|
|
968
|
+
Non-PASS with the fix budget exhausted, or a PASS/missing/malformed verdict whose proof
|
|
969
|
+
block is absent or mismatched (task 0703 R5) — block before done; defense catch-all so the
|
|
970
|
+
state always has a viable outgoing edge.
|
|
971
|
+
guard:
|
|
972
|
+
kind: always
|
|
973
|
+
# ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23)
|
|
974
|
+
# PLUS the proof-block re-assertion (task 0703 R5): the verdict artifact must still be PASS and
|
|
975
|
+
# still name the captured digest — a forged or mutated completion artifact fails closed here.
|
|
976
|
+
# Declaration order: pass guard first.
|
|
977
|
+
- from: record
|
|
978
|
+
to: done
|
|
979
|
+
description: Task check passed and the verdict proof block still names the captured digest — certify done.
|
|
980
|
+
guard:
|
|
981
|
+
kind: shell
|
|
982
|
+
options:
|
|
983
|
+
command: >-
|
|
984
|
+
$spurBin task check $wbs &&
|
|
985
|
+
test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS &&
|
|
986
|
+
test "$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" = "$proofDigest"
|
|
987
|
+
- from: record
|
|
988
|
+
to: failed
|
|
989
|
+
description: Task check failed or proof evidence missing/malformed/mismatched — block before done.
|
|
990
|
+
guard:
|
|
991
|
+
kind: always
|