Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions packages/loopover-engine/src/reward-risk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// (#4256).
import type { ScorePreviewResult } from "./scoring/preview.js";
import { buildScorePreview } from "./scoring/preview.js";
import { isSuspiciousConfiguredLabel } from "./scoring/label-match.js";
import { isFailingCheckSummary } from "./signals/check-summary.js";
import { nowIso } from "./utils/json.js";
import type {
Expand Down Expand Up @@ -832,11 +833,11 @@ function analysisRank(analysis: RepoRewardRisk): number {
function bestFitLabels(repo: RepositoryRecord | null): string[] {
const multipliers = repo?.registryConfig?.labelMultipliers ?? {};
const labels = Object.entries(multipliers)
// Exclude meta labels only at a keyword boundary (a real separator or end-of-string after the keyword),
// not mid-word — mirroring the anchored `suspiciousConfiguredLabels` matcher in engine.ts. The old
// unanchored regex over-matched substrings (e.g. "opensource" via "source", "risky-refactor" via "risk"),
// wrongly dropping a legitimate high-multiplier label from the best-fit suggestion.
.filter(([label]) => !/^(status|source|contributor|verified|risk|codex)([:/-]|$)/i.test(label))
// Exclude meta labels using the SHARED canonical matcher (#7251) so this can't drift from engine.ts's
// `suspiciousConfiguredLabels` audit again -- the two had diverged (this copy was missing
// state/bot/loopover/reward/score/miner and wrongly excluded `contributor`, which the canonical audit does
// not treat as suspicious).
.filter(([label]) => !isSuspiciousConfiguredLabel(label))
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))
.map(([label]) => label);
return labels.slice(0, 1);
Expand Down
9 changes: 9 additions & 0 deletions packages/loopover-engine/src/scoring/label-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ export function labelMatchesPattern(label: string, pattern: string): boolean {
return labelPatternToRegExp(pattern.toLowerCase()).test(label.toLowerCase());
}

/** Canonical "meta / suspicious configured-label" keyword matcher (#7251). A configured label multiplier key
* whose keyword matches at a real boundary (`status:ready`, `reward/x`, or bare `bot`) — but NOT mid-word
* (`bottleneck`, `scoreboard`, `riskier`) — is treated as a meta label rather than a genuine work label. Single
* source of truth so engine.ts's `suspiciousConfiguredLabels` audit and reward-risk.ts's `bestFitLabels`
* exclusion can't drift apart the way they already had. */
export function isSuspiciousConfiguredLabel(label: string): boolean {
return /^(status|state|source|bot|codex|loopover|reward|score|miner|verified|risk)([:/-]|$)/i.test(label);
}

// Compiled fnmatch→RegExp matchers are memoized by pattern. The same small,
// config-derived set of label keys is matched on every scored PR/issue, so the
// per-call recompile inside the nested label loops in engine.ts is pure waste.
Expand Down
3 changes: 2 additions & 1 deletion packages/loopover-engine/src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { nowIso } from "../utils/json.js";
import { extractLinkedIssueNumbers } from "../../../../src/db/repositories";
import { sanitizePublicComment } from "../../../../src/queue-intelligence";
import { labelMatchesPattern, projectLinkedIssueMultiplierForPlannedSolve, type LinkedIssueMultiplierStatus } from "../scoring/preview.js";
import { isSuspiciousConfiguredLabel } from "../scoring/label-match.js";
import { hasLocalTestEvidence, hasValidationNote, isTestPath } from "./test-evidence.js";
import { isCodeFile, isTestFile } from "./path-matchers.js";
import { isFailingCheckSummary } from "./check-summary.js";
Expand Down Expand Up @@ -1179,7 +1180,7 @@ export function buildLabelAudit(repo: RepositoryRecord | null, repoLabels: RepoL
// Require a real separator (`:`/`/`/`-`) OR end-of-string after the keyword so this flags prefix-style labels
// (`status:ready`, `reward/x`) and bare keywords (`bot`) — but NOT mid-word matches like `bottleneck` (`bot`),
// `scoreboard` (`score`), or `riskier` (`risk`). The old optional+unanchored `[:/-]?` over-matched those.
const suspiciousConfiguredLabels = configuredLabels.filter((label) => /^(status|state|source|bot|codex|loopover|reward|score|miner|verified|risk)([:/-]|$)/i.test(label));
const suspiciousConfiguredLabels = configuredLabels.filter((label) => isSuspiciousConfiguredLabel(label));
const findings: SignalFinding[] = [];
if (repo?.registryConfig?.trustedLabelPipeline && missingConfiguredLabels.length > 0) {
findings.push({
Expand Down
10 changes: 10 additions & 0 deletions test/unit/reward-risk-freshness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,14 @@ describe("bestFitLabels keyword anchoring", () => {
expect(pick({})).toEqual([]);
expect(rewardRiskFreshnessInternals.bestFitLabels(null)).toEqual([]);
});

it("aligns its meta-label exclusion set to engine.ts's canonical suspicious-label matcher (#7251)", () => {
// Keywords the canonical audit excludes that the old divergent copy MISSED are now excluded here too.
for (const key of ["state", "bot", "loopover", "reward", "score", "miner"]) {
expect(pick({ [`${key}:x`]: 5, bug: 2 })).toEqual(["bug"]);
}
// `contributor` was wrongly excluded before -- the canonical audit does not treat it as suspicious, so a
// high-multiplier contributor:* label must now surface as the best fit instead of being silently dropped.
expect(pick({ "contributor:top-tier": 5, bug: 2 })).toEqual(["contributor:top-tier"]);
});
});