Skip to content

Commit 82e3663

Browse files
committed
Resolve final search focus review findings (#4618)
1 parent 4c63a03 commit 82e3663

7 files changed

Lines changed: 89 additions & 22 deletions

File tree

DEVELOPER_GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,7 +1324,7 @@ Exact-match flag compatibility is documented in [USER_GUIDE.md](USER_GUIDE.md#fl
13241324

13251325
`search --json` and MCP `search` project full chunks into compact match-centered snippets with `chunk_start_line`, `chunk_end_line`, `snippet_start_line`, `snippet_end_line`, `snippet`, `match_lines`, `highlights`, `context_before`, `context_after`, `truncated_line_count`, `dropped_match_line_count`, and `truncation_context`. Compact CLI rows and MCP search results also echo effective output options with `snippet_lines` / `snippetLines`, `max_line_width` / `maxLineWidth`, `exact`, `raw_fts` / `rawFts`, `literal_highlights_available` / `literalHighlightsAvailable`, and optional `literal_highlight_warning` / `literalHighlightWarning`. `--snippet-lines` caps the snippet length up front (default: 8, max: 20), and `--max-line-width` (CLI) / `maxLineWidth` (MCP) clamps each individual snippet line around the first match token via the shared `LineWidthFormatter.ClampLine` contract used by `find` / `references` / `excerpt` / `inspect` (default: 512, max: 4096) so a single match inside a minified / transpiled / generated single-line file no longer returns hundreds of KB per hit. Clamped lines surface `...(+N)...` markers inside the snippet and expose `truncation_context.char_counts`, `truncation_context.total_chars`, `highlights[].truncated`, `highlights[].original_line_length`, and `highlights[].truncated_char_counts` so AI clients can detect clamping and quantify omitted characters. `highlights[].terms` remains a distinct term list for compatibility; `highlights[].term_occurrences` records every matched occurrence with `term`, 1-based `line`, 1-based `column`, `length`, plus `visible`, `visible_column`, and `visible_length` for the portion still present in the returned snippet text after line clamping. Exact substring search also adds `highlights[].literal_terms` and `highlights[].literal_term_occurrences` (camelCase in MCP) so clients can render only the requested literal phrase while preserving the broader diagnostic token list; raw FTS rows set `literal_highlight_warning` / `literalHighlightWarning` to `literal_highlights_unavailable_raw_fts` because FTS syntax can no longer be mapped to one literal phrase. Non-exact punctuation-heavy code-phrase searches add `exact_substring_hint` to CLI JSON compact results and `recovery_hint` to MCP `search` responses so clients can retry with exact substring semantics when FTS tokenization is likely to hide punctuation. `focus_mode`, `focus_line`, `focus_column`, and `focus_reason` describe the match window selected for the snippet, while `dropped_match_line_count` and optional `next_match` report match lines omitted because they fell outside that selected snippet window.
13261326

1327-
Default `quality` snippet focus treats a single query that begins with a letter or underscore and otherwise contains only letters, digits, and underscores as identifier-shaped. When a result mixes matching code with earlier comments or strings, the first `code`-origin occurrence supplies both the preferred snippet line and column, including when a literal and executable occurrence share one long line; space-delimited phrase queries and explicit `leftmost` / `proximity` focus modes retain their existing selection. The selected occurrence remains auditable through the existing focus and origin metadata, `dropped_match_line_count` is computed from the final returned window, and `next_match` continues forward from that window.
1327+
Default `quality` snippet focus treats a single query that begins with a letter or underscore and otherwise contains only letters, digits, and underscores as identifier-shaped. When a result mixes matching code with earlier comments or strings, the first `code`-origin occurrence supplies both the preferred snippet line and column, including when a literal and executable occurrence share one long line; automatic occurrence focus scans the complete valid chunk even when its text exceeds the bounded line-only preferred-focus probe. Space-delimited phrase queries and explicit `leftmost` / `proximity` focus modes retain their existing selection. Explicit origin filters refocus on the first retained facet's line and column so focus metadata, visibility, and line clamping describe the filtered result. The selected occurrence remains auditable through the existing focus and origin metadata, `dropped_match_line_count` is computed from the final returned window, and `next_match` continues forward from that window.
13281328

13291329
When the match line falls inside an indexed symbol range, `search --json` and MCP `search` also include optional `enclosing_symbol_name`, `enclosing_symbol_kind`, `enclosing_symbol_start_line`, `enclosing_symbol_end_line`, and `enclosing_container_name`.
13301330

@@ -4193,7 +4193,7 @@ literal-safe な `search` query は reader 層で FTS5 sanitization 前に 1000
41934193

41944194
`search --json` と MCP の `search` は、フルチャンクを `chunk_start_line`、`chunk_end_line`、`snippet_start_line`、`snippet_end_line`、`snippet`、`match_lines`、`highlights`、`context_before`、`context_after`、`truncated_line_count`、`dropped_match_line_count`、`truncation_context` を持つ軽量スニペットへ投影します。compact CLI row と MCP search result は有効な出力オプションも `snippet_lines` / `snippetLines`、`max_line_width` / `maxLineWidth`、`exact`、`raw_fts` / `rawFts`、`literal_highlights_available` / `literalHighlightsAvailable`、任意の `literal_highlight_warning` / `literalHighlightWarning` として返します。`--snippet-lines` で抜粋長を先に制限でき(デフォルト: 8、最大: 20)、`--max-line-width`(CLI)/ `maxLineWidth`(MCP)は `find` / `references` / `excerpt` / `inspect` と同じ共有 `LineWidthFormatter.ClampLine` 契約(デフォルト: 512、最大: 4096、`0` で切り詰め解除)で各スニペット行を最初のマッチトークン周辺にクランプするため、minified / transpiled / 生成された 1 行ファイル内の 1 ヒットで数百 KB を返さなくなります。クランプされた行はスニペットに `...(+N)...` マーカーが入り、`truncation_context.char_counts`、`truncation_context.total_chars`、`highlights[].truncated`、`highlights[].original_line_length`、`highlights[].truncated_char_counts` で AI クライアントがクランプの有無と省略文字数を検出できます。`highlights[].terms` は互換性のため distinct な term list のまま残し、`highlights[].term_occurrences` は一致ごとの `term`、1-based の `line` / `column`、`length` に加えて、行クランプ後に返却 snippet text 内へ残っている部分を示す `visible`、`visible_column`、`visible_length` を記録します。exact substring search では `highlights[].literal_terms` と `highlights[].literal_term_occurrences`(MCP では camelCase)も追加され、広めの診断 token list を残したまま、要求された literal phrase だけを render できます。raw FTS row は FTS 構文を単一の literal phrase へ対応付けられないため、`literal_highlight_warning` / `literalHighlightWarning` に `literal_highlights_unavailable_raw_fts` を設定します。exact ではない記号の多い code phrase 検索では、FTS tokenization が記号を失いやすい場合に exact substring semantics で再検索できるよう、CLI JSON compact result に `exact_substring_hint`、MCP `search` に `recovery_hint` を追加します。`focus_mode`、`focus_line`、`focus_column`、`focus_reason` は snippet に選ばれた match window を説明し、`dropped_match_line_count` と任意の `next_match` は選択された snippet window 外に落ちた一致行を示します。
41954195

4196-
既定の `quality` snippet focus は、先頭が文字または underscore で、残りが文字、数字、underscore だけで構成される単一 query を identifier 形状として扱います。一つの result に一致する code と、それより前の comment / string が混在する場合は、最初の `code` origin occurrence の行と列を snippet の優先位置にします。同じ長い行に literal と実行 code の occurrence がある場合も code 側を選びます。空白区切りの phrase query と、明示的な `leftmost` / `proximity` focus mode は従来の選択を維持します。選択位置は既存の focus / origin metadata で監査でき、`dropped_match_line_count` は最終的に返す window から計算し、`next_match` はその window の先を引き続き示します。
4196+
既定の `quality` snippet focus は、先頭が文字または underscore で、残りが文字、数字、underscore だけで構成される単一 query を identifier 形状として扱います。一つの result に一致する code と、それより前の comment / string が混在する場合は、最初の `code` origin occurrence の行と列を snippet の優先位置にします。同じ長い行に literal と実行 code の occurrence がある場合も code 側を選び、自動 occurrence focus は text が行だけを指定する preferred-focus probe の上限を超える有効な chunk でも chunk 全体を走査します。空白区切りの phrase query と、明示的な `leftmost` / `proximity` focus mode は従来の選択を維持します。明示的な origin filter がある場合は、最初に残る facet の行と列へ再 focus し、focus metadata、visibility、行 clamping を filter 後の result と一致させます。選択位置は既存の focus / origin metadata で監査でき、`dropped_match_line_count` は最終的に返す window から計算し、`next_match` はその window の先を引き続き示します。
41974197

41984198
マッチ行がインデックス済みシンボル範囲内にある場合、`search --json` と MCP の `search` は任意フィールドの `enclosing_symbol_name`、`enclosing_symbol_kind`、`enclosing_symbol_start_line`、`enclosing_symbol_end_line`、`enclosing_container_name` も返します。
41994199

TESTING_GUIDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Use `docs/test-doc-maintenance-plan.md` before moving oversized suites or adding
5353

5454
- `ChunkSplitterTests.cs`, `SymbolExtractorTests.cs`, `ReferenceExtractorTests.cs`, `SearchSnippetFormatterTests.cs`, `DbPathResolverTests.cs`, `ExcerptRecoveryCommandFormatterTests.cs`, `ConsoleUiTests.cs`
5555
Pure or mostly pure behavior tests with in-memory inputs.
56-
Search snippet origin-priority coverage keeps PascalCase, snake_case, and phrase queries in one mixed comment/string/code fixture so identifier focus, same-line code-column clamping, final-window dropped counts, and the phrase control share one contract fixture.
56+
Search snippet origin-priority coverage keeps PascalCase, snake_case, and phrase queries in coordinated mixed comment/string/code fixtures so identifier focus, same-line code-column clamping, over-1-MiB valid chunks, final-window dropped counts, filtered-origin refocusing, and the phrase control share one contract.
5757
Recovery-command coverage asserts structured argv, current `dotnet`/apphost prefix preservation, replay of option-like paths, and display-only escaping for both POSIX sh and PowerShell, including spaces, quotes, dollar signs, and shell metacharacters.
5858
Console writer synchronization coverage yields between character writes instead of sleeping per character; use enough whole-line iterations to expose interleaving without adding wall-clock delay.
5959
- `SymbolExtractor*Tests.cs` and `ReferenceExtractor*Tests.cs`
@@ -757,7 +757,7 @@ dotnet test --filter "FullyQualifiedName~GitHelperTests"
757757

758758
- `ChunkSplitterTests.cs``SymbolExtractorTests.cs``ReferenceExtractorTests.cs``SearchSnippetFormatterTests.cs``DbPathResolverTests.cs``ExcerptRecoveryCommandFormatterTests.cs``ConsoleUiTests.cs`
759759
インメモリ入力中心の、純粋またはほぼ純粋な振る舞いのテスト。
760-
search snippet の origin 優先順位 coverage は PascalCase、snake_case、phrase query を単一の comment / string / code 混在 fixture にまとめ、identifier focus、同一行の code 列への clamping、最終 window の dropped count、phrase の control を一つの contract fixture で検証します
760+
search snippet の origin 優先順位 coverage は PascalCase、snake_case、phrase query を連携した comment / string / code 混在 fixture にまとめ、identifier focus、同一行の code 列への clamping、1 MiB を超える有効 chunk、最終 window の dropped count、filter 後 origin への再 focus、phrase の control を一つの contract として検証します
761761
recovery command の coverage では構造化 argv、現在の `dotnet` / apphost prefix の維持、option と紛らわしい path の再実行、POSIX sh と PowerShell 双方の表示専用 escaping を、空白、quote、dollar sign、shell metacharacter を含めて検証します。
762762
console writer synchronization coverageは文字writeごとのsleepではなくyieldを使い、wall-clock delayを追加せずinterleavingを露出できる十分なwhole-line iterationを維持してください。
763763
- `SymbolExtractor*Tests.cs``ReferenceExtractor*Tests.cs`

changelog.d/unreleased/4618.fixed.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ issues:
44
- 4618
55
affected:
66
- src/CodeIndex/Cli/SearchSnippetFormatter.cs
7+
- src/CodeIndex/Cli/QueryCommandRunner.SearchResults.cs
78
- tests/CodeIndex.Tests/SearchSnippetFormatterTests.cs
9+
- tests/CodeIndex.Tests/QueryCommandRunnerSearchTests.cs
810
- DEVELOPER_GUIDE.md
911
- TESTING_GUIDE.md
1012
---
1113

1214
## English
1315

14-
- **Identifier searches now focus executable code ahead of comments and strings (#4618)** — default `quality` snippets prefer the first `code`-origin line and column for PascalCase, snake_case, and other identifier-shaped queries, including mixed-origin long lines, while preserving phrase-query behavior and accurate dropped-match navigation.
16+
- **Identifier searches now focus executable code ahead of comments and strings (#4618)** — default `quality` snippets prefer the first `code`-origin line and column for PascalCase, snake_case, and other identifier-shaped queries, including mixed-origin long lines and valid chunks over 1 MiB, while origin-filtered searches refocus on the retained occurrence and preserve phrase-query behavior and accurate dropped-match navigation.
1517

1618
## 日本語
1719

18-
- **識別子検索は comment や string より実行 code を先に focus するようになりました (#4618)** — 既定の `quality` snippet は PascalCase、snake_case、その他の identifier 形状 query で最初の `code` origin の行と列を優先し、複数 origin が混在する長い行にも対応しつつ、phrase query の動作と正確な dropped-match navigation を維持します。
20+
- **識別子検索は comment や string より実行 code を先に focus するようになりました (#4618)** — 既定の `quality` snippet は PascalCase、snake_case、その他の identifier 形状 query で最初の `code` origin の行と列を優先し、複数 origin が混在する長い行や 1 MiB を超える有効 chunk にも対応します。origin filter 付き検索は残った occurrence へ再 focus し、phrase query の動作と正確な dropped-match navigation を維持します。

src/CodeIndex/Cli/QueryCommandRunner.SearchResults.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -896,8 +896,10 @@ private static List<SearchDisplayRow> BuildSearchDisplayRows(
896896
result.Lang,
897897
options.SnippetFocus,
898898
exposeLiteralHighlights: exact);
899-
var preferredOriginFilterLine = GetPreferredSearchOriginFilterLine(compact, facetFilters);
900-
if (preferredOriginFilterLine.HasValue && !IsLineWithinSnippet(compact, preferredOriginFilterLine.Value))
899+
var preferredOriginFilterMatch = GetPreferredSearchOriginFilterMatch(compact, facetFilters);
900+
if (preferredOriginFilterMatch != null
901+
&& (compact.FocusLine != preferredOriginFilterMatch.Line
902+
|| compact.FocusColumn != preferredOriginFilterMatch.Column))
901903
{
902904
compact = SearchSnippetFormatter.ToCompactResult(
903905
result,
@@ -908,7 +910,9 @@ private static List<SearchDisplayRow> BuildSearchDisplayRows(
908910
result.Lang,
909911
options.SnippetFocus,
910912
exposeLiteralHighlights: exact,
911-
preferredMatchLine: preferredOriginFilterLine.Value);
913+
preferredMatchLine: preferredOriginFilterMatch.Line,
914+
preferredMatchColumn: preferredOriginFilterMatch.Column,
915+
preferredMatchLength: preferredOriginFilterMatch.Length);
912916
}
913917
SearchSnippetFormatter.ApplyOutputMetadata(compact, options.SnippetLines, options.MaxLineWidth, exact, rawFts);
914918

@@ -1012,21 +1016,18 @@ private static List<string> CombineExclusiveSearchFilters(IReadOnlyList<string>
10121016
.Distinct(StringComparer.Ordinal)
10131017
.ToList();
10141018

1015-
private static int? GetPreferredSearchOriginFilterLine(CompactSearchResult compact, SearchDisplayFacetFilters filters)
1019+
private static SearchMatchFacet? GetPreferredSearchOriginFilterMatch(CompactSearchResult compact, SearchDisplayFacetFilters filters)
10161020
{
10171021
if (!HasSearchOriginFilters(filters) || compact.MatchFacets.Count == 0)
10181022
return null;
10191023

10201024
return compact.MatchFacets
10211025
.Where(facet => !IsSearchFacetExcluded(facet, filters))
1022-
.Select(facet => (int?)facet.Line)
1023-
.OrderBy(line => line)
1026+
.OrderBy(facet => facet.Line)
1027+
.ThenBy(facet => facet.Column)
10241028
.FirstOrDefault();
10251029
}
10261030

1027-
private static bool IsLineWithinSnippet(CompactSearchResult compact, int line)
1028-
=> line >= compact.SnippetStartLine && line <= compact.SnippetEndLine;
1029-
10301031
private static List<SearchDisplayRow> ReadSearchDisplayRows(DbReader reader, QueryCommandOptions options, bool exact)
10311032
{
10321033
if (!HasSearchOriginFilters(options))

0 commit comments

Comments
 (0)