@gobing-ai/spur 0.3.99 → 0.4.1
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 +17 -0
- 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/decisions/task-pipeline.yaml +55 -0
- package/config/plugin-scripts.json +25 -0
- package/config/rules/boundary/sp-runtime-path.yaml +2 -0
- package/config/rules/docs/sourced-api-claims.yaml +60 -0
- package/config/rules/quality/coverage-gate.yaml +5 -3
- package/config/rules/recommended-pre-check.yaml +8 -3
- package/config/rules/strict/runtime-boundaries.yaml +11 -0
- package/config/rules/structure/protected-files.yaml +3 -0
- package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
- package/config/script-placement-baseline.json +2 -2
- 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 +368 -49
- package/config/workflows/wrapup-pipeline.yaml +106 -13
- package/package.json +9 -9
- package/plugins/sp/README.md +25 -2
- package/plugins/sp/agents/super-planner.md +11 -0
- package/plugins/sp/agents/super-reviewer.md +39 -11
- package/plugins/sp/bunfig.toml +8 -0
- package/plugins/sp/commands/dev-brainstorm.md +29 -16
- package/plugins/sp/commands/dev-changelog.md +5 -4
- package/plugins/sp/commands/dev-find-way.md +80 -0
- package/plugins/sp/commands/dev-idea.md +6 -0
- package/plugins/sp/commands/dev-parallel.md +8 -2
- package/plugins/sp/commands/dev-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +14 -7
- package/plugins/sp/commands/dev-run.md +17 -4
- package/plugins/sp/commands/dev-runall.md +21 -10
- package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
- package/plugins/sp/hooks/context-session-start.ts +1 -1
- package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
- package/plugins/sp/hooks/hooks.json +45 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +72 -11
- package/plugins/sp/lib/inline-run-app.ts +28 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +6 -0
- package/plugins/sp/lib/inline-run.generated.mjs +364 -46
- package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
- 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 +275 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +335 -0
- package/plugins/sp/scripts/batch-plan.ts +100 -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/history-anatomy-cache.mjs +5 -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 +563 -91
- package/plugins/sp/scripts/inline-run-setup.ts +65 -97
- package/plugins/sp/scripts/persist-out-check.mjs +90 -41
- package/plugins/sp/scripts/persist-out-check.ts +112 -57
- package/plugins/sp/scripts/quality-gate.mjs +30407 -73
- package/plugins/sp/scripts/quality-gate.ts +21 -5
- package/plugins/sp/scripts/residual-scan.mjs +88 -38
- package/plugins/sp/scripts/residual-scan.ts +35 -38
- package/plugins/sp/scripts/run-summary.mjs +693 -0
- package/plugins/sp/scripts/run-summary.ts +105 -0
- package/plugins/sp/scripts/session-timeline.mjs +331 -0
- package/plugins/sp/scripts/session-timeline.ts +204 -0
- 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 +8 -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 +63 -0
- package/plugins/sp/skills/session-review/SKILL.md +77 -24
- 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 +6 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +124 -17
- package/plugins/sp/skills/spur-cli/references/decision.md +98 -0
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +41 -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 +28 -2
- package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +51 -18
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -963
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +20 -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 +51 -9
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +361 -156
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
- package/schemas/decision-catalog.schema.json +226 -0
- package/schemas/spur-config.schema.json +25 -0
- package/schemas/state-machine-workflow.schema.json +23 -0
- package/spur.js +28165 -19051
- package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.D66k_s_T.js} +1 -1
- package/web/_astro/BoardApp.D6sjAJr_.js +324 -0
- package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.BynLnMNc.js} +1 -1
- package/web/_astro/{arc.Bu5XpBEz.js → arc._BUadrdH.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
- package/web/_astro/channel.DZVfto-r.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
- package/web/_astro/index.COh_kBxN.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
- package/web/_astro/{linear.Dxw00fGQ.js → linear.MzmRrNER.js} +1 -1
- package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.fK-WNswS.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
- package/web/board-runtime.json +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
- package/web/_astro/channel.6IFIKBhu.js +0 -1
- package/web/_astro/index.CbNz17Sx.css +0 -1
|
@@ -9,6 +9,23 @@ see_also:
|
|
|
9
9
|
|
|
10
10
|
# Execution Batch
|
|
11
11
|
|
|
12
|
+
## Driver state checklist (re-read this after a compaction)
|
|
13
|
+
|
|
14
|
+
- **Run id** — the batch id in `.spur/run/worktree-<marker-id>.json`, or the persisted Step 5 report
|
|
15
|
+
filename; every later artifact is keyed by it.
|
|
16
|
+
- **Markers under `.spur/run/`** — `worktree-<marker-id>.json` (identity + status),
|
|
17
|
+
`worktree-<marker-id>-batch-report.md` (frozen plan + outcomes), `<wbs>-verdict.json` (verdict),
|
|
18
|
+
`<wbs>-question.md` (paused on an operator question).
|
|
19
|
+
- **Re-read per state** — this file (Steps 1–5, WT-6/WT-7), [execution-batch-report.md](execution-batch-report.md)
|
|
20
|
+
at Step 5, [execution-worktree-setup.md](execution-worktree-setup.md) for a worktree start,
|
|
21
|
+
[execution-worktree-landing.md](execution-worktree-landing.md) at batch end,
|
|
22
|
+
[execution-parallel-isolation.md](execution-parallel-isolation.md) for `--mode parallel`,
|
|
23
|
+
[execution-batch-continuation.md](execution-batch-continuation.md) for `--continue`.
|
|
24
|
+
- **Never re-derive the plan** — the frozen plan is the persisted report; a new selector match is
|
|
25
|
+
`not-admitted`, not a plan change.
|
|
26
|
+
|
|
27
|
+
## What this file owns
|
|
28
|
+
|
|
12
29
|
`/sp:dev-runall` runs a **set** of task files through their pipelines in one operation, in
|
|
13
30
|
dependency-correct order. This file owns the batch algorithm: selector resolution, set freeze,
|
|
14
31
|
topological ordering, the per-task run loop, optional parallel fan-out, the failure policy, and the
|
|
@@ -76,18 +93,33 @@ dependency resolution, or worktree task execution:
|
|
|
76
93
|
|
|
77
94
|
```bash
|
|
78
95
|
# monorepo source; installed projects use their resolved `spur` binary
|
|
96
|
+
# 1132 R4 (auto-fix-first): repair before checking — machine repairs land first, the
|
|
97
|
+
# strict check then judges the repaired file. `--fix` output is collected as
|
|
98
|
+
# `autoRepairs` in the batch report.
|
|
99
|
+
bun run apps/cli/src/index.ts feature check <id> --fix --json
|
|
79
100
|
bun run apps/cli/src/index.ts feature check <id> --strict --json
|
|
80
101
|
```
|
|
81
102
|
|
|
82
103
|
- **Abort shape.** A non-zero check aborts the batch immediately: verdict `aborted`, zero attempted
|
|
83
104
|
tasks, and the structured feature findings (the `--json` finding list) reported verbatim. This is
|
|
84
|
-
the same abort vocabulary as cycle / unknown selector (Step 4/Step 5). **
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
the same abort vocabulary as cycle / unknown selector (Step 4/Step 5). **Expected pre-run
|
|
106
|
+
states are classified in code, not here:** the non-aborting set lives in
|
|
107
|
+
`NON_ABORTING_PREFLIGHT_CODES` (`plugins/sp/scripts/batch-preflight.ts`), consumed through
|
|
108
|
+
`classifyFeaturePreflightFindings`, which splits the finding list into `aborting` and
|
|
109
|
+
`nonAborting` and reports `shouldAbort`. `L4.scenario-unverified` is the expected state of any
|
|
110
|
+
not-yet-run feature (its covering tasks have no PASS verdicts yet) and
|
|
111
|
+
`L4.verifying-incomplete-tasks` is the expected state of a `verifying` feature that gained new
|
|
112
|
+
live tasks (a warning in `FeatureCheckService` until `done`); `--strict` elevates both to error,
|
|
113
|
+
so they are reported verbatim but do **not** abort the batch; every other error finding (L1–L3
|
|
114
|
+
structural, `L4.malformed-verdict-artifact`, `L4.uncovered-feature-scenario`) still aborts. The
|
|
115
|
+
terminal feature-transition gate enforces scenario verification after the batch runs, so pre-run
|
|
116
|
+
unverified scenarios and incomplete tasks are transient, not defects (dogfood 2026-08-11,
|
|
117
|
+
feature I2; 2026-10-08, feature H1). Extend the set by editing that constant, never this prose.
|
|
118
|
+
- **Auto-repairs are reported.** Structural repairs applied by `--fix` are listed under
|
|
119
|
+
`autoRepairs` in the batch report, so a green preflight that only passed after a machine repair is
|
|
120
|
+
visible rather than silent. Two producers: the task-level `task check <wbs> --fix` inside the
|
|
121
|
+
task-pipeline precheck guard (its `--json` output is captured at
|
|
122
|
+
`.spur/run/<wbs>-auto-repairs.json`) and the feature-level `feature check <id> --fix` pass above.
|
|
91
123
|
- **Exactly once.** The check runs once per batch, before `task list`; it is not re-run per task.
|
|
92
124
|
- **Non-feature exclusion.** Explicit WBS lists, status pseudo-lists, and `ready` selectors add no
|
|
93
125
|
feature check — only an effective `feature:<id>` selector is feature-derived. When explicit
|
|
@@ -96,12 +128,23 @@ bun run apps/cli/src/index.ts feature check <id> --strict --json
|
|
|
96
128
|
**warning**; feature-scoped batches never ran a strict check before freezing, so the late feature
|
|
97
129
|
transition became the first blocking check. A selector-local strict preflight catches a known
|
|
98
130
|
strict finding before any task pipeline action without changing corpus-wide severity. A not-yet-run
|
|
99
|
-
feature always fails `L4.scenario-unverified` under `--strict`, so that class
|
|
100
|
-
reported-not-aborting per the Abort shape above — the preflight targets defects, not the
|
|
131
|
+
feature always fails `L4.scenario-unverified` under `--strict`, so that class (and
|
|
132
|
+
`L4.verifying-incomplete-tasks`) is reported-not-aborting per the Abort shape above — the preflight targets defects, not the
|
|
101
133
|
expected pre-run state.
|
|
102
134
|
- **Scope.** This preflight is advisory to severity policy: it does not alter `FeatureCheckService`,
|
|
103
135
|
`L3.scope-delineation` severity, `feature sync`, or batch-create.
|
|
104
136
|
|
|
137
|
+
**Semantic precheck failure under `--auto` (1132 R5).** Per-task precheck runs
|
|
138
|
+
`task check <wbs> --fix` before `--precheck` (the task-pipeline precheck guard), so lossless
|
|
139
|
+
format defects are repaired in place and never reach the strict verdict. A finding that survives
|
|
140
|
+
`--fix` is semantic, and the two profiles diverge:
|
|
141
|
+
|
|
142
|
+
- **`--auto`:** the task gets **one** `/sp:dev-refineall --auto` refinement pass (exactly one — never
|
|
143
|
+
a loop). If the re-run precheck still fails, the task is marked **skipped** with its findings
|
|
144
|
+
recorded in the batch report, and independent tasks keep running. The batch does not abort.
|
|
145
|
+
- **without `--auto`:** unchanged halt behavior — the batch stops on the failing task (`--keep-going`
|
|
146
|
+
still skips only its in-batch dependents).
|
|
147
|
+
|
|
105
148
|
`--tasks <value>` (or the effective value after normalization) resolves to a frozen set of task WBS numbers. Resolution happens **once, at
|
|
106
149
|
kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
|
|
107
150
|
|
|
@@ -201,17 +244,58 @@ remain SSOT in `next-router/references/routing-table.md`. Step 2.3 already pre-b
|
|
|
201
244
|
out-of-set deps; 2.6 is belt-and-braces for status STOP rows and a uniform report shape
|
|
202
245
|
(`dev-next:`-style reasons). Parallel mode: preflight each WBS before fan-out.
|
|
203
246
|
|
|
247
|
+
### 2.7 Visible batch plan (1105 R3)
|
|
248
|
+
|
|
249
|
+
The batch publishes a generated two-layer plan in the host's native todo list — letters first, digits
|
|
250
|
+
per task only at task start. Rows come from `batch-plan.mjs` (ADR-130 glue over
|
|
251
|
+
`packages/app/src/workflow/plan-projection.ts`), reached like `batch-preflight`:
|
|
252
|
+
`node "$(superskill script path sp batch-plan.mjs)"`. The visible plan is a projection of the frozen
|
|
253
|
+
plan, never a second plan: labels carry identity, updates happen at the boundaries below only, and no
|
|
254
|
+
per-task letter is ever hand-assigned.
|
|
255
|
+
|
|
256
|
+
1. **Kickoff (before the first task):** publish `A Prepare batch` (A1 Resolve and freeze task set, A2
|
|
257
|
+
Order by dependencies, A3 Prepare Git, A4 Publish plan) and `Z Batch report`. The task set is
|
|
258
|
+
unknown before A1, so task letters do not exist yet.
|
|
259
|
+
2. **After freeze and ordering (Step 2 complete):** write the frozen ordered list to a temp file as a
|
|
260
|
+
JSON array of `{wbs, name}` and render the waves:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
node "$(superskill script path sp batch-plan.mjs)" waves --tasks <tasks.json>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Publish wave 1's rows — one letter (B…Y) per task with no digit children, then `Z Batch report`;
|
|
267
|
+
a batch past 24 tasks publishes the next wave on rollover (same A/Z bookends).
|
|
268
|
+
3. **When a task starts:** mark its letter in_progress and add its phase digits, rendered from a
|
|
269
|
+
`spur workflow show task-pipeline.yaml --no-logo --format todo --json` payload:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
node "$(superskill script path sp batch-plan.mjs)" task-children --letter <task-letter> --plan <plan.json>
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
4. **When a task ends:** mark the task's letter (and its published digits) with the task's terminal
|
|
276
|
+
outcome — `done` renders completed; `failed`/`skipped`/`blocked` render pending + ` [<outcome>]`
|
|
277
|
+
per the frozen status mapping in
|
|
278
|
+
[inline-pipeline-driver.md](inline-pipeline-driver.md) § Host todo update styles.
|
|
279
|
+
|
|
204
280
|
## Step 3 — The driver loop (R3, R4)
|
|
205
281
|
|
|
206
282
|
```
|
|
207
283
|
plan = resolve(--tasks) → freeze → order(deps) # may abort (cycle) or pre-block (unmet dep)
|
|
208
284
|
report = []
|
|
285
|
+
publish visible plan §2.7: A/Z rows now; one letter per task after this freeze (batch-plan.mjs waves)
|
|
286
|
+
RUN_ID = WT-3 marker id under --worktree, else "batch-$(date -u +%Y%m%dT%H%M%SZ)" # fixed ONCE here; the close-out reuses it
|
|
287
|
+
commit-guard start --run "$RUN_ID" # 1129 R1: fingerprint before the first task writes; the close-out commit stages through it
|
|
209
288
|
for wbs in plan: # default sequential mode
|
|
210
289
|
if any dependency of wbs failed earlier in THIS batch:
|
|
211
290
|
report += skipped(wbs, reason); continue # only relevant under --keep-going
|
|
291
|
+
task check <wbs> --fix; task check <wbs> --precheck # 1132 R4/R5 — repair first, then judge
|
|
292
|
+
if precheck fails on semantic findings:
|
|
293
|
+
if --auto: one /sp:dev-refineall --auto pass; still failing → report += skipped(wbs, findings); continue
|
|
294
|
+
else: HALT (unchanged non-auto behavior)
|
|
212
295
|
preflight = batch-preflight(wbs) # Step 2.6 — TABLE A STOP
|
|
213
296
|
if preflight.action == skip:
|
|
214
297
|
report += preflight-skip(wbs, preflight); continue
|
|
298
|
+
visible plan (§2.7): letter in_progress + task-children digits at start; letter ← outcome at end
|
|
215
299
|
run: if interactive sequential omit/inline:
|
|
216
300
|
inline-pipeline-driver(task-pipeline.yaml, wbs)
|
|
217
301
|
else:
|
|
@@ -233,7 +317,7 @@ for wbs in plan: # default sequential mode
|
|
|
233
317
|
if terminal == failed:
|
|
234
318
|
if --keep-going: mark wbs + in-batch dependents as failed/skipped; continue
|
|
235
319
|
else: HALT; remaining → not-attempted; break # stop-the-batch default (R3.1)
|
|
236
|
-
emit batch report (per-task outcome + preflight skips + recovery hints + batch verdict)
|
|
320
|
+
emit batch report (per-task outcome + preflight skips + recovery hints + autoRepairs + batch verdict)
|
|
237
321
|
```
|
|
238
322
|
|
|
239
323
|
Parallel mode keeps the same lifecycle but swaps the inner loop for the per-task-worktree fan-out
|
|
@@ -332,13 +416,12 @@ node "$(superskill script path sp batch-preflight.mjs)" --wbs <wbs> --status <st
|
|
|
332
416
|
Helper: `recoveryHint(status, wbs)` in `plugins/sp/scripts/batch-preflight.ts`. Tables remain SSOT
|
|
333
417
|
in next-router; this only maps status → primary TABLE A hop for recovery.
|
|
334
418
|
|
|
335
|
-
### 3.3c Feature-sync retry suppression (task 0411; 1004 R3
|
|
419
|
+
### 3.3c Feature-sync retry suppression (task 0411; 1004 R3)
|
|
336
420
|
|
|
337
421
|
During a batch, the per-task `record` step and the wrap-up `feature-transition` step each invoke
|
|
338
422
|
feature status sync. When a feature is L4-gate-blocked (e.g. not all linked tasks are `done`), the
|
|
339
|
-
identical blocked proposal repeats on every call with no intervening input change
|
|
340
|
-
|
|
341
|
-
engine.
|
|
423
|
+
identical blocked proposal repeats on every call with no intervening input change. The service fixes
|
|
424
|
+
this, not the engine.
|
|
342
425
|
|
|
343
426
|
Both `task-pipeline.yaml` (`record` step) and `wrapup-pipeline.yaml` (`feature-transition` step)
|
|
344
427
|
invoke `spur feature sync <feature-id> --json` directly — retry suppression lives inside the
|
|
@@ -419,802 +502,22 @@ derivable from the same dependency graph built in Step 2.
|
|
|
419
502
|
|
|
420
503
|
## Step 5 — Batch report (R5.2)
|
|
421
504
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
(cycle / unknown selector) — emit a structured report. The report is the orchestrator's sole
|
|
425
|
-
output; it does not mutate the corpus (the pipeline's `record` step already wrote per-task
|
|
426
|
-
results). A `paused` task is non-terminal — the report marks it `paused` (never `done`) and names
|
|
427
|
-
the resume command.
|
|
428
|
-
|
|
429
|
-
```
|
|
430
|
-
## Batch Report — <selector>
|
|
431
|
-
|
|
432
|
-
**Selector:** <value>
|
|
433
|
-
**Plan:** <n> tasks (ordered: <wbs-list>) · <m> blocked · <p> not-attempted
|
|
434
|
-
**Mode:** stop-the-batch | --keep-going | --auto
|
|
435
|
-
**Verdict:** clean | halted | aborted
|
|
436
|
-
|
|
437
|
-
| WBS | Status | Reason |
|
|
438
|
-
|-----|--------|--------|
|
|
439
|
-
| 0040 | done | — |
|
|
440
|
-
| 0042 | failed | verify verdict PARTIAL (see .spur/run/0042-verdict.json) |
|
|
441
|
-
| 0050 | not-attempted | batch halted after 0042 (stop-the-batch) |
|
|
442
|
-
| 0051 | skipped | dependency 0040 failed (--keep-going) |
|
|
443
|
-
| 0060 | blocked | unmet out-of-set dep: 0099 is wip |
|
|
444
|
-
|
|
445
|
-
**Next:** <one-line action — pick up halted run / resolve 0099 / all green, feature H1 complete>
|
|
446
|
-
|
|
447
|
-
**Excluded from wrap** (only under `--wrap`/`--next`, when some batch tasks are not `done`):
|
|
448
|
-
|
|
449
|
-
```
|
|
450
|
-
| WBS | Status | Recovery |
|
|
451
|
-
|-----|--------|--------|
|
|
452
|
-
| 0042 | failed | C6 recovery: /sp:dev-run 0042 (residual report: .spur/run/0042-residual-report.md) |
|
|
453
|
-
| 0050 | not-attempted | /sp:dev-next 0050 |
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
A task with a residual report (failing `residual-sweep` check in its verdict artifact) uses the
|
|
457
|
-
C6 recovery line (re-run the pipeline); every other excluded task uses the next-router A-row
|
|
458
|
-
command for its status (F96 task 0952 R2).
|
|
459
|
-
|
|
460
|
-
The per-task outcome vocabulary: `done` | `failed` | `blocked` | `skipped` | `not-attempted`,
|
|
461
|
-
plus the resume-only `recheck` (stale/mismatched evidence — pipeline re-run), `not-admitted`
|
|
462
|
-
(post-freeze selector match, never executed — task 0919 BC-1/BC-2), and the parallel-only
|
|
463
|
-
`integration-conflict` (rebase conflict retained for manual integration — task 0931 R4,
|
|
464
|
-
[§ Parallel isolation](#parallel-isolation---mode-parallel)).
|
|
465
|
-
The batch verdict: `clean` (all attempted tasks `done`) | `halted` (a failure stopped the batch) |
|
|
466
|
-
`aborted` (cycle or selector error before any run).
|
|
467
|
-
|
|
468
|
-
**Zero-task rule (task 0701 R7b).** A selector that resolves to an **empty set after the status
|
|
469
|
-
filter** is an `aborted` verdict (`aborted (empty set after filter)`), matching dev-operations.md
|
|
470
|
-
§5a. Under `--worktree`, **WT-2 is skipped entirely**: no worktree is cut and no WT-3 marker is
|
|
471
|
-
written for a batch with nothing to run. The early-exit report carries zero per-task rows,
|
|
472
|
-
`Steps: 0 derived, 0 executed`, and the `aborted` verdict; no WT-3b commit step and no WT-4/WT-5
|
|
473
|
-
terminal action runs. A contract test pins this
|
|
474
|
-
(`plugins/sp/tests/dogfood-testing/execution-batch-contract.test.ts`).
|
|
475
|
-
|
|
476
|
-
**Evidence persistence (worktree batches — task 0720 R3).** Copy-out is mandatory.
|
|
477
|
-
A worktree batch's Step 5 report, verdict artifacts, and the batch's own
|
|
478
|
-
`.spur/run` records live in the worktree while the batch runs — exactly the tree
|
|
479
|
-
create-mode WT-4 deletes. Before any WT-4 removal, persist them into the **invoking** tree, which
|
|
480
|
-
survives removal:
|
|
481
|
-
|
|
482
|
-
- Write the emitted batch report to `.spur/run/worktree-<marker-id>-batch-report.md`.
|
|
483
|
-
- Copy each attempted task's `.spur/run/<wbs>-verdict.json` from the worktree to
|
|
484
|
-
`.spur/run/worktree-<marker-id>-verdicts/<wbs>-verdict.json`.
|
|
485
|
-
- Make the report's per-task verdict references use those persisted invoking-tree paths, not the
|
|
486
|
-
worktree-local paths that removal deletes.
|
|
487
|
-
|
|
488
|
-
Evidence persistence precedes destructive cleanup: a persistence failure (unreadable verdict file,
|
|
489
|
-
disk-full, missing directory) routes to **WT-5** — the worktree and branch are retained so a green
|
|
490
|
-
batch can never destroy its own evidence. Reuse mode retains its operator-owned tree but still
|
|
491
|
-
persists the Step 5 report under the invoking tree; the reused tree's `.spur/run/` remains the live
|
|
492
|
-
copy while that tree lives on. The per-run provenance — the worktree DB's run/action rows and the
|
|
493
|
-
`.spur/memory/runs/<runId>.md` + `.state.json` records — is persisted mechanically by WT-4a's
|
|
494
|
-
`inline-run-setup.ts --persist-out --from <worktree> [--task-file <merged-task>]...` call,
|
|
495
|
-
not by hand.
|
|
496
|
-
|
|
497
|
-
**Stage records are worktree-local too (0948 R9, E7 Finding 5; persisted by 0975 R1).** Each task's
|
|
498
|
-
own per-stage run record (`.spur/memory/runs/<runId>.md` + `.state.json`) and the worktree DB's run rows are
|
|
499
|
-
written inside the worktree and **are removed with it** in create mode — the E7 batch lost exactly
|
|
500
|
-
this evidence. Copying them out is no longer a manual audit-time duty: WT-4a (create-mode block
|
|
501
|
-
below) runs `inline-run-setup.ts --persist-out --from "$WT_PATH"` **before** WT-4b holder cleanup,
|
|
502
|
-
which copies the run/action/phase/transition/workflow-state rows and both record files into the
|
|
503
|
-
invoking tree.
|
|
504
|
-
|
|
505
|
-
**Cited evidence rides the same call (0984 R1/R2).** The driver forwards each merged task file with
|
|
506
|
-
repeatable `--task-file <path>` (paths resolved in the invoking tree after the FF merge, e.g.
|
|
507
|
-
`spur task show <wbs> --json` → `.filePath`), and persist-out then copies/verifies every literal
|
|
508
|
-
`.spur/run/<file>` that file cites — WBS verdicts, check receipts, test-gate logs, run-ID records —
|
|
509
|
-
so a merged task file never anchors a path that died with the tree. Abbreviated references
|
|
510
|
-
(`fadca099-…`, `run-*-ac87.log`, `{batch-report.md,…}`) are not literal files and carry no
|
|
511
|
-
obligation; neither do root-qualified paths (`knowledge-kit/.spur/run/…`, `/abs/.spur/run/…`),
|
|
512
|
-
which cite another project's evidence — cite foreign run artifacts that way, never bare. A citation missing in BOTH trees, a divergent cited file (never overwritten — reconcile
|
|
513
|
-
by hand), an unreadable task file, or more than 64 distinct cited files fails the pass → WT-5.
|
|
514
|
-
|
|
515
|
-
**Durable planes ride it too (E71).** Persist-out copies canonical task verdicts, feature run/latest receipts, nested run artifacts and owned sessions before teardown. Artifact and task-run-link rows are exported with run rows, and stored path references are redirected to the invoking tree. Conflicting or unreadable retained evidence refuses teardown.
|
|
516
|
-
|
|
517
|
-
**Owned evidence rides it too (1012).** With at least one `--task-file`, persist-out also treats as
|
|
518
|
-
copy obligations the worktree's `.spur/run/` direct children named `<wbs>-…` (the WBS is each
|
|
519
|
-
forwarded task file's leading four digits before `_`) or `<runId>-…` (every run row in the worktree
|
|
520
|
-
DB, whichever task it ran) — `<wbs>-verdict.json`, check receipts, route reasons — whether or not
|
|
521
|
-
the task file cites them. They join the cited set: same copy / byte-identical no-op /
|
|
522
|
-
divergent-refuse handling. The 64-file cap bounds citations alone; owned names are bounded per
|
|
523
|
-
owner (each `<wbs>-` / `<runId>-` prefix gets its own 64-file budget, task 1034), so the bound
|
|
524
|
-
scales with the batch and one runaway owner refuses by name before any write. `<runId>.md` /
|
|
525
|
-
`<runId>.state.json` stay with the record copy (a conflict is reported and the delegate refuses teardown). Files
|
|
526
|
-
matching neither a citation nor an ownership prefix are left behind. An absent worktree `.spur/run/`
|
|
527
|
-
means nothing is owned; any other listing failure (not a directory, permission denied) fails the
|
|
528
|
-
pass before the invoking tree is written → WT-5. Without `--task-file` nothing is enumerated.
|
|
529
|
-
|
|
530
|
-
The shapes are pinned (task 0975 R1; `record-missing` and citation behavior per 0984): idempotent on re-persist;
|
|
531
|
-
success exits 0 printing
|
|
532
|
-
`{"ok":true,"persisted":<n>,"skipped":[{"id":<run-id>,"reason":"id-exists"|"external-key-conflict"|"record-conflict:<file>"|"record-missing:<file>"|"cited-directory:<name>"|"cited-symlink:<name>"|"cited-non-file:<name>"}]}`
|
|
533
|
-
— an `external-key-conflict` skip leaves the target run unchanged **and fails the pass (1049)**: the delegate exits 1 printing `{"ok":false,"error":…}` naming the skipped source run ids, because that (workflow, external key) identity already belongs to a different receiving run — the batch was not persisted, so reconcile the source worktree by hand (it stays the provenance owner of record) before any teardown: auditable reconciliation names the original archived DB snapshot and the skipped source run identities, verifies the archive by file hash and by merged-commit ancestry of the source branch, and treats any residual deletion as explicit operator-authorized cleanup under the strict canonical evidence rules above. An `id-exists` replay repairs missing owned artifacts and task links without duplicating them. A
|
|
534
|
-
`record-conflict:<file>` never overwrites a divergent invoking-tree record and causes the delegate to exit 1, retaining the worktree. A
|
|
535
|
-
`record-missing:<file>` skip is a known `task-lifecycle`/`feature-lifecycle` row with no record file
|
|
536
|
-
at all (its inserted DB row still counts in `persisted` — 0984 R5). Any failure
|
|
537
|
-
exits 1 printing `{"ok":false,"error":<message>}` (a worktree DB run id that is not a single safe
|
|
538
|
-
filename component is rejected before any target write). Any persist-out failure
|
|
539
|
-
routes to **WT-5** — worktree and branch retained — the same copy-out-first contract as the
|
|
540
|
-
verdict persistence above. After a green persist-out, `spur workflow progress --json` in the
|
|
541
|
-
invoking tree shows the merged run `done` with its per-action rows.
|
|
542
|
-
|
|
543
|
-
## Step 6 — Batch wrap (`--wrap` / `--next`) (F96 task 0952 R1)
|
|
544
|
-
|
|
545
|
-
Run **once for the batch**, after the Step 5 report — never per task (dev-operations.md §runall
|
|
546
|
-
agrees; the old "per task" flag-row wording in dev-runall.md was a contradiction, now fixed).
|
|
547
|
-
|
|
548
|
-
The wrap receives only what it would accept:
|
|
549
|
-
|
|
550
|
-
1. `vars.tasks` = the JSON-encoded array of batch WBS whose terminal status is `done`. A task that
|
|
551
|
-
is `failed`/`blocked`/`skipped`/`not-attempted`/`recheck`/`not-admitted` is **excluded** —
|
|
552
|
-
`wrapup task-resolve` refuses non-done members (wrapup-steps.ts task-resolve), so the driver
|
|
553
|
-
simply stops handing the wrap tasks it would refuse.
|
|
554
|
-
2. `vars.feature` = the batch feature **only when every task in the frozen batch is `done` or
|
|
555
|
-
`cancelled`**. Otherwise omit it and print `feature lifecycle not advanced: <n> task(s)
|
|
556
|
-
unfinished` — advancing a feature while some of its batch tasks are unfinished would overstate
|
|
557
|
-
completion. Learnings/metrics capture still happens for the done subset.
|
|
558
|
-
3. When the done subset is **empty**, skip the wrap entirely with the reason (e.g. `batch wrap
|
|
559
|
-
skipped: no done tasks`) instead of invoking wrapup-pipeline on an empty set.
|
|
560
|
-
|
|
561
|
-
**Repo-wide tripwire (1037).** After the doc-sync exits converge, the pipeline's `doc-tripwire` hop
|
|
562
|
-
runs the TRUSTED CONFIG ONLY `docTripwireCmd` over the still-uncommitted wrap diff before
|
|
563
|
-
metrics-record. The default probes `package.json` for a `test-repo-wide` script and runs
|
|
564
|
-
`bun run test-repo-wide` only when it is declared (a no-op in other projects), so the batch driver
|
|
565
|
-
passes no extra vars. Batch callers override it like any wrap var (`docTripwireCmd` in `--vars`);
|
|
566
|
-
an empty string disables the check while still recording PASS. A FAIL routes the wrap to `failed`
|
|
567
|
-
with already-written learnings/docs preserved — fix the flagged working-diff violation and re-run
|
|
568
|
-
the wrap.
|
|
569
|
-
|
|
570
|
-
Filtering lives here, in the batch driver — no change to wrapup-pipeline.yaml or wrapup-steps.ts;
|
|
571
|
-
the wrap's refusal of non-done tasks remains the hard invariant.
|
|
505
|
+
Read [execution-batch-report.md](execution-batch-report.md) at Step 5 — the structured Batch Report
|
|
506
|
+
template, the worktree evidence-persistence rules and Step 6 Batch wrap (`--wrap` / `--next`).
|
|
572
507
|
|
|
573
508
|
## Worktree isolation (`--worktree [<name>]`)
|
|
574
509
|
|
|
575
|
-
|
|
576
|
-
[
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
[§ Parallel isolation](#parallel-isolation---mode-parallel); `--worktree --mode parallel` is
|
|
580
|
-
rejected there (parallel mode already isolates each task in its own worktree).
|
|
581
|
-
|
|
582
|
-
**Startup ordering (task 0814 R3).** Resolve the selector/status filter and run the quick
|
|
583
|
-
command-aware readiness (the `quickReadiness` contract in `batch-preflight.ts`) **before** creating
|
|
584
|
-
or adopting the tree. The admission decision is what determines whether a tree should be cut at all;
|
|
585
|
-
all subsequent tools, agents, task/feature writes, and run artifacts use the confirmed execution
|
|
586
|
-
tree's cwd. A stale or empty selector, an unsupported mode, or an invalid target creates no tree and
|
|
587
|
-
no marker (WT-2/WT-7), and the required Git safety checks (WT-1) still precede creation.
|
|
588
|
-
|
|
589
|
-
> **Command wiring (task 0814 R3).** The four task-set worktree commands (`dev-run`, `dev-runall`,
|
|
590
|
-
> `dev-refineall`, `dev-verifyall`) each call `quickReadiness` with their operation (`run`/`refine`/
|
|
591
|
-
> `verify`), the resolved selector/status, and the filtered-set size **before** WT-1/WT-2. The
|
|
592
|
-
> admission outcome gates the tree: an invalid/empty selector, unsupported mode, or a target that
|
|
593
|
-
> quickReadiness marks `blocked`/`invalid` creates no tree and no marker (WT-2/WT-7); a
|
|
594
|
-
> `needs-refinement` refine batch is still work to do (the tree is created, the gaps are the work).
|
|
595
|
-
> The required Git safety checks (WT-1) still precede creation, and ownership/identity is confirmed
|
|
596
|
-
> before any tool, agent, corpus write, or run artifact. A later failure retains the tree with
|
|
597
|
-
> recovery information (WT-5).
|
|
598
|
-
|
|
599
|
-
**Single-task `dev-run` (batch of one).** `/sp:dev-run <wbs> --worktree [<name>]` runs this same
|
|
600
|
-
lifecycle with a one-task loop: WT-1…WT-6 apply unchanged, the marker's `command` is `dev-run` and
|
|
601
|
-
its `selector` is the `<wbs>` (so WT-6's command+selector fallback resolves the resume), and the
|
|
602
|
-
derived branch/directory slug is the WBS — `sp/run-<wbs>-<short-id>`. The WT-4 success condition
|
|
603
|
-
"no failed task" reads as "the task reached terminal `done` with no failed stage"; a failing gate, a
|
|
604
|
-
non-PASS verify verdict, or a HITL pause that ends the run take the WT-5 retention path. Only the
|
|
605
|
-
full pipeline is eligible — `--worktree --mode implement` is rejected (WT-7), because that mode is
|
|
606
|
-
the pipeline's implement stage and already runs in the driver's tree.
|
|
607
|
-
|
|
608
|
-
**Review triage `dev-review` (run of one).** `/sp:dev-review [--tasks <selector> | --feature <id>[,<id>] | --scope <path>[,<path>]] --triage --worktree [<name>]`
|
|
609
|
-
runs this lifecycle around one review-plus-triage pass: WT-1…WT-6 apply unchanged, the marker's
|
|
610
|
-
`command` is `dev-review` and its `selector` records the full normalized target list, and the slug
|
|
611
|
-
is `sp/review-<first>-and-<N>-<short-id>` for a multi-target run (N = target count) or
|
|
612
|
-
`sp/review-<slug>-<short-id>` for a single target (the WBS or the path's basename). It skips
|
|
613
|
-
`quickReadiness` (there is no task set; admission is "every target resolves" — each WBS/path must
|
|
614
|
-
resolve before the tree is cut). Under `--triage` the findings are bucketed across all targets once
|
|
615
|
-
(identical `file:line` findings deduped). WT-4 success reads as "every direct fix passed its check and the
|
|
616
|
-
project gate is green"; anything else takes WT-5. Contract: [dev-operations.md § 2. review](dev-operations.md#2-review).
|
|
617
|
-
|
|
618
|
-
One flag, two modes (see the glossary entry for the ownership rule). Bare `--worktree` is **create
|
|
619
|
-
mode** (cut a fresh branch + sibling tree). `--worktree <name>` is **reuse mode** (attach to a tree
|
|
620
|
-
that already exists); name resolution (§ WT-2 below) runs before WT-1. The deltas each mode applies
|
|
621
|
-
are stated inline in WT-1…WT-4. `--continue` re-entry (WT-6) resolves by explicit name first, then
|
|
622
|
-
falls back to the command+selector marker scan.
|
|
623
|
-
|
|
624
|
-
The lifecycle wraps Steps 1–5 unchanged: a precheck creates or adopts the worktree before selector
|
|
625
|
-
resolution runs, the loop executes with the worktree as process cwd, and a terminal action merges or
|
|
626
|
-
retains after the batch report is emitted. Steps 1–5 themselves are not modified — only their cwd
|
|
627
|
-
differs.
|
|
628
|
-
|
|
629
|
-
**Per-call tree pinning (task 1058).** A host shell call starts in an arbitrary directory and
|
|
630
|
-
inherits no cwd from the previous call, so "the loop runs with the worktree as process cwd" is a
|
|
631
|
-
bootstrap convenience, never a carried guarantee. Every host command boundary in this lifecycle —
|
|
632
|
-
corpus reads/writes, the WT-3b commit, WT-4's merge and evidence persistence, the WT-4a
|
|
633
|
-
persist-out — re-selects its intended tree within the same call via the canonical per-call pin
|
|
634
|
-
protocol ([inline-pipeline-driver.md](inline-pipeline-driver.md) § "Per-call execution-tree
|
|
635
|
-
pin"): `cd --` to the quoted absolute tree, verify `pwd -P` against the recorded physical tree,
|
|
636
|
-
verify Git top-level and expected branch, then execute the already-resolved Spur invocation. The
|
|
637
|
-
identity is consumed, not re-derived: it is the WT-3 marker's confirmed `path` + `branch` (or the
|
|
638
|
-
resolved worktree's physical path + checked-out branch in reuse mode). The one deliberate tree
|
|
639
|
-
change is WT-4: the terminal merge actions select the **invoking** tree, not the execution tree —
|
|
640
|
-
and pin it explicitly the same way. FF ancestry, marker and cleanup sequencing remain owned by
|
|
641
|
-
1059.
|
|
642
|
-
|
|
643
|
-
**Portability (R10).** Use portable `git worktree` commands only. Do **not** depend on the Claude
|
|
644
|
-
Code `EnterWorktree`/`ExitWorktree` tools — the `sp` plugin ships to Codex, Gemini CLI, pi, omp, and
|
|
645
|
-
OpenCode. The underlying git mechanics (create / list / remove / prune, sibling-directory naming,
|
|
646
|
-
disk-space awareness) are reused from [worktree-patterns.md](../../branch-workflow/references/worktree-patterns.md);
|
|
647
|
-
this section does not re-author them.
|
|
648
|
-
|
|
649
|
-
### WT-1 — Dirty-tree precheck (R3)
|
|
650
|
-
|
|
651
|
-
`git worktree add` branches from a ref, so uncommitted changes in the main tree do **not** carry
|
|
652
|
-
into the worktree — a batch would silently run against different tree state than the operator sees.
|
|
653
|
-
Before creating the worktree, check the main tree:
|
|
654
|
-
|
|
655
|
-
```bash
|
|
656
|
-
git status --porcelain
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
- **Clean tree** → proceed to WT-2.
|
|
660
|
-
- **Dirty tree** → **abort** before any worktree is created. Name the offending files (from
|
|
661
|
-
`git status --porcelain`) and instruct the operator to commit or stash. No task work has run.
|
|
662
|
-
- **`--force`** → proceed past a dirty tree with a divergence warning that names the uncommitted
|
|
663
|
-
files. The worktree is created and the batch proceeds against the committed base ref, not the
|
|
664
|
-
operator's working-directory state.
|
|
665
|
-
|
|
666
|
-
**Reuse-mode delta.** The main-tree precheck is unchanged — the divergence hazard is identical (the
|
|
667
|
-
batch still runs somewhere the operator is not standing). The *target* worktree being dirty is
|
|
668
|
-
**expected** (it holds retained partial work from a prior halt) and must **not** abort: report the
|
|
669
|
-
file list once and proceed. The batch runs against the target worktree's working-directory state, not
|
|
670
|
-
a clean checkout — which is exactly the point of reuse mode.
|
|
671
|
-
|
|
672
|
-
### WT-2 — Worktree creation or adoption
|
|
673
|
-
|
|
674
|
-
**Name resolution runs first** for both modes — bare `--worktree` skips it (no name to resolve);
|
|
675
|
-
`--worktree <name>` must resolve before WT-1's precheck touches anything. Resolution is specified
|
|
676
|
-
in [§ Name resolution](#name-resolution---worktree-name) below; this section covers creation
|
|
677
|
-
(create mode) and adoption (reuse mode).
|
|
678
|
-
|
|
679
|
-
#### Create mode (bare `--worktree`)
|
|
680
|
-
|
|
681
|
-
Create one worktree on a new branch cut from the current HEAD's ref (the **base ref** — often a
|
|
682
|
-
`feat/…` branch, not literally `main`). Location follows the sibling-directory convention in
|
|
683
|
-
[worktree-patterns.md](../../branch-workflow/references/worktree-patterns.md):
|
|
684
|
-
|
|
685
|
-
**Location rule (0948 R9) — never under `.spur/`.** The default root is the **sibling** directory
|
|
686
|
-
(`../<repo>-<command>-<selector-slug>-<short-id>`). A worktree nested under `.spur/` breaks Biome's
|
|
687
|
-
vcs-root detection: `bunx biome check .` inside it reports `Checked 0 files`, so lint/format silently
|
|
688
|
-
no-op for the whole run. Verified in both directions — a sibling worktree reports a non-zero file
|
|
689
|
-
count (`Checked 1076 files` at the time of the fix) while a `.spur/`-nested one reports
|
|
690
|
-
`Checked 0 files`. If a project's tooling needs a custom root, keep it **outside** any path that a
|
|
691
|
-
VCS-root-detecting tool treats as ignorable.
|
|
692
|
-
|
|
693
|
-
```bash
|
|
694
|
-
BASE_REF=$(git rev-parse --abbrev-ref HEAD)
|
|
695
|
-
BASE_SHA=$(git rev-parse HEAD)
|
|
696
|
-
BRANCH="sp/<command>-<selector-slug>-<short-id>" # e.g. sp/runall-h1-a3f2
|
|
697
|
-
# `git worktree add -b` creates the branch BEFORE the directory, so a failed create leaves a
|
|
698
|
-
# dangling branch and the natural retry dies on "a branch named ... already exists"
|
|
699
|
-
# (task 0701 R2b). Wrap the create: on failure, delete the branch — or derive a fresh
|
|
700
|
-
# short-id per attempt — before surfacing the error.
|
|
701
|
-
git worktree add "../<repo>-<command>-<selector-slug>-<short-id>" -b "$BRANCH" "$BASE_REF" \
|
|
702
|
-
|| { git branch -D "$BRANCH"; false; }
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
**Worktree root is outside `.spur/` (0948 R9).** The default create path is that
|
|
706
|
-
sibling directory, which sits next to the repository and not under it. Do not
|
|
707
|
-
put the default root at `.spur/worktrees` or any other gitignored path: Biome's
|
|
708
|
-
`vcs.useIgnoreFile` then treats the checkout as empty ("Checked 0 files") and
|
|
709
|
-
the quality gate cannot see the tree. Same class as the eval-pipeline worktree
|
|
710
|
-
move off `.spur/tmp/` (task 0610).
|
|
711
|
-
|
|
712
|
-
Branch and directory names are derived (command + selector slug + short id); the create path never
|
|
713
|
-
takes an operator-supplied name (R8.3 — no create-with-name; `--worktree <name>` where `<name>` does
|
|
714
|
-
not resolve is an error, not a create).
|
|
715
|
-
|
|
716
|
-
A fresh worktree has no `node_modules` (gitignored), so the first `bun test` or
|
|
717
|
-
typecheck fails on the first workspace import. Install before any task work:
|
|
718
|
-
|
|
719
|
-
cd "../<worktree-dir>" && bun install --frozen-lockfile --ignore-scripts
|
|
720
|
-
|
|
721
|
-
`--frozen-lockfile` pins the worktree to `bun.lock` rather than re-resolving,
|
|
722
|
-
so the worktree's dependency tree matches the base ref's. `--ignore-scripts` is required, not
|
|
723
|
-
stylistic (task 0701 R2a): worktrees share the main tree's `.git`, and this repo's `prepare`
|
|
724
|
-
script is `lefthook install` (`package.json`) — a bare install rewrites the operator's
|
|
725
|
-
main-repo hooks from inside the "isolated" tree. Scripts are skipped only at this call site;
|
|
726
|
-
a normal clone keeps `prepare`. The worktree still gets a usable dependency tree — the install
|
|
727
|
-
exists so the first `bun test` resolves workspace imports.
|
|
728
|
-
|
|
729
|
-
#### Reuse mode (`--worktree <name>`)
|
|
730
|
-
|
|
731
|
-
Creation is **skipped entirely**. `$BRANCH` is the resolved worktree's already-checked-out branch; a
|
|
732
|
-
detached HEAD aborts (no branch can serve as `$BRANCH` for WT-4's FF-merge). `BASE_REF` is the
|
|
733
|
-
invoking tree's current HEAD ref (not the worktree's branch) and `BASE_SHA` is
|
|
734
|
-
`git merge-base <BASE_REF> <BRANCH>` — so WT-4's FF-merge lands the worktree's accumulated commits
|
|
735
|
-
onto the invoking tree's base ref, exactly as create mode does.
|
|
736
|
-
|
|
737
|
-
`bun install --frozen-lockfile --ignore-scripts` runs **only when `node_modules` is absent** in
|
|
738
|
-
the resolved worktree (same `--ignore-scripts` rationale as create mode — task 0701 R2a). A warm reused tree does not re-pay the install; a cold one (hand-made, or a retained tree
|
|
739
|
-
whose deps were removed) installs exactly once before the first task. This is the R3 conditional
|
|
740
|
-
install rule (source: task 0481) — create mode always installs because a fresh tree is always cold.
|
|
741
|
-
|
|
742
|
-
After creation or adoption, immediately write/adopt the state marker (WT-3), then run the
|
|
743
|
-
existing batch loop (Steps 1–5) with the worktree as process cwd. `spur workflow run` resolves cwd
|
|
744
|
-
from the process (`apps/cli/src/commands/workflow.ts:124`), so no CLI change is needed — `cd` into
|
|
745
|
-
the worktree directory before launching the loop.
|
|
746
|
-
|
|
747
|
-
#### Name resolution (`--worktree <name>`)
|
|
748
|
-
|
|
749
|
-
<a id="name-resolution---worktree-name"></a>
|
|
750
|
-
|
|
751
|
-
Authority: **git**, not the marker store — a marker may be stale, git is not. A foreign worktree
|
|
752
|
-
with no marker is a valid target (R3 synthesizes one). Resolve `<name>` against
|
|
753
|
-
`git worktree list --porcelain`, matching in this order and stopping at the first tier that yields
|
|
754
|
-
≥1 hit:
|
|
755
|
-
|
|
756
|
-
1. **exact `worktree <path>`** (after path normalization against the invoking tree)
|
|
757
|
-
2. **`basename(<path>)`**
|
|
758
|
-
3. **checked-out branch** — accept both `<name>` and the full `refs/heads/<name>` form
|
|
759
|
-
|
|
760
|
-
Then require exactly one survivor across the tiers:
|
|
761
|
-
|
|
762
|
-
- **0 hits** → abort before any task work. Print each candidate worktree as
|
|
763
|
-
`<basename> <branch> <path>`, and the line: *"`--worktree <name>` selects an existing worktree;
|
|
764
|
-
it never creates one. Use bare `--worktree` to create."*
|
|
765
|
-
- **≥2 hits** → abort naming the candidates and require the path form (tier 1).
|
|
766
|
-
- **1 hit, but** the worktree is `locked`, `prunable`, or belongs to a different repo → abort naming
|
|
767
|
-
the condition.
|
|
768
|
-
|
|
769
|
-
### `spur` on PATH is not this checkout
|
|
770
|
-
|
|
771
|
-
`spur` (`~/.bun/bin/spur`) resolves to a *published* bundle in `~/node_modules/`,
|
|
772
|
-
not to the repo you are standing in and not to the worktree. `resolveSpurBin()`
|
|
773
|
-
propagates whichever binary you entered through into `vars.spurBin`, which the
|
|
774
|
-
`task-lifecycle.yaml` guards run as `$spurBin task check` — so one wrong entry
|
|
775
|
-
point silently gate-checks against the published bundle.
|
|
776
|
-
|
|
777
|
-
Inside a worktree, and in the monorepo whenever CLI behavior is under test, invoke
|
|
778
|
-
the tree's own source:
|
|
779
|
-
|
|
780
|
-
cd "<worktree>" && bun apps/cli/src/index.ts task check <wbs> --json
|
|
781
|
-
|
|
782
|
-
Run that as a per-call pin subshell (task 1058) — `cd --` plus the `pwd -P` / git-top-level /
|
|
783
|
-
branch identity checks of the canonical protocol (inline-pipeline-driver.md § "Per-call
|
|
784
|
-
execution-tree pin") — rather than relying on a persistent `cd` between host tool calls.
|
|
785
|
-
|
|
786
|
-
Confirm isolation by making a distinctive change in the worktree and checking that
|
|
787
|
-
the command reflects it.
|
|
788
|
-
|
|
789
|
-
**General rule — every path-resolving tool, not just `spur`.** The same failure class is not
|
|
790
|
-
limited to the CLI on PATH. A host-agent file-edit or hash/`hashline` tool may resolve main-repo
|
|
791
|
-
paths while the shell `cwd` is the worktree, silently acting on the wrong tree. Before relying on any
|
|
792
|
-
path-resolving tool inside a `--worktree` batch, verify it is acting on the worktree — for example
|
|
793
|
-
by making a distinctive change and confirming the path the tool reports matches the worktree. When a
|
|
794
|
-
tool cannot be pointed at the worktree, fall back to `perl -i` in-place edits (or the agent's `write`
|
|
795
|
-
verb) whose path argument you control.
|
|
796
|
-
|
|
797
|
-
### WT-3 — Crash-safe state marker (R6)
|
|
798
|
-
|
|
799
|
-
Worktree identity lives on disk under `.spur/run/`, not only in the orchestrator's memory, so a
|
|
800
|
-
session that dies mid-batch is recoverable. Write the marker at creation and update it at the
|
|
801
|
-
terminal transition (merged / retained). The marker is written to the **invoking** tree's
|
|
802
|
-
`.spur/run/` (task 0701 R2c) — the tree where the driver process started, not the worktree's own
|
|
803
|
-
`.spur/run/` — so WT-6's resume scan finds it regardless of where the operator stands. Schema:
|
|
804
|
-
|
|
805
|
-
```json
|
|
806
|
-
{
|
|
807
|
-
"id": "<marker-id>",
|
|
808
|
-
"path": "../<repo>-<command>-<selector-slug>-<short-id>",
|
|
809
|
-
"branch": "sp/<command>-<selector-slug>-<short-id>",
|
|
810
|
-
"baseRef": "feat/example",
|
|
811
|
-
"baseSha": "<sha-at-creation>",
|
|
812
|
-
"command": "dev-runall",
|
|
813
|
-
"selector": "feature:H1",
|
|
814
|
-
"createdAt": "<iso-8601>",
|
|
815
|
-
"status": "active"
|
|
816
|
-
}
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
`status` transitions: `active` → `merged` (WT-4 success) | `retained` (WT-5 failure/halt/non-FF),
|
|
820
|
-
and `retained` → `active` on a reuse re-entry (WT-6). The marker file is named
|
|
821
|
-
`.spur/run/worktree-<marker-id>.json`. It is the authority for WT-6 resume and for operator recovery
|
|
822
|
-
after a crash: a killed session leaves the marker at `status: active`, which the operator reads to
|
|
823
|
-
find the worktree path, branch, and base ref.
|
|
824
|
-
|
|
825
|
-
**Reuse-mode marker adoption.** Two new optional fields record when a run did not create the tree:
|
|
826
|
-
|
|
827
|
-
```json
|
|
828
|
-
{
|
|
829
|
-
"adopted": true,
|
|
830
|
-
"adoptedAt": "<iso-8601>"
|
|
831
|
-
}
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
`adopted` is what WT-4 reads to decide retain-vs-remove, so it is set whenever the current run did
|
|
835
|
-
not create the tree — including when reuse mode adopts a marker that create mode originally wrote.
|
|
836
|
-
It records "this run did not create this tree", not "this tree was never created by the flag".
|
|
837
|
-
|
|
838
|
-
Reuse mode resolves the marker by the resolved worktree's `path` (not by `command`+`selector`):
|
|
839
|
-
|
|
840
|
-
- **Existing marker for that path** → adopt it in place: update `command`/`selector` to the current
|
|
841
|
-
invocation, set `status` to `active`, set `adopted: true` + `adoptedAt`, **preserve `baseRef` and
|
|
842
|
-
`baseSha`**. This makes cross-command resume — `dev-runall` halted, now `dev-verifyall` over the
|
|
843
|
-
same tree — a supported path (R5.2).
|
|
844
|
-
- **No marker** (hand-made or foreign worktree) → synthesize one with `baseRef` = the invoking
|
|
845
|
-
tree's current HEAD ref, `baseSha` = `git merge-base <baseRef> <BRANCH>`, `adopted: true`.
|
|
846
|
-
- **Marker already at `status: active`** → **abort** — another session may own that tree
|
|
847
|
-
(AGENTS.md one-writer-per-tree; task 0487 R5). Overridable with `--force` (the operator can tell
|
|
848
|
-
a crashed-session marker from a live-session one; the harness cannot).
|
|
849
|
-
|
|
850
|
-
### WT-3b — Commit the batch's writes on `$BRANCH` (task 0701 R1)
|
|
851
|
-
|
|
852
|
-
Before any terminal action, commit the batch's corpus writes **on `$BRANCH`, inside the
|
|
853
|
-
worktree** — including the generated task files under `docs/tasks*/` and the kanban index. The
|
|
854
|
-
commit runs as a per-call pinned subshell (task 1058): it selects the execution tree inside the
|
|
855
|
-
same call, verifies the WT-3 identity, and never relies on a persistent `cd` or a trailing
|
|
856
|
-
`cd -` to restore the caller's directory:
|
|
857
|
-
|
|
858
|
-
```bash
|
|
859
|
-
(
|
|
860
|
-
cd -- "../<worktree-dir>" || { echo "tree missing: expected ../<worktree-dir>" >&2; exit 1; }
|
|
861
|
-
[ "$(git branch --show-current)" = "$BRANCH" ] \
|
|
862
|
-
|| { echo "branch mismatch: expected $BRANCH, got $(git branch --show-current)" >&2; exit 1; }
|
|
863
|
-
git add <files-the-batch-wrote>
|
|
864
|
-
git commit -m "<type>(<scope>): <command> <selector> batch writes"
|
|
865
|
-
)
|
|
866
|
-
```
|
|
867
|
-
|
|
868
|
-
The FF-only git merge carries only commits — uncommitted writes in the worktree would be left
|
|
869
|
-
behind by the merge and then destroyed by create mode's `git worktree remove`. WT-3b exists so
|
|
870
|
-
that can never happen.
|
|
871
|
-
|
|
872
|
-
### WT-4 — Success path (R4)
|
|
873
|
-
|
|
874
|
-
When the batch completes with **no failed task**, fast-forward-merge the worktree branch onto the
|
|
875
|
-
base ref. The terminal action after the merge differs by mode (ownership rule: *the flag removes
|
|
876
|
-
only what it created*):
|
|
877
|
-
|
|
878
|
-
#### Create mode — merge, remove, delete
|
|
879
|
-
|
|
880
|
-
```bash
|
|
881
|
-
# Run these from the main tree (not inside the worktree) - you merge the worktree branch
|
|
882
|
-
# back onto the base ref there. Per-call tree pinning (task 1058, § above): every command
|
|
883
|
-
# below selects the INVOKING tree (where the WT-3 marker lives), re-pinned with the canonical
|
|
884
|
-
# per-call pin protocol in its own subshell — never the execution tree. The whole sequence is
|
|
885
|
-
# fail-stop: checkout, guard, ancestry check, merge, landed verify, persistence, and cleanup
|
|
886
|
-
# each stop explicitly. Every tip/ancestry read uses local refs — no fetch runs anywhere:
|
|
887
|
-
MARKER=".spur/run/worktree-<marker-id>.json"
|
|
888
|
-
git checkout "$BASE_REF" \
|
|
889
|
-
|| { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
890
|
-
# Guard (task 0701 R1): a zero-commit branch makes the FF-only git merge exit 0
|
|
891
|
-
# ("Already up to date") while merging nothing — the lines below would then delete
|
|
892
|
-
# the worktree holding the only copy of the batch's writes. Refuse instead:
|
|
893
|
-
[ "$(git rev-list --count "$BASE_REF..$BRANCH")" -gt 0 ] \
|
|
894
|
-
|| { echo "WT-4 halt: branch carries no commits - nothing to merge" >&2; exit 1; } # -> WT-5, marker stays "active"
|
|
895
|
-
# Capture the batch tip BEFORE any cleanup or branch work: every later step (landed verify,
|
|
896
|
-
# mergeCommit, retained report) reads this captured value — a removed branch is never queried.
|
|
897
|
-
BATCH_TIP="$(git rev-parse "$BRANCH")" \
|
|
898
|
-
|| { echo "WT-4 halt: cannot read $BRANCH" >&2; exit 1; }
|
|
899
|
-
# Fresh LOCAL base tip, re-read now; the marker's baseSha may be stale and never governs this
|
|
900
|
-
# decision (an advanced-but-ancestor base still FF-merges, a divergent base halts):
|
|
901
|
-
BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
902
|
-
|| { echo "WT-4 halt: cannot read $BASE_REF" >&2; exit 1; }
|
|
903
|
-
# Ancestry check immediately before the sole mutation (the FF merge below). Command/read
|
|
904
|
-
# errors are distinguished from ordinary non-ancestry; both fail closed:
|
|
905
|
-
ANCESTRY_RC=0
|
|
906
|
-
git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
907
|
-
[ "$ANCESTRY_RC" -gt 1 ] \
|
|
908
|
-
&& { echo "WT-4 halt: ancestry check could not run (git exit $ANCESTRY_RC) - failing closed" >&2; exit 1; }
|
|
909
|
-
[ "$ANCESTRY_RC" -eq 1 ] && {
|
|
910
|
-
echo "WT-4 halt: divergent - batch tip $BATCH_TIP vs base tip $BASE_TIP" >&2
|
|
911
|
-
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2 # batch-side divergent commits
|
|
912
|
-
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2 # base-side divergent commits
|
|
913
|
-
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
914
|
-
git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
915
|
-
[ $? -eq 0 ] \
|
|
916
|
-
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
917
|
-
# Landed verification before anything else runs: the captured BATCH_TIP must be an ancestor of
|
|
918
|
-
# the base ref as it now stands (base tips advancing again later stay valid — this binds the
|
|
919
|
-
# merge result, not the end state):
|
|
920
|
-
LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
921
|
-
|| { echo "WT-4 halt: cannot read landed base tip" >&2; exit 1; }
|
|
922
|
-
git merge-base --is-ancestor "$BATCH_TIP" "$LANDED_BASE_TIP" \
|
|
923
|
-
|| { echo "WT-4 halt: landed verify failed - $BATCH_TIP is not an ancestor of $LANDED_BASE_TIP" >&2; exit 1; }
|
|
924
|
-
# --- Landed from here on. Any halt below is landed-but-incomplete: it records status
|
|
925
|
-
# --- "retained" + mergeCommit "$BATCH_TIP" via write_marker and stops — never a branch query.
|
|
926
|
-
write_marker () { # $1 = merged | retained; read-modify-write: preserve WT-3 fields (id, command, selector, createdAt, adopted, adoptedAt); jq already assumed by WT-3/task-resolution flows
|
|
927
|
-
mkdir -p "$(dirname "$MARKER")"
|
|
928
|
-
if [ -f "$MARKER" ]; then
|
|
929
|
-
TMP_MARKER="$(mktemp)"
|
|
930
|
-
jq --arg s "$1" --arg m "$BATCH_TIP" '.status = $s | .mergeCommit = $m' "$MARKER" > "$TMP_MARKER" \
|
|
931
|
-
|| { echo "WT-4 halt: cannot rewrite marker $MARKER" >&2; rm -f "$TMP_MARKER"; exit 1; }
|
|
932
|
-
mv "$TMP_MARKER" "$MARKER"
|
|
933
|
-
else
|
|
934
|
-
printf '{ "baseRef": "%s", "baseSha": "%s", "branch": "%s", "path": "%s", "status": "%s", "mergeCommit": "%s" }\n' \
|
|
935
|
-
"$BASE_REF" "$BASE_SHA" "$BRANCH" "$WT_PATH" "$1" "$BATCH_TIP" > "$MARKER"
|
|
936
|
-
fi
|
|
937
|
-
}
|
|
938
|
-
# WT-4a evidence persistence runs FIRST after the landed verify (Step 5, task 0720 R3):
|
|
939
|
-
# persist the batch report + verdict artifacts AND the per-run provenance (task
|
|
940
|
-
# 0975 R1) into the invoking tree before anything below touches the worktree.
|
|
941
|
-
# Any persistence failure routes to WT-5 — landed-but-incomplete: retained marker + mergeCommit.
|
|
942
|
-
WT_PATH="$(cd "../<worktree-dir>" && pwd)" # hoisted: needed by WT-4a AND WT-4b below
|
|
943
|
-
# WT-4a provenance persist-out (task 0975 R1): copy the worktree DB's run rows plus
|
|
944
|
-
# the .spur/memory/runs/<runId>.md + .state.json records into THIS tree. Run from the main
|
|
945
|
-
# tree (cwd = the invoking tree). --task-file (0984 R2) forwards each merged task
|
|
946
|
-
# file (post-merge path) so the cited .spur/run/<file> evidence is copied/verified
|
|
947
|
-
# too. Resolve the merged path(s) BEFORE this block — an empty value exits 2:
|
|
948
|
-
# TASK_FILE="$(spur task show <wbs> --json | jq -r .filePath)" # per done task;
|
|
949
|
-
# build TASK_FILE_ARGS=(--task-file "$TASK_FILE") # one flag each
|
|
950
|
-
# Idempotent; conflicts are reported, never overwritten. A non-zero exit —
|
|
951
|
-
# including an unresolved or divergent citation, or a half-readable worktree — must
|
|
952
|
-
# NOT proceed to WT-4b removal:
|
|
953
|
-
SETUP_SCRIPT=plugins/sp/scripts/inline-run-setup.ts; [ -f config/plugin-scripts.json -a -f "$SETUP_SCRIPT" ] || SETUP_SCRIPT="$(superskill script path sp inline-run-setup.mjs 2>/dev/null)"
|
|
954
|
-
bun "$SETUP_SCRIPT" --persist-out --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
|
|
955
|
-
|| { echo "halt: worktree run-record persist-out failed - worktree retained (WT-5)" >&2; write_marker retained; exit 1; }
|
|
956
|
-
#
|
|
957
|
-
# WT-4b — bounded CWD-holder cleanup (task 0720 R1). $WT_PATH above is the EXACT
|
|
958
|
-
# absolute worktree path; a relative path or a stale entry matches the wrong processes.
|
|
959
|
-
# Holders = processes with any open fd under the worktree tree (lsof +D walks the
|
|
960
|
-
# tree; CWD holders are the common case but +D also catches open-file holders —
|
|
961
|
-
# over-match errs toward removal success; a plain -t <dir> matches only the
|
|
962
|
-
# directory itself). Orphaned `serve` proof daemons (PPID 1) are exactly this
|
|
963
|
-
# class: they defeat `git worktree remove` (ENOTEMPTY), defeat rm -rf, while
|
|
964
|
-
# `git worktree prune` still deregisters the tree. Note +D is a full-tree walk,
|
|
965
|
-
# so the wait loop below bounds ITERATIONS (6 × 1s ticks + walk cost), not
|
|
966
|
-
# wall-clock.
|
|
967
|
-
HOLDERS="$(lsof -t +D "$WT_PATH" 2>/dev/null | sort -u)"
|
|
968
|
-
if [ -n "$HOLDERS" ]; then
|
|
969
|
-
kill -TERM $HOLDERS 2>/dev/null # 1) TERM first, all holders (unquoted — word-split PID list)
|
|
970
|
-
for _ in 1 2 3 4 5 6; do # 2) bounded wait: 6 × 1s ticks
|
|
971
|
-
sleep 1
|
|
972
|
-
[ -z "$(lsof -t +D "$WT_PATH" 2>/dev/null)" ] && break
|
|
973
|
-
done
|
|
974
|
-
SURVIVORS="$(lsof -t +D "$WT_PATH" 2>/dev/null | sort -u)"
|
|
975
|
-
if [ -n "$SURVIVORS" ]; then
|
|
976
|
-
kill -KILL $SURVIVORS 2>/dev/null # 3) KILL only the survivors (unquoted — one arg per PID)
|
|
977
|
-
sleep 1
|
|
978
|
-
fi
|
|
979
|
-
fi
|
|
980
|
-
# 4) Re-query: only an EMPTY holder set may proceed to remove/prune/branch delete.
|
|
981
|
-
FINAL="$(lsof -t +D "$WT_PATH" 2>/dev/null | sort -u)"
|
|
982
|
-
if [ -n "$FINAL" ]; then
|
|
983
|
-
PORTS="$(lsof -nP -a -p "$(echo "$FINAL" | paste -sd, -)" -iTCP -sTCP:LISTEN 2>/dev/null \
|
|
984
|
-
| awk 'NR>1 {print $9}' | sort -u | paste -sd' ' -)"
|
|
985
|
-
echo "halt: worktree still held by PID(s): $FINAL ${PORTS:+listening: $PORTS}" >&2
|
|
986
|
-
write_marker retained # landed-but-incomplete: retained + mergeCommit
|
|
987
|
-
exit 1 # -> WT-5: retain worktree + branch,
|
|
988
|
-
fi # NO prune/remove/branch delete
|
|
989
|
-
# Pre-removal evidence assertion (task 1067 R1, E71 enforcement): refuse to remove the
|
|
990
|
-
# worktree while it still owns evidence absent from (or divergent from) the invoking tree —
|
|
991
|
-
# the D63 landing skipped persist-out and silently lost verdicts + receipts. Task-file
|
|
992
|
-
# WBSes / run ids ride the same TASK_FILE_ARGS resolved for WT-4a (run ids: --run-id <id>).
|
|
993
|
-
CHECK_SCRIPT=plugins/sp/scripts/persist-out-check.ts; [ -f config/plugin-scripts.json -a -f "$CHECK_SCRIPT" ] || CHECK_SCRIPT="$(superskill script path sp persist-out-check.mjs 2>/dev/null)"
|
|
994
|
-
bun "$CHECK_SCRIPT" --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
|
|
995
|
-
|| { echo "WT-4 halt: worktree evidence would be abandoned - persist-out missing or stale (WT-5)" >&2; write_marker retained; exit 1; }
|
|
996
|
-
git worktree remove "../<worktree-dir>" \
|
|
997
|
-
|| { echo "WT-4 halt: worktree remove failed after landed merge $BATCH_TIP" >&2; write_marker retained; exit 1; }
|
|
998
|
-
# Branch deletion runs only after the merge landed, persisted, and the worktree is gone —
|
|
999
|
-
# and it is existence-guarded, so a removed branch is never queried. -D over -d: the landed
|
|
1000
|
-
# verify already proved the merge landed, so an unmerged-refusal would only block proven-merged cleanup.
|
|
1001
|
-
if git rev-parse --verify --quiet "$BRANCH" >/dev/null; then
|
|
1002
|
-
git branch -D "$BRANCH" || { echo "WT-4 halt: cannot delete branch $BRANCH after landed merge $BATCH_TIP" >&2; write_marker retained; exit 1; }
|
|
1003
|
-
fi
|
|
1004
|
-
# WT-4c — clean up registry entry in ~/.config/spur/projects.json (task 0924)
|
|
1005
|
-
spur projects remove "$WT_PATH" 2>/dev/null || spur projects clean --json 2>/dev/null || true
|
|
1006
|
-
# Success marker, written once and only here: status "merged" + mergeCommit "$BATCH_TIP" —
|
|
1007
|
-
# after landed verification, required persistence, and cleanup:
|
|
1008
|
-
write_marker merged
|
|
1009
|
-
```
|
|
1010
|
-
|
|
1011
|
-
Every halt above stops explicitly and falls through to **WT-5** — zero-commit branch (task 0701
|
|
1012
|
-
R1), failed checkout, divergent base, failed FF, persist-out failure (task 0975), or surviving CWD
|
|
1013
|
-
holders (task 0720 R1). Pre-merge failures leave the marker at `status: active` and the worktree +
|
|
1014
|
-
branch untouched. Landed-but-incomplete failures record `status: retained` + `mergeCommit`
|
|
1015
|
-
BATCH_TIP: the merge landed, and the retained report reads the captured tip only. While any holder
|
|
1016
|
-
remains, do **not** run `git worktree prune`, `git worktree remove`, or branch deletion — only an
|
|
1017
|
-
EMPTY holder set may proceed to those steps. The holder
|
|
1018
|
-
halt report names every surviving PID; the listening port is best-effort — a CWD holder may own no
|
|
1019
|
-
socket, and `lsof` port discovery failing must not hide the PIDs.
|
|
1020
|
-
|
|
1021
|
-
#### Reuse mode — merge, retain
|
|
1022
|
-
|
|
1023
|
-
The fail-stop merge sequence runs identically — same guard, captured `BATCH_TIP`/`BASE_TIP`,
|
|
1024
|
-
ancestry check immediately before FF, `git merge --ff-only`, landed verification (per-call pinned
|
|
1025
|
-
to the invoking tree, task 1058) — but no WT-4a/4b/4c and no removal: the worktree and branch are
|
|
1026
|
-
**retained**. Pre-merge halts leave the marker `active`; a persistence failure after the landed
|
|
1027
|
-
merge records `retained` + `mergeCommit`:
|
|
1028
|
-
|
|
1029
|
-
```bash
|
|
1030
|
-
# Same fail-stop sequence as create mode (zero-commit guard -> BATCH_TIP/BASE_TIP -> ancestry
|
|
1031
|
-
# rc check -> git merge --ff-only -> landed verify); local refs only — no fetch.
|
|
1032
|
-
MARKER=".spur/run/worktree-<marker-id>.json"
|
|
1033
|
-
git checkout "$BASE_REF" \
|
|
1034
|
-
|| { echo "WT-4 halt: checkout $BASE_REF failed - nothing merged, marker stays \"active\"" >&2; exit 1; } # -> WT-5
|
|
1035
|
-
[ "$(git rev-list --count "$BASE_REF..$BRANCH")" -gt 0 ] \
|
|
1036
|
-
|| { echo "WT-4 halt: branch carries no commits - nothing to merge" >&2; exit 1; }
|
|
1037
|
-
BATCH_TIP="$(git rev-parse "$BRANCH")" || { echo "WT-4 halt: cannot read $BRANCH" >&2; exit 1; }
|
|
1038
|
-
BASE_TIP="$(git rev-parse "$BASE_REF")" || { echo "WT-4 halt: cannot read $BASE_REF" >&2; exit 1; }
|
|
1039
|
-
ANCESTRY_RC=0
|
|
1040
|
-
git merge-base --is-ancestor "$BASE_TIP" "$BATCH_TIP" || ANCESTRY_RC=$?
|
|
1041
|
-
[ "$ANCESTRY_RC" -gt 1 ] \
|
|
1042
|
-
&& { echo "WT-4 halt: ancestry check could not run (git exit $ANCESTRY_RC) - failing closed" >&2; exit 1; }
|
|
1043
|
-
[ "$ANCESTRY_RC" -eq 1 ] && {
|
|
1044
|
-
echo "WT-4 halt: divergent - batch tip $BATCH_TIP vs base tip $BASE_TIP" >&2
|
|
1045
|
-
git log --oneline "$BASE_TIP..$BATCH_TIP" >&2
|
|
1046
|
-
git log --oneline "$BATCH_TIP..$BASE_TIP" >&2
|
|
1047
|
-
exit 1; } # -> WT-5: tree + branch retained, marker stays "active"
|
|
1048
|
-
git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
|
|
1049
|
-
[ $? -eq 0 ] \
|
|
1050
|
-
|| { echo "WT-4 halt: FF-merge failed - the concurrent-writer race survives every precheck; marker stays \"active\"" >&2; exit 1; }
|
|
1051
|
-
LANDED_BASE_TIP="$(git rev-parse "$BASE_REF")" \
|
|
1052
|
-
|| { echo "WT-4 halt: cannot read landed base tip" >&2; exit 1; }
|
|
1053
|
-
git merge-base --is-ancestor "$BATCH_TIP" "$LANDED_BASE_TIP" \
|
|
1054
|
-
|| { echo "WT-4 halt: landed verify failed - $BATCH_TIP is not an ancestor of $LANDED_BASE_TIP" >&2; exit 1; }
|
|
1055
|
-
WT_PATH="<resolved-worktree-path>" # reuse mode: the adopted tree's physical path
|
|
1056
|
-
write_marker () { # $1 = merged | retained; read-modify-write: preserve WT-3 fields (id, command, selector, createdAt, adopted, adoptedAt); jq already assumed by WT-3/task-resolution flows
|
|
1057
|
-
mkdir -p "$(dirname "$MARKER")"
|
|
1058
|
-
if [ -f "$MARKER" ]; then
|
|
1059
|
-
TMP_MARKER="$(mktemp)"
|
|
1060
|
-
jq --arg s "$1" --arg m "$BATCH_TIP" '.status = $s | .mergeCommit = $m' "$MARKER" > "$TMP_MARKER" \
|
|
1061
|
-
|| { echo "WT-4 halt: cannot rewrite marker $MARKER" >&2; rm -f "$TMP_MARKER"; exit 1; }
|
|
1062
|
-
mv "$TMP_MARKER" "$MARKER"
|
|
1063
|
-
else
|
|
1064
|
-
printf '{ "baseRef": "%s", "baseSha": "%s", "branch": "%s", "path": "%s", "status": "%s", "mergeCommit": "%s" }\n' \
|
|
1065
|
-
"$BASE_REF" "$BASE_SHA" "$BRANCH" "$WT_PATH" "$1" "$BATCH_TIP" > "$MARKER"
|
|
1066
|
-
fi
|
|
1067
|
-
}
|
|
1068
|
-
# Step 5 evidence persistence (task 0720 R3) — required BEFORE the success marker:
|
|
1069
|
-
if printf 'batch report %s\n' "$BATCH_TIP" > ".spur/run/worktree-<marker-id>-batch-report.md"; then :; else
|
|
1070
|
-
echo "WT-5 halt: persistence failed after landed merge $BATCH_TIP; recovery reads captured tips only" >&2
|
|
1071
|
-
write_marker retained
|
|
1072
|
-
exit 1
|
|
1073
|
-
fi
|
|
1074
|
-
# Success marker: status "merged" + mergeCommit "$BATCH_TIP" — written once, only after landed verification and required persistence.
|
|
1075
|
-
# The worktree and branch are intentionally NOT removed.
|
|
1076
|
-
write_marker merged
|
|
1077
|
-
```
|
|
1078
|
-
|
|
1079
|
-
The operator supplied the tree, so the operator owns its lifetime. After a green reuse batch
|
|
1080
|
-
`baseRef == $BRANCH`, so the same worktree keeps fast-forwarding on the next invocation instead of
|
|
1081
|
-
having to be rebuilt — the continue-the-work loop is stable.
|
|
1082
|
-
|
|
1083
|
-
**Fast-forward only.** `git merge --ff-only` is the sole mutation of the terminal sequence; every
|
|
1084
|
-
other step is a read, a check, a persistence write, or a cleanup, and every tip/ancestry read uses
|
|
1085
|
-
local refs — no fetch runs anywhere in this lifecycle. If the fresh `BASE_TIP` is not an ancestor of
|
|
1086
|
-
`BATCH_TIP`, the ancestry check halts before the merge: the report names both tips and both
|
|
1087
|
-
divergent commit ranges (`$BASE_TIP..$BATCH_TIP` and `$BATCH_TIP..$BASE_TIP`) and falls to the
|
|
1088
|
-
retention path (WT-5) with the tree and branch retained. A concurrent writer that lands between the
|
|
1089
|
-
ancestry check and the merge still trips `--ff-only` — the race survives every precheck, and a
|
|
1090
|
-
failed FF leaves the marker at `status: active`. Do **not** rebase, merge-commit, or resolve
|
|
1091
|
-
conflicts. The corpus files (`docs/tasks*/`, kanban/index) are
|
|
1092
|
-
auto-generated and conflict-prone; automated conflict resolution over generated files is exactly the
|
|
1093
|
-
wrong thing to attempt unattended. FF-only means the merge either is trivially correct or does not
|
|
1094
|
-
happen.
|
|
1095
|
-
|
|
1096
|
-
**Auto-decision carve-out.** The create-mode success path runs unattended on a fully-passing
|
|
1097
|
-
`--worktree --auto` batch — it does **not** pause even though it performs a merge and a branch
|
|
1098
|
-
deletion. That is the explicit single exception to Auto-Decision Principle #6 (`cross-cutting.md`),
|
|
1099
|
-
which otherwise pauses any `--merge` / branch-deletion action regardless of `--auto`. The exception
|
|
1100
|
-
is safe because `git merge --ff-only` fails closed (it refuses rather than risk losing work), the
|
|
1101
|
-
branch deletion runs only after the merge has landed, persisted, and cleaned up — and is
|
|
1102
|
-
existence-guarded (`git rev-parse --verify --quiet`), so a removed branch is never queried;
|
|
1103
|
-
WT-5 retains the worktree and branch whenever FF is impossible or any task fails.
|
|
1104
|
-
Reuse mode is **narrower** than the carve-out (it merges but does not delete the branch), so the
|
|
1105
|
-
carve-out text needs no widening.
|
|
1106
|
-
|
|
1107
|
-
**Lifecycle-DB disposition (task 0701 R2d, amended by 0720).** The worktree has its own `.spur`
|
|
1108
|
-
lifecycle DB, and WT-4/WT-5 remove or retain that tree — the DB state does **not** travel with the
|
|
1109
|
-
merge. One contract, no alternatives:
|
|
1110
|
-
|
|
1111
|
-
- **Committed task files own lifecycle state.** The **committed task file is authoritative**: after
|
|
1112
|
-
a green merge the branch's task files already read `done`/`testing` in the invoking tree; no
|
|
1113
|
-
`spur task update` or `spur task record` replay runs post-merge. Replay is not "one of two
|
|
1114
|
-
options" — it is removed: it writes `updated_at`-only churn and can never restore worktree-only
|
|
1115
|
-
DB rows.
|
|
1116
|
-
- **The persisted invoking-tree artifacts own evidence.** The Step 5 batch report at
|
|
1117
|
-
`.spur/run/worktree-<marker-id>-batch-report.md` and the copied verdict JSONs under
|
|
1118
|
-
`.spur/run/worktree-<marker-id>-verdicts/` (written before WT-4 removal) are the batch/verdict
|
|
1119
|
-
record.
|
|
1120
|
-
- **Per-worktree lifecycle DB rows intentionally do not travel.** No `task_run_links` import, no
|
|
1121
|
-
cross-database provenance synthesis — the DB is per-tree by design.
|
|
1122
|
-
- **No timestamp-only corpus churn.** Post-merge the invoking tree's DB statuses may read stale
|
|
1123
|
-
relative to the committed files; that divergence is accepted, not repaired. Do not run
|
|
1124
|
-
`task update`/`task record` to "catch up" the DB, and do not repair churn with
|
|
1125
|
-
`git checkout -- docs/tasks*/`.
|
|
1126
|
-
|
|
1127
|
-
This is a deliberate choice over auto-migrating DB state: the committed corpus files are the durable
|
|
1128
|
-
record and the persisted run artifacts are the evidence record.
|
|
1129
|
-
|
|
1130
|
-
### WT-5 — Failure path: retain and report (R5)
|
|
1131
|
-
|
|
1132
|
-
On any per-task failure, batch halt, HITL pause that ends the run, or a WT-4 halt, the
|
|
1133
|
-
worktree directory and branch are left **intact**. No destructive automation on this path under
|
|
1134
|
-
any flag combination (`--auto`, `--force`, `--keep-going` — all leave the worktree in place). The
|
|
1135
|
-
marker records which class of halt occurred (frozen vocabulary):
|
|
1136
|
-
|
|
1137
|
-
- **Pre-merge failure** (failed checkout, zero-commit guard, divergent base, failed FF): the merge
|
|
1138
|
-
never landed, so the marker **stays at `status: active`** — the WT-3 marker stands and WT-5 is
|
|
1139
|
-
its disposition. Nothing was merged onto the base ref.
|
|
1140
|
-
- **Landed-but-incomplete failure** (persist-out, holder cleanup, or worktree removal failed after
|
|
1141
|
-
a landed merge): the marker records `status: retained` + `mergeCommit` BATCH_TIP — a truthful
|
|
1142
|
-
report that the merge landed. The retained path reads the captured `BATCH_TIP` only; a removed
|
|
1143
|
-
branch is never queried.
|
|
1144
|
-
|
|
1145
|
-
The worktree's own `.spur` lifecycle DB is retained with the tree,
|
|
1146
|
-
so nothing is lost on either path (see the WT-4 lifecycle-DB disposition for the merged case —
|
|
1147
|
-
task 0701 R2d). Emit a retention report in the existing halt-report shape:
|
|
1148
|
-
|
|
1149
|
-
**Persist-out skip recovery (task 1067 R2, E71).** When evidence was lost to a landing that
|
|
1150
|
-
skipped persist-out (or the pre-removal assertion fired too late), verdicts remain derivable
|
|
1151
|
-
from the task records — `spur task show <wbs> --json` carries the recorded verdict and its
|
|
1152
|
-
proof block, so a lost `<wbs>-verdict.json` is regenerated by re-recording or copying from
|
|
1153
|
-
the task store. Feature run/latest receipts are re-runnable, not recovered: rerun the
|
|
1154
|
-
feature-verification workflow in the invoking tree —
|
|
1155
|
-
`spur workflow run feature-verification.yaml --vars '{"featureId":"<id>"}'` — which rewrites
|
|
1156
|
-
the receipt files it owns. Divergent files are never auto-overwritten; reconcile by hand.
|
|
1157
|
-
|
|
1158
|
-
```
|
|
1159
|
-
## Worktree retained — <command> <selector>
|
|
1160
|
-
|
|
1161
|
-
**Halt cause:** <one-line cause — batch halted at task <wbs> / non-FF base ref / HITL pause / landed-but-incomplete cleanup failure>
|
|
1162
|
-
**Worktree path:** ../<worktree-dir>
|
|
1163
|
-
**Branch:** sp/<command>-<selector-slug>-<short-id>
|
|
1164
|
-
**Base ref:** <base-ref> (<base-sha>)
|
|
1165
|
-
**Merge state:** pre-merge failure — nothing landed | landed-but-incomplete — mergeCommit <batch-tip-sha>
|
|
1166
|
-
|
|
1167
|
-
The worktree and its branch are intact.
|
|
1168
|
-
pre-merge failure: nothing was merged onto the base ref.
|
|
1169
|
-
landed-but-incomplete: the batch merge LANDED as <batch-tip-sha> on <base-ref>; retention covers post-merge persistence/cleanup only.
|
|
1170
|
-
Resume, merge, or discard:
|
|
1171
|
-
|
|
1172
|
-
resume: cd <worktree-path> && <command> --continue --worktree <worktree-path>
|
|
1173
|
-
merge: git checkout <base-ref> && git merge <branch> # resolve conflicts manually
|
|
1174
|
-
discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
|
|
1175
|
-
```
|
|
1176
|
-
|
|
1177
|
-
When the halt cause is `non-FF base ref`, the report replaces the one-line `merge:` hint with this
|
|
1178
|
-
ordered divergence recipe, run by the operator — the driver never merges, rebases, or resolves
|
|
1179
|
-
conflicts itself. Other halt causes (task failure, HITL pause) keep the hint as printed:
|
|
1180
|
-
|
|
1181
|
-
```
|
|
1182
|
-
# 1. integrate as a merge commit — never a rebase; task evidence cites the branch's commit SHAs
|
|
1183
|
-
git checkout <base-ref> && git merge --no-ff --no-commit <branch>
|
|
1184
|
-
# 2. resolve source conflicts by hand; generated files are then regenerated with the project's
|
|
1185
|
-
# generator, never hand-merged
|
|
1186
|
-
# (this repo: bun run build:plugin-lib && bun run --filter @gobing-ai/spur build:bundle)
|
|
1187
|
-
# 3. stage every resolved path and regenerated bundle (git add …) — an unmerged or
|
|
1188
|
-
# unstaged path makes step 5 abort
|
|
1189
|
-
# 4. run qualityGateCmd once, after ALL conflicts are resolved
|
|
1190
|
-
# 5. commit the merge with the prepared message file
|
|
1191
|
-
git commit -F <message-file>
|
|
1192
|
-
# 6. persist evidence out (WT-4a), then WT-4b/4c cleanup, and set the marker to merged
|
|
1193
|
-
inline-run-setup --persist-out --from <worktree> --task-file …
|
|
1194
|
-
```
|
|
1195
|
-
|
|
1196
|
-
The report reuses the [`--next` chain contract](flag-glossary.md#--next-chain-contract) halt-report
|
|
1197
|
-
shape (halt cause + where + why), not new vocabulary. Retention is the right default: these batches
|
|
1198
|
-
are long and already resumable via `--continue`; auto-deleting is data loss, auto-merging is a
|
|
1199
|
-
partial result presented as a whole. The answer to "what happens if it fails" is "nothing happens,
|
|
1200
|
-
and we tell you where the work is."
|
|
1201
|
-
|
|
1202
|
-
**Partial worktree-removal recovery is inspection-only.** If a create-mode run halted partway
|
|
1203
|
-
through removal (leftover directory, stale registration), the recovery pass gathers facts and
|
|
1204
|
-
nothing else: `git worktree list` (is the tree still registered?), `ls` of the path (what remains
|
|
1205
|
-
on disk?), and the marker / `~/.config/spur/projects.json` registration state. Before removing
|
|
1206
|
-
anything, verify the leftover path is the batch's **owned** path — match it against the marker's
|
|
1207
|
-
`path`/`branch` (or the resolved `--worktree <name>` identity), never a guessed sibling directory.
|
|
1208
|
-
Recursive deletion of leftover directories that survived `git worktree remove` is **never
|
|
1209
|
-
automatic** — it requires explicit operator authorization. No automatic rebase, recovery merge,
|
|
1210
|
-
`git worktree prune`, or holder kill runs on this path.
|
|
510
|
+
Read [execution-worktree-setup.md](execution-worktree-setup.md) when `--worktree` is passed at batch
|
|
511
|
+
start (the lifecycle, WT-1…WT-3b). Read [execution-worktree-landing.md](execution-worktree-landing.md)
|
|
512
|
+
at batch end (WT-4/WT-5, the retained-worktree report and the conflict-merge recipe). WT-6/WT-7 and
|
|
513
|
+
the corpus-visibility note stay below.
|
|
1211
514
|
|
|
1212
515
|
### WT-6 — `--continue` re-entry (R7)
|
|
1213
516
|
|
|
1214
517
|
A `--continue` resume of a batch started with `--worktree` must re-enter the existing worktree via
|
|
1215
518
|
its WT-3 marker rather than creating a second one. Marker lookup tries two paths in order:
|
|
1216
519
|
|
|
1217
|
-
1. **Name-resolution path (when `--worktree <name>` is present)** — run the [§ Name resolution](#name-resolution---worktree-name)
|
|
520
|
+
1. **Name-resolution path (when `--worktree <name>` is present)** — run the [§ Name resolution](execution-worktree-setup.md#name-resolution---worktree-name)
|
|
1218
521
|
algorithm against `<name>`. The resolved worktree's path identifies the marker file to adopt.
|
|
1219
522
|
This path covers the common resume shapes: the operator remembers the name used last time, or
|
|
1220
523
|
passes the path (tier-1 match).
|
|
@@ -1258,12 +561,11 @@ While the batch runs, corpus writes (`spur task update`, `spur feature update`)
|
|
|
1258
561
|
the merge (WT-4) or manual integration (WT-5) propagates the writes back. Worth one line in each
|
|
1259
562
|
command doc so it does not read as a bug.
|
|
1260
563
|
|
|
1261
|
-
## Gate preflight
|
|
564
|
+
## Gate preflight
|
|
1262
565
|
|
|
1263
|
-
The
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
time. Before launching a full `spur-check-new`:
|
|
566
|
+
The cheap rule gates fail fast when run first — the full-gate run is dominated by the ~65 s test
|
|
567
|
+
suite, so a gate run that dies at `test-post-check` or `corpus-check` wastes most of its wall time.
|
|
568
|
+
Before launching a full `spur-check-new`:
|
|
1267
569
|
|
|
1268
570
|
1. **Run the two rule gates first** — `bun run test-pre-check` (43 rules: `no-console-output`,
|
|
1269
571
|
`no-direct-process-spawn`, `cli-*`, `require-corresponding-test`) and `bun run test-post-check`
|
|
@@ -1301,105 +603,9 @@ time. Before launching a full `spur-check-new`:
|
|
|
1301
603
|
|
|
1302
604
|
## Parallel isolation (`--mode parallel`)
|
|
1303
605
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
pipeline (`task-pipeline.yaml`) is unchanged — only where it runs and when dependents may start
|
|
1308
|
-
differ. The fan-out decision framework (independence, overlap, budget) stays owned by
|
|
1309
|
-
[sp:parallel-execution](../../parallel-execution/SKILL.md); this section owns the isolation +
|
|
1310
|
-
integration lifecycle and reuses WT-1…WT-5 by reference — it defines no new worktree mechanics.
|
|
1311
|
-
|
|
1312
|
-
### Driver loop
|
|
1313
|
-
|
|
1314
|
-
```
|
|
1315
|
-
WT-1 once on the main tree (dirty → abort) # one precheck for the whole batch, not per task
|
|
1316
|
-
ready = topo frontier; running = {}; integrated = set()
|
|
1317
|
-
while ready or running:
|
|
1318
|
-
while |running| < CONCURRENCY and ready has t with deps(t) ⊆ integrated:
|
|
1319
|
-
WT-2 create sp/run-<wbs>-<short-id> (branch sp/run-<wbs>-<short-id>) from BASE_REF tip
|
|
1320
|
-
WT-3 marker {command: dev-runall, selector: <wbs>, batchId: <batchId>}
|
|
1321
|
-
# WT-3 schema fields (path, branch, baseRef, baseSha) + batchId shared by every marker
|
|
1322
|
-
RUN[t] = (cd "$WT" && spur workflow run task-pipeline.yaml \
|
|
1323
|
-
--vars '{"wbs":"<wbs>","deferFeatureSync":"true",...}' --async --json)
|
|
1324
|
-
wait for any RUN terminal # trace poll --follow --timeout 600000; stale-evidence rule applies
|
|
1325
|
-
on done: WT-3b commit on $BRANCH → integrate(t)
|
|
1326
|
-
on failed: WT-5 retain (marker retained); failure policy — stop-the-batch default / --keep-going subtree skip
|
|
1327
|
-
on timeout/paused: WT-5 retain; run is non-terminal (HITL approve pause or poll bound) — stop-the-batch
|
|
1328
|
-
default / --keep-going subtree skip; the report marks the task `non-terminal`, never `done`
|
|
1329
|
-
post: feature sync + refresh per touched feature, one chore(corpus) commit (generated regions, R5)
|
|
1330
|
-
emit batch report (per-task outcomes + preflight skips + recovery hints + batch verdict)
|
|
1331
|
-
```
|
|
1332
|
-
|
|
1333
|
-
- **Bound (R2).** `CONCURRENCY` is the [`--concurrency <n>`](flag-glossary.md#flag-concurrency)
|
|
1334
|
-
value: default **2**, `n ≥ 1`; at most that many pipelines run at once.
|
|
1335
|
-
- **Eligibility (R2).** A task becomes eligible only when all of its in-set dependencies are
|
|
1336
|
-
**integrated** onto the base ref — pipeline-terminal is not enough, because its branch must
|
|
1337
|
-
rebase over theirs. Omitting `--mode` stays sequential; this loop is entered only via
|
|
1338
|
-
`--mode parallel` on `/sp:dev-runall`.
|
|
1339
|
-
- **Workdir.** `spur workflow run` records the launch workdir (0784 R1), so launch with `cwd` = the
|
|
1340
|
-
task worktree: resume and `.spur/run/` artifact paths resolve there. Read the verdict from
|
|
1341
|
-
`$WT/.spur/run/<wbs>-verdict.json` before integration; WT-4a persists it into the invoking tree.
|
|
1342
|
-
|
|
1343
|
-
### Integration — rebase, then fast-forward only (R3)
|
|
1344
|
-
|
|
1345
|
-
Pipeline-terminal is not terminal under parallel mode. The orchestrator integrates each succeeded
|
|
1346
|
-
task from the main tree; integrations are **serialized** (one at a time) in completion order:
|
|
1347
|
-
|
|
1348
|
-
```
|
|
1349
|
-
integrate(t):
|
|
1350
|
-
git -C "$WT" rebase "$BASE_REF" # replay onto the CURRENT base tip (re-read per integration)
|
|
1351
|
-
├─ rebase fails → git -C "$WT" rebase --abort → conflict path (R4) below
|
|
1352
|
-
└─ rebase clean → git checkout "$BASE_REF" → zero-commit guard → git merge --ff-only "$BRANCH"
|
|
1353
|
-
→ WT-4a verdict persist → WT-4b holders → WT-4c registry
|
|
1354
|
-
→ git worktree remove → existence-guarded git branch -D → marker merged
|
|
1355
|
-
```
|
|
1356
|
-
|
|
1357
|
-
The merge is always `--ff-only`: a base that will not fast-forward after a clean rebase is a
|
|
1358
|
-
conflict (R4). The pipeline never creates a merge commit, and no conflict is ever resolved
|
|
1359
|
-
automatically. `BASE_REF` is re-read at each integration so a task that finished late rebases over
|
|
1360
|
-
everything integrated before it.
|
|
1361
|
-
|
|
1362
|
-
### Conflict — retain, report, block (R4)
|
|
1363
|
-
|
|
1364
|
-
A failed rebase is aborted (`git -C "$WT" rebase --abort`), which leaves the worktree clean on its
|
|
1365
|
-
original branch tip. The worktree and branch are **retained** (WT-5, marker `retained`) and the
|
|
1366
|
-
task's outcome is `integration-conflict`. There is no auto-resolution — not for generated paths,
|
|
1367
|
-
not for anything. The batch report's row names the worktree path, the branch, and the manual
|
|
1368
|
-
commands:
|
|
1369
|
-
|
|
1370
|
-
```
|
|
1371
|
-
resume: cd <worktree-path> && git rebase <BASE_REF> # the operator resolves, never the driver
|
|
1372
|
-
merge: git checkout <BASE_REF> && git merge --ff-only <branch>
|
|
1373
|
-
discard: git worktree remove <worktree-path> && git branch -D <branch> && spur projects remove <worktree-path>
|
|
1374
|
-
```
|
|
1375
|
-
|
|
1376
|
-
The task's dependent subtree is blocked under the normal failure policy (Step 4). Resuming a
|
|
1377
|
-
retained parallel batch with `--continue` is future work; today a retained task is resumed
|
|
1378
|
-
per-task with `/sp:dev-run <wbs> --worktree <branch>`.
|
|
1379
|
-
|
|
1380
|
-
### Generated regions — defer the sync, regenerate once (R5)
|
|
1381
|
-
|
|
1382
|
-
The only per-task writer of feature files is the `record` step's post-record feature sync
|
|
1383
|
-
(`task-pipeline.yaml`). Parallel launches set
|
|
1384
|
-
the pipeline var `deferFeatureSync: "true"` (default `"false"`): the record step appends
|
|
1385
|
-
`feature sync deferred to batch integration` to the task report and skips the sync, so task
|
|
1386
|
-
branches never touch feature files or `docs/features/INDEX.md`. After the last integration, on the
|
|
1387
|
-
base ref, the orchestrator runs `spur feature sync <f> --json` (service-level suppression) plus `spur feature refresh --feature <f>`
|
|
1388
|
-
once per touched feature and commits the result as one `chore(corpus)` commit. Sequential and
|
|
1389
|
-
inline runs keep the default `"false"` and are unchanged. Any rebase conflict — on a generated
|
|
1390
|
-
path or any other — is an R4 `integration-conflict`; there is no path-based exception.
|
|
1391
|
-
|
|
1392
|
-
**Report.** Step 5's per-task outcome vocabulary gains the parallel-only `integration-conflict`;
|
|
1393
|
-
its row carries `worktree`, `branch`, and the manual commands above. Under parallel mode `done`
|
|
1394
|
-
means **integrated onto the base ref**, not merely pipeline-terminal.
|
|
1395
|
-
|
|
1396
|
-
**`--worktree` is rejected under parallel mode.** `--worktree --mode parallel` fails with
|
|
1397
|
-
"parallel mode already isolates each task in its own worktree" — reuse mode (`--worktree <name>`)
|
|
1398
|
-
has no per-task meaning (WT-7). Run parallel batches without `--worktree`, or run them sequentially
|
|
1399
|
-
with it.
|
|
1400
|
-
|
|
1401
|
-
**See also:** `sp:parallel-execution` skill (fan-out decision framework), `sp:super-planner` agent
|
|
1402
|
-
(parallel mode), `execution-batch.md` § Worktree isolation (WT-1…WT-7 mechanics).
|
|
606
|
+
Read [execution-parallel-isolation.md § Parallel isolation](execution-parallel-isolation.md#parallel-isolation---mode-parallel)
|
|
607
|
+
with `--mode parallel` — per-task worktrees, the rebase + fast-forward integration, conflict
|
|
608
|
+
retention and the generated-region deferral.
|
|
1403
609
|
|
|
1404
610
|
## Subagent execution disciplines
|
|
1405
611
|
|
|
@@ -1413,61 +619,5 @@ Parallel fan-out and any subagent dispatch obey the four disciplines owned by
|
|
|
1413
619
|
|
|
1414
620
|
## Batch continuation (`--continue`) — identity binding + checkpoint reconciliation (task 0919)
|
|
1415
621
|
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
### BC-1 — Re-bind the original frozen identity (R1; AC1/AC5)
|
|
1420
|
-
|
|
1421
|
-
Resume identity comes from persisted batch artifacts, in priority order:
|
|
1422
|
-
|
|
1423
|
-
1. **WT-3 marker** (worktree batches): the marker's `command` + `selector` and worktree
|
|
1424
|
-
name/branch re-derive the original launch (the WT-6 fallback already resolves this way).
|
|
1425
|
-
2. **Persisted batch report** (`.spur/run/worktree-<marker-id>-batch-report.md`, or the
|
|
1426
|
-
invoking-tree Step 5 report of a non-worktree batch): its `Plan:` row IS the frozen ordered
|
|
1427
|
-
membership — the resumed loop iterates exactly those WBS rows.
|
|
1428
|
-
|
|
1429
|
-
Re-running the selector is a **validation, not a re-definition**: tasks that newly match the
|
|
1430
|
-
selector but are absent from the frozen plan are reported `not-admitted` and never executed —
|
|
1431
|
-
freshly listed tasks do not join a resumed batch implicitly. A frozen task whose dependencies
|
|
1432
|
-
changed after freeze may invalidate its admission: report `blocked (admission invalidated —
|
|
1433
|
-
re-plan required)` and stop for operator decision. The authorized frozen set is never silently
|
|
1434
|
-
rewritten (Design).
|
|
1435
|
-
|
|
1436
|
-
### BC-2 — Checkpoints are hints (R2; AC2)
|
|
1437
|
-
|
|
1438
|
-
Do **not** pick the resume point by mtime alone: `ls -t … | head -1` over `.spur/memory/sessions/`
|
|
1439
|
-
is identity-blind, so an unrelated later session (different feature or task) must not become
|
|
1440
|
-
authoritative. Select candidate checkpoints by identity first, newest first within the match:
|
|
1441
|
-
|
|
1442
|
-
- frontmatter `workflow` is `task-pipeline` (or the batch's own workflow), and
|
|
1443
|
-
- `feature_id` equals the batch's feature selector, or `task_wbs` is a member of the frozen plan.
|
|
1444
|
-
|
|
1445
|
-
Checkpoints matching neither rule are ignored regardless of recency. A matched checkpoint only
|
|
1446
|
-
**hints** where the loop left off (`phase`, `next_action` — surface them to the operator); the
|
|
1447
|
-
driver then reconciles against authoritative state before skipping or repeating any task:
|
|
1448
|
-
|
|
1449
|
-
- **Skip** requires ALL of: task file status `done` AND a persisted PASS verdict artifact
|
|
1450
|
-
(`.spur/run/<wbs>-verdict.json`, or the worktree-persisted
|
|
1451
|
-
`.spur/run/worktree-<marker-id>-verdicts/<wbs>-verdict.json`) consistent with the task's
|
|
1452
|
-
current metadata. Anything less is not a valid skip.
|
|
1453
|
-
- **Stale or mismatched evidence** — checkpoint claims done but the verdict artifact is missing,
|
|
1454
|
-
the task file moved backwards, or the recorded `source_commit`/`digest` no longer matches —
|
|
1455
|
-
yields outcome `recheck`: re-run that task's pipeline. When reconciliation cannot proceed
|
|
1456
|
-
safely (lost worktree, unresolvable marker), report `blocked` with the reason. Never treat an
|
|
1457
|
-
unverified claim as done.
|
|
1458
|
-
|
|
1459
|
-
### BC-3 — Ordering, write ownership, terminal mix, evidence (R3/R4; AC3/AC4)
|
|
1460
|
-
|
|
1461
|
-
Sequential dependency-correct execution remains the default; opt-in `--mode parallel` keeps the
|
|
1462
|
-
existing proven-independence requirements with one writer per tree (Step 3). Mixed terminal
|
|
1463
|
-
results resume under the unchanged failure policy (Step 4). The Step 5 report keeps per-task
|
|
1464
|
-
outcomes distinct — `done | failed | blocked | skipped | not-attempted` plus the resume-only
|
|
1465
|
-
`recheck` and `not-admitted` — and the batch verdict stays `halted`/`aborted`: a resumed,
|
|
1466
|
-
partially-complete batch is never reported `clean`. Worktree evidence survives cleanup via the
|
|
1467
|
-
invoking-tree persistence in Step 5 (task 0720 R3); a persistence failure routes to **WT-5**
|
|
1468
|
-
(retain tree + branch), so partial outcomes are never silently lost and never read as a completed
|
|
1469
|
-
batch.
|
|
1470
|
-
|
|
1471
|
-
See [cross-cutting.md](cross-cutting.md) § "Session Checkpoint Convention" for the canonical
|
|
1472
|
-
frontmatter fields and the per-task owner-mismatch semantics
|
|
1473
|
-
(`packages/app/src/workflow/checkpoint-contract.ts`).
|
|
622
|
+
Read [execution-batch-continuation.md](execution-batch-continuation.md) with `--continue` — original
|
|
623
|
+
identity re-binding, checkpoints-as-hints reconciliation and the resumed-run terminal rules.
|