From c4a0386a0dbfe1a86906891aa40d7a3cef0949b8 Mon Sep 17 00:00:00 2001 From: shin-core <153108882+shin-core@users.noreply.github.com> Date: Sun, 26 Jul 2026 22:18:52 +0900 Subject: [PATCH] fix(engine): anchor objective-anchor's dependency-file regex to exact filenames kindsFromPath tagged any path ending in package.json / package-lock.json as a "dependency" change kind via an unanchored /package(?:-lock)?\.json$/, so a differently-prefixed sibling like mock-package.json or sub-package.json was misclassified -- inconsistent with the adjacent config check in the same function, which uses exact-match CONFIG_FILENAMES set membership. Anchor the regex (/^package(?:-lock)?\.json$/) so only the exact filenames package.json and package-lock.json classify as "dependency". Adds a negative test on both the engine's own suite and the Codecov-graded vitest classification suite: package.json/package-lock.json still classify, sub-package.json/mock-package.json no longer do. Closes #8874 --- .../loopover-engine/src/objective-anchor.ts | 2 +- .../test/objective-anchor.test.ts | 8 +++++++ ...ctive-anchor-config-classification.test.ts | 21 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) 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"); + }); });