From a2446db8e808529d19803ada464cc9d605a89638 Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Sun, 26 Jul 2026 21:34:45 +0800 Subject: [PATCH] fix(engine): classify 'ci' change-kind only by real CI path segments, not any .yml/.yaml objective-anchor.ts's kindsFromPath tagged any YAML file as 'ci' via a bare `.yml`/`.yaml` extension fallback, so a root `.loopover.yml` or `docs/mkdocs.yml` diluted the 'ci' change-kind dimension scoreObjectiveAnchor relies on. Drop the extension fallback -- 'ci' is now classified only via a real CI path segment (CI_SEGMENTS), the same path-segment discipline every other change-kind uses. Tests: a config YAML at the repo root and docs/mkdocs.yml no longer produce 'ci'; a YAML under a real CI path segment (.github/workflows/ci.yml) still does. --- packages/loopover-engine/src/objective-anchor.ts | 5 ++++- ...ne-objective-anchor-config-classification.test.ts | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/loopover-engine/src/objective-anchor.ts b/packages/loopover-engine/src/objective-anchor.ts index ce59c55f03..d815ca3a75 100644 --- a/packages/loopover-engine/src/objective-anchor.ts +++ b/packages/loopover-engine/src/objective-anchor.ts @@ -228,7 +228,10 @@ function kindsFromPath(path: string): ObjectiveAnchorChangeKind[] { if (DOC_EXTENSIONS.has(extensionOf(path)) || segments.includes("docs") || filename.toLowerCase() === "readme.md") { kinds.push("docs"); } - if (segments.some((segment) => CI_SEGMENTS.has(segment)) || filename.endsWith(".yml") || filename.endsWith(".yaml")) { + // "ci" is classified ONLY by a real CI path segment (CI_SEGMENTS), matching the path-segment discipline every + // other kind here uses. The old bare `.yml`/`.yaml` extension fallback tagged any YAML file anywhere (root + // `.loopover.yml`, `docs/mkdocs.yml`) as "ci", diluting the signal scoreObjectiveAnchor relies on (#8873). + if (segments.some((segment) => CI_SEGMENTS.has(segment))) { kinds.push("ci"); } if (CONFIG_FILENAMES.has(filename) || filename.endsWith(".jsonc") || filename.endsWith(".toml")) { diff --git a/test/unit/engine-objective-anchor-config-classification.test.ts b/test/unit/engine-objective-anchor-config-classification.test.ts index e7b7a1fdc2..a631b7f8f1 100644 --- a/test/unit/engine-objective-anchor-config-classification.test.ts +++ b/test/unit/engine-objective-anchor-config-classification.test.ts @@ -14,6 +14,18 @@ describe("loopover-engine objective-anchor config-filename classification", () = }); expect(features.changeKinds).toContain("config"); + // #8873: a config YAML at the repo root must NOT be tagged "ci" purely by its extension. + expect(features.changeKinds).not.toContain("ci"); expect(features.paths).toEqual([".loopover.yml"]); }); + + it("does NOT tag a non-CI YAML file (docs/mkdocs.yml) as a 'ci' change kind (#8873)", () => { + const features = extractObjectiveAnchorFeatures({ paths: ["docs/mkdocs.yml"], labels: [], titles: [], notes: [] }); + expect(features.changeKinds).not.toContain("ci"); + }); + + it("still tags a YAML under a real CI path segment (.github/workflows/ci.yml) as 'ci' (#8873)", () => { + const features = extractObjectiveAnchorFeatures({ paths: [".github/workflows/ci.yml"], labels: [], titles: [], notes: [] }); + expect(features.changeKinds).toContain("ci"); + }); });