indexion-refactor について
indexion-refactorは、コードベースの重複を分析・削減する開発者向けスキルです。テキスト、構造、概念の各レベルを扱い、共有コードの抽出やラッパーの整理、SoTの検証を支援します。
主な機能
- 01テキスト上のコード重複を検出
- 02ディレクトリ間の共有候補を抽出
- 03単純な委譲ラッパーを特定
- 04概念レベルの重複を分析
- 05SoTへの集約を支援
活用シーン
- 01リファクタリング前の重複調査
- 02パッケージ間での共有コード抽出
- 03不要なラッパーの削除
- 04ドメインロジックの分散確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trkbt10/ indexion-skills --skill indexion-refactorインストール後、Claude Code で/indexion-refactorを入力して読み込まれたか確認します。
詳細
indexion リファクタリング — コードベースのリファクタリング
indexion の分析コマンドを使って、テキスト、構造、概念の 3 つのレベルで重複を検出して解消し、SoT が適用されていることを確認します。
使用するタイミング
- 新しい抽象化(型、モジュール、API レイヤー)を追加した後
- 新しいファイル形式や I/O 境界を導入した後
- 同じ理由で 3 つ以上のファイルを変更する必要があった修正を行ったとき
- 構造上の問題を回避するために「ガード」や「スキップ」を追加したとき
- 予期しないパスから
opendir、ENOENTなどのファイルシステムエラーが発生したとき - パッケージ間で共有コードを抽出するとき
- リファクタリング後にクリーンアップするとき(単純なラッパー関数を削除する)
- コードベースの SoT の健全性を定期的に確認するとき
3 つのレベルの重複
| レベル | 内容 | ツール | 例 |
|---|---|---|---|
| テキスト | コピー&ペーストされたコードブロック、同一の関数 | plan refactor | 5 つのモジュールにコピーされた is_whitespace |
| 構造 | 名前は異なるが、同じロジック構造 | plan solid、plan unwrap | パッケージ間の抽出候補、単純なラッパー |
| 概念 | 同じドメイン概念を独立して実装している | explore + 手動分析 | 3 つのモジュールがそれぞれ「このファイルはアーカイブか」を判定している |
テキストの重複は簡単に見つけて修正できます。概念の重複は最も見つけにくく、最も危険です。コピー&ペーストの一致箇所は見つからない一方で、1 つの概念を変更するたびに、散在するすべての実装を更新する必要があります。
ワークフロー
フェーズ 1:テキストの重複を解消する(plan refactor)
確度の高い一致箇所から始め、徐々に対象を広げます。
# Step 1: Find 90%+ duplicates (high confidence)
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
cmd/indexion/
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/
出力は次の 3 つのセクションを確認します。
| セクション | 検出対象 | 対応 |
|---|---|---|
| 類似ファイル | 全体的な類似度が高いファイル | 構造的な統合が可能か調査する |
| 重複コードブロック | ファイル間で行レベルで一致するコード | @common または共有モジュールに抽出する |
| 関数レベルの重複 | 構造的に類似した関数(関数本体に対する TF-IDF) | 1 つの SoT 関数に統合する |
同一ファイル内の重複(類似度 90% 以上の関数)は、最も価値の高い対象です。修正が最も簡単で、効果も明確です。例:get_global_data_dir と get_global_cache_dir は構造が 95% 共通しているため、resolve_os_dir に抽出しました。
# Step 2: Use grep to trace references before consolidating
indexion grep "TypeIdent:TfidfEmbeddingProvider" src/
indexion grep --semantic=name:is_whitespace src/
# Step 3: Fix, then re-run to confirm duplicates are gone
indexion plan refactor --threshold=0.9 --include='*.mbt' ...
# Step 4: Lower threshold and iterate
indexion plan refactor --threshold=0.85 --include='*.mbt' ...
plan refactor のオプション:
| オプション | デフォルト | 説明 |
|---|---|---|
--threshold=FLOAT | 0.7 | 類似度の最小しきい値 |
--strategy=NAME | hybrid | 類似度の方式:hybrid、tfidf、bm25、jsd、ncd |
--fdr=FLOAT | 0 | FDR 補正(0=無効) |
--style=STYLE | raw | 出力形式:raw、structured |
--format=FORMAT | md | 出力形式:md、json、text、github-issue |
--name=NAME | -- | プロジェクト名(structured スタイル用) |
--include=PATTERN | -- | 含めるパターン(繰り返し指定可能) |
--exclude=PATTERN | -- | 除外するパターン(繰り返し指定可能) |
-o, --output=FILE | stdout | 出力ファイルのパス |
--specs-dir=DIR | kgfs | KGF 仕様のディレクトリ |
クリーンアップ後に残るもの(停止のサイン):
- プラットフォームスタブ(
native.mbt/stub.mbt) — 意図的なプラットフォーム分岐 - 型メソッドの類似性(異なる型の
to_string) — 型は異なるが、パターンは同じ - CLI コマンドの定型コード(
command()関数) — @argparse の API パターンであり、重複ではない - 意味は似ているが異なる関数(
is_disqualifying_keywordとis_skip_token) — 目的が異なる
フェーズ 2:パッケージ間で共有するコードを抽出する(plan solid)
各ディレクトリ内の整理が終わったら、パッケージ間で共有すべきコードを探します。
# Find overlap between two packages
indexion plan solid --from=src/a,src/b
# Specify extraction target
indexion plan solid --from=src/a,src/b --to=src/common
# Use tree edit distance for precise function-level matching
indexion plan solid --from=src/a,src/b --strategy=apted
# Higher threshold for stricter matching
indexion plan solid --from=src/a,src/b --threshold=0.95
# Filter files
indexion plan solid --from=src/a,src/b --include='*.mbt' --exclude='*_test.mbt'
plan solid は plan refactor とは異なります:
plan refactor | plan solid | |
|---|---|---|
| 範囲 | ディレクトリ内の内部重複 | ディレクトリ間の重複 |
| 目標 | コードベース内で統合する | 共有コードを新しいパッケージに抽出する |
| 入力 | <path> | --from=dirA,dirB |
plan solid のオプション:
| オプション | デフォルト | 説明 |
|---|---|---|
--from=DIRS | (必須) | ソースディレクトリ(カンマ区切り、または複数回指定可能) |
--to=DIR | -- | 抽出先のディレクトリ |
--rules=FILE | -- | ルールファイル(.solidrc) |
--rule=RULE | -- | インラインルール(複数回指定可能) |
--threshold=FLOAT | 0.9 | 類似度の最小しきい値 |
--strategy=NAME | tfidf | 類似度の計算方法:tfidf、apted、tsed |
--include=PATTERN | -- | 対象に含めるパターン(複数回指定可能) |
--exclude=PATTERN | -- | 対象から除外するパターン(複数回指定可能) |
--format=FORMAT | md | 出力形式:md、json、github-issue |
-o, --output=FILE | stdout | 出力ファイルのパス |
--specs-dir=DIR | kgfs | KGF 仕様ディレクトリ |
ワークフロー:
- まず各ディレクトリで個別に
plan refactorを実行し、内部の重複を整理します plan solid --from=dirA,dirBを実行し、ディレクトリ間で抽出できる候補を探します- 計画の推奨事項に従って共有コードを抽出します
indexion grep "TypeIdent:SharedType"を使って、すべての参照が更新されていることを確認します
フェーズ 3:不要なラッパーを削除する(plan unwrap)
統合後、価値を加えずに間接性を増やすだけの単純な委譲関数を整理します。
# Step 1: Quick check
indexion grep --semantic=proxy src/
# Step 2: Detailed report
indexion plan unwrap --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# Step 3: Preview changes (safe — no files modified)
indexion plan unwrap --dry-run --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# Step 4: Apply fixes
indexion plan unwrap --fix --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# Step 5: Run tests
moon test --target native
検出されるもの: 本体が単一の関数呼び出しで、すべての引数が単純な識別子としてそのまま渡される関数です。制御フローや変換は含まれません。
// Detected (default) — trivial delegation
fn matches_pattern(text : String, pat : String) -> Bool {
@glob.glob_match(text, pat)
}
// Excluded by default (use --all to include)
fn length(self : MyList) -> Int {
self.items.length() // self-delegation (encapsulation)
}
fn emit(value : String) -> Action {
Emit(value) // bare constructor
}
plan unwrap のモード:
| モード | フラグ | 説明 |
|---|---|---|
| レポート | (デフォルト) | 見つかったラッパーの一覧 |
| プレビュー | --dry-run | ファイルを変更せずに、すべての編集内容を表示 |
| 修正 | --fix | ファイルに編集を適用 |
plan unwrap のオプション:
| オプション | デフォルト | 説明 |
|---|---|---|
--dry-run | -- | 編集内容をプレビュー |
--fix | -- | 編集を適用 |
--all | -- | 自己委譲と本体のみのコンストラクターラッパーを含める |
--include-self | -- | self.field.method パターンを含める |
--include-bare | -- | 本体のみのコンストラクターラッパーを含める |
--include=PATTERN | -- | 対象に含めるパターン(複数回指定可能) |
--exclude=PATTERN | -- | 対象から除外するパターン(複数回指定可能) |
--format=FORMAT | md | 出力形式:md、json、text |
-o, --output=FILE | stdout | 出力ファイルのパス |
--specs-dir=DIR | kgfs | KGF 仕様ディレクトリ |
削除する前に確認してください:
- プラットフォームラッパー(FFI、
@osenv_path)は抽象化レイヤーであり、意図しない間接性ではありません - 外部パッケージで使われているパブリック API ラッパー — 削除すると破壊的変更になります
- 必ず最初に
--dry-runを実行してください
フェーズ 4:概念レベルの重複を検出する(explore + 分析)
これは最も難しいレベルです。コードが異なるため、テキストや構造のツールでは検出できません。しかし、概念は同じです。
# Find which files share vocabulary (= work in the same concept domain)
indexion explore --threshold=0.4 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/ cmd/
構造的な重複がないにもかかわらず類似度が40~60%のファイルは、概念上の近接ファイルです。同じ領域を扱っているため、同じ用語を使用しています。
類似度の高いペアごとに、次のように問いかけます。「どの概念を共有していて、その概念の所有者は誰か?」
# Inspect shared vocabulary with tree structure comparison
indexion explore file_a.mbt file_b.mbt --threshold=0 --strategy=apted
概念の漏出によく見られるパターン:
| 症状 | 漏出している概念 | 修正方法 |
|---|---|---|
両方のファイルで is_X(spec) を呼び出し、その後 Y::from_spec(spec) を呼び出す | 「Xかどうかを判定し、Yを設定する」 | Xを所有するモジュールに try_do_X(path, spec) を抽出する |
コンテンツがすでに読み込まれているのに、両方のファイルで @fs.read_file_to_string(path) を呼び出す | 「ファイルの内容を読み込む」 | コンテンツを引数として渡し、再読み込みしない |
両方のファイルで parent_dir(path) の後に @fs.read_dir(dir) を呼び出す | 「同じディレクトリ内のファイルを列挙する」 | ディレクトリ走査をパイプラインに一元化する |
複数の if is_virtual_path(x) { skip } ガードがある | 「実パスと仮想パス」 | 仮想パスがここに到達しないよう、型システムで防ぐ |
両方のファイルで buf.write_string("\n"); buf.write_string(x) を呼び出す | 「テキスト項目を結合する」 | 所有するモジュールに join_text_entries() を抽出する |
フェーズ 5:SoT に統合する
概念を定義するモジュールだけが、そのロジックを実装する必要があります。
ルール:
- 1つの概念、1つのモジュール、1つの関数。「アーカイブからテキストを抽出する」処理が
vfs.mbt、discover.mbt、args.mbtにある場合、vfs.mbtだけに置きます。 - 呼び出し元には材料ではなく結果を渡します。
is_archive_spec+ArchiveSpec::from_spec+expand_archiveを個別にエクスポートしないでください。try_extract_archive_text(path, spec) -> String?をエクスポートします。 - ガードは症状であり、修正ではありません。
if is_virtual_path(x) { skip }は、仮想パスがそもそもここに到達すべきでないことを意味します。受け側ではなく、発生元を修正します。 - メモリ内にあるものをディスクから再読み込みするのは、概念の漏出です。
SupportedFile.contentにテキストが格納されている場合、後続のコードで@fs.read_file_to_string(file.path)を呼び出してはいけません。
フェーズ 6:検証する
# Confirm textual duplication is gone
indexion plan refactor --threshold=0.9 \
--include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' \
src/ cmd/indexion/
# Confirm concept similarity is reduced
indexion explore file_a.mbt file_b.mbt --threshold=0
# Confirm wrappers are cleaned up
indexion plan unwrap --include='*.mbt' --exclude='*_wbtest.mbt' \
--exclude='*moon.pkg*' --exclude='*pkg.generated*' src/
# Run tests
moon test --target native
SoT への統合後:
- 概念の所有者とその呼び出し元とのテキスト類似度が下がる
- 呼び出し元が短くなる(複数の手順によるロジックではなく、API 呼び出し1回で済む)
- 概念の所有者は大きくなる場合がありますが、変更箇所は1か所だけになります
フェーズ 7:テストで再発しないことを証明する
古いパターンの再発を構造的に防ぐテストを作成します。
test "SoT: SupportedFile.path is always a real filesystem path" {
// Create an archive, run load_supported_file_info
// Assert: no path contains "!/"
// Assert: every path passes @fs.path_exists
}
このテストでは動作を確認するのではなく、SoT の不変条件を確認します。
危険信号
「ここにガードを追加する必要がある」
特殊ケースを受け取るべきではない関数に if is_special_case(x) { skip } を追加する場合、問題は上流にあります。関数の呼び出し元が、その値を渡してはいけません。
「動作するけれど、エラーが stderr に出力される」
C ランタイムからの stderr メッセージ(opendir: No such file or directory)は、無効なデータがシステムコールに渡されたことを意味します。catch でエラーを吸収しても、perror() はすでに出力しています。唯一の解決策は、無効なデータが呼び出しに渡されないようにすることです。
「コマンドごとに個別に修正する」
explore、search、grep、reconcile、plan のドキュメントなどで同じ修正が必要なら、その修正は各コマンドではなく、共有パイプラインに置くべきです。
「類似性は単に語彙が共通しているだけで、実際の重複ではない」
概念を共有しないはずのモジュール間で TF-IDF 類似度が40~60%ある場合は、警告と捉えてください。語彙の一致こそがシグナルです。
クイックリファレンス:どのコマンドを使うか
| 質問 | コマンド |
|---|---|
| 「類似しているファイルはどれですか?」 | explore --format=list |
| 「具体的に何が重複していますか?」 | plan refactor --threshold=0.9 |
| 「パッケージ A と B で重複しているコードは何ですか?」 | plan solid --from=A,B |
| 「単純なラッパー関数はどれですか?」 | plan unwrap または grep --semantic=proxy |
| 「これらのファイルに共通する概念は何ですか?」 | explore file_a file_b --threshold=0 --strategy=apted |
| 「重複は解消されましたか?」 | 同じしきい値で plan refactor を再実行します |