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
33 changes: 33 additions & 0 deletions src/services/score-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,38 @@ function openIssueBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdo
};
}

// Sibling of openIssueBreakdown/openPrBreakdown for the merged-PR history floor (upstream MIN_VALID_MERGED_PRS):
// a contributor whose observed merged-PR count on this repo is below the floor has the entire preview zeroed.
// Explained here so a miner sees the same actionable breakdown the open-PR / open-issue gates already provide.
function mergedHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { mergedHistoryMultiplier } = preview.scoreEstimate;
const { mergedPrFloor, mergedPullRequests } = preview.gates;
// mergedPullRequests is optional: when unobserved the floor is not enforced (the multiplier stays 1).
if (mergedPullRequests === undefined) {
return {
component: "mergedHistoryMultiplier",
band: "neutral",
summary: `Merged-PR history floor is not enforced for this preview (no contributor history observed; upstream floor is ${mergedPrFloor}).`,
lever: "No action needed for this preview; the upstream merged-PR floor applies once contributor history is observed.",
leverageScore: 0,
};
}
const band = bandForMultiplier(mergedHistoryMultiplier);
return {
component: "mergedHistoryMultiplier",
band,
summary:
mergedPullRequests >= mergedPrFloor
? `Merged PR history (${mergedPullRequests}) meets the upstream floor (${mergedPrFloor}).`
: `Merged PR history (${mergedPullRequests}) is below the upstream floor (${mergedPrFloor}), so this preview is zeroed.`,
lever:
mergedPullRequests >= mergedPrFloor
? "Keep landing merged PRs in this repo to maintain contributor history."
: "Land more merged PRs in this repo to clear the contributor-history floor before relying on this preview.",
leverageScore: mergedPullRequests >= mergedPrFloor ? 5 : 100,
};
}

function credibilityBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { credibilityMultiplier } = preview.scoreEstimate;
const { credibilityObserved, credibilityFloor } = preview.gates;
Expand Down Expand Up @@ -239,6 +271,7 @@ export function explainScoreBreakdown(preview: ScorePreviewResult): ScoreBreakdo
reviewPenaltyBreakdown(preview),
openPrBreakdown(preview),
openIssueBreakdown(preview),
mergedHistoryBreakdown(preview),
].map((entry) => ({
...entry,
summary: sanitizePublicComment(entry.summary),
Expand Down
23 changes: 23 additions & 0 deletions test/unit/score-breakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe("explainScoreBreakdown", () => {
"reviewPenaltyMultiplier",
"openPrMultiplier",
"openIssueMultiplier",
"mergedHistoryMultiplier",
]),
);
for (const component of breakdown.components) {
Expand All @@ -97,6 +98,28 @@ describe("explainScoreBreakdown", () => {
expect(breakdown.components.find((entry) => entry.component === "openIssueMultiplier")).toMatchObject({ band: "full" });
});

it("explains the merged-PR history floor as neutral (unobserved), full (meets floor), and blocked (below floor)", () => {
// Unobserved history -> floor not enforced -> neutral.
const unobserved = explainScoreBreakdown(
buildScorePreview({ repo, snapshot, input: { repoFullName: repo.fullName, contributorLogin: "miner", sourceTokenScore: 40, totalTokenScore: 60, sourceLines: 80, openPrCount: 1, credibility: 0.9, linkedIssueMode: "none" } }),
);
expect(unobserved.components.find((entry) => entry.component === "mergedHistoryMultiplier")).toMatchObject({ band: "neutral" });

// Observed >= upstream floor (MIN_VALID_MERGED_PRS = 3) -> full.
const meets = explainScoreBreakdown(
buildScorePreview({ repo, snapshot, input: { repoFullName: repo.fullName, contributorLogin: "miner", sourceTokenScore: 40, totalTokenScore: 60, sourceLines: 80, openPrCount: 1, credibility: 0.9, linkedIssueMode: "none", mergedPullRequests: 5 } }),
);
expect(meets.components.find((entry) => entry.component === "mergedHistoryMultiplier")).toMatchObject({ band: "full" });

// Observed < floor -> blocked, and the merged-PR lever is the top-leverage one.
const blocked = explainScoreBreakdown(
buildScorePreview({ repo, snapshot, input: { repoFullName: repo.fullName, contributorLogin: "miner", sourceTokenScore: 40, totalTokenScore: 60, sourceLines: 80, openPrCount: 1, credibility: 0.9, linkedIssueMode: "none", mergedPullRequests: 1 } }),
);
expect(blocked.components.find((entry) => entry.component === "mergedHistoryMultiplier")).toMatchObject({ band: "blocked", leverageScore: 100 });
expect(blocked.highestLeverageLever.lever).toMatch(/merge/i);
expect(JSON.stringify(blocked)).not.toMatch(FORBIDDEN);
});

it("explains an over-threshold open-issue count as a blocked open-issue spam gate", () => {
const preview = buildScorePreview({
repo,
Expand Down
Loading