diff --git a/gitnexus/test/helpers/literal-collectors.ts b/gitnexus/test/helpers/literal-collectors.ts index 6e34e5165..f4aed2937 100644 --- a/gitnexus/test/helpers/literal-collectors.ts +++ b/gitnexus/test/helpers/literal-collectors.ts @@ -139,6 +139,17 @@ function fileLanguages(relPath: string): SupportedLanguages[] { } const base = relPath.replace(/\.ts$/, '').split('/').pop() ?? ''; if (BASENAME_LANGS[base]) return BASENAME_LANGS[base]; + // A `-harvest.ts` CFG def/use harvester is validated against the SAME + // grammar(s) as its `.ts` CFG visitor (go-harvest → Go, c-cpp-harvest → + // C+C++, typescript-harvest → TS, …) — strip the suffix and reuse the visitor + // basename map. The two genuinely language-agnostic harvesters — + // call-site-harvest.ts (pure taint-site mechanism) and scope-tree-harvest.ts + // (shared lexical-scope substrate) — name no grammar, so their stripped base + // (`call-site`, `scope-tree`) misses BASENAME_LANGS and they fall through to + // the valid-if-any ALL_LANGS bucket below (correct: they pin no per-grammar + // literal). + const harvestBase = base.replace(/-harvest$/, ''); + if (harvestBase !== base && BASENAME_LANGS[harvestBase]) return BASENAME_LANGS[harvestBase]; // generic / shared / cross-language helpers → any grammar (valid-if-any) return [...ALL_LANGS]; } @@ -183,12 +194,15 @@ function mode2Files(): string[] { if (existsSync(cap)) files.push(cap); } } - // CFG visitors (cfg/visitors/.ts) hard-code tree-sitter node-type and - // field literals directly; include them so the gate validates per-language - // CFG literals against the right grammar (basename → grammar via - // `fileLanguages`: c-cpp → C+C++, csharp → C#, java → Java, go → Go, - // typescript[-harvest] → TS). Without this, the gate stays green on dead - // literals in a new visitor — the exact failure KTD5 warns about. + // CFG visitors (cfg/visitors/.ts) AND their def/use harvesters + // (cfg/visitors/-harvest.ts) hard-code tree-sitter node-type and field + // literals directly; include them so the gate validates per-language CFG + // literals against the right grammar (basename → grammar via `fileLanguages`: + // c-cpp[-harvest] → C+C++, csharp[-harvest] → C#, go[-harvest] → Go, + // typescript[-harvest] → TS, …; the language-agnostic call-site-harvest.ts and + // scope-tree-harvest.ts carry no grammar literal and stay valid-if-any). + // Without this, the gate stays green on a dead literal in a new visitor or + // harvester — the exact failure KTD5 warns about. const cfgVisitorsDir = join(INGESTION_DIR, 'cfg', 'visitors'); if (existsSync(cfgVisitorsDir)) walkTs(cfgVisitorsDir, files); const exportDetection = join(INGESTION_DIR, 'export-detection.ts'); diff --git a/gitnexus/test/integration/literal-collectors.test.ts b/gitnexus/test/integration/literal-collectors.test.ts index 9715cdb18..064bc0445 100644 --- a/gitnexus/test/integration/literal-collectors.test.ts +++ b/gitnexus/test/integration/literal-collectors.test.ts @@ -82,6 +82,29 @@ describe('literal-collectors', () => { }); }); + describe('fileLanguages — `*-harvest.ts` map to their grammar (U10)', () => { + const fl = __test.fileLanguages; + it('maps each -harvest.ts to the same grammar(s) as its .ts visitor', () => { + expect(fl('cfg/visitors/go-harvest.ts')).toEqual([SupportedLanguages.Go]); + expect(fl('cfg/visitors/csharp-harvest.ts')).toEqual([SupportedLanguages.CSharp]); + expect(fl('cfg/visitors/typescript-harvest.ts')).toEqual([SupportedLanguages.TypeScript]); + expect(fl('cfg/visitors/c-cpp-harvest.ts')).toEqual([ + SupportedLanguages.C, + SupportedLanguages.CPlusPlus, + ]); + // a harvester pins exactly its visitor's grammar set, not the weak ALL_LANGS + expect(fl('cfg/visitors/go-harvest.ts')).toEqual(fl('cfg/visitors/go.ts')); + }); + it('leaves the language-agnostic harvesters valid-if-any (ALL_LANGS)', () => { + const all = fl('cfg/visitors/call-site-harvest.ts'); + const scopeTree = fl('cfg/visitors/scope-tree-harvest.ts'); + // both name no grammar → fall through to the full gated set, which is far + // larger than any single-/dual-language harvester mapping + expect(all.length).toBeGreaterThan(2); + expect(scopeTree).toEqual(all); + }); + }); + describe('Mode 4 — registry resolution layer (TypeChecker-gated)', () => { it('scans the resolution layer and tags literals by language dir', () => { const { nodeTypes } = __test.collectResolutionLayerLiterals();