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
19 changes: 19 additions & 0 deletions src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,25 @@ export function buildConfigQuality(
action: "Verify those labels exist and are actually used by maintainers or trusted automation.",
});
}
// A label multiplier must be a positive, finite number — a penalty multiplier is below 1 but still
// positive, so it is valid; 0, negative, NaN, or Infinity are config errors that would silently
// misweight scoring. Distinct from notObservedConfiguredLabels (which checks whether a label is *used*,
// not whether its multiplier is *valid*).
// Surface each bad multiplier as `label=value` so a maintainer sees the offending value inline.
const invalidLabelMultipliers = Object.entries(repo?.registryConfig?.labelMultipliers ?? {})
.filter(([, multiplier]) => !(typeof multiplier === "number" && Number.isFinite(multiplier) && multiplier > 0))
.map(([label, multiplier]) => `${label}=${String(multiplier)}`)
.sort();
if (invalidLabelMultipliers.length > 0) {
score -= Math.min(30, invalidLabelMultipliers.length * 10);
findings.push({
code: "invalid_label_multipliers",
severity: "warning",
title: "Configured label multipliers are out of range",
detail: `Label multipliers must be positive, finite numbers; these are not: ${invalidLabelMultipliers.join(", ")}.`,
action: "Set each flagged label multiplier to a positive, finite number (a penalty multiplier below 1 is allowed) in the registry config.",
});
}

const finalScore = clamp(score, 0, 100);
return {
Expand Down
33 changes: 33 additions & 0 deletions test/unit/signals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,39 @@ describe("world-class backend signals", () => {
expect(noMultiplierQuality.findings.map((finding) => finding.code)).toContain("trusted_labels_without_multipliers");
});

it("flags non-positive, non-finite, or non-numeric label multipliers as a config error", () => {
const badRepo = {
...repo,
registryConfig: {
...repo.registryConfig!,
labelMultipliers: { bug: -1, stale: 0, broken: Number.NaN, weird: "x" as unknown as number, good: 1.2, penalty: 0.5 },
},
};
// Observe every configured label so the unrelated "not observed" penalty does not also fire.
const observed = [{ ...issues[0]!, labels: ["bug", "stale", "broken", "weird", "good", "penalty"] }];
const quality = buildConfigQuality(badRepo, observed, [], repo.fullName);
const invalid = quality.findings.find((finding) => finding.code === "invalid_label_multipliers");
expect(invalid).toBeDefined();
// Each bad multiplier is named with its value (sorted); valid ones are not listed.
expect(invalid?.detail).toMatch(/broken=NaN, bug=-1, stale=0, weird=x/);
expect(invalid?.detail).not.toMatch(/good|penalty/);
// Four invalid entries deduct the capped 30 points; valid ones do not contribute.
const baseline = buildConfigQuality({ ...badRepo, registryConfig: { ...badRepo.registryConfig!, labelMultipliers: { good: 1.2, penalty: 0.5 } } }, observed, [], repo.fullName).score;
expect(quality.score).toBe(baseline - 30);
});

it("does not flag valid positive label multipliers, including penalty (<1) multipliers", () => {
const okRepo = { ...repo, registryConfig: { ...repo.registryConfig!, labelMultipliers: { feature: 1.25, refactor: 0.5 } } };
const quality = buildConfigQuality(okRepo, [{ ...issues[0]!, labels: ["feature", "refactor"] }], [], repo.fullName);
expect(quality.findings.map((finding) => finding.code)).not.toContain("invalid_label_multipliers");
});

it("treats a repo with no labelMultipliers key as having no invalid multipliers", () => {
const noLabelsRepo = { ...repo, registryConfig: { ...repo.registryConfig!, labelMultipliers: undefined as unknown as Record<string, number> } };
const quality = buildConfigQuality(noLabelsRepo, [], [], repo.fullName);
expect(quality.findings.map((finding) => finding.code)).not.toContain("invalid_label_multipliers");
});

it("keeps contributor detection and comment modes conservative", () => {
const currentPr = pullRequests[0]!;
const settings: RepositorySettings = {
Expand Down
Loading