(cwd: string, prompt: string)
| 345 | * 3. **nothing indexed reachable** → do nothing (the agent's own tools apply). |
| 346 | */ |
| 347 | export function planFrontload(cwd: string, prompt: string): FrontloadPlan { |
| 348 | const none: FrontloadPlan = { exploreRoot: null, nudgeProjects: [], viaSubScan: false }; |
| 349 | |
| 350 | // 1. up-walk — nearest indexed ancestor (incl. cwd). Cheap; covers the common |
| 351 | // single-project case without a down-scan. |
| 352 | let dir = path.resolve(cwd); |
| 353 | for (let i = 0; i < 6; i++) { |
| 354 | if (isInitialized(dir)) return { exploreRoot: dir, nudgeProjects: [], viaSubScan: false }; |
| 355 | const parent = path.dirname(dir); |
| 356 | if (parent === dir) break; |
| 357 | dir = parent; |
| 358 | } |
| 359 | |
| 360 | // 2. down-scan — only from something that looks like a workspace root, so a |
| 361 | // non-project cwd (e.g. $HOME) is a cheap no-op, not a deep crawl. |
| 362 | const base = path.resolve(cwd); |
| 363 | if (!looksLikeProjectRoot(base)) return none; |
| 364 | const subs = findIndexedSubprojectRoots(base); |
| 365 | if (subs.length === 0) return none; |
| 366 | if (subs.length === 1) return { exploreRoot: subs[0]!, nudgeProjects: [], viaSubScan: true }; |
| 367 | |
| 368 | // Several indexed sub-projects — pick the one the prompt points at, if any. |
| 369 | const p = prompt.toLowerCase(); |
| 370 | let best: { root: string; score: number; relLen: number } | null = null; |
| 371 | for (const s of subs) { |
| 372 | const rel = path.relative(base, s); |
| 373 | const relLc = rel.split(path.sep).join('/').toLowerCase(); |
| 374 | const name = path.basename(s).toLowerCase(); |
| 375 | let score = 0; |
| 376 | if (relLc && p.includes(relLc)) score = 10; // "packages/api" |
| 377 | else if (name.length >= 3 && new RegExp(`\\b${escapeRegExp(name)}\\b`).test(p)) score = 5; // "api" |
| 378 | if (score > 0 && (!best || score > best.score || (score === best.score && rel.length < best.relLen))) { |
| 379 | best = { root: s, score, relLen: rel.length }; |
| 380 | } |
| 381 | } |
| 382 | if (best) { |
| 383 | return { exploreRoot: best.root, nudgeProjects: subs.filter((s) => s !== best!.root), viaSubScan: true }; |
| 384 | } |
| 385 | // No clear match — nudge the full list rather than front-load a guess. |
| 386 | return { exploreRoot: null, nudgeProjects: subs, viaSubScan: true }; |
| 387 | } |
| 388 | |
| 389 | /** |
| 390 | * Contents of `.codegraph/.gitignore`. A single wildcard ignore keeps every |
no test coverage detected