概要
search --semantic の結果にスニペット(本文抜粋)が含まれず、見出しのみが返る。セマンティック検索で判断に関連する文書が見つかっても、中身が空では判断を取り出せない。
再現手順
commandindexdev search --semantic "認証の仕組み" --format llm
Note: commandindexdev は開発用バイナリ名。commandindex でも同様に再現可能。
実際の結果
<!-- estimated tokens: ~0 -->
## dev-reports/feature/1/technical-spec.md
### 認証フロー
## data/logs/claude/mycodebranchdesk-feature-331-worktree-2026-02-21.md
### 認証あり時: {"authEnabled":true}
見出しのみ。本文スニペットなし。estimated tokens: ~0。
期待される結果
<!-- estimated tokens: ~500 -->
## dev-reports/feature/1/technical-spec.md
### 認証フロー
トークンベースの認証を採用。サーバー起動時にランダムトークンを生成し、
環境変数CM_AUTH_TOKENで外部から指定も可能。Edge Runtimeのmiddleware.tsで
全APIリクエストを検証する。
## dev-reports/design/issue-96-npm-cli-design-policy.md
### 認証トークンのセキュリティ
CLI側ではCM_AUTH_TOKEN環境変数からトークンを取得し、HTTPヘッダーに付与。
平文保存のリスクはあるが、ローカルネットワーク前提のため許容。
根本原因分析
enrich_with_metadata() (search.rs) はtantivyからbody本文を取得し SemanticSearchResult.body に格納しているが、以下の2つの原因でスニペットが表示されない:
- heading不一致によるfallbackパス: embedding側の
section_heading とtantivy側のheadingが一致しない場合、body: String::new() のfallback結果が使われる(空文字)
- SnippetConfig/LlmFormatOptionsが未接続:
run_semantic_search() に SnippetConfig / LlmFormatOptions パラメータが渡されておらず、format関数に伝播されない
format別の現状
| format |
現状の挙動 |
問題点 |
| human |
truncate_body(&..., 2, 120) ハードコード |
SnippetConfig未参照 |
| llm |
write_body() でbody全文出力 |
bodyが空の場合はestimated tokens: ~0、LlmFormatOptions未適用 |
| json |
bodyフィールドをそのまま出力 |
bodyが空の場合は空文字 |
影響範囲
変更対象ファイル
| ファイル |
変更内容 |
src/main.rs |
run_semantic_search()呼び出し時にsnippet_config/llm_optionsを渡す |
src/cli/search.rs |
run_semantic_search()シグネチャ変更、enrich_with_metadata()のfallback改善 |
src/output/mod.rs |
format_semantic_results()シグネチャにSnippetConfig/LlmFormatOptions追加 |
src/output/human.rs |
format_semantic_human()にSnippetConfig追加、ハードコード値を置換 |
src/output/llm.rs |
format_semantic_llm()にLlmFormatOptions追加、max_body_linesトランケーション適用 |
src/output/json.rs |
bodyフィールドが正しく出力されることを確認 |
tests/output_format.rs |
format_semantic_results()の新シグネチャに合わせたテスト更新 |
影響なし
- CLIレベルの破壊的変更なし(既存オプションがセマンティック検索でも有効になるだけ)
- 外部クレートの追加不要
- BM25検索・ハイブリッド検索のコードパスに変更なし
- パフォーマンスへの影響なし(truncate_bodyは軽量な文字列操作のみ)
影響(ユーザー視点)
#168(issue/before-changeへのスニペット付与)と類似の症状。ただし技術的アプローチは異なる:
セマンティック検索は「キーワードを知らなくても意味で判断を引ける」唯一の手段だが、見つかった結果に中身がなければ判断を取り出せない。
対象バリュー
- 判断再利用: スニペットがあれば、検索結果だけで「過去にどう判断したか」が読める
- 文脈先回り: AIエージェントがsemanticで見つけた文書をfile.readせずに判断理由を把握できる
改善案
run_semantic_search() にSnippetConfig/LlmFormatOptionsを追加: main.rsからsemantic分岐で既存のsnippet_options/llm_optionsを渡す
format_semantic_results() シグネチャ変更: SnippetConfigとLlmFormatOptionsを受け取り、各フォーマッタに伝播
- format関数の修正:
format_semantic_human(): SnippetConfigを引数追加、ハードコード値(2行/120文字)を置換
format_semantic_llm(): LlmFormatOptionsを追加しmax_body_linesによるtruncate_body_for_llm適用
format_semantic_json(): bodyフィールドはそのまま(全文出力)
- heading不一致時のfallbackパス改善:
sections.first() が存在すればそのbodyを使用
--snippet-lines/--snippet-charsオプションをセマンティック検索でも有効にする
受け入れ基準
search --semantic <query> --format llm でヒットセクションのbody本文が出力され、estimated tokensが0でないこと
search --semantic <query> --format human でSnippetConfigに従った行数・文字数でスニペットが表示されること
search --semantic <query> --format json でbodyフィールドに本文が含まれること
--snippet-lines / --snippet-chars オプションがセマンティック検索でも機能すること
- 既存のBM25検索・ハイブリッド検索の動作に影響がないこと
テスト方針
format_semantic_human() にSnippetConfigを渡した場合のユニットテスト
format_semantic_llm() でbody付きSemanticSearchResultが正しく出力されるユニットテスト
format_semantic_llm() でLlmFormatOptions.max_body_linesが適用されるテスト
enrich_with_metadata のheading不一致fallbackテスト
- 既存テスト(tests/output_format.rs)の新シグネチャ対応
- BM25/ハイブリッド検索の既存テストが全パスすること
テスト環境
- commandindex 0.1.0
- embeddingモデル: qllama/bge-m3:q8_0
- CommandMateリポジトリ(2910ファイル、77832セクションembedding済み)
概要
search --semanticの結果にスニペット(本文抜粋)が含まれず、見出しのみが返る。セマンティック検索で判断に関連する文書が見つかっても、中身が空では判断を取り出せない。再現手順
commandindexdev search --semantic "認証の仕組み" --format llm実際の結果
見出しのみ。本文スニペットなし。
estimated tokens: ~0。期待される結果
根本原因分析
enrich_with_metadata()(search.rs) はtantivyからbody本文を取得しSemanticSearchResult.bodyに格納しているが、以下の2つの原因でスニペットが表示されない:section_headingとtantivy側のheadingが一致しない場合、body: String::new()のfallback結果が使われる(空文字)run_semantic_search()にSnippetConfig/LlmFormatOptionsパラメータが渡されておらず、format関数に伝播されないformat別の現状
truncate_body(&..., 2, 120)ハードコードwrite_body()でbody全文出力影響範囲
変更対象ファイル
src/main.rssrc/cli/search.rssrc/output/mod.rssrc/output/human.rssrc/output/llm.rssrc/output/json.rstests/output_format.rs影響なし
影響(ユーザー視点)
#168(issue/before-changeへのスニペット付与)と類似の症状。ただし技術的アプローチは異なる:
snippet_helper.rsのenrich_*_with_snippets()パターンを使用enrich_with_metadata()でbodyは既に取得済みだが、出力段階で活用されていない/bodyが空セマンティック検索は「キーワードを知らなくても意味で判断を引ける」唯一の手段だが、見つかった結果に中身がなければ判断を取り出せない。
対象バリュー
改善案
run_semantic_search()にSnippetConfig/LlmFormatOptionsを追加: main.rsからsemantic分岐で既存のsnippet_options/llm_optionsを渡すformat_semantic_results()シグネチャ変更: SnippetConfigとLlmFormatOptionsを受け取り、各フォーマッタに伝播format_semantic_human(): SnippetConfigを引数追加、ハードコード値(2行/120文字)を置換format_semantic_llm(): LlmFormatOptionsを追加しmax_body_linesによるtruncate_body_for_llm適用format_semantic_json(): bodyフィールドはそのまま(全文出力)sections.first()が存在すればそのbodyを使用--snippet-lines/--snippet-charsオプションをセマンティック検索でも有効にする受け入れ基準
search --semantic <query> --format llmでヒットセクションのbody本文が出力され、estimated tokensが0でないことsearch --semantic <query> --format humanでSnippetConfigに従った行数・文字数でスニペットが表示されることsearch --semantic <query> --format jsonでbodyフィールドに本文が含まれること--snippet-lines/--snippet-charsオプションがセマンティック検索でも機能することテスト方針
format_semantic_human()にSnippetConfigを渡した場合のユニットテストformat_semantic_llm()でbody付きSemanticSearchResultが正しく出力されるユニットテストformat_semantic_llm()でLlmFormatOptions.max_body_linesが適用されるテストenrich_with_metadataのheading不一致fallbackテストテスト環境