@@ -21,6 +21,20 @@ interface ReviewCritiqueProjection {
2121 overall_decision ?: string ;
2222}
2323
24+ interface ReviewPacketProjection {
25+ readiness ?: {
26+ status ?: "ready" | "warning" | "blocking" ;
27+ } ;
28+ decision ?: {
29+ outcome ?: string ;
30+ recommended_transition ?: string ;
31+ } ;
32+ }
33+
34+ interface ReviewScorecardProjection {
35+ overall_score_1_to_5 ?: number ;
36+ }
37+
2438interface PaperReadinessProjection {
2539 paper_ready ?: boolean ;
2640 readiness_state ?: string ;
@@ -77,7 +91,9 @@ export async function buildRunJobsSnapshot(input: {
7791
7892 return {
7993 generated_at : new Date ( ) . toISOString ( ) ,
80- runs : projected . map ( stripInternalProjection ) ,
94+ runs : projected
95+ . sort ( ( left , right ) => Date . parse ( right . last_event_at ) - Date . parse ( left . last_event_at ) )
96+ . map ( stripInternalProjection ) ,
8197 top_failures : summarizeFailures ( projected )
8298 } ;
8399}
@@ -93,13 +109,21 @@ export async function buildAnalyzeResultsOperatorSummary(input: {
93109 const transitionRecommendation = await readJsonArtifact < Record < string , unknown > > (
94110 path . join ( runDir , "transition_recommendation.json" )
95111 ) ;
112+ const reviewPacket = await readJsonArtifact < ReviewPacketProjection > (
113+ path . join ( runDir , "review" , "review_packet.json" )
114+ ) ;
115+ const reviewScorecard = await readJsonArtifact < ReviewScorecardProjection > (
116+ path . join ( runDir , "review" , "scorecard.json" )
117+ ) ;
96118
97119 const artifactRefs = [
98120 maybeArtifactRef ( projected . analysis_ready , "Analysis report" , "result_analysis.json" ) ,
99121 maybeArtifactRef ( projected . analysis_ready , "Transition recommendation" , "transition_recommendation.json" ) ,
100- maybeArtifactRef ( projected . review_ready , "Review packet" , "review/review_packet.json" ) ,
122+ maybeArtifactRef ( Boolean ( reviewPacket ) , "Review packet" , "review/review_packet.json" ) ,
123+ maybeArtifactRef ( Boolean ( reviewScorecard ) , "Review scorecard" , "review/scorecard.json" ) ,
101124 maybeArtifactRef ( projected . review_ready , "Paper critique" , "review/paper_critique.json" ) ,
102125 maybeArtifactRef ( projected . review_ready , "Review minimum gate" , "review/minimum_gate.json" ) ,
126+ maybeArtifactRef ( projected . review_ready , "Review readiness risks" , "review/readiness_risks.json" ) ,
103127 maybeArtifactRef ( projected . paper_ready || Boolean ( projected . paper_readiness_state ) , "Paper readiness" , "paper/paper_readiness.json" )
104128 ] . filter ( ( item ) : item is { label : string ; path : string } => Boolean ( item ) ) ;
105129
@@ -121,6 +145,26 @@ export async function buildAnalyzeResultsOperatorSummary(input: {
121145 lines . push ( `Transition: ${ transitionRecommendation . action } ${ target } .` ) ;
122146 }
123147
148+ if ( ! projected . review_ready ) {
149+ lines . push ( "Review gate: not started yet or still missing one of the required review artifacts." ) ;
150+ } else if ( projected . review_gate_status || projected . review_decision_outcome ) {
151+ const transitionHint = projected . review_recommended_transition
152+ ? ` -> ${ projected . review_recommended_transition } `
153+ : "" ;
154+ const decisionHint = projected . review_decision_outcome
155+ ? `${ projected . review_decision_outcome } ${ transitionHint } `
156+ : projected . review_gate_status ;
157+ lines . push ( `Review gate: ${ decisionHint } .` ) ;
158+ }
159+
160+ if ( typeof projected . review_score_overall === "number" ) {
161+ lines . push ( `Review scorecard: ${ projected . review_score_overall } /5 overall.` ) ;
162+ }
163+
164+ if ( projected . paper_readiness_state ) {
165+ lines . push ( `Paper readiness state: ${ projected . paper_readiness_state } .` ) ;
166+ }
167+
124168 if ( projected . blocker_summary ) {
125169 lines . push ( `Blocker: ${ projected . blocker_summary } ` ) ;
126170 }
@@ -148,9 +192,16 @@ export function buildJobsTemplateLines(input: {
148192} ) : string [ ] {
149193 const activeCount = input . snapshot . runs . filter ( ( run ) => run . lifecycle_status !== "completed" ) . length ;
150194 const blockedCount = input . snapshot . runs . filter ( ( run ) => run . recommended_next_action === "inspect_blocker" ) . length ;
195+ const reviewPendingCount = input . snapshot . runs . filter (
196+ ( run ) => run . recommended_next_action === "resume_review" || run . current_node === "review"
197+ ) . length ;
198+ const paperBlockedCount = input . snapshot . runs . filter (
199+ ( run ) => Boolean ( run . paper_readiness_state ) && ! run . paper_ready
200+ ) . length ;
151201 return [
152202 `${ input . window === "3d" ? "3-day" : "7-day" } operator check-in template` ,
153203 `Runs in view: ${ input . snapshot . runs . length } . Active: ${ activeCount } . Blocked for inspection: ${ blockedCount } .` ,
204+ `Review-adjacent runs: ${ reviewPendingCount } . Paper-blocked runs: ${ paperBlockedCount } .` ,
154205 "1. Confirm the current_node and recommended_next_action for the top active runs." ,
155206 "2. Inspect one blocker artifact before retrying any failed or paused run." ,
156207 "3. Verify whether review is the next governed gate before treating a run as paper-ready." ,
@@ -200,6 +251,12 @@ async function buildRunJobProjectionInternal(input: {
200251 const reviewCritique = await readJsonArtifact < ReviewCritiqueProjection > (
201252 path . join ( runDir , "review" , "paper_critique.json" )
202253 ) ;
254+ const reviewPacket = await readJsonArtifact < ReviewPacketProjection > (
255+ path . join ( runDir , "review" , "review_packet.json" )
256+ ) ;
257+ const reviewScorecard = await readJsonArtifact < ReviewScorecardProjection > (
258+ path . join ( runDir , "review" , "scorecard.json" )
259+ ) ;
203260 const lifecycleStatus = deriveLifecycleStatus ( input . run ) ;
204261 const lastEventAt = await readLastEventTimestamp ( runDir , input . run . updatedAt ) ;
205262 const dominantFailure = deriveDominantFailure ( {
@@ -229,8 +286,16 @@ async function buildRunJobProjectionInternal(input: {
229286 analysis_ready : analysisReady ,
230287 review_ready : reviewReady ,
231288 paper_ready : paperReady ,
289+ review_gate_status : normalizeReviewGateStatus ( reviewPacket ?. readiness ?. status ) ,
290+ review_decision_outcome : asNonEmptyString ( reviewPacket ?. decision ?. outcome ) ,
291+ review_recommended_transition : asNonEmptyString ( reviewPacket ?. decision ?. recommended_transition ) ,
292+ review_score_overall :
293+ typeof reviewScorecard ?. overall_score_1_to_5 === "number"
294+ ? Number ( reviewScorecard . overall_score_1_to_5 . toFixed ( 1 ) )
295+ : undefined ,
232296 paper_readiness_state :
233297 asNonEmptyString ( paperReadiness ?. readiness_state ) || asNonEmptyString ( reviewCritique ?. paper_readiness_state ) ,
298+ paper_readiness_reason : asNonEmptyString ( paperReadiness ?. reason ) ,
234299 blocker_summary : dominantFailure ?. summary ,
235300 dominantFailure
236301 } ;
@@ -248,7 +313,12 @@ function stripInternalProjection(input: RunJobProjectionInternal): RunJobProject
248313 analysis_ready : input . analysis_ready ,
249314 review_ready : input . review_ready ,
250315 paper_ready : input . paper_ready ,
316+ review_gate_status : input . review_gate_status ,
317+ review_decision_outcome : input . review_decision_outcome ,
318+ review_recommended_transition : input . review_recommended_transition ,
319+ review_score_overall : input . review_score_overall ,
251320 paper_readiness_state : input . paper_readiness_state ,
321+ paper_readiness_reason : input . paper_readiness_reason ,
252322 blocker_summary : input . blocker_summary
253323 } ;
254324}
@@ -480,6 +550,15 @@ function asNonEmptyString(value: unknown): string | undefined {
480550 return typeof value === "string" && value . trim ( ) . length > 0 ? value . trim ( ) : undefined ;
481551}
482552
553+ function normalizeReviewGateStatus (
554+ value : "ready" | "warning" | "blocking" | undefined
555+ ) : RunJobProjection [ "review_gate_status" ] {
556+ if ( value === "ready" || value === "warning" || value === "blocking" ) {
557+ return value ;
558+ }
559+ return undefined ;
560+ }
561+
483562function yesNo ( value : boolean ) : string {
484563 return value ? "yes" : "no" ;
485564}
@@ -524,6 +603,21 @@ export function formatRunJobProjectionLines(input: {
524603 ` readiness: analysis=${ yesNo ( input . projection . analysis_ready ) } review=${ yesNo ( input . projection . review_ready ) } paper=${ yesNo ( input . projection . paper_ready ) } | next=${ input . projection . recommended_next_action } ` ,
525604 ` last event: ${ input . projection . last_event_at } `
526605 ] ;
606+ if ( input . projection . review_gate_status || input . projection . review_decision_outcome || typeof input . projection . review_score_overall === "number" ) {
607+ const gateLabel = input . projection . review_decision_outcome
608+ ? `${ input . projection . review_decision_outcome } ${ input . projection . review_recommended_transition ? ` -> ${ input . projection . review_recommended_transition } ` : "" } `
609+ : input . projection . review_gate_status || "missing" ;
610+ const scoreLabel = typeof input . projection . review_score_overall === "number"
611+ ? ` | score=${ input . projection . review_score_overall } /5`
612+ : "" ;
613+ lines . push ( ` review gate: ${ gateLabel } ${ scoreLabel } ` ) ;
614+ }
615+ if ( input . projection . paper_readiness_state ) {
616+ const paperDetail = input . projection . paper_readiness_reason
617+ ? ` | ${ compactOneLine ( input . projection . paper_readiness_reason , 120 ) } `
618+ : "" ;
619+ lines . push ( ` paper state: ${ input . projection . paper_readiness_state } ${ paperDetail } ` ) ;
620+ }
527621 if ( input . projection . blocker_summary ) {
528622 lines . push ( ` blocker: ${ compactOneLine ( input . projection . blocker_summary , 180 ) } ` ) ;
529623 }
0 commit comments