Skip to content
Closed
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
58 changes: 58 additions & 0 deletions src/services/score-breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,63 @@ function mergedHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBre
};
}

// Sibling of mergedHistoryBreakdown for the issue-discovery validity floor (upstream
// MIN_VALID_SOLVED_ISSUES and MIN_ISSUE_CREDIBILITY). Mirrors preview.ts: the multiplier
// stays 1 unless linked-issue mode is active AND both history fields are observed.
function issueDiscoveryHistoryBreakdown(preview: ScorePreviewResult): ScoreMultiplierBreakdown {
const { issueDiscoveryHistoryMultiplier } = preview.scoreEstimate;
const { validSolvedIssues, validSolvedIssuesFloor, issueCredibility, issueCredibilityFloor } = preview.gates;
const linkedIssueMode = preview.linkedIssueMultiplier.mode;

if (linkedIssueMode === "none") {
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary:
"Issue-discovery validity floor is not enforced for this preview (linked-issue mode is inactive; upstream only gates previews that claim linked-issue scoring).",
lever: "Use a linked-issue preview when planning issue-discovery work so validity floors can be evaluated.",
leverageScore: 0,
};
}

const hasValidSolved = validSolvedIssues !== undefined;
const hasIssueCredibility = issueCredibility !== undefined;
if (!hasValidSolved && !hasIssueCredibility) {
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary: `Issue-discovery validity floor is not enforced for this preview (contributor issue-history is unobserved; upstream floors are ${validSolvedIssuesFloor} valid solved and ${issueCredibilityFloor} credibility).`,
lever: "No action needed for this preview; the validity floor applies once both valid solved count and issue credibility are observed.",
leverageScore: 0,
};
}
if (!hasValidSolved || !hasIssueCredibility) {
return {
component: "issueDiscoveryHistoryMultiplier",
band: "neutral",
summary: hasValidSolved
? "Issue-discovery validity floor is not enforced for this preview (valid solved count is observed but issue credibility is not; upstream requires both before gating)."
: "Issue-discovery validity floor is not enforced for this preview (issue credibility is observed but valid solved count is not; upstream requires both before gating).",
lever: "Supply both valid solved-issue count and issue credibility before relying on issue-discovery validity floors.",
leverageScore: 0,
};
}

const band = bandForMultiplier(issueDiscoveryHistoryMultiplier);
const meetsFloor = validSolvedIssues >= validSolvedIssuesFloor && issueCredibility >= issueCredibilityFloor;
return {
component: "issueDiscoveryHistoryMultiplier",
band,
summary: meetsFloor
? `Issue-discovery history (${validSolvedIssues} valid solved, credibility ${roundBand(issueCredibility)}) meets upstream floors (${validSolvedIssuesFloor} valid solved, ${issueCredibilityFloor} credibility).`
: `Issue-discovery history (${validSolvedIssues} valid solved, credibility ${roundBand(issueCredibility)}) is below upstream floors (${validSolvedIssuesFloor} valid solved, ${issueCredibilityFloor} credibility), so this preview is zeroed.`,
lever: meetsFloor
? "Keep building valid solved-issue history with strong issue credibility."
: "Close more valid solved issues and improve issue credibility before relying on issue-discovery scoring.",
leverageScore: meetsFloor ? 8 : 100,
};
}

// Upstream time-decay (#703), env-gated by SCORING_TIME_DECAY_ENABLED (default OFF) and opted into per-preview
// via input.applyTimeDecay. When the flag is off (the common case) or the PR is fresh, the multiplier is 1 and
// the breakdown reads as "not enabled" / "fresh" — surfacing the value is a no-op for those previews but
Expand Down Expand Up @@ -318,6 +375,7 @@ export function explainScoreBreakdown(preview: ScorePreviewResult): ScoreBreakdo
openPrBreakdown(preview),
openIssueBreakdown(preview),
mergedHistoryBreakdown(preview),
issueDiscoveryHistoryBreakdown(preview),
timeDecayBreakdown(preview),
].map((entry) => ({
...entry,
Expand Down
114 changes: 114 additions & 0 deletions test/unit/score-breakdown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe("explainScoreBreakdown", () => {
"openPrMultiplier",
"openIssueMultiplier",
"mergedHistoryMultiplier",
"issueDiscoveryHistoryMultiplier",
"timeDecayMultiplier",
]),
);
Expand Down Expand Up @@ -122,6 +123,119 @@ describe("explainScoreBreakdown", () => {
expect(JSON.stringify(blocked)).not.toMatch(FORBIDDEN);
});

it("explains the issue-discovery validity floor as neutral (unobserved), full (meets floor), and blocked (below floor)", () => {
const issueDiscoveryRepo: RepositoryRecord = {
...repo,
registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 0.25 },
};
const baseInput = {
repoFullName: issueDiscoveryRepo.fullName,
contributorLogin: "miner",
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 0,
credibility: 1,
mergedPullRequests: 5,
linkedIssueMode: "standard" as const,
};

const unobserved = explainScoreBreakdown(buildScorePreview({ repo: issueDiscoveryRepo, snapshot, input: baseInput }));
expect(unobserved.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "neutral",
leverageScore: 0,
});

const meets = explainScoreBreakdown(
buildScorePreview({
repo: issueDiscoveryRepo,
snapshot,
input: { ...baseInput, validSolvedIssues: 4, issueCredibility: 0.9 },
}),
);
expect(meets.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "full",
summary: expect.stringMatching(/4 valid solved, private context 0.9/i),
});

const blocked = explainScoreBreakdown(
buildScorePreview({
repo: issueDiscoveryRepo,
snapshot,
input: { ...baseInput, validSolvedIssues: 1, issueCredibility: 0.5 },
}),
);
expect(blocked.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "blocked",
summary: expect.stringMatching(/private context.*zeroed/i),
leverageScore: 100,
});
expect(blocked.highestLeverageLever.lever).toMatch(/valid solved issues|issue credibility/i);
expect(JSON.stringify(blocked)).not.toMatch(FORBIDDEN);
});

it("keeps issue-discovery validity neutral when linked-issue mode is inactive even if history is supplied", () => {
const issueDiscoveryRepo: RepositoryRecord = {
...repo,
registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 0.25 },
};
const preview = buildScorePreview({
repo: issueDiscoveryRepo,
snapshot,
input: {
repoFullName: issueDiscoveryRepo.fullName,
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 0,
credibility: 1,
linkedIssueMode: "none",
validSolvedIssues: 4,
issueCredibility: 0.95,
},
});
expect(preview.scoreEstimate.issueDiscoveryHistoryMultiplier).toBe(1);
const breakdown = explainScoreBreakdown(preview);
expect(breakdown.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "neutral",
summary: expect.stringMatching(/linked-issue mode is inactive/i),
leverageScore: 0,
});
});

it("keeps issue-discovery validity neutral when only one history field is observed", () => {
const issueDiscoveryRepo: RepositoryRecord = {
...repo,
registryConfig: { ...repo.registryConfig!, issueDiscoveryShare: 0.25 },
};
const baseInput = {
repoFullName: issueDiscoveryRepo.fullName,
sourceTokenScore: 40,
totalTokenScore: 60,
sourceLines: 80,
openPrCount: 0,
credibility: 1,
mergedPullRequests: 5,
linkedIssueMode: "standard" as const,
};

const validSolvedOnly = explainScoreBreakdown(
buildScorePreview({ repo: issueDiscoveryRepo, snapshot, input: { ...baseInput, validSolvedIssues: 4 } }),
);
expect(validSolvedOnly.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "neutral",
summary: expect.stringMatching(/valid solved count is observed but issue private context is not/i),
});

const credibilityOnly = explainScoreBreakdown(
buildScorePreview({ repo: issueDiscoveryRepo, snapshot, input: { ...baseInput, issueCredibility: 0.9 } }),
);
expect(credibilityOnly.components.find((entry) => entry.component === "issueDiscoveryHistoryMultiplier")).toMatchObject({
band: "neutral",
summary: expect.stringMatching(/issue private context is observed but valid solved count is not/i),
});
});

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