@@ -15,6 +15,7 @@ public sealed class PipelineRunner
1515 private readonly TextWriter _output ;
1616 private readonly bool _verbose ;
1717 private readonly IChatClient ? _chatClient ;
18+ private readonly SemaphoreSlim _outputLock = new ( 1 , 1 ) ;
1819
1920 public PipelineRunner ( TextWriter output , bool verbose = false , IChatClient ? chatClient = null )
2021 {
@@ -66,7 +67,9 @@ public PipelineRunner(TextWriter output, bool verbose = false, IChatClient? chat
6667 var extractionBag = new ConcurrentBag < ExtractionResult > ( ) ;
6768 int processed = 0 ;
6869 int skipped = 0 ;
70+ int completed = 0 ;
6971 var verboseWarnings = new ConcurrentQueue < string > ( ) ;
72+ var extractionProgressStep = Math . Max ( 1 , detectedFiles . Count / 10 ) ;
7073
7174 await Parallel . ForEachAsync (
7275 detectedFiles ,
@@ -89,10 +92,21 @@ await Parallel.ForEachAsync(
8992 {
9093 Interlocked . Increment ( ref skipped ) ;
9194 }
95+
96+ var finished = Interlocked . Increment ( ref completed ) ;
97+ if ( ShouldReportProgress ( finished , detectedFiles . Count , extractionProgressStep ) )
98+ {
99+ await WriteLineAsync ( $ " Progress: { finished } /{ detectedFiles . Count } files analyzed") ;
100+ }
92101 }
93102 catch ( Exception ex )
94103 {
95104 Interlocked . Increment ( ref skipped ) ;
105+ var finished = Interlocked . Increment ( ref completed ) ;
106+ if ( ShouldReportProgress ( finished , detectedFiles . Count , extractionProgressStep ) )
107+ {
108+ await WriteLineAsync ( $ " Progress: { finished } /{ detectedFiles . Count } files analyzed") ;
109+ }
96110 if ( _verbose )
97111 {
98112 verboseWarnings . Enqueue ( $ " Warning: Failed to extract { file . RelativePath } : { ex . Message } ") ;
@@ -118,6 +132,8 @@ await Parallel.ForEachAsync(
118132 await WriteLineAsync ( "[2b/6] Running AI-enhanced semantic extraction..." ) ;
119133 var semanticExtractor = new SemanticExtractor ( _chatClient ) ;
120134 int semanticProcessed = 0 ;
135+ int semanticCompleted = 0 ;
136+ var semanticProgressStep = Math . Max ( 1 , detectedFiles . Count / 10 ) ;
121137
122138 foreach ( var file in detectedFiles )
123139 {
@@ -136,6 +152,12 @@ await Parallel.ForEachAsync(
136152 if ( _verbose )
137153 await WriteLineAsync ( $ " Warning: Semantic extraction failed for { file . RelativePath } : { ex . Message } ") ;
138154 }
155+
156+ var finished = ++ semanticCompleted ;
157+ if ( ShouldReportProgress ( finished , detectedFiles . Count , semanticProgressStep ) )
158+ {
159+ await WriteLineAsync ( $ " Progress: { finished } /{ detectedFiles . Count } files semantically analyzed") ;
160+ }
139161 }
140162
141163 await WriteLineAsync ( $ " AI extracted from { semanticProcessed } files") ;
@@ -333,7 +355,20 @@ await Parallel.ForEachAsync(
333355
334356 private async Task WriteLineAsync ( string message = "" )
335357 {
336- await _output . WriteLineAsync ( message ) ;
358+ await _outputLock . WaitAsync ( ) ;
359+ try
360+ {
361+ await _output . WriteLineAsync ( message ) ;
362+ }
363+ finally
364+ {
365+ _outputLock . Release ( ) ;
366+ }
367+ }
368+
369+ private static bool ShouldReportProgress ( int completed , int total , int step )
370+ {
371+ return completed == total || completed % step == 0 ;
337372 }
338373
339374 private static Dictionary < int , string > BuildCommunityLabels ( KnowledgeGraph graph )
0 commit comments