diff --git a/src/review/content-lane/registry-logic.ts b/src/review/content-lane/registry-logic.ts index 921b018b2e..1d8dc3e913 100644 --- a/src/review/content-lane/registry-logic.ts +++ b/src/review/content-lane/registry-logic.ts @@ -929,11 +929,14 @@ export function checkContentLaneDeliverable( changedFiles: readonly string[], issueTitle?: string, ): ContentLaneDeliverableCheck { - const matchesSpec = (candidate: string): boolean => spec.entryFilePattern.test(candidate) || (spec.providerFilePattern?.test(candidate) ?? false); + // Mirror classifyRegistryPrScope's matchesPattern: test on canonicalize(candidate) while keeping the original + // string for anything user-visible (mentionedPath in the public PR comment). + const matchesSpec = (candidate: string): boolean => + spec.entryFilePattern.test(canonicalize(candidate)) || (spec.providerFilePattern?.test(canonicalize(candidate)) ?? false); const mentionedPath = extractPathTokens(issueText).find(matchesSpec); const titleImplies = !mentionedPath && Boolean(issueTitle && spec.issueTitleImpliesEntryPattern?.test(issueTitle)); if (!mentionedPath && !titleImplies) return { verdict: "not-applicable" }; - const delivered = changedFiles.some((file) => matchesSpec(canonicalize(file))); + const delivered = changedFiles.some((file) => matchesSpec(file)); if (delivered) return { verdict: "delivered" }; return { verdict: "missing", mentionedPath: mentionedPath ?? `a registry entry file matching ${spec.entryFilePattern} (implied by this issue's title)` }; } diff --git a/test/unit/content-lane-registry-logic.test.ts b/test/unit/content-lane-registry-logic.test.ts index f017c0798b..d3aa96a353 100644 --- a/test/unit/content-lane-registry-logic.test.ts +++ b/test/unit/content-lane-registry-logic.test.ts @@ -669,6 +669,29 @@ describe("checkContentLaneDeliverable (generic, spec-driven — #content-lane-de expect(checkContentLaneDeliverable(spec, issueText, ["registry\\subnets\\foo.json"]).verdict).toBe("delivered"); }); + it("canonicalizes mixed-case issue-body path tokens while preserving the original mentionedPath (#9667)", () => { + const issueText = "Add the missing surfaces to Registry/Subnets/Foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "Registry/Subnets/Foo.json", + }); + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"])).toEqual({ verdict: "delivered" }); + }); + + it("keeps all-lowercase issue-body path behavior byte-identical (#9667)", () => { + const issueText = "Add the missing surfaces to registry/subnets/foo.json."; + expect(checkContentLaneDeliverable(spec, issueText, ["tests/foo-verify.test.mjs"])).toEqual({ + verdict: "missing", + mentionedPath: "registry/subnets/foo.json", + }); + expect(checkContentLaneDeliverable(spec, issueText, ["registry/subnets/foo.json"])).toEqual({ verdict: "delivered" }); + }); + + it("stays not-applicable when the issue body names a path that matches no spec pattern (#9667)", () => { + const issueText = "Update the docs at Docs/Guides/Setup.md."; + expect(checkContentLaneDeliverable(spec, issueText, ["tests/unrelated.test.mjs"])).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.";