@@ -298,13 +298,33 @@ export class RepositoryCrawler {
298298 // Get file contents with semaphore-controlled concurrency
299299 const blobFiles = filteredTree . filter ( item => item . type === 'blob' ) ;
300300 const filesWithContent = [ ] ;
301- let processedFiles = 0 ;
302301 const totalFiles = blobFiles . length ;
303302
304303 this . clearErrors ( ) ; // Clear any previous errors
304+
305+ // Track timing and statistics
306+ const startTime = Date . now ( ) ;
307+ let successCount = 0 ;
308+ let failureCount = 0 ;
309+
310+ // Initialize progress tracking
311+ if ( onProgress ) {
312+ onProgress ( {
313+ phase : 'fetching' ,
314+ current : 0 ,
315+ total : totalFiles ,
316+ details : {
317+ status : 'Starting file downloads...' ,
318+ startTime : startTime
319+ }
320+ } ) ;
321+ }
322+
323+ // Track progress with atomic counter to avoid race conditions
324+ let completedCount = 0 ;
305325
306326 // Use semaphore to control concurrency
307- const filePromises = blobFiles . map ( file =>
327+ const filePromises = blobFiles . map ( ( file , index ) =>
308328 this . semaphore . execute ( async ( ) => {
309329 try {
310330 // Use raw content URL for better performance
@@ -319,17 +339,40 @@ export class RepositoryCrawler {
319339 statusText : response . statusText ,
320340 filePath : file . path
321341 } ) ;
342+
343+ // Update progress and failure count atomically
344+ completedCount ++ ;
345+ failureCount ++ ;
346+ if ( onProgress ) {
347+ onProgress ( {
348+ phase : 'fetching' ,
349+ current : completedCount ,
350+ total : totalFiles ,
351+ details : {
352+ currentFile : file . path ,
353+ successCount : successCount ,
354+ failureCount : failureCount
355+ }
356+ } ) ;
357+ }
322358 return null ;
323359 }
324360
325361 const content = await response . text ( ) ;
326- processedFiles ++ ;
327362
363+ // Update progress and success count atomically
364+ completedCount ++ ;
365+ successCount ++ ;
328366 if ( onProgress ) {
329367 onProgress ( {
330368 phase : 'fetching' ,
331- current : processedFiles ,
332- total : totalFiles
369+ current : completedCount ,
370+ total : totalFiles ,
371+ details : {
372+ currentFile : file . path ,
373+ successCount : successCount ,
374+ failureCount : failureCount
375+ }
333376 } ) ;
334377 }
335378
@@ -339,12 +382,20 @@ export class RepositoryCrawler {
339382 originalError : error . message ,
340383 filePath : file . path
341384 } ) ;
342- processedFiles ++ ;
385+
386+ // Update progress and failure count atomically
387+ completedCount ++ ;
388+ failureCount ++ ;
343389 if ( onProgress ) {
344390 onProgress ( {
345391 phase : 'fetching' ,
346- current : processedFiles ,
347- total : totalFiles
392+ current : completedCount ,
393+ total : totalFiles ,
394+ details : {
395+ currentFile : file . path ,
396+ successCount : successCount ,
397+ failureCount : failureCount
398+ }
348399 } ) ;
349400 }
350401 return null ;
@@ -355,21 +406,59 @@ export class RepositoryCrawler {
355406 const results = await Promise . all ( filePromises ) ;
356407 filesWithContent . push ( ...results . filter ( f => f !== null ) ) ;
357408
358- console . log ( `Successfully fetched ${ filesWithContent . length } files` ) ;
409+ // Calculate final statistics
410+ const endTime = Date . now ( ) ;
411+ const duration = Math . round ( ( endTime - startTime ) / 1000 * 100 ) / 100 ; // seconds with 2 decimal places
412+ const actualSuccessCount = filesWithContent . length ;
413+ const actualFailureCount = totalFiles - actualSuccessCount ;
414+ const completionRate = totalFiles > 0 ? Math . round ( ( actualSuccessCount / totalFiles ) * 100 ) : 100 ;
415+
416+ console . log ( `Successfully fetched ${ actualSuccessCount } files in ${ duration } s` ) ;
359417
360- if ( this . errors . length > 0 ) {
361- console . warn ( `${ this . errors . length } non-fatal errors occurred during file fetching` ) ;
418+ if ( actualFailureCount > 0 ) {
419+ console . warn ( `${ actualFailureCount } files failed to download` ) ;
420+ }
421+
422+ // Send completion summary
423+ if ( onProgress ) {
424+ onProgress ( {
425+ phase : 'completed' ,
426+ current : totalFiles ,
427+ total : totalFiles ,
428+ details : {
429+ duration : duration ,
430+ successCount : actualSuccessCount ,
431+ failureCount : actualFailureCount ,
432+ completionRate : completionRate ,
433+ totalAttempted : totalFiles ,
434+ summary : `Scanned ${ actualSuccessCount } /${ totalFiles } files (${ completionRate } %) in ${ duration } s`
435+ }
436+ } ) ;
362437 }
363438
364439 const result = {
365440 files : filesWithContent ,
366441 errors : this . errors ,
367- partial : this . errors . length > 0
442+ partial : actualFailureCount > 0 ,
443+ scanStats : {
444+ duration : duration ,
445+ totalFiles : totalFiles ,
446+ successCount : actualSuccessCount ,
447+ failureCount : actualFailureCount ,
448+ completionRate : completionRate
449+ }
368450 } ;
369451 this . cache . set ( cacheKey , result , 24 * 60 * 60 ) ; // Cache for 24 hours
370452
371453 if ( onProgress ) {
372- onProgress ( { phase : 'analyzing' , current : 0 , total : filesWithContent . length } ) ;
454+ onProgress ( {
455+ phase : 'analyzing' ,
456+ current : 0 ,
457+ total : filesWithContent . length ,
458+ details : {
459+ status : 'Starting vulnerability analysis...'
460+ }
461+ } ) ;
373462 }
374463
375464 return { ...result , fromCache : false } ;
0 commit comments