@@ -4,11 +4,13 @@ import {
44 type FreshnessIssue ,
55} from "../../packages/loopover-engine/src/opportunity-freshness" ;
66import {
7+ buildContributorFit ,
78 buildContributorOutcomeHistory ,
89 buildContributorProfile ,
10+ buildContributorScoringProfile ,
911} from "../../src/signals/engine" ;
1012import { buildRepoRewardRisk , rewardRiskFreshnessInternals } from "../../src/signals/reward-risk" ;
11- import type { IssueRecord , RepositoryRecord , ScoringModelSnapshotRecord } from "../../src/types" ;
13+ import type { IssueRecord , PullRequestRecord , RepositoryRecord , ScoringModelSnapshotRecord } from "../../src/types" ;
1214
1315function scoringSnapshot ( ) : ScoringModelSnapshotRecord {
1416 return {
@@ -71,6 +73,22 @@ function toFreshnessIssues(issues: IssueRecord[]): FreshnessIssue[] {
7173 } ) ) ;
7274}
7375
76+ function pr ( repoFullName : string , number : number , title : string , overrides : Partial < PullRequestRecord > = { } ) : PullRequestRecord {
77+ return {
78+ repoFullName,
79+ number,
80+ title,
81+ state : "open" ,
82+ authorLogin : "dev" ,
83+ authorAssociation : "NONE" ,
84+ labels : [ ] ,
85+ linkedIssues : [ ] ,
86+ body : "" ,
87+ updatedAt : new Date ( ) . toISOString ( ) ,
88+ ...overrides ,
89+ } ;
90+ }
91+
7492describe ( "reward-risk freshness parity with loopover-engine" , ( ) => {
7593 const collab = repo ( "owner/collab-repo" ) ;
7694 const profile = buildContributorProfile ( "dev" , { login : "dev" , topLanguages : [ ] , source : "github" } , [ ] , [ ] ) ;
@@ -174,6 +192,11 @@ describe("bestFitLabels keyword anchoring", () => {
174192 expect ( rewardRiskFreshnessInternals . bestFitLabels ( null ) ) . toEqual ( [ ] ) ;
175193 } ) ;
176194
195+ it ( "breaks an equal-multiplier tie by label name" , ( ) => {
196+ // Two labels with the SAME multiplier force the sort comparator's `|| localeCompare` fallback.
197+ expect ( pick ( { zebra : 1.5 , alpha : 1.5 } ) ) . toEqual ( [ "alpha" ] ) ;
198+ } ) ;
199+
177200 it ( "aligns its meta-label exclusion set to engine.ts's canonical suspicious-label matcher (#7251)" , ( ) => {
178201 // Keywords the canonical audit excludes that the old divergent copy MISSED are now excluded here too.
179202 for ( const key of [ "state" , "bot" , "loopover" , "reward" , "score" , "miner" ] ) {
@@ -184,3 +207,92 @@ describe("bestFitLabels keyword anchoring", () => {
184207 expect ( pick ( { "contributor:top-tier" : 5 , bug : 2 } ) ) . toEqual ( [ "contributor:top-tier" ] ) ;
185208 } ) ;
186209} ) ;
210+
211+ describe ( "buildRepoRewardRisk — reviewChurnRisk and maintainer-cut readiness branch coverage (#2281)" , ( ) => {
212+ it ( "reviewChurnRisk reports high risk when the repo-specific closed-PR rate is high" , ( ) => {
213+ const profile = buildContributorProfile ( "dev" , { login : "dev" , topLanguages : [ "TypeScript" ] , source : "github" } , [ ] , [ ] ) ;
214+ const churnRepo = repo ( "owner/churn" ) ;
215+ // Two closed + one merged PR => closedPullRequestRate ~0.67 => reviewChurnRisk risk >= 45 => "high".
216+ const outcomeHistory = buildContributorOutcomeHistory ( {
217+ login : "dev" ,
218+ profile,
219+ repositories : [ churnRepo ] ,
220+ pullRequests : [
221+ pr ( churnRepo . fullName , 30 , "Closed one" , { state : "closed" } ) ,
222+ pr ( churnRepo . fullName , 31 , "Closed two" , { state : "closed" } ) ,
223+ pr ( churnRepo . fullName , 32 , "Merged" , { state : "merged" , mergedAt : "2026-05-20T00:00:00.000Z" } ) ,
224+ ] ,
225+ issues : [ ] ,
226+ repoStats : [ ] ,
227+ } ) ;
228+ const fit = buildContributorFit ( profile , [ churnRepo ] , [ ] , [ ] , [ ] , [ ] ) ;
229+ const scoringProfile = buildContributorScoringProfile ( { login : "dev" , fit, scoringSnapshot : scoringSnapshot ( ) } ) ;
230+ const analysis = buildRepoRewardRisk ( {
231+ login : "dev" ,
232+ repo : churnRepo ,
233+ repoFullName : churnRepo . fullName ,
234+ profile,
235+ outcomeHistory,
236+ scoringSnapshot : scoringSnapshot ( ) ,
237+ scoringProfile,
238+ issues : [ ] ,
239+ pullRequests : [ ] ,
240+ } ) ;
241+ expect ( analysis . riskBreakdown . reviewChurnRisk ) . toBe ( "high" ) ;
242+ } ) ;
243+
244+ it ( "reviewChurnRisk reports medium risk for a moderate closed-PR rate" , ( ) => {
245+ const profile = buildContributorProfile ( "dev" , { login : "dev" , topLanguages : [ "TypeScript" ] , source : "github" } , [ ] , [ ] ) ;
246+ const churnRepo = repo ( "owner/churn-mid" ) ;
247+ // One closed + two merged => closedPullRequestRate ~0.33 => risk in [20, 45) => "medium".
248+ const outcomeHistory = buildContributorOutcomeHistory ( {
249+ login : "dev" ,
250+ profile,
251+ repositories : [ churnRepo ] ,
252+ pullRequests : [
253+ pr ( churnRepo . fullName , 40 , "Closed one" , { state : "closed" } ) ,
254+ pr ( churnRepo . fullName , 41 , "Merged one" , { state : "merged" , mergedAt : "2026-05-20T00:00:00.000Z" } ) ,
255+ pr ( churnRepo . fullName , 42 , "Merged two" , { state : "merged" , mergedAt : "2026-05-21T00:00:00.000Z" } ) ,
256+ ] ,
257+ issues : [ ] ,
258+ repoStats : [ ] ,
259+ } ) ;
260+ const fit = buildContributorFit ( profile , [ churnRepo ] , [ ] , [ ] , [ ] , [ ] ) ;
261+ const scoringProfile = buildContributorScoringProfile ( { login : "dev" , fit, scoringSnapshot : scoringSnapshot ( ) } ) ;
262+ const analysis = buildRepoRewardRisk ( {
263+ login : "dev" ,
264+ repo : churnRepo ,
265+ repoFullName : churnRepo . fullName ,
266+ profile,
267+ outcomeHistory,
268+ scoringSnapshot : scoringSnapshot ( ) ,
269+ scoringProfile,
270+ issues : [ ] ,
271+ pullRequests : [ ] ,
272+ } ) ;
273+ expect ( analysis . riskBreakdown . reviewChurnRisk ) . toBe ( "medium" ) ;
274+ } ) ;
275+
276+ it ( "maintainer-cut readiness scores without the low-queue bonus when the owned repo's queue is not low" , ( ) => {
277+ // Owner === login => maintainer lane; a heavily loaded queue keeps queueHealth.level above "low",
278+ // exercising the `level === "low" ? 20 : 0` false branch.
279+ const ownedRepo = repo ( "dev/owned" ) ;
280+ const busyPrs = Array . from ( { length : 14 } , ( _ , i ) => pr ( ownedRepo . fullName , i + 1 , `Open work ${ i } ` , { authorLogin : `other${ i } ` } ) ) ;
281+ const profile = buildContributorProfile ( "dev" , { login : "dev" , topLanguages : [ "TypeScript" ] , source : "github" } , [ ] , [ ] ) ;
282+ const fit = buildContributorFit ( profile , [ ownedRepo ] , [ ] , [ ] , [ ] , [ ] ) ;
283+ const scoringProfile = buildContributorScoringProfile ( { login : "dev" , fit, scoringSnapshot : scoringSnapshot ( ) } ) ;
284+ const analysis = buildRepoRewardRisk ( {
285+ login : "dev" ,
286+ repo : ownedRepo ,
287+ repoFullName : ownedRepo . fullName ,
288+ profile,
289+ outcomeHistory : buildContributorOutcomeHistory ( { login : "dev" , profile, repositories : [ ownedRepo ] , pullRequests : busyPrs , issues : [ ] , repoStats : [ ] } ) ,
290+ scoringSnapshot : scoringSnapshot ( ) ,
291+ scoringProfile,
292+ issues : [ ] ,
293+ pullRequests : busyPrs ,
294+ } ) ;
295+ expect ( analysis . roleContext . maintainerLane ) . toBe ( true ) ;
296+ expect ( analysis . actions . some ( ( a ) => a . actionKind === "maintainer_cut_readiness" ) ) . toBe ( true ) ;
297+ } ) ;
298+ } ) ;
0 commit comments