diff --git a/packages/loopover-engine/src/review/enrichment-analyzer-names.ts b/packages/loopover-engine/src/review/enrichment-analyzer-names.ts index 7eec217916..1d13489cd4 100644 --- a/packages/loopover-engine/src/review/enrichment-analyzer-names.ts +++ b/packages/loopover-engine/src/review/enrichment-analyzer-names.ts @@ -26,6 +26,7 @@ export const REES_ANALYZER_NAMES = [ "history", "docCommentDrift", "duplication", + "duplicationDelta", "churnHotspot", "blameLink", "approvalIntegrity", diff --git a/src/review/enrichment-analyzer-names.ts b/src/review/enrichment-analyzer-names.ts index 7eec217916..1d13489cd4 100644 --- a/src/review/enrichment-analyzer-names.ts +++ b/src/review/enrichment-analyzer-names.ts @@ -26,6 +26,7 @@ export const REES_ANALYZER_NAMES = [ "history", "docCommentDrift", "duplication", + "duplicationDelta", "churnHotspot", "blameLink", "approvalIntegrity", diff --git a/test/unit/enrichment-analyzers-taxonomy.test.ts b/test/unit/enrichment-analyzers-taxonomy.test.ts index 2f191415ff..329c98d43b 100644 --- a/test/unit/enrichment-analyzers-taxonomy.test.ts +++ b/test/unit/enrichment-analyzers-taxonomy.test.ts @@ -5,6 +5,8 @@ import { buildEnrichmentAnalyzersTaxonomyDocument, ENRICHMENT_ANALYZERS_URI, } from "../../src/review/enrichment-analyzers-taxonomy"; +import { REES_ANALYZER_NAMES } from "../../src/review/enrichment-analyzer-names"; +import { REES_ANALYZER_NAMES as ENGINE_REES_ANALYZER_NAMES } from "../../packages/loopover-engine/src/review/enrichment-analyzer-names"; const metadataPath = join(process.cwd(), "review-enrichment/analyzer-metadata.json"); @@ -45,3 +47,34 @@ describe("enrichment analyzers taxonomy document", () => { expect(ENRICHMENT_ANALYZERS_URI).toBe("gittensory://enrichment-analyzers"); }); }); + +describe("REES_ANALYZER_NAMES stays in sync with analyzer-metadata.json", () => { + const metadataNames = ( + JSON.parse(readFileSync(metadataPath, "utf8")) as { analyzers: Array<{ name: string }> } + ).analyzers.map((a) => a.name); + + it("covers exactly the analyzers the metadata registry defines (no missing, no extra)", () => { + // The canonical name list validates every operator `REES_ANALYZERS` env entry and per-repo + // `.loopover.yml review.enrichment` toggle, so an analyzer present in the metadata registry but absent + // here is silently un-toggleable/un-selectable. Compared as sets against the registry (the source of + // truth) rather than a hardcoded count so a newly-added analyzer can't drift the two apart unnoticed. + expect(new Set(REES_ANALYZER_NAMES)).toEqual(new Set(metadataNames)); + }); + + it("includes duplicationDelta alongside duplication (regression for the one-entry gap)", () => { + expect(REES_ANALYZER_NAMES).toContain("duplication"); + expect(REES_ANALYZER_NAMES).toContain("duplicationDelta"); + }); + + it("has no duplicate entries", () => { + expect(new Set(REES_ANALYZER_NAMES).size).toBe(REES_ANALYZER_NAMES.length); + }); + + it("stays in lockstep with the loopover-engine twin copy (same names, same order)", () => { + // enrichment-analyzer-names.ts is duplicated across the server (`src/`) and the engine package + // (`packages/loopover-engine/src/`) as a leaf module the engine-parity contract keeps byte-identical. + // Assert the two exported lists by value so a name added to one copy but not the other — e.g. the + // duplicationDelta gap this fixes — is caught as a divergence rather than silently drifting. + expect([...ENGINE_REES_ANALYZER_NAMES]).toEqual([...REES_ANALYZER_NAMES]); + }); +});