@gobing-ai/spur 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +4 -2
  3. package/config/templates/AGENTS.md +1 -1
  4. package/config/workflows/wrapup-pipeline.yaml +1 -1
  5. package/package.json +10 -10
  6. package/plugins/sp/README.md +1 -2
  7. package/plugins/sp/agents/super-planner.md +5 -6
  8. package/plugins/sp/commands/dev-review-session.md +2 -1
  9. package/plugins/sp/commands/dev-runall.md +18 -9
  10. package/plugins/sp/hooks/context-owner.ts +56 -0
  11. package/plugins/sp/hooks/context-post-tool.ts +23 -0
  12. package/plugins/sp/hooks/context-session-start.ts +49 -27
  13. package/plugins/sp/hooks/context-session-stop.ts +131 -30
  14. package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
  15. package/plugins/sp/lib/idea-handoff.generated.mjs +4 -2
  16. package/plugins/sp/lib/inline-run.generated.mjs +47 -30
  17. package/plugins/sp/lib/plan-projection.generated.d.mts +19 -0
  18. package/plugins/sp/lib/plan-projection.generated.mjs +113 -0
  19. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
  20. package/plugins/sp/lib/quality-gate.generated.mjs +48 -230
  21. package/plugins/sp/lib/transcript.ts +49 -21
  22. package/plugins/sp/plugin.json +1 -1
  23. package/plugins/sp/references/roles.md +1 -1
  24. package/plugins/sp/scripts/batch-plan.mjs +154 -1
  25. package/plugins/sp/scripts/batch-plan.ts +48 -3
  26. package/plugins/sp/scripts/inline-run-setup.mjs +24 -13
  27. package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
  28. package/plugins/sp/scripts/pr-reviewing.ts +34 -9
  29. package/plugins/sp/scripts/quality-gate.mjs +25 -207
  30. package/plugins/sp/scripts/run-summary.mjs +24 -13
  31. package/plugins/sp/scripts/session-timeline.mjs +26 -25
  32. package/plugins/sp/scripts/session-timeline.ts +4 -18
  33. package/plugins/sp/scripts/task-diffstat.mjs +32 -29
  34. package/plugins/sp/scripts/task-diffstat.ts +34 -34
  35. package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
  36. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  37. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  38. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
  39. package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
  40. package/plugins/sp/skills/session-review/SKILL.md +5 -3
  41. package/plugins/sp/skills/spur-cli/references/decision.md +9 -2
  42. package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
  43. package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
  44. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
  45. package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
  46. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +5 -0
  47. package/plugins/sp/skills/spur-dev/references/execution-batch.md +43 -4
  48. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
  49. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
  50. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +8 -13
  51. package/spur.js +14261 -2846
  52. package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.B3GQZvj8.js} +1 -1
  53. package/web/_astro/{BoardApp.D6sjAJr_.js → BoardApp.BT7L2kSb.js} +192 -192
  54. package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
  55. package/web/_astro/{arc._BUadrdH.js → arc.vaT7tPOB.js} +1 -1
  56. package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
  57. package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
  58. package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
  59. package/web/_astro/channel.D4nG9YLf.js +1 -0
  60. package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
  61. package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
  62. package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
  63. package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
  64. package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
  65. package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
  66. package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
  67. package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
  68. package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
  69. package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
  70. package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
  71. package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
  72. package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
  73. package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
  74. package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
  75. package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
  76. package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
  77. package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
  78. package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
  79. package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
  80. package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
  81. package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
  82. package/web/_astro/{index.COh_kBxN.css → index.BhSwN8yY.css} +1 -1
  83. package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
  84. package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
  85. package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
  86. package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
  87. package/web/_astro/{linear.MzmRrNER.js → linear.BzGnBwNL.js} +1 -1
  88. package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CSbTpIVz.js} +4 -4
  89. package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
  90. package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
  91. package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
  92. package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
  93. package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
  94. package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
  95. package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
  96. package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
  97. package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
  98. package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
  99. package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
  100. package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
  101. package/web/index.html +2 -2
  102. package/plugins/sp/commands/dev-parallel.md +0 -37
  103. package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
  104. package/web/_astro/channel.DZVfto-r.js +0 -1
@@ -7,11 +7,11 @@
7
7
  * `precheck`, F96 R1) and the current working tree, then writes:
8
8
  * `.spur/run/<wbs>-diffstat.json` `{ files, insertions, deletions, paths[], sensitive }`
9
9
  *
10
- * Counts come from `git diff --numstat <base>` (tracked changes; binary rows count the
11
- * path but no lines) plus untracked files (`git ls-files --others --exclude-standard`,
12
- * newline-counted — # ponytail: untracked line counts are a newline scan, numstat-grade
13
- * binary detection is unnecessary for a lane heuristic; switch to `--numstat --no-index`
14
- * if a miscount ever flips a lane).
10
+ * Counts come from `git diff --numstat -z <base>` (tracked changes; binary rows count the path
11
+ * but no lines; NUL records carry RAW paths, so a non-ASCII or newline-bearing path reaches the
12
+ * sensitive matchers intact) plus untracked files (`git ls-files --others --exclude-standard -z`,
13
+ * newline-counted — # ponytail: untracked line counts are a newline scan, numstat-grade binary
14
+ * detection is unnecessary here; switch to `--numstat --no-index` if a miscount flips a lane).
15
15
  *
16
16
  * `sensitive` is true when ANY changed path matches the exported SENSITIVE list (0943 R2c);
17
17
  * the workflow's pre-decide guard maps sensitive (or >400 changed lines) to the safety lane
@@ -85,7 +85,7 @@ function globToRegex(glob: string): RegExp {
85
85
  .replaceAll('*', '[^/]*')
86
86
  .replaceAll('\u0000', '(?:.*/)?')
87
87
  .replaceAll('\u0001', '.*');
88
- return new RegExp(`^${escaped}$`);
88
+ return new RegExp(`^${escaped}$`, 's');
89
89
  }
90
90
 
91
91
  const SENSITIVE_MATCHERS: Array<{ label: string; test: (path: string) => boolean }> = [
@@ -125,26 +125,10 @@ function countNewlines(path: string, cwd?: string): number {
125
125
  return lines;
126
126
  }
127
127
 
128
- /**
129
- * git renders renames in --numstat paths as `old => new` (full) or `head{old => new}tail`
130
- * (partial). The sensitive matchers anchor on single paths, so both rename halves must be
131
- * surfaced — otherwise a rename INTO a sensitive path (review 0943 P3#1) classified clean.
132
- */
133
- export function expandDiffPath(raw: string): string[] {
134
- const arrow = raw.indexOf(' => ');
135
- if (arrow === -1) return [raw];
136
- const open = raw.indexOf('{');
137
- const close = raw.lastIndexOf('}');
138
- if (open !== -1 && close > open && open < arrow && arrow < close) {
139
- const inner = raw.slice(open + 1, close);
140
- const innerArrow = inner.indexOf(' => ');
141
- if (innerArrow !== -1) {
142
- const head = raw.slice(0, open);
143
- const tail = raw.slice(close + 1);
144
- return [head + inner.slice(0, innerArrow) + tail, head + inner.slice(innerArrow + 4) + tail];
145
- }
146
- }
147
- return [raw.slice(0, arrow), raw.slice(arrow + 4)];
128
+ function addCounts(add: string, del: string): [number, number] | null {
129
+ for (const count of [add, del]) if (count !== '-' && !/^\d+$/.test(count)) return null;
130
+ const [a, d] = [add === '-' ? 0 : Number(add), del === '-' ? 0 : Number(del)];
131
+ return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
148
132
  }
149
133
 
150
134
  /**
@@ -190,18 +174,34 @@ export function runDiffstat(
190
174
  }
191
175
  if (!/^[0-9a-f]{7,40}$/i.test(base)) return failSafe('run base .spur/run/<wbs>-base.sha missing or malformed');
192
176
 
193
- const numstat = git(['diff', '--numstat', base]);
177
+ const numstat = git(['diff', '--numstat', '-z', base]);
194
178
  if (numstat.status !== 0) return failSafe(`git diff --numstat failed (status=${numstat.status})`);
179
+ if (numstat.stdout.length > 0 && !numstat.stdout.endsWith('\u0000'))
180
+ return failSafe('git diff --numstat -z output is not NUL-terminated (malformed framing)');
195
181
  const paths = new Set<string>();
196
182
  let insertions = 0;
197
183
  let deletions = 0;
198
- for (const line of numstat.stdout.split('\n')) {
199
- // Binary rows report `-` for both counts; the path still counts as changed.
200
- const row = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
201
- if (!row) continue;
202
- for (const p of expandDiffPath(row[3] ?? '')) paths.add(p);
203
- if (row[1] !== '-') insertions += Number(row[1]);
204
- if (row[2] !== '-') deletions += Number(row[2]);
184
+ const records = numstat.stdout.split('\u0000').slice(0, -1); // drop the terminating NUL tail
185
+ for (let i = 0; i < records.length; i++) {
186
+ const header = records[i] ?? '';
187
+ const first = header.indexOf('\t'); // first two tabs only
188
+ const second = header.indexOf('\t', first + 1); // a raw path may itself hold more tabs
189
+ if (first < 1 || second < first + 2) return failSafe('git diff --numstat -z record is missing counts or path');
190
+ const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
191
+ if (counts === null) return failSafe('git diff --numstat -z record has a malformed count');
192
+ insertions += counts[0];
193
+ deletions += counts[1]; // once per count header, never once per rename half
194
+ const path = header.slice(second + 1);
195
+ if (path !== '') {
196
+ paths.add(path);
197
+ continue;
198
+ }
199
+ // An empty path field is Git's rename/copy form: the next two records are old and new.
200
+ const oldPath = records[++i];
201
+ const newPath = records[++i];
202
+ if (!oldPath || !newPath) return failSafe('git diff --numstat -z rename record has incomplete paths');
203
+ paths.add(oldPath);
204
+ paths.add(newPath);
205
205
  }
206
206
  const others = git(['ls-files', '--others', '--exclude-standard', '-z']);
207
207
  if (others.status !== 0) return failSafe(`git ls-files failed (status=${others.status})`);
@@ -144,14 +144,21 @@ an explicit allowlist entry.
144
144
  env dumps. Token estimates for Bash/Grep/Glob use response size **after a 4 KiB cap** and secret
145
145
  scrub; omit `tokens` when unknown (never `0`).
146
146
 
147
+ **Session lifecycle:** `.spur/context/.session.json` is a private pointer to the in-flight session.
148
+ It records the creating host's owner identity (`{host, sessionId}` — Claude's hook `session_id`, Pi's
149
+ session-manager id) and only that owner's `SessionEnd` / `session_shutdown` may write the rollup and
150
+ remove the pointer. A nested run reuses the parent's ledger for tool recording but cannot finalize
151
+ or delete it; an ownerless (legacy) pointer is never finalized and expires under the existing
152
+ idle-start window instead.
153
+
147
154
  ```jsonl
148
- {"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-1423","type":"session_start"}
149
- {"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-1423","type":"read","file":"src/dao.ts","tokens":648}
150
- {"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-1423","type":"write","file":"src/dao.ts","tokens":222,"action":"edit"}
151
- {"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-1500","type":"bash","summary":"ls -la","tokens":12}
152
- {"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-1500","type":"grep","summary":"/TODO/ src"}
153
- {"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-1500","type":"glob","summary":"**/*.test.ts"}
154
- {"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-1423","type":"session_end","totals":{"reads":5,"writes":19,"tokens":2783}}
155
+ {"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_start"}
156
+ {"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"read","file":"src/dao.ts","tokens":648}
157
+ {"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"write","file":"src/dao.ts","tokens":222,"action":"edit"}
158
+ {"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"bash","summary":"ls -la","tokens":12}
159
+ {"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"grep","summary":"/TODO/ src"}
160
+ {"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"glob","summary":"**/*.test.ts"}
161
+ {"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_end","totals":{"reads":5,"writes":19,"tokens":2783}}
155
162
  ```
156
163
 
157
164
  **Never edit `token-ledger.jsonl` by hand.** It feeds the Board **Observability → Tool Using** tab.
@@ -104,7 +104,7 @@ was meant to avoid. Let the reviewer reach its own conclusion.
104
104
 
105
105
  - **`sp:spur-dev`** owns task selection and lifecycle. When a batch contains independent tasks, the spine consults this skill for the fan-out pattern.
106
106
  - **`sp:super-planner`** is the orchestrator that executes the fan-out. Its parallel mode (documented in its agent definition) applies the patterns from this skill.
107
- - **`/sp:dev-parallel`** is the thin slash-command entry point that delegates to this skill.
107
+ - **`/sp:dev-runall --mode parallel|auto`** is the task-batch entry point; its `batch-plan.mjs order` schedule applies this skill's dependency and file-overlap rows deterministically.
108
108
 
109
109
  ## When to use
110
110
 
@@ -135,7 +135,7 @@ Do **not** use this skill for:
135
135
 
136
136
  - **`sp:spur-dev`** — the orchestration spine that consults this skill for fan-out decisions.
137
137
  - **`sp:super-planner`** — the batch orchestrator that executes fan-out patterns.
138
- - **`/sp:dev-parallel`** — the slash command entry point for parallel execution.
138
+ - **`/sp:dev-runall --mode parallel|auto`** — the slash command entry point for parallel task batches.
139
139
 
140
140
  ## Platform Notes
141
141
 
@@ -70,7 +70,7 @@ own axis.
70
70
  Surface choice and model choice stay separate axes, but the invocation surface is where the
71
71
  cheap-model hint is applied. On hosts whose native subagent invocation accepts a per-call model
72
72
  override (Claude Code: the Agent tool's `model` parameter), dispatch read-only fan-out workers —
73
- `dev-parallel --mode investigation` and competency-lens review's read-only lenses — with the host's
73
+ N-way investigation and competency-lens review's read-only lenses — with the host's
74
74
  cheapest capable model. The rule is owned by
75
75
  [fan-out-patterns.md § Cheap-model hint for read-only fan-out](fan-out-patterns.md#cheap-model-hint-for-read-only-fan-out)
76
76
  and carries two prohibitions and one exemption: invocation-side only, **never** a subagent
@@ -134,13 +134,13 @@ blocker** — never a permission-probing subsystem:
134
134
  [Keep a durable progress ledger](../SKILL.md#keep-a-durable-progress-ledger)).
135
135
 
136
136
  Read-only investigation fan-out avoids the write-permission class by construction:
137
- `dev-parallel --mode investigation` and competency-lens review's read-only lenses dispatch
137
+ N-way investigation and competency-lens review's read-only lenses dispatch
138
138
  **read-only worker shapes** — search/read tools only, with no write surface declared.
139
139
 
140
140
  ## Cheap-model hint for read-only fan-out
141
141
 
142
142
  On hosts whose native subagent invocation accepts a per-call model override (Claude Code: the Agent
143
- tool's `model` parameter), dispatch read-only fan-out workers — `dev-parallel --mode investigation`
143
+ tool's `model` parameter), dispatch read-only fan-out workers — N-way investigation
144
144
  and competency-lens review's read-only lenses — with the host's cheapest capable model. The rule is
145
145
  invocation-side, with two prohibitions and one exemption:
146
146
 
@@ -112,9 +112,13 @@ unrelated uncommitted changes make safe editing or committing ambiguous.
112
112
  **Current-HEAD invariant.** Treat the PR's pushed `headRefOid` as the review identity. After every
113
113
  preflight, push, commit, and request, record the returned HEAD and require `wait`, `collect`, and
114
114
  the report to match that exact HEAD. A result for an older commit is stale; a result absent for the
115
- current HEAD is pending, not clean. Reject uncorrelated issue/conversation comments rather than
116
- using them to manufacture a current-HEAD result. In `fix` mode, discard the old result after the
117
- fix commit and judge only the forced re-review for the new pushed HEAD.
115
+ current HEAD is pending, not clean. `wait` re-asserts this at two points, not one: at the start of
116
+ every poll, and again immediately before it emits FOUND/CLEAN/TIMEOUT — a head that moved while the
117
+ review was being fetched, or between polls, fails the wait (exit 2) instead of accepting an old-head
118
+ result. The pinned identity is the PR number AND the expected head, so a different PR appearing on
119
+ the branch is also a failure, never a silent retarget. Reject uncorrelated issue/conversation
120
+ comments rather than using them to manufacture a current-HEAD result. In `fix` mode, discard the old
121
+ result after the fix commit and judge only the forced re-review for the new pushed HEAD.
118
122
 
119
123
  **Reviewer authority.** Codex is an independent reviewer, not an authority. For every finding that
120
124
  may lead to a code change: inspect the referenced code, verify reachability and intended behavior,
@@ -101,9 +101,11 @@ issues with its evidence. Notes stay report-only.
101
101
  refresh it with `superskill install sp` before reporting numbers; report `n/a` until then.
102
102
  The transcript resolves from `--transcript`, else `CLAUDE_CODE_SESSION_ID`, else `PI_SESSION_FILE`
103
103
  (the pi host sets it and no Claude session id); with none of them the measurement reports
104
- `available:false` with a reason and the table renders `n/a` instead of guessing. A
105
- skill-injected body (`<skill name="…">`) is counted as activity in `injectedPrompts`, never as a
106
- segment — only the operator's own prompts segment the session.
104
+ `available:false` with a reason and the table renders `n/a` instead of guessing. A pi
105
+ `<skill name="…">` wrapper row is the OPERATOR'S own `/skill` invocation, not an injected body:
106
+ it opens a segment rendered `/<name> <args>` (or `/<name>` when no arguments follow the closing
107
+ tag), and the wrapper body never appears in the prompt excerpt. Only the operator's prompts
108
+ segment the session.
107
109
  2. **Inventory outcomes.** List requested outcomes and classify each as completed, partial, blocked,
108
110
  or not attempted. Collapse repeated attempts into one outcome.
109
111
  3. **Classify issues.** For every material issue, distinguish resolved, open, or attempted. Record
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spur-cli-decision
3
- description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.16 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093."
3
+ description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.22 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093."
4
4
  see_also:
5
5
  - spur-cli
6
6
  ---
@@ -79,6 +79,13 @@ exist there). The final `catalog-default` maker is the hub built-in `typesafe`
79
79
  without a configured backend still produce deterministic closed-vocabulary outcomes, which is why
80
80
  `run` is usable in tests and scripts with no model access.
81
81
 
82
+ **Available makers:** the three upstream built-ins (`typesafe`, `fm-local`, `laya-local`) plus
83
+ Spur's hosted `clef` (Cloudflare Workers AI via `@gobing-ai/ts-decision-clef`). `clef` registers
84
+ only when both `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_AUTH_TOKEN` are set in the environment;
85
+ without them it is absent from `registeredMakers`, so naming it in config or `--maker` is a config
86
+ error, never a silent fallback. Its model defaults to `clef-flash` (a catalog `model` selects
87
+ `clef`).
88
+
82
89
  ## Examples
83
90
 
84
91
  ```bash
@@ -93,6 +100,6 @@ spur decision status --json | jq '.loadErrors' # audit catalog health
93
100
  ## References
94
101
 
95
102
  - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface)
96
- - Hub: `@gobing-ai/ts-ai-decision` 0.5.16 (`DecisionService`, catalog schema, maker registry)
103
+ - Hub: `@gobing-ai/ts-ai-decision` 0.5.22 (`DecisionService`, catalog schema, maker registry)
97
104
  - App service: `packages/app/src/decision/decision-service.ts`
98
105
  - Shipped catalogs: `config/decisions/*.yaml`
@@ -22,7 +22,7 @@ shapes live in `apps/cli/src/commands/projects.ts`.
22
22
  | `clean` | Purge missing project folders and terminate lingering processes (alias: `refresh`) | `--no-terminate-processes` `--json` |
23
23
  | `list` | List entries with live running status | `--json` `--fleet` |
24
24
  | `start <target>` | Start or reuse a detached project server | `--port <n>` `--json` |
25
- | `stop <target>` | Best-effort stop the listener and clear its recorded port | `--json` |
25
+ | `stop <target>` | Ownership-verified stop of the listener and clear its recorded port | `--json` |
26
26
 
27
27
  Every verb also advertises `--json-envelope`; use the facade's machine-output contract. Success is
28
28
  exit `0`; validation, registry, spawn, health, or lookup failure is exit `1`.
@@ -36,8 +36,8 @@ exit `0`; validation, registry, spawn, health, or lookup failure is exit `1`.
36
36
  - `add` requires an existing path, resolves a relative path from the current working directory, and
37
37
  defaults the display name to its basename. It upserts; it does not start a server. The current
38
38
  source does not enforce a `.spur/` marker or directory type.
39
- - `list` automatically heals tilde paths, verifies project directories exist on disk (purging missing entries and terminating lingering processes), and heals stale entries to `port: 0` before reporting `running`.
40
- - `clean` (alias `refresh`) explicitly purges non-existent project directories from `projects.json`, probes live ports for removed entries, and cleanly terminates orphaned listening processes (SIGTERM with bounded wait and SIGKILL escalation). Supports `--no-terminate-processes` to skip process kills.
39
+ - `list` automatically heals tilde paths, verifies project directories exist on disk (purging missing entries and terminating only ownership-verified lingering processes), and heals stale entries to `port: 0` before reporting `running`.
40
+ - `clean` (alias `refresh`) explicitly purges non-existent project directories from `projects.json`, probes live ports for removed entries, and terminates orphaned listeners only after ownership verification (SIGTERM with bounded wait and SIGKILL escalation, each signal gated on `service:spur` identity plus exact project path and a stable PID; foreign or unverified listeners are never signaled). Supports `--no-terminate-processes` to skip process kills.
41
41
  - `list --fleet` (0835/0858) additionally resolves each project's `agent.fleet` section from that
42
42
  project's `.spur/config.yaml` under the existing verb (no new noun). Per project it prints one line
43
43
  per member: instance id (the spec id / mailbox identity), `role`, resolved `executor`,
@@ -76,8 +76,10 @@ port is returned idempotently; otherwise the service allocates a port (3000–39
76
76
  set), spawns `spur serve --host 127.0.0.1 --no-open` detached in the project root, waits for the port,
77
77
  then persists it.
78
78
 
79
- `stop` finds processes bound to the recorded port, sends `SIGTERM` best-effort while excluding the
80
- CLI and its parent, and clears the registry port. It is not a persistent supervisor contract.
79
+ `stop` signals only a listener that proves `service:spur` identity plus the exact registered project
80
+ path with a stable rediscovered PID; it refuses foreign or unverified listeners with exit 1 and
81
+ preserves the port, treats an absent listener as idempotent success, and excludes the CLI and its
82
+ parent. It is not a persistent supervisor contract.
81
83
  `remove` only removes the registry entry; stop a running project first when process cleanup matters.
82
84
 
83
85
  Raw JSON success payloads are verb-specific (`project`, `projects`, `removed`, `stopped`, or start
@@ -110,6 +110,8 @@ Read this `SKILL.md` plus your mode's files below before the first dispatch; eve
110
110
  - --worktree: `references/execution-batch.md`, `references/execution-worktree-setup.md`
111
111
  - --mode parallel: `references/execution-batch.md`, `references/execution-parallel-isolation.md`
112
112
 
113
+ `--mode auto` reads as sequential-inline until § 2.4a resolves it.
114
+
113
115
  Byte budgets for these sums live in `plugins/sp/tests/bootstrap-budget.test.ts`
114
116
  (`BOOTSTRAP_BUDGETS_BYTES`, task 1128); the test fails the build when a mode's bootstrap grows.
115
117
 
@@ -88,6 +88,33 @@ DD-06 permissive start: treat only the core tier as must-ship initially; promote
88
88
  prove important in a later iteration. (When tiered gating lands in `spur feature check`, these tags
89
89
  become the gate input — the CLI stays the validator; this guide stays the convention.)
90
90
 
91
+ ### Home-node tier (structural, exempt from the orphan sweep)
92
+
93
+ A **home node** carries one structural or placement scenario that states *where the work lives*
94
+ (e.g. "work on the kk workflow-run ledger has a home node"). No task can cover that statement — it
95
+ describes planning placement, not behaviour — so tag it `@home-node` to remove it from the
96
+ feature-side DD-09 orphan sweep (`L4.uncovered-feature-scenario`):
97
+
98
+ ```gherkin
99
+ @home-node
100
+ Scenario: R1 — Work on the kk workflow run ledger has a home node
101
+ Given a change that targets the kk workflow run ledger workflow
102
+ When the change is planned as a feature
103
+ Then it is feature G7 or a child feature of G7
104
+ ```
105
+
106
+ Rules:
107
+
108
+ - **Scenario-level tag only.** A `Feature:`-level `@home-node` exempts nothing; the tag is read per
109
+ scenario.
110
+ - **Exempts the orphan sweep only.** Scenario satisfaction (0340), verdict-row matching, and the
111
+ known-issues ratchet are unchanged — a tagged scenario a task actually covers is classified
112
+ exactly as before.
113
+ - **Never a hiding place.** Use it only for a scenario no task can own. A deliberately uncoverable
114
+ *capability* scenario belongs to `@core`/`@edge` and must stay in the sweep: tagging it
115
+ `@home-node` hides a real gap. Wording is never the signal.
116
+ - A feature with **no linked task** is still flagged as `L4.orphan-scenarios`, tagged or not.
117
+
91
118
  ## Scenario-title stability
92
119
 
93
120
  The scenario title is the **identity key** for traceability. When a task references a
@@ -23,7 +23,7 @@ The `Skill()` commands back onto six skills: `sp:spur-dev` (planning + execution
23
23
  `sp:code-verification` (SECUA review + traceability), `sp:parallel-execution` (fan-out / parallel batch),
24
24
  and `sp:brainstorm` (structured ideation). The `runall` operation (#13) is `sp:spur-dev`'s batch entry — it delegates the driver loop to the
25
25
  `sp:super-planner` agent (the batch orchestrator) per [execution-batch.md](execution-batch.md).
26
- `dev-parallel` (#13a) is the parallel counterpart. `dev-brainstorm` carries the two artifact exits — `--task` (one task) and `--feature` (validated
26
+ Parallel task batches are `dev-runall --mode parallel|auto` (`dev-parallel` retired 2026-10-10). `dev-brainstorm` carries the two artifact exits — `--task` (one task) and `--feature` (validated
27
27
  feature with BDD AC; the front-half entry that hands off to `dev-plan`). The 4 `inline` commands
28
28
  cover git tooling and operational utilities that have no natural skill home — creating a skill for
29
29
  each would be scope creep for one-liner procedures.
@@ -86,8 +86,7 @@ each would be scope creep for one-liner procedures.
86
86
  | 11b | job-resume | `dev-job-resume` | `Skill()` | `sp:spur-dev` (`job-resume`) | `--file <path>` |
87
87
  | 12 | brainstorm | `dev-brainstorm` | `Skill()` | `sp:brainstorm` (`dev-brainstorm`) | `<topic> [--depth <basic\|detailed\|comprehensive>] [--options <n>] [--agent <inline\|auto\|name>] [--skip-discovery] [--task [<feature-id>]] [--feature [<parent-id>]] [--next]` |
88
88
  | 12a | find-way | `dev-find-way` | `Skill()` | `sp:wayfinder` (worktree-isolated) | `<idea> \| <feature-id> [<wbs>] [--agent <inline\|auto\|name>] [--auto] [--worktree [<name>]] [--wrap]` |
89
- | 13 | runall | `dev-runall` | `Skill()` → agent | `sp:spur-dev` (`runall`) → `sp:super-planner` | `--tasks <selector> [--feature <id>] [--mode <sequential\|parallel>] [--keep-going] [--auto] [--agent <inline\|auto\|name>] [--json] [--wrap] [--next] [--continue] [--worktree [<name>]] [--no-summary] [--defer-gate]` |
90
- | 13a | parallel | `dev-parallel` | `Skill()` | `sp:parallel-execution` | `--tasks <selector> [--feature <id>] [--mode <fan-out\|review-panel\|investigation>] [--agent <inline\|auto\|name>] [--json] [--defer-gate]` |
89
+ | 13 | runall | `dev-runall` | `Skill()` → agent | `sp:spur-dev` (`runall`) → `sp:super-planner` | `--tasks <selector> [--feature <id>] [--mode <sequential\|parallel\|auto>] [--concurrency <n>] [--keep-going] [--auto] [--agent <inline\|auto\|name>] [--json] [--wrap] [--next] [--continue] [--worktree [<name>]] [--no-summary] [--defer-gate]` |
91
90
  | 14 | wrap | `dev-wrap` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `<wbs> [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
92
91
  | 15 | wrapall | `dev-wrapall` | `Skill()` | `spur workflow run` (wrapup-pipeline) | `[--since <iso>] [--feature <id>] [--status <s>] [--agent <inline\|auto\|name>] [--auto] [--merge] [--dry-run]` |
93
92
  | 16 | idea | `dev-idea` | `Skill()` | inline driver (`idea-pipeline`) or async workflow | `"<idea>" \| --from-file <path> [--skip-design] [--agent <inline\|auto\|name>] [--auto]` |
@@ -178,6 +178,11 @@ the wrap's refusal of non-done tasks remains the hard invariant.
178
178
 
179
179
  **Close-out commit (1129 R2/R5).** In sequential mode the wrap's writes (learnings, metrics,
180
180
  doc-sync) land in the invoking tree and are committed as ONE close-out commit after the wrap exits.
181
+ **Under `--worktree` they land in the EXECUTION tree instead** and ride the branch as its own commit
182
+ on `$BRANCH`: Step 6 runs before WT-4, so a close-out commit on the base ref would advance it and
183
+ make the batch's own FF-merge impossible (the fresh base tip would no longer be an ancestor of the
184
+ batch tip — the step that makes the close-out the report's own contradiction). The close-out commit
185
+ is never a point on the base ref.
181
186
  Stage them through the guard — never a free-form `git add` — so a concurrent writer's file cannot
182
187
  ride into the close-out commit (incident `36f274590`). `$RUN_ID` is the id Step 3 fixed before
183
188
  `commit-guard start` (execution-batch.md § Step 3); an unset id makes the guard exit 1 — fail closed:
@@ -204,7 +204,18 @@ For each dependency edge to a task **outside** the frozen set, resolve its curre
204
204
 
205
205
  ### 2.4 Topological sort
206
206
 
207
- Topological-sort the remaining in-set, non-blocked tasks using Kahn's algorithm:
207
+ Do not hand-run the sort — compute it. Write the remaining in-set, non-blocked tasks to a temp file
208
+ as a JSON array of `{wbs, dependencies, files?}` (`dependencies` from `spur task show <wbs> --json`
209
+ frontmatter; `files` = repo paths the task's Design/Plan names, omit when it names none) and run:
210
+
211
+ ```bash
212
+ node "$(superskill script path sp batch-plan.mjs)" order --tasks <tasks.json> [--concurrency <n>]
213
+ # → {"order":[…],"waves":[[…],…],"cycle":null|[…],"overlaps":[…],"mode":"sequential|parallel","reason":"…"}
214
+ ```
215
+
216
+ `order` is the execution plan; a non-null `cycle` is the abort below. `waves`, `overlaps`, `mode`
217
+ and `reason` feed `--mode auto` (§ 2.4a) and the batch report. The script implements Kahn's
218
+ algorithm (ADR-130 glue over `scheduleBatch` in `packages/app/src/workflow/plan-projection.ts`):
208
219
 
209
220
  1. Seed the queue with zero-indegree nodes, **sorted WBS-ascending** (deterministic tie-break).
210
221
  2. Repeatedly dequeue the lowest-WBS zero-indegree node, emit it, decrement its successors'
@@ -214,7 +225,35 @@ Topological-sort the remaining in-set, non-blocked tasks using Kahn's algorithm:
214
225
  **Cycle handling (R2.3):** a cycle aborts the **entire batch** before any task runs. Reconstruct a
215
226
  representative cycle path via DFS over the remaining unsorted nodes and report it (e.g.
216
227
  `0040 → 0042 → 0040`). Do not run any task in a cyclic batch — running a prefix would partially
217
- execute work whose ordering is undefined.
228
+ execute work whose ordering is undefined. The script reports it as `cycle` (e.g.
229
+ `["0040","0042","0040"]`) with an empty `order`.
230
+
231
+ **File overlap.** Two tasks that declare the same file get a serialization edge oriented by their
232
+ dependency order (`overlaps[]` records each), so overlap never creates a cycle and never puts
233
+ overlapping tasks in the same wave (`sp:parallel-execution` row 3). It only matters under parallel
234
+ execution; sequential order stays a valid topological order either way.
235
+
236
+ ### 2.4a `--mode auto` resolution
237
+
238
+ `--mode auto` is decided **once, here, by the active orchestrator** (the host session, or
239
+ `sp:super-planner` when an explicit executor is set), after freeze, Step 2.3 blocking and
240
+ the § 2.4 schedule, and before any task runs:
241
+
242
+ | Condition (first match wins) | Resolved mode |
243
+ | --- | --- |
244
+ | `--worktree` present | `sequential` (parallel rejects `--worktree`) |
245
+ | `--agent inline` explicitly passed | `sequential` (inline is the host-session driver) |
246
+ | script `mode` is `sequential` (fewer than 2 tasks, `--concurrency 1`, or every wave holds one task) | `sequential` |
247
+ | script `mode` is `parallel` | `parallel` |
248
+
249
+ - **Sequential resolution** continues the Step 3 loop with the frozen `order` (host-inline, or
250
+ subprocess per task under an explicit executor).
251
+ - **Parallel resolution** hands off to the explicit/parallel path as
252
+ `runall --tasks <order as comma WBS list> --mode parallel` plus the operator's other flags. The
253
+ explicit list carries the frozen membership, so the feature preflight (Step 1) is not re-run.
254
+ - `--defer-gate` survives only a parallel resolution; a sequential one drops it.
255
+ - The batch report records `mode: auto → <resolved>` with the script's `reason`, the widest wave,
256
+ and any `overlaps`.
218
257
 
219
258
  ### 2.5 Result
220
259
 
@@ -321,8 +360,8 @@ emit batch report (per-task outcome + preflight skips + recovery hints + autoRep
321
360
  ```
322
361
 
323
362
  Parallel mode keeps the same lifecycle but swaps the inner loop for the per-task-worktree fan-out
324
- specified in [§ Parallel isolation](#parallel-isolation---mode-parallel): identify a zero-edge,
325
- non-overlapping subset; **preflight each** selected task; run each ready task's `task-pipeline.yaml`
363
+ specified in [§ Parallel isolation](#parallel-isolation---mode-parallel): take the zero-edge,
364
+ non-overlapping frontier from the § 2.4 schedule's waves; **preflight each** selected task; run each ready task's `task-pipeline.yaml`
326
365
  invocation in its own create-mode worktree; integrate by rebase + `--ff-only` as tasks finish;
327
366
  recovery stays **sequential** (one WBS). If any decision-framework check fails, serialize and
328
367
  record the reason.
@@ -45,7 +45,10 @@ emit batch report (per-task outcomes + preflight skips + recovery hints + batch
45
45
  - **Eligibility (R2).** A task becomes eligible only when all of its in-set dependencies are
46
46
  **integrated** onto the base ref — pipeline-terminal is not enough, because its branch must
47
47
  rebase over theirs. Omitting `--mode` stays sequential; this loop is entered only via
48
- `--mode parallel` on `/sp:dev-runall`.
48
+ `--mode parallel` on `/sp:dev-runall`, directly or as a `--mode auto` resolution
49
+ ([execution-batch.md § 2.4a](execution-batch.md#24a---mode-auto-resolution)).
50
+ - **Fan-out set.** Run order and eligibility come from the § 2.4 `batch-plan.mjs order` schedule;
51
+ its file-overlap edges are in-set dependencies here, so overlapping tasks never run at once.
49
52
  - **Workdir.** `spur workflow run` records the launch workdir (0784 R1), so launch with `cwd` = the
50
53
  task worktree: resume and `.spur/run/` artifact paths resolve there. Read the verdict from
51
54
  `$WT/.spur/run/<wbs>-verdict.json` before integration; WT-4a persists it into the invoking tree.
@@ -148,7 +148,7 @@ halt-on-first-failure. Never silently retried; the failure is still reported.
148
148
 
149
149
  ### `--defer-gate` — opt-in parallel-batch gate policy (task 1111 / H15)
150
150
 
151
- **Applies to:** `/sp-dev-runall --mode parallel`, `/sp-dev-parallel --mode fan-out`.
151
+ **Applies to:** `/sp-dev-runall --mode parallel`, and `--mode auto` when it resolves to parallel.
152
152
  **Default:** off — every task pipeline runs its own FULL project gate inside its worktree.
153
153
 
154
154
  With `--defer-gate`, each task's `test` / `test-recheck` hop runs the **light tier** (changed-scope
@@ -167,9 +167,10 @@ Two properties are load-bearing and are asserted structurally:
167
167
  project gate; the per-task token is `DEFERRED`, and the `test → triage` /
168
168
  `test-recheck → triage` guards accept it only while `deferQualityGate = "true"`.
169
169
 
170
- `--defer-gate` without `--mode parallel` is rejected — the flag exists to move one full gate from N
171
- worktrees to the one place the integrated tree exists. Sequential and inline batches never set the
172
- var. ADR-124 carries the dated clarification.
170
+ `--defer-gate` with `--mode sequential` (or omitted `--mode`) is rejected — the flag exists to move
171
+ one full gate from N worktrees to the one place the integrated tree exists. Under `--mode auto` it
172
+ applies only if the batch resolves to parallel; a sequential resolution drops it with a report note.
173
+ Sequential and inline batches never set the var. ADR-124 carries the dated clarification.
173
174
 
174
175
  **Anchor:** `#flag-defer-gate`.
175
176
 
@@ -177,11 +178,11 @@ var. ADR-124 carries the dated clarification.
177
178
 
178
179
  **Anchor:** `#flag-concurrency`.
179
180
 
180
- `dev-runall` under `--mode parallel` (task 0931): at most `<n>` task pipelines run at once
181
+ `dev-runall` under `--mode parallel` (task 0931), or `--mode auto` resolved to parallel: at most `<n>` task pipelines run at once
181
182
  (default 2, `n ≥ 1`). A dependent task starts only after every in-set dependency has been
182
183
  **integrated** onto the base ref — see
183
184
  [execution-batch.md § Parallel isolation](execution-batch.md#parallel-isolation---mode-parallel).
184
- Ignored in sequential mode. Each worker is one more concurrent quality gate; raise it only when
185
+ Ignored in sequential mode; `--concurrency 1` pins `--mode auto` to sequential. Each worker is one more concurrent quality gate; raise it only when
185
186
  the host has the CPU for it.
186
187
 
187
188
  ### `--continue` — resume an interrupted batch from checkpoint
@@ -271,12 +272,12 @@ is the contract; divergence between `--dry-run` and the real run is a bug.
271
272
 
272
273
  **Anchor:** `#flag-tasks`.
273
274
 
274
- Batch operation (`dev-parallel`, `dev-refineall`, `dev-review`, `dev-runall`, `dev-verifyall`). An
275
+ Batch operation (`dev-refineall`, `dev-review`, `dev-runall`, `dev-verifyall`). An
275
276
  explicit selector — WBS list, status pseudo-list (`todo`, `wip`), `feature:<id>`, or `ready` —
276
277
  resolving to the set the batch runs over. On `dev-review` the selector is restricted to the
277
278
  review-safe forms: comma WBS list and `feature:<id>` — status pseudo-lists and `ready` are
278
- rejected (they select work to do, not work to review). Required on `dev-parallel`, `dev-runall`,
279
- and `dev-verifyall`, where `--feature` is an optional restrictor. On `dev-refineall` it is instead
279
+ rejected (they select work to do, not work to review). Required on `dev-runall` and
280
+ `dev-verifyall`, where `--feature` is an optional restrictor. On `dev-refineall` it is instead
280
281
  one of a required pair — supply exactly one of `--feature` or `--tasks`.
281
282
 
282
283
  ### `--mode <kind>` — select an execution mode
@@ -284,8 +285,9 @@ one of a required pair — supply exactly one of `--feature` or `--tasks`.
284
285
  **Anchor:** `#flag-mode`.
285
286
 
286
287
  Select an execution mode: `full|implement` on `dev-run` (full pipeline vs implement-only),
287
- `sequential|parallel` on `dev-runall` (serial vs fanned-out-independent-subset),
288
- `fan-out|review-panel|investigation` on `dev-parallel`, the reconstruction depth
288
+ `sequential|parallel|auto` on `dev-runall` (serial, fanned-out independent subset, or chosen from the
289
+ frozen batch's dependency waves — see [execution-batch.md § 2.4](execution-batch.md#24-topological-sort)),
290
+ the reconstruction depth
289
291
  `briefing|structure|architecture|design|full` on `dev-reverse`, and `daily|ad-hoc` on
290
292
  `dev-find-issue` (the history-anatomy report mode). Mode selection is explicit and orthogonal
291
293
  to `--next`.
@@ -86,7 +86,9 @@ fallback) — labels are display addresses only, never an execution key.
86
86
  1. Resolve the command inputs, `--auto`, and any explicit `--vars` — **without reading the selected
87
87
  YAML yet**. An explicit non-inline executor selection chooses the subprocess workflow path.
88
88
  2. Allocate a collision-resistant inline run id (`uuidgen`, with a timestamp/pid fallback), create
89
- `.spur/run/` for attempt staging and `.spur/memory/runs/` for retained records, and use the two-file run record (task 0927) — append lines to
89
+ `.spur/run/` and the run's scratch subdirectory `.spur/run/<run-id>/` for attempt staging (action
90
+ batches, captured stage output, temporary status/answer files) plus `.spur/memory/runs/` for
91
+ retained records, and use the two-file run record (task 0927) — append lines to
90
92
  `.spur/memory/runs/<run-id>.md` and read machine state from `.spur/memory/runs/<run-id>.state.json`.
91
93
  The run id lives in those two files and the persisted run row — never stage it in an ad-hoc
92
94
  `*.env` file: the `no-env-files` gate rule flags any `.env` name, so run-scoped staging files
@@ -606,7 +608,7 @@ prompt the host cannot see is invisible until join. Record one run-log line per
606
608
  stage <id> permission precheck: ok | <missing capability>
607
609
  ```
608
610
 
609
- Read-only investigation fan-out (`/sp:dev-parallel --mode investigation`) dispatches read-only
611
+ Read-only investigation fan-out (N-way investigation) dispatches read-only
610
612
  worker shapes by construction — see the [pre-dispatch permission
611
613
  rule](../../parallel-execution/references/fan-out-patterns.md).
612
614
 
@@ -761,21 +763,14 @@ grading its own work defeats the stage. A continuation stage also dispatches fre
761
763
  same-task subagent exists (the earlier stage ran host-inline or below the dispatch floor), when a
762
764
  host-owned gate sat between the stages, or when the platform cannot address completed subagents.
763
765
 
764
- **Implement and test-fix workers never run the full gate (task 1127 R8 / task 1142 R3).** Since 1127 the full gate
765
- takes a host-wide lock (`SPUR_GATE_LOCK_DIR`, default `~/.config/spur/run/full-gate.lock`):
766
- concurrent full-gate runs serialize, and a manual `spur-check` (wrapped via
767
- `scripts/commands/gate-lock.ts`) holds the same lock. A dispatched **implement** or `test-fix` child
768
- that re-ran the full gate would queue behind every other gate on the host and burn its dispatch
769
- timeout waiting instead of working. Workers use the light tier as their iteration check (task 1142 R3) in exact order:
766
+ **Implement and test-fix workers never run the full gate (task 1142 R3).** Workers use the light tier as their iteration check (task 1142 R3) in exact order:
770
767
  1. the **changed-path matrix** (`code-implementation/SKILL.md` § Changed-path targeted checks) over the paths they touched;
771
768
  2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome, per-workspace typecheck, rule presets, related tests).
772
769
  Neither step takes the full-gate lock. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
773
770
  `--gate-log <path>` through to the child **verbatim**: never paraphrase it into "re-run the named
774
- gate command". That paraphrase is not cosmetic — it dropped this rule in the H15 run (task
775
- Background, seq 232) and sent a worker into the locked gate. The pipeline's next `test` (recheck)
776
- stage re-acquires the lock and delivers the deciding verdict; that deciding run may wait for a live
777
- holder, so a queued worker's effective budget is dispatch timeout minus observed lock queue wait,
778
- not the timeout alone.
771
+ gate command". That paraphrase is not cosmetic — it sent a worker into a full gate run instead of
772
+ targeted iteration checks. The pipeline's next `test` (recheck)
773
+ stage delivers the deciding verdict.
779
774
 
780
775
  **Null or timed-out dispatch (task 1134 R9).** The dispatch bound stays the existing YAML
781
776
  `timeoutMs` (`implementTimeoutMs` / `stepTimeoutMs`), passed to the host dispatch as today — R9 adds