(cwd: string, prompt: string)
| 541 | * 3. **nothing indexed reachable** → do nothing (the agent's own tools apply). |
| 542 | */ |
| 543 | export function planFrontload(cwd: string, prompt: string): FrontloadPlan { |
| 544 | const none: FrontloadPlan = { exploreRoot: null, nudgeProjects: [], viaSubScan: false }; |
| 545 | |
| 546 | // 1. up-walk — nearest indexed ancestor (incl. cwd). Cheap; covers the common |
| 547 | // single-project case without a down-scan. |
| 548 | let dir = path.resolve(cwd); |
| 549 | for (let i = 0; i < 6; i++) { |
| 550 | if (isInitialized(dir)) return { exploreRoot: dir, nudgeProjects: [], viaSubScan: false }; |
| 551 | const parent = path.dirname(dir); |
| 552 | if (parent === dir) break; |
| 553 | dir = parent; |
| 554 | } |
| 555 | |
| 556 | // 2. down-scan — only from something that looks like a workspace root, so a |
| 557 | // non-project cwd (e.g. $HOME) is a cheap no-op, not a deep crawl. |
| 558 | const base = path.resolve(cwd); |
| 559 | if (!looksLikeProjectRoot(base)) return none; |
| 560 | const subs = findIndexedSubprojectRoots(base); |
| 561 | if (subs.length === 0) return none; |
| 562 | if (subs.length === 1) return { exploreRoot: subs[0]!, nudgeProjects: [], viaSubScan: true }; |
| 563 | |
| 564 | // Several indexed sub-projects — pick the one the prompt points at, if any. |
| 565 | const p = prompt.toLowerCase(); |
| 566 | let best: { root: string; score: number; relLen: number } | null = null; |
| 567 | for (const s of subs) { |
| 568 | const rel = path.relative(base, s); |
| 569 | const relLc = rel.split(path.sep).join('/').toLowerCase(); |
| 570 | const name = path.basename(s).toLowerCase(); |
| 571 | let score = 0; |
| 572 | if (relLc && p.includes(relLc)) score = 10; // "packages/api" |
| 573 | else if (name.length >= 3 && new RegExp(`\\b${escapeRegExp(name)}\\b`).test(p)) score = 5; // "api" |
| 574 | if (score > 0 && (!best || score > best.score || (score === best.score && rel.length < best.relLen))) { |
| 575 | best = { root: s, score, relLen: rel.length }; |
| 576 | } |
| 577 | } |
| 578 | if (best) { |
| 579 | return { exploreRoot: best.root, nudgeProjects: subs.filter((s) => s !== best!.root), viaSubScan: true }; |
| 580 | } |
| 581 | // No clear match — nudge the full list rather than front-load a guess. |
| 582 | return { exploreRoot: null, nudgeProjects: subs, viaSubScan: true }; |
| 583 | } |
| 584 | |
| 585 | /** |
| 586 | * Contents of `.codegraph/.gitignore`. A single wildcard ignore keeps every |
no test coverage detected