diff --git a/packages/loopover-engine/src/objective-anchor.ts b/packages/loopover-engine/src/objective-anchor.ts index ce59c55f03..915d65eff9 100644 --- a/packages/loopover-engine/src/objective-anchor.ts +++ b/packages/loopover-engine/src/objective-anchor.ts @@ -234,7 +234,7 @@ function kindsFromPath(path: string): ObjectiveAnchorChangeKind[] { if (CONFIG_FILENAMES.has(filename) || filename.endsWith(".jsonc") || filename.endsWith(".toml")) { kinds.push("config"); } - if (/package(?:-lock)?\.json$/u.test(filename)) { + if (/^package(?:-lock)?\.json$/u.test(filename)) { kinds.push("dependency"); } return kinds; diff --git a/packages/loopover-engine/test/objective-anchor.test.ts b/packages/loopover-engine/test/objective-anchor.test.ts index 5f0d84d57c..8de3808495 100644 --- a/packages/loopover-engine/test/objective-anchor.test.ts +++ b/packages/loopover-engine/test/objective-anchor.test.ts @@ -70,6 +70,14 @@ test("extractObjectiveAnchorFeatures normalizes paths, derives modules, and clas ]); }); +test("extractObjectiveAnchorFeatures tags only exact package(.-lock).json as a dependency, not a prefixed sibling (#8874)", () => { + const positive = extractObjectiveAnchorFeatures({ paths: ["package.json", "package-lock.json"] }); + assert.ok(positive.changeKinds.includes("dependency")); + + const negative = extractObjectiveAnchorFeatures({ paths: ["sub-package.json", "mock-package.json"] }); + assert.ok(!negative.changeKinds.includes("dependency")); +}); + test("scoreObjectiveAnchor returns 1 for full structural overlap", () => { const result = scoreObjectiveAnchor({ replayed: replay(), revealed: revealed() }); diff --git a/test/unit/engine-objective-anchor-config-classification.test.ts b/test/unit/engine-objective-anchor-config-classification.test.ts index e7b7a1fdc2..f2afafae2f 100644 --- a/test/unit/engine-objective-anchor-config-classification.test.ts +++ b/test/unit/engine-objective-anchor-config-classification.test.ts @@ -16,4 +16,25 @@ describe("loopover-engine objective-anchor config-filename classification", () = expect(features.changeKinds).toContain("config"); expect(features.paths).toEqual([".loopover.yml"]); }); + + // The dependency check must use the same exact-match discipline as the adjacent CONFIG_FILENAMES + // check: an anchored /^package(?:-lock)?\.json$/ so a differently-prefixed sibling is NOT tagged + // "dependency" (#8874). Exercised on the vitest side because Codecov grades this file via vitest. + it("tags only exact package(.-lock).json as a 'dependency' change kind, not a prefixed sibling (#8874)", () => { + const dependency = extractObjectiveAnchorFeatures({ + paths: ["package.json", "package-lock.json"], + labels: [], + titles: [], + notes: [], + }); + expect(dependency.changeKinds).toContain("dependency"); + + const notDependency = extractObjectiveAnchorFeatures({ + paths: ["sub-package.json", "mock-package.json"], + labels: [], + titles: [], + notes: [], + }); + expect(notDependency.changeKinds).not.toContain("dependency"); + }); });