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
18 changes: 18 additions & 0 deletions changelog.d/unreleased/3097.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
category: fixed
issues:
- 3097
affected:
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Mcp/McpToolHandlers.cs
- tests/CodeIndex.Tests/QueryCommandRunnerTests.cs
- tests/CodeIndex.Tests/McpServerTests.cs
---

## English

- **Dependency JSON graph output avoids duplicate full graph array materialization (#3097)** — CLI graph output now writes JSON incrementally, and MCP graph payload construction fills bounded arrays without LINQ `ToArray` copies.

## 日本語

- **dependency JSON graph 出力が graph 配列全体の重複 materialization を避けるようになりました (#3097)** — CLI graph 出力は JSON を逐次書き込み、MCP graph payload は LINQ `ToArray` のコピーを作らず bounded array を構築します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3111.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3111
affected:
- src/CodeIndex/Database/DbSymbolReader.cs
- tests/CodeIndex.Tests/DbReaderTests.cs
---

## English

- **Visibility-filtered symbol and definition count queries no longer materialize full result lists (#3111)** — count-only paths apply visibility filters directly in SQL while preserving the existing result counts.

## 日本語

- **visibility filter 付きの symbol / definition count query が全結果リストを materialize しなくなりました (#3111)** — count-only 経路で visibility filter を SQL に直接適用し、既存の件数結果を維持します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3115.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3115
affected:
- src/CodeIndex/Mcp/McpToolHandlers.cs
- tests/CodeIndex.Tests/McpServerTests.cs
---

## English

- **MCP graph count-only handlers no longer use `int.MaxValue` row probes (#3115)** — references, callers, and callees now use total-count queries for count-only and paginated-total metadata.

## 日本語

- **MCP graph の count-only handler が `int.MaxValue` の行取得 probe を使わなくなりました (#3115)** — references / callers / callees は count-only と paginated total metadata に total-count query を使います。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3139.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3139
affected:
- src/CodeIndex/Cli/IndexFreshnessChecker.cs
- src/CodeIndex/Database/DbReader.cs
---

## English

- **`status --check` now compares freshness without materializing the full workspace snapshot (#3139)** — indexed snapshots stream from the database, and workspace files are checked as they are scanned while unmatched indexed rows remain for missing-file classification.

## 日本語

- **`status --check` が workspace snapshot 全体を materialize せずに freshness を比較するようになりました (#3139)** — indexed snapshot は DB から streaming し、workspace file は scan しながら照合し、未照合の indexed row だけを missing file 分類に回します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/3149.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 3149
affected:
- src/CodeIndex/Database/RepoMapBuilder.cs
---

## English

- **Repo map now keeps bounded file-summary rankings instead of materializing every file stat before limits (#3149)** — map aggregation streams file stats, maintains top-N lists as it scans, and resolves Java module grouping without mutating a full file list.

## 日本語

- **repo map が limit 適用前に全 file stat を materialize せず、bounded な file-summary ranking を保持するようになりました (#3149)** — map 集約は file stat を streaming しながら top-N を更新し、Java module grouping も全 file list を mutate せずに解決します。
15 changes: 15 additions & 0 deletions changelog.d/unreleased/3150.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
category: fixed
issues:
- 3150
affected:
- src/CodeIndex/Database/DbSymbolReader.cs
---

## English

- **`unused` fallback queries no longer load every referenced symbol name into memory (#3150)** — non-SQL resolver paths now exclude referenced symbols in the candidate SQL before applying their bounded result loops.

## 日本語

- **`unused` fallback query が参照済み symbol 名を全件メモリへ読み込まなくなりました (#3150)** — 非 SQL resolver 経路では、bounded result loop の前に候補 SQL 側で参照済み symbol を除外します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/3152.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 3152
affected:
- src/CodeIndex/Cli/QueryCommandRunner.cs
- tests/CodeIndex.Tests/QueryCommandRunnerSearchTests.cs
---

## English

- **CLI structured format helpers no longer duplicate full result arrays for LSP, compact, and SARIF output (#3152)** — formatted writers now stream array items to stdout while preserving their JSON schemas.

## 日本語

- **CLI structured format helper が LSP / compact / SARIF 出力で全 result array を重複保持しなくなりました (#3152)** — formatted writer は JSON schema を保ったまま array item を stdout へ逐次書き込みます。
130 changes: 72 additions & 58 deletions src/CodeIndex/Cli/IndexFreshnessChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ internal static IndexFreshnessCheckResult Check(DbReader reader, string? project
};
}

var indexed = reader.GetIndexedFileSnapshots()
.ToDictionary(file => file.Path, StringComparer.Ordinal);
var workspace = new Dictionary<string, WorkspaceFileSnapshot>(StringComparer.Ordinal);
var indexedHeadCommit = reader.GetMetaString(DbContext.IndexedHeadCommitMetaKey);
var indexedHeadSha = reader.GetMetaString(DbContext.IndexedHeadShaMetaKey);
var commitScopedFreshHeadSha = reader.GetMetaString(DbContext.CommitScopedFreshHeadShaMetaKey);
Expand All @@ -43,7 +40,6 @@ internal static IndexFreshnessCheckResult Check(DbReader reader, string? project
&& !currentHeadCoveredByCommitRefresh;
var result = new IndexFreshnessCheckResult
{
IndexedFileCount = indexed.Count,
IndexedHeadCommit = string.IsNullOrWhiteSpace(indexedHeadCommit) ? null : indexedHeadCommit,
WorkspaceHeadCommit = string.IsNullOrWhiteSpace(workspaceHeadCommit) ? null : workspaceHeadCommit,
HeadChanged = headChanged,
Expand All @@ -62,12 +58,50 @@ internal static IndexFreshnessCheckResult Check(DbReader reader, string? project
AddSample(result.ScanErrors, $"{error.Path}: {error.Message}");
}

foreach (var absolutePath in scan.Files.OrderBy(path => path, StringComparer.Ordinal))
using var indexedEnumerator = reader.EnumerateIndexedFileSnapshots().GetEnumerator();
var hasIndexed = MoveNextIndexed();
var skipWorktreePathsLoaded = false;
HashSet<string>? skipWorktreePaths = null;

foreach (var absolutePath in scan.Files.OrderBy(path => FileIndexer.NormalizeIndexPath(Path.GetRelativePath(projectRoot, path)), StringComparer.Ordinal))
{
try
{
var (record, _, _, _) = indexer.BuildRecordWithRawBytes(absolutePath);
workspace[record.Path] = new WorkspaceFileSnapshot(record.Checksum ?? string.Empty, record.Lines);
result.WorkspaceFileCount++;
while (hasIndexed && string.Compare(indexedEnumerator.Current.Path, record.Path, StringComparison.Ordinal) < 0)
{
AddMissingIndexedPath(indexedEnumerator.Current.Path);
hasIndexed = MoveNextIndexed();
}

if (!hasIndexed || string.Compare(indexedEnumerator.Current.Path, record.Path, StringComparison.Ordinal) > 0)
{
result.UnindexedFileCount++;
AddSample(result.UnindexedFiles, record.Path);
continue;
}

var indexedFile = indexedEnumerator.Current;
if (string.IsNullOrWhiteSpace(indexedFile.Checksum))
{
result.UnverifiableFileCount++;
AddSample(result.UnverifiableFiles, record.Path);
hasIndexed = MoveNextIndexed();
continue;
}

if (!string.Equals(indexedFile.Checksum, record.Checksum ?? string.Empty, StringComparison.OrdinalIgnoreCase)
|| (indexedFile.Lines.HasValue && indexedFile.Lines.Value != record.Lines))
{
result.ChangedFileCount++;
AddSample(result.ChangedFiles, record.Path);
hasIndexed = MoveNextIndexed();
continue;
}

result.MatchedFileCount++;
hasIndexed = MoveNextIndexed();
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or InvalidOperationException)
{
Expand All @@ -77,52 +111,44 @@ internal static IndexFreshnessCheckResult Check(DbReader reader, string? project
}
}

result.WorkspaceFileCount = workspace.Count;

foreach (var (path, workspaceFile) in workspace.OrderBy(kv => kv.Key, StringComparer.Ordinal))
while (hasIndexed)
{
if (!indexed.TryGetValue(path, out var indexedFile))
{
result.UnindexedFileCount++;
AddSample(result.UnindexedFiles, path);
continue;
}

if (string.IsNullOrWhiteSpace(indexedFile.Checksum))
{
result.UnverifiableFileCount++;
AddSample(result.UnverifiableFiles, path);
continue;
}
AddMissingIndexedPath(indexedEnumerator.Current.Path);
hasIndexed = MoveNextIndexed();
}

if (!string.Equals(indexedFile.Checksum, workspaceFile.Checksum, StringComparison.OrdinalIgnoreCase)
|| (indexedFile.Lines.HasValue && indexedFile.Lines.Value != workspaceFile.Lines))
{
result.ChangedFileCount++;
AddSample(result.ChangedFiles, path);
continue;
}
result.Checked = result.ScanErrorCount == 0;
result.MatchesWorkspace = result.Checked
&& !result.HeadChanged
&& result.ChangedFileCount == 0
&& result.MissingFileCount == 0
&& result.UnindexedFileCount == 0
&& result.UnverifiableFileCount == 0;
result.Reason = BuildReason(result);
return result;

result.MatchedFileCount++;
bool MoveNextIndexed()
{
var moved = indexedEnumerator.MoveNext();
if (moved)
result.IndexedFileCount++;
return moved;
}

var missingCandidates = indexed.Keys
.Except(workspace.Keys, StringComparer.Ordinal)
.OrderBy(path => path, StringComparer.Ordinal)
.ToList();

// Skip-worktree paths are intentionally absent from disk (sparse-checkout cone/non-cone,
// partial clone, or manual update-index --skip-worktree). Reclassify them so the freshness
// gate stops flagging them as "missing" and rebuilds.
// skip-worktree のパスは意図的に worktree から外されている(sparse-checkout cone/non-cone、
// partial clone、手動の update-index --skip-worktree)。これらを "missing" から切り分け、
// 不要な rebuild トリガーを止める。
HashSet<string>? skipWorktreePaths = null;
if (missingCandidates.Count > 0)
skipWorktreePaths = GitHelper.TryGetSkipWorktreePaths(projectRoot);

foreach (var path in missingCandidates)
void AddMissingIndexedPath(string path)
{
// Skip-worktree paths are intentionally absent from disk (sparse-checkout cone/non-cone,
// partial clone, or manual update-index --skip-worktree). Reclassify them so the freshness
// gate stops flagging them as "missing" and rebuilds.
// skip-worktree のパスは意図的に worktree から外されている(sparse-checkout cone/non-cone、
// partial clone、手動の update-index --skip-worktree)。これらを "missing" から切り分け、
// 不要な rebuild トリガーを止める。
if (!skipWorktreePathsLoaded)
{
skipWorktreePaths = GitHelper.TryGetSkipWorktreePaths(projectRoot);
skipWorktreePathsLoaded = true;
}

if (skipWorktreePaths != null && skipWorktreePaths.Contains(path))
{
result.OutsideSparseConeFileCount++;
Expand All @@ -134,16 +160,6 @@ internal static IndexFreshnessCheckResult Check(DbReader reader, string? project
AddSample(result.MissingFiles, path);
}
}

result.Checked = result.ScanErrorCount == 0;
result.MatchesWorkspace = result.Checked
&& !result.HeadChanged
&& result.ChangedFileCount == 0
&& result.MissingFileCount == 0
&& result.UnindexedFileCount == 0
&& result.UnverifiableFileCount == 0;
result.Reason = BuildReason(result);
return result;
}

private static string BuildReason(IndexFreshnessCheckResult result)
Expand Down Expand Up @@ -175,6 +191,4 @@ private static void AddSample(List<string> samples, string value)
if (samples.Count < SampleLimit)
samples.Add(value);
}

private readonly record struct WorkspaceFileSnapshot(string Checksum, int Lines);
}
Loading
Loading