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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **`--exact` ranking now prefers exact-case siblings first** — When multiple symbols or graph rows collapse to the same folded exact-match key (for example `ApiClient` and `apiClient`), `symbols` / `definition` / `references` / `callers` / `callees` still return every case-insensitive exact match, but now rank the row whose stored name exactly matches the caller's casing first instead of letting path order win. This keeps the intended symbol at rank 0 for AI follow-up flows without hiding the fold-sibling rows. Added regression coverage for both symbol lookup and graph readers. Affected: `src/CodeIndex/Database/DbSymbolReader.cs`, `src/CodeIndex/Database/DbReader.cs`, `tests/CodeIndex.Tests/DbReaderTests.cs`.

#### Fixed
- **C# named-argument labels no longer masquerade as local functions in `symbols` / `deps` (#106)** — Tightened the C# explicit-interface implementation pattern so call-site lines like `isWindows: OperatingSystem.IsWindows()` are no longer parsed as fake local function definitions. This removes the downstream false positives where `symbols "IsWindows"` surfaced named-argument labels and `deps` created bogus file-level edges to those files. Added extractor-level and DbReader integration coverage for the repro shape from Praxis. Affected: `src/CodeIndex/Indexer/SymbolExtractor.cs`, `tests/CodeIndex.Tests/SymbolExtractorTests.cs`, `tests/CodeIndex.Tests/DbReaderTests.cs`. Closes #106.
- **True Unicode CaseFold for `--exact` symbol and graph name matching (#96)** — `NameFold` now applies full Unicode CaseFold on top of NFKC normalization instead of stopping at `ToLowerInvariant()`. This closes the remaining Unicode-exact gaps for sharp-S (`Straße` / `STRASSE`), Greek final sigma (`Σ` / `ς` / `σ`), Cherokee, and other non-ASCII pairs that invariant lowercase still missed. The persisted fold-key version is bumped from 1 to 2 so existing indexes automatically degrade to ASCII `COLLATE NOCASE` until the DB truly contains only current-version fold keys, preventing silent mixed-version misses. Normal non-`--rebuild` scans remain incremental and may skip unchanged rows, so CLI and MCP indexers keep `fold_ready=false` only while unchanged old-version rows remain; if a full scan rewrites or purges every old-version row, it may safely restamp without forcing `--rebuild`. Added direct fold tests plus CLI/MCP regression coverage for sharp-S, sigma, and both sides of the mixed-version upgrade path. Locale-invariant Unicode behavior is preserved: Turkish dotted `İ` still folds to `i\u0307` rather than plain `i`, so locale-sensitive Turkish matching remains a separate follow-up. Affected: `src/CodeIndex/Database/NameFold.cs`, `src/CodeIndex/Database/DbReader.cs`, `src/CodeIndex/Database/DbWriter.cs`, `src/CodeIndex/Cli/IndexCommandRunner.cs`, `src/CodeIndex/Cli/ConsoleUi.cs`, `src/CodeIndex/Mcp/McpToolDefinitions.cs`, `src/CodeIndex/Mcp/McpToolHandlers.cs`, `tests/CodeIndex.Tests/NameFoldTests.cs`, `tests/CodeIndex.Tests/DbReaderTests.cs`, `tests/CodeIndex.Tests/IndexCommandRunnerTests.cs`, `tests/CodeIndex.Tests/McpServerTests.cs`, `README.md`. Closes #96.
- **`exact_zero_hint` now uses a 2-phase relaxed probe on exact-miss paths (#100)** — The five exact-match surfaces (`symbols`, `definition`, `references`, `callers`, `callees`) no longer jump straight from a zero-result `--exact` query into a potentially large relaxed sample query bounded only by the user-facing limit. The relaxed hint path now does a bounded existence probe first (`LIMIT 1`), and only if that hits does it run a second bounded sample probe capped at 5 rows. Single-name lookups preserve the existing `relaxed_count` semantics under the caller's `limit`; multi-name `symbols` keeps the cheaper existence+sample path and omits `relaxed_count` rather than re-running the full round-robin merge just to count it. This keeps the additive hint behavior unchanged for callers while avoiding an unnecessary second non-SARGable substring pass when the relaxed query also returns zero, and it prevents large `--limit` values from inflating the hint cost. The same 2-phase logic now applies to the MCP zero-result hint path as well. Added regression coverage for the no-sample-on-miss behavior, the fixed 5-row cap, the preserved single-name count semantics, and the multi-name `symbols` cheap path. Affected: `src/CodeIndex/Cli/QueryCommandRunner.cs`, `src/CodeIndex/Database/DbReader.cs`, `src/CodeIndex/Database/DbSymbolReader.cs`, `src/CodeIndex/Mcp/McpToolHandlers.cs`, `src/CodeIndex/Models/QueryResults.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `tests/CodeIndex.Tests/McpServerTests.cs`. Closes #100.
- **Read-only legacy `--exact` graph queries now surface degraded index signals (#89)** — `references`, `callers`, `callees`, `inspect --exact`, and MCP `analyze_symbol` now detect when a legacy DB is missing the supporting fallback exact-match graph indexes while running in the degraded read path. Human-readable CLI output prints a WARN + reindex hint, JSON/MCP responses expose `exact_index_available` / `degraded_reason`, and bundled symbol analysis carries the same signal so AI clients can distinguish "correct but potentially slow" exact queries from normal indexed lookups. Affected: `src/CodeIndex/Database/DbReader.cs`, `src/CodeIndex/Database/DbSymbolReader.cs`, `src/CodeIndex/Cli/QueryCommandRunner.cs`, `src/CodeIndex/Mcp/McpServer.cs`, `src/CodeIndex/Mcp/McpToolHandlers.cs`, `src/CodeIndex/Models/QueryResults.cs`, `tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `tests/CodeIndex.Tests/McpServerTests.cs`, `README.md`. Closes #89.
Expand Down Expand Up @@ -625,6 +626,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **`--exact` の順位付けで完全一致 casing を先頭化** — `ApiClient` と `apiClient` のように folded exact-match key が同一になる複数行がある場合でも、`symbols` / `definition` / `references` / `callers` / `callees` は従来どおり大文字小文字を無視した完全一致の全行を返しつつ、呼び出し側の入力 casing と完全一致する行を path 順より優先して先頭に並べるようになった。fold sibling を隠さずに、AI の後続クエリで意図したシンボルを rank 0 に戻す。シンボル検索とグラフ reader の両方に回帰テストを追加。対象: `src/CodeIndex/Database/DbSymbolReader.cs`、`src/CodeIndex/Database/DbReader.cs`、`tests/CodeIndex.Tests/DbReaderTests.cs`。

#### 修正
- **C# named-argument label が `symbols` / `deps` でローカル関数扱いされなくなった (#106)** — C# の explicit-interface implementation pattern を締め、`isWindows: OperatingSystem.IsWindows()` のような call-site 行を偽のローカル関数定義として解釈しないようにした。これにより `symbols "IsWindows"` が named-argument label を返したり、`deps` がそのファイルへ bogus な file-level edge を張ったりする downstream false positive が解消される。Praxis の再現形に対する extractor-level と DbReader integration の回帰テストも追加。対象: `src/CodeIndex/Indexer/SymbolExtractor.cs`、`tests/CodeIndex.Tests/SymbolExtractorTests.cs`、`tests/CodeIndex.Tests/DbReaderTests.cs`。#106 をクローズ。
- **`--exact` のシンボル名 / グラフ名マッチを true Unicode CaseFold に更新 (#96)** — `NameFold` が NFKC 正規化の後に `ToLowerInvariant()` で止まらず、full Unicode CaseFold を適用するようになった。これにより sharp-S(`Straße` / `STRASSE`)、Greek final sigma(`Σ` / `ς` / `σ`)、Cherokee など、invariant lowercase ではまだ取りこぼしていた非 ASCII の exact-match 差分を解消する。永続 fold key の version は 1 から 2 に bump され、DB に current-version の fold key だけが残るまで既存 index は自動で ASCII `COLLATE NOCASE` に降格するため、mixed-version の silent miss を防げる。通常の non-`--rebuild` scan は incremental のままで unchanged row を skip するため、CLI / MCP の indexer は旧 fold-key version の unchanged row が残っている間だけ `fold_ready=false` を維持し、full scan が旧 row をすべて rewrite / purge した場合は `--rebuild` を強制せず安全に再 stamp する。direct fold テストに加え、sharp-S / sigma と mixed-version upgrade path の両方向を固定する CLI / MCP 回帰テストも追加。Unicode の locale-invariant 挙動は維持するため、トルコ語の `İ` は引き続き plain `i` ではなく `i\u0307` に fold される。locale-sensitive な Turkish matching は別 follow-up とする。対象: `src/CodeIndex/Database/NameFold.cs`、`src/CodeIndex/Database/DbReader.cs`、`src/CodeIndex/Database/DbWriter.cs`、`src/CodeIndex/Cli/IndexCommandRunner.cs`、`src/CodeIndex/Cli/ConsoleUi.cs`、`src/CodeIndex/Mcp/McpToolDefinitions.cs`、`src/CodeIndex/Mcp/McpToolHandlers.cs`、`tests/CodeIndex.Tests/NameFoldTests.cs`、`tests/CodeIndex.Tests/DbReaderTests.cs`、`tests/CodeIndex.Tests/IndexCommandRunnerTests.cs`、`tests/CodeIndex.Tests/McpServerTests.cs`、`README.md`。Closes #96。
- **`exact_zero_hint` が exact miss 時に 2 段階の緩和 probe を使うよう修正 (#100)** — `symbols` / `definition` / `references` / `callers` / `callees` の 5 つの exact-match 系は、`--exact` が 0 件になった直後にユーザー指定 limit までの緩和サンプルをそのまま引きに行くのではなく、まず `LIMIT 1` の existence probe を実行し、そこでヒットしたときだけ 2 本目の sample probe を最大 5 行で走らせるようになった。single-name の lookup では従来どおり caller の `limit` を反映した `relaxed_count` を維持しつつ、複数名 `symbols` だけは full の round-robin merge を数え直さず、cheap な existence+sample 経路を優先して `relaxed_count` を省略する。これにより、呼び出し側から見える additive な hint は保ちつつ、緩和側も 0 件のケースで不要な 2 回目の非 SARGable substring 走査を避けられ、大きな `--limit` 値が hint コストを膨らませることもなくなる。同じ 2 段階化は MCP の zero-result hint 経路にも適用。sample probe 非実行、5 行 cap、single-name の count 維持、複数名 `symbols` の cheap path を検証する回帰テストも追加した。対象: `src/CodeIndex/Cli/QueryCommandRunner.cs`、`src/CodeIndex/Database/DbReader.cs`、`src/CodeIndex/Database/DbSymbolReader.cs`、`src/CodeIndex/Mcp/McpToolHandlers.cs`、`src/CodeIndex/Models/QueryResults.cs`、`tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`、`tests/CodeIndex.Tests/McpServerTests.cs`。Closes #100.
- **read-only な旧DBでの `--exact` graph クエリが縮退 index シグナルを返すよう改善 (#89)** — `references`、`callers`、`callees`、`inspect --exact`、MCP `analyze_symbol` は、縮退 read path 上で fallback 用 exact-match graph index が欠けた旧DBを検出すると、人間向けCLIで WARN と再インデックスヒントを表示し、JSON/MCP では `exact_index_available` / `degraded_reason` を返すようになりました。bundle された symbol analysis も同じシグナルを持つため、AIクライアントは「結果は正しいが遅い可能性がある」exact クエリと通常の index 済み exact lookup を区別できます。対象: `src/CodeIndex/Database/DbReader.cs`, `src/CodeIndex/Database/DbSymbolReader.cs`, `src/CodeIndex/Cli/QueryCommandRunner.cs`, `src/CodeIndex/Mcp/McpServer.cs`, `src/CodeIndex/Mcp/McpToolHandlers.cs`, `src/CodeIndex/Models/QueryResults.cs`, `tests/CodeIndex.Tests/LegacySchemaMigrationTests.cs`, `tests/CodeIndex.Tests/QueryCommandRunnerTests.cs`, `tests/CodeIndex.Tests/McpServerTests.cs`, `README.md`。#89 をクローズ。
Expand Down
4 changes: 2 additions & 2 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ Supported symbol kinds by language (32 languages with symbol extraction):
| Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes |
| JavaScript | function, arrow, methods | class | -- | -- | -- | -- | -- | import...from | yes |
| TypeScript | function, arrow, methods | class, type | -- | interface | enum, const enum | -- | -- | import...from | yes |
| C# | methods, ctors, operators, indexers, const, static readonly, enum members, #region, finalizers | class, record | struct, record struct, ref struct | interface | enum | property, expression-bodied | event, delegate | using, using alias | yes |
| C# | methods, ctors, explicit-interface impls (guards named-argument labels), operators, indexers, const, static readonly, enum members, #region, finalizers | class, record | struct, record struct, ref struct | interface | enum | property, expression-bodied | event, delegate | using, using alias | yes |
| Go | func, methods | type alias | struct | interface | -- | -- | -- | import | yes |
| Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes |
| Java | methods, static final, enum members | class, record, sealed, @interface | -- | interface | enum | -- | -- | import | yes |
Expand Down Expand Up @@ -1286,7 +1286,7 @@ LIMIT 20;
| Python | def, async def | class | -- | -- | -- | @property | -- | from/import | yes |
| JavaScript | function, アロー, メソッド | class | -- | -- | -- | -- | -- | import...from | yes |
| TypeScript | function, アロー, メソッド | class, type | -- | interface | enum, const enum | -- | -- | import...from | yes |
| C# | メソッド, コンストラクタ, 演算子, インデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record | struct, record struct, ref struct | interface | enum | property, 式本体 | event, delegate | using, using alias | yes |
| C# | メソッド, コンストラクタ, explicit-interface 実装(named-argument label は除外), 演算子, インデクサ, const, static readonly, enum メンバー, #region, ファイナライザ | class, record | struct, record struct, ref struct | interface | enum | property, 式本体 | event, delegate | using, using alias | yes |
| Go | func, メソッド | 型エイリアス | struct | interface | -- | -- | -- | import | yes |
| Rust | fn, macro_rules!, const, static | impl, type alias | struct, union | trait | enum | -- | -- | use | yes |
| Java | メソッド, static final, enum メンバー | class, record, sealed, @interface | -- | interface | enum | -- | -- | import | yes |
Expand Down
4 changes: 3 additions & 1 deletion src/CodeIndex/Indexer/SymbolExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,11 @@ private sealed record SymbolPattern(
new("event", new Regex(@"^\s*(?:(?<visibility>public|private|protected\s+internal|private\s+protected|protected|internal)\s+)?(?:(?:static)\s+)?event\s+\S+\s+(?<name>\w+)", RegexOptions.Compiled), BodyStyle.None, "visibility"),
// Explicit interface implementation (e.g. void IDisposable.Dispose())
// Requires a valid return type (not a statement keyword) and interface name before the dot.
// Also reject named-argument labels like `isWindows: OperatingSystem.IsWindows()`.
// 明示的インターフェース実装 (例: void IDisposable.Dispose())
// 有効な戻り値型(ステートメントキーワードではない)とドット前のインターフェース名を要求。
new("function", new Regex(@"^\s*(?!(?:await|return|throw|yield|var|typeof|sizeof|nameof|default|if|for|foreach|while|switch|catch|lock|using|case|else|when|break|continue|goto)\b)(?<returnType>(?:global::)?[\w?.<>\[\],:]+)\s+[\w.<>]+\.(?<name>\w+)\s*(?:<[^>]+>\s*)?[\(\[]", RegexOptions.Compiled), BodyStyle.Brace, ReturnTypeGroup: "returnType"),
// `isWindows: OperatingSystem.IsWindows()` のような named-argument label も除外する。
new("function", new Regex(@"^\s*(?!(?:await|return|throw|yield|var|typeof|sizeof|nameof|default|if|for|foreach|while|switch|catch|lock|using|case|else|when|break|continue|goto)\b)(?!\w+\s*:)(?<returnType>(?:global::)?[\w?.<>\[\],:]+)\s+[\w.<>]+\.(?<name>\w+)\s*(?:<[^>]+>\s*)?[\(\[]", RegexOptions.Compiled), BodyStyle.Brace, ReturnTypeGroup: "returnType"),
// Indexer (this[...]) / インデクサ (this[...])
new("function", new Regex(@"^\s*(?:(?<visibility>public|private|protected\s+internal|private\s+protected|protected|internal)\s+)?(?:(?:static|virtual|override|abstract|sealed|new)\s+)*(?<returnType>(?:global::)?[\w?.<>\[\],:]+)\s+(?<name>this)\s*\[", RegexOptions.Compiled), BodyStyle.Brace, "visibility", "returnType"),
// Static constructor / 静的コンストラクタ
Expand Down
26 changes: 26 additions & 0 deletions tests/CodeIndex.Tests/DbReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,32 @@ public void SearchSymbols_FindsByName()
Assert.Equal("src/auth.py", results[0].Path);
}

[Fact]
public void SearchSymbols_AndDeps_DoNotTreatNamedArgumentLabelsAsLocalFunctions()
{
InsertIndexedFile("src/platform.cs", "csharp",
"""
public class PlatformState
{
public static bool Detect() =>
new Options(
isWindows: OperatingSystem.IsWindows(),
isMacCatalyst: OperatingSystem.IsMacCatalyst()).Ready;
}
""");
InsertIndexedFile("src/app.cs", "csharp",
"""
public class App
{
public bool Read() => OperatingSystem.IsWindows() || OperatingSystem.IsMacCatalyst();
}
""");

Assert.Empty(_reader.SearchSymbols("IsWindows", lang: "csharp"));
Assert.Empty(_reader.SearchSymbols("IsMacCatalyst", lang: "csharp"));
Assert.Empty(_reader.GetFileDependencies(lang: "csharp"));
}

[Fact]
public void SearchSymbols_ReturnsRichMetadataWhenAvailable()
{
Expand Down
26 changes: 26 additions & 0 deletions tests/CodeIndex.Tests/SymbolExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,32 @@ public void Extract_CSharp_DoesNotMatchQualifiedCallSitesAsDefinitions()
Assert.DoesNotContain(symbols, s => s.Name == "CreateException");
}

[Fact]
public void Extract_CSharp_DoesNotMatchNamedArgumentFrameworkCallsAsDefinitions()
{
// Named-argument labels preceding qualified framework calls must not look like explicit interface impls.
// 修飾付き framework call の前にある named-argument label を明示的インターフェース実装と誤認しないこと。
var content = """
public class PlatformState
{
public PlatformState(bool isWindows, bool isMacCatalyst)
{
}

public static PlatformState Detect() =>
new(
isWindows: OperatingSystem.IsWindows(),
isMacCatalyst: OperatingSystem.IsMacCatalyst());
}
""";
var symbols = SymbolExtractor.Extract(1, "csharp", content);

Assert.Contains(symbols, s => s.Kind == "class" && s.Name == "PlatformState");
Assert.Contains(symbols, s => s.Kind == "function" && s.Name == "Detect");
Assert.DoesNotContain(symbols, s => s.Name == "IsWindows");
Assert.DoesNotContain(symbols, s => s.Name == "IsMacCatalyst");
}

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