@gobing-ai/spur 0.3.53 → 0.3.55
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/corpus-baseline.json +2783 -2783
- package/config/pipeline-budgets.json +42 -0
- package/config/plugin-scripts.json +73 -0
- package/config/workflow-composition-baseline.json +561 -0
- package/config/workflows/docs-pipeline.yaml +29 -13
- package/config/workflows/feature-dev.yaml +54 -2
- package/config/workflows/idea-pipeline.yaml +15 -0
- package/config/workflows/task-pipeline.yaml +77 -65
- package/config/workflows/wrapup-pipeline.yaml +40 -45
- package/package.json +9 -9
- package/plugins/README.md +1 -1
- package/plugins/sp/README.md +3 -3
- package/plugins/sp/agents/super-planner.md +1 -1
- package/plugins/sp/commands/dev-daily.md +1 -1
- package/plugins/sp/commands/dev-history-load.md +1 -1
- package/plugins/sp/commands/dev-idea.md +10 -2
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +160 -0
- package/plugins/sp/scripts/batch-preflight.ts +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +479 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +75 -37
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +204 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -1
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +141 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +1 -1
- package/plugins/sp/scripts/feature-sync-bounded.mjs +290 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +9 -8
- package/plugins/sp/scripts/history-load.mjs +268 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +685 -0
- package/plugins/sp/scripts/pr-reviewing.ts +1 -1
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/skills/daily-summary/SKILL.md +3 -3
- package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -5
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
- package/plugins/sp/skills/pr-reviewing/SKILL.md +3 -3
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +3 -4
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +1 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +3 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +4 -4
- package/spur.js +4270 -1323
- package/web/_astro/{BoardApp.plVXYkKo.js → BoardApp.DCLSB3Zs.js} +63 -63
- package/web/_astro/BoardApp.Dx5gzAhb.js +1 -0
- package/web/_astro/{TaskDetail.5DIyVQ_N.js → TaskDetail.DN_RxS-2.js} +1 -1
- package/web/_astro/{arc.BnUgL7ho.js → arc.CldTHRg-.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B9YMJau-.js → architectureDiagram-3BPJPVTR.DQ4T9oeU.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Dw2qgvWJ.js → blockDiagram-GPEHLZMM.CGDaXH8J.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.jTbU0-Nk.js → c4Diagram-AAUBKEIU.Cqsc2iub.js} +1 -1
- package/web/_astro/channel.CrBJYpxo.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.Di46EtkU.js → chunk-2J33WTMH.CQSKsRmq.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DweHBwqQ.js → chunk-4BX2VUAB.Pv1HCtp8.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.rRjTKaa7.js → chunk-55IACEB6.-9MseOIk.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BkEQIkSV.js → chunk-727SXJPM.M3OI1DF7.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.Cfe9IvAd.js → chunk-AQP2D5EJ.NfHka5Ca.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.fI6tLbeN.js → chunk-FMBD7UC4.B2suYe3s.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BIjgpq0O.js → chunk-ND2GUHAM.BfJ7aucP.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.ZwLnVxyz.js → chunk-QZHKN3VN._582hZVc.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.BJAGYidE.js → classDiagram-4FO5ZUOK._tttU_jk.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BJAGYidE.js → classDiagram-v2-Q7XG4LA2._tttU_jk.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.B5y3nz26.js → cose-bilkent-S5V4N54A.C4rVATLQ.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.DHmw0ZBC.js → dagre-BM42HDAG.8BLRi7f9.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.10gapJfY.js → diagram-2AECGRRQ.D6lwUZos.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Gh33_drM.js → diagram-5GNKFQAL.Cl_rMiVT.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.8ROWlfcX.js → diagram-KO2AKTUF.DhMExHLp.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BsP3tKmg.js → diagram-LMA3HP47.3OAxzH_o.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.DM2WFDhU.js → diagram-OG6HWLK6.Uh9RLucG.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.royaC_lH.js → erDiagram-TEJ5UH35.izzElG0c.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.C9GEvIie.js → flowDiagram-I6XJVG4X.BlGwp5qM.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.CXO4T2U0.js → ganttDiagram-6RSMTGT7.je7Vf8dN.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Q02KzYH7.js → gitGraphDiagram-PVQCEYII.DNk0Ycop.js} +1 -1
- package/web/_astro/{index.DAxu50UF.css → index.V6Q7nhed.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DNfSDKJt.js → infoDiagram-5YYISTIA.K2HhGyWH.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BFttvtgJ.js → ishikawaDiagram-YF4QCWOH.BbbOW4IN.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.Cfm-2SbG.js → journeyDiagram-JHISSGLW.B1UaZKqN.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BWGNW6fV.js → kanban-definition-UN3LZRKU.9tsw5QFd.js} +1 -1
- package/web/_astro/{linear.BprdUk2Y.js → linear.I9dvtu-j.js} +1 -1
- package/web/_astro/{mermaid.core.B2zRNxtX.js → mermaid.core.5bKJsMgf.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.g9jij8sw.js → mindmap-definition-RKZ34NQL.BC5MqSn6.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CbzR4n5b.js → pieDiagram-4H26LBE5.B8GIMyxD.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DxV3viWt.js → quadrantDiagram-W4KKPZXB.K_aZSM_u.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DvAb90JS.js → requirementDiagram-4Y6WPE33.B_V5kzfT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.CIFz5t4D.js → sankeyDiagram-5OEKKPKP.DyV2quLS.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DzlEQynd.js → sequenceDiagram-3UESZ5HK.BlKvPnBv.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Dph2V-ts.js → stateDiagram-AJRCARHV.C1lYUMdS.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.wJhfrLAy.js → stateDiagram-v2-BHNVJYJU.DJWjr3QV.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.BrSiyubX.js → timeline-definition-PNZ67QCA.CYYiXVlv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.COobPrUX.js → vennDiagram-CIIHVFJN.zYjQKa_S.js} +1 -1
- package/web/_astro/{wardley-L42UT6IY.CrjeTx8A.js → wardley-L42UT6IY.BGrWTY3D.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B8_fTQ8E.js → wardleyDiagram-YWT4CUSO.CkXxtcI1.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.BPpokgVv.js → xychartDiagram-2RQKCTM6.BhKH3KG3.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/planning-pipeline.yaml +0 -249
- package/config/workflows/task-pipeline2.yaml +0 -778
- package/web/_astro/BoardApp.pdtYu-yW.js +0 -1
- package/web/_astro/channel.DEqvaz-I.js +0 -1
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
# Task execution pipeline 2 — rival pipeline (feature I6, task 0596): task-pipeline.yaml
|
|
2
|
-
# plus a residual-sweep stage. NOT promoted — task-pipeline.yaml stays the default.
|
|
3
|
-
#
|
|
4
|
-
# Delta from task-pipeline.yaml:
|
|
5
|
-
# 1. `verify` PASS now routes to `residual-sweep` (operator's manual fallback, wired
|
|
6
|
-
# into the FSM) before `record`/commit — it no longer depends on the operator
|
|
7
|
-
# remembering to type the prompt.
|
|
8
|
-
# 2. The two-layer plan (Layer 1 = stages; Layer 2 = the active stage's onEnter steps)
|
|
9
|
-
# is sourced from `workflow run --dry-run` + the parsed definition and rendered by
|
|
10
|
-
# the inline driver (see inline-pipeline-driver.md § Two-layer plan) — zero engine
|
|
11
|
-
# change, consumption-only.
|
|
12
|
-
#
|
|
13
|
-
# Orchestration is configuration (ADR-022 / §3.2): this is YAML over the existing
|
|
14
|
-
# dual-workflow engine — zero new engine code. The pipeline NEVER touches files
|
|
15
|
-
# directly — `record` writes via `spur task update --section`, and status moves go
|
|
16
|
-
# through the normal `spur task update <wbs> <status>` verb so the lifecycle guards
|
|
17
|
-
# (0055) apply identically. Run linkage is written to `task_run_links` (kind=pipeline).
|
|
18
|
-
#
|
|
19
|
-
# Shape: precheck → implement → test[→test-fix↔test-recheck] → review → approve(HITL)
|
|
20
|
-
# → verify → record → done
|
|
21
|
-
# (precheck failure short-circuits to `failed`; approve routes to `failed` on
|
|
22
|
-
# operator rejection or `cancelled` on operator cancel — R1, bug-750).
|
|
23
|
-
# `test` is the project quality gate (shell + bounded /sp:dev-fixall), not
|
|
24
|
-
# /sp:dev-unit (coverage gap-fill; router C3/C5).
|
|
25
|
-
#
|
|
26
|
-
# Vars (passed as a JSON object via `--vars`):
|
|
27
|
-
# wbs — task WBS (required)
|
|
28
|
-
# profile — "auto" skips HITL approve (R4)
|
|
29
|
-
# agent / spurBin — executor + spur binary (CLI overrides spurBin)
|
|
30
|
-
# stepTimeoutMs — agent.run budget for review/verify/test-fix (ms)
|
|
31
|
-
# implementTimeoutMs — implement agent.run budget (ms)
|
|
32
|
-
# qualityGateCmd — project gate (default: bun run autofix && bun run spur-check)
|
|
33
|
-
# qualityGateMaxFixAttempts — max /sp:dev-fixall hops after a red gate (default: 2)
|
|
34
|
-
#
|
|
35
|
-
# Seeded by `spur init`. agent.run inputs are pure slash commands (ADR-043).
|
|
36
|
-
|
|
37
|
-
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
38
|
-
kind: state-machine
|
|
39
|
-
name: task-pipeline2
|
|
40
|
-
description: "Rival task pipeline: precheck → implement → quality-gate (test/fix/recheck) → review → approve → verify → residual-sweep → record → done"
|
|
41
|
-
# Task entity lifecycle (owned by task-lifecycle.yaml):
|
|
42
|
-
# normal: backlog → todo → wip → testing → done
|
|
43
|
-
# blocked is bidirectional with todo/wip/testing; done may reopen to wip.
|
|
44
|
-
# any non-terminal state may transition to cancelled.
|
|
45
|
-
# Guards:
|
|
46
|
-
# wip → testing: spur task check <wbs>
|
|
47
|
-
# testing → done: spur task check <wbs> --strict-core, then PASS-verdict provenance
|
|
48
|
-
# Invalid: todo → testing, todo → done, and wip → done.
|
|
49
|
-
# Pipeline states below are orchestration states, not task statuses.
|
|
50
|
-
iterationBound: 20
|
|
51
|
-
initialState: precheck
|
|
52
|
-
terminalStates:
|
|
53
|
-
- done
|
|
54
|
-
- failed
|
|
55
|
-
- cancelled
|
|
56
|
-
failureStates:
|
|
57
|
-
- failed
|
|
58
|
-
- cancelled
|
|
59
|
-
vars:
|
|
60
|
-
wbs: "0000"
|
|
61
|
-
profile: "standard"
|
|
62
|
-
# PATH-independent spur invocation for shell guards/actions. The CLI overrides this
|
|
63
|
-
# at run start (resolveSpurBin); the literal default is a safe fallback so direct/dry
|
|
64
|
-
# runs and `workflow validate` resolve the template. (See AGENTS.md / ADR-026.)
|
|
65
|
-
spurBin: "spur"
|
|
66
|
-
# Agent the pipeline's agent.run steps invoke. Override per run with
|
|
67
|
-
# `--vars '{"agent":"claude"}'`. Pinned (not left to the AiRunner's <default>
|
|
68
|
-
# selection) so a broken/misconfigured agent on the box can't silently capture the run.
|
|
69
|
-
agent: "omp"
|
|
70
|
-
# Implement-only executor override (R1, task 0454). Resolves to this YAML literal
|
|
71
|
-
# unless overridden. The `--agent` flag (execution-batch.md §3.2) forwards into BOTH
|
|
72
|
-
# `agent` and `implementAgent`, so a pinned `--agent X` reaches implement too. To
|
|
73
|
-
# pin ONLY implement while other hops keep the default, pass
|
|
74
|
-
# `--vars '{"implementAgent":"omp-zai"}'`.
|
|
75
|
-
implementAgent: "omp"
|
|
76
|
-
# Step-level timeout for agentic hops (review / verify / test-fix) in ms.
|
|
77
|
-
# Soft quality-gate shells are unbounded by this var (host shell only).
|
|
78
|
-
# Raised 600s → 1800s (task 0398 R4 / H6 dogfood). Override:
|
|
79
|
-
# `--vars '{"stepTimeoutMs":"120000"}'`. 30 min is headroom, not a licence to
|
|
80
|
-
# run unbounded — if a hop still hits this wall, STOP and record it.
|
|
81
|
-
stepTimeoutMs: "1800000"
|
|
82
|
-
# Timeout for the `implement` step specifically (R2a). Implementation is the
|
|
83
|
-
# heaviest agent.run step (full read/write/test-probe loop) and has timed out
|
|
84
|
-
# at the 600s default in five consecutive dogfood runs (bugs 742/744/746/748),
|
|
85
|
-
# a 100% failure rate at that budget. 30 min gives real headroom; STOP and
|
|
86
|
-
# record honestly rather than raising it further without operator sign-off.
|
|
87
|
-
implementTimeoutMs: "1800000"
|
|
88
|
-
# Answer captured by the approve gate's hitl.confirm (R1): "yes" | "no" | "cancel".
|
|
89
|
-
# Empty by default; only meaningful once the approve state has been entered.
|
|
90
|
-
__hitlAnswer: ""
|
|
91
|
-
# Residual-sweep prompt subject (R4). The operator's verbatim fallback prompt is
|
|
92
|
-
# templated over this noun phrase so both forms stay verbatim. Single-task default
|
|
93
|
-
# "current task"; a feature batch overrides it via `--vars`:
|
|
94
|
-
# --vars '{"residualSweepTarget":"these tasks for current feature"}'
|
|
95
|
-
residualSweepTarget: "current task"
|
|
96
|
-
# Project quality gate for the `test` hop (probe + fixall + recheck). Override per project
|
|
97
|
-
# with the same package-manager surface (this monorepo is Bun-only):
|
|
98
|
-
# `--vars '{"qualityGateCmd":"bun run lint && bun run test"}'`. Soft probe, hard recheck,
|
|
99
|
-
# and the fixall slash input all use this same var so the command stays single-sourced.
|
|
100
|
-
# TRUSTED CONFIG ONLY — this string is executed via `sh -c` (see test/test-recheck). Never
|
|
101
|
-
# interpolate untrusted operator/LLM input into qualityGateCmd (task 0436 SECUA residual).
|
|
102
|
-
qualityGateCmd: "bun run format && bun run spur-check"
|
|
103
|
-
# Cheap red-detector run before the full gate on **recheck only**; empty ⇒ no probe
|
|
104
|
-
# (full gate every recheck — the pre-0587 behavior). A project overriding qualityGateCmd
|
|
105
|
-
# should override this too. TRUSTED CONFIG ONLY — executed via `sh -c` (same surface as
|
|
106
|
-
# qualityGateCmd). Invariant: `review` is only ever entered through a full green
|
|
107
|
-
# qualityGateCmd — only the full gate writes PASS to <wbs>-test-gate.status.
|
|
108
|
-
gateProbeCmd: "bun run lint"
|
|
109
|
-
# Max /sp:dev-fixall attempts after a red quality-gate probe/recheck (bounded; no thrash).
|
|
110
|
-
# Attempt counter: .spur/run/<wbs>-test-fix-attempt. Default 2 = two fixall hops before failed.
|
|
111
|
-
qualityGateMaxFixAttempts: "2"
|
|
112
|
-
# Post-implement auto-format. Overridable like qualityGateCmd so a non-Bun seeded
|
|
113
|
-
# project can point it at its own formatter; invoked best-effort (a missing or
|
|
114
|
-
# failing formatter must never abort a run — the quality gate is the real gate).
|
|
115
|
-
formatCmd: "bun run format"
|
|
116
|
-
# Bounded `file:line` anchors extracted from the failing quality-gate log by the
|
|
117
|
-
# `test` / `test-recheck` hops (deduped, max 20, space-separated on one line).
|
|
118
|
-
# `test-fix` reads `.spur/run/<wbs>-test-gate.findings` into this var and names the
|
|
119
|
-
# anchors directly in the /sp:dev-fixall input, so the fix agent starts AT the failing
|
|
120
|
-
# file:line instead of re-deriving it from a fresh gate run (0482 R3).
|
|
121
|
-
gateFindings: ""
|
|
122
|
-
# Max R-items in ## Requirements before size precheck fails (R2, task 0454).
|
|
123
|
-
# Override with `--vars '{"maxImplementReqs":"12"}'`.
|
|
124
|
-
maxImplementReqs: "5"
|
|
125
|
-
# Max checklist items under ## Plan before size precheck fails (R2, task 0454).
|
|
126
|
-
# Override with `--vars '{"maxImplementPlanItems":"15"}'`.
|
|
127
|
-
maxImplementPlanItems: "8"
|
|
128
|
-
# Diff-scope guard on the implement hop (R1, task 0487). When the target task
|
|
129
|
-
# body backticks at least one path, non-corpus changes outside those paths
|
|
130
|
-
# fail the step by name. New files beside a declared file are allowed. Empty
|
|
131
|
-
# (default) = on; set to "off" to bypass:
|
|
132
|
-
# `--vars '{"implementScopeGuard":"off"}'`.
|
|
133
|
-
implementScopeGuard: ""
|
|
134
|
-
|
|
135
|
-
states:
|
|
136
|
-
- id: precheck
|
|
137
|
-
description: >
|
|
138
|
-
Pre-flight: soft agent doctor + transition guards for doctor PASS and
|
|
139
|
-
`spur task check <wbs>`. Failures route to the `failed` terminal state
|
|
140
|
-
(not a raw lifecycle abort mid-enter).
|
|
141
|
-
onEnter:
|
|
142
|
-
# Soft doctor — write status and always exit 0 so transitions can branch to
|
|
143
|
-
# `failed` cleanly (same pattern as the quality-gate soft probe).
|
|
144
|
-
#
|
|
145
|
-
# R2 (0487): probe BOTH resolved executors ($agent and $implementAgent) and
|
|
146
|
-
# FAIL on `authenticated: unauthenticated`. Previously only $agent was probed
|
|
147
|
-
# and auth was informational, so a run whose implement executor had no
|
|
148
|
-
# provider key sailed through precheck into a guaranteed implement failure
|
|
149
|
-
# (runs e8cb00e7 / b16bfbf4: "auth: no … API key not found for provider
|
|
150
|
-
# 'volc'" → precheck ✓). `unknown` auth keeps the old soft behavior — some
|
|
151
|
-
# agents expose no auth-status verb. `spur agent doctor` CLI exit-code
|
|
152
|
-
# semantics are deliberately untouched; the gate lives here.
|
|
153
|
-
# R4 (0487): one divergence line when the two executors differ (legitimate
|
|
154
|
-
# when only implementAgent is pinned, but it must be visible in the log).
|
|
155
|
-
# R2 (0503): omp/pi env-key probe misses are soft because the CLI process
|
|
156
|
-
# cannot see relay-owned credentials; explicit non-omp auth failures remain hard.
|
|
157
|
-
- kind: shell
|
|
158
|
-
options:
|
|
159
|
-
command: >-
|
|
160
|
-
mkdir -p .spur/run &&
|
|
161
|
-
DOCTOR_FILE=".spur/run/$wbs-precheck-doctor.status" &&
|
|
162
|
-
classifyDoctorProbe() {
|
|
163
|
-
PROBE_DETAIL="$1";
|
|
164
|
-
PROBE_AGENT="$2";
|
|
165
|
-
case "$PROBE_AGENT" in
|
|
166
|
-
omp|omp-*|pi|pi-*)
|
|
167
|
-
if printf '%s' "$PROBE_DETAIL" | grep -Eiq 'API key not found for provider|no probe registered'; then
|
|
168
|
-
printf 'env-miss\n';
|
|
169
|
-
return;
|
|
170
|
-
fi;;
|
|
171
|
-
esac;
|
|
172
|
-
if printf '%s' "$PROBE_DETAIL" | grep -Eiq 'invalid (API )?key|unauthori[sz]ed|forbidden|authentication failed|credential(s)? rejected'; then
|
|
173
|
-
printf 'auth-fail\n';
|
|
174
|
-
else
|
|
175
|
-
printf 'unknown\n';
|
|
176
|
-
fi;
|
|
177
|
-
} &&
|
|
178
|
-
STATUS=PASS;
|
|
179
|
-
if [ -n "$implementAgent" ] && [ "$implementAgent" != "$agent" ]; then
|
|
180
|
-
echo "precheck: agent=$agent implementAgent=$implementAgent (executors diverge)";
|
|
181
|
-
EXECS="$agent $implementAgent";
|
|
182
|
-
else
|
|
183
|
-
EXECS="$agent";
|
|
184
|
-
fi;
|
|
185
|
-
for EXE in $EXECS; do
|
|
186
|
-
OUT=$($spurBin agent doctor "$EXE" --json 2>&1) || {
|
|
187
|
-
echo "precheck: FAIL - doctor exited non-zero for $EXE";
|
|
188
|
-
echo "$OUT";
|
|
189
|
-
STATUS=FAIL;
|
|
190
|
-
continue;
|
|
191
|
-
};
|
|
192
|
-
AUTH=$(printf '%s' "$OUT" | jq -r '.agents[0].authenticated // "unknown"' 2>/dev/null || echo unknown);
|
|
193
|
-
DETAIL=$(printf '%s' "$OUT" | jq -r '.agents[0].modelStatus.detail // ""' 2>/dev/null || echo "");
|
|
194
|
-
PROBE=$(classifyDoctorProbe "$DETAIL" "$EXE");
|
|
195
|
-
echo "precheck: $EXE auth=$AUTH probe=$PROBE $DETAIL";
|
|
196
|
-
if [ "$AUTH" = unauthenticated ]; then
|
|
197
|
-
case "$EXE:$PROBE" in
|
|
198
|
-
omp*:env-miss|pi*:env-miss|omp*:unknown|pi*:unknown)
|
|
199
|
-
echo "precheck: SOFT - executor $EXE auth probe cannot see agent-owned credentials";;
|
|
200
|
-
*)
|
|
201
|
-
echo "precheck: FAIL - executor $EXE is unauthenticated; fix agent.default or pass --vars '{\"agent\":\"<authenticated-executor>\"}' ($spurBin agent doctor $EXE --json); $DETAIL";
|
|
202
|
-
STATUS=FAIL;;
|
|
203
|
-
esac;
|
|
204
|
-
fi;
|
|
205
|
-
done;
|
|
206
|
-
echo "$STATUS" > "$DOCTOR_FILE";
|
|
207
|
-
exit 0
|
|
208
|
-
# R6 (0487): pre-launch hygiene WARNING (never a block) — starting a task on
|
|
209
|
-
# a tree already dirty with another task's implementation is how 0485's diff
|
|
210
|
-
# got swept into 0486's run. Corpus dirs are excluded: the pipeline writes
|
|
211
|
-
# those itself.
|
|
212
|
-
# R1 (0511): task-corpus dirt gets its own advisory NOTE — docs/tasks* is
|
|
213
|
-
# excluded from the non-corpus warning, so pre-existing task-corpus edits are
|
|
214
|
-
# invisible at launch yet can be accidentally staged with the current task
|
|
215
|
-
# later. Advisory only (never a block): the pipeline legitimately updates
|
|
216
|
-
# task files, so corpus dirt is launch-time visibility, not a lifecycle guard.
|
|
217
|
-
- kind: shell
|
|
218
|
-
options:
|
|
219
|
-
command: >-
|
|
220
|
-
DIRTY=$(git status --porcelain -- . ':(exclude)docs/tasks*' ':(exclude)docs/features' 2>/dev/null);
|
|
221
|
-
if [ -n "$DIRTY" ]; then
|
|
222
|
-
echo "precheck: WARNING - working tree has uncommitted non-corpus changes; commit or stash before starting a new task:";
|
|
223
|
-
echo "$DIRTY";
|
|
224
|
-
fi;
|
|
225
|
-
CORPUS_DIRTY=$(git status --porcelain -- ':(glob)docs/tasks*/**' 2>/dev/null);
|
|
226
|
-
if [ -n "$CORPUS_DIRTY" ]; then
|
|
227
|
-
echo "precheck: NOTE - task corpus has uncommitted changes; review before staging with this task:";
|
|
228
|
-
echo "$CORPUS_DIRTY";
|
|
229
|
-
fi;
|
|
230
|
-
exit 0
|
|
231
|
-
- kind: note
|
|
232
|
-
options:
|
|
233
|
-
# R4 task_run_links linkage (kind=pipeline) is written by a WorkflowService
|
|
234
|
-
# hook at run start — see the 0062 follow-up note; there is no link-writing
|
|
235
|
-
# CLI verb to call from a shell step, so it cannot live in pure YAML.
|
|
236
|
-
message: "Pipeline start for task ${vars.wbs}."
|
|
237
|
-
# R1 (0453): auto-profile precheck reopens a done feature before task check.
|
|
238
|
-
# Under profile=auto, resolve feature_id, sync (preferred) or update to active.
|
|
239
|
-
# Under non-auto, leave R4 message to guide the operator.
|
|
240
|
-
- kind: shell
|
|
241
|
-
options:
|
|
242
|
-
command: >-
|
|
243
|
-
if [ "$profile" = auto ]; then
|
|
244
|
-
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
245
|
-
jq -r '.feature_id // .frontmatter.feature_id // empty' 2>/dev/null);
|
|
246
|
-
if [ -n "$FID" ]; then
|
|
247
|
-
$spurBin feature sync "$FID" --force --json 2>/dev/null ||
|
|
248
|
-
$spurBin feature update "$FID" active 2>/dev/null || true;
|
|
249
|
-
fi;
|
|
250
|
-
fi;
|
|
251
|
-
exit 0
|
|
252
|
-
# R2 (0454): task size precheck — evaluate R-item and Plan-item counts.
|
|
253
|
-
# Writes PASS/FAIL to .spur/run/<wbs>-precheck-size.status. Always exit 0
|
|
254
|
-
# (soft check, like doctor). The precheck→implement guard reads the file.
|
|
255
|
-
# R3 (0487): `--executor` adds the size-vs-capability gate — a task past the
|
|
256
|
-
# DEFAULT caps routed to a sub-`capable-1` executor blocks here instead of
|
|
257
|
-
# burning the full implementTimeoutMs and exiting 3 (run ca130182).
|
|
258
|
-
- kind: shell
|
|
259
|
-
options:
|
|
260
|
-
command: >-
|
|
261
|
-
SIZE_FILE=".spur/run/$wbs-precheck-size.status" &&
|
|
262
|
-
mkdir -p .spur/run &&
|
|
263
|
-
bun plugins/sp/scripts/task-size-precheck.ts "$wbs"
|
|
264
|
-
--spur-bin "$spurBin" --max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems"
|
|
265
|
-
--executor "$implementAgent" &&
|
|
266
|
-
exit 0
|
|
267
|
-
|
|
268
|
-
- id: implement
|
|
269
|
-
description: >
|
|
270
|
-
Run agent-driven implementation via /sp:dev-run --mode implement, THEN move the
|
|
271
|
-
task to `wip` via the normal verb. The lifecycle transition fires AFTER the
|
|
272
|
-
implement agent.run succeeds — onEnter runs in declaration order and the default
|
|
273
|
-
'fail' policy halts the sequence (routing the run to `failed`) if the agent.run
|
|
274
|
-
fails or times out, so a halted implement never leaves the task at `wip` with an
|
|
275
|
-
empty Solution (the F3 half-state). The `--no-lifecycle` flag is correct here: the
|
|
276
|
-
pipeline owns this transition; the FSM guard would otherwise re-run `spur task
|
|
277
|
-
check`, which is precheck's job.
|
|
278
|
-
NOTE: this step must NOT call /sp:dev-run in full mode (the default) —
|
|
279
|
-
that command DRIVES this pipeline, so calling it here recurses.
|
|
280
|
-
--mode implement is the single-step implement entry.
|
|
281
|
-
onEnter:
|
|
282
|
-
- kind: agent.run
|
|
283
|
-
options:
|
|
284
|
-
agent: ${vars.implementAgent}
|
|
285
|
-
# Pure slash command only (ADR-043). Anti-recursion / implement discipline
|
|
286
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
287
|
-
role: coder
|
|
288
|
-
# lives in /sp:dev-run --mode implement → sp:code-implementation, not YAML prose.
|
|
289
|
-
input: /sp:dev-run --mode implement ${vars.wbs} --auto
|
|
290
|
-
timeoutMs: ${vars.implementTimeoutMs}
|
|
291
|
-
# R3 (task 0424): empty-implement no-op guard — the agent.run action
|
|
292
|
-
# fails the step when exit 0 produced zero non-corpus file changes, so
|
|
293
|
-
# a silent no-op routes the run to `failed` here instead of drifting
|
|
294
|
-
# into test/review and being caught a full pass later.
|
|
295
|
-
requireDiff: true
|
|
296
|
-
- kind: shell
|
|
297
|
-
options:
|
|
298
|
-
command: >-
|
|
299
|
-
retry_transient() {
|
|
300
|
-
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
301
|
-
printf '%s\n' "$RETRY_OUTPUT";
|
|
302
|
-
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
303
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
304
|
-
sleep 2;
|
|
305
|
-
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
306
|
-
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
307
|
-
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
308
|
-
echo "node_modules link/dependency broken — run bun install and retry";
|
|
309
|
-
fi;
|
|
310
|
-
return "$RETRY_RC";
|
|
311
|
-
};
|
|
312
|
-
retry_transient $spurBin task update "$wbs" wip --no-lifecycle
|
|
313
|
-
# Post-implement cleanup: auto-format any unformatted output the agent produced.
|
|
314
|
-
# The agent may leave files that don't pass `biome check`, which then fails
|
|
315
|
-
# `bun run lint` downstream. Running format here is cheap (< 1 s) and prevents
|
|
316
|
-
# the test stage from tripping on a purely mechanical formatting defect (dogfood
|
|
317
|
-
# bug-733 — omp left agent-run.test.ts unformatted, causing a spurious lint gate
|
|
318
|
-
# failure that the ## Testing section mis-attributed to "pre-existing gaps").
|
|
319
|
-
# Best-effort: `; exit 0` (not `&&`) so a missing/failing formatter in a seeded
|
|
320
|
-
# non-Bun project cannot abort a run mid-implement. Formatting is a convenience;
|
|
321
|
-
# `${vars.qualityGateCmd}` at `test` is the gate that actually decides.
|
|
322
|
-
- kind: shell
|
|
323
|
-
options:
|
|
324
|
-
command: "$formatCmd ; exit 0"
|
|
325
|
-
|
|
326
|
-
# ── test hop (quality gate + bounded auto-fix) ─────────────────────────────
|
|
327
|
-
# NOT /sp:dev-unit. That command (sp:code-testing) *extends/generates* tests toward
|
|
328
|
-
# a coverage target; it is not the project quality gate. Coverage gap-fill remains
|
|
329
|
-
# available via router C3/C5 (`/sp:dev-unit`) or inside implement — not this hop.
|
|
330
|
-
#
|
|
331
|
-
# Shape (engine has no per-action "run next only if previous failed" without
|
|
332
|
-
# `onError`, and Spur's action schema does not expose `onError`):
|
|
333
|
-
# test — soft probe: qualityGateCmd → PASS|FAIL status; always exit 0
|
|
334
|
-
# (green path: one gate run → review; no fixall/recheck tax)
|
|
335
|
-
# test-fix — increment attempt + pure slash /sp:dev-fixall (≤ max attempts)
|
|
336
|
-
# test-recheck — soft recheck → review | test-fix | failed (never silent lifecycle abort)
|
|
337
|
-
- id: test
|
|
338
|
-
description: >
|
|
339
|
-
Soft quality-gate probe (single logical gate on the green path; bounded retries only
|
|
340
|
-
for SQLite lock contention). Runs
|
|
341
|
-
`${vars.qualityGateCmd}`, records PASS|FAIL under
|
|
342
|
-
`.spur/run/<wbs>-test-gate.status`, resets the fix attempt counter, always exit 0.
|
|
343
|
-
onEnter:
|
|
344
|
-
- kind: shell
|
|
345
|
-
options:
|
|
346
|
-
command: >-
|
|
347
|
-
mkdir -p .spur/run &&
|
|
348
|
-
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
349
|
-
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
350
|
-
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
351
|
-
echo 0 > "$ATTEMPT_FILE" &&
|
|
352
|
-
: > "$LOG_FILE" &&
|
|
353
|
-
gate_attempt=1;
|
|
354
|
-
while [ "$gate_attempt" -le 5 ]; do
|
|
355
|
-
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
356
|
-
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
357
|
-
gate_locked=0;
|
|
358
|
-
grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
|
|
359
|
-
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
360
|
-
rm -f "$ATTEMPT_LOG";
|
|
361
|
-
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
362
|
-
printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
|
|
363
|
-
sleep 10;
|
|
364
|
-
gate_attempt=$((gate_attempt + 1));
|
|
365
|
-
done &&
|
|
366
|
-
cat "$LOG_FILE" &&
|
|
367
|
-
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
368
|
-
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 &&
|
|
369
|
-
if [ "$gate_rc" -eq 0 ]; then
|
|
370
|
-
printf 'PASS\n' > "$STATUS_FILE";
|
|
371
|
-
else
|
|
372
|
-
printf 'FAIL\n' > "$STATUS_FILE";
|
|
373
|
-
fi &&
|
|
374
|
-
exit 0
|
|
375
|
-
|
|
376
|
-
- id: test-fix
|
|
377
|
-
description: >
|
|
378
|
-
Bounded auto-fix hop when the quality gate is red. Increments
|
|
379
|
-
`.spur/run/<wbs>-test-fix-attempt`, then pure slash (ADR-043)
|
|
380
|
-
`/sp:dev-fixall` against `${vars.qualityGateCmd}`.
|
|
381
|
-
onEnter:
|
|
382
|
-
- kind: shell
|
|
383
|
-
options:
|
|
384
|
-
command: >-
|
|
385
|
-
mkdir -p .spur/run &&
|
|
386
|
-
ATTEMPT_FILE=".spur/run/$wbs-test-fix-attempt" &&
|
|
387
|
-
n=$(cat "$ATTEMPT_FILE" 2>/dev/null || echo 0) &&
|
|
388
|
-
printf '%s\n' "$((n + 1))" > "$ATTEMPT_FILE"
|
|
389
|
-
# R3 (0482): project the extracted gate anchors into a var so the dispatch input
|
|
390
|
-
# can NAME the failing file:line, not merely point at a log. A vars template cannot
|
|
391
|
-
# run a shell, so the read is an action, not an inline `$(cat ...)` substitution.
|
|
392
|
-
- kind: file.read.into-var
|
|
393
|
-
options:
|
|
394
|
-
path: .spur/run/${vars.wbs}-test-gate.findings
|
|
395
|
-
var: gateFindings
|
|
396
|
-
- kind: agent.run
|
|
397
|
-
options:
|
|
398
|
-
agent: ${vars.agent}
|
|
399
|
-
# R3 (0482): `--findings` names the failing anchors inline; `--gate-log` remains
|
|
400
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
401
|
-
role: coder
|
|
402
|
-
# the full-context escape hatch when the digest is not enough.
|
|
403
|
-
input: /sp:dev-fixall "${vars.qualityGateCmd}" --gate-log .spur/run/${vars.wbs}-test-gate.log --findings "${vars.gateFindings}"
|
|
404
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
405
|
-
|
|
406
|
-
- id: test-recheck
|
|
407
|
-
description: >
|
|
408
|
-
Soft recheck after fixall with bounded SQLite-lock retry. Writes PASS|FAIL (always exit 0). Transitions
|
|
409
|
-
branch to review (PASS), another test-fix (FAIL and under max attempts),
|
|
410
|
-
or the pipeline `failed` state (FAIL and attempts exhausted) — never a
|
|
411
|
-
raw lifecycle abort that skips the terminal `failed` state.
|
|
412
|
-
onEnter:
|
|
413
|
-
# 0587 R3: probe-then-full recheck. A red gateProbeCmd records FAIL and skips the full
|
|
414
|
-
# gate (the measured waste is re-running a 110–140s gate to learn the tree is still red);
|
|
415
|
-
# a green probe (or empty gateProbeCmd) falls through to the full-gate loop unchanged.
|
|
416
|
-
# Only the full gate writes PASS, so the `test-recheck → review` guard (reads PASS)
|
|
417
|
-
# still means a full green qualityGateCmd ran — invariant preserved by construction.
|
|
418
|
-
- kind: shell
|
|
419
|
-
options:
|
|
420
|
-
command: >-
|
|
421
|
-
mkdir -p .spur/run &&
|
|
422
|
-
STATUS_FILE=".spur/run/$wbs-test-gate.status" &&
|
|
423
|
-
LOG_FILE=".spur/run/$wbs-test-gate.log" &&
|
|
424
|
-
FINDINGS_FILE=".spur/run/$wbs-test-gate.findings" &&
|
|
425
|
-
: > "$LOG_FILE" &&
|
|
426
|
-
probe_rc=0;
|
|
427
|
-
if [ -n "$gateProbeCmd" ]; then
|
|
428
|
-
sh -c "$gateProbeCmd" > "$LOG_FILE.probe" 2>&1; probe_rc=$?;
|
|
429
|
-
cat "$LOG_FILE.probe" >> "$LOG_FILE";
|
|
430
|
-
rm -f "$LOG_FILE.probe";
|
|
431
|
-
fi;
|
|
432
|
-
if [ "$probe_rc" -ne 0 ]; then
|
|
433
|
-
gate_rc=$probe_rc;
|
|
434
|
-
else
|
|
435
|
-
gate_attempt=1;
|
|
436
|
-
while [ "$gate_attempt" -le 5 ]; do
|
|
437
|
-
ATTEMPT_LOG="$LOG_FILE.attempt-$gate_attempt";
|
|
438
|
-
sh -c "$qualityGateCmd" > "$ATTEMPT_LOG" 2>&1; gate_rc=$?;
|
|
439
|
-
gate_locked=0;
|
|
440
|
-
grep -q 'SQLiteError: database is locked' "$ATTEMPT_LOG" && gate_locked=1;
|
|
441
|
-
cat "$ATTEMPT_LOG" >> "$LOG_FILE";
|
|
442
|
-
rm -f "$ATTEMPT_LOG";
|
|
443
|
-
if [ "$gate_rc" -eq 0 ] || [ "$gate_locked" -ne 1 ] || [ "$gate_attempt" -ge 5 ]; then break; fi;
|
|
444
|
-
printf 'quality gate: database is locked; retrying (%s/5) in 10s\n' "$gate_attempt" | tee -a "$LOG_FILE";
|
|
445
|
-
sleep 10;
|
|
446
|
-
gate_attempt=$((gate_attempt + 1));
|
|
447
|
-
done;
|
|
448
|
-
fi &&
|
|
449
|
-
cat "$LOG_FILE" &&
|
|
450
|
-
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 &&
|
|
451
|
-
if [ "$gate_rc" -eq 0 ]; then
|
|
452
|
-
printf 'PASS\n' > "$STATUS_FILE";
|
|
453
|
-
else
|
|
454
|
-
printf 'FAIL\n' > "$STATUS_FILE";
|
|
455
|
-
fi &&
|
|
456
|
-
exit 0
|
|
457
|
-
|
|
458
|
-
- id: review
|
|
459
|
-
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`.
|
|
460
|
-
onEnter:
|
|
461
|
-
- kind: agent.run
|
|
462
|
-
options:
|
|
463
|
-
agent: ${vars.agent}
|
|
464
|
-
input: /sp:dev-review ${vars.wbs} --auto
|
|
465
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
466
|
-
role: reviewer
|
|
467
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
468
|
-
|
|
469
|
-
- id: approve
|
|
470
|
-
description: >
|
|
471
|
-
Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
|
|
472
|
-
(review routes around it straight to verify). In interactive mode this state pauses
|
|
473
|
-
the run for `spur workflow continue` (E3), making approval an explicit operator action.
|
|
474
|
-
The operator's answer is routed (R1, bug-750): yes -> verify; no -> failed (rejection
|
|
475
|
-
recorded, not silently approved); cancel -> cancelled (a distinct terminal state).
|
|
476
|
-
pause: true
|
|
477
|
-
onEnter:
|
|
478
|
-
- kind: hitl.confirm
|
|
479
|
-
options:
|
|
480
|
-
prompt: "Approve task ${vars.wbs} to proceed to verification?"
|
|
481
|
-
|
|
482
|
-
- id: verify
|
|
483
|
-
description: >
|
|
484
|
-
Functional verification (BDD + traceability) via /sp:dev-verify. The agent's
|
|
485
|
-
captured answer is written to `.spur/run/<wbs>-verify-answer.txt` and MUST follow
|
|
486
|
-
the answer-file schema contract (explicit `Verdict: PASS|PARTIAL|FAIL` line plus
|
|
487
|
-
`| Req | Status | Evidence |` and `| AC | Status | Evidence Type | Evidence |` tables);
|
|
488
|
-
a deterministic shell step then derives the verdict and writes the gate artifact
|
|
489
|
-
`.spur/run/<wbs>-verdict.json` (so the completion gate reads a real verdict, not
|
|
490
|
-
agent discretion — R9). The verdict is PASS only if the agent both reported PASS
|
|
491
|
-
AND `spur task check` passes; otherwise FAIL.
|
|
492
|
-
onEnter:
|
|
493
|
-
- kind: agent.run
|
|
494
|
-
options:
|
|
495
|
-
agent: ${vars.agent}
|
|
496
|
-
input: /sp:dev-verify ${vars.wbs} --auto --fix all --focus all
|
|
497
|
-
# Declared Layer-1 role (0538 R2): routing reason beside the agent: pin.
|
|
498
|
-
role: reviewer
|
|
499
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
500
|
-
answerFile: .spur/run/${vars.wbs}-verify-answer.txt
|
|
501
|
-
- kind: shell
|
|
502
|
-
options:
|
|
503
|
-
command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
|
|
504
|
-
|
|
505
|
-
- id: residual-sweep
|
|
506
|
-
description: >
|
|
507
|
-
Residual sweep (R4) — the operator's manual fallback, wired into the FSM as
|
|
508
|
-
its own stage (NOT a step inside verify). Decision: the PASS verdict is a
|
|
509
|
-
TRANSITION guard (`verify -> record`), evaluated only after verify's onEnter
|
|
510
|
-
finishes — a step inside verify would run before that guard and could not be
|
|
511
|
-
conditioned on the verdict. A separate stage reached only via the PASS guard
|
|
512
|
-
is the only way to hold the fixed position (after PASS, before record/commit).
|
|
513
|
-
It also makes the per-task sweep free in the `--feature` batch: the batch runs
|
|
514
|
-
this pipeline once per task, so each task sweeps itself (Q&A: per task, never
|
|
515
|
-
once per feature). The agent.run prompt is the operator's verbatim fallback,
|
|
516
|
-
templated over `residualSweepTarget` (single-task vs feature wording).
|
|
517
|
-
onEnter:
|
|
518
|
-
- kind: agent.run
|
|
519
|
-
options:
|
|
520
|
-
agent: ${vars.agent}
|
|
521
|
-
# Post-verification completeness judgment (catch what the per-requirement
|
|
522
|
-
# verdict structurally misses) — folds the `verify` stage, so it takes the
|
|
523
|
-
# reviewer role (capable-1), matching the operator's strong-model usage.
|
|
524
|
-
role: reviewer
|
|
525
|
-
input: "Anything remained in ${vars.residualSweepTarget}? if any, you should get all of them done before we commit all the changes in next step"
|
|
526
|
-
timeoutMs: ${vars.stepTimeoutMs}
|
|
527
|
-
|
|
528
|
-
- id: record
|
|
529
|
-
description: >
|
|
530
|
-
Record pipeline results into the task file via `spur task record` —
|
|
531
|
-
Testing/Review from the verdict, Solution backfilled from git diff as a
|
|
532
|
-
safety net, optional transition to testing. Post-record step conditionally syncs
|
|
533
|
-
feature status via the bounded `feature-sync-bounded` wrapper (task 0411
|
|
534
|
-
retry-suppression) if `feature_id` is present, or appends an orphan link proposal
|
|
535
|
-
to the run report if absent (task 0328 / ADR-0322).
|
|
536
|
-
onEnter:
|
|
537
|
-
- kind: shell
|
|
538
|
-
options:
|
|
539
|
-
command: >-
|
|
540
|
-
retry_transient() {
|
|
541
|
-
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
542
|
-
printf '%s\n' "$RETRY_OUTPUT";
|
|
543
|
-
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
544
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
545
|
-
sleep 2;
|
|
546
|
-
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
547
|
-
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
548
|
-
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
549
|
-
echo "node_modules link/dependency broken — run bun install and retry";
|
|
550
|
-
fi;
|
|
551
|
-
return "$RETRY_RC";
|
|
552
|
-
};
|
|
553
|
-
retry_transient $spurBin task record "$wbs" --solution-from-diff --transition testing
|
|
554
|
-
# Best-effort, and portable across layouts. `feature-sync-bounded.ts` is a
|
|
555
|
-
# Spur-monorepo path — `spur init` never scaffolds `plugins/sp/`, so a seeded
|
|
556
|
-
# project must fall back to the plain `spur feature sync` verb rather than
|
|
557
|
-
# abort a run that already produced a PASS verdict. Trailing `exit 0` keeps the
|
|
558
|
-
# whole step advisory for the same reason (feature status sync is a follow-up,
|
|
559
|
-
# not a completion gate — `record → done` runs `spur task check`).
|
|
560
|
-
- kind: shell
|
|
561
|
-
options:
|
|
562
|
-
command: >-
|
|
563
|
-
FID=$($spurBin task show $wbs --json 2>/dev/null |
|
|
564
|
-
jq -r ".feature_id // .frontmatter.feature_id // empty" 2>/dev/null);
|
|
565
|
-
if [ -n "$FID" ]; then
|
|
566
|
-
if [ -f plugins/sp/scripts/feature-sync-bounded.ts ]; then
|
|
567
|
-
bun plugins/sp/scripts/feature-sync-bounded.ts "$FID" --spur-bin "$spurBin" --json;
|
|
568
|
-
else
|
|
569
|
-
$spurBin feature sync "$FID" --json;
|
|
570
|
-
fi;
|
|
571
|
-
else
|
|
572
|
-
echo "Orphan task $wbs — no feature_id linked; proposal: consider linking to a parent feature." >> ".spur/run/$wbs-report.txt";
|
|
573
|
-
fi;
|
|
574
|
-
exit 0
|
|
575
|
-
|
|
576
|
-
- id: done
|
|
577
|
-
description: >
|
|
578
|
-
Terminal — task executed, verified, and recorded. The `record → done`
|
|
579
|
-
guard runs `spur task check` before certifying; a genuinely non-compliant
|
|
580
|
-
task routes to `failed` instead of a silent bad `done`.
|
|
581
|
-
onEnter:
|
|
582
|
-
- kind: shell
|
|
583
|
-
options:
|
|
584
|
-
command: >-
|
|
585
|
-
retry_transient() {
|
|
586
|
-
RETRY_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
587
|
-
printf '%s\n' "$RETRY_OUTPUT";
|
|
588
|
-
if [ "$RETRY_RC" -eq 0 ]; then return 0; fi;
|
|
589
|
-
if ! printf '%s' "$RETRY_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY|database is locked'; then return "$RETRY_RC"; fi;
|
|
590
|
-
sleep 2;
|
|
591
|
-
RETRY_SECOND_OUTPUT=$("$@" 2>&1); RETRY_RC=$?;
|
|
592
|
-
printf '%s\n' "$RETRY_SECOND_OUTPUT";
|
|
593
|
-
if [ "$RETRY_RC" -ne 0 ] && printf '%s\n%s' "$RETRY_OUTPUT" "$RETRY_SECOND_OUTPUT" | grep -Eq 'ENOENT|EBUSY|ENOTEMPTY'; then
|
|
594
|
-
echo "node_modules link/dependency broken — run bun install and retry";
|
|
595
|
-
fi;
|
|
596
|
-
return "$RETRY_RC";
|
|
597
|
-
};
|
|
598
|
-
retry_transient $spurBin task update "$wbs" done --no-lifecycle
|
|
599
|
-
- kind: note
|
|
600
|
-
options:
|
|
601
|
-
message: "Pipeline complete for task ${vars.wbs} (done gate cleared)."
|
|
602
|
-
# Checkpoint write: record session state for resume (Phase 4, task 0171 R3)
|
|
603
|
-
- kind: shell
|
|
604
|
-
options:
|
|
605
|
-
command: 'mkdir -p .spur/memory/sessions && echo "checkpoint: task-pipeline done wbs=$wbs ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)" > .spur/memory/sessions/$wbs-checkpoint.md'
|
|
606
|
-
|
|
607
|
-
- id: failed
|
|
608
|
-
description: >
|
|
609
|
-
Terminal — precheck, quality-gate exhaustion, verify non-PASS, record check
|
|
610
|
-
failure, or operator rejection; reported, not advanced.
|
|
611
|
-
|
|
612
|
-
- id: cancelled
|
|
613
|
-
description: Terminal — pipeline cancelled by operator at the approval gate (R1).
|
|
614
|
-
|
|
615
|
-
transitions:
|
|
616
|
-
# ── precheck: doctor PASS + task check → implement; else → failed ──
|
|
617
|
-
- from: precheck
|
|
618
|
-
to: implement
|
|
619
|
-
description: Agent doctor PASS and task check green — begin implementation.
|
|
620
|
-
guard:
|
|
621
|
-
kind: shell
|
|
622
|
-
options:
|
|
623
|
-
command: 'test "$(cat .spur/run/$wbs-precheck-doctor.status 2>/dev/null)" = PASS && test "$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)" = PASS && $spurBin task check $wbs'
|
|
624
|
-
- from: precheck
|
|
625
|
-
to: failed
|
|
626
|
-
description: Doctor FAIL and/or task check failed — stop before implement.
|
|
627
|
-
guard:
|
|
628
|
-
kind: always
|
|
629
|
-
|
|
630
|
-
# ── linear body ──
|
|
631
|
-
- from: implement
|
|
632
|
-
to: test
|
|
633
|
-
description: Implementation done — quality-gate probe.
|
|
634
|
-
guard:
|
|
635
|
-
kind: always
|
|
636
|
-
# Soft probe branching (declaration order: PASS first, then FAIL, then defense).
|
|
637
|
-
- from: test
|
|
638
|
-
to: review
|
|
639
|
-
description: Quality gate already green — one gate run only; skip fixall/recheck.
|
|
640
|
-
guard:
|
|
641
|
-
kind: shell
|
|
642
|
-
options:
|
|
643
|
-
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
|
|
644
|
-
- from: test
|
|
645
|
-
to: test-fix
|
|
646
|
-
description: Quality gate red — start bounded fixall loop.
|
|
647
|
-
guard:
|
|
648
|
-
kind: shell
|
|
649
|
-
options:
|
|
650
|
-
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = FAIL'
|
|
651
|
-
# Defense: missing/corrupt status — treat as FAIL path, not silent PASS.
|
|
652
|
-
- from: test
|
|
653
|
-
to: test-fix
|
|
654
|
-
description: Probe status missing/corrupt — attempt fixall then recheck.
|
|
655
|
-
guard:
|
|
656
|
-
kind: always
|
|
657
|
-
- from: test-fix
|
|
658
|
-
to: test-recheck
|
|
659
|
-
description: Fixall finished — soft recheck the same quality gate.
|
|
660
|
-
guard:
|
|
661
|
-
kind: always
|
|
662
|
-
# Recheck branching (PASS first; under-max FAIL → fixall again; exhausted → failed).
|
|
663
|
-
- from: test-recheck
|
|
664
|
-
to: review
|
|
665
|
-
description: Quality gate green after fixall — proceed to review.
|
|
666
|
-
guard:
|
|
667
|
-
kind: shell
|
|
668
|
-
options:
|
|
669
|
-
command: 'test "$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)" = PASS'
|
|
670
|
-
- from: test-recheck
|
|
671
|
-
to: test-fix
|
|
672
|
-
description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
|
|
673
|
-
guard:
|
|
674
|
-
kind: shell
|
|
675
|
-
options:
|
|
676
|
-
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"'
|
|
677
|
-
- from: test-recheck
|
|
678
|
-
to: failed
|
|
679
|
-
description: Still red after max fixall attempts — stop at failed (not silent abort).
|
|
680
|
-
guard:
|
|
681
|
-
kind: shell
|
|
682
|
-
options:
|
|
683
|
-
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"'
|
|
684
|
-
# Defense: corrupt recheck status — failed, not review.
|
|
685
|
-
- from: test-recheck
|
|
686
|
-
to: failed
|
|
687
|
-
description: Recheck status missing/corrupt — stop at failed.
|
|
688
|
-
guard:
|
|
689
|
-
kind: always
|
|
690
|
-
# ── review → approve, OR skip the HITL gate entirely when profile=auto (R4) ──
|
|
691
|
-
# Declaration order matters: the auto-skip guard is tried FIRST, so under
|
|
692
|
-
# profile=auto the run routes straight to verify and never ENTERS approve
|
|
693
|
-
# (whose onEnter hitl.confirm would otherwise block). Otherwise fall through
|
|
694
|
-
# to approve for the human gate.
|
|
695
|
-
- from: review
|
|
696
|
-
to: verify
|
|
697
|
-
description: profile=auto — skip the human approval gate, go straight to verify.
|
|
698
|
-
guard:
|
|
699
|
-
kind: shell
|
|
700
|
-
options:
|
|
701
|
-
command: 'test "$profile" = auto'
|
|
702
|
-
- from: review
|
|
703
|
-
to: approve
|
|
704
|
-
description: Review done — gate on human approval.
|
|
705
|
-
guard:
|
|
706
|
-
kind: always
|
|
707
|
-
|
|
708
|
-
# ── approve: HITL gate (reached only when profile != auto). Routed by the
|
|
709
|
-
# operator's captured answer (R1, bug-750) — three ordered guards, declaration
|
|
710
|
-
# order matters (yes first, then no, then cancel), mirroring idea-pipeline's
|
|
711
|
-
# design-approval gate (.spur/workflows/idea-pipeline.yaml:352-366). No
|
|
712
|
-
# `always` edge remains out of `approve`. ──
|
|
713
|
-
- from: approve
|
|
714
|
-
to: verify
|
|
715
|
-
description: Approved — proceed to verification.
|
|
716
|
-
guard:
|
|
717
|
-
kind: shell
|
|
718
|
-
options:
|
|
719
|
-
command: 'test "$__hitlAnswer" = yes'
|
|
720
|
-
- from: approve
|
|
721
|
-
to: failed
|
|
722
|
-
description: Operator rejected at the approval gate — report and stop.
|
|
723
|
-
guard:
|
|
724
|
-
kind: shell
|
|
725
|
-
options:
|
|
726
|
-
command: 'test "$__hitlAnswer" = no'
|
|
727
|
-
- from: approve
|
|
728
|
-
to: cancelled
|
|
729
|
-
description: Operator cancelled at the approval gate.
|
|
730
|
-
guard:
|
|
731
|
-
kind: shell
|
|
732
|
-
options:
|
|
733
|
-
command: 'test "$__hitlAnswer" = cancel'
|
|
734
|
-
|
|
735
|
-
# ── completion gate (the YAML-native replacement for rd3's default-on --postflight-verify) ──
|
|
736
|
-
# The verify step emits .spur/run/<wbs>-verdict.json. Only `verdict: PASS` clears
|
|
737
|
-
# the gate to `residual-sweep` (thence `record`); any non-PASS (PARTIAL/FAIL), a
|
|
738
|
-
# missing file, or malformed JSON routes to `failed`. Declaration order: PASS guard
|
|
739
|
-
# tried FIRST. The residual sweep therefore runs ONLY on a PASS verdict, before
|
|
740
|
-
# record/commit — its fixed position is enforced by the FSM, not operator memory.
|
|
741
|
-
- from: verify
|
|
742
|
-
to: residual-sweep
|
|
743
|
-
description: Verification verdict is PASS — sweep residual work before record/commit.
|
|
744
|
-
guard:
|
|
745
|
-
kind: shell
|
|
746
|
-
options:
|
|
747
|
-
command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" = PASS'
|
|
748
|
-
- from: verify
|
|
749
|
-
to: failed
|
|
750
|
-
description: Verification verdict is not PASS (PARTIAL/FAIL/missing) — block before done.
|
|
751
|
-
guard:
|
|
752
|
-
kind: shell
|
|
753
|
-
options:
|
|
754
|
-
command: 'test "$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" != PASS'
|
|
755
|
-
- from: residual-sweep
|
|
756
|
-
to: record
|
|
757
|
-
description: Residual sweep done — record results and proceed to done.
|
|
758
|
-
guard:
|
|
759
|
-
kind: always
|
|
760
|
-
# ── done gate: record → done/failed gated on `spur task check` (ADR-026 amendment 2026-06-23) ──
|
|
761
|
-
# The record step guarantees every done-required section ([Solution, Testing, Review])
|
|
762
|
-
# has real content (each owned by its pipeline step). This guard is defense-in-depth —
|
|
763
|
-
# it certifies the matrix before done; a genuinely non-compliant task routes to failed.
|
|
764
|
-
# Declaration order: pass guard first.
|
|
765
|
-
- from: record
|
|
766
|
-
to: done
|
|
767
|
-
description: Task check passed — certify done.
|
|
768
|
-
guard:
|
|
769
|
-
kind: shell
|
|
770
|
-
options:
|
|
771
|
-
command: "$spurBin task check $wbs"
|
|
772
|
-
- from: record
|
|
773
|
-
to: failed
|
|
774
|
-
description: Task check failed — block before done; investigate missing sections.
|
|
775
|
-
guard:
|
|
776
|
-
kind: shell
|
|
777
|
-
options:
|
|
778
|
-
command: "! $spurBin task check $wbs"
|