@gobing-ai/spur 0.4.0 → 0.4.2
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 +4 -2
- package/config/decisions/gates.yaml +36 -0
- package/config/decisions/history-anatomy.yaml +35 -0
- package/config/decisions/idea-pipeline.yaml +38 -0
- package/config/plugin-scripts.json +15 -0
- package/config/rules/docs/sourced-api-claims.yaml +60 -0
- package/config/rules/recommended-pre-check.yaml +8 -3
- package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
- package/config/script-placement-baseline.json +2 -2
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/feature-lifecycle.yaml +9 -2
- package/config/workflows/history-anatomy.yaml +22 -0
- package/config/workflows/idea-pipeline.yaml +75 -0
- package/config/workflows/task-pipeline.yaml +216 -57
- package/config/workflows/wrapup-pipeline.yaml +107 -14
- package/package.json +10 -10
- package/plugins/sp/README.md +12 -3
- package/plugins/sp/agents/super-planner.md +16 -6
- package/plugins/sp/agents/super-reviewer.md +14 -0
- package/plugins/sp/bunfig.toml +8 -0
- package/plugins/sp/commands/dev-brainstorm.md +29 -16
- package/plugins/sp/commands/dev-find-way.md +80 -0
- package/plugins/sp/commands/dev-idea.md +6 -0
- package/plugins/sp/commands/dev-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +4 -1
- package/plugins/sp/commands/dev-run.md +12 -2
- package/plugins/sp/commands/dev-runall.md +32 -15
- package/plugins/sp/hooks/context-owner.ts +56 -0
- package/plugins/sp/hooks/context-post-tool.ts +23 -0
- package/plugins/sp/hooks/context-session-start.ts +49 -27
- package/plugins/sp/hooks/context-session-stop.ts +131 -30
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
- package/plugins/sp/lib/idea-handoff.generated.mjs +43 -5
- package/plugins/sp/lib/inline-run-app.ts +28 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
- package/plugins/sp/lib/inline-run.generated.mjs +351 -46
- package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
- package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
- package/plugins/sp/lib/run-summary-core.ts +561 -0
- package/plugins/sp/lib/transcript.ts +150 -7
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +488 -0
- package/plugins/sp/scripts/batch-plan.ts +145 -0
- package/plugins/sp/scripts/batch-preflight.mjs +51 -3
- package/plugins/sp/scripts/batch-preflight.ts +72 -1
- package/plugins/sp/scripts/commit-guard.mjs +216 -0
- package/plugins/sp/scripts/commit-guard.ts +249 -0
- package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
- package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
- package/plugins/sp/scripts/inline-run-setup.mjs +562 -80
- package/plugins/sp/scripts/inline-run-setup.ts +52 -78
- package/plugins/sp/scripts/persist-out-check.mjs +90 -41
- package/plugins/sp/scripts/persist-out-check.ts +112 -57
- package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +30213 -61
- package/plugins/sp/scripts/quality-gate.ts +21 -5
- package/plugins/sp/scripts/residual-scan.mjs +86 -31
- package/plugins/sp/scripts/residual-scan.ts +28 -30
- package/plugins/sp/scripts/run-summary.mjs +432 -14
- package/plugins/sp/scripts/run-summary.ts +41 -177
- package/plugins/sp/scripts/session-timeline.mjs +109 -11
- package/plugins/sp/scripts/session-timeline.ts +57 -19
- package/plugins/sp/scripts/task-diffstat.mjs +32 -29
- package/plugins/sp/scripts/task-diffstat.ts +34 -34
- package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
- package/plugins/sp/scripts/wrapup-steps.ts +203 -1
- package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
- package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
- package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
- package/plugins/sp/skills/code-verification/SKILL.md +3 -4
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
- package/plugins/sp/skills/fleet-join/SKILL.md +1 -1
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +40 -7
- package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
- package/plugins/sp/skills/spur-cli/SKILL.md +5 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
- package/plugins/sp/skills/spur-cli/references/decision.md +16 -8
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
- package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
- package/plugins/sp/skills/spur-composer/SKILL.md +1 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +20 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +44 -12
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
- package/schemas/state-machine-workflow.schema.json +23 -0
- package/spur.js +42402 -26889
- package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
- package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
- package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
- package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
- package/web/_astro/channel.D4nG9YLf.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
- package/web/_astro/index.BhSwN8yY.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
- package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
- package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/commands/dev-parallel.md +0 -31
- package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
- package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
- package/web/_astro/channel.CUFdtWhm.js +0 -1
- package/web/_astro/index.Dg_3bOaJ.css +0 -1
|
@@ -19,6 +19,17 @@ the interpreter and trace instructions must match. Never hand-edit the installed
|
|
|
19
19
|
|
|
20
20
|
**Retirement criterion (0755 R5, D8 decision D7):** the per-task interpreter retires once the engine covers per-task execution for `/sp:dev-runall` with real terminal runs **and** the parity check (this doc's documented action/guard set ≡ the resolved action/guard set of every `.spur/workflows/*.yaml`) is green. Recording the criterion is part of this task; acting on it is not — that is a separate A3-gate decision.
|
|
21
21
|
|
|
22
|
+
## Driver state checklist (re-read this after a compaction)
|
|
23
|
+
|
|
24
|
+
- **Run id** — the run row `inline-run-setup` persisted (`.spur/run/<run-id>-inline-setup.json`
|
|
25
|
+
sidecar, `.spur/memory/runs/<run-id>.md` + `.state.json`); never re-derive it from the transcript.
|
|
26
|
+
- **Markers under `.spur/run/`** — `<run-id>-inline-setup.json` (frozen invocation identity),
|
|
27
|
+
`<run-id>-tree-start.json` (commit-guard fingerprint), `<wbs>-verdict.json` (per-stage verdict),
|
|
28
|
+
`<wbs>-question.md` / `<wbs>-escalation.md` (operator pause), `<run-id>-event-trace.md`.
|
|
29
|
+
- **Re-read per state** — this file (Run setup → YAML interpreter → Record & done),
|
|
30
|
+
[structured-trace-emission.md](structured-trace-emission.md) at the first trace event.
|
|
31
|
+
- **Authoritative state** — the run record and verdict artifacts, never the host todo list.
|
|
32
|
+
|
|
22
33
|
## Supported action and guard set (0755 R2 parity contract)
|
|
23
34
|
|
|
24
35
|
The action and guard kinds this driver implements. The parity check
|
|
@@ -51,7 +62,7 @@ command, skill, script, or second workflow.
|
|
|
51
62
|
|
|
52
63
|
**Inline HITL defer semantics (0911):** bundled pipeline human gates ship `decision: {mode:
|
|
53
64
|
never}`, so the inline driver always prompts the operator for those gates regardless of
|
|
54
|
-
`
|
|
65
|
+
`workflows.hitlDecisionMaker` — the decision policy never answers inline pipeline gates. A local
|
|
55
66
|
workflow override (`.spur/workflows/*.yaml`) may declare `mode: evidence`, but the driver does
|
|
56
67
|
not claim full inline parity for the DecisionMaker path: evidence-mode auto-answering inside the
|
|
57
68
|
inline driver is an explicit non-goal (subprocess runs own that behavior). No hot reload: config
|
|
@@ -59,21 +70,25 @@ and YAML changes require restarting the invoking process.
|
|
|
59
70
|
|
|
60
71
|
## Run setup
|
|
61
72
|
|
|
62
|
-
**Shared startup contract (task 0814 R1/R3/R4/R6/R7).** The order is
|
|
63
|
-
**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
**Shared startup contract (task 0814 R1/R3/R4/R6/R7; publish-first 1105 R1).** The order is
|
|
74
|
+
load-bearing: publish the **generated plan** first — resolve the selected workflow through the same
|
|
75
|
+
project/bundled resolver as execution, run `spur workflow show <resolved-file> --no-logo --format
|
|
76
|
+
todo --json`, and publish the `.plan` rows verbatim (the projection reads the definition and executes
|
|
77
|
+
nothing, so it is safe before readiness and isolation); run quick deterministic readiness (admission,
|
|
78
|
+
not an implementation certificate) next; when `--worktree` is valid, create/adopt and switch to the
|
|
79
|
+
execution tree; then bind the published plan to the run's persisted `__definitionDigest` — and only
|
|
80
|
+
then read the full YAML for comprehensive/model work. Comprehensive checks stay at their owning
|
|
81
|
+
boundaries and run after the plan is visible and after isolation when requested (R7); quick readiness
|
|
82
|
+
and plan projection dispatch zero models and execute zero workflow actions (R8). Labels come from the
|
|
83
|
+
projection (or `columnLabel` in `packages/app/src/workflow/step-reporter.ts` for the `steps[]`
|
|
84
|
+
fallback) — labels are display addresses only, never an execution key.
|
|
72
85
|
|
|
73
86
|
1. Resolve the command inputs, `--auto`, and any explicit `--vars` — **without reading the selected
|
|
74
87
|
YAML yet**. An explicit non-inline executor selection chooses the subprocess workflow path.
|
|
75
88
|
2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
|
|
76
|
-
`.spur/run/`
|
|
89
|
+
`.spur/run/` and the run's scratch subdirectory `.spur/run/<run-id>/` for attempt staging (action
|
|
90
|
+
batches, captured stage output, temporary status/answer files) plus `.spur/memory/runs/` for
|
|
91
|
+
retained records, and use the two-file run record (task 0927) — append lines to
|
|
77
92
|
`.spur/memory/runs/<run-id>.md` and read machine state from `.spur/memory/runs/<run-id>.state.json`.
|
|
78
93
|
The run id lives in those two files and the persisted run row — never stage it in an ad-hoc
|
|
79
94
|
`*.env` file: the `no-env-files` gate rule flags any `.env` name, so run-scoped staging files
|
|
@@ -93,6 +108,20 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
93
108
|
|| { echo "inline run setup failed closed — checker not found; run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 1; }
|
|
94
109
|
```
|
|
95
110
|
|
|
111
|
+
**Commit fingerprint (1129 R1).** Fingerprint the tree the task commit will land in — before the
|
|
112
|
+
first task write — because Record & done stages through this snapshot, so a concurrent writer's
|
|
113
|
+
file cannot ride into the task commit. The artifact is cwd-relative under `.spur/run/` (gitignored
|
|
114
|
+
per tree), so the snapshot and the commit it guards must name the SAME tree: without `--worktree`
|
|
115
|
+
take it here in the setup window; with `--worktree` take it immediately **after step 7's
|
|
116
|
+
isolation, from the worktree root** (still before the first task write) — a snapshot taken here
|
|
117
|
+
would land in the invoking tree, and the worktree's commit would find none (1129 review N1).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
RUN_ID="<marker-id>" # the WT-3 marker's id (execution-batch.md), or the run id allocated at setup
|
|
121
|
+
GUARD=plugins/sp/scripts/commit-guard.ts; [ -f config/plugin-scripts.json -a -f "$GUARD" ] || GUARD="$(superskill script path sp commit-guard.mjs 2>/dev/null)";
|
|
122
|
+
bun "$GUARD" start --run "$RUN_ID" # <this tree>/.spur/run/$RUN_ID-tree-start.json = { head, dirty[] }
|
|
123
|
+
```
|
|
124
|
+
|
|
96
125
|
The delegate uses the source app service when the SPUR_BIN chain identifies a checkout;
|
|
97
126
|
otherwise it loads the generated application bundle shipped with the plugin. Both setup paths
|
|
98
127
|
obtains the selected definition from the existing CLI `workflow show --format todo --json`
|
|
@@ -128,10 +157,21 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
128
157
|
`session` and the Codex key `session_id` (in that order). If neither is available, allocate
|
|
129
158
|
`host-session-<run-id>` and record that fallback in the log; provenance must never be blank or
|
|
130
159
|
guessed from an executor subprocess.
|
|
131
|
-
5. **Publish the
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
160
|
+
5. **Publish the generated plan (R1, 1105) — the run's first action.** Run `spur workflow show
|
|
161
|
+
<resolved-file> --no-logo --format todo --json` and publish the projection into the host todo list
|
|
162
|
+
before any expensive check:
|
|
163
|
+
- **Annotated workflow (the normal case):** publish every `.plan` row's `text` **verbatim** —
|
|
164
|
+
`A Prepare`, `A1 Quick readiness`, `A2 Prepare Git`, `A3 Publish plan`, `B Implement`, … — in
|
|
165
|
+
plan order. Never hand-write rows, never copy the YAML state table into prose, and never
|
|
166
|
+
renumber: the labels carry identity.
|
|
167
|
+
- **Unannotated workflow (`.plan` is `null`):** publish the flat `steps[]` inventory in
|
|
168
|
+
declaration order with `columnLabel` labels and their `initial` / `terminal` / `failure` /
|
|
169
|
+
`pause` / `loopBack` / `conditional` markers. Never re-derive this list from the YAML.
|
|
170
|
+
- **No workflow plan at all** (skill-only refine/verify batches): keep the existing host
|
|
171
|
+
preparation rows; do not fabricate a workflow YAML.
|
|
172
|
+
|
|
173
|
+
Publishing executes nothing, so it safely precedes readiness and isolation, which keep their
|
|
174
|
+
existing order below.
|
|
135
175
|
6. **Quick deterministic readiness (R2), before isolation.** Evaluate `quickReadiness` from
|
|
136
176
|
`plugins/sp/scripts/batch-preflight.ts` with the operation, status, filtered-set size, the
|
|
137
177
|
matrix-selected required/present sections, and content-policy findings. Record the outcome
|
|
@@ -146,15 +186,12 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
146
186
|
inherited**: a host shell call starts in an arbitrary directory and carries no cwd from the
|
|
147
187
|
previous call, so every later call runs inside the **Per-call execution-tree pin** protocol
|
|
148
188
|
below (task 1058), which consumes the identity confirmed here.
|
|
149
|
-
8. **
|
|
150
|
-
|
|
151
|
-
`spur workflow show <resolved-file> --no-logo --format todo --json`. Validate the projection with
|
|
152
|
-
`parseWorkflowInventory` and bind it to the run's persisted `__definitionDigest` with
|
|
189
|
+
8. **Bind the published plan to the run digest (R4) — plan row A3.** Validate the step-5 projection
|
|
190
|
+
with `parseWorkflowInventory` and bind it to the run's persisted `__definitionDigest` with
|
|
153
191
|
`assertInventoryIdentity` — a drift or projection failure stops the run before any
|
|
154
|
-
comprehensive/model work, never executing with a misleading plan.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
the active state. Never re-derive this list from the YAML.
|
|
192
|
+
comprehensive/model work, never executing with a misleading plan. The A rows published at step 5
|
|
193
|
+
track this setup order: A1 = quick readiness (step 6), A2 = Git/isolation (step 7), A3 = this
|
|
194
|
+
binding; mark each row completed as its step settles.
|
|
158
195
|
9. **Read the selected YAML and overlay its `vars` defaults** with the invocation values. Compare the
|
|
159
196
|
resolved definition identity against the bound `__definitionDigest`; a mismatch is identity drift
|
|
160
197
|
and fails closed (step 8 already caught projection-side drift; this re-checks the same definition
|
|
@@ -176,15 +213,25 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
176
213
|
ended 0/11 with precheck and implement still open).
|
|
177
214
|
- **Source of truth** = the CLI projection for layer 1; the YAML read here for layer 2.
|
|
178
215
|
Never hand-copy or hand-derive the state list into the driver, a command, a skill, or a script.
|
|
179
|
-
- **Stable labels (task 0814 R5
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
- **Stable labels (task 0814 R5; A–Z / 1–9 rule, 1105 R1).** Labels come from the published
|
|
217
|
+
`.plan`: single letters A–Z for top-level rows, digits 1–9 for children under one letter. The
|
|
218
|
+
projection enforces that cap instead of rolling to two-letter labels (`planLetter` throws past
|
|
219
|
+
Z — split into waves or stop; never render `AA`-style addresses). An `on-entry` state is
|
|
220
|
+
inserted as the next digit the moment the host enters it (`insertOnEntry` in
|
|
221
|
+
`packages/app/src/workflow/plan-projection.ts`), appended after its letter's existing children;
|
|
222
|
+
re-entering an already-published loop state keeps its label and appends the re-entry note
|
|
223
|
+
` — attempt N`. Labels never replace the canonical step id, and are re-derived identically on
|
|
224
|
+
retry/resume against the same definition.
|
|
225
|
+
- **Truthful progress (task 0814 R6; frozen status mapping, 1105 R1).** Publish pending/active
|
|
226
|
+
state before the visible item starts, then update it immediately after the observed item
|
|
227
|
+
completes and before the next item starts. Frozen status mapping: `pending → pending`,
|
|
228
|
+
`active → in_progress`, `completed → completed`,
|
|
229
|
+
`skipped|failed|unattempted|blocked → pending` + ` [<outcome>]` — the four engine-decided
|
|
230
|
+
outcomes stay visible on a pending row, never cleared. Keep completed, skipped, failed, blocked,
|
|
231
|
+
paused, and unattempted outcomes distinct; never mark skipped/conditional work completed merely
|
|
232
|
+
to clear the UI. Re-entry appends ` — attempt N`. If the host has no suitable native todo tool,
|
|
233
|
+
or it fails, use the `renderProgressMarkdown` fallback with the same labels and truth — never a
|
|
234
|
+
fabricated successful tool invocation.
|
|
188
235
|
10. For task execution only, record lifecycle provenance before entering the FSM:
|
|
189
236
|
|
|
190
237
|
```bash
|
|
@@ -200,6 +247,25 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
200
247
|
worktree first and run this command (and every lifecycle-touching command: `task record`,
|
|
201
248
|
`task update <wbs> testing|done`) from the worktree root.
|
|
202
249
|
|
|
250
|
+
### Host todo update styles (1105 R2)
|
|
251
|
+
|
|
252
|
+
The same published plan is updated differently per host family. Both styles keep plan order and are
|
|
253
|
+
bookkeeping for the one projection — never a second source of truth:
|
|
254
|
+
|
|
255
|
+
| Host | Tool(s) | Update style |
|
|
256
|
+
| --- | --- | --- |
|
|
257
|
+
| Claude Code | `TaskCreate` / `TaskUpdate` | per-item — create each item once, then update by id; append inserted (`on-entry`) steps at the end (the label carries identity, so list position is display only) |
|
|
258
|
+
| pi | `todo` | per-item — same create-once/update-by-id discipline |
|
|
259
|
+
| Codex | `update_plan` | full-list — rewrite the whole list in plan order on every update |
|
|
260
|
+
| Gemini | `write_todos` | full-list — rewrite the whole list in plan order |
|
|
261
|
+
| OpenCode | `todowrite` | full-list — rewrite the whole list in plan order |
|
|
262
|
+
| Grok | `todo_write` | full-list — rewrite the whole list in plan order |
|
|
263
|
+
| omp | host todo tool | maps plan letters to `phase` |
|
|
264
|
+
| any host without a native tool | `renderProgressMarkdown` | Markdown fallback with the same labels and truth |
|
|
265
|
+
|
|
266
|
+
Per-item hosts update the one row whose outcome changed; full-list hosts re-render every row from the
|
|
267
|
+
current plan state. Both render the frozen status mapping above identically.
|
|
268
|
+
|
|
203
269
|
### Idea-pipeline quick start (0887 R1/R2)
|
|
204
270
|
|
|
205
271
|
Minimum files to read for `/sp:dev-idea` inline runs — then drive `idea-pipeline.yaml`:
|
|
@@ -276,6 +342,10 @@ Rules (R1):
|
|
|
276
342
|
first, then verify the physical path and repository identity.
|
|
277
343
|
- **Native tool cwd options are additive.** A tool-level cwd/`cd` pin may additionally set the
|
|
278
344
|
call's directory, but never replaces the identity checks above.
|
|
345
|
+
- **Dispatch cwd is mandatory when the tool accepts one.** A subagent dispatch whose tool exposes a
|
|
346
|
+
`cwd` (pi `subagent`) passes the execution-tree cwd. Omitted, the child starts in the host tree:
|
|
347
|
+
it can edit main, and the timeout watchdog diffs the wrong tree (H15 2026-10-07: "changed
|
|
348
|
+
tracked files: none" on a worker that changed 5 worktree files).
|
|
279
349
|
- **Host-only scope.** Engine `shell` actions already bind `context.workdir`
|
|
280
350
|
(`packages/app/src/workflow/actions/shell.ts:98`) — leave them alone. No service-wide
|
|
281
351
|
`process.chdir`, no new public flag, no helper framework.
|
|
@@ -314,8 +384,6 @@ entry and `.spur/memory/runs/runall-D63-2ebbd97c.md`.
|
|
|
314
384
|
|
|
315
385
|
## Comprehensive-check retention and evidence (R7/R8)
|
|
316
386
|
|
|
317
|
-
## Comprehensive-check retention and evidence (R7/R8)
|
|
318
|
-
|
|
319
387
|
**R7 — comprehensive checks stay at their owning boundaries.** Quick readiness and plan projection are
|
|
320
388
|
admission and visibility, not a substitute for the owning gates. After the plan is visible and after
|
|
321
389
|
isolation when requested, retain the full task/feature integrity, size, evidence-channel,
|
|
@@ -363,6 +431,19 @@ Action semantics come from the YAML and the workflow action contract:
|
|
|
363
431
|
|
|
364
432
|
- `shell` — run the expanded command in the project working tree with resolved vars exported as
|
|
365
433
|
environment variables. A non-zero result follows the action's existing failure policy.
|
|
434
|
+
**Long commands must not hold the host call (session finding 2026-10-09).** A `shell` action whose
|
|
435
|
+
command can outlive the host's per-call limit — the full quality gate, a repo-wide suite, any
|
|
436
|
+
`bun run spur-check`-class gate — is started **detached and polled**, never held in one foreground
|
|
437
|
+
call: redirect to a run-scoped file and disown it
|
|
438
|
+
(`nohup <cmd> > .spur/run/<wbs>-gate.out 2>&1 < /dev/null & disown`), then observe it in bounded
|
|
439
|
+
calls (`pgrep`, the declared `.status` file, the log tail). A call killed at the cap leaves the
|
|
440
|
+
substrate's own FAIL receipt — for the quality gate, `.spur/run/<wbs>-check-receipt.json` — and the
|
|
441
|
+
recheck's `no-progress` guard honours it, so a cap-kill reads as a red gate the next time. Preserve
|
|
442
|
+
the signal-induced receipt under a distinct name (`<wbs>-check-receipt.false-fail-<signal>.json`)
|
|
443
|
+
and say so in the run log before re-running: never delete it silently, and never report a
|
|
444
|
+
cap-killed attempt as a genuine gate failure. Measured this session: one foreground gate call
|
|
445
|
+
(25.6 min host-wide lock wait + 7.4 min runtime) was SIGTERM-killed at the host cap and produced
|
|
446
|
+
exactly that false FAIL receipt.
|
|
366
447
|
- `note` — append the expanded message to the inline run log.
|
|
367
448
|
- `doctor.probe` — run the declared Spur doctor once, persist its status file, and apply any
|
|
368
449
|
`setVars` result (including a resolved executor) before the next action or state.
|
|
@@ -398,8 +479,10 @@ Action semantics come from the YAML and the workflow action contract:
|
|
|
398
479
|
`answerFile`; assert `expectFile`; honor `escalationFile` (a non-empty file after exit 0 means
|
|
399
480
|
the agent paused on an operator question — treat the attempt as succeeded-with-escalation and
|
|
400
481
|
skip `requireDiff` for that attempt only); enforce `requireDiff` against a pre-action git snapshot,
|
|
401
|
-
including the task-scope guard; honor declared error policy.
|
|
402
|
-
applicable because the host session has no independent kill boundary
|
|
482
|
+
including the task-scope guard; honor declared error policy. A host-inline action records
|
|
483
|
+
`timeoutMs` as not applicable because the host session has no independent kill boundary; a
|
|
484
|
+
dispatched subagent passes it to the host when the dispatch tool accepts a timeout (see
|
|
485
|
+
§ Timeout boundary).
|
|
403
486
|
- `run.artifact` — the engine's ledger registration has **no inline execution surface** (0808 R4).
|
|
404
487
|
The inline equivalent is a documented **registration-equivalent convention**: before the record
|
|
405
488
|
state mutates the task, the host validates the same refusal conditions inline — the declared
|
|
@@ -470,6 +553,49 @@ reformulate the prose into a command, spawn a subagent for it, or silently promo
|
|
|
470
553
|
Beyond the deterministic `estimate_hours` floor in condition 5, no token estimate, model heuristic,
|
|
471
554
|
or configuration switch is added.
|
|
472
555
|
|
|
556
|
+
**Contingent fallback (task 1134 R1/R2) — capacity is not capability.** That fallback is correct for
|
|
557
|
+
capability-shaped failures and **wrong when the exhausted resource is the host session's own
|
|
558
|
+
provider quota**: re-selecting the same exhausted provider cannot succeed by construction, and the
|
|
559
|
+
run collapses into one serial host worker with no executor diversity. So classify the pre-dispatch
|
|
560
|
+
failure before falling back:
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
DISPATCH_SCRIPT=plugins/sp/scripts/inline-run-dispatch.ts; [ -f config/plugin-scripts.json -a -f "$DISPATCH_SCRIPT" ] || DISPATCH_SCRIPT="$(superskill script path sp inline-run-dispatch.mjs 2>/dev/null)"
|
|
564
|
+
bun "$DISPATCH_SCRIPT" --dispatch-failure --run-id "$RUN_ID" --stage <state-id> --decision <escalate|stop> \
|
|
565
|
+
--text-file <bounded failure record> [--executor <name>] [--agent <a>] [--model <m>]
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
The helper wraps the upstream `@gobing-ai/ts-ai-runner` classifier (Spur matches **no** provider
|
|
569
|
+
code or quota vocabulary of its own) and writes `.spur/run/<run-id>-dispatch-fallback.json` as
|
|
570
|
+
`{stage, class, reason?, resetAt?, decision, executor, attribution, observedAt}`. Read `.class` from
|
|
571
|
+
that file — never judge the prose yourself.
|
|
572
|
+
|
|
573
|
+
- **`class: capability`** (generic 429, rate limit, overload, auth failure, timeout, missing
|
|
574
|
+
permission, missing capability, non-dispatch-eligible prose, below the size floor) → today's
|
|
575
|
+
fallback, unchanged: execute the stage once in the host session and log
|
|
576
|
+
`stage <id> executed inline in session <session-id>`.
|
|
577
|
+
- **`class: capacity`** (a confirmed exhausted usage allowance or credit balance) → the stage is
|
|
578
|
+
**never** re-executed in the orchestrating host session. Hand it to the subprocess dispatch path
|
|
579
|
+
(`--agent auto|name` resolves an executor through the registry and can pick another rung); when
|
|
580
|
+
that path is unavailable, terminate the run at `failed` with `terminalReason: failed-agent`,
|
|
581
|
+
naming the executor and the record's `resetAt` when it carries one. Record
|
|
582
|
+
`stage <id> capacity exhaustion: <executor> <reason> reset <resetAt|unknown> — escalate|stop`.
|
|
583
|
+
|
|
584
|
+
**Attribution before dispatch (task 1134 R3).** Immediately before each inline `agent.run` stage,
|
|
585
|
+
record the executor identity the stage will use, so a later classified exhaustion can be attributed
|
|
586
|
+
to a rung configuration can disable:
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
bun "$DISPATCH_SCRIPT" --attribution --run-id "$RUN_ID" --stage <state-id> \
|
|
590
|
+
[--executor <name>] [--agent <name>] [--model <m>] # omit --executor for the no-attribution marker
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
This appends one line to `.spur/run/<run-id>-attribution.jsonl` and one run-log line
|
|
594
|
+
(`stage <id> attribution: <executor|none>`). When no named executor resolves, pass no `--executor`:
|
|
595
|
+
the ledger records the explicit `executor: null` marker — never a silent drop. An unattributed stage
|
|
596
|
+
is observable but cannot write configuration (executor-availability §4), and `spur agent doctor
|
|
597
|
+
--json` counts those stages as `inlineFailFast.unattributedStages`.
|
|
598
|
+
|
|
473
599
|
**Pre-dispatch permission check (2026-09-15 subagent-dispatch evaluation).** Claude Code exposes no
|
|
474
600
|
dry-run permission API, so the contract is **name-capabilities + fail-fast blocker** — never a
|
|
475
601
|
permission-probing subsystem. Before dispatching a stage, the driver names the stage's required
|
|
@@ -482,7 +608,7 @@ prompt the host cannot see is invisible until join. Record one run-log line per
|
|
|
482
608
|
stage <id> permission precheck: ok | <missing capability>
|
|
483
609
|
```
|
|
484
610
|
|
|
485
|
-
Read-only investigation fan-out (
|
|
611
|
+
Read-only investigation fan-out (N-way investigation) dispatches read-only
|
|
486
612
|
worker shapes by construction — see the [pre-dispatch permission
|
|
487
613
|
rule](../../parallel-execution/references/fan-out-patterns.md).
|
|
488
614
|
|
|
@@ -515,13 +641,30 @@ boundary, and a delegate left to re-derive them re-derives them against its own
|
|
|
515
641
|
**owning stage's artifact contract** — for a verify stage, the compact contract below.
|
|
516
642
|
6. **The implement-stage acceptance-evidence requirement** — for the implement stage and any stage
|
|
517
643
|
whose YAML action declares `requireDiff`. A `requireDiff` stage is the one whose delegate writes
|
|
518
|
-
the deliverable, so its handoff carries
|
|
644
|
+
the deliverable, so its handoff carries four components:
|
|
519
645
|
(a) **the task's AC identities verbatim** — the exact `Scenario:` titles / checklist text read by
|
|
520
646
|
the driver from the task file, never paraphrased;
|
|
521
647
|
(b) **required evidence** — the pasted output of the narrow targeted tests the delegate ran, and
|
|
522
648
|
a `file:line` change map written into the task's `## Solution` section;
|
|
523
649
|
(c) the reminder that **a delegate success message is not evidence** — post-join validation reads
|
|
524
|
-
the artifacts and the diff, not the delegate's claims
|
|
650
|
+
the artifacts and the diff, not the delegate's claims;
|
|
651
|
+
(d) **the public-surface probe requirement (task 1135 R1/R2/R6).** Before the stage reports done
|
|
652
|
+
it runs at least one end-to-end probe of each new or changed **public surface** — a CLI
|
|
653
|
+
verb/flag, an HTTP route, or a plugin script entry — against a throwaway project root (never the
|
|
654
|
+
repo corpus, never the execution tree's tracked files), and reports the exact command plus its
|
|
655
|
+
observed output as acceptance evidence. A service-layer test suite is **not** a substitute. The
|
|
656
|
+
probe is bounded: 120 s per invocation, output capped at 8 KiB, no network beyond the CLI under
|
|
657
|
+
test, and a probe failure is evidence, never a corpus mutation.
|
|
658
|
+
A stage that changed no public surface returns instead the single line
|
|
659
|
+
`probe: not-applicable (no public surface changed)`.
|
|
660
|
+
The stage's own changed-path static self-check (task 1135 R8) also runs here: `spur rule run`
|
|
661
|
+
with the configured pre-check preset over the changed paths, `bun run script-contract-check`
|
|
662
|
+
when a plugin script was touched, and the paired-script parity test when `package.json` changed —
|
|
663
|
+
so an env-var-hygiene violation, a stale generated twin, or a half-updated paired chain is
|
|
664
|
+
named by the stage rather than discovered by the pipeline `test` hop.
|
|
665
|
+
**A stage that returns neither probe evidence nor the not-applicable line is not silently
|
|
666
|
+
complete (task 1135 R2): the driver records the stage `done` with a `probe-missing` marker in
|
|
667
|
+
the run log and carries the same marker into that task's batch-report row.**
|
|
525
668
|
Component (a) is read from the task by the driver; this extends the payload contract and is NOT a
|
|
526
669
|
new YAML key.
|
|
527
670
|
|
|
@@ -533,6 +676,26 @@ execution tree, and an ad-hoc git worktree created for a comparison is removed b
|
|
|
533
676
|
reports. The G65 batch's implement dispatches left an 867 MB worktree plus a trail of `/tmp` scratch
|
|
534
677
|
files that outlived the run (2026-09-15).
|
|
535
678
|
|
|
679
|
+
**Delegate search discipline.** A delegate searches with bounded scope: `rg -g '!node_modules' …`
|
|
680
|
+
(or an explicit narrow path), never `grep -r .` / `grep -rn --include=*` from a repository root —
|
|
681
|
+
those walk `node_modules` and `.git` and can burn many minutes at full CPU in the wrong tree
|
|
682
|
+
(measured 2026-10-09: a delegate's `grep -rn --include=* .` from the invoking tree root ran 4+ min
|
|
683
|
+
before the orchestrator killed it). The dispatch payload's execution-tree cwd is the only tree a
|
|
684
|
+
delegate searches or edits; a search it cannot bound is a search it should not run.
|
|
685
|
+
|
|
686
|
+
**Driver scratch lives with the run (task 1136 R6).** Every driver scratch file — action-batch
|
|
687
|
+
JSON, captured stage output, temporary status/answer files — lives under `.spur/run/<run-id>/`,
|
|
688
|
+
never `/tmp` or `$TMPDIR`: the OS may reap a temp directory mid-run, and a reaped scratch file
|
|
689
|
+
surfaces as an unrelated downstream failure. Run-scoped files directly under `.spur/run/` keep
|
|
690
|
+
their existing `<run-id>-*` naming (the `no-env-files` rule still bans `.env` names).
|
|
691
|
+
|
|
692
|
+
**Declared vars are checked before a shell action runs (task 1136 R6).** Before executing a `shell`
|
|
693
|
+
action whose command references a declared workflow var (for example `$qualityGateCmd`), the driver
|
|
694
|
+
confirms the var is exported and non-empty; when it is not, the action fails by name —
|
|
695
|
+
`shell action requires $qualityGateCmd but it is empty/unset` — and the command never runs. The
|
|
696
|
+
check is scoped to vars the workflow declares with a value: legitimately empty optional vars
|
|
697
|
+
(`$featureSpecPath` for an orphan task, `$gateFindings` on a green gate) pass unchanged. This turnsa silently empty `$qualityGateCmd` into a named failure instead of a shell that runs nothing.
|
|
698
|
+
|
|
536
699
|
**Verify-stage artifact contract.** A verify handoff names
|
|
537
700
|
[`code-verification/references/verdict-schema.md`](../../code-verification/references/verdict-schema.md)
|
|
538
701
|
as the canonical answer schema and carries this compact form verbatim:
|
|
@@ -546,6 +709,9 @@ Evidence Type = test | command | static-ref | manual-review | llm-judge | n/a
|
|
|
546
709
|
AC rows use the task's exact AC identities (verbatim `Scenario:` titles / checklist text).
|
|
547
710
|
A behavioral AC marked MET requires executable evidence (test | command);
|
|
548
711
|
static-ref or llm-judge alone cannot carry it.
|
|
712
|
+
Evidence anchors are repo-root-relative (`packages/app/src/foo.ts:42`); a basename
|
|
713
|
+
(`foo.ts:42`) is an unresolved anchor and blocks the done projection (`L4.anchor-unresolved`) —
|
|
714
|
+
see `code-verification/references/verdict-schema.md` § Basename-only anchors are unresolved anchors.
|
|
549
715
|
```
|
|
550
716
|
|
|
551
717
|
**AC table shape (0948 R9).** The AC table must be exactly **4 columns** and **cell 3 must hold a
|
|
@@ -597,11 +763,34 @@ grading its own work defeats the stage. A continuation stage also dispatches fre
|
|
|
597
763
|
same-task subagent exists (the earlier stage ran host-inline or below the dispatch floor), when a
|
|
598
764
|
host-owned gate sat between the stages, or when the platform cannot address completed subagents.
|
|
599
765
|
|
|
600
|
-
**
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
766
|
+
**Implement and test-fix workers never run the full gate (task 1142 R3).** Workers use the light tier as their iteration check (task 1142 R3) in exact order:
|
|
767
|
+
1. the **changed-path matrix** (`code-implementation/SKILL.md` § Changed-path targeted checks) over the paths they touched;
|
|
768
|
+
2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome, per-workspace typecheck, rule presets, related tests).
|
|
769
|
+
Neither step takes the full-gate lock. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
|
|
770
|
+
`--gate-log <path>` through to the child **verbatim**: never paraphrase it into "re-run the named
|
|
771
|
+
gate command". That paraphrase is not cosmetic — it sent a worker into a full gate run instead of
|
|
772
|
+
targeted iteration checks. The pipeline's next `test` (recheck)
|
|
773
|
+
stage delivers the deciding verdict.
|
|
774
|
+
|
|
775
|
+
**Null or timed-out dispatch (task 1134 R9).** The dispatch bound stays the existing YAML
|
|
776
|
+
`timeoutMs` (`implementTimeoutMs` / `stepTimeoutMs`), passed to the host dispatch as today — R9 adds
|
|
777
|
+
no new bound. A dispatch that returns **no result**, or that hits that bound, is a **recorded stage
|
|
778
|
+
failure**, not a fallback trigger: record the stage failed with `dispatch: null-result|timeout`,
|
|
779
|
+
follow the stage's normal failure edge (implement's default `fail` policy routes the run to
|
|
780
|
+
`failed`), and start **no** host-inline attempt of that stage. R1–R4 cover a *pre-dispatch* capacity
|
|
781
|
+
refusal (the executor must shift); R9 covers the *dispatched-but-unproductive* case.
|
|
782
|
+
|
|
783
|
+
**Timeout boundary (task 0727, amended by task 1108):** when the host's dispatch tool accepts a
|
|
784
|
+
per-dispatch timeout (pi: subagent `timeoutMs`, default 30 min — pi-subagents `docs/tool-reference.md`
|
|
785
|
+
line 97), the driver passes the stage's resolved YAML
|
|
786
|
+
`timeoutMs` (implement → `implementTimeoutMs`) and records `host timeout <ms> (yaml timeoutMs)`.
|
|
787
|
+
When it does not, a dispatched subagent is governed by
|
|
788
|
+
**the host platform's subagent limit, not the YAML timeoutMs**, and the driver records
|
|
789
|
+
`host timeout <ms> (platform subagent limit)`. `timeoutMs` stays not-applicable for host-inline
|
|
790
|
+
execution only. Either way the driver must
|
|
791
|
+
**record the governing timeout boundary and its source before dispatch** in the run log. Rationale:
|
|
792
|
+
the operator sets one budget in one place; a host silently imposing its own 30 min default is what
|
|
793
|
+
killed the P1 implement dispatches (1096, 1099). If the dispatch reaches that
|
|
605
794
|
boundary, **a dispatch timeout is a started-subagent failure**: the no-replay rule above and the
|
|
606
795
|
stage's declared YAML error policy govern (implement's default `fail` policy routes the run to
|
|
607
796
|
`failed`); it is never a host re-execution. Recovery follows the
|
|
@@ -624,139 +813,48 @@ Run-log stamps (task 0727): every appended line is prefixed with an **ISO-8601 U
|
|
|
624
813
|
(`YYYY-MM-DDTHH:MM:SSZ`, e.g. `2026-08-31T17:51:11Z`); the exact-template provenance lines above
|
|
625
814
|
keep their exact content after the stamp prefix. This normalization is contractual:
|
|
626
815
|
**bare local-clock stamps are prohibited** — a hand-appended `[stage 12:31]` form mixes timezones
|
|
627
|
-
in one file and makes the run unauditable (task 0726
|
|
816
|
+
in one file and makes the run unauditable (task 0726).
|
|
628
817
|
|
|
629
818
|
## Structured trace emission (ADR-117, task 0868)
|
|
630
819
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
observability is a property of the run, so the inline driver owes the same structured trace the
|
|
634
|
-
engine subprocess writes — and it owes it through the **same writer**, never a parallel
|
|
635
|
-
implementation. The shared writer is `WorkflowActionTraceWriter`
|
|
636
|
-
(`packages/app/src/workflow/action-trace.ts`): the same decorator the engine composition installs
|
|
637
|
-
around `DbWorkflowPersistenceAdapter`, so the two surfaces call one emission path and one run-row
|
|
638
|
-
closure path and cannot drift.
|
|
820
|
+
Read [structured-trace-emission.md](structured-trace-emission.md) when the first trace event is
|
|
821
|
+
emitted — the action vocabulary, the emit shape and the parse/close contract.
|
|
639
822
|
|
|
640
|
-
|
|
641
|
-
`plugins/sp/scripts/inline-run-setup.ts`) — no new entry point, no second resolution chain:
|
|
823
|
+
## Post-gate tree freeze (task 1135 R3/R4/R5)
|
|
642
824
|
|
|
643
|
-
|
|
644
|
-
|
|
825
|
+
The quality gate's `proofDigest` certifies a specific tree. Two rules protect it, and both are
|
|
826
|
+
**ordering plus early detection** — a host session cannot intercept a file write, so the contract is:
|
|
645
827
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
time (a value reconstructed after the action returned). The row's provenance stamp rides
|
|
656
|
-
`action_runs.result_json` as `{provenance:'host-reported', estimated}`, and the projection
|
|
657
|
-
exposes it per attempt — `provenance` is `host-reported` for every inline row and `unknown`
|
|
658
|
-
for an engine-written or pre-stamp row, so the Board can label a host-reported duration
|
|
659
|
-
instead of presenting it as measured. This writes the `action_runs` row (node, kind, status,
|
|
660
|
-
`ok`, `duration_ms`, `run_id`) the engine would have written, so the run's rows are queryable
|
|
661
|
-
by run id (`spur workflow progress <run-id>`, `ActionRunDao`) without reading the text log.
|
|
662
|
-
The writer
|
|
663
|
-
back-dates the row's `started_at` from its own `completed_at` minus the measured duration
|
|
664
|
-
(0887 R8), so `completed_at − started_at == duration_ms` exactly; a back-date failure is
|
|
665
|
-
recorded (`action.backdate`) and never affects the run.
|
|
666
|
-
|
|
667
|
-
- **A state with several actions (1007 R5)** — emit the whole state's boundaries in one call
|
|
668
|
-
instead of one `--action` invocation per action. Write a JSON array
|
|
669
|
-
(`[{node,kind,status,ok,durationMs,estimated?}, …]` — the `--action` fields, `estimated`
|
|
670
|
-
optional and `false` when absent) to a temp file and pass it with `--actions-file`:
|
|
671
|
-
|
|
672
|
-
```bash
|
|
673
|
-
bun "$SETUP_SCRIPT" --actions-file <actions.json> --run-id "$RUN_ID"
|
|
674
|
-
```
|
|
828
|
+
1. **Every tree edit belongs to `implement`, or to the bounded `test-fix` remediation hop** — stray-file
|
|
829
|
+
cleanup, anchor qualification, contract pins, doc edits, and task-section writes other than the
|
|
830
|
+
pipeline-owned `## Testing`/`## Review`/`## Solution`. After `test` captures `proofDigest`, no
|
|
831
|
+
other state edits the tree. A genuine later correction goes through `test-fix → test-recheck`,
|
|
832
|
+
which re-captures the digest and re-snapshots the tree state.
|
|
833
|
+
2. **Review joins the proof bracket.** The pipeline now compares `proofDigestNow` against
|
|
834
|
+
`proofDigest` at **review** entry as well as verify and record, so a drifted tree stops the run
|
|
835
|
+
before a review dispatch is spent. The mismatch routes through the existing tripwire to `failed`;
|
|
836
|
+
there is no new re-certify path.
|
|
675
837
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
- **A `decide` action (0941)** — the driver never executes the DecisionMaker itself; it delegates
|
|
687
|
-
to the same app runner the engine registers, which writes the resultFile row (schemaVersion 1)
|
|
688
|
-
and returns the decision, then the delegate records the `action_runs` row (`kind=decide`)
|
|
689
|
-
through the same writer as every other action:
|
|
838
|
+
**A mismatch names the drifted paths (R5).** At gate entry the pipeline snapshots the non-corpus tree
|
|
839
|
+
state (`git status --porcelain=v1 -uall` plus `git diff --name-only HEAD`, excluding
|
|
840
|
+
`docs/tasks*` / `docs/features*`) into `.spur/run/<run-id>-gate-paths.txt`. On a mismatch the
|
|
841
|
+
`proof.fingerprint` action diffs the current state against that file and reports the changed paths in
|
|
842
|
+
both the tripwire event and the run log (`proof-compare-failed … drifted paths: …`), so the operator
|
|
843
|
+
reads *which file* moved, not only the `D1`/`D2` pair. An **untracked** file outside those exclusions
|
|
844
|
+
is part of the certified set: the digest's git-tree half stages everything outside the excluded
|
|
845
|
+
globs, so a stray file added after the gate is real drift and must be named (the behaviour is
|
|
846
|
+
documented beside `DEFAULT_EXCLUDE_GLOBS` in `proof-input-fingerprint.ts`).
|
|
690
847
|
|
|
691
|
-
|
|
692
|
-
bun "$SETUP_SCRIPT" --decide --run-id "$RUN_ID" --node <state-id> --options-json <options-file>
|
|
693
|
-
```
|
|
694
|
-
|
|
695
|
-
The options JSON mirrors the YAML `decide` options (`id`, `method: choice|noul`, `question`,
|
|
696
|
-
`choices`/`default`, optional `evidence`, optional `minConfidence`, `resultFile`); paths resolve
|
|
697
|
-
against the project workdir. The decision never pauses and never fails the run for model
|
|
698
|
-
problems: a degraded outcome (feature switch off, no backend, error, timeout, low confidence)
|
|
699
|
-
prints `ok:true` with `degraded:true`, the declared `reason`, and `value = default`, and exits
|
|
700
|
-
`0` — route on the resultFile's `.value` with the declared file guards. Every row carries
|
|
701
|
-
`source: model|default` (0976 R2), and the delegate appends
|
|
702
|
-
`decide node=<id> value=<v> source=<s> reason=<r>` to the run log, so a declared-default
|
|
703
|
-
fallback is never read as a model decision. Only an invalid options
|
|
704
|
-
schema exits `1` (fail closed), and usage errors exit `2`. The `action_runs` trace row is
|
|
705
|
-
best-effort exactly like `--action`.
|
|
706
|
-
|
|
707
|
-
- **At the run's declared terminal state** — before the driver reports the run complete, close the
|
|
708
|
-
row so a successful inline run is never left non-terminal for `spur workflow clean` to reap as
|
|
709
|
-
stale:
|
|
710
|
-
|
|
711
|
-
```bash
|
|
712
|
-
bun "$SETUP_SCRIPT" --close --run-id "$RUN_ID" --status <done|failed|paused>
|
|
713
|
-
```
|
|
848
|
+
## Record & done sequencing
|
|
714
849
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
the close reports the recorded evidence: `{"ok":true,"runId":…,"actionRows":<n>}`.
|
|
718
|
-
After the close, the invoking command prints the measured
|
|
719
|
-
[execution summary](dev-operations.md#execution-summary) for `$RUN_ID` unless `--no-summary`.
|
|
720
|
-
|
|
721
|
-
**Zero-row done closes are a named failure (task 0975 R2).** A run closed `done` with **zero**
|
|
722
|
-
`action_runs` rows (`actionRows:0`) finalizes the row but exits `1` with
|
|
723
|
-
`{"ok":false,"code":"NO_ACTION_ROWS","actionRows":0}` — a run that claims success without a
|
|
724
|
-
single recorded action is exactly the untraced-runs gap ADR-117 closes, so the driver must
|
|
725
|
-
surface the code in its final report instead of reporting a clean close. The failure must NOT
|
|
726
|
-
be repaired by backfilling rows: the run row is already terminal, and hand-written rows are
|
|
727
|
-
forbidden (ADR-117) — record the finding and let the task's evidence show the gap. A `failed`
|
|
728
|
-
close with zero rows stays a clean `0` (a halt before the first boundary legitimately records
|
|
729
|
-
nothing), and any `done` close with `actionRows ≥ 1` exits `0`.
|
|
730
|
-
|
|
731
|
-
**Best-effort at the action boundary only (ADR-117).** An `--action` persistence failure is
|
|
732
|
-
recorded — the delegate appends a `trace-emission-failed` line to `.spur/memory/runs/<run-id>.md` and
|
|
733
|
-
prints `{"ok":false}` on stdout — and the run continues to its declared terminal state; the
|
|
734
|
-
delegate exits `0` for that outcome and the driver must never treat an emission failure as a run
|
|
735
|
-
failure, retry it in a loop, or substitute a hand-written row. The run-row closure (`--close`) is
|
|
736
|
-
bookkeeping, not trace emission, and is **not** best-effort: a missing run row or a persistence
|
|
737
|
-
failure exits `1` with `{"ok":false}` and a named error (a missing row also carries
|
|
738
|
-
`code:"RUN_NOT_FOUND"`), because a silently `running` row is exactly the stale state
|
|
739
|
-
`spur workflow clean` reaps as `failed`. Exit `2` means the invocation itself was malformed
|
|
740
|
-
(missing `--node`/`--kind`/`--status`/`--ok`, a miscased `--ok`, a missing or malformed
|
|
741
|
-
`--duration-ms`, or an unsafe run id) and must be corrected, not ignored.
|
|
742
|
-
|
|
743
|
-
Emission is not optional and not deferred: an inline run that skips it reintroduces the
|
|
744
|
-
1,011-untraced-runs gap ADR-117 exists to close.
|
|
745
|
-
|
|
746
|
-
Transition guards are not advisory. Execute the declared guard exactly, in order, with the same
|
|
747
|
-
resolved variables and artifacts. `--no-lifecycle` remains bookkeeping only; the YAML's task checks,
|
|
748
|
-
verdict gate, record step, and done guard all remain authoritative.
|
|
749
|
-
|
|
750
|
-
## Record & done sequencing (dogfood 2026-08-21, feature A3)
|
|
751
|
-
|
|
752
|
-
Order matters for the `testing → done` hop. The A3 batch hit the same clobbering spiral on two
|
|
753
|
-
tasks (0617, 0619) because the sections were hand-written **before** the verdict artifact existed:
|
|
850
|
+
Order matters for the `testing → done` hop: the sections must not be hand-written **before** the
|
|
851
|
+
verdict artifact exists (tasks 0617, 0619 — the clobbering spiral).
|
|
754
852
|
|
|
755
853
|
1. **Write the verdict artifact first.** `spur task record --solution-from-diff --transition testing`
|
|
756
854
|
reads `.spur/run/<wbs>-verdict.json` (default attempt output) on every invocation and atomically retains the recorded verdict under `.spur/memory/evidence/`. A missing or malformed artifact
|
|
757
855
|
yields **UNKNOWN**: bare Testing receives a "No requirements recorded" stub, while already-authored
|
|
758
|
-
Testing is preserved. `--solution-from-diff` backfills only a bare Solution. The
|
|
759
|
-
|
|
856
|
+
Testing is preserved. `--solution-from-diff` backfills only a bare Solution. The clobbering above is
|
|
857
|
+
the historical behavior, corrected by the authored-Testing safeguard (tasks 0617, 0619). Creating the
|
|
760
858
|
artifact first (PASS, with requirement rows keyed by scenario title) remains the standard order;
|
|
761
859
|
re-running record after a real verdict arrives refreshes Testing from that verdict.
|
|
762
860
|
|
|
@@ -775,6 +873,12 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
|
|
|
775
873
|
2. **Done-probe before done.** Run the check projected to `done` (`spur task check <wbs> --as done`
|
|
776
874
|
via the `TaskCheckService` probe pattern) — it surfaces `L3.unchecked-checklist` (flip `- [ ]` → `- [x]`)
|
|
777
875
|
and `L3.required-section-placeholder` before the transition, not after.
|
|
876
|
+
**Anchor repair is the YAML implement stage's job, not the driver's** (task 1109 R4):
|
|
877
|
+
`task-pipeline.yaml` runs `$spurBin task migrate-anchors --wbs $wbs --json ; exit 0` after the
|
|
878
|
+
format step and before `test` captures the proof digest, so unique basename anchors are already
|
|
879
|
+
repo-relative by the time this probe runs. The driver does not hand-patch anchors: anything the
|
|
880
|
+
scoped pass leaves (ambiguous basenames, line drift, `L4.anchor-subject-mismatch`) is reported by
|
|
881
|
+
this probe and belongs to the implementer to re-anchor.
|
|
778
882
|
3. **Solution change-map anchor rule (L4.anchor-subject-mismatch).** A Solution change-map table must
|
|
779
883
|
list **one `file:line` per row**. A ·-joined paragraph makes every anchor's "subject" the other
|
|
780
884
|
anchors and trips the L4 subject check. Since 0804 R9, subject extraction ignores complete parsed
|
|
@@ -782,6 +886,77 @@ tasks (0617, 0619) because the sections were hand-written **before** the verdict
|
|
|
782
886
|
(`docs/help/cmd_example.md:12`) is checked exactly like any other row — cite an **existing file**
|
|
783
887
|
with a **valid line or line range** whose content names the requirement's subject. A real absent
|
|
784
888
|
symbol, nonexistent file or invalid range still reports; never replace a citable row with prose.
|
|
889
|
+
4. **Stage through the commit guard (1129 R2/R3/R5).** The per-task commit that follows the record
|
|
890
|
+
hop stages only the files THIS run wrote — `git add -A` and `git add .` are **forbidden** in
|
|
891
|
+
every driver commit step (they staged a concurrent session's files in `a94f9f431` and
|
|
892
|
+
`36f274590`). `check` first and record a non-empty `foreign` list in the run report; `stage`
|
|
893
|
+
exits 2 when it refuses a listed path (foreign) and 3 when a target is unmerged or still carries
|
|
894
|
+
conflict markers — both mean "resolve it, do not commit":
|
|
895
|
+
|
|
896
|
+
```bash
|
|
897
|
+
GUARD=plugins/sp/scripts/commit-guard.ts; [ -f config/plugin-scripts.json -a -f "$GUARD" ] || GUARD="$(superskill script path sp commit-guard.mjs 2>/dev/null)";
|
|
898
|
+
bun "$GUARD" check --run "$RUN_ID"
|
|
899
|
+
bun "$GUARD" stage --run "$RUN_ID" -- <files this run wrote> \
|
|
900
|
+
|| { echo "record halt: commit-guard refused a path (foreign, unmerged or conflict-marked); nothing committed" >&2; exit 1; }
|
|
901
|
+
git commit -m "<type>(<scope>): <wbs> <summary>"
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
5. **Print the run's execution summary (1146).** The terminal `--close` returns `summaryFile`; print
|
|
905
|
+
that file's contents verbatim under an `### Execution summary` heading, and pass `--no-summary`
|
|
906
|
+
through to `--close` when the operator asked for it. The window is the run row's `started_at` →
|
|
907
|
+
close time — the driver records no `SINCE` and assembles no measurement of its own
|
|
908
|
+
(structured-trace-emission.md § the close step owns the contract).
|
|
909
|
+
|
|
910
|
+
## Running the full gate (task 0872 / 1127 R7)
|
|
911
|
+
|
|
912
|
+
The full gate serializes behind a host-wide lock and legitimately runs 10-25 minutes. Two rules keep
|
|
913
|
+
it from becoming a hazard (both violated once on 2026-10-10, blocking every other session for ~11
|
|
914
|
+
minutes and aborting the run's own verification):
|
|
915
|
+
|
|
916
|
+
- **Never wrap it in an external `timeout`, and never treat its runtime as a hang.** An outer bound
|
|
917
|
+
that fires mid-suite leaves the holder alive holding the lock while the caller believes it is gone.
|
|
918
|
+
- **Run it detached and poll the log**, so losing the foreground call cannot lose the holder:
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
nohup bash -c 'bun run spur-check > /tmp/gate.log 2>&1; echo "EXIT=$?" >> /tmp/gate.log' &
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
- **Never kill a gate holder whose pid is alive.** The waiter line `waiting for holder (… held Nms)`
|
|
925
|
+
is printed only after `isProcessAlive(held.claim.pid)` succeeded, so a printed holder is a live one
|
|
926
|
+
by construction; only the lock's own dead-pid reclaim is authorised to clear a claim.
|
|
927
|
+
|
|
928
|
+
## Runtime interface contracts (from run evidence)
|
|
929
|
+
|
|
930
|
+
Each item below cost a driver cycle to discover. They are contracts, not tips.
|
|
931
|
+
|
|
932
|
+
- **`--decide --options-json <file>` takes one option object, never an array.** Shape:
|
|
933
|
+
`{id, decision, params?, evidence? | instructions?, resultFile}`; `evidence` and `instructions` are
|
|
934
|
+
mutually exclusive (`CatalogDecideOptionsSchema`). A JSON array parses as neither dialect and
|
|
935
|
+
surfaces as `decide: invalid options — Invalid input` with no field detail. `resultFile` resolves
|
|
936
|
+
against the confirmed tree's cwd, so pass a worktree-relative path such as
|
|
937
|
+
`.spur/run/<wbs>-triage.decision`.
|
|
938
|
+
- **`task-diffstat.ts` reads `wbs` from the environment and writes an artifact.** Invoke it as
|
|
939
|
+
`wbs=<wbs> node "$(superskill script path sp task-diffstat.mjs)"`; stdout stays empty, so assert success on
|
|
940
|
+
`.spur/run/<wbs>-diffstat.json` and never on command output. Never the bare unguarded
|
|
941
|
+
source-repo path form — the script-contract check forbids it on a shipped surface because it
|
|
942
|
+
shadows the installed twin in a consumer.
|
|
943
|
+
- **The verify answer grammar is exact.** One `Verdict:` line, one `Confidence:` line, then
|
|
944
|
+
`### Per-Requirement Traceability` (`| Req | Status | Evidence |`) and
|
|
945
|
+
`### Acceptance Criteria Verification` (`| AC | Status | Evidence Type | Evidence |`). Requirement
|
|
946
|
+
statuses are `MET | PARTIAL | UNMET`; AC statuses add `N/A`; every AC row id must match a declared
|
|
947
|
+
scenario title verbatim, including its `(req: ...)` suffix. A `MET` row whose evidence hedges
|
|
948
|
+
(`likely`, `probably`, `seems`, `appears`, `possibly`, `presumably`) is rejected as
|
|
949
|
+
`evidence-hedged`, and a level the rows do not earn is rejected as `confidence-unwarranted` or
|
|
950
|
+
`confidence-understated`. `spur task verdict` lints before writing, so a rejected answer leaves no
|
|
951
|
+
artifact and exits non-zero.
|
|
952
|
+
- **Harness tooling refuses direct task-file writes.** Resolve a merge conflict in a task file by
|
|
953
|
+
choosing a git side, then applying the corpus delta through `spur task update` (for example
|
|
954
|
+
`--feature <id>`), never by editing the file.
|
|
955
|
+
- **A skill body has a byte budget** (20,000 B for `SKILL.md`); procedure detail belongs in
|
|
956
|
+
`references/`. Adding a clause to a SKILL.md can fail `R44 — skill BODY budgets`.
|
|
957
|
+
- **`.spur/run/` is volatile** (ADR-131, as amended): its contents are execution scratch. Anything
|
|
958
|
+
that outlives the run cites the durable owner (`.spur/memory/evidence/`, `.spur/memory/runs/`) or
|
|
959
|
+
the tracked Testing section.
|
|
785
960
|
|
|
786
961
|
## Failure contract
|
|
787
962
|
|