Skip to content

issue/before-changeの出力に判断理由のスニペットを付与する #168

@Kewton

Description

@Kewton

概要

issue --format llmbefore-change --format llm はファイルパスのリストのみを返す。「過去の判断を取り出す」というプロダクトコアを実現するには、各文書の**判断理由の要約(スニペット)**をインライン表示する必要がある。

現状

# issue --format llm: パスのみ
commandindexdev issue 299 --format llm
# → - dev-reports/design/issue-299-ipad-layout-fix-design-policy.md
#   (中身は不明。読みに行く必要がある)

# before-change --format json: snippet が NONE
commandindexdev before-change src/config/z-index.ts --format json
# → findings[].snippet: null(全件)

期待される結果

# Issue #299 関連ドキュメント

## 設計
- dev-reports/design/issue-299-ipad-layout-fix-design-policy.md
  > z-index指定方式をinline style方式で統一。Z_INDEX定数を直接参照し、Tailwindのz-[60]を廃止。DRY違反を解消。

## レビュー
- dev-reports/review/2026-02-18-issue299-consistency-review-stage2.md
  > Must Fix 1件: Toast.tsxがZ_INDEX.TOASTではなくz-50ハードコード。設計書の前提と不整合。
# before-change --format llm: 判断理由がインライン
commandindexdev before-change src/config/z-index.ts --format llm --with-snippet
# → - issue-299-ipad-layout-fix-design-policy.md (#299, has_design)
#     > inline style方式で統一。Z_INDEX定数を直接参照。

対象バリュー

  • 判断再利用: パスのリストでは判断を再利用できない。判断理由が直接読めて初めて「次の意思決定に接続」できる
  • 文脈先回り: AIエージェントがbefore-changeの結果だけで設計制約を把握できる。各ファイルをfile.readする多段ステップが不要になる

実装方針

Phase 1: 基本スニペット付与(本Issue スコープ)

既存の snippet_helper::fetch_snippet() を活用し、tantivy インデックスの body フィールドから先頭N文字を取得してスニペットとする。

snippet 未取得時の契約

snippet: Option<String> に統一:

  • 取得成功時: Some("非空のスニペット文字列")
  • tantivy 未存在 / 未インデックス / 空本文 / 検索失敗: None
  • Some("")(空文字列)は使用しない
  • JSON出力: None → フィールド省略(--with-snippet 未指定時)または null--with-snippet 指定時)
  • 既存 impact/related の JSON ポリシーに合わせ、--with-snippet 指定時のみ snippet フィールドを出力

既存の fetch_snippet() は空文字列を返すが、呼び出し側で空文字列を None に変換する。

CLIオプション

  • issuebefore-change--with-snippet フラグを追加(デフォルトオフ)
    • 他コマンド(impact, search)との一貫性を維持
    • main.rs の Cli 定義に追加
  • --snippet-lines / --snippet-chars も既存コマンドと同じパターンで追加
    • デフォルト: lines=3, chars=200(既存の lines=2, chars=120 とは異なる設定)
    • 設定ファイル連携も既存パターンに従う

型変更

  • BeforeChangeFinding (output/mod.rs) に snippet: Option<String> フィールドを追加
    • 影響箇所: before_change.rs 内コンストラクタ5箇所、テスト10箇所以上(全て snippet: None で初期化)
  • IssueDocumentEntry (cli/issue.rs) に snippet: Option<String> フィールドを追加
    • 影響箇所: symbol_store.rs find_documents_by_issue() 1箇所、issue.rs テスト6箇所(全て snippet: None で初期化)

tantivy IndexReader の追加

  • issue コマンド: commandindex_dir は既に受け取り済み。IndexReaderWrapper を開き、enrich_issue_documents_with_snippets() を呼び出す。
  • before-change コマンド: run_before_change() に IndexReaderWrapper を追加。main.rs のコール箇所も更新。
  • tantivy 未存在時のフォールバック: IndexReaderWrapper のオープンに失敗した場合は snippet: None でフォールバック(エラーにしない)。

snippet_helper パターンの一貫性

既存の enrich_impact_with_snippets() / enrich_related_with_snippets() パターンに従い、以下を追加:

  • enrich_issue_documents_with_snippets()
  • enrich_before_change_with_snippets()

出力フォーマット

before-change の表示順序(human/llm):

  1. 1行目: doc_path [similarity] (#issue, relation)
  2. 2行目: doc_title(存在する場合)
  3. 3行目: > snippet...--with-snippet 指定時かつ snippet が Some の場合)

issue の JSON スキーマ:

  • --with-snippet 未指定時: 現行の string[] を維持(後方互換性)
  • --with-snippet 指定時: オブジェクト配列に拡張
// --with-snippet 未指定時(現行互換)
{
  "issue_number": "299",
  "documents": {
    "設計": ["path/to/design.md"],
    "レビュー": ["path/to/review.md"]
  }
}

// --with-snippet 指定時
{
  "issue_number": "299",
  "documents": {
    "設計": [{"file_path": "path/to/design.md", "snippet": "z-index指定方式を..."}],
    "レビュー": [{"file_path": "path/to/review.md", "snippet": null}]
  }
}

before-change の JSON:

  • --with-snippet 指定時のみ snippet フィールドを追加(既存ポリシー準拠: None時はフィールド省略ではなく null で出力)

--format path: スニペット非出力(変更なし)

help-llm 更新

  • src/cli/help_llm.rs の issue/before-change コマンド説明に --with-snippet / --snippet-lines / --snippet-chars 追加
  • snippet 付き出力例を追加(例: issue 140 --with-snippet --format llm, before-change src/auth.rs --with-snippet --format llm

Phase 2: セクション優先抽出(将来の拡張、本Issue スコープ外)

  • has_design: 設計ポリシーの「採用方針」または「結論」セクションを優先抽出
  • has_review: summary-reportの「Executive Summary」セクションを優先抽出
  • has_workplan: Phase一覧の要約を抽出

現在の fetch_snippet() は先頭N文字を返すのみ。セクション優先抽出にはheadingフィールドでのフィルタリングが必要であり、別Issueとして対応する。

影響範囲

変更が必要なファイル

ファイル 変更内容
src/output/mod.rs BeforeChangeFinding に snippet フィールド追加
src/indexer/knowledge.rs or src/cli/issue.rs IssueDocumentEntry に snippet フィールド追加
src/indexer/symbol_store.rs find_documents_by_issue() で snippet: None 設定
src/cli/before_change.rs コンストラクタ5箇所 + テスト10箇所に snippet: None 追加、enrich 呼び出し
src/cli/issue.rs enrich 呼び出し追加、フォーマッタ4関数更新、テスト6箇所更新
src/cli/snippet_helper.rs enrich 関数2つ追加
src/output/human.rs format_before_change_human にスニペット表示追加
src/output/llm.rs format_before_change_llm にスニペット表示追加
src/output/json.rs format_before_change_json にスニペットフィールド追加
src/main.rs --with-snippet / --snippet-lines / --snippet-chars 追加、引数更新
src/cli/help_llm.rs コマンド説明・出力例更新
tests/cli_args.rs 新オプションのhelp表示・境界値・受理/拒否テスト追加
tests/e2e_issue.rs JSON スキーマ対応(--with-snippet 有無の両パターン)
tests/e2e_before_change.rs snippet フィールド検証追加
tests/output_format.rs フォーマッタ単体テスト(snippet=None/Some × human/llm/json/path)

テスト方針

  • CLIテスト (tests/cli_args.rs): 新オプションのhelp表示、境界値(--snippet-lines 0, --snippet-chars 0)、受理/拒否テスト
  • フォーマッタ単体テスト (tests/output_format.rs): issue/before-change の human/llm/json/path × snippet=None/Some
  • E2Eテスト: 各コマンド2系統
    1. tantivy あり: snippet 取得成功ケース(tantivy index を含むフィクスチャ構築)
    2. tantivy なし: snippet なし(None)フォールバックケース
  • issue E2E: --with-snippet 有無の両パターンでJSON スキーマ検証
  • before-change E2E: --with-snippet 指定時の snippet フィールド存在検証

依存関係の影響

  • 新規外部依存なし
  • 実行時依存の拡大: issue / before-change に tantivy IndexReaderWrapper 依存が追加される
    • 障害モード: symbols.db はあるが tantivy インデックスが壊れている/ない → 非fatal フォールバック(snippet: None)
    • 既存の impact/search コマンドは同じ依存を既に持っており、パターンは確立済み

パフォーマンス影響

  • before-change: tantivy IndexReader オープン1回 + fetch_snippet() 最大20回(issue limit × 2 docs)— 軽微
  • issue: tantivy IndexReader オープン1回 + fetch_snippet() 最大100回(find_documents_by_issue の LIMIT 100)— 件数比例だが TermQuery(完全一致)で O(1) に近く影響は軽微
  • 必要なら同一パス重複の除去や limit の見直しも検討可能

影響なし

  • issue/before-change 以外のコマンド(search, impact, why, suggest 等)

受け入れ基準

  • issue --with-snippet --format human で各ドキュメントにスニペットが表示される
  • issue --with-snippet --format llm で各ドキュメントにスニペットが表示される(> snippet... 形式)
  • issue --with-snippet --format json で各ドキュメントに snippet フィールドが含まれる(オブジェクト配列)
  • issue --format json(--with-snippet なし)は現行の string[] を維持(後方互換性)
  • before-change --with-snippet --format human で各 finding にスニペットが表示される
  • before-change --with-snippet --format llm で各 finding にスニペットがインライン表示される
  • before-change --with-snippet --format json で各 finding に snippet フィールドが含まれる
  • --format path ではスニペットを出力しない
  • --with-snippet 未指定時はスニペットを出力しない(後方互換性維持)
  • snippet 未取得時は None(JSON: null)を返す(Some("") は使用しない)
  • tantivy インデックスが存在しない場合はスニペットなし(None)でフォールバック(非fatal)
  • --snippet-lines / --snippet-chars で文字数制御可能(デフォルト: lines=3, chars=200)
  • cargo clippy --all-targets -- -D warnings 警告0件
  • cargo test --all 全テストパス

テスト環境

  • commandindex 0.1.0
  • 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