@@ -125,27 +125,34 @@ export async function runReviewHeadless(options = {}) {
125125 // ── Split into per-file requests ────────────────────────────────────
126126 const perFileRequests = ReviewApiHelper . splitIntoPerFileRequests ( requestBody ) ;
127127
128- onProgress ( `Analyzing ${ perFileRequests . length } file${ perFileRequests . length !== 1 ? 's' : '' } in parallel...` ) ;
128+ const totalFiles = perFileRequests . reduce ( ( n , r ) => n + ( r . _filenames ?. length || 0 ) , 0 ) ;
129+ onProgress ( `Analyzing ${ totalFiles } file${ totalFiles !== 1 ? 's' : '' } ...` ) ;
129130
130- // ── Per-file agent turn loops (parallel, fault-tolerant) ─────────────
131+ // ── Per-batch agent turn loops (parallel, fault-tolerant) ────────────
132+ // Each batch covers up to 5 files; backend reviews the multi-file diff
133+ // in a single session (matches pragent's FileBatcher).
131134 const perFileResults = await Promise . all (
132135 perFileRequests . map ( async ( fileReq ) => {
133- const filename = fileReq . _filename ;
134- delete fileReq . _filename ;
135-
136- if ( fileReq . file_contents ?. [ filename ] ) {
137- fileReq . file_content = fileReq . file_contents [ filename ] ;
138- fileReq . file_path = filename ;
136+ const filenames = fileReq . _filenames || [ ] ;
137+ delete fileReq . _filenames ;
138+ const label = filenames . join ( ', ' ) ;
139+
140+ // Single-file batch: still pass file_content/file_path so backend can
141+ // build head_file_str. Multi-file batches drop these (head_file_str
142+ // becomes empty; agent gathers context via tools instead).
143+ if ( filenames . length === 1 && fileReq . file_contents ?. [ filenames [ 0 ] ] ) {
144+ fileReq . file_content = fileReq . file_contents [ filenames [ 0 ] ] ;
145+ fileReq . file_path = filenames [ 0 ] ;
139146 }
140147 delete fileReq . file_contents ;
141148
142149 try {
143- onProgress ( `Reviewing ${ filename } ...` ) ;
150+ onProgress ( `Reviewing ${ label } ...` ) ;
144151 const result = await runTurnLoop ( fileReq , gitRoot , false ) ;
145- onProgress ( `Done reviewing ${ filename } ` ) ;
152+ onProgress ( `Done reviewing ${ label } ` ) ;
146153 return result ;
147154 } catch ( err ) {
148- console . error ( `[error] Failed to review ${ filename } : ${ err . message } ` ) ;
155+ console . error ( `[error] Failed to review ${ label } : ${ err . message } ` ) ;
149156 return { finalMessage : null , finalOutput : null } ;
150157 }
151158 } )
@@ -158,7 +165,12 @@ export async function runReviewHeadless(options = {}) {
158165 output : perFileResults [ i ] . finalOutput ,
159166 } ) ) . filter ( r => r . output ?. code_suggestions ?. length > 0 ) ;
160167
161- onProgress ( `${ perFileWithSuggestions . length } file(s) have suggestions, running reflector...` ) ;
168+ const filesWithSuggestions = new Set (
169+ perFileWithSuggestions . flatMap ( r =>
170+ ( r . output ?. code_suggestions || [ ] ) . map ( s => ( s . relevant_file || '' ) . trim ( ) ) . filter ( Boolean )
171+ )
172+ ) . size ;
173+ onProgress ( `${ filesWithSuggestions } file${ filesWithSuggestions !== 1 ? 's' : '' } have suggestions, running reflector...` ) ;
162174
163175 // ── Per-file reflector loops (parallel, fault-tolerant) ──────────────
164176 const reflectorResults = await Promise . all (
@@ -180,15 +192,34 @@ export async function runReviewHeadless(options = {}) {
180192 } )
181193 ) ;
182194
183- // ── Parse results ───────────────────────────────────────────────────
184- const issues = reflectorResults . flatMap ( r =>
185- ( r . finalOutput ?. code_suggestions || [ ] ) . map ( ( issue ) => ( {
195+ // ── Parse results, carrying generator labels through reflector ──────
196+ // Pragent's rejector schema drops `label`; preserve it by matching the
197+ // reflector's per-issue (file, start_line) back to the generator's output.
198+ const issues = reflectorResults . flatMap ( ( r , i ) => {
199+ const genSuggestions = perFileWithSuggestions [ i ] ?. output ?. code_suggestions || [ ] ;
200+ const norm = ( v ) => ( typeof v === 'string' ? v . trim ( ) : v ) ;
201+ const labelFor = ( issue ) => {
202+ if ( norm ( issue . label ) ) return norm ( issue . label ) ;
203+ // Match by summary (pragent's primary strategy): rejector's
204+ // suggestion_summary (renamed to issue_content server-side) is
205+ // "Repeated from the input" — i.e. the generator's one_sentence_summary.
206+ const summary = norm ( issue . issue_content ) ;
207+ if ( summary ) {
208+ const exact = genSuggestions . find ( g => norm ( g . one_sentence_summary ) === summary ) ;
209+ if ( norm ( exact ?. label ) ) return norm ( exact . label ) ;
210+ }
211+ // Fallback: first generator suggestion in the same file with a label.
212+ const file = norm ( issue . relevant_file ) ;
213+ const sameFile = genSuggestions . find ( g => norm ( g . relevant_file ) === file && norm ( g . label ) ) ;
214+ return norm ( sameFile ?. label ) || 'Code Quality' ;
215+ } ;
216+ return ( r . finalOutput ?. code_suggestions || [ ] ) . map ( ( issue ) => ( {
186217 issue_content : issue . issue_content || '' ,
187218 relevant_file : issue . relevant_file || 'Unknown' ,
188219 start_line : issue . start_line || 0 ,
189- label : issue . label || 'Code Quality' ,
190- } ) )
191- ) ;
220+ label : labelFor ( issue ) ,
221+ } ) ) ;
222+ } ) ;
192223
193224 const labelCounts = { } ;
194225 for ( const i of issues ) { labelCounts [ i . label ] = ( labelCounts [ i . label ] || 0 ) + 1 ; }
0 commit comments