Skip to content

Commit b657253

Browse files
elbrunoCopilot
andauthored
Add file progress updates (#11)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 49eb5bd commit b657253

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

src/Graphify.Cli/PipelineRunner.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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)

src/Graphify/Pipeline/WatchMode.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ private async Task ProcessChangesAsync(
188188
// Extract
189189
var extractor = new Extractor();
190190
var newResults = new List<ExtractionResult>();
191+
var progressStep = Math.Max(1, filesToProcess.Count / 10);
192+
var completed = 0;
191193
foreach (var file in filesToProcess)
192194
{
193195
ct.ThrowIfCancellationRequested();
@@ -202,6 +204,12 @@ private async Task ProcessChangesAsync(
202204
if (_verbose)
203205
await _output.WriteLineAsync($" Warning: extraction failed for {file.RelativePath}: {ex.Message}");
204206
}
207+
208+
completed++;
209+
if (completed == filesToProcess.Count || completed % progressStep == 0)
210+
{
211+
await _output.WriteLineAsync($" Progress: {completed}/{filesToProcess.Count} changed files analyzed");
212+
}
205213
}
206214

207215
if (newResults.Count == 0)

src/tests/Graphify.Tests/Cli/PipelineRunnerTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,34 @@ public async Task RunAsync_LadybugFormat_RoutesToLadybugExporter()
7272
try { if (Directory.Exists(tempOutput)) Directory.Delete(tempOutput, recursive: true); } catch { /* best-effort cleanup */ }
7373
}
7474
}
75+
76+
[Fact]
77+
[Trait("Category", "Cli")]
78+
public async Task RunAsync_ReportsFileProgressDuringExtraction()
79+
{
80+
var tempInput = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
81+
var tempOutput = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
82+
Directory.CreateDirectory(tempInput);
83+
84+
try
85+
{
86+
await File.WriteAllTextAsync(Path.Combine(tempInput, "alpha.cs"), "namespace Demo; public sealed class Alpha { }");
87+
await File.WriteAllTextAsync(Path.Combine(tempInput, "beta.cs"), "namespace Demo; public sealed class Beta { }");
88+
89+
var sb = new StringBuilder();
90+
await using var writer = new StringWriter(sb);
91+
var runner = new PipelineRunner(writer, verbose: false);
92+
93+
await runner.RunAsync(tempInput, tempOutput, formats: ["json"], useCache: false);
94+
95+
var log = sb.ToString();
96+
Assert.Contains("Progress:", log, StringComparison.OrdinalIgnoreCase);
97+
Assert.Contains("2/2", log, StringComparison.OrdinalIgnoreCase);
98+
}
99+
finally
100+
{
101+
try { Directory.Delete(tempInput, recursive: true); } catch { /* best-effort cleanup */ }
102+
try { if (Directory.Exists(tempOutput)) Directory.Delete(tempOutput, recursive: true); } catch { /* best-effort cleanup */ }
103+
}
104+
}
75105
}

0 commit comments

Comments
 (0)