( dir: string, )
| 127 | * the right file on resume failure. |
| 128 | */ |
| 129 | export async function readBridgePointerAcrossWorktrees( |
| 130 | dir: string, |
| 131 | ): Promise<{ pointer: BridgePointer & { ageMs: number }; dir: string } | null> { |
| 132 | // Fast path: current dir. Covers standalone bridge (always matches) and |
| 133 | // REPL bridge when no worktree mutation happened. |
| 134 | const here = await readBridgePointer(dir) |
| 135 | if (here) { |
| 136 | return { pointer: here, dir } |
| 137 | } |
| 138 | |
| 139 | // Fanout: scan worktree siblings. getWorktreePathsPortable has a 5s |
| 140 | // timeout and returns [] on any error (not a git repo, git not installed). |
| 141 | const worktrees = await getWorktreePathsPortable(dir) |
| 142 | if (worktrees.length <= 1) return null |
| 143 | if (worktrees.length > MAX_WORKTREE_FANOUT) { |
| 144 | logForDebugging( |
| 145 | `[bridge:pointer] ${worktrees.length} worktrees exceeds fanout cap ${MAX_WORKTREE_FANOUT}, skipping`, |
| 146 | ) |
| 147 | return null |
| 148 | } |
| 149 | |
| 150 | // Dedupe against `dir` so we don't re-stat it. sanitizePath normalizes |
| 151 | // case/separators so worktree-list output matches our fast-path key even |
| 152 | // on Windows where git may emit C:/ vs stored c:/. |
| 153 | const dirKey = sanitizePath(dir) |
| 154 | const candidates = worktrees.filter(wt => sanitizePath(wt) !== dirKey) |
| 155 | |
| 156 | // Parallel stat+read. Each readBridgePointer is a stat() that ENOENTs |
| 157 | // for worktrees with no pointer (cheap) plus a ~100-byte read for the |
| 158 | // rare ones that have one. Promise.all → latency ≈ slowest single stat. |
| 159 | const results = await Promise.all( |
| 160 | candidates.map(async wt => { |
| 161 | const p = await readBridgePointer(wt) |
| 162 | return p ? { pointer: p, dir: wt } : null |
| 163 | }), |
| 164 | ) |
| 165 | |
| 166 | // Pick freshest (lowest ageMs). The pointer stores environmentId so |
| 167 | // resume reconnects to the right env regardless of which worktree |
| 168 | // --continue was invoked from. |
| 169 | let freshest: { |
| 170 | pointer: BridgePointer & { ageMs: number } |
| 171 | dir: string |
| 172 | } | null = null |
| 173 | for (const r of results) { |
| 174 | if (r && (!freshest || r.pointer.ageMs < freshest.pointer.ageMs)) { |
| 175 | freshest = r |
| 176 | } |
| 177 | } |
| 178 | if (freshest) { |
| 179 | logForDebugging( |
| 180 | `[bridge:pointer] fanout found pointer in worktree ${freshest.dir} (ageMs=${freshest.pointer.ageMs})`, |
| 181 | ) |
| 182 | } |
| 183 | return freshest |
| 184 | } |
| 185 | |
| 186 | /** |
no test coverage detected