@@ -15,6 +15,15 @@ export interface CheckStatus {
1515 detailsUrl ?: string
1616}
1717
18+ export interface PRReview {
19+ user : string
20+ avatarUrl : string
21+ state : 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'DISMISSED' | 'PENDING'
22+ body : string
23+ submittedAt : string
24+ htmlUrl : string
25+ }
26+
1827export interface PRStatus {
1928 number : number
2029 title : string
@@ -25,6 +34,9 @@ export interface PRStatus {
2534 checksOverall : 'success' | 'failure' | 'pending' | 'none'
2635 /** true = has conflicts with base, false = mergeable, null = still computing */
2736 hasConflict : boolean | null
37+ reviews : PRReview [ ]
38+ /** Overall review decision: approved, changes requested, or pending */
39+ reviewDecision : 'approved' | 'changes_requested' | 'review_required' | 'none'
2840}
2941
3042function getToken ( ) : string | null {
@@ -129,6 +141,14 @@ interface ApiCombinedStatus {
129141 statuses : ApiStatus [ ]
130142}
131143
144+ interface ApiReview {
145+ user : { login : string ; avatar_url : string }
146+ state : 'APPROVED' | 'CHANGES_REQUESTED' | 'COMMENTED' | 'DISMISSED' | 'PENDING'
147+ body : string
148+ submitted_at : string
149+ html_url : string
150+ }
151+
132152function normalizeCheckState (
133153 status : ApiCheckRun [ 'status' ] ,
134154 conclusion : ApiCheckRun [ 'conclusion' ]
@@ -202,10 +222,11 @@ export async function getPRStatus(worktreePath: string): Promise<PRStatus | null
202222 // Fetch check runs, status contexts, and PR detail (for mergeable) in parallel.
203223 // The /pulls/{n} endpoint triggers GitHub's background mergeability computation
204224 // and returns the result if it's ready — otherwise mergeable is null.
205- const [ checkRunsRes , combinedRes , prDetail ] = await Promise . all ( [
225+ const [ checkRunsRes , combinedRes , prDetail , reviewsRes ] = await Promise . all ( [
206226 githubFetch ( `https://api.github.com/repos/${ owner } /${ repo } /commits/${ sha } /check-runs?per_page=100` ) as Promise < ApiCheckRunsResponse > ,
207227 githubFetch ( `https://api.github.com/repos/${ owner } /${ repo } /commits/${ sha } /status` ) as Promise < ApiCombinedStatus > ,
208- githubFetch ( `https://api.github.com/repos/${ owner } /${ repo } /pulls/${ pr . number } ` ) as Promise < ApiPRDetail >
228+ githubFetch ( `https://api.github.com/repos/${ owner } /${ repo } /pulls/${ pr . number } ` ) as Promise < ApiPRDetail > ,
229+ githubFetch ( `https://api.github.com/repos/${ owner } /${ repo } /pulls/${ pr . number } /reviews?per_page=100` ) as Promise < ApiReview [ ] >
209230 ] )
210231
211232 // mergeable_state 'dirty' is the definitive conflict signal. mergeable===false
@@ -236,6 +257,29 @@ export async function getPRStatus(worktreePath: string): Promise<PRStatus | null
236257 } )
237258 }
238259
260+ // Process reviews — keep all reviews, dedupe to latest per user for decision
261+ const reviews : PRReview [ ] = ( Array . isArray ( reviewsRes ) ? reviewsRes : [ ] )
262+ . filter ( ( r ) => r . user && r . state !== 'PENDING' )
263+ . map ( ( r ) => ( {
264+ user : r . user . login ,
265+ avatarUrl : r . user . avatar_url ,
266+ state : r . state ,
267+ body : r . body || '' ,
268+ submittedAt : r . submitted_at ,
269+ htmlUrl : r . html_url
270+ } ) )
271+
272+ // Compute overall review decision from the latest review per user
273+ const latestByUser = new Map < string , PRReview [ 'state' ] > ( )
274+ for ( const r of reviews ) {
275+ latestByUser . set ( r . user , r . state )
276+ }
277+ const latestStates = [ ...latestByUser . values ( ) ]
278+ let reviewDecision : PRStatus [ 'reviewDecision' ] = 'none'
279+ if ( latestStates . some ( ( s ) => s === 'CHANGES_REQUESTED' ) ) reviewDecision = 'changes_requested'
280+ else if ( latestStates . some ( ( s ) => s === 'APPROVED' ) ) reviewDecision = 'approved'
281+ else if ( latestStates . length > 0 ) reviewDecision = 'review_required'
282+
239283 // Determine PR state
240284 let state : PRStatus [ 'state' ]
241285 if ( pr . merged_at ) state = 'merged'
@@ -251,7 +295,9 @@ export async function getPRStatus(worktreePath: string): Promise<PRStatus | null
251295 branch : branchName ,
252296 checks,
253297 checksOverall : computeOverall ( checks ) ,
254- hasConflict
298+ hasConflict,
299+ reviews,
300+ reviewDecision
255301 }
256302 } catch ( err ) {
257303 log ( 'github' , `getPRStatus failed for ${ branchName } ` , err instanceof Error ? err . message : err )
0 commit comments