Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions changelog.d/unreleased/1471.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 1471
affected:
- src/CodeIndex/Cli/IndexCommandRunner.cs
- tests/CodeIndex.Tests/IndexCommandRunnerTests.cs
---

## English

- **`index --verbose --json` now keeps stdout parseable (#1471)** — verbose index status lines are written to stderr when JSON output is requested, so stdout remains a clean JSON stream for machine consumers.

## 日本語

- **`index --verbose --json` の stdout が parse 可能なままになりました (#1471)** — JSON 出力が指定された場合、verbose な index status 行は stderr に出力されるため、機械処理向けの stdout は純粋な JSON stream として保たれます。
69 changes: 38 additions & 31 deletions src/CodeIndex/Cli/IndexCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,22 @@ void ResumeUpdateSpinnerAfterConsoleWrite()
StartUpdateSpinnerIfNeeded();
}

void WriteUpdateVerboseStatus(string message)
{
if (!options.Verbose || options.Quiet)
return;

if (options.Json)
{
Console.Error.WriteLine(message);
return;
}

PauseUpdateSpinnerForConsoleWrite();
Console.WriteLine(message);
ResumeUpdateSpinnerAfterConsoleWrite();
}

void ThrowIfUpdateCancelled()
{
if (!cancellationToken.IsCancellationRequested)
Expand Down Expand Up @@ -1412,12 +1428,7 @@ void ThrowIfUpdateCancelled()
if (!writer.HasFileAtPath(relPath))
{
skipped++;
if (options.Verbose && !options.Json && !options.Quiet)
{
PauseUpdateSpinnerForConsoleWrite();
Console.WriteLine($" [SKIP] {relPath} (not in DB)");
ResumeUpdateSpinnerAfterConsoleWrite();
}
WriteUpdateVerboseStatus($" [SKIP] {relPath} (not in DB)");
continue;
}

Expand All @@ -1429,22 +1440,12 @@ void ThrowIfUpdateCancelled()
deleteTxn.Commit();
removed++;
ftsMutated = true;
if (options.Verbose && !options.Json && !options.Quiet)
{
PauseUpdateSpinnerForConsoleWrite();
Console.WriteLine($" [DEL ] {relPath}");
ResumeUpdateSpinnerAfterConsoleWrite();
}
WriteUpdateVerboseStatus($" [DEL ] {relPath}");
}
else
{
skipped++;
if (options.Verbose && !options.Json && !options.Quiet)
{
PauseUpdateSpinnerForConsoleWrite();
Console.WriteLine($" [SKIP] {relPath} (not in DB)");
ResumeUpdateSpinnerAfterConsoleWrite();
}
WriteUpdateVerboseStatus($" [SKIP] {relPath} (not in DB)");
}
continue;
}
Expand Down Expand Up @@ -1658,12 +1659,7 @@ void ThrowIfUpdateCancelled()
updated++;
ftsMutated = true;
ThrowIfUpdateCancelled();
if (options.Verbose && !options.Json && !options.Quiet)
{
PauseUpdateSpinnerForConsoleWrite();
Console.WriteLine($" [OK ] {relPath} ({chunks.Count} chunks, {symbols.Count} symbols, {references.Count} refs)");
ResumeUpdateSpinnerAfterConsoleWrite();
}
WriteUpdateVerboseStatus($" [OK ] {relPath} ({chunks.Count} chunks, {symbols.Count} symbols, {references.Count} refs)");
}
catch (Exception ex)
{
Expand Down Expand Up @@ -2702,6 +2698,23 @@ void ResumeIndexSpinnerAfterConsoleWrite()
StartIndexSpinnerIfNeeded();
}

void WriteIndexVerboseStatus(string message)
{
if (!options.Verbose || options.Quiet)
return;

if (options.Json)
{
Console.Error.WriteLine(message);
return;
}

PauseIndexSpinnerForConsoleWrite();
ConsoleUi.ClearProgressLine();
Console.WriteLine(message);
ResumeIndexSpinnerAfterConsoleWrite();
}

void EnsureIndexingActivityVisible()
{
if (options.Json || options.Quiet)
Expand Down Expand Up @@ -2975,13 +2988,7 @@ void StopJsonHeartbeat()
WriteProjectRootOnce();
txn.Commit();

if (options.Verbose && !options.Json && !options.Quiet)
{
PauseIndexSpinnerForConsoleWrite();
ConsoleUi.ClearProgressLine();
Console.WriteLine($" [OK ] {record.Path} ({chunks.Count} chunks, {symbols.Count} symbols, {references.Count} refs)");
ResumeIndexSpinnerAfterConsoleWrite();
}
WriteIndexVerboseStatus($" [OK ] {record.Path} ({chunks.Count} chunks, {symbols.Count} symbols, {references.Count} refs)");
}
catch (Exception ex)
{
Expand Down
29 changes: 29 additions & 0 deletions tests/CodeIndex.Tests/IndexCommandRunnerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,6 +2871,35 @@ public void Run_UpdateMode_VerboseRedirectedOutput_DoesNotRepeatUpdatingBanner()
}
}

[Fact]
public void Run_UpdateMode_VerboseJson_WritesStatusToStderrAndKeepsStdoutJson()
{
var projectRoot = CreateTempProject();
try
{
var sourcePath = Path.Combine(projectRoot, "app.cs");
File.WriteAllText(sourcePath, "public class App { public void Run() { } }\n");

var initialExitCode = IndexCommandRunner.Run([projectRoot, "--json"], _jsonOptions);
Assert.Equal(CommandExitCodes.Success, initialExitCode);

File.WriteAllText(sourcePath, "public class App { public void Run() { } public void Extra() { } }\n");
File.SetLastWriteTimeUtc(sourcePath, DateTime.UtcNow.AddSeconds(2));

var (exitCode, stdout, stderr) = RunAndCaptureStreams([projectRoot, "--files", "app.cs", "--verbose", "--json"]);

Assert.Equal(CommandExitCodes.Success, exitCode);
using var json = JsonDocument.Parse(stdout);
Assert.Equal("success", json.RootElement.GetProperty("Status").GetString());
Assert.DoesNotContain("[OK ]", stdout);
Assert.Contains("[OK ] app.cs", stderr);
}
finally
{
DeleteDirectory(projectRoot);
}
}

[Fact]
public void Run_UpdateMode_JsonKeepsGraphAndIssuesReadyAfterHealthyScopedRefresh()
{
Expand Down
Loading