(fileCount: number)
| 190 | } |
| 191 | |
| 192 | export function getExploreOutputBudget(fileCount: number): ExploreOutputBudget { |
| 193 | // Tiered budget, scaled to project size. The budget is a CEILING (relevance |
| 194 | // still gates WHAT is included), and it MUST stay under the agent's INLINE |
| 195 | // tool-result cap (~25K chars). Above that, the host externalizes the result |
| 196 | // to a file the agent then Reads back — re-introducing a read AND the |
| 197 | // cache-write cost — which is exactly what a 35K vscode explore did in the |
| 198 | // n=4 README A/B. So even large repos cap at ~24K: the answer is the handful |
| 199 | // of ~100-line flow windows the agent would have grep-located and read (it |
| 200 | // natively reads ~6–9 files, median 100-line ranges), NOT a sprawl of 12 |
| 201 | // files. Concentration onto the flow emerges from this cap + the named-file- |
| 202 | // first sort dropping peripheral files. Invariant: a larger tier must never |
| 203 | // get a smaller `maxCharsPerFile` than a smaller tier. |
| 204 | if (fileCount < 150) { |
| 205 | return { |
| 206 | // ITER3: revert iter2's aggressive body shrink (forced Read fallback — |
| 207 | // the per-file 2.5K cap pushed the agent to Read instead of node). |
| 208 | // Back to the iter1 shape (13K/4/3.8K) but keep the test-file |
| 209 | // hard-exclude. The cost lever for this tier lives in steering the |
| 210 | // agent to stop after 1-2 calls, not in this budget. |
| 211 | maxOutputChars: 13000, |
| 212 | defaultMaxFiles: 4, |
| 213 | maxCharsPerFile: 3800, |
| 214 | gapThreshold: 7, |
| 215 | maxSymbolsInFileHeader: 5, |
| 216 | maxEdgesPerRelationshipKind: 4, |
| 217 | includeRelationships: false, |
| 218 | includeAdditionalFiles: false, |
| 219 | includeCompletenessSignal: false, |
| 220 | includeBudgetNote: false, |
| 221 | excludeLowValueFiles: true, |
| 222 | }; |
| 223 | } |
| 224 | if (fileCount < 500) { |
| 225 | return { |
| 226 | // ITER3: same revert/keep-filter pattern as <150. |
| 227 | maxOutputChars: 18000, |
| 228 | defaultMaxFiles: 5, |
| 229 | maxCharsPerFile: 3800, |
| 230 | gapThreshold: 8, |
| 231 | maxSymbolsInFileHeader: 6, |
| 232 | maxEdgesPerRelationshipKind: 6, |
| 233 | includeRelationships: false, |
| 234 | includeAdditionalFiles: false, |
| 235 | includeCompletenessSignal: false, |
| 236 | includeBudgetNote: false, |
| 237 | excludeLowValueFiles: true, |
| 238 | }; |
| 239 | } |
| 240 | if (fileCount < 5000) { |
| 241 | return { |
| 242 | // ~150-line per-file window (the native read unit) × ~6 files, capped at |
| 243 | // the ~24K inline ceiling so the response is never externalized. Per-file |
| 244 | // stays ≥ the <500 tier (3800) — monotonic. |
| 245 | maxOutputChars: 24000, |
| 246 | defaultMaxFiles: 8, |
| 247 | maxCharsPerFile: 6500, |
| 248 | gapThreshold: 12, |
| 249 | maxSymbolsInFileHeader: 10, |
no outgoing calls
no test coverage detected