Skip to content

whyコマンドの出力に重複エントリが含まれる #158

@Kewton

Description

@Kewton

概要

why コマンドの出力で、同一Issueのレビュー文書が2回ずつ表示される(dedup不足)。

再現手順

commandindexdev why dev-reports/design/issue-299-ipad-layout-fix-design-policy.md

実際の結果

Why: dev-reports/design/issue-299-ipad-layout-fix-design-policy.md

  Issue #299
    [review] dev-reports/issue/299/multi-stage-design-review/summary-report.md
    [review] dev-reports/issue/299/pm-auto-dev/iteration-1/progress-report.md
    [review] dev-reports/issue/299/issue-review/summary-report.md
    [review] dev-reports/review/2026-02-18-issue299-security-review-stage4.md
    [review] dev-reports/review/2026-02-18-issue299-impact-analysis-review-stage3.md
    [review] dev-reports/review/2026-02-18-issue299-design-principles-review-stage1.md
    [review] dev-reports/review/2026-02-18-issue299-consistency-review-stage2.md
    [review] dev-reports/issue/299/multi-stage-design-review/summary-report.md    ← 重複
    ... (同一リストが繰り返される)
    [workplan] dev-reports/issue/299/work-plan.md
    [workplan] dev-reports/issue/299/work-plan.md                                ← 重複
    [modifies] modifies: 84 files

期待される結果

各ファイルが1回のみ表示される。

原因分析(検証済み)

根本原因1: SQL JOINによるCartesian Product

find_knowledge_related() (symbol_store.rs:1074) のSQLクエリで、入力ファイルからIssueへの全incoming edge (ke1) とIssueから兄弟ノードへの全outgoing edge (ke2) をJOINしている。同一Issueから入力ファイルへ異なるrelationの複数エッジが存在する場合、(ke1の数) × (ke2の数) のCartesian productが生成され、sibling行がエッジ数の倍数分重複する。

根本原因2: why.rs のdedup処理不足

run_why() (why.rs:109-125) のIssue別グルーピング処理で、同一 (file_path, relation) の重複チェックを行わず Vec::push() で無条件追加している。

根本原因3: modifies集約のデータモデル不整合

run_why() (why.rs:127-133) で modifies をカウント集約し file_path: "modifies: N files" として WhyDocumentEntry に混入させている。これにより path/json フォーマットで実パスではない合成文字列が出力される。

修正方針

修正1: SQLクエリに SELECT DISTINCT を追加

  • find_knowledge_related() のSQLに SELECT DISTINCT を追加し、DB層で重複を排除

修正2: アプリケーション層でのdedup追加

  • run_why() のグルーピングループで HashSet<(String, String)> 等を使い (file_path, relation) ペアの重複を防御的に除去

修正3: modifies集約の適切な表現

  • WhyIssueEntrymodifies_count: Option<usize> フィールドを追加
  • WhyDocumentEntryfile_path に合成文字列を格納しない
  • human/llm フォーマットでのみ modifies: N files を表示
  • json フォーマットでは modifies_count フィールドとして出力
  • path フォーマットでは実パスのみを返す(modifies情報は出力しない)
  • 全 formatter(human/json/path/llm)と既存テストを同時に更新する

修正4: テスト追加・更新

  • find_knowledge_related() のCartesian Product再現テスト
  • run_why() の重複入力dedupテスト
  • modifiesカウントの正確性テスト
  • 既存 formatter テストの期待値見直し(json/pathからsynthetic path削除)
  • modifies_count の表示/非表示パターンテスト

影響範囲

find_knowledge_related() の呼び出し元

  • src/cli/why.rs - whyコマンド(本修正の主対象)
  • src/search/related.rs:461-470 - search --related コマンド
    • 重複行があると add_relation() (related.rs:191-203) が同一パスに重みを重複加算
    • SELECT DISTINCT追加により重複由来の過大スコアが是正される(正しい副作用)
    • ランキング結果の順位変動があり得ることを許容する

WhyIssueEntry の JSON スキーマ変更

  • modifies_count: Option<usize> フィールド追加
  • CLI利用者やテストフィクスチャがJSON形状に依存している場合は互換性に注意

パフォーマンス

  • DISTINCT + HashSet の二層dedup。LIMIT 100 の範囲内なのでコストは軽微だが、二層でコストが発生する

出力フォーマット

  • human/json/path/llm すべてに影響
  • 全 formatter の更新が必要

受け入れ基準

  • why コマンドの出力で同一ファイルが複数回表示されない
  • 同一 (file_path, relation) ペアが1回のみ出力される
  • modifies カウントが正確(重複によるカウント水増しがない)
  • why --json で合成文字列("modifies: N files")が file_path に出力されない
  • why --json で modifies_count が適切なフィールドとして出力される
  • why --path が実ファイルパスのみを返す(合成文字列を含まない)
  • search --related で同一relation によるスコア水増しが起きない(順位変動は許容)
  • Cartesian Product再現ケースの回帰テストが存在する
  • 既存 formatter テストが更新され全パスする
  • cargo test --all 全パス
  • cargo clippy --all-targets -- -D warnings 警告0件

スコープ外

  • 複数経路で発見された場合のマージ表示(別Issue化を推奨)
  • LIMIT 100 の定数化(別Issue化を推奨)

テスト環境

  • commandindex 0.1.0 (スキーマv4)
  • CommandMateリポジトリ(2910ファイル、124690セクション)

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