@delegance/claude-autopilot 7.11.0-pre.2 → 7.11.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,172 @@
2
2
 
3
3
  - v5.6 Phase 7 (docs reconciliation) — pending.
4
4
 
5
+ ## 7.11.0 — 2026-05-20
6
+
7
+ **v7.11.0 — Concurrent subagent dispatch (GA).** This release closes the
8
+ v7.11.0 stack landed as `v7.11.0-pre.1` through `v7.11.0-pre.5` and ships
9
+ the user-facing skill + schema + release wiring. The implementation was
10
+ split into six PRs for reviewability (see PR links below); the published
11
+ version users install is `7.11.0` on npm `latest`. The five `pre.*` tags
12
+ remain in git history as the implementation breakdown.
13
+
14
+ Spec: [PR #187](https://github.com/axledbetter/claude-autopilot/pull/187)
15
+ ([`docs/superpowers/specs/2026-05-19-v7.11.0-concurrent-subagent-execution-design.md`](docs/superpowers/specs/2026-05-19-v7.11.0-concurrent-subagent-execution-design.md)).
16
+
17
+ Implementation PRs:
18
+ - [#197](https://github.com/axledbetter/claude-autopilot/pull/197) — dep graph foundations (`v7.11.0-pre.1`).
19
+ - [#198](https://github.com/axledbetter/claude-autopilot/pull/198) — locking primitives (`v7.11.0-pre.2`).
20
+ - [#199](https://github.com/axledbetter/claude-autopilot/pull/199) — event + budget atomicity (`v7.11.0-pre.3`).
21
+ - [#200](https://github.com/axledbetter/claude-autopilot/pull/200) — scheduler + worktree lifecycle (`v7.11.0-pre.4`).
22
+ - [#201](https://github.com/axledbetter/claude-autopilot/pull/201) — merge orchestrator (`v7.11.0-pre.5`).
23
+ - This PR (#193) — SKILL integration + release wiring.
24
+
25
+ ### Added
26
+
27
+ - **Concurrent subagent dispatch in autopilot Step 3.** The sequential
28
+ one-task-at-a-time loop is replaced with a tier-based scheduler that
29
+ dispatches independent plan tasks in parallel into isolated per-task
30
+ worktrees under `.claude/worktrees/<run-ulid>/<task-id>/`. A dedicated
31
+ integration worktree at `.claude/worktrees/<run-ulid>/integration/` owns
32
+ the only checkout of `feature/<topic-slug>`; the main repo working tree
33
+ is never touched for the duration of the run.
34
+ - **`depends_on:` annotation in plan files** (documented in
35
+ `skills/writing-plans/SKILL.md`). Tasks declare dependencies by `### Task
36
+ N: <name>` reference; the scheduler builds a DAG, detects cycles, and
37
+ topologically sorts tasks into dispatchable tiers. Multi-commit task
38
+ branches are cherry-picked in `git rev-list --reverse base..tip` order,
39
+ producing a linear feature-branch history.
40
+ - **`concurrency:` config block in `guardrail.config.yaml`** (schema:
41
+ `presets/schemas/guardrail.config.schema.json`). New keys:
42
+ - `maxParallelSubagents` (int, **range 1..8**, default 3) — invalid
43
+ values fail at config-load with a schema validation error.
44
+ - `perSubagentTimeoutMs` (int ms, default 30 minutes).
45
+ - `assumeIndependentWithoutDependsOn` (bool, default false).
46
+ - `useDedicatedMergeWorktree` (bool, default true).
47
+ - `allowMergeCommitsInTasks` (bool, default false).
48
+ - **`budgets.perSubagentUSD`** — HARD cost cap per subagent. Dispatch is
49
+ rejected when `preFlightEstimate > perSubagentUSD`; an in-flight
50
+ subagent is SIGTERMed (process group; SIGKILL after 30s) and emits
51
+ `task.failed` if actual cost exceeds the cap mid-execution. Set to
52
+ `null` to disable.
53
+ - **11 new run-state event types** for concurrent dispatch:
54
+ `task.started`, `task.budget_reserved`,
55
+ `task.budget_increased_reservation`, `task.budget_released`,
56
+ `task.completed`, `task.failed`, `task.merged`,
57
+ `task.merge_conflict`, `task.merge_aborted`, `task.timeout`,
58
+ `task.budget_halt`. All event writes route through a per-run serialized
59
+ writer that holds an exclusive `flock` on `events.ndjson` for the
60
+ `(replay → check → append → release)` critical section. **The
61
+ append is a single `O_APPEND` write (atomic at the byte level) but
62
+ is NOT followed by `fsync` per event** — see "Audit-log durability"
63
+ below for the rationale and the no-fsync limitation.
64
+ - **Cross-process repo lock** at `.claude/run-state/repo.lock`, acquired
65
+ by every CLI command that mutates repo state (autopilot, run resume,
66
+ runs gc, runs cleanup). Uses `flock(LOCK_EX|LOCK_NB)` — NOT
67
+ check-then-create. Stale-lock detection (PID not running on host AND
68
+ acquired > 1 hour ago) surfaces an explicit
69
+ `claude-autopilot runs cleanup --force-unlock` recovery command — no
70
+ auto-clear.
71
+ - **In-process git operation queue** serializing all repo-level git
72
+ operations (worktree add/remove, branch create/delete, cherry-pick,
73
+ abort, GC) so concurrent scheduler callers cannot corrupt
74
+ `.git/refs/` or pack files.
75
+ - **Public package subpath export** `./concurrent-dispatch` — consumers
76
+ can `import { runScheduler, computeEffectiveConcurrency }` from
77
+ `@delegance/claude-autopilot/concurrent-dispatch` without deep-importing
78
+ into compiled paths.
79
+ - **Skill integration:** `skills/autopilot/SKILL.md` Step 2 now creates
80
+ `feature/<topic-slug>` as a ref-only (no checkout in main worktree),
81
+ and Step 3 invokes the concurrent dispatcher with the fallback rule
82
+ enforced by `src/core/concurrent-dispatch/dep-graph.ts`. New skill
83
+ `skills/writing-plans/SKILL.md` documents the `depends_on:` annotation,
84
+ the three-clause fallback policy, and walks through a mixed
85
+ annotated/unannotated plan example.
86
+
87
+ ### Changed — new failure modes (operator-visible)
88
+
89
+ These are **new behaviors v7.10.0 did not have**. Read carefully before
90
+ upgrading:
91
+
92
+ - **Cherry-pick conflict halts the run.** The merge orchestrator does NOT
93
+ auto-resolve, even for trivial conflicts. Diagnostics
94
+ (`git diff --name-only --diff-filter=U`, `git ls-files -u`, full
95
+ porcelain) are persisted to
96
+ `.claude/run-state/<run-ulid>/conflicts/<task-id>.md` BEFORE
97
+ `cherry-pick --abort` clears the working tree. The user resolves by
98
+ (a) adding an explicit `depends_on:` to the plan and rerunning,
99
+ (b) manually resolving in the preserved worktree, or
100
+ (c) marking the offending pair as sequential in
101
+ `guardrail.config.yaml`.
102
+ - **Interrupted tasks require explicit confirmation on resume.** A task
103
+ in state `started` with NO terminal event (no `task.completed`,
104
+ `task.failed`, `task.timeout`) is classified `interrupted` by
105
+ `claude-autopilot run resume <ulid>`. The CLI refuses to auto-merge
106
+ any partial commits — the user must pass
107
+ `--confirm-interrupted-tasks <task-id>...` to either re-dispatch from
108
+ a clean branch (discarding partial commits) or merge what's there
109
+ (user accepts risk). This is stricter than the pass-1 draft, which
110
+ would have auto-merged "if commits present" — that was unsafe because
111
+ commits alone do not prove the subagent finished its task contract.
112
+ - **`budgets.perSubagentUSD` is a HARD cap, not soft.** Dispatch is
113
+ rejected pre-flight if the estimate exceeds the cap, and a running
114
+ subagent is killed (SIGTERM → SIGKILL of its process group) if actual
115
+ cost overruns. To get soft-reservation semantics, omit
116
+ `perSubagentUSD` and rely solely on `perRunUSD`.
117
+ - **Task branches with merge commits are rejected by default.** Set
118
+ `concurrency.allowMergeCommitsInTasks: true` if your subagents
119
+ legitimately produce merge commits.
120
+ - **Audit-log durability — documented limitation.** Event writes are
121
+ single-`write(O_APPEND)` per JSONL line under the exclusive `flock`,
122
+ but the writer does NOT `fsync(events.ndjson)` per event. A power
123
+ loss between two appends can therefore lose the last few events even
124
+ though the file lock was held atomically. This is a deliberate
125
+ trade-off: per-event fsync would dominate scheduler throughput at
126
+ high concurrency. If your environment requires per-event durability,
127
+ set `maxParallelSubagents: 1` and the audit-log will degrade to
128
+ v7.10.0 semantics.
129
+
130
+ ### Fallback policy (backwards compatibility)
131
+
132
+ Plans without `depends_on:` annotations continue to work without changes:
133
+
134
+ - `concurrency.maxParallelSubagents: 1` → sequential dispatch
135
+ reproducing v7.10.0 behavior exactly.
136
+ - ZERO tasks declare `depends_on:` → sequential by default. Existing
137
+ plans are NEVER silently parallelized. Override with
138
+ `concurrency.assumeIndependentWithoutDependsOn: true` to opt in to
139
+ file-overlap inference.
140
+ - At least one task declares `depends_on:` → explicit deps + file-
141
+ overlap inference for the remaining unannotated tasks.
142
+
143
+ Cycle detection runs in all three cases before dispatch begins.
144
+
145
+ ### Resume semantics
146
+
147
+ `claude-autopilot run resume <ulid>` classifies tasks by their LAST
148
+ terminal event in `events.ndjson`:
149
+
150
+ - `task.merged` → DONE; skip.
151
+ - `task.completed` (no merged) → eligible; re-run merge orchestrator if
152
+ deps are merged.
153
+ - `task.failed` → terminal; surface, do not retry without intervention.
154
+ - `task.merge_conflict` → terminal; surface report path; user must
155
+ resolve before resume continues.
156
+ - `task.timeout` → terminal (dual emission of `task.timeout` +
157
+ `task.failed`).
158
+ - `task.started` with no terminal event → INTERRUPTED; requires
159
+ `--confirm-interrupted-tasks <task-id>...`.
160
+
161
+ ### Out of scope (deferred, per spec)
162
+
163
+ - Hosted profile sharing — separate spec.
164
+ - AI-generated custom config — separate spec.
165
+ - v6 run-state engine integration into the autopilot skill — issue #180.
166
+ - Frontend quality — issue #178.
167
+ - Expand/contract migration safety — issue #179.
168
+ - Parallelizing validate / codex-review / bugbot steps — separate spec.
169
+ - Distributed execution across machines — separate spec.
170
+
5
171
  ## 7.10.1 — 2026-05-13
6
172
 
7
173
  **v7.10.1 — `examples` verb.** Patch release. Closes the discoverability
@@ -37,6 +37,18 @@ export declare const HELP_GROUPS: HelpGroup[];
37
37
  * `claude-autopilot help <verb>` will show just the row in that case.
38
38
  */
39
39
  export declare const HELP_OPTIONS: Record<string, string>;
40
+ /**
41
+ * Documentation block for the v7.11.0+ `concurrency:` configuration in
42
+ * `guardrail.config.yaml`. Rendered by `claude-autopilot help concurrency`
43
+ * (advisory pointer — there is no `concurrency` verb; this is config docs).
44
+ *
45
+ * Keep this block in sync with:
46
+ * - `presets/schemas/guardrail.config.schema.json` (the validated shape)
47
+ * - `skills/autopilot/SKILL.md` Step 3 (the fallback rule consumer)
48
+ * - `skills/writing-plans/SKILL.md` (`depends_on:` annotation contract)
49
+ * - `src/core/concurrent-dispatch/dep-graph.ts` (`DEFAULT_FALLBACK_POLICY`)
50
+ */
51
+ export declare const CONCURRENCY_CONFIG_BLOCK = "guardrail.config.yaml \u2014 concurrency block (v7.11.0+):\n\n concurrency:\n maxParallelSubagents: 3 # int, range 1..8, default 3\n perSubagentTimeoutMs: 1800000 # int ms, default 30 min (1,800,000)\n assumeIndependentWithoutDependsOn: false # bool, default false\n useDedicatedMergeWorktree: true # bool, default true\n allowMergeCommitsInTasks: false # bool, default false\n\n budgets:\n perRunUSD: 10 # number USD, existing\n perPhaseUSD: 5 # number USD, existing\n perSubagentUSD: 3 # number USD or null (v7.11.0+)\n # HARD cap per subagent: dispatch\n # is rejected if preflight estimate\n # exceeds it; an in-flight subagent\n # is SIGTERMed if actual cost\n # exceeds it. Set null to disable.\n\nKeys:\n\n maxParallelSubagents\n Maximum concurrent subagents. Schema range 1..8 (enforced at config-load\n via presets/schemas/guardrail.config.schema.json \u2014 invalid values fail).\n Effective concurrency at runtime is further clamped by\n providerRateLimitConcurrency and the remaining task count. Setting to 1\n reproduces v7.10.0 sequential behavior exactly.\n\n perSubagentTimeoutMs\n Hard per-subagent timeout. On expiry the scheduler SIGTERMs the\n subagent's entire process group (kill -TERM -<pgid>); SIGKILL follows\n after 30s if no response. Emits both task.timeout (informational) and\n task.failed (terminal, error_type: 'timeout') events.\n\n assumeIndependentWithoutDependsOn\n Fallback policy for plans where ZERO tasks declare depends_on. Default\n false = run sequentially (preserves v7.10.0 semantics). Set true to opt\n in to file-overlap inference for unannotated plans. Has no effect when\n at least one task in the plan declares depends_on.\n\n useDedicatedMergeWorktree\n When true (default), the merge orchestrator cherry-picks task branches\n inside a dedicated integration worktree at\n .claude/worktrees/<run-ulid>/integration/ \u2014 the main repo working tree\n is never touched. Disable only if your repo cannot afford the extra\n worktree (rarely necessary).\n\n allowMergeCommitsInTasks\n When false (default), the merge orchestrator rejects task branches that\n contain merge commits. Subagents are expected to produce a linear\n sequence of commits on top of base_sha.\n\n budgets.perSubagentUSD\n HARD per-subagent cost cap. Dispatch is rejected when preFlightEstimate\n > perSubagentUSD. An in-flight subagent is killed and emits task.failed\n with error_type: 'budget_exceeded' if actual_cost_usd exceeds the cap\n mid-run. Set to null to disable the per-subagent cap entirely (in which\n case only the perRunUSD soft reservation applies).\n\nFallback rule (single source of truth \u2014 enforced in\nsrc/core/concurrent-dispatch/dep-graph.ts):\n\n 1. concurrency.maxParallelSubagents: 1 \u2192 sequential dispatch.\n 2. ZERO tasks declare depends_on \u2192 sequential, unless\n assumeIndependentWithoutDependsOn: true.\n 3. At least one task declares depends_on \u2192 use explicit deps + file-\n overlap inference for unannotated tasks.\n\nSee skills/writing-plans/SKILL.md for the depends_on annotation contract.";
40
52
  /**
41
53
  * Global flags advertised in --help. These work across most verbs (per-verb
42
54
  * support varies; v6.0.1 wires them into `scan` first, additional verbs land
@@ -364,6 +364,86 @@ function padVerb(verb) {
364
364
  const WIDTH = 16;
365
365
  return verb.length >= WIDTH ? verb + ' ' : verb + ' '.repeat(WIDTH - verb.length);
366
366
  }
367
+ /**
368
+ * Documentation block for the v7.11.0+ `concurrency:` configuration in
369
+ * `guardrail.config.yaml`. Rendered by `claude-autopilot help concurrency`
370
+ * (advisory pointer — there is no `concurrency` verb; this is config docs).
371
+ *
372
+ * Keep this block in sync with:
373
+ * - `presets/schemas/guardrail.config.schema.json` (the validated shape)
374
+ * - `skills/autopilot/SKILL.md` Step 3 (the fallback rule consumer)
375
+ * - `skills/writing-plans/SKILL.md` (`depends_on:` annotation contract)
376
+ * - `src/core/concurrent-dispatch/dep-graph.ts` (`DEFAULT_FALLBACK_POLICY`)
377
+ */
378
+ export const CONCURRENCY_CONFIG_BLOCK = `guardrail.config.yaml — concurrency block (v7.11.0+):
379
+
380
+ concurrency:
381
+ maxParallelSubagents: 3 # int, range 1..8, default 3
382
+ perSubagentTimeoutMs: 1800000 # int ms, default 30 min (1,800,000)
383
+ assumeIndependentWithoutDependsOn: false # bool, default false
384
+ useDedicatedMergeWorktree: true # bool, default true
385
+ allowMergeCommitsInTasks: false # bool, default false
386
+
387
+ budgets:
388
+ perRunUSD: 10 # number USD, existing
389
+ perPhaseUSD: 5 # number USD, existing
390
+ perSubagentUSD: 3 # number USD or null (v7.11.0+)
391
+ # HARD cap per subagent: dispatch
392
+ # is rejected if preflight estimate
393
+ # exceeds it; an in-flight subagent
394
+ # is SIGTERMed if actual cost
395
+ # exceeds it. Set null to disable.
396
+
397
+ Keys:
398
+
399
+ maxParallelSubagents
400
+ Maximum concurrent subagents. Schema range 1..8 (enforced at config-load
401
+ via presets/schemas/guardrail.config.schema.json — invalid values fail).
402
+ Effective concurrency at runtime is further clamped by
403
+ providerRateLimitConcurrency and the remaining task count. Setting to 1
404
+ reproduces v7.10.0 sequential behavior exactly.
405
+
406
+ perSubagentTimeoutMs
407
+ Hard per-subagent timeout. On expiry the scheduler SIGTERMs the
408
+ subagent's entire process group (kill -TERM -<pgid>); SIGKILL follows
409
+ after 30s if no response. Emits both task.timeout (informational) and
410
+ task.failed (terminal, error_type: 'timeout') events.
411
+
412
+ assumeIndependentWithoutDependsOn
413
+ Fallback policy for plans where ZERO tasks declare depends_on. Default
414
+ false = run sequentially (preserves v7.10.0 semantics). Set true to opt
415
+ in to file-overlap inference for unannotated plans. Has no effect when
416
+ at least one task in the plan declares depends_on.
417
+
418
+ useDedicatedMergeWorktree
419
+ When true (default), the merge orchestrator cherry-picks task branches
420
+ inside a dedicated integration worktree at
421
+ .claude/worktrees/<run-ulid>/integration/ — the main repo working tree
422
+ is never touched. Disable only if your repo cannot afford the extra
423
+ worktree (rarely necessary).
424
+
425
+ allowMergeCommitsInTasks
426
+ When false (default), the merge orchestrator rejects task branches that
427
+ contain merge commits. Subagents are expected to produce a linear
428
+ sequence of commits on top of base_sha.
429
+
430
+ budgets.perSubagentUSD
431
+ HARD per-subagent cost cap. Dispatch is rejected when preFlightEstimate
432
+ > perSubagentUSD. An in-flight subagent is killed and emits task.failed
433
+ with error_type: 'budget_exceeded' if actual_cost_usd exceeds the cap
434
+ mid-run. Set to null to disable the per-subagent cap entirely (in which
435
+ case only the perRunUSD soft reservation applies).
436
+
437
+ Fallback rule (single source of truth — enforced in
438
+ src/core/concurrent-dispatch/dep-graph.ts):
439
+
440
+ 1. concurrency.maxParallelSubagents: 1 → sequential dispatch.
441
+ 2. ZERO tasks declare depends_on → sequential, unless
442
+ assumeIndependentWithoutDependsOn: true.
443
+ 3. At least one task declares depends_on → use explicit deps + file-
444
+ overlap inference for unannotated tasks.
445
+
446
+ See skills/writing-plans/SKILL.md for the depends_on annotation contract.`;
367
447
  /**
368
448
  * Global flags advertised in --help. These work across most verbs (per-verb
369
449
  * support varies; v6.0.1 wires them into `scan` first, additional verbs land
@@ -409,6 +489,8 @@ export function buildHelpText() {
409
489
  }
410
490
  }
411
491
  }
492
+ lines.push(CONCURRENCY_CONFIG_BLOCK);
493
+ lines.push('');
412
494
  lines.push('Run \x1b[36mclaude-autopilot help <command>\x1b[0m for command-specific options.');
413
495
  return lines.join('\n') + '\n';
414
496
  }
@@ -232,6 +232,43 @@ export function renderEventLine(ev, runningTotal, opts) {
232
232
  case 'replay.override': {
233
233
  return `${ts} ${colorize(verb, 'magenta', opts.ansi)} ${ev.phase} reason=${ev.reason}`;
234
234
  }
235
+ // v7.11.0 concurrent subagent dispatch events. Detailed renderers will
236
+ // land in PR 6 (#193) when the watch UI exposes the per-task pane.
237
+ // For now, render a single-line summary so existing `runs watch`
238
+ // sessions don't silently skip these events.
239
+ case 'task.started': {
240
+ return `${ts} ${colorize(verb, 'cyan', opts.ansi)} ${ev.task_id} branch=${ev.branch}`;
241
+ }
242
+ case 'task.budget_reserved': {
243
+ return `${ts} ${colorize(verb, 'cyan', opts.ansi)} ${ev.task_id} reserved=${fmtUSD(ev.reserved_usd)}`;
244
+ }
245
+ case 'task.budget_increased_reservation': {
246
+ return `${ts} ${colorize(verb, 'cyan', opts.ansi)} ${ev.task_id} ${fmtUSD(ev.prior_reserved_usd)}→${fmtUSD(ev.new_reserved_usd)} ${ev.reason}`;
247
+ }
248
+ case 'task.budget_released': {
249
+ return `${ts} ${colorize(verb, 'cyan', opts.ansi)} ${ev.task_id} actual=${fmtUSD(ev.actual_cost_usd)} delta=${fmtUSD(ev.delta_vs_reservation_usd)}`;
250
+ }
251
+ case 'task.completed': {
252
+ return `${ts} ${colorize(verb, 'green', opts.ansi)} ${ev.task_id} status=${ev.exit_status} commits=${ev.commit_shas.length}`;
253
+ }
254
+ case 'task.failed': {
255
+ return `${ts} ${colorize(verb, 'red', opts.ansi)} ${ev.task_id} type=${ev.error_type} ${ev.error_message}`;
256
+ }
257
+ case 'task.merged': {
258
+ return `${ts} ${colorize(verb, 'green', opts.ansi)} ${ev.task_id}`;
259
+ }
260
+ case 'task.merge_conflict': {
261
+ return `${ts} ${colorize(verb, 'red', opts.ansi)} ${ev.task_id} paths=${ev.conflicting_paths.length}`;
262
+ }
263
+ case 'task.merge_aborted': {
264
+ return `${ts} ${colorize(verb, 'red', opts.ansi)} ${ev.task_id} reason=${ev.reason}`;
265
+ }
266
+ case 'task.timeout': {
267
+ return `${ts} ${colorize(verb, 'yellow', opts.ansi)} ${ev.task_id} ${ev.timeout_ms}ms signal=${ev.killed_signal}`;
268
+ }
269
+ case 'task.budget_halt': {
270
+ return `${ts} ${colorize(verb, 'red', opts.ansi)} ${ev.task_id} remaining=${fmtUSD(ev.budget_remaining_usd)} needed=${fmtUSD(ev.preflight_estimate_usd)}`;
271
+ }
235
272
  default: {
236
273
  // Exhaustiveness guard. New event variants must be added here so a
237
274
  // future RunEvent extension forces a compile error rather than
@@ -406,6 +406,24 @@ function applyEvent(state, ev) {
406
406
  // events.ndjson directly to compute actualSoFar — replay does not
407
407
  // need to track budget decisions for state-correctness purposes.
408
408
  break;
409
+ case 'task.started':
410
+ case 'task.budget_reserved':
411
+ case 'task.budget_increased_reservation':
412
+ case 'task.budget_released':
413
+ case 'task.completed':
414
+ case 'task.failed':
415
+ case 'task.merged':
416
+ case 'task.merge_conflict':
417
+ case 'task.merge_aborted':
418
+ case 'task.timeout':
419
+ case 'task.budget_halt':
420
+ // v7.11.0 concurrent-dispatch task events. State for these lives in
421
+ // the dispatch layer (budget-reservation ledger + scheduler), NOT in
422
+ // the per-phase RunState snapshot — phases remain the coarse-grained
423
+ // unit for the run-state engine. Replay is handled by
424
+ // `budgetReservation.replayFromEvents()` for cost reconstruction and
425
+ // by the scheduler's resume path for task-level state.
426
+ break;
409
427
  case 'phase.start': {
410
428
  state.status = 'running';
411
429
  state.currentPhaseIdx = ev.phaseIdx;
@@ -251,9 +251,135 @@ export interface ReplayOverrideEvent extends RunEventBase {
251
251
  /** Refs the underlying refusal cited (echoed for triage). */
252
252
  refsConsulted: ExternalRef[];
253
253
  }
254
+ /** Subagent dispatch — emitted AFTER `task.budget_reserved` succeeds and the
255
+ * worktree is created. Carries the immutable `base_sha` so resume / merge
256
+ * can verify ancestry against an unforgeable reference. */
257
+ export interface TaskStartedEvent extends RunEventBase {
258
+ event: 'task.started';
259
+ task_id: string;
260
+ worktree_path: string;
261
+ branch: string;
262
+ base_sha: string;
263
+ subagent_id: string;
264
+ /** ISO timestamp the subagent process was spawned. */
265
+ dispatched_at: string;
266
+ preflight_cost_estimate_usd: number;
267
+ }
268
+ /** Budget reservation — emitted atomically with the (replay → check → append
269
+ * → fsync) critical section under the writer's exclusive lock. Two
270
+ * concurrent callers cannot both pass the budget check. */
271
+ export interface TaskBudgetReservedEvent extends RunEventBase {
272
+ event: 'task.budget_reserved';
273
+ task_id: string;
274
+ reserved_usd: number;
275
+ /** `perRunUSD - reserved_total` AFTER this reservation lands. May be 0,
276
+ * never negative (would have failed the check). */
277
+ run_budget_remaining_after_reservation_usd: number;
278
+ }
279
+ /** Mid-execution reservation bump — emitted when telemetry from the subagent
280
+ * shows actual cost is approaching the reservation. Re-checks `perRunUSD`
281
+ * under the writer lock; halts the run if exceeded. */
282
+ export interface TaskBudgetIncreasedReservationEvent extends RunEventBase {
283
+ event: 'task.budget_increased_reservation';
284
+ task_id: string;
285
+ prior_reserved_usd: number;
286
+ new_reserved_usd: number;
287
+ reason: string;
288
+ }
289
+ /** Reservation closed — emitted on task completion OR failure. The
290
+ * `delta_vs_reservation_usd` (positive = under, negative = over) lets
291
+ * cost-analytics consumers spot estimate drift. */
292
+ export interface TaskBudgetReleasedEvent extends RunEventBase {
293
+ event: 'task.budget_released';
294
+ task_id: string;
295
+ actual_cost_usd: number;
296
+ delta_vs_reservation_usd: number;
297
+ }
298
+ /** Successful subagent exit with commits on the task branch. The
299
+ * `task_branch_tip_sha` is the IMMUTABLE authoritative ref for all
300
+ * subsequent merge / resume operations — `task_branch_name` is for
301
+ * diagnostics only (branch can be tampered with). */
302
+ export interface TaskCompletedEvent extends RunEventBase {
303
+ event: 'task.completed';
304
+ task_id: string;
305
+ base_sha: string;
306
+ task_branch_tip_sha: string;
307
+ task_branch_name: string;
308
+ /** Ordered list of commit SHAs `base_sha..tip_sha` (oldest first, from
309
+ * `git rev-list --reverse`). Empty array implies `task.failed` should
310
+ * have been emitted with `error_type: 'no_commits'` instead. */
311
+ commit_shas: string[];
312
+ completed_at: string;
313
+ actual_cost_usd: number;
314
+ exit_status: 'success' | 'failure';
315
+ }
316
+ /** Subagent terminal failure. `error_type` is the resume classifier — see
317
+ * spec "Resume semantics" for the classification table. */
318
+ export interface TaskFailedEvent extends RunEventBase {
319
+ event: 'task.failed';
320
+ task_id: string;
321
+ error_message: string;
322
+ error_type: 'timeout' | 'no_commits' | 'ancestry_violation' | 'budget_exceeded' | 'crash' | 'other';
323
+ failed_at: string;
324
+ actual_cost_usd: number;
325
+ }
326
+ /** Cherry-pick chain landed on the integration worktree. The
327
+ * `feature_branch_sha_after_merge` is recorded so the next merge can
328
+ * verify preconditions (HEAD matches the last `task.merged`). */
329
+ export interface TaskMergedEvent extends RunEventBase {
330
+ event: 'task.merged';
331
+ task_id: string;
332
+ feature_branch_sha_after_merge: string;
333
+ merged_at: string;
334
+ }
335
+ /** Cherry-pick conflict captured BEFORE `cherry-pick --abort` runs.
336
+ * Diagnostics are also persisted to `conflict_report_path`
337
+ * (`.claude/run-state/<run-ulid>/conflicts/<task-id>.md`). */
338
+ export interface TaskMergeConflictEvent extends RunEventBase {
339
+ event: 'task.merge_conflict';
340
+ task_id: string;
341
+ conflicting_paths: string[];
342
+ /** Output of `git ls-files -u` — index stages 1/2/3 for each conflicted
343
+ * path. Free-form lines preserved verbatim. */
344
+ index_stages: string[];
345
+ /** Output of `git status --porcelain`. */
346
+ porcelain: string;
347
+ conflict_report_path: string;
348
+ }
349
+ /** Merge precondition violation — dirty tree, wrong HEAD, in-progress
350
+ * cherry-pick / rebase, or ancestry violation at merge time. Halts the
351
+ * run; user must resolve before resume. */
352
+ export interface TaskMergeAbortedEvent extends RunEventBase {
353
+ event: 'task.merge_aborted';
354
+ task_id: string;
355
+ reason: string;
356
+ precondition_violated: string;
357
+ occurred_at: string;
358
+ }
359
+ /** Subagent exceeded `perSubagentTimeoutMs`. Informational — the resume
360
+ * classifier requires a paired `task.failed` event with
361
+ * `error_type: 'timeout'` for terminal classification. */
362
+ export interface TaskTimeoutEvent extends RunEventBase {
363
+ event: 'task.timeout';
364
+ task_id: string;
365
+ timeout_ms: number;
366
+ /** `SIGTERM` or `SIGKILL` — set to `SIGKILL` when the 30s grace after
367
+ * SIGTERM elapsed without process exit. */
368
+ killed_signal: 'SIGTERM' | 'SIGKILL';
369
+ }
370
+ /** Pre-dispatch budget halt — emitted when `reserve()` finds remaining
371
+ * budget insufficient for the new task's preflight estimate. The task
372
+ * never dispatches; the scheduler halts the run with this event as the
373
+ * terminal record. */
374
+ export interface TaskBudgetHaltEvent extends RunEventBase {
375
+ event: 'task.budget_halt';
376
+ task_id: string;
377
+ budget_remaining_usd: number;
378
+ preflight_estimate_usd: number;
379
+ }
254
380
  /** Discriminated union of every event variant. Add new variants here and
255
381
  * the code that switches over `event` will type-error at compile time. */
256
- export type RunEvent = RunStartEvent | RunCompleteEvent | RunWarningEvent | RunRecoveryEvent | PhaseStartEvent | PhaseSuccessEvent | PhaseFailedEvent | PhaseAbortedEvent | PhaseCostEvent | PhaseExternalRefEvent | PhaseNeedsHumanEvent | LockTakeoverEvent | IndexRebuiltEvent | BudgetCheckEvent | ReplayOverrideEvent;
382
+ export type RunEvent = RunStartEvent | RunCompleteEvent | RunWarningEvent | RunRecoveryEvent | PhaseStartEvent | PhaseSuccessEvent | PhaseFailedEvent | PhaseAbortedEvent | PhaseCostEvent | PhaseExternalRefEvent | PhaseNeedsHumanEvent | LockTakeoverEvent | IndexRebuiltEvent | BudgetCheckEvent | ReplayOverrideEvent | TaskStartedEvent | TaskBudgetReservedEvent | TaskBudgetIncreasedReservationEvent | TaskBudgetReleasedEvent | TaskCompletedEvent | TaskFailedEvent | TaskMergedEvent | TaskMergeConflictEvent | TaskMergeAbortedEvent | TaskTimeoutEvent | TaskBudgetHaltEvent;
257
383
  /** Distributive Omit so the discriminated-union shape is preserved when we
258
384
  * strip the fields the appender fills in. Plain `Omit<RunEvent, ...>`
259
385
  * collapses the union into a single intersection and loses variant-specific
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "7.11.0-pre.2",
3
+ "version": "7.11.0",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
- "tag": "next"
6
+ "tag": "latest"
7
7
  },
8
8
  "description": "Autonomous development pipeline for Claude Code: brainstorm → spec → plan → implement → migrate → validate → PR → review → merge. Multi-model, local-first, every phase a skill you can intervene in.",
9
9
  "keywords": [
@@ -53,6 +53,10 @@
53
53
  "types": "./dist/src/core/run-state/sameness-detector.d.ts",
54
54
  "default": "./dist/src/core/run-state/sameness-detector.js"
55
55
  },
56
+ "./concurrent-dispatch": {
57
+ "types": "./dist/src/core/concurrent-dispatch/index.d.ts",
58
+ "default": "./dist/src/core/concurrent-dispatch/index.js"
59
+ },
56
60
  "./bin/claude-autopilot.js": "./bin/claude-autopilot.js",
57
61
  "./bin/guardrail.js": "./bin/guardrail.js",
58
62
  "./package.json": "./package.json"
@@ -0,0 +1,70 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/axledbetter/claude-autopilot/presets/schemas/guardrail.config.schema.json",
4
+ "title": "guardrail.config.yaml",
5
+ "description": "Top-level shape of guardrail.config.yaml. Only blocks that are validated at config-load are listed under properties; unrecognized keys are still permitted today (additionalProperties: true) so existing presets continue to parse while new validated blocks land in subsequent releases.",
6
+ "type": "object",
7
+ "required": ["configVersion"],
8
+ "additionalProperties": true,
9
+ "properties": {
10
+ "configVersion": {
11
+ "type": "integer",
12
+ "minimum": 1
13
+ },
14
+ "budgets": {
15
+ "type": "object",
16
+ "additionalProperties": true,
17
+ "description": "Cost caps in USD. `perRunUSD` and `perPhaseUSD` are enforced run-wide. `perSubagentUSD` is a HARD cap per concurrent subagent — dispatch is rejected if the preflight estimate exceeds it, and a running subagent is SIGTERMed if its actual cost overruns the cap. Leave `perSubagentUSD` null/omitted to fall back to a soft `perRunUSD`-only reservation model. additionalProperties is intentionally `true` in v7.11.0 for backwards-compatibility with existing user configs that may carry historical budget keys; a future minor will enumerate the full set and tighten this to `false`.",
18
+ "properties": {
19
+ "perRunUSD": {
20
+ "type": "number",
21
+ "minimum": 0
22
+ },
23
+ "perPhaseUSD": {
24
+ "type": "number",
25
+ "minimum": 0
26
+ },
27
+ "perSubagentUSD": {
28
+ "type": ["number", "null"],
29
+ "minimum": 0,
30
+ "description": "HARD per-subagent cost cap in USD. Dispatch is rejected when preFlightEstimate > perSubagentUSD; an in-flight subagent is killed and emits task.failed if actual_cost_usd exceeds it mid-run. Set to null to disable the per-subagent cap and rely solely on perRunUSD."
31
+ }
32
+ }
33
+ },
34
+ "concurrency": {
35
+ "type": "object",
36
+ "additionalProperties": false,
37
+ "description": "Concurrent subagent dispatch settings (v7.11.0+). All keys are optional; defaults reproduce v7.10.0 sequential behavior for plans without depends_on annotations.",
38
+ "properties": {
39
+ "maxParallelSubagents": {
40
+ "type": "integer",
41
+ "minimum": 1,
42
+ "maximum": 8,
43
+ "default": 3,
44
+ "description": "Maximum number of concurrent subagents (effective concurrency is further clamped by providerRateLimitConcurrency and taskCount). Setting to 1 reproduces v7.10.0 sequential behavior exactly. Above 8 the API rate limits dominate and additional parallelism does not help."
45
+ },
46
+ "perSubagentTimeoutMs": {
47
+ "type": "integer",
48
+ "minimum": 1000,
49
+ "default": 1800000,
50
+ "description": "Hard timeout per subagent in milliseconds (default 30 minutes). On timeout the scheduler SIGTERMs the subagent's process group; SIGKILL follows after 30 seconds if no response."
51
+ },
52
+ "assumeIndependentWithoutDependsOn": {
53
+ "type": "boolean",
54
+ "default": false,
55
+ "description": "When false (default), a plan in which ZERO tasks declare depends_on is run sequentially — preserving v7.10.0 semantics. Set true to opt in to file-overlap inference for unannotated plans."
56
+ },
57
+ "useDedicatedMergeWorktree": {
58
+ "type": "boolean",
59
+ "default": true,
60
+ "description": "When true (default), the merge orchestrator cherry-picks task branches inside a dedicated integration worktree under .claude/worktrees/<run-ulid>/integration/ so the main working tree is never mutated by the autopilot run. Disable only if your repo cannot afford the extra worktree (rarely necessary)."
61
+ },
62
+ "allowMergeCommitsInTasks": {
63
+ "type": "boolean",
64
+ "default": false,
65
+ "description": "When false (default), the merge orchestrator rejects task branches that contain merge commits. Subagents are expected to produce a linear sequence of commits on top of base_sha."
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
@@ -188,26 +188,81 @@ Output: Plan at docs/superpowers/plans/YYYY-MM-DD-<topic>.md
188
188
 
189
189
  After the plan is written but BEFORE committing it, run `npx tsx scripts/codex-review.ts <plan-path>`. Apply CRITICAL findings (sequencing errors, missing test coverage on a load-bearing path, schema/migration ordering bugs) to the plan inline. Then commit. Always use subagent-driven development for execution — do not ask the user.
190
190
 
191
- ### Step 2: Set up worktree
191
+ ### Step 2: Set up feature branch (ref only — do NOT check out in main worktree)
192
192
 
193
193
  ```
194
194
  Invoke: superpowers:using-git-worktrees
195
195
  Branch: feature/<topic-slug>
196
+ Mode: create as a ref only; leave the main worktree on its prior branch.
196
197
  ```
197
198
 
198
- ### Step 3: Execute plan
199
+ Step 2 creates `feature/<topic-slug>` but does NOT check it out in the main
200
+ worktree. Concurrent dispatch (Step 3) will create the SOLE checkout of the
201
+ feature branch in `.claude/worktrees/<run-ulid>/integration/` — git linked
202
+ worktrees forbid the same branch being checked out twice, so the scheduler
203
+ must own the only checkout. The main repo working tree stays untouched for the
204
+ entire run.
205
+
206
+ If you are using `superpowers:using-git-worktrees`, pass it the "branch only,
207
+ no checkout" mode (or equivalent) so it stops at `git branch
208
+ feature/<topic-slug> <base-sha>` and does not run `git checkout` /
209
+ `git worktree add` against the main repo.
210
+
211
+ ### Step 3: Execute plan (concurrent dispatch)
199
212
 
200
213
  ```
201
- Invoke: superpowers:subagent-driven-development
202
- Input: The plan file
203
- Mode: dispatch fresh subagent per task
214
+ Invoke: claude-autopilot's concurrent dispatcher
215
+ Input: The plan file (with depends_on: annotations where applicable)
216
+ Mode: dispatch fresh subagents in parallel per the dep graph
204
217
  ```
205
218
 
206
- For each task:
207
- - Dispatch implementer subagent
208
- - On completion: verify commit landed in worktree
209
- - Skip formal spec/quality review to maintain speed (the validate step catches issues)
210
- - If subagent fails to write to worktree: implement directly
219
+ **Default effective concurrency:**
220
+ `min(3, providerRateLimitConcurrency, taskCount)`.
221
+ Override via `guardrail.config.yaml` `concurrency.maxParallelSubagents`
222
+ (schema range `1..8`).
223
+
224
+ **Fallback rule (single source of truth — also enforced in
225
+ `src/core/concurrent-dispatch/dep-graph.ts`):**
226
+
227
+ - If `concurrency.maxParallelSubagents: 1` → sequential (reproduces v7.10.0
228
+ behavior exactly).
229
+ - If ZERO tasks in the plan have `depends_on:` annotations → sequential,
230
+ unless `concurrency.assumeIndependentWithoutDependsOn: true` (opt in to
231
+ file-overlap inference).
232
+ - If at least one task has `depends_on:` → use the explicit deps + fall back
233
+ to file-overlap inference for the remaining unannotated tasks.
234
+
235
+ For each task, the scheduler:
236
+ - creates a per-task worktree at `.claude/worktrees/<run-ulid>/<task-id>/`,
237
+ branched from the current feature tip (`base_sha` captured at dispatch);
238
+ - dispatches an implementer subagent to commit on
239
+ `autopilot/<run-ulid>/<task-id>`;
240
+ - on success, runs the merge orchestrator (under the cross-process repo lock)
241
+ to cherry-pick `base_sha..task_branch_tip_sha` onto the integration
242
+ worktree's `feature/<topic-slug>` checkout in plan-declaration order.
243
+
244
+ **On any task failure**: halt the run, SIGTERM in-flight subagent process
245
+ groups (kill -TERM -<pgid>; SIGKILL after 30s if no response), and surface a
246
+ breakdown to the user:
247
+ - `merged`: tasks whose commits are on the feature branch (guaranteed-good
248
+ set).
249
+ - `completed-but-unmerged`: commits exist on per-task branches; worktrees
250
+ preserved for inspection.
251
+ - `in-flight`: subagents that received SIGTERM; worktrees preserved.
252
+
253
+ **On cherry-pick conflict**: diagnostics are written to
254
+ `.claude/run-state/<run-ulid>/conflicts/<task-id>.md` BEFORE
255
+ `cherry-pick --abort` runs; the run halts and the user resolves manually
256
+ (add an explicit `depends_on:` to the plan, or fix the conflict in the
257
+ preserved worktree).
258
+
259
+ **Subagent contract:** subagent writes its own commits in its worktree. If
260
+ the subagent fails to write to its worktree, fall back to direct
261
+ implementation in that worktree only — never in the integration worktree or
262
+ main repo working tree.
263
+
264
+ Skip formal spec/quality review per task to maintain speed; the validate
265
+ step (Step 4) catches issues across the merged feature branch.
211
266
 
212
267
  ### Step 4: Validate
213
268
 
@@ -0,0 +1,188 @@
1
+ ---
2
+ name: writing-plans
3
+ description: Write an implementation plan from an approved spec — task-by-task breakdown with files-to-change, step checkboxes, and (v7.11.0+) optional depends_on annotations for concurrent subagent dispatch. Use when the user has an approved spec and needs a plan ready for /implement or claude-autopilot Step 3.
4
+ ---
5
+
6
+ # Writing plans
7
+
8
+ A plan turns an approved spec into a sequence of executable tasks. Each task
9
+ is the atomic unit dispatched to one subagent. Tasks declare the files they
10
+ touch, the steps the subagent should perform, and optionally the other tasks
11
+ they depend on (v7.11.0+).
12
+
13
+ > **For the underlying brainstorming + spec flow**, see
14
+ > `superpowers:brainstorming` and `superpowers:writing-plans` from the
15
+ > superpowers plugin. This document is the claude-autopilot-side companion
16
+ > covering the `depends_on:` annotation introduced in v7.11.0 and the
17
+ > fallback policy that governs how it interacts with concurrent dispatch.
18
+
19
+ ## Plan file shape
20
+
21
+ Plans live at `docs/superpowers/plans/YYYY-MM-DD-<topic>.md`. Top-level
22
+ structure:
23
+
24
+ ```markdown
25
+ # <Topic> Implementation Plan
26
+
27
+ > **For agentic workers:** Use claude-autopilot Step 3 (concurrent dispatch)
28
+ > or superpowers:subagent-driven-development to execute this plan task-by-task.
29
+
30
+ **Goal:** <one-paragraph statement>
31
+ **Architecture:** <one-paragraph statement>
32
+ **Tech Stack:** <brief notes>
33
+
34
+ ---
35
+
36
+ ### Task 1: <task name>
37
+
38
+ **Files:**
39
+ - Create: `src/foo.ts`
40
+ - Test: `tests/foo.test.ts`
41
+
42
+ - [ ] **Step 1: <step name>**
43
+
44
+ <step content — code blocks, instructions, etc.>
45
+
46
+ - [ ] **Step 2: <step name>**
47
+
48
+ <more content>
49
+
50
+ ### Task 2: <task name>
51
+
52
+ **Files:**
53
+ - Modify: `src/bar.ts`
54
+
55
+ - [ ] **Step 1: ...**
56
+ ```
57
+
58
+ Each task has:
59
+ - A heading `### Task N: <name>` (numbered case-sensitively; the name is
60
+ human-readable and may be fuzzy-matched in `depends_on:` references).
61
+ - A `**Files:**` block listing files this task creates, modifies, or tests.
62
+ - One or more `- [ ] **Step N: ...**` checkboxes the subagent ticks as it
63
+ implements.
64
+
65
+ ## `depends_on:` annotation (v7.11.0+)
66
+
67
+ To enable concurrent execution, annotate tasks that depend on other tasks.
68
+ The annotation goes on its own line in the task header, after `**Files:**`:
69
+
70
+ ```markdown
71
+ ### Task 3: Wire foo into bar
72
+
73
+ **Files:**
74
+ - Modify: `src/bar.ts`
75
+ - Modify: `src/foo.ts:42-60`
76
+
77
+ **depends_on:** Task 1, Task 2
78
+
79
+ - [ ] **Step 1: ...**
80
+ ```
81
+
82
+ ### Rules
83
+
84
+ - `depends_on:` is **optional**. If absent, the scheduler infers
85
+ dependencies from file overlap (two tasks touching the same path become a
86
+ sequential pair, ordered by plan-declaration index).
87
+ - References are by `### Task N: <name>` heading. **The task number is
88
+ matched case-sensitively; the name is fuzzy-matched** so minor wording
89
+ drift between the reference and the heading is tolerated.
90
+ - Multiple deps are comma-separated: `**depends_on:** Task 1, Task 2`.
91
+ - Cycles are a **hard error**. The scheduler surfaces the cycle path and
92
+ refuses to dispatch.
93
+ - A task that **modifies** a file another task **creates** has an implicit
94
+ dependency on the creating task — the scheduler injects it automatically,
95
+ even without an explicit annotation.
96
+ - File overlap without an explicit `depends_on:` produces a **warning**, not
97
+ an error. The scheduler treats overlapping tasks as a sequential pair in
98
+ plan-declaration order. Surfaced in the run report so the user can add an
99
+ explicit annotation if the inferred ordering was wrong.
100
+
101
+ ## Fallback policy (single source of truth)
102
+
103
+ The fallback policy lives in `src/core/concurrent-dispatch/dep-graph.ts`
104
+ (`DEFAULT_FALLBACK_POLICY` + `buildDepGraph`) and is mirrored in
105
+ `skills/autopilot/SKILL.md` Step 3:
106
+
107
+ 1. **`concurrency.maxParallelSubagents: 1`** → sequential dispatch.
108
+ Reproduces v7.10.0 behavior exactly. Use this as the escape hatch.
109
+ 2. **ZERO tasks in the plan have `depends_on:`** → sequential by default.
110
+ Existing plans without annotations are never silently parallelized.
111
+ Override with `concurrency.assumeIndependentWithoutDependsOn: true` to
112
+ opt in to file-overlap inference for an unannotated plan.
113
+ 3. **At least one task has `depends_on:`** → use the explicit deps + fall
114
+ back to file-overlap inference for the remaining unannotated tasks.
115
+
116
+ Cycle detection runs in all three cases before dispatch begins.
117
+
118
+ ## Example — mixed annotated + unannotated tasks
119
+
120
+ ```markdown
121
+ # Banking integration plan
122
+
123
+ **Goal:** wire Fiserv adapter into the data sync pipeline.
124
+ **Architecture:** adapter under app/services/banking-integrations/adapters,
125
+ sync orchestrator under inbound/, compliance check piggy-backs on the sync.
126
+
127
+ ---
128
+
129
+ ### Task 1: Fiserv adapter skeleton
130
+
131
+ **Files:**
132
+ - Create: `app/services/banking-integrations/adapters/fiserv.adapter.ts`
133
+ - Test: `app/services/banking-integrations/adapters/__tests__/fiserv.adapter.test.ts`
134
+
135
+ - [ ] **Step 1: Define the adapter class extending BaseBankingAdapter.**
136
+
137
+ ### Task 2: Borrower-matcher service
138
+
139
+ **Files:**
140
+ - Create: `app/services/banking-integrations/inbound/borrower-matcher.service.ts`
141
+ - Test: `app/services/banking-integrations/inbound/__tests__/borrower-matcher.service.test.ts`
142
+
143
+ - [ ] **Step 1: Implement EIN-then-name-then-email matching.**
144
+
145
+ ### Task 3: Wire adapter into data sync
146
+
147
+ **Files:**
148
+ - Modify: `app/services/banking-integrations/inbound/bank-data-sync.service.ts`
149
+ - Modify: `app/services/banking-integrations/adapters/fiserv.adapter.ts`
150
+
151
+ **depends_on:** Task 1, Task 2
152
+
153
+ - [ ] **Step 1: Call the adapter from syncBankData; pipe results through borrower-matcher.**
154
+
155
+ ### Task 4: Update CHANGELOG
156
+
157
+ **Files:**
158
+ - Modify: `CHANGELOG.md`
159
+
160
+ - [ ] **Step 1: Prepend a Banking integration entry under Unreleased.**
161
+ ```
162
+
163
+ How the scheduler resolves this plan with `maxParallelSubagents: 3` and
164
+ `assumeIndependentWithoutDependsOn: false`:
165
+
166
+ - Task 1 and Task 2 declare no overlap with each other and have no
167
+ `depends_on:`, but Task 3 declares `depends_on: Task 1, Task 2` — so the
168
+ plan has at least one annotation, the explicit-deps + file-overlap
169
+ inference branch is used.
170
+ - Tier 0: `Task 1, Task 2, Task 4` (no satisfied deps, no overlap with each
171
+ other) — dispatched in parallel up to `maxParallelSubagents`.
172
+ - Tier 1: `Task 3` — eligible only after Tasks 1 and 2 reach state
173
+ `merged`.
174
+
175
+ If you remove the `**depends_on:** Task 1, Task 2` line from Task 3, the
176
+ plan has zero annotations, falls back to sequential dispatch, and runs
177
+ Tasks 1 → 2 → 3 → 4 in plan-declaration order — reproducing v7.10.0
178
+ behavior.
179
+
180
+ ## When NOT to annotate
181
+
182
+ - The plan is small (≤ 3 tasks) and the wall-clock gain is not worth the
183
+ annotation maintenance cost.
184
+ - All tasks touch the same handful of files (high overlap, no
185
+ parallelizable structure).
186
+ - You want to ship the plan against v7.10.0 with the
187
+ `concurrency.maxParallelSubagents: 1` escape hatch and add annotations in
188
+ a follow-up.