Skip to content

[Feature] チーム向けstatusコマンド拡張(インデックスカバレッジ・統計) #79

@Kewton

Description

@Kewton

概要

commandindex status コマンドを拡張し、チーム運用に必要な統計情報を表示する。

背景・動機

チームでCommandIndexを運用する際、「どのファイルがインデックスされているか」「どの言語がカバーされているか」「インデックスの鮮度は十分か」といった情報が必要になる。

提案する解決策

拡張された status 出力

commandindex status --detail
Index Status:
  Path: .commandindex/
  Version: 5
  Last indexed: 2026-03-22 14:30:00
  Last commit at index: abc1234

Coverage:
  Total files: 1500 (walkdir scan, respecting .cmindexignore)
  Indexed files: 1420
  Skipped files: 80 (filtered by .cmindexignore / unsupported extensions)

  By type:
    Markdown:   800 files (56%)
    TypeScript: 450 files (32%)
    Python:     170 files (12%)

  Embedding coverage:
    Files with embeddings: 1200 / 1420 (85%)
    Model: nomic-embed-text (or "(not configured)" if config.toml absent)

Staleness:
  Commits since last index: 12
  Files changed since last index: 23
  Recommendation: Run `commandindex update`
  (Git info unavailable: git not found)  ← git未インストール時

Storage:
  tantivy/:      45 MB
  symbols.db:    12 MB
  embeddings.db:  8 MB
  Other:          0 MB
  Total:         65 MB

CLIオプション

# 基本情報(既存互換)
commandindex status

# 詳細情報
commandindex status --detail

# JSON出力(CI/スクリプト向け)— 既存の --format json を拡張情報に対応
commandindex status --format json

# カバレッジのみ(--detail との併用不可、排他オプション)
commandindex status --coverage

オプション排他ルール:

  • --detail--coverage は排他。同時指定時はエラーメッセージを表示して終了。
  • --format json--detail / --coverage いずれとも併用可能。
  • オプションなしの commandindex status は既存の出力と完全互換を維持。

実装方針

IndexState の拡張(MF-1: 後方互換性の確保)

  • IndexState 構造体に last_commit_hash: Option<String> フィールドを追加する。
  • #[serde(default)] を付与し、既存の state.json との後方互換を保つ。フィールドが存在しない古い state.json をデシリアライズしても None となりパニックしない。
  • schema_version のバンプは不要Option + serde(default) で既存データを壊さないため)。この判断根拠をコード内コメントにも明記する。
  • マイグレーション戦略: 次回 commandindex index / commandindex update 実行時に自動的にフィールドが書き込まれる。明示的なマイグレーションコマンドは不要。

status コマンドの run() シグネチャ変更(MF-2: StatusOptions 構造体の導入)

  • 現在の run() 関数のシグネチャを直接変更するのではなく、StatusOptions 構造体を導入してオプションを集約する。
    pub struct StatusOptions {
        pub detail: bool,
        pub coverage: bool,
        pub format: OutputFormat,
    }
  • StatusOptions には Default トレイトを実装し、StatusOptions::default() で既存互換の動作(detail: false, coverage: false, format: OutputFormat::Human)となるようにする。
  • 既存テストでは StatusOptions::default() を使用することで、新オプション追加による既存テストの破壊を防ぐ。
  • 将来のオプション追加時も StatusOptions にフィールドを追加 + Default 更新のみで済む拡張性を確保。

--detail / --coverage の排他制御(MF-3: clap conflicts_with + テスト)

  • clap の conflicts_with 属性--detail--coverage を排他にする。
    #[arg(long, conflicts_with = "coverage")]
    detail: bool,
    
    #[arg(long, conflicts_with = "detail")]
    coverage: bool,
  • --coverage 単体指定時は Coverage セクションのみ出力。
  • --detail 単体指定時は全セクション(Index Status / Coverage / Staleness / Storage)を出力。
  • tests/cli_args.rs に排他テストを追加:
    • --detail --coverage 同時指定でエラー終了することを検証。
    • --detail 単体、--coverage 単体がそれぞれ正常動作することを検証。

StatusInfo の新フィールド後方互換(SF-1: serde skip_serializing_if)

  • StatusInfo 構造体の新規追加フィールド(coverage, staleness, storage, embedding_coverage)には全て #[serde(skip_serializing_if = "Option::is_none")] を付与する。
  • これにより、--format json の出力が既存スクリプトを壊さない(新フィールドが None の場合はキーごと省略される)。
  • 既存の --format json パーサーが未知のキーを無視する前提だが、念のためデフォルト出力では不要フィールドを含めない。

EmbeddingStore の拡張(SF-2: get_embedding_file_count ヘルパー)

  • EmbeddingStorecount_distinct_files() -> Result<usize> メソッドを追加する。
  • SELECT COUNT(DISTINCT file_path) FROM embeddings で取得。
  • get_embedding_file_count() ヘルパー関数を追加し、status コマンドから直接呼び出す。
    • DB ファイルが存在しない場合: エラーではなく 0 を返却。
    • DB 接続エラー / クエリエラー: Result::Err を返し、呼び出し側で (unavailable) と表示。
  • status --detail の Embedding coverage 表示に使用する。

ファイル走査(SF-3: パフォーマンスとフィルタリング)

  • Total files: status 実行時に walkdir でプロジェクトルートを再走査し、対象ファイル数をカウントする。
  • フィルタリング:
    • .cmindexignore のルールを適用してスキップ。
    • デフォルト除外ディレクトリ: .git/, node_modules/, target/.cmindexignore の有無にかかわらず常に除外。
    • .gitignore にも対応(既存の ignore 処理ロジックを再利用)。
  • Indexed files: tantivy インデックスに登録されているドキュメント数(manifest.json から取得、または tantivy reader で num_docs() を使用)。
  • Skipped files: Total files - Indexed files で算出。理由として .cmindexignore フィルタおよび未対応拡張子を表示。

Git 情報の取得(SF-4: best-effort 方式)

  • std::process::Commandgit コマンドを呼び出す方式を採用する(git2 crate は不使用)。
  • git rev-parse HEADgit log --oneline <last_commit>..HEAD などで差分情報を取得。
  • Graceful degradation(best-effort):
    • git コマンドが見つからない場合: Staleness セクション全体を (Git info unavailable: git not found) と表示。None を返却。
    • git は存在するがリポジトリでない場合: (Git info unavailable: not a git repository) と表示。None を返却。
    • git コマンドがタイムアウト / 予期せぬエラー: None / デフォルト値を使用し、エラーにはしない。
    • いずれの場合も status コマンド自体は正常終了する(exit code 0)。

Storage 内訳の構造化(SF-5: StorageBreakdown 構造体)

  • StorageBreakdown 構造体を導入:
    pub struct StorageBreakdown {
        pub tantivy_bytes: u64,
        pub symbols_db_bytes: u64,
        pub embeddings_db_bytes: u64,
        pub other_bytes: u64,
        pub total_bytes: u64,
    }
  • 各項目は fs::metadata またはディレクトリ走査で実サイズを取得。
  • indexer モジュールの既存パスヘルパーindex_dir_path(), state_file_path() 等)を活用してパスのハードコーディングを避ける。
  • 表示時は humanize_bytes() ヘルパーで人間が読みやすい単位(KB/MB/GB)に変換。

config.toml 不在時の表示

  • Embedding coverage の Model 表示について、config.toml が存在しない / embedding モデル設定がない場合は (not configured) と表示する。

影響を受けるファイル・テスト

変更対象ファイル

ファイル 変更内容
src/cli/status.rs StatusOptions 構造体導入、run() シグネチャ変更、全表示ロジック追加
src/indexer/state.rs IndexStatelast_commit_hash 追加(#[serde(default)]
src/indexer/mod.rs パスヘルパー公開(StorageBreakdown で利用)
src/embedding/store.rs count_distinct_files() / get_embedding_file_count() 追加
src/output/mod.rs StatusInfo に新フィールド追加(#[serde(skip_serializing_if)]
src/main.rs --detail / --coverage clap 定義、conflicts_with 設定

影響を受けるテストファイル

テストファイル 変更内容
tests/cli_args.rs --detail / --coverage 排他テスト追加、StatusOptions::default() 互換テスト
tests/status_test.rs(新規) status --detail の統合テスト、JSON 出力の構造検証
src/indexer/state.rs(ユニットテスト) last_commit_hash の serde 後方互換テスト(古い JSON → 新しい struct)
src/embedding/store.rs(ユニットテスト) count_distinct_files() の正常系 / DB 不在時テスト

新規追加される型

型名 定義場所 用途
StatusOptions src/cli/status.rs status コマンドのオプション集約
StorageBreakdown src/cli/status.rs または src/indexer/ ストレージ内訳の構造化

受け入れ基準

機能要件

  • status --detail でファイルカバレッジ(Total / Indexed / Skipped)が表示される
  • Total files は walkdir による実行時走査で算出される(.git/, node_modules/, target/ はデフォルト除外)
  • ファイル種別ごとの内訳(Markdown / TypeScript / Python 等)が表示される
  • Embedding カバレッジが EmbeddingStore::count_distinct_files() を使って表示される
  • embeddings.db 不在時は Embedding count が 0 として扱われる(エラーにならない)
  • config.toml 不在時、Embedding Model は (not configured) と表示される
  • 最終インデックス以降の変更件数(staleness)が git コマンドで取得・表示される
  • git 未インストール / リポジトリ外 / コマンドエラー時は Staleness セクションが best-effort で (Git info unavailable) と表示され、exit code 0 で終了する
  • --format json の出力に拡張情報(coverage / staleness / storage / embedding_coverage)が含まれる
  • --format json の新フィールドは #[serde(skip_serializing_if = "Option::is_none")] でデフォルト出力の後方互換を維持
  • ストレージ使用量が StorageBreakdown 構造体を使い tantivy/ / symbols.db / embeddings.db / Other の内訳で表示される
  • ストレージパスは indexer モジュールの既存パスヘルパーを活用(ハードコーディング禁止)
  • --detail--coverage は clap conflicts_with で排他オプションとして動作する(同時指定時エラー)
  • オプションなしの status は既存出力と完全互換

データモデル

  • IndexStatelast_commit_hash: Option<String> が追加されている
  • #[serde(default)] により既存 state.json との後方互換が維持される
  • schema_version バンプ不要であることが確認され、コード内コメントで理由が明記されている

設計要件

  • StatusOptions 構造体が導入され、Default トレイト実装により既存テストが StatusOptions::default() で互換維持される
  • StorageBreakdown 構造体が導入され、ストレージ情報が構造化されている
  • get_embedding_file_count() ヘルパーが存在し、DB 不在時に 0 を返す

テスト要件

  • tests/cli_args.rs--detail --coverage 排他テストが存在する
  • IndexState の serde 後方互換テスト(古い JSON → 新しい struct)が存在する
  • count_distinct_files() の正常系 / DB 不在時ユニットテストが存在する
  • StatusOptions::default() を使った既存テスト互換が維持されている

品質要件

  • cargo test --all 全パス
  • cargo clippy --all-targets -- -D warnings 警告ゼロ
  • cargo fmt --all -- --check 差分なし

将来の拡張候補

  • --watch モード(リアルタイム status 更新)
  • status 結果のキャッシュ(大規模リポジトリでの walkdir 高速化)
  • Health check スコア表示(カバレッジ + staleness を総合評価)
  • --output-file オプション(結果をファイルに保存)

依存 Issue

  • なし(Phase 5完了が前提)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions