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
8 changes: 6 additions & 2 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,9 @@ Exact-match flag compatibility is documented in [USER_GUIDE.md](USER_GUIDE.md#fl

`search`, `definition`, `references`, `callers`, `callees`, `symbols`, and `files` also share path-aware narrowing via `--path`, repeatable `--exclude-path`, and `--exclude-tests`. The read layer ranks source files ahead of tests and docs, and `search` further boosts exact symbol-name and path matches so AI clients are more likely to land on implementation files first.

`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`, and `truncation_context`. `--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.
`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`. `--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`, and `length`. `dropped_match_line_count` reports match lines omitted because they fell outside the selected snippet window.

`excerpt --json` includes `semantic_tokens`, a lightweight range list with 1-based start/end positions, token `type`, and `modifiers`, so IDE and LLM clients can render or post-process excerpt spans without reparsing the raw `content` string.

`inspect` and MCP `analyze_symbol` bundle the primary definition, nearby symbols from the same file, references, callers, callees, file metadata, workspace freshness/git metadata, and graph-support metadata into one response. When those bundled graph sections actually depend on SQL-backed reads, the payload also mirrors `sql_graph_contract_ready` / `sql_graph_contract_degraded_reason` (plus the existing camelCase aliases on MCP responses); mixed-language bundles that only return C# / JS / etc. graph rows omit the SQL trust signal entirely. This is intended for symbol-oriented AI workflows that would otherwise need several back-to-back calls. Call graph sections remain language-aware: for unsupported languages, clients can now distinguish "unsupported" from "no hits" via `graphSupported` / `graphSupportReason`, and should prefer `search` instead of assuming graph data will exist.

Expand Down Expand Up @@ -2265,7 +2267,9 @@ exact-match flag の互換性は [USER_GUIDE.md](USER_GUIDE.md#フラグ互換

`search`、`definition`、`references`、`callers`、`callees`、`symbols`、`files` は `--path`、繰り返し指定できる `--exclude-path`、`--exclude-tests` による絞り込みを共有します。読み取り層は tests や docs より source を優先し、`search` はシンボル名やパスがクエリと正確に一致する候補をさらに上位に出して、AIクライアントが実装ファイルへ早く到達できるようにします。

`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`、`truncation_context` を持つ軽量スニペットへ投影します。`--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 クライアントがクランプの有無と省略文字数を検出できます。
`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` を持つ軽量スニペットへ投影します。`--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` を記録します。`dropped_match_line_count` は選択された snippet window 外に落ちた一致行数を示します。

`excerpt --json` は 1-based の開始/終了位置、token `type`、`modifiers` を持つ軽量 range list の `semantic_tokens` を返すため、IDE や LLM クライアントは生の `content` 文字列を再パースせずに抜粋範囲を描画・後処理できます。

`inspect` と MCP の `analyze_symbol` は、主定義、同一ファイル内の近傍シンボル、参照、caller、callee、ファイルメタデータ、さらにワークスペース鮮度/git メタデータと graph 対応メタデータを1レスポンスにまとめます。bundle 内の graph 節が実際に SQL ベースの read に依存する場合だけ、`sql_graph_contract_ready` / `sql_graph_contract_degraded_reason`(MCP では既存の camelCase alias も)も返します。mixed-language bundle で C# / JS などの graph row しか返っていない場合は SQL trust signal を出さないため、無関係なクエリが stale SQL state に引きずられません。複数の連続クエリを避けたい AI ワークフロー向けです。call graph 系の節は言語差分を考慮しており、未対応言語では `graphSupported` / `graphSupportReason` によって「未対応」と「ヒットなし」を区別できます。その場合は `search` を優先して使う前提です。

Expand Down
17 changes: 17 additions & 0 deletions changelog.d/unreleased/1746.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
category: added
issues:
- 1746
affected:
- src/CodeIndex/Cli/QueryCommandRunner.cs
- src/CodeIndex/Models/QueryResults.cs
- tests/CodeIndex.Tests/QueryCommandRunnerTests.cs
---

## English

- **Excerpt JSON now includes semantic token ranges (#1746)** — `excerpt --json` emits `semantic_tokens` with 1-based ranges and token types so IDE and LLM clients can render excerpt spans without reparsing `content`.

## 日本語

- **excerpt JSON が semantic token range を含むようになりました (#1746)** — `excerpt --json` は 1-based range と token type を持つ `semantic_tokens` を返し、IDE や LLM クライアントが `content` を再パースせずに抜粋範囲を描画できるようにします。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/1773.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 1773
affected:
- src/CodeIndex/Cli/SearchSnippetFormatter.cs
- tests/CodeIndex.Tests/SearchSnippetFormatterTests.cs
---

## English

- **Search highlights now include per-occurrence positions (#1773)** — `highlights[].term_occurrences` records every matched term with its line, column, and length while preserving the existing distinct `terms` list.

## 日本語

- **検索 highlight が一致ごとの位置を含むようになりました (#1773)** — 既存の distinct な `terms` list は維持しつつ、`highlights[].term_occurrences` に各一致の line、column、length を記録します。
16 changes: 16 additions & 0 deletions changelog.d/unreleased/1775.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
category: fixed
issues:
- 1775
affected:
- src/CodeIndex/Cli/SearchSnippetFormatter.cs
- tests/CodeIndex.Tests/SearchSnippetFormatterTests.cs
---

## English

- **Search snippets now report omitted match lines (#1775)** — `search --json` exposes `dropped_match_line_count` when additional matching lines fall outside the selected snippet window.

## 日本語

- **検索スニペットが省略された一致行数を返すようになりました (#1775)** — `search --json` は、選択された snippet window 外に追加の一致行がある場合に `dropped_match_line_count` を返します。
2 changes: 2 additions & 0 deletions src/CodeIndex/Cli/JsonOutputContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ internal sealed record VersionInfoJsonResult(
[JsonSerializable(typeof(DiffSummaryOnlyJsonResult))]
[JsonSerializable(typeof(DiffSummaryJsonResult))]
[JsonSerializable(typeof(ExactZeroHintResult))]
[JsonSerializable(typeof(ExcerptSemanticToken))]
[JsonSerializable(typeof(FileDependencyResult))]
[JsonSerializable(typeof(FileExcerptResult))]
[JsonSerializable(typeof(FileFindResult))]
Expand Down Expand Up @@ -337,6 +338,7 @@ internal sealed record VersionInfoJsonResult(
[JsonSerializable(typeof(ReportBundleSummary))]
[JsonSerializable(typeof(SearchHighlight))]
[JsonSerializable(typeof(SearchResult))]
[JsonSerializable(typeof(SearchTermOccurrence))]
[JsonSerializable(typeof(SearchTruncationContext))]
[JsonSerializable(typeof(StatusResult))]
[JsonSerializable(typeof(StatusDbPragmaSettings))]
Expand Down
53 changes: 53 additions & 0 deletions src/CodeIndex/Cli/QueryCommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,8 @@ public static int RunExcerpt(string[] cmdArgs, JsonSerializerOptions jsonOptions
Console.Error.WriteLine("No excerpt found.");
return ZeroResultExitCode(options);
}
if (options.Json)
excerpt.SemanticTokens = BuildExcerptSemanticTokens(excerpt);

if (options.Json)
{
Expand All @@ -1510,6 +1512,57 @@ public static int RunExcerpt(string[] cmdArgs, JsonSerializerOptions jsonOptions
});
}

private static List<ExcerptSemanticToken> BuildExcerptSemanticTokens(FileExcerptResult excerpt)
{
var tokens = new List<ExcerptSemanticToken>();
var lines = excerpt.Content.Replace("\r\n", "\n").Split('\n');
for (var lineIndex = 0; lineIndex < lines.Length; lineIndex++)
{
var line = lines[lineIndex];
var column = 0;
while (column < line.Length)
{
if (!IsSemanticTokenStart(line[column]))
{
column++;
continue;
}

var start = column;
column++;
while (column < line.Length && IsSemanticTokenPart(line[column]))
column++;

var tokenText = line[start..column];
tokens.Add(new ExcerptSemanticToken
{
StartLine = excerpt.StartLine + lineIndex,
StartColumn = start + 1,
EndLine = excerpt.StartLine + lineIndex,
EndColumn = column + 1,
Type = ClassifySemanticToken(tokenText),
});
}
}

return tokens;
}

private static bool IsSemanticTokenStart(char value) =>
char.IsLetter(value) || value == '_' || char.IsDigit(value);

private static bool IsSemanticTokenPart(char value) =>
char.IsLetterOrDigit(value) || value == '_';

private static string ClassifySemanticToken(string token)
{
if (token.All(char.IsDigit))
return "number";
if (char.IsUpper(token[0]))
return "type";
return "variable";
}

public static int RunFind(string[] cmdArgs, JsonSerializerOptions jsonOptions)
{
var preparedFindArgs = PrepareFindArgs(cmdArgs, out var preparationError);
Expand Down
Loading
Loading