diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 66aacaff56..ca6da49209 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -929,6 +929,40 @@ cdidx report --output report.tgz --json | `--include-args` | | Keep literal `cwd=` and `args=` values in the log tail (opt-in; share only with trusted recipients). | | `--json` | | Print a stable summary envelope (`output_path`, `version`, `files`, `schema_tables`, `log_lines_included`, `log_included`, `db_included`, `db_path`) instead of the human-friendly output. | +## Search query syntax + +Default `cdidx search` uses the literal-safe FTS5 path: each whitespace-separated +query token is quoted as a phrase, and multiple tokens are combined with FTS5's +implicit AND semantics. For example, `cdidx search foo bar` means "find chunks +that contain both `foo` and `bar`"; it is equivalent to a raw FTS5 query that +requires both terms. Use `--fts 'foo OR bar'` when either term is acceptable, or +quote a phrase in raw FTS5 mode (`--fts '"foo bar"'`) when adjacency matters. +With `--fts`, the query is passed to FTS5 as raw syntax. Supported operators +include `content:term` column filters, `NEAR(foo bar, 5)`, `foo OR bar`, +`foo NOT bar`, parenthesized groups, prefix tokens such as `foo*`, and quoted +phrases such as `"foo bar"`. Without `--fts`, those characters are treated as +literal query content except for cdidx's documented literal-safe prefix +shorthand. + +Search case behavior depends on the mode. Default search and raw `--fts` use +SQLite FTS5's `unicode61` tokenizer, so ASCII case is folded and Latin +diacritics are removed by the tokenizer. CJK text is mostly case-neutral, but +matching still follows FTS5 token boundaries. Locale-specific Unicode cases are +not a full collation: Turkish dotted/dotless I and German sharp-S versus `SS` +should be checked with exact mode when identity matters. `--exact-substring` +uses SQLite `instr()` and is case-sensitive byte-for-byte over the stored text. +Symbol-name exactness is separate: `--exact-name` uses cdidx's documented NFKC + +Unicode CaseFold path when the DB reports `fold_ready`. + +### Result deduplication + +Search chunks overlap by 10 lines so matches near a chunk boundary still have +context. By default, `search` collapses duplicate hits that come from this +overlap. Use `--no-dedup` only when you need every raw chunk hit, such as +debugging chunk-boundary behavior, comparing directly with the `chunks` table, +or measuring exact raw match density. It can return repeated snippets for the +same source location. + ## Options | Option | Applies to | Description | @@ -987,7 +1021,7 @@ cdidx report --output report.tgz --json | `--watch` | `index` | After the initial scan completes, stay running and reindex incrementally as files change (FileSystemWatcher / inotify / FSEvents). Rejects `--commits`, `--changed-between`, `--files`, and `--dry-run` because the loop already drives continuous incremental updates. | | `--debounce ` | `index` (watch only) | Coalesce bursts of file events into a single update after `` of quiet (non-negative integer; default: 500). Invalid values emit a warning and are ignored. | | `--since ` | `search`, `definition`, `symbols`, `files` | Filter to files modified since this ISO 8601 timestamp. Offsetless values (e.g. `2024-01-01T00:00:00`) are treated as UTC so the same flag resolves to the same instant in every timezone; append `Z` or an explicit offset (`+09:00`) to be explicit. | -| `--no-dedup` | `search` | Disable overlapping-chunk deduplication for raw results | +| `--no-dedup` | `search` | Disable overlapping-chunk deduplication and return every raw chunk hit; useful for debugging chunk boundaries or measuring raw match density | | `--reverse` | `deps` | Reverse lookup: show files that depend ON the matched path | | `--strict-not-found` | Query commands | Return exit code `2` when a valid query produces zero rows. Without this flag, zero-result queries exit `0` and keep their normal empty/zero-result output. | | `--top ` | Query commands | Alias for `--limit` | @@ -2802,6 +2836,41 @@ cdidx report --output report.tgz --json | `--include-args` | | ログ末尾の `cwd=` / `args=` 値を伏字化せずそのまま含めます(信頼できる相手にだけ使用してください)。 | | `--json` | | 人間向け出力の代わりに、安定したサマリ JSON(`output_path` / `version` / `files` / `schema_tables` / `log_lines_included` / `log_included` / `db_included` / `db_path`)を出力します。 | +## 検索クエリ構文 + +既定の `cdidx search` は literal-safe な FTS5 経路を使います。空白で +区切られた各 query token は phrase として引用され、複数 token は FTS5 の +implicit AND として結合されます。たとえば `cdidx search foo bar` は +「`foo` と `bar` の両方を含む chunk」を探す意味で、両方の term を要求する +raw FTS5 query と同等です。どちらか一方でよい場合は `--fts 'foo OR bar'`、 +隣接 phrase を要求したい場合は raw FTS5 mode で引用します +(`--fts '"foo bar"'`)。 +`--fts` 付きでは query は raw FTS5 構文としてそのまま渡されます。利用できる +演算子には `content:term` の列 filter、`NEAR(foo bar, 5)`、`foo OR bar`、 +`foo NOT bar`、括弧 grouping、`foo*` のような prefix token、`"foo bar"` の +ような quoted phrase があります。`--fts` なしでは、cdidx が明示している +literal-safe prefix shorthand を除き、これらの文字はリテラルな query 内容として +扱われます。 + +検索の大小文字の扱いは mode ごとに異なります。既定検索と raw `--fts` は +SQLite FTS5 の `unicode61` tokenizer を使うため、ASCII の大小文字は畳み込まれ、 +ラテン文字の diacritic は tokenizer により除去されます。CJK は多くの場合 +大小文字の概念がありませんが、一致範囲は FTS5 token 境界に従います。Unicode の +locale 固有ケースを完全な collation として扱うわけではないため、トルコ語の +dotted/dotless I やドイツ語 sharp-S と `SS` の同一性が重要な場合は exact mode で +確認してください。`--exact-substring` は SQLite `instr()` を使い、保存された本文に +対して byte-for-byte に大文字小文字を区別します。symbol-name exactness は別経路で、 +`--exact-name` は DB が `fold_ready` のとき cdidx の NFKC + Unicode CaseFold 経路を +使います。 + +### 結果の重複排除 + +検索 chunk は、chunk 境界付近の一致でも文脈を持てるよう 10 行重複しています。 +既定の `search` は、この overlap から生じる重複 hit を折りたたみます。 +`--no-dedup` は、chunk 境界の挙動を調査する、`chunks` table と直接突き合わせる、 +raw match density を正確に測る、といった理由で全 raw chunk hit が必要な場合にだけ +使います。同じ source location の snippet が繰り返し返ることがあります。 + ## オプション一覧 | オプション | 対象 | 説明 | @@ -2858,7 +2927,7 @@ cdidx report --output report.tgz --json | `--watch` | `index` | 初回スキャン完了後もプロセスを残し、ファイル変更を検知して差分更新を繰り返す(FileSystemWatcher / inotify / FSEvents)。連続的な差分更新を内蔵しているため `--commits` / `--changed-between` / `--files` / `--dry-run` との併用は拒否する。 | | `--debounce ` | `index`(`--watch` 専用) | 一連のイベントを `` の静止後に 1 つの更新へ集約する(0 以上の整数。既定: 500)。不正な値は警告を出して無視する。 | | `--since ` | `search`, `definition`, `symbols`, `files` | 指定タイムスタンプ以降に変更されたファイルのみ(ISO 8601)。オフセットなしの値(例: `2024-01-01T00:00:00`)は UTC として解釈されるため、どのタイムゾーンから呼び出しても同じ UTC 時点になります。明示したい場合は末尾に `Z` または `+09:00` 等のオフセットを付与してください。 | -| `--no-dedup` | `search` | オーバーラップチャンク重複排除を無効化 | +| `--no-dedup` | `search` | overlap chunk の重複排除を無効化し、全 raw chunk hit を返す。chunk 境界の debug や raw match density 計測向け | | `--reverse` | `deps` | 逆引き: 指定パスに依存しているファイルを表示 | | `--workspace-db ` | `deps` | file dependency query に別の CodeIndex DB を追加する。複数 member DB を集約する場合は繰り返し指定でき、JSON edge には同じ相対パスを区別できるよう `source_db` / `target_db` が含まれる。 | | `--top ` | クエリ系 | `--limit` のエイリアス | diff --git a/changelog.d/unreleased/1843.docs.md b/changelog.d/unreleased/1843.docs.md new file mode 100644 index 0000000000..b4f4415448 --- /dev/null +++ b/changelog.d/unreleased/1843.docs.md @@ -0,0 +1,16 @@ +--- +category: docs +issues: + - 1843 +affected: + - USER_GUIDE.md + - src/CodeIndex/Mcp/McpToolDefinitions.cs +--- + +## English + +- **Documented implicit-AND search semantics (#1843)** — default `cdidx search` now explains that whitespace-separated terms require all terms to match, with `--fts 'foo OR bar'` and raw quoted phrases documented as alternatives. + +## 日本語 + +- **検索の implicit-AND セマンティクスを明文化しました (#1843)** — 既定の `cdidx search` は空白区切りの term すべてを要求することを説明し、代替として `--fts 'foo OR bar'` と raw quoted phrase を案内するようにしました。 diff --git a/changelog.d/unreleased/1844.docs.md b/changelog.d/unreleased/1844.docs.md new file mode 100644 index 0000000000..2e5ba01abd --- /dev/null +++ b/changelog.d/unreleased/1844.docs.md @@ -0,0 +1,18 @@ +--- +category: docs +issues: + - 1844 +affected: + - USER_GUIDE.md + - src/CodeIndex/Cli/ConsoleUi.cs + - src/CodeIndex/Mcp/McpToolDefinitions.cs + - tests/CodeIndex.Tests/ConsoleUiTests.cs +--- + +## English + +- **Documented raw `--fts` operators (#1844)** — help, USER_GUIDE, and MCP schema text now list column filters, `NEAR`, boolean operators, grouping, prefixes, and quoted phrases exposed by raw FTS5 mode. + +## 日本語 + +- **raw `--fts` の演算子を明文化しました (#1844)** — help、USER_GUIDE、MCP schema の説明に、raw FTS5 mode が公開する列 filter、`NEAR`、boolean operator、grouping、prefix、quoted phrase を列挙しました。 diff --git a/changelog.d/unreleased/1846.docs.md b/changelog.d/unreleased/1846.docs.md new file mode 100644 index 0000000000..71ca8f64f6 --- /dev/null +++ b/changelog.d/unreleased/1846.docs.md @@ -0,0 +1,15 @@ +--- +category: docs +issues: + - 1846 +affected: + - USER_GUIDE.md +--- + +## English + +- **Documented search case-sensitivity by mode (#1846)** — USER_GUIDE now explains FTS5 `unicode61` case and diacritic behavior, exact substring matching via `instr()`, and the separate `--exact-name` fold path. + +## 日本語 + +- **検索 mode ごとの大小文字区別を文書化しました (#1846)** — USER_GUIDE に FTS5 `unicode61` の大小文字・diacritic の扱い、`instr()` による exact substring、別経路の `--exact-name` fold を説明しました。 diff --git a/changelog.d/unreleased/1848.docs.md b/changelog.d/unreleased/1848.docs.md new file mode 100644 index 0000000000..4db7e57714 --- /dev/null +++ b/changelog.d/unreleased/1848.docs.md @@ -0,0 +1,18 @@ +--- +category: docs +issues: + - 1848 +affected: + - USER_GUIDE.md + - src/CodeIndex/Cli/ConsoleUi.cs + - src/CodeIndex/Mcp/McpToolDefinitions.cs + - tests/CodeIndex.Tests/ConsoleUiTests.cs +--- + +## English + +- **Documented `--no-dedup` rationale (#1848)** — USER_GUIDE, CLI help, and MCP schema text now explain that search chunks overlap by 10 lines and `--no-dedup` returns every raw chunk hit for boundary debugging or match-density analysis. + +## 日本語 + +- **`--no-dedup` の意図を文書化しました (#1848)** — USER_GUIDE、CLI help、MCP schema の説明で、検索 chunk が 10 行 overlap し、`--no-dedup` は境界 debug や match density 分析のために全 raw chunk hit を返すことを明記しました。 diff --git a/src/CodeIndex/Cli/ConsoleUi.cs b/src/CodeIndex/Cli/ConsoleUi.cs index a4cd0b082c..d27966865f 100644 --- a/src/CodeIndex/Cli/ConsoleUi.cs +++ b/src/CodeIndex/Cli/ConsoleUi.cs @@ -719,7 +719,7 @@ void WriteHelpLine(string line = "") Console.WriteLine(" --focus-line excerpt: line whose focused column should stay visible (requires --focus-column)"); Console.WriteLine(" --focus-column excerpt: column to keep centered when clamping (must be within the focused line)"); Console.WriteLine(" --focus-length excerpt: width of the focused span (default: 1, requires --focus-column)"); - WriteHelpLine($" --fts Use raw FTS5 query syntax for search (search query max {QueryLimits.MaxQueryLength} chars; raw FTS parser max {DbReader.MaxRawFtsQueryLength} chars, {DbReader.MaxRawFtsBooleanOperators} boolean ops, {DbReader.MaxRawFtsNearOperators} NEAR ops; trailing * is a prefix shorthand in literal-safe mode)"); + WriteHelpLine($" --fts Use raw FTS5 query syntax for search (content:term, NEAR(a b, 5), OR, NOT, groups, prefix*, \"phrase\"; search query max {QueryLimits.MaxQueryLength} chars; raw FTS parser max {DbReader.MaxRawFtsQueryLength} chars, {DbReader.MaxRawFtsBooleanOperators} boolean ops, {DbReader.MaxRawFtsNearOperators} NEAR ops; trailing * is a prefix shorthand in literal-safe mode)"); Console.WriteLine(" --exact Backward-compatible shorthand."); Console.WriteLine(" Prefer --exact-substring for search,"); Console.WriteLine(" --exact for find,"); @@ -737,6 +737,7 @@ void WriteHelpLine(string line = "") WriteHelpLine(" --exclude-visibility Exclude symbols/definitions/unused/hotspots by visibility"); WriteHelpLine(" --count Count only; search/definition/references/callers/callees/symbols/files/find/unused ignore --limit, impact/hotspots still use visible page counts"); Console.WriteLine(" --since Filter to files modified since this timestamp (ISO 8601)"); + Console.WriteLine(" --no-dedup search only: return every raw overlapping chunk hit (debug/density)"); Console.WriteLine(" --bytes Show raw byte counts in human output for files/map instead of binary units; JSON always keeps raw integer bytes"); WriteHelpLine(" --max-hops Max BFS hops for impact analysis, inclusive (default: 5; --max-hops 2 returns callers at hop 1 and 2; --max-hops 0 resolves the symbol without traversing callers)"); Console.WriteLine(" --depth Deprecated alias for --max-hops"); diff --git a/src/CodeIndex/Mcp/McpToolDefinitions.cs b/src/CodeIndex/Mcp/McpToolDefinitions.cs index da399aca27..13af83f623 100644 --- a/src/CodeIndex/Mcp/McpToolDefinitions.cs +++ b/src/CodeIndex/Mcp/McpToolDefinitions.cs @@ -20,7 +20,7 @@ private JsonNode HandleToolsList(JsonNode? id) { CreateToolDefinition( "search", - "Full-text search across indexed code chunks using FTS5. Returns compact match-centered snippets with line metadata. The literal-safe path quotes each whitespace-separated token as an FTS5 phrase and matches only what the user typed — for CJK that means `search 計算` no longer also matches `計算する`/`計算機`, because unicode61 keeps adjacent CJK codepoints in one token. Two opt-ins enable FTS5 prefix expansion: (1) trailing `*` on a single token in the `query` string (`search 計算*` to match `計算する`); (2) the `prefix` flag, which promotes every token in the query to a prefix phrase. Use `exactSubstring` for case-sensitive exact-substring matching that bypasses FTS5 entirely; `exact` is the backward-compatible alias documented in USER_GUIDE.md's flag compatibility table. Non-CJK tokens follow the same rule — ASCII identifiers also no longer auto-prefix, so use `--prefix` or trailing `*` to widen. Emoji-mixed tokens cannot be distinguished from their plain ASCII counterpart at the FTS layer (unicode61 drops the emoji on both index and query side — `foo🎉` is FTS-equivalent to `foo`), and pure emoji substring search is 0-result for the same reason; use `exactSubstring` when emoji identity matters. Examples: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`. / FTS5を使ったコードチャンクの全文検索。一致中心の軽量スニペットと行メタデータを返す。literal-safe 経路は空白区切りの各トークンを FTS5 phrase として引用し、ユーザーが入力したものだけにマッチする。CJK の場合、unicode61 は隣接 CJK コードポイントを一語として扱うため、`search 計算` は `計算する`/`計算機` にはマッチしない。FTS5 prefix への昇格は 2 通りでオプトイン: (1) `query` 文字列内のトークン末尾に `*` を付ける(`search 計算*` で `計算する` にマッチ)。(2) `prefix` フラグで、クエリの全トークンを prefix phrase に昇格させる。`exactSubstring` を使うと FTS5 を経由せず大小文字区別の厳密部分文字列マッチになり、`exact` は USER_GUIDE.md の flag compatibility table に記載された後方互換 alias。CJK 以外(ASCII 識別子等)も同じルールで、自動 prefix は行わないため、広げたい場合は `--prefix` か末尾 `*` を使う。絵文字混在トークンは、unicode61 が indexing とクエリの両側で絵文字を削ぐため FTS 層で素の ASCII トークンと区別できず(`foo🎉` は FTS 上 `foo` と等価)、絵文字単独の部分一致も同じ理由で 0 件になる。絵文字の同一性が必要な場合は `exactSubstring` を使う。例: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`。", + "Full-text search across indexed code chunks using FTS5. Returns compact match-centered snippets with line metadata. The literal-safe path quotes each whitespace-separated token as an FTS5 phrase and combines multiple tokens with implicit AND semantics (`foo bar` requires both terms). For CJK that means `search 計算` no longer also matches `計算する`/`計算機`, because unicode61 keeps adjacent CJK codepoints in one token. Two opt-ins enable FTS5 prefix expansion: (1) trailing `*` on a single token in the `query` string (`search 計算*` to match `計算する`); (2) the `prefix` flag, which promotes every token in the query to a prefix phrase. Use `exactSubstring` for case-sensitive exact-substring matching that bypasses FTS5 entirely; `exact` is the backward-compatible alias documented in USER_GUIDE.md's flag compatibility table. Non-CJK tokens follow the same rule — ASCII identifiers also no longer auto-prefix, so use `--prefix` or trailing `*` to widen. Emoji-mixed tokens cannot be distinguished from their plain ASCII counterpart at the FTS layer (unicode61 drops the emoji on both index and query side — `foo🎉` is FTS-equivalent to `foo`), and pure emoji substring search is 0-result for the same reason; use `exactSubstring` when emoji identity matters. Examples: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`. / FTS5を使ったコードチャンクの全文検索。一致中心の軽量スニペットと行メタデータを返す。literal-safe 経路は空白区切りの各トークンを FTS5 phrase として引用し、複数 token は implicit AND として結合する(`foo bar` は両方の term を要求する)。CJK の場合、unicode61 は隣接 CJK コードポイントを一語として扱うため、`search 計算` は `計算する`/`計算機` にはマッチしない。FTS5 prefix への昇格は 2 通りでオプトイン: (1) `query` 文字列内のトークン末尾に `*` を付ける(`search 計算*` で `計算する` にマッチ)。(2) `prefix` フラグで、クエリの全トークンを prefix phrase に昇格させる。`exactSubstring` を使うと FTS5 を経由せず大小文字区別の厳密部分文字列マッチになり、`exact` は USER_GUIDE.md の flag compatibility table に記載された後方互換 alias。CJK 以外(ASCII 識別子等)も同じルールで、自動 prefix は行わないため、広げたい場合は `--prefix` か末尾 `*` を使う。絵文字混在トークンは、unicode61 が indexing とクエリの両側で絵文字を削ぐため FTS 層で素の ASCII トークンと区別できず(`foo🎉` は FTS 上 `foo` と等価)、絵文字単独の部分一致も同じ理由で 0 件になる。絵文字の同一性が必要な場合は `exactSubstring` を使う。例: `search {\"query\":\"handleRequest\",\"lang\":\"csharp\"}`; `search {\"query\":\"Authenticate\",\"lang\":\"csharp\",\"path\":\"src/Auth\",\"prefix\":true}`。", new JsonObject { ["type"] = "object", @@ -31,13 +31,13 @@ private JsonNode HandleToolsList(JsonNode? id) ["lang"] = new JsonObject { ["type"] = "string", ["description"] = "Filter by language (e.g. csharp, python, javascript)" }, ["snippetLines"] = new JsonObject { ["type"] = "integer", ["description"] = "Max snippet lines per result (default: 8, max: 20)", ["default"] = 8, ["minimum"] = 1, ["maximum"] = SearchSnippetFormatter.MaxSnippetLines }, ["maxLineWidth"] = new JsonObject { ["type"] = "integer", ["description"] = "Clamp very long single-line snippets per line (default: 512; 0 disables clamping). Match lines are clamped around the first match; non-match lines are clamped from the head. Each clamp inserts a `...(+N)...` marker showing how many chars were elided.", ["default"] = LineWidthFormatter.DefaultMaxLineWidth, ["minimum"] = 0, ["maximum"] = LineWidthFormatter.MaxAllowedLineWidth }, - ["rawQuery"] = new JsonObject { ["type"] = "boolean", ["description"] = "Use raw FTS5 syntax instead of literal-safe quoting", ["default"] = false }, + ["rawQuery"] = new JsonObject { ["type"] = "boolean", ["description"] = "Use raw FTS5 syntax instead of literal-safe quoting: content:term, NEAR(a b, 5), OR, NOT, parenthesized groups, prefix*, and quoted phrases.", ["default"] = false }, ["path"] = new JsonObject { ["oneOf"] = new JsonArray { new JsonObject { ["type"] = "string" }, new JsonObject { ["type"] = "array", ["items"] = new JsonObject { ["type"] = "string" } } }, ["description"] = "Prefer or restrict glob-style path patterns. `*` and `?` are wildcards. Accepts a single string or an array; multiple values are OR'd together." }, ["excludePaths"] = new JsonObject { ["type"] = "array", ["items"] = new JsonObject { ["type"] = "string" }, ["description"] = "Exclude glob-style path patterns. `*` and `?` are wildcards." }, ["excludeTests"] = new JsonObject { ["type"] = "boolean", ["description"] = "Exclude likely test files", ["default"] = false }, ["includeGenerated"] = new JsonObject { ["type"] = "boolean", ["description"] = "Include files detected as generated code", ["default"] = false }, ["since"] = new JsonObject { ["type"] = "string", ["description"] = "Filter to files modified since this ISO 8601 timestamp" }, - ["noDedup"] = new JsonObject { ["type"] = "boolean", ["description"] = "Disable overlapping-chunk deduplication for raw results", ["default"] = false }, + ["noDedup"] = new JsonObject { ["type"] = "boolean", ["description"] = "Disable overlapping-chunk deduplication and return every raw chunk hit; useful for debugging chunk boundaries or measuring raw match density.", ["default"] = false }, ["exactSubstring"] = new JsonObject { ["type"] = "boolean", ["description"] = "Preferred explicit name for search's exact mode: case-sensitive exact substring match (bypasses FTS5).", ["default"] = false }, ["exact"] = new JsonObject { ["type"] = "boolean", ["description"] = "Backward-compatible alias for `exactSubstring`.", ["default"] = false }, ["prefix"] = new JsonObject { ["type"] = "boolean", ["description"] = "Opt into FTS5 prefix expansion for every token in `query`. Cannot be combined with `exact`/`exactSubstring`.", ["default"] = false } diff --git a/tests/CodeIndex.Tests/ConsoleUiTests.cs b/tests/CodeIndex.Tests/ConsoleUiTests.cs index 62724d8faa..7ca3e0a0d6 100644 --- a/tests/CodeIndex.Tests/ConsoleUiTests.cs +++ b/tests/CodeIndex.Tests/ConsoleUiTests.cs @@ -77,7 +77,7 @@ public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() Assert.Contains("--snippet-focus search only: long-line focus mode (leftmost|quality|proximity, default: quality)", output); Assert.Contains("--max-line-width search/references/callers/callees/find/excerpt/impact/inspect only: clamp very long single-line snippet/context/excerpt payloads (`0` disables clamping; default: 512)", output); Assert.Contains("cdidx find --path ", output); - Assert.Contains("--fts Use raw FTS5 query syntax for search (search query max 1000 chars; raw FTS parser max 2000 chars, 64 boolean ops, 16 NEAR ops", output); + Assert.Contains("--fts Use raw FTS5 query syntax for search (content:term, NEAR(a b, 5), OR, NOT, groups, prefix*, \"phrase\"; search query max 1000 chars; raw FTS parser max 2000 chars, 64 boolean ops, 16 NEAR ops", output); Assert.Contains("--exact Backward-compatible shorthand.", output); Assert.Contains(" Prefer --exact-substring for search,", output); Assert.Contains(" --exact for find,", output); @@ -92,6 +92,7 @@ public void PrintUsage_WithoutBanner_HidesAsciiArtAndEasterEggFlags() Assert.Contains(" run `cdidx backfill-fold` or check fold_ready.", output); Assert.Contains("--kind definition/symbols/hotspots/unused: symbol kind; references: reference kind (call/instantiate/subscribe/attribute/annotation); callers/callees: call-graph kinds only (call/instantiate/subscribe — metadata kinds rejected, use references instead); validate: issue kind", output); Assert.Contains("--count Count only; search/definition/references/callers/callees/symbols/files/find/unused ignore --limit, impact/hotspots still use visible page counts", output); + Assert.Contains("--no-dedup search only: return every raw overlapping chunk hit (debug/density)", output); Assert.Contains("--commits [id ...] Update only files changed in the specified git commits (preferred after commits)", output); Assert.Contains("--files [path ...] Update only the specified files; old rename/delete paths are not purged unless also listed", output); Assert.Contains("--optimize index only: optimize the existing FTS5 table for this project's DB without scanning files", output);