@gobing-ai/spur 0.3.65 → 0.3.67

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.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: spur-cli-history
3
+ description: "spur-cli noun reference: operate `spur history` to import coding-agent transcripts, aggregate versioned forensic artifacts, render an artifact without database access, or run the checkpoint-resumed daily pipeline."
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # spur history - local forensic analytics
9
+
10
+ `spur history` is the local history data plane: import transcripts into SQLite, aggregate a
11
+ versioned artifact, render that artifact, or run the daily composition. Command registration and
12
+ CLI guards live in `apps/cli/src/commands/history.ts` (`registerHistoryCommand`); payload contracts
13
+ live in `packages/app/src/services/history-service.ts` (`FanOutResult`, `DailyResult`,
14
+ `HistoryService`, `runHistoryReport`) and `packages/domain/src/analytics/artifact.ts`
15
+ (`HistoryArtifact`).
16
+
17
+ ## Verb map
18
+
19
+ | Verb | Purpose | Key flags |
20
+ | ---- | ------- | --------- |
21
+ | `import` | Import one source or fan out across all supported sources | `--source <source>` `--file <path>` `--root <path>` `--mode <mode>` `--dry-run` `--source-timeout <ms>` `--json` |
22
+ | `analyze` | Aggregate imported rows and write a versioned forensic artifact | `--since <iso>` `--until <iso>` `--source <source>` `--session <id>` `--run <runId>` `--task <wbs>` `--top <n>` `--out <path>` `--json` |
23
+ | `report [path]` | Purely render an existing artifact; default to `latest.json` | `--mode <name>` `--task <wbs>` `--top <n>` `--json` |
24
+ | `daily` | Run import-all → analyze → artifact → 90-day report pruning once | `--since <iso>` `--until <iso>` `--root <path>` `--source-timeout <ms>` `--mode <name>` `--json` |
25
+
26
+ Every JSON-capable verb also advertises `--json-envelope`; use the facade's machine-output contract.
27
+
28
+ ## `import` - isolated fan-out
29
+
30
+ ```bash
31
+ bun run apps/cli/src/index.ts history import --source all --dry-run --json
32
+ bun run apps/cli/src/index.ts history import --source codex --mode incremental --json
33
+ bun run apps/cli/src/index.ts history import --source codex --file session.jsonl --mode force-file --json
34
+ ```
35
+
36
+ - `--source all` and a single source use the same per-source fan-out path. A failed/timed-out source
37
+ does not abort its siblings.
38
+ - Modes are `incremental`, `full`, and `force-file`. `--file` with the default `all` source is a
39
+ usage error. `--file --mode full` requires `--dry-run`; use `force-file` for a real single-file
40
+ write.
41
+ - JSON contains `entries`, `warnings`, `exitCode`, and CLI/importer `provenance`. For real-data
42
+ validation, invoke the source-local CLI and record that provenance; never trust a bare global
43
+ `spur` that may be stale.
44
+ - Exit `0` when every source is clean/empty, `2` for a mixed failure or any degraded source, and `1`
45
+ when all sources fail. CLI usage guards also exit `1` on this noun.
46
+
47
+ ## `analyze` - artifact writer
48
+
49
+ `analyze` performs SQL aggregation over imported history and writes a stable, versioned
50
+ `HistoryArtifact`. Selectors combine with AND. `--top` bounds leaderboards, not totals. `--out`
51
+ overrides the dated report path; otherwise the service writes under `.spur/reports/history/` and
52
+ updates `latest.json`. Human mode renders a summary; `--json` emits the artifact.
53
+
54
+ ## `report` - pure artifact renderer
55
+
56
+ `report` never opens the database. It reads an explicit artifact or the `latest.json` pointer,
57
+ validates the schema version, optionally narrows the loaded artifact with `--task` / `--top`, and
58
+ renders `default` or `forensics` mode. Unknown modes, invalid `--top`, missing/mismatched task
59
+ dimensions, missing artifacts, and schema mismatches exit `1` instead of silently widening output.
60
+
61
+ ## `daily` - run-once composition
62
+
63
+ `daily` runs incremental import-all, analyze, artifact write, and report-directory pruning in one
64
+ process. `--since` / `--until` scope analysis only, not import. Checkpoints make a missed run resume
65
+ without double-counting. The result carries `{ fanOut, artifact, pruned, coverage, reportPath? }`;
66
+ its exit code is the fan-out exit code. `--mode` adds a rendered sidecar after analysis.
67
+
68
+ For the full artifact and data-plane contracts, use `docs/04_DESIGN.md` history sections and
69
+ `docs/design/history-data-processing.md`; do not infer fields from rendered prose.
@@ -24,8 +24,8 @@ use it well*.
24
24
  | `reply <msg-id> <body>` | Thread a reply to a message | `--json` |
25
25
  | `watch` | Follow an agent inbox - surface new messages as they arrive | `--agent <id>` `--interval <ms>` `--json` |
26
26
 
27
- All verbs accept `--json` for machine consumption. **Exit codes:** `0` success, `1` error, `2`
28
- invalid usage.
27
+ All verbs accept `--json` and `--json-envelope`. `watch` applies the envelope per emitted row.
28
+ **Exit codes:** `0` success, `1` error, `2` invalid usage.
29
29
 
30
30
  ## `send` - enqueue a message
31
31
 
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: spur-cli-projects
3
+ description: "spur-cli noun reference: operate `spur projects` to register local project roots, inspect live status, start detached project servers, and stop or remove registry entries."
4
+ see_also:
5
+ - spur-cli
6
+ ---
7
+
8
+ # spur projects - local multi-project registry
9
+
10
+ `spur projects` manages the machine-local registry resolved by
11
+ `packages/config/src/projects.ts` (`getProjectsFilePath`) and implemented by
12
+ `packages/app/src/services/project-registry.ts` (`ProjectRegistry`). Server startup is owned by
13
+ `packages/app/src/services/project-start.ts` (`startRegisteredProject`); CLI registration and JSON
14
+ shapes live in `apps/cli/src/commands/projects.ts`.
15
+
16
+ ## Verb map
17
+
18
+ | Verb | Purpose | Key flags |
19
+ | ---- | ------- | --------- |
20
+ | `add <path>` | Upsert an existing path in the registry | `--name <name>` `--json` |
21
+ | `remove <target>` | Remove an entry by display name or path | `--json` |
22
+ | `list` | List entries with live running status | `--json` |
23
+ | `start <target>` | Start or reuse a detached project server | `--port <n>` `--json` |
24
+ | `stop <target>` | Best-effort stop the listener and clear its recorded port | `--json` |
25
+
26
+ Every verb also advertises `--json-envelope`; use the facade's machine-output contract. Success is
27
+ exit `0`; validation, registry, spawn, health, or lookup failure is exit `1`.
28
+
29
+ ## Registry behavior
30
+
31
+ - The default file is `~/.config/spur/projects.json`; `SPUR_PROJECTS_FILE` overrides it. Entries are
32
+ `{ name, path, port }`, where `port: 0` means stopped.
33
+ - Paths are normalized (including `~`) and existing paths resolve to real paths. Name lookup is
34
+ case-insensitive. Registry mutations use an advisory lock.
35
+ - `add` requires an existing path, resolves a relative path from the current working directory, and
36
+ defaults the display name to its basename. It upserts; it does not start a server. The current
37
+ source does not enforce a `.spur/` marker or directory type.
38
+ - `list` probes recorded ports and heals stale entries to `port: 0` before reporting `running`.
39
+
40
+ ## Server lifecycle
41
+
42
+ ```bash
43
+ spur projects start my-project --json
44
+ spur projects start /path/to/unregistered/project --port 3333 --json
45
+ spur projects stop my-project --json
46
+ ```
47
+
48
+ `start` resolves by name or path. An existing unregistered path is auto-registered. A live recorded
49
+ port is returned idempotently; otherwise the service allocates a port (3000–3999 unless explicitly
50
+ set), spawns `spur serve --host 127.0.0.1 --no-open` detached in the project root, waits for the port,
51
+ then persists it.
52
+
53
+ `stop` finds processes bound to the recorded port, sends `SIGTERM` best-effort while excluding the
54
+ CLI and its parent, and clears the registry port. It is not a persistent supervisor contract.
55
+ `remove` only removes the registry entry; stop a running project first when process cleanup matters.
56
+
57
+ Raw JSON success payloads are verb-specific (`project`, `projects`, `removed`, `stopped`, or start
58
+ status fields). Under `--json-envelope` they move beneath `data`; failures normalize beneath
59
+ `error`. Read `apps/cli/src/commands/projects.ts` for exact fields.
@@ -54,7 +54,9 @@ frontmatter scalar.
54
54
  cancelled`. Two transitions run a target-aware `check` guard (§7.5): `wip→testing` →
55
55
  `spur task check <wbs> --as testing`; `testing→done` → `spur task check <wbs> --as done`
56
56
  (F92 R3 — each evaluates the transition target, so `testing→done` checks the `done` row).
57
- A failing gate blocks the transition.
57
+ A failing gate blocks the transition. The `wip→testing` guard additionally requires a Solution
58
+ section citing concrete `file:line` evidence — a missing/unsubstantiated Solution is rejected with
59
+ `[invalid-solution]`.
58
60
  - **`--no-lifecycle`**: suppress lifecycle workflow *run record* creation (use inside pipeline runs
59
61
  to avoid orphaned nested lifecycle runs). **It is not a guard bypass** — the `wip→testing` and
60
62
  `testing→done` `check` gates above still run; the CLI evaluates them inline when the FSM guard
@@ -221,6 +223,18 @@ the active tasks folder (or `--folder`).
221
223
  | `--folder <path>` | Custom tasks folder. |
222
224
  | `--json` | Machine-readable report envelope. |
223
225
 
226
+ ## `migrate-anchors`
227
+
228
+ Qualify ambiguous in-repo evidence anchors to repo-relative paths across the task corpus. Run
229
+ `--dry-run` first: unambiguous matches are reported in `qualified`, multiple matches in `ambiguous`
230
+ without rewriting, and schema-incompatible files in `skipped`. The write path uses the planning
231
+ service rather than raw file edits.
232
+
233
+ ```bash
234
+ spur task migrate-anchors --dry-run --json
235
+ spur task migrate-anchors --json
236
+ ```
237
+
224
238
  ## `resolve <file-path>`
225
239
 
226
240
  Map a file path to its **owning task** — returns the WBS + task file. Strategies, in order: direct
@@ -230,6 +244,10 @@ only the exact corpus path, with no basename-WBS fallback. Returns exit `1` if n
230
244
 
231
245
  ## `verdict <wbs>`
232
246
 
247
+ There is **no `verify` verb**. The verify leg is three steps: the agent writes its structured
248
+ evidence write-up to `.spur/run/<wbs>-verify-answer.txt`, then `spur task verdict <wbs>` derives
249
+ the verdict artifact, then `spur task record <wbs>` lands it into the task.
250
+
233
251
  Derive a PASS / PARTIAL / FAIL / UNKNOWN verdict from a verify-step answer text file (the agent's
234
252
  structured evidence write-up). Replaces the pipeline's verify→record transition's previous grep/shell
235
253
  ladder (0108; ADR-022). On a readable answer, emits `.spur/run/<wbs>-verdict.json` (mkdir-p the
@@ -323,6 +341,19 @@ A free-form prose answer (no tables, or tables missing the required headers) yie
323
341
  the artifact source and directs the operator to `/sp:dev-verify <wbs>`. Re-run verify with the
324
342
  table format above.
325
343
 
344
+ ## `verifyall-aggregate`
345
+
346
+ Read a JSON array of `{wbs,outcome[,reason]}` rows from `--from-file` (default
347
+ `.spur/run/verifyall-batch-input.json`) and derive one deterministic batch verdict. Valid outcomes
348
+ are `PASS`, `PARTIAL`, `FAIL`, `NOT-STARTED`, and `UNKNOWN`; `NOT-STARTED` rows are reported but
349
+ excluded from rollup. Exit `1` when the aggregate verdict is `FAIL` or the input is invalid.
350
+
351
+ ## `scaffold-tests <wbs>`
352
+
353
+ Generate BDD stubs from the task's Acceptance Criteria. `--file <path>` overrides the target test
354
+ file; `--folder <path>` overrides task lookup. JSON reports the target plus created, skipped,
355
+ drifted, and warning results.
356
+
326
357
  ## `refresh-roster <wbs>`
327
358
 
328
359
  Regenerate a parent task's sub-task roster block in `## Plan` — the marker-delimited table that the
@@ -361,12 +392,15 @@ spur task sections <wbs> <init|add|list> [name] [--folder] [--json]
361
392
  spur task list [--status <s>] [--phase <p>] [--parent <wbs>] [--feature <id>] [--folder] [--json]
362
393
  spur task refresh [--folder] [--json]
363
394
  spur task migrate [--dry-run] [--folder] [--json]
395
+ spur task migrate-anchors [--dry-run] [--json]
364
396
  spur task refresh-roster <wbs> [--folder] [--json]
365
397
  spur task batch-create --file <path> [--folder] [--json]
366
398
  spur task record <wbs> [--verdict-file <p>] [--solution-from-diff] [--transition <s>] [--folder] [--json]
367
399
  spur task verdict <wbs> [--from-answer <p>] [--folder] [--json]
400
+ spur task verifyall-aggregate [--from-file <path>] [--json]
368
401
  spur task check [wbs] [--strict] [--as <status>] [--strict-core] [--folder] [--json]
369
402
  spur task resolve <file-path> [--strict] [--folder] [--json]
370
403
  spur task path <wbs> [--folder] [--json]
371
404
  spur task run-link <wbs> [--source <src>] [--run-id <id>] [--json]
405
+ spur task scaffold-tests <wbs> [--file <path>] [--folder] [--json]
372
406
  ```
@@ -26,7 +26,7 @@ use it well*.
26
26
  | `start <agent-id>` | Start a supervised agent process (requires `spur serve`) | `--server <url>` `--json` |
27
27
  | `stop <agent-id>` | Stop a supervised agent process (requires `spur serve`) | `--server <url>` `--json` |
28
28
 
29
- All verbs accept `--json` for machine consumption. `--server <url>` (default:
29
+ All verbs except the text-only `assign` accept `--json` and `--json-envelope`. `--server <url>` (default:
30
30
  `http://localhost:3000/api`) targets the supervisor API started by `spur serve`. **Exit codes:** `0`
31
31
  success, `1` error, `2` invalid usage.
32
32
 
@@ -264,6 +264,12 @@ spur workflow list [--json]
264
264
  spur workflow trace [run-id] [--workflow <name>] [--status <s>] [--since <iso>] [--last <n>] [--follow] [--poll <ms>] [--output] [--json]
265
265
  ```
266
266
 
267
+ ### `show` - project a definition
268
+
269
+ `spur workflow show <file>` renders the declared graph as Mermaid. `--format todo` instead emits a
270
+ declared-step checklist; `--json` serializes the selected projection. This verb intentionally does
271
+ not advertise `--json-envelope` because its JSON projection is a kept-raw document surface.
272
+
267
273
  | Flag (on `run`) | Effect |
268
274
  | --------------- | ------ |
269
275
  | `--vars <json>` | Per-run variable overrides (JSON object). Merged over the workflow's `vars`. Values must be strings. User vars win over injected defaults (`spurBin`). |
@@ -234,6 +234,11 @@ or `args="refine <wbs>"` for task refinement, or `args="refineall --feature <id>
234
234
  batch refine under a feature (add `--depth ready` for implement-ready freeze). Use `spur agent run`
235
235
  for isolated LLM invocations within pipeline steps.
236
236
 
237
+ **Argument parsing.** Split `$ARGUMENTS` into target + flags before dispatching. Unknown flags are
238
+ not silently dropped: note them in the plan line, or stop (rule imported from `next-router`,
239
+ task 0701 R3 — e.g. `--worktree` on `dev-refine` is undeclared, so it must be surfaced, never
240
+ discarded).
241
+
237
242
  ### Codex / OpenClaw / OpenCode / Antigravity
238
243
 
239
244
  Run `spur` CLI via the Bash tool; parse `--json` output. Invoke this skill directly for
@@ -236,7 +236,7 @@ must not be changed without updating the backing skill.
236
236
  - **Purpose:** Batch-refine a set of tasks (or all refine-eligible tasks under a feature) — resolve a set, topo-sort by dependencies, run per-task `refine`, emit a summary report. Planning-half counterpart of `verifyall` / `runall` for the just-in-time spec-completion gate. With `--depth ready`, batch **implement-ready** freeze before multi-agent implement or runall.
237
237
  - **Inputs:**
238
238
  - `--feature <id>` **or** `--tasks <selector>` (required — at least one). `--feature` is sugar for `--tasks feature:<id>` (shared selector grammar: explicit WBS list, `feature:<id>`, `ready`, status pseudo-list — [execution-batch.md](execution-batch.md) Step 1). If both are present, `--tasks` wins (one-line note in the report).
239
- - Shared refine flags (passed through to each per-task refine): `--focus <mode>`, `--description <text>`, `--depth <standard|ready>`, `--agent <inline|auto|name>`, `--auto`, `--next`.
239
+ - Shared refine flags (passed through to each per-task refine): `--focus <mode>`, `--description <text>`, `--depth <standard|ready>`, `--agent <inline|auto|name>`, `--auto`.
240
240
  - Batch-only flags: `--keep-going` (continue independents after a failure; default halt), `--status <s>` (filter resolved membership; default **`backlog,todo`** — planning-side fill candidates), `--json` (machine-readable batch report).
241
241
  - **Backing:** `sp:spur-dev` skill, `refineall` operation (orchestrates; per-task body is the single-task `refine` operation — never a second refine implementation).
242
242
  - **Behavior:**
@@ -248,7 +248,7 @@ must not be changed without updating the backing skill.
248
248
  6. Emit a batch report (markdown or `--json`) that records `depth` once at the header.
249
249
  - **Per-task outcome vocabulary:** `refined` (synthesis wrote sections) | `SKIP` (already meets the active depth bar under `--auto`) | `failed` | `skipped` (dep failed under `--keep-going`) | `not-attempted` (halted) | `blocked` (unmet out-of-set dep).
250
250
  - **Batch verdict:** `clean` (all attempted tasks `refined` or `SKIP`) | `halted` (a failure stopped the batch) | `aborted` (cycle / unknown selector / empty set after filter).
251
- - **`--next` warning:** Passing `--next` chains **each** successful refine into `/sp:dev-run <wbs> --mode implement --auto --next`, which can balloon into implement+verify execution for every task. Prefer refineall without `--next`, then `/sp:dev-runall --feature <id>` for execution. Document the risk in the batch report header when `--next` is set.
251
+ - **`--next` is not accepted** (dropped by feature H8, 2026-07-31 — see `plugins/sp/commands/dev-refineall.md` for the removal record). Chain execution explicitly: refineall, then `/sp:dev-runall --feature <id>`.
252
252
  - **`--auto` recommendation:** Batch refine without `--auto` requires per-task interactive Q&A and does not scale. Default operator path: `/sp:dev-refineall --feature <id> --auto`. For implement handoffs: `/sp:dev-refineall --feature <id> --auto --depth ready`.
253
253
  - **Delegation:** `Skill(skill="sp:spur-dev", args="refineall $ARGUMENTS")` → per task `Skill(skill="sp:spur-dev", args="refine <wbs> $SHARED_FLAGS")` (shared flags include `--depth` when set).
254
254
 
@@ -61,6 +61,7 @@ function normalizeArgs(raw: Args): Args {
61
61
  ```
62
62
 
63
63
  **Normalization rules (performed by the command layer before the skill sees $ARGUMENTS, or by the batch resolver):**
64
+
64
65
  - If `--feature FOO` is present and `--tasks` is absent, treat the effective selector as `feature:FOO`.
65
66
  - If both are present, `--tasks` wins (with a one-line note in the batch report).
66
67
 
@@ -101,7 +102,7 @@ bun run apps/cli/src/index.ts feature check <id> --strict --json
101
102
  kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
102
103
 
103
104
  | Selector form | Regex / match | Resolution |
104
- |---|---|---|
105
+ | --- | --- | --- |
105
106
  | Explicit WBS list | `^[0-9, ]+$` | Split on comma; validate each token is a 4-digit WBS; collect the explicit set. (R1.1) |
106
107
  | `feature:<id>` (via `--tasks` or `--feature <id>`) | literal `feature:` prefix or `--feature` flag | `spur task list --feature <id> --json`; collect `wbs` from each row. The `--feature` flag is sugar that becomes `--tasks feature:<id>` at the command layer. (R1.3) |
107
108
  | `ready` | literal `ready` | Resolve the union of `spur task list --status todo --json` + `spur task list --status backlog --json`, drop tasks with open children (R1.5, umbrella-parent exclusion below), then keep only tasks whose every `dependencies[]` entry resolves to `status == done` (via `spur task show <dep> --json | jq '{wbs, status, dependencies, feature_id}'` — R5 metadata-only). Report each excluded task with its unmet dependency. (R1.4) |
@@ -185,7 +186,7 @@ node "$(superskill script path sp batch-preflight.mjs)" \
185
186
  ```
186
187
 
187
188
  | Result | Batch action |
188
- |--------|----------------|
189
+ | -------- | ---------------- |
189
190
  | `action: run` | Launch `task-pipeline.yaml` for this WBS (happy path **unchanged**) |
190
191
  | `action: skip` code **A2** | Do not launch; report `preflight-skip` + unmet deps (mirrors TABLE A2) |
191
192
  | `action: skip` code **A7** | Do not launch; report blocked (handover is operator-side) |
@@ -253,7 +254,7 @@ Only two flags cross the orchestrator→pipeline boundary; both are merged into
253
254
  `--vars` JSON:
254
255
 
255
256
  | Flag | Effect on per-task `--vars` |
256
- |---|---|
257
+ | --- | --- |
257
258
  | `--auto` | sets `"profile":"auto"` (skips the HITL approve gate). Omitting it forwards nothing, so the pipeline uses its default profile (standard — HITL pause surfaces to the operator). (R4.2) |
258
259
  | `--agent <value>` | omit/`inline` in interactive sequential mode selects the host driver and is not forwarded. `auto` or a name sets **both** `"agent":"<value>"` and `"implementAgent":"<value>"` so every workflow `agent.run` step — including implement — spawns that executor. Headless omit/inline falls through the executor precedence chain. To pin ONLY implement, pass `--vars '{"implementAgent":"..."}'` separately; that explicit var selects the subprocess path. (R4.3, tasks 0483/0503) |
259
260
 
@@ -283,7 +284,7 @@ node "$(superskill script path sp batch-preflight.mjs)" --wbs <wbs> --status <st
283
284
  ```
284
285
 
285
286
  | Rule | Detail |
286
- |------|--------|
287
+ | ------ | -------- |
287
288
  | Budget | **≤ 1** recovery consult per WBS per batch — never loop until done |
288
289
  | Default | Print the exact child command in the batch report |
289
290
  | `--auto` batch | May dispatch the child **once** when cardinality is 1 and the hop is a single lifecycle command |
@@ -295,7 +296,6 @@ in next-router; this only maps status → primary TABLE A hop for recovery.
295
296
 
296
297
  ### 3.3c Bounded feature-sync retry suppression (task 0411)
297
298
 
298
-
299
299
  During a batch, the per-task `record` step and the wrap-up `feature-transition` step each invoke
300
300
  feature status sync. When a feature is L4-gate-blocked (e.g. not all linked tasks are `done`), the
301
301
  identical blocked proposal repeats on every call with no intervening input change — in the H9
@@ -413,6 +413,14 @@ The per-task outcome vocabulary: `done` | `failed` | `blocked` | `skipped` | `no
413
413
  The batch verdict: `clean` (all attempted tasks `done`) | `halted` (a failure stopped the batch) |
414
414
  `aborted` (cycle or selector error before any run).
415
415
 
416
+ **Zero-task rule (task 0701 R7b).** A selector that resolves to an **empty set after the status
417
+ filter** is an `aborted` verdict (`aborted (empty set after filter)`), matching dev-operations.md
418
+ §5a. Under `--worktree`, **WT-2 is skipped entirely**: no worktree is cut and no WT-3 marker is
419
+ written for a batch with nothing to run. The early-exit report carries zero per-task rows,
420
+ `Steps: 0 derived, 0 executed`, and the `aborted` verdict; no WT-3b commit step and no WT-4/WT-5
421
+ terminal action runs. A contract test pins this
422
+ (`plugins/sp/tests/dogfood-testing/execution-batch-contract.test.ts`).
423
+
416
424
  ## Worktree isolation (`--worktree [<name>]`)
417
425
 
418
426
  When a batch command (`dev-runall`, `dev-refineall`, `dev-verifyall`) is invoked with
@@ -487,7 +495,12 @@ Create one worktree on a new branch cut from the current HEAD's ref (the **base
487
495
  BASE_REF=$(git rev-parse --abbrev-ref HEAD)
488
496
  BASE_SHA=$(git rev-parse HEAD)
489
497
  BRANCH="sp/<command>-<selector-slug>-<short-id>" # e.g. sp/runall-h1-a3f2
490
- git worktree add "../<repo>-<command>-<selector-slug>-<short-id>" -b "$BRANCH" "$BASE_REF"
498
+ # `git worktree add -b` creates the branch BEFORE the directory, so a failed create leaves a
499
+ # dangling branch and the natural retry dies on "a branch named ... already exists"
500
+ # (task 0701 R2b). Wrap the create: on failure, delete the branch — or derive a fresh
501
+ # short-id per attempt — before surfacing the error.
502
+ git worktree add "../<repo>-<command>-<selector-slug>-<short-id>" -b "$BRANCH" "$BASE_REF" \
503
+ || { git branch -D "$BRANCH"; false; }
491
504
  ```
492
505
 
493
506
  Branch and directory names are derived (command + selector slug + short id); the create path never
@@ -497,10 +510,15 @@ not resolve is an error, not a create).
497
510
  A fresh worktree has no `node_modules` (gitignored), so the first `bun test` or
498
511
  typecheck fails on the first workspace import. Install before any task work:
499
512
 
500
- cd "../<worktree-dir>" && bun install --frozen-lockfile
513
+ cd "../<worktree-dir>" && bun install --frozen-lockfile --ignore-scripts
501
514
 
502
515
  `--frozen-lockfile` pins the worktree to `bun.lock` rather than re-resolving,
503
- so the worktree's dependency tree matches the base ref's.
516
+ so the worktree's dependency tree matches the base ref's. `--ignore-scripts` is required, not
517
+ stylistic (task 0701 R2a): worktrees share the main tree's `.git`, and this repo's `prepare`
518
+ script is `lefthook install` (`package.json`) — a bare install rewrites the operator's
519
+ main-repo hooks from inside the "isolated" tree. Scripts are skipped only at this call site;
520
+ a normal clone keeps `prepare`. The worktree still gets a usable dependency tree — the install
521
+ exists so the first `bun test` resolves workspace imports.
504
522
 
505
523
  #### Reuse mode (`--worktree <name>`)
506
524
 
@@ -510,8 +528,8 @@ invoking tree's current HEAD ref (not the worktree's branch) and `BASE_SHA` is
510
528
  `git merge-base <BASE_REF> <BRANCH>` — so WT-4's FF-merge lands the worktree's accumulated commits
511
529
  onto the invoking tree's base ref, exactly as create mode does.
512
530
 
513
- `bun install --frozen-lockfile` runs **only when `node_modules` is absent** in the resolved
514
- worktree. A warm reused tree does not re-pay the install; a cold one (hand-made, or a retained tree
531
+ `bun install --frozen-lockfile --ignore-scripts` runs **only when `node_modules` is absent** in
532
+ 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
515
533
  whose deps were removed) installs exactly once before the first task. This is the R3 conditional
516
534
  install rule (source: task 0481) — create mode always installs because a fresh tree is always cold.
517
535
 
@@ -570,7 +588,9 @@ verb) whose path argument you control.
570
588
 
571
589
  Worktree identity lives on disk under `.spur/run/`, not only in the orchestrator's memory, so a
572
590
  session that dies mid-batch is recoverable. Write the marker at creation and update it at the
573
- terminal transition (merged / retained). Schema:
591
+ terminal transition (merged / retained). The marker is written to the **invoking** tree's
592
+ `.spur/run/` (task 0701 R2c) — the tree where the driver process started, not the worktree's own
593
+ `.spur/run/` — so WT-6's resume scan finds it regardless of where the operator stands. Schema:
574
594
 
575
595
  ```json
576
596
  {
@@ -617,6 +637,22 @@ Reuse mode resolves the marker by the resolved worktree's `path` (not by `comman
617
637
  (AGENTS.md one-writer-per-tree; task 0487 R5). Overridable with `--force` (the operator can tell
618
638
  a crashed-session marker from a live-session one; the harness cannot).
619
639
 
640
+ ### WT-3b — Commit the batch's writes on `$BRANCH` (task 0701 R1)
641
+
642
+ Before any terminal action, commit the batch's corpus writes **on `$BRANCH`, inside the
643
+ worktree** — including the generated task files under `docs/tasks*/` and the kanban index:
644
+
645
+ ```bash
646
+ cd "../<worktree-dir>"
647
+ git add <files-the-batch-wrote>
648
+ git commit -m "<type>(<scope>): <command> <selector> batch writes"
649
+ cd - >/dev/null
650
+ ```
651
+
652
+ The FF-only git merge carries only commits — uncommitted writes in the worktree would be left
653
+ behind by the merge and then destroyed by create mode's `git worktree remove`. WT-3b exists so
654
+ that can never happen.
655
+
620
656
  ### WT-4 — Success path (R4)
621
657
 
622
658
  When the batch completes with **no failed task**, fast-forward-merge the worktree branch onto the
@@ -629,6 +665,11 @@ only what it created*):
629
665
  # Run these from the main tree (not inside the worktree) - you merge the worktree branch
630
666
  # back onto the base ref there:
631
667
  git checkout "$BASE_REF"
668
+ # Guard (task 0701 R1): a zero-commit branch makes the FF-only git merge exit 0
669
+ # ("Already up to date") while merging nothing — the two lines below would then delete
670
+ # the worktree holding the only copy of the batch's writes. Refuse instead:
671
+ [ "$(git rev-list --count "$BASE_SHA..$BRANCH")" -gt 0 ] \
672
+ || { echo "halt: branch carries no commits - nothing to merge" >&2; false; } # -> WT-5
632
673
  git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or resolve conflicts
633
674
  # if FF succeeded:
634
675
  git worktree remove "../<worktree-dir>"
@@ -636,6 +677,9 @@ git branch -d "$BRANCH"
636
677
  # update marker: status = "merged"
637
678
  ```
638
679
 
680
+ On the zero-commit guard firing, fall through to **WT-5** with the halt cause *"branch carries no
681
+ commits — nothing to merge"*: the worktree and branch are retained, never removed (task 0701 R1).
682
+
639
683
  #### Reuse mode — merge, retain
640
684
 
641
685
  The FF-merge runs identically (same `git checkout "$BASE_REF" && git merge --ff-only "$BRANCH"`),
@@ -667,12 +711,23 @@ risk losing work); WT-5 retains the worktree and branch whenever FF is impossibl
667
711
  Reuse mode is **narrower** than the carve-out (it merges but does not delete the branch), so the
668
712
  carve-out text needs no widening.
669
713
 
714
+ **Lifecycle-DB disposition (task 0701 R2d).** The worktree has its own `.spur` lifecycle DB, and
715
+ WT-4/WT-5 remove or retain that tree — the DB state does **not** travel with the merge. The
716
+ **committed task file is authoritative**: after a green merge the branch's task files read
717
+ `done`/`testing` while the invoking tree's DB still reports the pre-batch statuses. Re-sync
718
+ explicitly by replaying the recorded terminal transitions in the invoking tree (`spur task update
719
+ <wbs> <status>` per task, then `spur task record <wbs>`), or treat the batch report's per-task
720
+ table as the source of truth. This is a deliberate choice over auto-migrating DB state: the DB is
721
+ per-tree by design and the committed corpus files are the durable record.
722
+
670
723
  ### WT-5 — Failure path: retain and report (R5)
671
724
 
672
725
  On any per-task failure, batch halt, HITL pause that ends the run, or non-FF merge from WT-4, the
673
726
  worktree directory and branch are left **intact**. No destructive automation on this path under any
674
727
  flag combination (`--auto`, `--force`, `--keep-going` — all leave the worktree in place). Update the
675
- marker: `status = "retained"`. Emit a retention report in the existing halt-report shape:
728
+ marker: `status = "retained"`. The worktree's own `.spur` lifecycle DB is retained with the tree,
729
+ so nothing is lost on this path (see the WT-4 lifecycle-DB disposition for the merged case —
730
+ task 0701 R2d). Emit a retention report in the existing halt-report shape:
676
731
 
677
732
  ```
678
733
  ## Worktree retained — <command> <selector>
@@ -706,7 +761,8 @@ its WT-3 marker rather than creating a second one. Marker lookup tries two paths
706
761
  This path covers the common resume shapes: the operator remembers the name used last time, or
707
762
  passes the path (tier-1 match).
708
763
  2. **Command+selector fallback (bare `--worktree` or absent flag)** — scan
709
- `.spur/run/worktree-*.json` for a marker whose `command` + `selector` match the current
764
+ `.spur/run/worktree-*.json` **in the invoking tree** (where WT-3 wrote the marker —
765
+ task 0701 R2c) for a marker whose `command` + `selector` match the current
710
766
  invocation and whose `status` is `active` or `retained`. Create-mode runs that did not name their
711
767
  tree resolve here.
712
768
  3. **Found by either path** → `cd` into the marker's `path`, skip WT-1/WT-2 (no new worktree), and
@@ -769,7 +825,7 @@ time. Before launching a full `spur-check-new`:
769
825
  ## AC traceability
770
826
 
771
827
  | AC | Where satisfied |
772
- |---|---|
828
+ | --- | --- |
773
829
  | R1.1–R1.4 (selector grammar) | Step 1 — selector resolution table |
774
830
  | R1.5 (umbrella-parent exclusion) | Step 1 — "Umbrella-parent exclusion" paragraph |
775
831
  | R2.1 (freeze at kickoff) | Step 2.1 |
@@ -793,6 +849,7 @@ time. Before launching a full `spur-check-new`:
793
849
  When a batch contains tasks with **zero dependency edges between them** and **no file-overlap conflicts**, the orchestrator can fan them out in parallel instead of running them sequentially. This is an **orchestrator-level optimization** — the per-task pipeline (`task-pipeline.yaml`) is unchanged; only the execution order differs.
794
850
 
795
851
  **Decision framework:** `sp:parallel-execution` owns the full fan-out decision logic and patterns. Consult its [fan-out-patterns.md](../../parallel-execution/references/fan-out-patterns.md) before parallelizing. The orchestrator's responsibility is:
852
+
796
853
  1. Identify the independent subset from the topo-sorted batch (tasks with no edges to each other).
797
854
  2. Check for file-overlap conflicts (two tasks touching the same `file:line` range must serialize).
798
855
  3. Verify token budget supports N-way fan-out.
@@ -803,7 +860,6 @@ When a batch contains tasks with **zero dependency edges between them** and **no
803
860
 
804
861
  **See also:** `sp:parallel-execution` skill, `sp:super-planner` agent (parallel mode), `/sp:dev-parallel` command.
805
862
 
806
-
807
863
  ## Subagent execution disciplines
808
864
 
809
865
  Parallel fan-out and any subagent dispatch obey the four disciplines owned by