diff --git a/packages/loopover-engine/src/miner/repo-map.ts b/packages/loopover-engine/src/miner/repo-map.ts index 8e103ae1fa..40d229dc01 100644 --- a/packages/loopover-engine/src/miner/repo-map.ts +++ b/packages/loopover-engine/src/miner/repo-map.ts @@ -268,11 +268,22 @@ export async function buildRepoMap( } const languageName = resolveRepoMapLanguage(file.path); const sourceBytes = Buffer.byteLength(file.sourceText, "utf8"); + // A file exceeding the per-file cap is skipped without being parsed, so it must NOT consume the + // aggregate parsed-work budget. Counting it before this check let one oversized file (a vendored/ + // minified asset or generated bundle) exhaust maxTotalSourceBytes and force every subsequent small, + // legitimate file to skip too — a silent, order-dependent near-empty map (#7247). Only files that pass + // the per-file cap accrue against the aggregate, exactly as before for in-cap files. + if (sourceBytes > maxSourceBytes) { + entries.push({ + path: file.path, + language: languageName, + symbols: [], + skipped: "resource_limit", + }); + continue; + } totalSourceBytes += sourceBytes; - if ( - sourceBytes > maxSourceBytes || - totalSourceBytes > maxTotalSourceBytes - ) { + if (totalSourceBytes > maxTotalSourceBytes) { entries.push({ path: file.path, language: languageName, diff --git a/test/unit/repo-map.test.ts b/test/unit/repo-map.test.ts index 5108c8041d..90cadc6271 100644 --- a/test/unit/repo-map.test.ts +++ b/test/unit/repo-map.test.ts @@ -229,6 +229,28 @@ describe("buildRepoMap + extractRepoMapSymbols (#4280)", () => { }); }); + it("does not charge a file skipped for the per-file cap against the aggregate budget (#7247)", async () => { + // "function reallyLongName() {}" (28 bytes) exceeds the per-file cap and is skipped WITHOUT being + // parsed; pre-#7247 its bytes were still charged to the aggregate budget, exhausting it and skipping + // the small, legitimate file after it. The aggregate must only account for files actually parsed. + const entries = await buildRepoMap( + [ + { path: "src/huge.ts", sourceText: "function reallyLongName() {}" }, + { path: "src/small.ts", sourceText: "function s() {}" }, + ], + { maxSourceBytes: 20, maxTotalSourceBytes: 20 }, + ); + expect(entries[0]).toEqual({ + path: "src/huge.ts", + language: "typescript", + symbols: [], + skipped: "resource_limit", + }); + // The small file after the skipped-oversized one is still parsed, not starved of aggregate budget. + expect(entries[1]!.skipped).toBeUndefined(); + expect(entries[1]!.symbols.map((symbol) => symbol.name)).toEqual(["s"]); + }); + it("keeps one entry per input file but resource-limits files beyond the file-count budget", async () => { const entries = await buildRepoMap( [