* test(#3849): phase allocation must skip numbers held by sibling worktrees (failing first)
* fix(#3849): phase allocation counts numbers held by sibling git worktrees
Both allocators (cmdPhaseAdd, cmdPhaseAddBatch) chose max+1 over numbers
gathered from ONE checkout — headers, bullets (add only), on-disk dirs.
Every sibling git worktree carries its own .planning/ on its own branch,
so a phase minted there was invisible and the same number was allocated
twice (the reported incident: two Phase 441s, one with six written
plans, surfaced a day late by human memory).
New shared horizon collectSiblingWorktreePhaseNums: one
git worktree list --porcelain, then per sibling — phase-dir names (the
cheap scan that would have caught the incident) and the WHOLE sibling
ROADMAP.md headers (a row can predate its dir; milestone-scoping would
be wrong — a number used under any milestone on another branch is
taken). Widen, never refuse: unreadable sibling / no .planning / not a
git repo / git unavailable each contribute nothing and allocation is
unchanged. Reuses isSentinelPhaseId and the allocators' own patterns.
Secondary (#1229 never reached batch): cmdPhaseAddBatch now also scans
roadmap bullets — a bullet-only 'Phase N' row was invisible to batch
allocation, exactly the condition #1229 was filed for.
Also fixes the two new tests' result-key access (output.phases, not
output.results).
* fix(#3849): review fold-ins — subprocess band, bounded test git, linked-worktree fixture
- execFileSync options now match the repo's git band (10s window,
windowsHide, 4MiB maxBuffer) — a spurious 4s timeout silently reverted
to the pre-fix collision.
- test git helper bounded (15s) per local/no-unbounded-spawn.
- new fixture: allocation FROM a linked worktree counts the main
checkout — the incident's actual topology direction.
- fixture-setup rmSync carries the sanctioned lint-disable (setup, not
teardown; cleanup() still owns directory removal).
* fix(#3849): exempt the sibling-worktree scan from the enumeration-drift guard
collectSiblingWorktreePhaseNums reads a SIBLING checkout's phases dir —
a different question from the cwd-scoped listMilestonePhaseDirs the
guard routes everything to (which cannot see another worktree's
.planning at all). Function-scoped, per ADR-3180 Decision 4(a): any
other re-derivation in phase.cts is still caught. The GREEN bench
caught the omission.
* chore(#3849): changeset fragment (pr number backfilled after PR creation)
* chore(#3849): backfill changeset PR number (4042)
---------
Co-authored-by: sim <sim@local>