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
10 changes: 10 additions & 0 deletions src/scoring/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,16 @@ function selectLabelMultiplier(labels: string[], multipliers: Record<string, num
return matched.length > 0 ? Math.max(...matched) : fallback || 1;
}

/** True when `label` matches the configured multiplier `pattern` under the SAME case-insensitive fnmatch glob
* semantics scoring uses to resolve label multipliers (see {@link labelPatternToRegExp}). Exported so the
* signals surfaces that audit configured label keys (config-quality, label-audit) match them as the GLOBS they
* are: a `type:*` key must count `type:bug-fix` as observed/configured, not silently report it missing because
* the literal pattern never appears verbatim on a real issue/PR. A literal key (no glob metacharacter) still
* matches only its exact label, so existing configs behave identically. */
export function labelMatchesPattern(label: string, pattern: string): boolean {
return labelPatternToRegExp(pattern.toLowerCase()).test(label.toLowerCase());
}

// Upstream resolves label multipliers by matching each configured key as a Python `fnmatch` GLOB, not a
// literal string: `fnmatch(label.lower(), pattern.lower())` in
// gittensor/validator/oss_contributions/label_resolution.py, so a repo can configure `type:*`, `kind/*`, or
Expand Down
16 changes: 12 additions & 4 deletions src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type { FocusManifestReviewConfig, ReviewFieldKey } from "./focus-manifest
import type { GittensorContributorSnapshot } from "../gittensor/api";
import { nowIso } from "../utils/json";
import { sanitizePublicComment } from "../queue-intelligence";
import { projectLinkedIssueMultiplierForPlannedSolve, type LinkedIssueMultiplierStatus } from "../scoring/preview";
import { labelMatchesPattern, projectLinkedIssueMultiplierForPlannedSolve, type LinkedIssueMultiplierStatus } from "../scoring/preview";
import { hasLocalTestEvidence } from "./test-evidence";
import { isDuplicateClusterWinner } from "./duplicate-winner";
import { PREFLIGHT_LIMITS } from "./preflight-limits";
Expand Down Expand Up @@ -1009,7 +1009,11 @@ export function buildConfigQuality(
const lane = buildLaneAdvice(repo, fullName);
const configuredLabels = Object.keys(repo?.registryConfig?.labelMultipliers ?? {}).sort();
const observedLabels = [...new Set([...issues, ...pullRequests].flatMap((record) => record.labels))].sort();
const notObservedConfiguredLabels = configuredLabels.filter((label) => !observedLabels.includes(label));
// Configured keys are fnmatch GLOBS (scoring resolves them via labelMatchesPattern), so a key is "observed"
// when it matches any cached label — not only when the literal pattern string appears verbatim. The old
// exact `.includes` reported every wildcard key (e.g. `type:*`) as not-observed even when `type:bug-fix` is in
// active use, spuriously docking the config-quality score for glob-configured repos. (#1769)
const notObservedConfiguredLabels = configuredLabels.filter((pattern) => !observedLabels.some((label) => labelMatchesPattern(label, pattern)));
const findings: SignalFinding[] = [];
let score = 100;

Expand Down Expand Up @@ -1097,10 +1101,14 @@ export function buildLabelAudit(repo: RepositoryRecord | null, repoLabels: RepoL
.map(([name, count]) => ({
name,
count,
configured: configuredLabels.includes(name),
// Match each observed label against the configured GLOB keys (fnmatch), the same way scoring resolves a
// label's multiplier — so a label covered by a `type:*` key is reported as configured, not unconfigured. (#1769)
configured: configuredLabels.some((pattern) => labelMatchesPattern(name, pattern)),
existsOnGitHub: liveLabels.includes(name),
}));
const missingConfiguredLabels = configuredLabels.filter((label) => !liveLabels.includes(label));
// A configured key is "missing" only when NO live GitHub label matches it as a glob; a `type:*` key backed by a
// real `type:bug` label is present, not missing (the old exact `.includes` flagged every wildcard key missing). (#1769)
const missingConfiguredLabels = configuredLabels.filter((pattern) => !liveLabels.some((live) => labelMatchesPattern(live, pattern)));
// 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.
Expand Down
30 changes: 30 additions & 0 deletions test/unit/signals-coverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,36 @@ describe("signal coverage edge cases", () => {
expect(audit.findings.map((finding) => finding.code)).toContain("suspicious_configured_labels");
});

it("matches configured glob label keys against observed and live labels, not literal strings (#1769)", () => {
// `type:*` is a wildcard multiplier key; `area:*` matches nothing in use; `bug` is a plain literal key.
const globRepo = repo("owner/glob-labels", { labelMultipliers: { "type:*": 1.3, "area:*": 1.2, bug: 1.1 }, trustedLabelPipeline: true });

// buildConfigQuality: a glob key with a matching cached label is "observed" (not flagged); one with no match is.
const quality = buildConfigQuality(
globRepo,
[issue(globRepo.fullName, 1, "Crash", { labels: ["type:bug-fix"] })],
[pr(globRepo.fullName, 2, "Fix", { labels: ["bug"] })],
globRepo.fullName,
);
expect(quality.notObservedConfiguredLabels).toEqual(["area:*"]);
expect(quality.notObservedConfiguredLabels).not.toContain("type:*");
expect(quality.findings.map((finding) => finding.code)).toContain("configured_labels_not_observed");

// buildLabelAudit: live `type:bug` satisfies the `type:*` glob; `area:*` has no live label → it alone is missing.
const liveLabels: RepoLabelRecord[] = [
{ repoFullName: globRepo.fullName, name: "type:bug", isConfigured: true, observedCount: 2, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" },
{ repoFullName: globRepo.fullName, name: "bug", isConfigured: true, observedCount: 1, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" },
{ repoFullName: globRepo.fullName, name: "wontfix", isConfigured: false, observedCount: 1, payload: {}, lastSeenAt: "2026-05-25T00:00:00.000Z" },
];
const audit = buildLabelAudit(globRepo, liveLabels, [], [], globRepo.fullName);
expect(audit.missingConfiguredLabels).toEqual(["area:*"]);
expect(audit.missingConfiguredLabels).not.toContain("type:*");
// `type:bug` is covered by the `type:*` glob (configured: true); the unrelated `wontfix` label is not (false).
const byName = new Map(audit.observedLabels.map((label) => [label.name, label.configured]));
expect(byName.get("type:bug")).toBe(true);
expect(byName.get("wontfix")).toBe(false);
});

it("awards the personalFit language bonus only on a real repo-language match", () => {
const targetRepo = repo("owner/lang-fit");
const profile = buildContributorProfile("dev", { login: "dev", topLanguages: ["TypeScript"], source: "github" }, [], []);
Expand Down
Loading