(cwd: string, prompt: string)
| 600 | * 3. **nothing indexed reachable** → do nothing (the agent's own tools apply). |
| 601 | */ |
| 602 | export function planFrontload(cwd: string, prompt: string): FrontloadPlan { |
| 603 | const none: FrontloadPlan = { exploreRoot: null, nudgeProjects: [], viaSubScan: false }; |
| 604 | |
| 605 | // 1. up-walk — nearest indexed ancestor (incl. cwd). Cheap; covers the common |
| 606 | // single-project case without a down-scan. |
| 607 | let dir = path.resolve(cwd); |
| 608 | for (let i = 0; i < 6; i++) { |
| 609 | if (isInitialized(dir)) return { exploreRoot: dir, nudgeProjects: [], viaSubScan: false }; |
| 610 | const parent = path.dirname(dir); |
| 611 | if (parent === dir) break; |
| 612 | dir = parent; |
| 613 | } |
| 614 | |
| 615 | // 2. down-scan — only from something that looks like a workspace root, so a |
| 616 | // non-project cwd (e.g. $HOME) is a cheap no-op, not a deep crawl. |
| 617 | const base = path.resolve(cwd); |
| 618 | if (!looksLikeProjectRoot(base)) return none; |
| 619 | const subs = findIndexedSubprojectRoots(base); |
| 620 | if (subs.length === 0) return none; |
| 621 | if (subs.length === 1) return { exploreRoot: subs[0]!, nudgeProjects: [], viaSubScan: true }; |
| 622 | |
| 623 | // Several indexed sub-projects — pick the one the prompt points at, if any. |
| 624 | const p = prompt.toLowerCase(); |
| 625 | let best: { root: string; score: number; relLen: number } | null = null; |
| 626 | for (const s of subs) { |
| 627 | const rel = path.relative(base, s); |
| 628 | const relLc = rel.split(path.sep).join('/').toLowerCase(); |
| 629 | const name = path.basename(s).toLowerCase(); |
| 630 | let score = 0; |
| 631 | if (relLc && p.includes(relLc)) score = 10; // "packages/api" |
| 632 | else if (name.length >= 3 && new RegExp(`\\b${escapeRegExp(name)}\\b`).test(p)) score = 5; // "api" |
| 633 | if (score > 0 && (!best || score > best.score || (score === best.score && rel.length < best.relLen))) { |
| 634 | best = { root: s, score, relLen: rel.length }; |
| 635 | } |
| 636 | } |
| 637 | if (best) { |
| 638 | return { exploreRoot: best.root, nudgeProjects: subs.filter((s) => s !== best!.root), viaSubScan: true }; |
| 639 | } |
| 640 | // No clear match — nudge the full list rather than front-load a guess. |
| 641 | return { exploreRoot: null, nudgeProjects: subs, viaSubScan: true }; |
| 642 | } |
| 643 | |
| 644 | /** |
| 645 | * Contents of `.codegraph/.gitignore`. A single wildcard ignore keeps every |
no test coverage detected