@gobing-ai/spur 0.3.84 → 0.3.86
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.global.yaml +11 -6
- package/config/pipeline-budgets.json +0 -25
- package/config/plugin-scripts.json +0 -5
- package/config/rules/boundary/env-var-hygiene.yaml +41 -0
- package/config/transition-shims.json +1 -1
- package/config/workflow-candidates.json +41 -0
- package/config/workflows/feature-lifecycle.yaml +4 -2
- package/config/workflows/feature-verification.yaml +70 -0
- package/config/workflows/idea-pipeline.yaml +25 -13
- package/config/workflows/task-pipeline.yaml +43 -12
- package/config/workflows/wrapup-pipeline.yaml +71 -8
- package/package.json +9 -9
- package/plugins/sp/README.md +0 -3
- package/plugins/sp/agents/super-planner.md +2 -1
- package/plugins/sp/hooks/agent-hint.ts +5 -4
- package/plugins/sp/hooks/careful-guard.ts +3 -1
- package/plugins/sp/hooks/context-post-tool.ts +2 -1
- package/plugins/sp/hooks/context-session-start.ts +4 -3
- package/plugins/sp/hooks/context-session-stop.ts +2 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
- package/plugins/sp/hooks/task-write-guard.ts +4 -3
- package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
- package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
- package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
- package/plugins/sp/scripts/idea-handoff.mjs +6 -1
- package/plugins/sp/scripts/idea-handoff.ts +4 -1
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.ts +185 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
- package/plugins/sp/scripts/pr-reviewing.ts +2 -1
- package/plugins/sp/scripts/quality-gate.mjs +8 -1
- package/plugins/sp/scripts/quality-gate.ts +2 -1
- package/plugins/sp/scripts/surface-drift-inventory.ts +2 -5
- package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
- package/plugins/sp/scripts/task-size-precheck.ts +6 -4
- package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
- package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
- package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
- package/plugins/sp/scripts/wrapup-steps.ts +2 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
- package/schemas/state-machine-workflow.schema.json +5 -0
- package/schemas/transition-flow-workflow.schema.json +5 -0
- package/spur.js +21393 -20831
- package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
- package/web/_astro/{BoardApp.D4mBUGLT.js → BoardApp.yBBcFXWP.js} +60 -60
- package/web/_astro/{TaskDetail.DhCWaOPR.js → TaskDetail.DqFJbRFc.js} +1 -1
- package/web/_astro/{arc.D3X0nfq-.js → arc.DL-BpHoi.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.BQDoiGme.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BjHekBbM.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BvHt171l.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
- package/web/_astro/channel.CX5453qQ.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CwxGXlDR.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Dia1YtwU.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.Oz9epCXa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DKFYJjYJ.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.AD7w5oVz.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D0OqTj52.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CU5ppR7j.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Bu1rFztw.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.C0Pgm5yE.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.C0ecLb1e.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.CpStVmhO.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BPWN3W9f.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DnXbEot4.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BYJsgUiI.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CU4Bqprx.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BQLOANIu.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.Cn1UVhqj.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.C8FdZyvA.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.QSZCR8Uo.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.O1Py31MY.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DoeJRBfY.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
- package/web/_astro/{index.TPF1FerP.css → index.CcU5weKX.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.9npLJecW.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.DXanvOr3.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DxbVjxAv.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BDk6_iq0.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
- package/web/_astro/{linear.C9cCDjT3.js → linear.Nv_xOUjP.js} +1 -1
- package/web/_astro/{mermaid.core.CpHRe5nH.js → mermaid.core.Bc4LqQgX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D-6C85bn.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.B3Oxodty.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.QZ_9Hshu.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BimvuK53.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.D5Kkpx2C.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B4YR6fAE.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bt2wkZok.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DmiTBL8w.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.KNJV40mf.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BVAaKgWH.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.D5Jx7zB2.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/basic.yaml +0 -146
- package/config/workflows/docs-pipeline.yaml +0 -350
- package/config/workflows/feature-dev.yaml +0 -288
- package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
- package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
- package/web/_astro/BoardApp.DFPYpI5O.js +0 -1
- package/web/_astro/channel.BlQwxyoI.js +0 -1
|
@@ -95,7 +95,7 @@ agent:
|
|
|
95
95
|
# claude-*-5 via claude, grok-4.6 via grok, gemini-3.x via agy) —
|
|
96
96
|
# consistently outperforms the same model routed through a third-party CLI,
|
|
97
97
|
# and belongs at the capable rungs. `portable` models (glm-5.x,
|
|
98
|
-
# deepseek-v4-*, …) are provider-agnostic and best carried by
|
|
98
|
+
# deepseek-v4-*, …) are provider-agnostic and best carried by pi at
|
|
99
99
|
# the cheap/standard rungs. The ladder below mixes both classes on purpose;
|
|
100
100
|
# measure pairings with the history plane before promoting a portable model
|
|
101
101
|
# into a capable rung. Live tiers: cheap | standard | capable-1 | capable-2
|
|
@@ -106,19 +106,24 @@ agent:
|
|
|
106
106
|
# capable-1.
|
|
107
107
|
#
|
|
108
108
|
# Because this is the global layer, a project that wants one different model
|
|
109
|
-
# writes only `- name:
|
|
110
|
-
# here.
|
|
109
|
+
# writes only `- name: pi-dsv4-flash` + `model: …` — the agent and tier come
|
|
110
|
+
# from here.
|
|
111
111
|
executors:
|
|
112
112
|
# cheap rung is optional: with none declared, scribe-role work starts
|
|
113
113
|
# one rung up at the cheapest standard executor. Add one when
|
|
114
114
|
# transcript-heavy mechanical commands (changelog/gitmsg/handover) get
|
|
115
115
|
# noisy on cost.
|
|
116
116
|
# - name: minimax
|
|
117
|
-
# agent:
|
|
117
|
+
# agent: pi
|
|
118
118
|
# model: minimax/MiniMax-M3
|
|
119
119
|
# tier: cheap
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
# Standard rung. An `agent:` here is a live DISPATCH target, not a label:
|
|
121
|
+
# role routing (`agent.default: coder`, `--agent auto`, every workflow step
|
|
122
|
+
# declaring `agent: auto`) lands on the cheapest eligible standard executor,
|
|
123
|
+
# so listing an agent makes Spur delegate work to it. Keep this rung on an
|
|
124
|
+
# agent the supported install targets actually carry.
|
|
125
|
+
- name: pi-dsv4-flash
|
|
126
|
+
agent: pi
|
|
122
127
|
model: opencode/deepseek-v4-flash
|
|
123
128
|
tier: standard
|
|
124
129
|
- name: pi
|
|
@@ -28,17 +28,6 @@
|
|
|
28
28
|
"source": "Post-merge count (0607 R2: doc-sync + learning-capture consolidated, 2 -> 1). Real-run reading (R5): median 253s, max-sane 2319s (n=44). Budget = 60 min ceiling.",
|
|
29
29
|
"decision": null
|
|
30
30
|
},
|
|
31
|
-
"docs-pipeline": {
|
|
32
|
-
"modelQueries": 2,
|
|
33
|
-
"wallClockMs": null,
|
|
34
|
-
"tokenCostUsd": null,
|
|
35
|
-
"source": "No fixture (0607 option a) and only dry/short runs recorded in history (max 1s, n=4) — wall-clock budget unenforced until a real docs-pipeline run exists. Query budget 2 matches the live workflow definition's query list ['draft','verify'] (added by task 0704; 0607 R3 recorded 1, which is stale against the definition). 0754 R6: FIX, not raise — the value was wrong against the definition, the workflow's declared query count did not change.",
|
|
36
|
-
"decision": {
|
|
37
|
-
"date": "2026-09-03",
|
|
38
|
-
"wbs": "0754",
|
|
39
|
-
"note": "align docs-pipeline.modelQueries to the live workflow definition's query list ['draft','verify']; not a measured-cost raise — the workflow's declared query count was already 2 (added by 0704); the budget was the stale side, not the workflow. Per D8 Decision 11."
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
31
|
"pr-review": {
|
|
43
32
|
"modelQueries": 0,
|
|
44
33
|
"wallClockMs": null,
|
|
@@ -53,26 +42,12 @@
|
|
|
53
42
|
"source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
|
|
54
43
|
"decision": null
|
|
55
44
|
},
|
|
56
|
-
"feature-dev": {
|
|
57
|
-
"modelQueries": 2,
|
|
58
|
-
"wallClockMs": null,
|
|
59
|
-
"tokenCostUsd": null,
|
|
60
|
-
"source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
|
|
61
|
-
"decision": null
|
|
62
|
-
},
|
|
63
45
|
"wayfinder-resolution": {
|
|
64
46
|
"modelQueries": 2,
|
|
65
47
|
"wallClockMs": null,
|
|
66
48
|
"tokenCostUsd": null,
|
|
67
49
|
"source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
|
|
68
50
|
"decision": null
|
|
69
|
-
},
|
|
70
|
-
"basic": {
|
|
71
|
-
"modelQueries": 1,
|
|
72
|
-
"wallClockMs": null,
|
|
73
|
-
"tokenCostUsd": null,
|
|
74
|
-
"source": "Coverage entry (0826 R2): the live definition's model-query count (extractResolvedWorkflowFacts, 2026-09-11). No real-run measurement yet: wall-clock and cost are unenforced until measured, never 0.",
|
|
75
|
-
"decision": null
|
|
76
51
|
}
|
|
77
52
|
}
|
|
78
53
|
}
|
|
@@ -26,11 +26,6 @@
|
|
|
26
26
|
"contract": "standard",
|
|
27
27
|
"twin": "dogfood-testing/validate-report.mjs"
|
|
28
28
|
},
|
|
29
|
-
{
|
|
30
|
-
"rel": "feature-dev-precheck.ts",
|
|
31
|
-
"contract": "standard",
|
|
32
|
-
"twin": "feature-dev-precheck.mjs"
|
|
33
|
-
},
|
|
34
29
|
{
|
|
35
30
|
"rel": "feature-sync-bounded.ts",
|
|
36
31
|
"contract": "standard",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
$schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
|
|
2
|
+
# Environment-variable hygiene — env access funnels through the config gateway (task 0902).
|
|
3
|
+
#
|
|
4
|
+
# Contract:
|
|
5
|
+
# - `@gobing-ai/ts-utils` (`packages/utils/src/env.ts` in ts-libs) is the single
|
|
6
|
+
# implementation source; `packages/config/src/index.ts` re-exports `getEnvVar` /
|
|
7
|
+
# `getEnvVars` / `setEnvVar` / `removeEnvVar` / `getAppOptions` as the app-side funnel,
|
|
8
|
+
# and NO file in this repo touches `process.env` directly — packages/config included.
|
|
9
|
+
# Env flows into the app as typed config or injected `env` records (ADR-027). Runtime
|
|
10
|
+
# options belong in `.spur/config.yaml` `bootstrap.options` via `getAppOptions`;
|
|
11
|
+
# dev-only knobs belong in consts.
|
|
12
|
+
# - Plugin scripts/hooks import the same functions from `@gobing-ai/ts-utils`; the
|
|
13
|
+
# `superskill script convert` `.mjs` twins bundle them in (self-contained on any
|
|
14
|
+
# install target).
|
|
15
|
+
# - `apps/web` uses `import.meta.env` (Vite browser plane) — not process env, never matches.
|
|
16
|
+
# - Which variables exist and who consumes them is documented in `.env.example`.
|
|
17
|
+
# (The former per-var lease table moved there; the rule now enforces the funnel,
|
|
18
|
+
# not a file-by-var inventory.)
|
|
19
|
+
#
|
|
20
|
+
# Scope: apps/**, packages/**, scripts/**, plugins/sp/** — src AND tests. No exclusions:
|
|
21
|
+
# even the gateway package itself is free of direct access.
|
|
22
|
+
include:
|
|
23
|
+
- "apps/**/src/**/*.{ts,tsx}"
|
|
24
|
+
- "apps/**/tests/**/*.{ts,tsx}"
|
|
25
|
+
- "packages/**/src/**/*.{ts,tsx}"
|
|
26
|
+
- "packages/**/tests/**/*.{ts,tsx}"
|
|
27
|
+
- "scripts/**/*.ts"
|
|
28
|
+
- "plugins/sp/**/*.ts"
|
|
29
|
+
rules:
|
|
30
|
+
- id: env-var-hygiene
|
|
31
|
+
description: >
|
|
32
|
+
No direct `process.env` or `Bun.env` access outside the packages/config gateway.
|
|
33
|
+
Read via `getEnvVar` (single var, undefined-only fallback), seed/compose via
|
|
34
|
+
`getEnvVars()` (live record — also the sanctioned form for setting/deleting
|
|
35
|
+
parent→child contract markers), or take an injected `env` record parameter.
|
|
36
|
+
Aliasing (`const env = process.env`) is caught too — it must touch `process.env`.
|
|
37
|
+
severity: error
|
|
38
|
+
evaluator:
|
|
39
|
+
type: rg
|
|
40
|
+
config:
|
|
41
|
+
pattern: "process\\.env|\\bBun\\.env\\b"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"note": "Transition-shim manifest (task 0541, feature B2). Each entry records one compatibility path marked @transition-shim(<id>) in source. Every field is required. The gate (bun run transition-shim-check,
|
|
2
|
+
"note": "Transition-shim manifest (task 0541, feature B2). Each entry records one compatibility path marked @transition-shim(<id>) in source. Every field is required. The gate (bun run transition-shim-check, in the feature-scoped `spur-check-feature` pass since task 0872) is two-sided: an unregistered marker fails, and a listed entry whose marker is gone fails. Emptying this file is the definition of the transition being complete (docs/04_DESIGN.md §2.5). Removal conditions must be objectively checkable against the repository, not 'when convenient'. Shims are registered by the tasks that create them (0536/0537/0538/0542).",
|
|
3
3
|
"entries": [
|
|
4
4
|
{
|
|
5
5
|
"id": "agent-bare-binary-name",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"note": "Workflow graph-change promotion candidates (task 0873, feature D62; ADR-076 amendment). A candidate is shadow-run against recorded real-run inputs and is promoted into the canonical definition or deleted by its named deadline — never left as a standing parallel <name>2.yaml. `verdict` is null while pending and is filled by `bun scripts/spur-dev.ts promotion evaluate <id>`; `promotion check` (spur-check-feature) fails any candidate still present past its deadline and any unreferenced parallel definition in config/workflows/.",
|
|
4
|
+
"candidates": [
|
|
5
|
+
{
|
|
6
|
+
"id": "wrapup-contract-violation-pilot-routing",
|
|
7
|
+
"canonical": "wrapup-pipeline",
|
|
8
|
+
"deadline": "2026-10-17",
|
|
9
|
+
"createdAt": "2026-09-17",
|
|
10
|
+
"rationale": "ADR-118 pilot graph change shipped by task 0871 (doc-sync contract-violation routing to the shell-only repair state). Registered by task 0876 to take the promotion decision on its first real-run measurement (R4/R5): wrapup-pipeline run fadca099-25a7-4884-a4ae-923cd0239775 (done, state-machine, definition sha256:cad7ad8b..., v4, created 2026-09-17T15:14:46Z, 14h28m after 0871 landed at 00:46:46Z - the only post-landing terminal non-dry wrapup run) routed doc-sync->learnings-append on the default edge: the doc-sync agent.run exited clean (exitCode 0) and produced its declared expectFile capture (.spur/run/fadca099-...-wrapup-learnings.md), so data.outcome was success, ContractViolationGuardRunner passed:false (packages/app/src/workflow/guards/contract-violation.ts:26), zero workflow.agent.contract-violation system_events, zero failed actions (executor-failure path not taken), no repair status file. Measured absence on real traffic - never fabricated. The change projects delta.agentRunCount=1 (unchanged): the repair edge adds no model hop (repair is a shell that records the miss; ADR-118 forbids re-dispatch), so the ADR-076 cost bar cannot reward it; its justification is ADR-118 routing correctness, evidenced here by the measured absence on the first real run. Disposition (resolve delete, or hold as the pattern-spread precedent for 0873's gate) belongs to the D62 owner before the deadline.",
|
|
11
|
+
"measurement": {
|
|
12
|
+
"workflow": "wrapup-pipeline",
|
|
13
|
+
"runIds": ["fadca099-25a7-4884-a4ae-923cd0239775"]
|
|
14
|
+
},
|
|
15
|
+
"delta": {
|
|
16
|
+
"agentRunCount": 1
|
|
17
|
+
},
|
|
18
|
+
"verdict": {
|
|
19
|
+
"decision": "delete",
|
|
20
|
+
"evaluatedAt": "2026-09-17T15:33:27.274Z",
|
|
21
|
+
"agentRunCount": {
|
|
22
|
+
"runs": 1,
|
|
23
|
+
"mean": 1,
|
|
24
|
+
"median": 1,
|
|
25
|
+
"min": 1,
|
|
26
|
+
"max": 1
|
|
27
|
+
},
|
|
28
|
+
"agentRunDurationMs": {
|
|
29
|
+
"runs": 1,
|
|
30
|
+
"mean": 505937,
|
|
31
|
+
"median": 505937,
|
|
32
|
+
"min": 505937,
|
|
33
|
+
"max": 505937
|
|
34
|
+
},
|
|
35
|
+
"candidateAgentRunCount": 1,
|
|
36
|
+
"canonicalAgentRunCount": 1,
|
|
37
|
+
"reason": "candidate projects 1 agent.run action(s), not fewer than the canonical wrapup-pipeline count of 1 (1 real run(s), median 1 agent.run action(s)/run, median 505937 ms/run) — ADR-076 rejected a graph adding a model hop"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -62,11 +62,13 @@ transitions:
|
|
|
62
62
|
- from: verifying
|
|
63
63
|
to: done
|
|
64
64
|
description: >
|
|
65
|
-
Feature verified — complete.
|
|
65
|
+
Feature verified — complete. Requires the feature-scoped verification pass
|
|
66
|
+
(ADR-119) to have recorded PASS, then strict check (AC validated,
|
|
67
|
+
traceability clean).
|
|
66
68
|
guard:
|
|
67
69
|
kind: shell
|
|
68
70
|
options:
|
|
69
|
-
command: '$spurBin feature check $featureId --strict --as done'
|
|
71
|
+
command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS && $spurBin feature check $featureId --strict --as done'
|
|
70
72
|
|
|
71
73
|
# Rework: verifying → active (mandatory History entry)
|
|
72
74
|
- from: verifying
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Feature-scoped verification pass (ADR-119, feature D62, task 0872).
|
|
2
|
+
#
|
|
3
|
+
# Owns a lifecycle boundary no per-task graph owns: the repo-wide checks whose
|
|
4
|
+
# invariant spans the repository rather than a single task's diff — corpus
|
|
5
|
+
# consistency, contract baselines, dependency/schema drift, the frozen History
|
|
6
|
+
# surface, and the repo-wide test set. They run once per feature against a
|
|
7
|
+
# settled tree, deliberately the slow pass, and `feature-lifecycle`'s
|
|
8
|
+
# verifying→done guard refuses to complete the feature until this pass records
|
|
9
|
+
# PASS (ADR-119 consequence: a feature is not done until it passes).
|
|
10
|
+
#
|
|
11
|
+
# Run: spur workflow run feature-verification.yaml --vars '{"featureId":"D62"}'
|
|
12
|
+
#
|
|
13
|
+
# Shape: verify -> done | failed. The shell action writes the verdict to
|
|
14
|
+
# `.spur/run/<featureId>-feature-verification.status` and always exits 0; the
|
|
15
|
+
# transition guard reads the status so a missing, corrupt or FAIL verdict routes
|
|
16
|
+
# to `failed` (fail-closed), never a silent PASS.
|
|
17
|
+
#
|
|
18
|
+
# Reliability (aligned with task-pipeline / ADR-043): no agent.run nodes; the
|
|
19
|
+
# verification command is a trusted config var executed via `sh -c` (same
|
|
20
|
+
# surface as task-pipeline's qualityGateCmd — never interpolate untrusted input).
|
|
21
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
22
|
+
kind: state-machine
|
|
23
|
+
name: feature-verification
|
|
24
|
+
version: "1"
|
|
25
|
+
description: "Feature-scoped verification pass (ADR-119): runs the repo-wide check set once per feature against a settled tree and records PASS/FAIL."
|
|
26
|
+
iterationBound: 2
|
|
27
|
+
initialState: verify
|
|
28
|
+
terminalStates:
|
|
29
|
+
- done
|
|
30
|
+
- failed
|
|
31
|
+
failureStates:
|
|
32
|
+
- failed
|
|
33
|
+
vars:
|
|
34
|
+
featureId: "X"
|
|
35
|
+
spurBin: "spur"
|
|
36
|
+
verificationCmd: "bun run spur-check-feature"
|
|
37
|
+
|
|
38
|
+
states:
|
|
39
|
+
- id: verify
|
|
40
|
+
description: >
|
|
41
|
+
Run the repo-wide check set, record the verdict, always exit 0.
|
|
42
|
+
onEnter:
|
|
43
|
+
- kind: shell
|
|
44
|
+
options:
|
|
45
|
+
command: >-
|
|
46
|
+
mkdir -p .spur/run;
|
|
47
|
+
sh -c "$verificationCmd" > ".spur/run/$featureId-feature-verification.log" 2>&1
|
|
48
|
+
&& printf 'PASS\n' > ".spur/run/$featureId-feature-verification.status"
|
|
49
|
+
|| printf 'FAIL\n' > ".spur/run/$featureId-feature-verification.status";
|
|
50
|
+
exit 0
|
|
51
|
+
|
|
52
|
+
- id: done
|
|
53
|
+
description: Terminal — repo-wide checks passed.
|
|
54
|
+
|
|
55
|
+
- id: failed
|
|
56
|
+
description: Terminal — repo-wide checks failed or the verdict is missing/corrupt.
|
|
57
|
+
|
|
58
|
+
transitions:
|
|
59
|
+
- from: verify
|
|
60
|
+
to: done
|
|
61
|
+
description: Repo-wide checks passed — the feature-scoped verification pass records PASS.
|
|
62
|
+
guard:
|
|
63
|
+
kind: shell
|
|
64
|
+
options:
|
|
65
|
+
command: 'test "$(cat .spur/run/$featureId-feature-verification.status 2>/dev/null)" = PASS'
|
|
66
|
+
- from: verify
|
|
67
|
+
to: failed
|
|
68
|
+
description: Repo-wide checks failed (or the verdict is missing/corrupt) — stop at failed.
|
|
69
|
+
guard:
|
|
70
|
+
kind: always
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
# lets profile=auto route around the idea-eval taste gate (default "false")
|
|
34
34
|
# CLI --approve-taste sets both design_approved and idea_approved to true.
|
|
35
35
|
# spurBin — PATH-independent spur invocation (overridden by CLI at run start)
|
|
36
|
-
# agent — agent for agent.run steps (default:
|
|
36
|
+
# agent — agent for agent.run steps (default: auto → `agent.default` in config)
|
|
37
37
|
#
|
|
38
38
|
# Reliability (aligned with task-pipeline / ADR-043):
|
|
39
39
|
# - Soft agent doctor at start → failed via transitions (not raw lifecycle abort)
|
|
@@ -568,12 +568,14 @@ transitions:
|
|
|
568
568
|
- from: ac-generate
|
|
569
569
|
to: system-design
|
|
570
570
|
description: "profile=auto, check passed, design route — run system design."
|
|
571
|
-
# (warn) 4
|
|
571
|
+
# (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + route signal jointly own the
|
|
572
572
|
# route; guards never re-run the CLI (0769), so there is nothing smaller to extract.
|
|
573
573
|
guard:
|
|
574
574
|
kind: shell
|
|
575
575
|
options:
|
|
576
|
-
command:
|
|
576
|
+
command: >-
|
|
577
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
578
|
+
test "$profile" = auto && test "$ac_status" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
|
|
577
579
|
# Auto-skip 2: pass + skip-design route → decompose
|
|
578
580
|
- from: ac-generate
|
|
579
581
|
to: decompose
|
|
@@ -588,22 +590,26 @@ transitions:
|
|
|
588
590
|
- from: ac-generate
|
|
589
591
|
to: ac-generate
|
|
590
592
|
description: "profile=auto, check failed, retry cap not reached — re-run ac-generate."
|
|
591
|
-
# (warn) 4
|
|
593
|
+
# (warn) 5 commands (named status capture + 4 conditions): profile gate + captured status + captured retry count; the
|
|
592
594
|
# retry loop is the smallest honest formulation of cap<3 routing (0769).
|
|
593
595
|
guard:
|
|
594
596
|
kind: shell
|
|
595
597
|
options:
|
|
596
|
-
command:
|
|
598
|
+
command: >-
|
|
599
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
600
|
+
test "$profile" = auto && test "$ac_status" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
|
|
597
601
|
# Auto-skip 4: check failed, retry cap reached → escalate to failed
|
|
598
602
|
- from: ac-generate
|
|
599
603
|
to: failed
|
|
600
604
|
description: "profile=auto, check failed after 3 retries — escalate to failed."
|
|
601
|
-
# (warn) 4
|
|
605
|
+
# (warn) 5 commands (named status capture + 4 conditions): mirror of the retry guard with cap>=3; keeping escalation and
|
|
602
606
|
# retry as one test-chain pair makes the cap boundary auditable in the diff (0769).
|
|
603
607
|
guard:
|
|
604
608
|
kind: shell
|
|
605
609
|
options:
|
|
606
|
-
command:
|
|
610
|
+
command: >-
|
|
611
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
612
|
+
test "$profile" = auto && test "$ac_status" != PASS && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
|
|
607
613
|
# Interactive fallback: enter feature-check HITL gate
|
|
608
614
|
- from: ac-generate
|
|
609
615
|
to: feature-check
|
|
@@ -616,12 +622,14 @@ transitions:
|
|
|
616
622
|
- from: feature-check
|
|
617
623
|
to: system-design
|
|
618
624
|
description: Feature check passed, design route — run system design.
|
|
619
|
-
# (warn) 4
|
|
625
|
+
# (warn) 5 commands (named status capture + 4 conditions): HITL answer + captured status + design route; same shape as the
|
|
620
626
|
# ac-generate auto-skips, reading the same captured signal files (0769).
|
|
621
627
|
guard:
|
|
622
628
|
kind: shell
|
|
623
629
|
options:
|
|
624
|
-
command:
|
|
630
|
+
command: >-
|
|
631
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
632
|
+
test "$__hitlAnswer" = yes && test "$ac_status" = PASS && test "$design" = auto && test "$(jq -r .needs_design .spur/run/$__runId-idea-needs-design.json 2>/dev/null)" != false
|
|
625
633
|
- from: feature-check
|
|
626
634
|
to: decompose
|
|
627
635
|
description: Feature check passed, skip-design route — go directly to decompose.
|
|
@@ -634,20 +642,24 @@ transitions:
|
|
|
634
642
|
- from: feature-check
|
|
635
643
|
to: ac-generate
|
|
636
644
|
description: "Feature check failed — revise AC (retry cap: 3)."
|
|
637
|
-
# (warn) 4
|
|
645
|
+
# (warn) 5 commands (named status capture + 4 conditions): HITL reject/failed-status OR-pair + captured retry count; the
|
|
638
646
|
# interactive retry mirror of the ac-generate cap<3 guard (0769).
|
|
639
647
|
guard:
|
|
640
648
|
kind: shell
|
|
641
649
|
options:
|
|
642
|
-
command:
|
|
650
|
+
command: >-
|
|
651
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
652
|
+
(test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -lt 3
|
|
643
653
|
- from: feature-check
|
|
644
654
|
to: failed
|
|
645
655
|
description: Feature check failed after 3 retries — escalate to failed.
|
|
646
|
-
# (warn) 4
|
|
656
|
+
# (warn) 5 commands (named status capture + 4 conditions): mirror of the revise guard with cap>=3 (0769).
|
|
647
657
|
guard:
|
|
648
658
|
kind: shell
|
|
649
659
|
options:
|
|
650
|
-
command:
|
|
660
|
+
command: >-
|
|
661
|
+
ac_status="$(cat .spur/run/$__runId-idea-ac-check.status 2>/dev/null)";
|
|
662
|
+
(test "$__hitlAnswer" = no || test "$ac_status" != PASS) && test "$(cat .spur/run/$__runId-idea-ac-retry-count 2>/dev/null || echo 0)" -ge 3
|
|
651
663
|
- from: feature-check
|
|
652
664
|
to: cancelled
|
|
653
665
|
description: Operator cancelled the feature-check gate.
|
|
@@ -603,10 +603,14 @@ transitions:
|
|
|
603
603
|
- from: precheck
|
|
604
604
|
to: implement
|
|
605
605
|
description: Deterministic size, evidence, and task checks are green — begin implementation.
|
|
606
|
+
# (warn) 5 commands: named size/evidence status reads + task check (legibility, 0874).
|
|
606
607
|
guard:
|
|
607
608
|
kind: shell
|
|
608
609
|
options:
|
|
609
|
-
command:
|
|
610
|
+
command: >-
|
|
611
|
+
size_status="$(cat .spur/run/$wbs-precheck-size.status 2>/dev/null)";
|
|
612
|
+
evidence_status="$(cat .spur/run/$wbs-precheck-evidence.status 2>/dev/null)";
|
|
613
|
+
test "$size_status" = PASS && test "$evidence_status" = PASS && $spurBin task check $wbs
|
|
610
614
|
- from: precheck
|
|
611
615
|
to: failed
|
|
612
616
|
description: Size and/or task check failed — stop before implement.
|
|
@@ -626,14 +630,18 @@ transitions:
|
|
|
626
630
|
guard:
|
|
627
631
|
kind: shell
|
|
628
632
|
options:
|
|
629
|
-
command:
|
|
633
|
+
command: >-
|
|
634
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
635
|
+
test "$gate_status" = PASS && test "$mode" = fast
|
|
630
636
|
- from: test
|
|
631
637
|
to: review
|
|
632
638
|
description: Quality gate already green and safety mode — proceed to review.
|
|
633
639
|
guard:
|
|
634
640
|
kind: shell
|
|
635
641
|
options:
|
|
636
|
-
command:
|
|
642
|
+
command: >-
|
|
643
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
644
|
+
test "$gate_status" = PASS && test "$mode" != fast
|
|
637
645
|
- from: test
|
|
638
646
|
to: test-fix
|
|
639
647
|
description: Quality gate red — start bounded fixall loop.
|
|
@@ -659,28 +667,40 @@ transitions:
|
|
|
659
667
|
guard:
|
|
660
668
|
kind: shell
|
|
661
669
|
options:
|
|
662
|
-
command:
|
|
670
|
+
command: >-
|
|
671
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
672
|
+
test "$gate_status" = PASS && test "$mode" = fast
|
|
663
673
|
- from: test-recheck
|
|
664
674
|
to: review
|
|
665
675
|
description: Quality gate green after fixall and safety mode — proceed to review.
|
|
666
676
|
guard:
|
|
667
677
|
kind: shell
|
|
668
678
|
options:
|
|
669
|
-
command:
|
|
679
|
+
command: >-
|
|
680
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
681
|
+
test "$gate_status" = PASS && test "$mode" != fast
|
|
670
682
|
- from: test-recheck
|
|
671
683
|
to: test-fix
|
|
672
684
|
description: Still red and under qualityGateMaxFixAttempts — another fixall hop.
|
|
685
|
+
# (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
|
|
673
686
|
guard:
|
|
674
687
|
kind: shell
|
|
675
688
|
options:
|
|
676
|
-
command:
|
|
689
|
+
command: >-
|
|
690
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
691
|
+
fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
|
|
692
|
+
test "$gate_status" = FAIL && test "$fix_attempts" -lt "$qualityGateMaxFixAttempts"
|
|
677
693
|
- from: test-recheck
|
|
678
694
|
to: failed
|
|
679
695
|
description: Still red after max fixall attempts — stop at failed (not silent abort).
|
|
696
|
+
# (warn) 5 commands: named gate status + fix attempts (legibility, 0874).
|
|
680
697
|
guard:
|
|
681
698
|
kind: shell
|
|
682
699
|
options:
|
|
683
|
-
command:
|
|
700
|
+
command: >-
|
|
701
|
+
gate_status="$(cat .spur/run/$wbs-test-gate.status 2>/dev/null)";
|
|
702
|
+
fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
|
|
703
|
+
test "$gate_status" = FAIL && test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
|
|
684
704
|
# Defense: corrupt recheck status — failed, not review.
|
|
685
705
|
- from: test-recheck
|
|
686
706
|
to: failed
|
|
@@ -748,7 +768,16 @@ transitions:
|
|
|
748
768
|
kind: shell
|
|
749
769
|
options:
|
|
750
770
|
command: >-
|
|
751
|
-
jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '
|
|
771
|
+
jq -e --arg d "$proofDigest" --arg r "$__runId" --arg dd "$__definitionDigest" '
|
|
772
|
+
.verdict == "PASS"
|
|
773
|
+
and (.proof.digest // "") == $d
|
|
774
|
+
and (.proof.stages.qualityGate.digest // "") == $d
|
|
775
|
+
and (.proof.stages.review.digest // "") == $d
|
|
776
|
+
and (.proof.stages.review.status // "") == "completed"
|
|
777
|
+
and (.proof.stages.verification.digest // "") == $d
|
|
778
|
+
and (.proof.runId // "") == $r
|
|
779
|
+
and (.proof.definitionDigest // "") == $dd
|
|
780
|
+
' ".spur/run/$wbs-verdict.json" >/dev/null 2>&1
|
|
752
781
|
- from: verify
|
|
753
782
|
to: test-fix
|
|
754
783
|
description: >-
|
|
@@ -760,8 +789,8 @@ transitions:
|
|
|
760
789
|
kind: shell
|
|
761
790
|
options:
|
|
762
791
|
command: >-
|
|
763
|
-
|
|
764
|
-
test -n "$
|
|
792
|
+
verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)";
|
|
793
|
+
test -n "$verdict" && test "$verdict" != PASS &&
|
|
765
794
|
test "$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)" -lt "$qualityGateMaxFixAttempts"
|
|
766
795
|
- from: verify
|
|
767
796
|
to: failed
|
|
@@ -778,6 +807,7 @@ transitions:
|
|
|
778
807
|
- from: record
|
|
779
808
|
to: done
|
|
780
809
|
description: Task check passed and the verdict proof block still names the captured digest — certify done.
|
|
810
|
+
# (warn) 5 commands: named verdict + proof digest reads after the task check (legibility, 0874).
|
|
781
811
|
guard:
|
|
782
812
|
kind: shell
|
|
783
813
|
options:
|
|
@@ -788,8 +818,9 @@ transitions:
|
|
|
788
818
|
# one shell line.
|
|
789
819
|
command: >-
|
|
790
820
|
$spurBin task check $wbs --as done &&
|
|
791
|
-
|
|
792
|
-
|
|
821
|
+
verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)" &&
|
|
822
|
+
proof_digest="$(jq -r '.proof.digest // ""' .spur/run/$wbs-verdict.json 2>/dev/null)" &&
|
|
823
|
+
test "$verdict" = PASS && test "$proof_digest" = "$proofDigest"
|
|
793
824
|
- from: record
|
|
794
825
|
to: failed
|
|
795
826
|
description: Task check failed or proof evidence missing/malformed/mismatched — block before done.
|