From 17e77fe6a16683c43237868ae89942ffe7682e84 Mon Sep 17 00:00:00 2001 From: RealDiligent Date: Thu, 30 Jul 2026 03:12:51 +0800 Subject: [PATCH] fix(content-lane): canonicalize issue-body path tokens in checkContentLaneDeliverable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkContentLaneDeliverable compared changed files canonicalized (lowercased, ./-stripped, backslash-normalized) but tested the issue-body path tokens RAW. The spec patterns are compiled by globToRegExp against a canonicalized path with no `i` flag, so an issue naming its target file with any capitalization — Registry/Subnets/ Foo.json — produced no mentionedPath, and the deliverable gate silently returned not-applicable, reopening the "test-only PR closes a content issue without delivering the content" gap the check exists to close. Test each token canonicalized at the point of match, mirroring classifyRegistryPrScope's matchesPattern helper, while find() still returns the ORIGINAL token so the mentionedPath rendered into the public PR comment quotes the issue body verbatim. Already-lowercase tokens are byte-identical. Closes #9667 --- src/review/content-lane/registry-logic.ts | 6 ++++- test/unit/content-lane-registry-logic.test.ts | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/review/content-lane/registry-logic.ts b/src/review/content-lane/registry-logic.ts index 921b018b2e..57f8869e37 100644 --- a/src/review/content-lane/registry-logic.ts +++ b/src/review/content-lane/registry-logic.ts @@ -930,7 +930,11 @@ export function checkContentLaneDeliverable( issueTitle?: string, ): ContentLaneDeliverableCheck { const matchesSpec = (candidate: string): boolean => spec.entryFilePattern.test(candidate) || (spec.providerFilePattern?.test(candidate) ?? false); - const mentionedPath = extractPathTokens(issueText).find(matchesSpec); + // Test each token CANONICALIZED (globToRegExp compiles the spec patterns against a canonicalized path with no + // `i` flag, so an uppercase / `./`-prefixed / `\`-separated body token would otherwise never match), exactly + // as classifyRegistryPrScope's matchesPattern does — while `.find` still returns the ORIGINAL token so the + // `mentionedPath` rendered into the public PR comment quotes the issue body verbatim. + const mentionedPath = extractPathTokens(issueText).find((token) => matchesSpec(canonicalize(token))); const titleImplies = !mentionedPath && Boolean(issueTitle && spec.issueTitleImpliesEntryPattern?.test(issueTitle)); if (!mentionedPath && !titleImplies) return { verdict: "not-applicable" }; const delivered = changedFiles.some((file) => matchesSpec(canonicalize(file))); diff --git a/test/unit/content-lane-registry-logic.test.ts b/test/unit/content-lane-registry-logic.test.ts index f017c0798b..df73c8fb24 100644 --- a/test/unit/content-lane-registry-logic.test.ts +++ b/test/unit/content-lane-registry-logic.test.ts @@ -669,6 +669,32 @@ describe("checkContentLaneDeliverable (generic, spec-driven — #content-lane-de expect(checkContentLaneDeliverable(spec, issueText, ["registry\\subnets\\foo.json"]).verdict).toBe("delivered"); }); + it("regression (#9667): canonicalizes issue-BODY path tokens — a mixed-case path is 'missing' when undelivered, quoting the body verbatim", () => { + const issueText = "Add the missing surfaces to Registry/Subnets/Foo.json."; + // No matching changed file ⇒ missing; the reported mentionedPath stays the ORIGINAL mixed-case token so the + // public PR comment quotes the issue as written (canonicalizing it would corrupt that text). + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "Registry/Subnets/Foo.json", + }); + }); + + it("regression (#9667): the same mixed-case body is 'delivered' when the PR changes the (lowercase) file", () => { + const issueText = "Add the missing surfaces to Registry/Subnets/Foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"]).verdict).toBe("delivered"); + }); + + it("all-lowercase body behaviour is byte-identical — delivered when the file is touched, missing (quoting the token) when it is not", () => { + const issueText = "Add registry/subnets/foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"]).verdict).toBe("delivered"); + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo.test.mjs"])).toEqual({ verdict: "missing", mentionedPath: "registry/subnets/foo.json" }); + }); + + it("does not over-broaden: a real body path token matching NO spec pattern is still not-applicable", () => { + const result = checkContentLaneDeliverable(spec, "See docs/architecture/overview.md for context.", ["registry/subnets/foo.json"]); + expect(result).toEqual({ verdict: "not-applicable" }); + }); + it("is not-applicable for a spec with no providerFilePattern configured, even if the issue mentions an entry-shaped path (guards the optional-chaining branch)", () => { const entryOnlySpec: RegistryLaneSpec = { entryFilePattern: SUBNET_ENTRY_PATTERN, collectionField: "surfaces" }; const issueText = "Add registry/subnets/foo.json.";