diff --git a/src/rules/advisory.ts b/src/rules/advisory.ts index 4eb21b139..09db8a585 100644 --- a/src/rules/advisory.ts +++ b/src/rules/advisory.ts @@ -15,6 +15,7 @@ import { isTestPath } from "../signals/test-evidence"; import { nowIso } from "../utils/json"; import { GITTENSORY_GATE_CHECK_NAME } from "../review/check-names"; import { REVIEW_THREAD_BLOCKER_CODE } from "../review/review-thread-findings"; +import { labelMatchesPattern } from "../scoring/preview"; export type GateCheckConclusion = "success" | "failure" | "action_required" | "neutral" | "skipped"; @@ -710,8 +711,8 @@ function addPullRequestFindings( publicText: "This repo has a busy review queue in the local Gittensory cache.", }); } - const repoMultipliers = repo?.registryConfig?.labelMultipliers ?? {}; - const matchedLabels = pr.labels.filter((label) => repoMultipliers[label] !== undefined); + const multiplierPatterns = Object.keys(repo?.registryConfig?.labelMultipliers ?? {}); + const matchedLabels = pr.labels.filter((label) => multiplierPatterns.some((pattern) => labelMatchesPattern(label, pattern))); if (matchedLabels.length > 0) { findings.push({ code: "label_context_found", diff --git a/test/unit/rules.test.ts b/test/unit/rules.test.ts index abe320371..9941d47e9 100644 --- a/test/unit/rules.test.ts +++ b/test/unit/rules.test.ts @@ -489,6 +489,51 @@ describe("advisory rules", () => { expect(codes).toEqual(expect.arrayContaining(["pr_not_open", "busy_pr_queue", "label_context_found", "maintainer_authored_pr"])); }); + it("matches configured label multipliers case-insensitively and with glob patterns", () => { + const repoWithPatterns: RepositoryRecord = { + ...repo, + registryConfig: { + ...repo.registryConfig!, + labelMultipliers: { feature: 1.5, "type:*": 1.2 }, + }, + }; + const caseMismatch = buildPullRequestAdvisory(repoWithPatterns, { + repoFullName: repo.fullName, + number: 16, + title: "Feature work", + state: "open", + authorLogin: "miner1", + authorAssociation: "NONE", + labels: ["Feature"], + linkedIssues: [7], + }); + expect(caseMismatch.findings.some((finding) => finding.code === "label_context_found")).toBe(true); + + const globMatch = buildPullRequestAdvisory(repoWithPatterns, { + repoFullName: repo.fullName, + number: 17, + title: "Bug fix", + state: "open", + authorLogin: "miner1", + authorAssociation: "NONE", + labels: ["type:bug-fix"], + linkedIssues: [7], + }); + expect(globMatch.findings.some((finding) => finding.code === "label_context_found")).toBe(true); + + const noMatch = buildPullRequestAdvisory(repoWithPatterns, { + repoFullName: repo.fullName, + number: 18, + title: "Docs only", + state: "open", + authorLogin: "miner1", + authorAssociation: "NONE", + labels: ["docs"], + linkedIssues: [7], + }); + expect(noMatch.findings.some((finding) => finding.code === "label_context_found")).toBe(false); + }); + it("handles uncached PRs and closed issues", () => { const closedIssue: IssueRecord = { repoFullName: repo.fullName,