Skip to content

[BUG] --with-snippet が空文字列を返す(related/impact) #123

@Kewton

Description

@Kewton

再現手順

commandindexdev index --path .
commandindexdev search --related src/components/worktree/TerminalDisplay.tsx --format json --with-snippet --limit 3

期待される動作

各結果の snippet フィールドにファイル本文の先頭部分(デフォルト500文字/10行)が含まれる。
また、file_path フィールドにはインポートパスではなく実ファイルの相対パスが含まれる。

実際の動作

{"path":"@/components/worktree/TerminalSearchBar","relations":["import_dependency"],"score":1.3,"snippet":""}
{"path":"react","relations":["import_dependency"],"score":0.9,"snippet":""}
{"path":"@/hooks/useTerminalSearch","relations":["import_dependency"],"score":0.9,"snippet":""}

全結果で snippet が空文字列 ""--format llm でも同様にスニペットが含まれない。
さらに、path フィールドがインポートパス(@/components/..., react)のまま返され、実ファイルパス(src/components/...)に変換されていない。

根本原因

問題1: related検索結果の file_path がインポートパスのまま

score_import_deps() (related.rs:181) が imp.target_module.clone() をscores HashMapのキーとして使用。
target_module はTypeScriptパーサから直接取得したインポートパス(例: @/components/worktree/TerminalSearchBar, react)であり、
tantivyインデックスに保存された実ファイルパス(例: src/components/worktree/TerminalSearchBar.tsx)と一致しない。

問題2: 逆方向ルックアップの不一致

find_imports_by_target(target) (related.rs:194) は正規化された実ファイルパスでSQLiteを検索するが、
dependencies.target_module にはインポートパス形式で格納されているため、逆方向の依存関係(「このファイルをimportしているファイル」)も常に0件になる。

問題3: snippet空はパス不一致の副作用

fetch_snippet()search_by_exact_path() でtantivy TermQuery完全一致検索を行うが、
インポートパス形式のキーでは実ファイルパスにマッチせず、空文字列が返される。

影響範囲

related サブコマンド

  • commandindex search --related <file> --with-snippet でsnippetが空
  • file_path がインポートパスのまま返される
  • enrich_related_with_snippets() (snippet_helper.rs:66) が影響

impact サブコマンド

  • commandindex impact <file> --with-snippet でもsnippetが空
  • enrich_impact_with_snippets() (snippet_helper.rs:47) が影響
  • aggregate_impact() (impact.rs) が find_related の結果をそのまま使用するため、impacted_files にインポートパスが出力される

context サブコマンド

  • context.rsenrich_entryfind_imports_by_source(path) も影響
  • ImportDependency 関連のエンリッチメント(symbols フィールド)が不正確になる可能性

影響を受けないコマンド

  • keyword search (run) — score_import_deps を経由しない
  • symbol search (run_symbol_search) — score_import_deps を経由しない
  • semantic search (run_semantic_search) — score_import_deps を経由しない
  • 出力フォーマッタ (json/llm/human/path) — file_pathを受け取って表示するだけ

LLMコンテキスト提供

  • CommandIndexの差別化機能(コード内容付きの関連ファイル出力)が機能しない
  • LLMエージェントへのコンテキスト提供において、パス一覧のみになり file.read の代替にならない

受け入れ基準

  1. --with-snippet 指定時、相対パスインポート(./xxx, ../xxx)の結果で snippet が非空であること
  2. エイリアスインポート(@/xxx)の結果でも snippet が非空であること(パスエイリアス解決により)
  3. 外部パッケージ(react 等、tantivyインデックスに存在しないもの)は snippet 空文字列を許容する
  4. --format json--format llm の両方で snippet が正しく出力されること
  5. related検索結果の file_path が実ファイルの相対パスで返されること
  6. impact検索結果のimpacted_filesも実ファイルパスで返されること
  7. 既存テスト(cargo test --all)が全てパスすること
  8. cargo clippy --all-targets -- -D warnings が警告0件であること
  9. import依存関係のE2Eテスト(--with-snippet付き)が追加されていること

解決方針

案A(推奨): score_import_deps 内で、インポートパスからtantivyインデックスのファイルパスをマッチングで解決する

  • 最も局所的かつ安全
  • パフォーマンス: resolve結果のキャッシュまたはメモリ上マッチングで軽微
  • M1修正でsnippet問題(M3)も連鎖的に解決

案B(将来対応): インデックス時にインポートパスを実ファイルパスに解決してSQLiteに保存

  • スキーマ変更が必要(dependencies テーブルに resolved_file_path カラム追加)
  • 既存インデックスとの互換性問題
  • 根本的解決だが初期リリースでは案Aを優先

環境

  • commandindex 0.1.0
  • 対象リポジトリ: 388ソースファイル(TypeScript/Next.js)

深刻度

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions