* Gathers candidate session files for a specific project directory * (and optionally its git worktrees).
( dir: string, includeWorktrees: boolean, doStat: boolean, )
| 307 | * (and optionally its git worktrees). |
| 308 | */ |
| 309 | async function gatherProjectCandidates( |
| 310 | dir: string, |
| 311 | includeWorktrees: boolean, |
| 312 | doStat: boolean, |
| 313 | ): Promise<Candidate[]> { |
| 314 | const canonicalDir = await canonicalizePath(dir) |
| 315 | |
| 316 | let worktreePaths: string[] |
| 317 | if (includeWorktrees) { |
| 318 | try { |
| 319 | worktreePaths = await getWorktreePathsPortable(canonicalDir) |
| 320 | } catch { |
| 321 | worktreePaths = [] |
| 322 | } |
| 323 | } else { |
| 324 | worktreePaths = [] |
| 325 | } |
| 326 | |
| 327 | // No worktrees (or git not available / scanning disabled) — just scan the single project dir |
| 328 | if (worktreePaths.length <= 1) { |
| 329 | const projectDir = await findProjectDir(canonicalDir) |
| 330 | if (!projectDir) return [] |
| 331 | return listCandidates(projectDir, doStat, canonicalDir) |
| 332 | } |
| 333 | |
| 334 | // Worktree-aware scanning: find all project dirs matching any worktree |
| 335 | const projectsDir = getProjectsDir() |
| 336 | const caseInsensitive = process.platform === 'win32' |
| 337 | |
| 338 | // Sort worktree paths by sanitized prefix length (longest first) so |
| 339 | // more specific matches take priority over shorter ones |
| 340 | const indexed = worktreePaths.map(wt => { |
| 341 | const sanitized = sanitizePath(wt) |
| 342 | return { |
| 343 | path: wt, |
| 344 | prefix: caseInsensitive ? sanitized.toLowerCase() : sanitized, |
| 345 | } |
| 346 | }) |
| 347 | indexed.sort((a, b) => b.prefix.length - a.prefix.length) |
| 348 | |
| 349 | let allDirents: Dirent[] |
| 350 | try { |
| 351 | allDirents = await readdir(projectsDir, { withFileTypes: true }) |
| 352 | } catch { |
| 353 | // Fall back to single project dir |
| 354 | const projectDir = await findProjectDir(canonicalDir) |
| 355 | if (!projectDir) return [] |
| 356 | return listCandidates(projectDir, doStat, canonicalDir) |
| 357 | } |
| 358 | |
| 359 | const all: Candidate[] = [] |
| 360 | const seenDirs = new Set<string>() |
| 361 | |
| 362 | // Always include the user's actual directory (handles subdirectories |
| 363 | // like /repo/packages/my-app that won't match worktree root prefixes) |
| 364 | const canonicalProjectDir = await findProjectDir(canonicalDir) |
| 365 | if (canonicalProjectDir) { |
| 366 | const dirBase = basename(canonicalProjectDir) |
no test coverage detected