(cwd: string, prompt: string)
| 798 | * 3. **nothing indexed reachable** → do nothing (the agent's own tools apply). |
| 799 | */ |
| 800 | export function planFrontload(cwd: string, prompt: string): FrontloadPlan { |
| 801 | const none: FrontloadPlan = { exploreRoot: null, nudgeProjects: [], viaSubScan: false }; |
| 802 | |
| 803 | // 1. up-walk — nearest indexed ancestor (incl. cwd). Cheap; covers the common |
| 804 | // single-project case without a down-scan. |
| 805 | let dir = path.resolve(cwd); |
| 806 | for (let i = 0; i < 6; i++) { |
| 807 | if (isInitialized(dir)) return { exploreRoot: dir, nudgeProjects: [], viaSubScan: false }; |
| 808 | const parent = path.dirname(dir); |
| 809 | if (parent === dir) break; |
| 810 | dir = parent; |
| 811 | } |
| 812 | |
| 813 | // 2. down-scan — only from something that looks like a workspace root, so a |
| 814 | // non-project cwd (e.g. $HOME) is a cheap no-op, not a deep crawl. |
| 815 | const base = path.resolve(cwd); |
| 816 | if (!looksLikeProjectRoot(base)) return none; |
| 817 | const subs = findIndexedSubprojectRoots(base); |
| 818 | if (subs.length === 0) return none; |
| 819 | if (subs.length === 1) return { exploreRoot: subs[0]!, nudgeProjects: [], viaSubScan: true }; |
| 820 | |
| 821 | // Several indexed sub-projects — pick the one the prompt points at, if any. |
| 822 | const p = prompt.toLowerCase(); |
| 823 | let best: { root: string; score: number; relLen: number } | null = null; |
| 824 | for (const s of subs) { |
| 825 | const rel = path.relative(base, s); |
| 826 | const relLc = rel.split(path.sep).join('/').toLowerCase(); |
| 827 | const name = path.basename(s).toLowerCase(); |
| 828 | let score = 0; |
| 829 | if (relLc && p.includes(relLc)) score = 10; // "packages/api" |
| 830 | else if (name.length >= 3 && new RegExp(`\\b${escapeRegExp(name)}\\b`).test(p)) score = 5; // "api" |
| 831 | if (score > 0 && (!best || score > best.score || (score === best.score && rel.length < best.relLen))) { |
| 832 | best = { root: s, score, relLen: rel.length }; |
| 833 | } |
| 834 | } |
| 835 | if (best) { |
| 836 | return { exploreRoot: best.root, nudgeProjects: subs.filter((s) => s !== best!.root), viaSubScan: true }; |
| 837 | } |
| 838 | // No clear match — nudge the full list rather than front-load a guess. |
| 839 | return { exploreRoot: null, nudgeProjects: subs, viaSubScan: true }; |
| 840 | } |
| 841 | |
| 842 | /** |
| 843 | * Contents of `.codegraph/.gitignore`. A single wildcard ignore keeps every |
no test coverage detected