## What
Pins the **worktree-capacity arithmetic** — the gap #3262 measured,
named, and explicitly left for someone to claim. Two commits: a
behaviour-preserving seam extraction, then the test.
#3262's own scope note:
> blinding this predicate to `false` leaves all 22 scheduler suites
green (365 tests). The capacity logic it feeds has no behavioural
coverage at all.
## Two live defects, opposite directions
Both came out of these few lines:
- **UNDER-COUNT admits work over the cap.** `maxWorktrees=4`, four
planning sessions each holding a worktree, and a replan dispatch
admitted as the **fifth** — the ledger counted WIP cards only and never
learned to count planners.
- **OVER-COUNT self-deadlocks.** A planned Ready card *retains* its
planning worktree for execution reuse, so counting it as a holder blocks
its own release: `2 wip + 3 idle-held = 5/4`, and the first unpause
released 2 of 4 slots' worth of work.
**Both are pinned, and the asymmetry is why.** Under-counting breaks the
cap and lets real work over it; over-counting only starves dispatch. A
test covering the "safe" direction alone would leave the expensive one
open.
## Mutation-tested — all four caught
| mutation | result |
|---|---|
| drop the terminal exclusion | 1 failed / 7 passed |
| count WIP cards twice | 2 failed / 6 passed |
| count cards holding no worktree | 1 failed / 7 passed |
| drop the self-slot subtraction | 1 failed / 7 passed |
```
clean: 8 passed (8)
scheduler suite: 16 files / 151 tests passed (behaviour preserved by the extraction)
typecheck, lint: clean
```
## Scope, stated rather than implied
The terminal predicate is **injected**, not resolved here. Which lanes
are terminal is #3262's test; resolving it in this file would make it
fail for that reason instead of this one. This pins the **set
arithmetic** — who is excluded, and how the total is formed.
Still not covered, and I am not claiming otherwise: the *stateful* half
of the ledger — the `+= 1` on dispatch and the `Math.max(0, … - 1)` on
failure inside `schedule()`'s loop. Extracting that would mean
restructuring dispatch itself, which is a different change from this
one.
## Process note
I claimed this on #3262 **before** starting rather than after, because
`scheduler.ts` is the hottest file in the tree and I produced three
duplicate PRs earlier tonight by picking up small shared-surface work
someone else already had in flight. Announcing first cost one comment;
the duplicates cost three PRs and two closes.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved scheduler capacity calculations for tasks that retain
existing worktrees.
* Prevented WIP tasks from being counted twice.
* Excluded completed and worktree-less tasks from reserved capacity.
* Corrected candidate capacity calculations when no worktree capacity is
reserved.
* **Tests**
* Added coverage for worktree reservation totals and candidate reuse
scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->