4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeのカスタムコマンドで"コード考古学"を自動化してみた

Last updated at Posted at 2025-12-09

*本記事で掲載しているコードは、所属企業とは何も関係のない、AIが生成しただけの例としての文字列になります。

この記事でわかること

  • Claude Codeのカスタムコマンドの活用例
  • コードの起源・来歴を調査するプロンプトの設計

作った背景

「このコード、なんでこうなってるの?」
長く運用されているプロジェクトで開発していると、こういう疑問が結構あります。
答えを見つけるには、だいたいこんな流れになると思います:

  1. git blame で変更したコミットを特定
  2. コミットメッセージからPRを探す
  3. PRの説明やJiraチケットを読む
  4. それでもわからなければ仕様書を漁る
  5. 実はファイル移動されていて、移動前のファイルで1からやり直し

これ、毎回手動でやるの結構辛いですよね。
特に厄介なのが「ファイル移動」や「リファクタリング」を挟んでいるケース。表面的な変更だけ見ても真の起源にたどり着けないんですよね。
というわけで、この一連の調査をClaude Codeに任せるカスタムコマンドを作りました。

何を作ったか

/trace-code-origin というカスタムコマンドです。

使い方

  /trace-code-origin UserProfileScreen.kt:120

または

  /trace-code-origin なぜProfileStateでフォロー状態を管理しているのか?

ファイルパスと行番号でも、自然言語の質問形式でもOKです。

出力イメージ

コマンドを実行すると、こんな感じのレポートが出力されます:

実装履歴レポート: UserProfileScreen の ProfileState

対象コード

app/src/main/java/.../UserProfileScreen.kt:120-150

変更履歴

| 日付         | コミット    | バージョン  | 変更内容                   | Jira       | PR   |
|------------|---------|--------|------------------------|------------|------|
| 2021-08-15 | a1b2c3d | v2.0.0 | ⭐ 起源: ProfileState新規作成 | TICKET-100 | #500 |
| 2021-09-20 | e4f5g6h | v2.1.0 | フォロー機能追加               | TICKET-120 | #550 |
| 2022-03-10 | i7j8k9l | v3.0.0 | Compose化に伴うリファクタリング    | TICKET-200 | #800 |

背景・目的

なぜProfileStateが導入されたのか?

1. 状態管理の一元化: 元々プロフィール画面の状態は複数のLiveDataに分散していたが、UiStateパターンに統一するためProfileStateを導入
2. テスタビリティ向上: 状態を1つのdata classにまとめることで、ViewModelのユニットテストが書きやすくなった
3. SNS機能追加: v2.0.0でフォロー/フォロワー機能が追加され、状態管理が複雑化したため整理が必要だった

PR #500 の説明より:
プロフィール画面の状態をProfileStateで一元管理するようにしました。
今後の状態追加はこのクラスに集約してください。

結論

なぜこのコードがこの形になっているのか?

1. 初回実装の背景: SNS機能追加に合わせて、画面状態の管理方法を刷新するために新規作成された
2. 設計判断の根拠: 複数のLiveDataではなく単一のStateFlowで状態を管理することで、状態の整合性を保ちやすくした
3. 現在の形に至った経緯: Compose化を経て、現在はMVI的なアーキテクチャの中核として機能している

関連リンク

- 初回実装PR: #500
- JIRAチケット: TICKET-100
- Confluence (仕様): [リンク]

PRやJira、Confluenceのバージョン別ページへのリンクも含まれるので、詳細をすぐに確認できます。

工夫したポイント

1. 「振る舞い」の起源を追う

単純に git blame で見つかったコミットを報告して終わり。

例えばこんなdiffがあったとします:

- updateState { it.copy(isFollowing = userRepository.isFollowing(userId)) }
+ updateState { it.copy(profileState = ProfileState(isFollowing = userRepository.isFollowing(userId))) }

ProfileState は新しく追加されたように見えますが、isFollowing を取得する振る舞い自体は変更前から存在していたんですよね。もっと前の期限を追う必要があります。

なのでプロンプトでは「diffの削除行(-)も必ず確認し、変更前のコードの起源も追跡すること」と明示しています。

2.Phaseを明確に定義してスキップを防ぐ

Claudeくん、時々「まぁこれでいいか」と浅い調査で満足してしまうことがあるので、調査手順を7つのPhaseに分けて、チェックリスト形式で定義しました:

  • Phase 1: 質問の本質を明確化
  • Phase 2: 対象コードを特定
  • Phase 3: git blameで直近の変更を確認
  • Phase 4: 真の起源まで深掘り
  • Phase 5: PR・Jiraチケットを調査
  • Phase 6: リリースバージョン特定・Confluence参照
  • Phase 7: 統合レポート作成

おわりに

「コード考古学」は地味だけど重要な作業ですよね。特にレビューや設計判断の際に「なぜこうなっているか」を知ることは、より良い意思決定につながると思っています。

Claude Codeのカスタムコマンドを使えば、こういった定型的だけど手間のかかる調査作業を効率化できます。便利ですね。

コマンド全文 一部抽象化しているので、各ユースケースに合わせて改修してお使いください。:pray

 ---
 description: コードの起源・変遷を調査。git履歴→PR→Jira→Confluenceを辿り、真の起源まで深掘りする。
 argument-hint: [ファイルパス:行番号] or [クラス名/関数名] or [コードスニペット]
 ---

 # Trace Code Origin

 特定の実装コードの**真の起源**と変遷を調査し、包括的なレポートを作成するコマンドです。

 ## 基本方針

 **浅い調査で終わらせない。** 以下のケースを必ず考慮する:

 1. **ファイル移動**: 現在のファイルは別の場所から移動されただけかもしれない
 2. **コードコピー**: 別のファイルからコピーされたコードかもしれない
 3. **リネーム**: クラス名や関数名が変更されているかもしれない
 4. **定数/イベントの起源**: 使っているイベントや定数自体の起源も調べておく

 ### ⚠️ 最重要:「振る舞い」の起源を追う

 具体的なクラス名やメソッド名だけではなく、**その振る舞いがいつから存在していたか**も調査する。

 - diffを見たとき、削除行(-)にも同じ値やロジックがあれば、それはもっと前から存在していた証拠。→ 変更前のコードがいつから存在していたかを即座に追跡する。
 - 削除行に存在しない場合でも、削除漏れなど、起源となるコードが別の場所に存在する可能性がある。必ず検索する。

 #### 例

 ```diff
 - updateState { it.copy(isFollowing = userRepository.isFollowing(userId)) }
 + updateState { it.copy(profileState = ProfileState(isFollowing = userRepository.isFollowing(userId))) }

 上記の例で、isFollowing の指定は変更前にも存在していた。
 → この「変更前のコード」がいつから存在していたかを追跡する必要がある。

 入力形式

 以下のいずれかの形式で調査対象を受け取る:

 1. ファイルパス + 行番号: path/to/File.kt:123
 2. 関数名・クラス名: SomeViewModel や fetchData()
 3. コードブロック: 特定のコードスニペット
 4. 質問形式: 「なぜこのコードは〇〇なのか?」

 ---
 調査手順(深掘りループ)

 重要: 各 Phase を順番に実行し、絶対にスキップしないこと。
 特に Phase 6 (リリースバージョン特定・Confluence参照) は忘れがちなので注意。

 実行チェックリスト

 調査完了前に、以下のすべてにチェックが入っていることを確認する:

 - Phase 1: 質問の本質を明確化した(何を調べるべきか具体化した)
 - Phase 2: 対象コードを特定した
 - Phase 3: git blame で直近の変更を確認し、diffの「変更前」のコードも追跡した
 - Phase 4: 真の起源まで深掘りした(コピー元、ファイル移動等を追跡)
 - Phase 5: 関連する PR と JIRA チケットを調査した
 - Phase 6: リリースバージョンを特定し、Confluence を参照した
 - Phase 7: 統合レポートを作成した

 ---
 Phase 1: 質問の本質を明確化する ⚠️ 最初に必ず実行

 調査を始める前に、何を調べるべきかを正確に定義する。

 確認すべきこと

 1. 質問の対象は何か?
   - 「なぜこのコードがあるのか」→ コード全体の起源
   - 「なぜこの値が使われているのか」→ その値(定数・固定値)の起源
   - 「なぜこの分岐があるのか」→ 分岐ロジックの起源
 2. 調査対象を具体化する
 3. 検索キーワードを決める
   - 新しく追加されたクラス名/メソッド名ではなく
   - 変わっていない値・パターンを検索キーワードにする

 ---
 Phase 2: 対象コードの特定

 # 該当コードを検索
 grep -rn "対象のコード" --include="*.kt" --include="*.java"

 # または Glob で該当ファイルを探す

 Phase 3: 直近の変更履歴を確認 ⚠️ 変更前のコードも追跡!

 # 該当行の blame を取得
 git blame -L <start>,<end> <file>

 # 該当コミットの詳細を確認
 git show <commit-hash> --stat -p -- <file>

 変更前のコードを追跡する

 diffを見たら、必ず「変更前」の行を確認する。

 # 変更前のコードに含まれる固定値・パターンで検索
 git log -p --all --reverse -S "TYPE_NEW" -- "**/SomeFragment*" | head -300

 # 変更前の行が最初に追加されたコミットを探す

 判定基準

 | 状況                    | 対応                   |
 |-----------------------|----------------------|
 | 変更前のコードに調査対象の値が含まれている | そちらを追跡する(真の起源はそこにある) |
 | 変更は純粋な新規追加だった         | Phase 4 に進む          |

 Phase 4: 深掘りループ(重要!)

 このループを「真の起源」が見つかるまで繰り返す

 Step 4.1: ファイル移動の追跡

 # --follow でファイル移動を追跡
 git log --follow --oneline --all -- "**/FileName*" | tail -30

 # ファイルの最初のコミットを探す
 git log --follow --diff-filter=A -- <file>

 Step 4.2: コードのコピー元を探す

 # -S オプションで特定の文字列を含むコミットを探す(逆順で最初を見つける)
 git log -p --all --reverse -S "探したい文字列やコード" -- "**/*.kt" "**/*.java" | head -150

 Step 4.3: 周辺コミットを確認する

 # 直前5件を確認
 git log --oneline <hash>~5..<hash>

 # 直後5件を確認(HEAD側)
 git log --oneline <hash>..HEAD | head -5

 Step 4.4: 判定 - さらに深掘りが必要か?

 以下の場合は さらに遡る:

 | 状況                      | 次のアクション                     |
 |-------------------------|-----------------------------|
 | コミットが「ファイル移動」だった        | 移動元ファイルで同じ調査を繰り返す           |
 | コミットが「リネーム/リファクタ」だった    | 変更前の名前で -S 検索               |
 | コードが別ファイルからコピーされていた     | コピー元ファイルで同じ調査を繰り返す          |
 | 使用しているイベント/定数が別で定義されていた | そのイベント/定数の起源を調査             |
 | コミットメッセージが「〇〇対応」「〇〇実装」  | そのPR/チケットを調査し、なぜその実装になったか確認 |

 以下の場合は 深掘り完了:

 - --diff-filter=A(追加)で最初のコミットが見つかった
 - コミットメッセージに「新規実装」「初期実装」などの記述がある
 - それ以上遡れない(リポジトリの最初のコミットに到達)

 Phase 5: 関連PRとチケットの調査

 # コミットに関連するPRを検索
 gh pr list --search "<commit-hash>" --state all --json number,title,url,body,author

 # PRの詳細を取得
 gh pr view <pr-number> --json title,body,author,createdAt,mergedAt,url

 # チケット番号を探す
 git log --oneline -- <file> | grep -oE 'TICKET-[0-9]+'

 Phase 6: リリースバージョンの特定とConfluence参照 ⚠️ 必須

 このフェーズは絶対にスキップしない!
 コードの「なぜ」を理解するには、リリース時の背景情報が不可欠。

 よくある失敗パターン

 | 失敗                   | 正しい対応                          |
 |----------------------|--------------------------------|
 | PRが見つからなかったので諦めた     | PRがなくてもConfluenceは必ず確認する       |
 | 技術的な理由が見つかったので満足した   | ビジネス的背景(なぜその設計にしたか)も調べる        |
 | 古いコミットだから情報がないと決めつけた | 古いほどConfluenceに仕様書が残っている可能性が高い |

 コミットがどのバージョンでリリースされたかを特定する:

 # コミットを含む最初のリリースタグを特定
 git tag --contains <commit-hash> | head -5

 Confluenceのバージョン別ページを参照:

 バージョンが特定できたら、Confluenceでそのバージョンの案件詳細を確認する。

 Phase 7: 統合レポートの作成

 レポート作成前の最終確認:

 ✅ Phase 1-6 をすべて実行したか?
 ✅ 特に Phase 6 (リリースバージョン・Confluence) を忘れていないか?
 ✅ 真の起源(最初のコミット)まで遡れたか?
 ✅ PR/JIRA の情報を取得したか?

 上記すべてが完了してから、以下の形式でレポートを作成する。

 ---
 出力形式

 ⚠️ 重要: 結論セクションには必ず以下のリンクを含めること:
 - 最初の実装コミットへのリンク
 - JIRAチケットへのリンク
 - Confluenceバージョンページへのリンク

 # 実装履歴レポート: [対象コード]

 ### 対象コード
 [ファイルパス:行番号]

 ---

 ### 変更履歴

 | 日付 | コミット | バージョン | 変更内容 | Jira | PR | 補足 |
 |------|---------|-----------|---------|------|-----|------|
 | YYYY-MM-DD | `hash` | v1.0.0 | **⭐ 起源: 初回実装** | TICKET-1 | #100 | 備考 |
 | YYYY-MM-DD | `hash` | v2.0.0 | ファイル移動 | - | - | |
 | YYYY-MM-DD | `hash` | v3.0.0 | リファクタリング | TICKET-2 | #200 | |

 ---

 ### 関連する定義/イベントの起源(該当する場合)

 | 対象 | 起源コミット | 日付 | 背景 |
 |------|-------------|------|------|
 | `TYPE_NEW` | `hash` | YYYY-MM-DD | [なぜこの定義が作られたのか] |

 ---

 ### 結論

 **なぜこのコードがこの形になっているのか?**

 1. [理由1 - 初回実装の背景]
 2. [理由2 - 設計判断の根拠]
 3. [理由3 - 現在の形に至った経緯]

 **潜在的な問題**: [もしあれば]

 注意事項

 - 古いコードの場合、git履歴が途切れている可能性がある
 - チケット番号がコミットメッセージに含まれていない場合もある(PRから探す)
 - ファイル移動が複数回行われている場合がある
 - 「表面的な変更」で満足せず、真の起源を探ること

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?