Claude Codeで巨大コードベースを読む: 検索術と依存追跡で迷わない手順
10万行のリポジトリで迷子になった僕が使う、rg検索の段階化・依存の追い方・実行経路の追跡まで。読み進め方を具体コマンドで解説。
引き継いだリポジトリを開いて、10分後にまだ「で、ログイン処理どこ?」と独り言を言っていたことがあります。フォルダは200個。READMEは2年前で止まっている。grepしたら同じ単語が400件ヒットして、画面がスクロールバーに飲まれました。
正直に言うと、最初の頃の僕は「全部読めば分かる」と思っていました。違いました。巨大コードベースで詰まるのは、知識が足りないからじゃない。読む順番と探し方を決めていないからです。
この記事は「最初の地図を作る話」ではありません。地図はもうある前提で、その上をどう歩くか——検索をどう絞り、依存をどうたどり、処理の流れをどう追うか——を、僕が実案件で使っているコマンドとプロンプトに落とします。地図そのものの作り方はRepo Map初回パスの手順にまとめたので、そっちと往復してもらうのが一番効きます。
この記事の要点
- 巨大コードベースで迷う原因は知識不足ではなく、検索と読む順番が無計画なこと。まず探し方を設計する。
- 検索は「構造 → 語彙 → 呼び出し元 → 設定 → 履歴」の5段階に分ける。
rgのヒット件数は理解度ではなくただの候補リスト。 - 「このファイルを触ると何が壊れる?」は、相対importを拾う30行のスクリプトで十分つかめる。完璧な解析ツールは要らない。
- Claude CodeやCodexには検索結果を丸投げせず、分類・経路表・リスク評価の形に変換させると精度が跳ね上がる。
- 初回は編集権限を渡さない。
claude -pやCodexのread-onlyで「読むだけ」に縛ると、勝手な修正で話が散らからない。
まず「探し方」を決める。読むのはその後
巨大コードベースを前にして、いきなりReadを連打するのは一番やってはいけない手です。やるべきは、今日たどりたい1本の処理を決めること。「ログインするとき、何がどの順で動くか」みたいに、入口と出口のある問いを立てます。
問いが決まると、検索が一気に楽になります。闇雲なgrep authではなく、「ログインのHTTP入口はどこか」「セッションを書くのはどの関数か」と、段階を追って探せるからです。
ここで主役になるのがrg(ripgrep)。grepの高速版で、.gitignoreを自動で尊重するのでnode_modulesやdistに邪魔されません。検索のたびに除外オプションを並べる手間が消えるだけでも、体感速度がかなり変わります。
僕は探索の前に、まず立ち位置を確認します。
# 今どのブランチの、どの状態にいるか。差分があるとAIの読解がブレる
git status --short
git branch --show-current
# 拡張子ごとのファイル数。何で書かれたリポジトリか一発で分かる
rg --files -g '!*node_modules*' -g '!dist' -g '!build' \
| rev | cut -d. -f1 | rev | sort | uniq -c | sort -nr | head -15
.tsが3000、.sqlが80、.goが5、みたいな内訳が出ます。これだけで「TypeScript中心で、DBは生SQL、Goは何かの補助だな」と当たりがつく。読む前の体温測定です。
検索を5段階に分ける(grep一発で分かった気にならない)
僕が一番やらかしていた失敗が、名前検索だけで「理解した」と勘違いすることでした。rg AuthServiceで50件出て、「うんうん認証ね」と分かった気になる。でもそれは登場場所のリストであって、処理の流れじゃない。
なので検索は目的別に5段階へ分けます。順に回すと、点だった情報が線になります。
# 1. 構造: 実行の入口候補を探す(サーバ起動・ルーティング・main)
rg -n "createServer|listen\(|app\.use|router\.|main\(|bootstrap|createRoot" \
src apps packages server web
# 2. 語彙: ドメインの言葉で当たりをつける(認証・課金・通知など)
rg -n "Auth|Billing|Invoice|Notification|Search|FeatureFlag" \
src apps packages test tests
# 3. 呼び出し元: これから触るモジュールを誰が使っているか
rg -n "AuthService|useAuth|requireAuth|authMiddleware" \
src apps packages test tests
# 4. 設定: 環境変数や外部サービスの鍵がどこで読まれるか
rg -n "process\.env|import\.meta\.env|DATABASE_URL|JWT|STRIPE|ANTHROPIC" \
. -g '!node_modules' -g '!dist' -g '!build'
# 5. 履歴: なぜ今の形になったかを最近のコミットで見る
git log --oneline --date=short --max-count=30 -- src/auth packages/auth
ポイントは段階3と5です。呼び出し元を見ると影響範囲が見え、履歴を見ると設計の理由が見える。 この2つを飛ばすと、「動くけどレビューで差し戻される変更」を平気で作ってしまいます。
そしてAIに渡すときが本題。検索結果を丸ごと貼って「全部読んで」は最悪手です。50件ヒットしたら、上位の十数ファイルだけ渡して、分類させる。AIに読解させるのではなく、整理係をやらせる感覚です。
あなたはコードリーディングの整理係です。
以下のrg結果を、実装の入口 / 呼び出し元 / 設定 / テスト / ノイズ に分類してください。
各分類で先に読むべきファイルを最大5件に絞り、理由を1行ずつ書いてください。
判断できないものは推測せず「追加検索が必要」と書いてください。
この一手間で、AIの出力が「auth周辺が重要そうです」みたいな感想文から、「まずauth/login.tsを読め、理由はHTTP入口だから」という指示書に変わります。
「触ると何が壊れる?」を30行で可視化する
変更前にいちばん怖いのが、波及範囲が読めないことです。あるファイルを直したら、どこに影響が出るのか。本格的な依存解析ツールを入れてもいいですが、最初は相対importを拾うだけの小さなスクリプトで十分役に立ちます。
「対象ファイルを直接importしているのは誰か」を一覧にするだけ。これがAIと影響範囲を話すときの材料になります。
#!/usr/bin/env node
// 指定ファイルを「直接import している側」を git 管理下から探して一覧化する
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import path from "node:path";
const target = process.argv[2]?.replace(/\\/g, "/");
if (!target) {
console.error("使い方: node scripts/who-imports.mjs src/path/to/file.ts");
process.exit(1);
}
// git 管理下のファイルだけ対象にする(生成物やnode_modulesを自然に除外できる)
const tracked = execFileSync("git", ["ls-files"], { encoding: "utf8" })
.split(/\r?\n/)
.filter(Boolean)
.map((f) => f.replace(/\\/g, "/"));
const trackedSet = new Set(tracked);
const sources = tracked.filter((f) => /\.(mjs|cjs|js|jsx|ts|tsx)$/.test(f));
const importRe =
/(?:from\s+["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)|require\s*\(\s*["']([^"']+)["']\s*\))/g;
// 相対importを実ファイルに解決する(拡張子やindexを補う)
function resolveLocal(spec, fromFile) {
if (!spec.startsWith(".")) return null; // 外部パッケージは無視
const base = path.normalize(path.join(path.dirname(fromFile), spec)).replace(/\\/g, "/");
const candidates = [base, `${base}.ts`, `${base}.tsx`, `${base}.js`, `${base}.jsx`,
`${base}/index.ts`, `${base}/index.tsx`, `${base}/index.js`];
return candidates.find((c) => trackedSet.has(c)) ?? base;
}
const importers = [];
for (const file of sources) {
const src = readFileSync(file, "utf8");
for (const m of src.matchAll(importRe)) {
const resolved = resolveLocal(m[1] || m[2] || m[3], file);
if (resolved === target || resolved?.endsWith(`/${path.basename(target)}`)) {
importers.push(file);
}
}
}
console.log(`対象: ${target}`);
console.log("直接importしているファイル:");
for (const f of [...new Set(importers)].sort()) console.log(`- ${f}`);
保存して、調べたいファイルを渡すだけです。
mkdir -p scripts
node scripts/who-imports.mjs src/services/AuthService.ts
出てきた一覧を、今度はAIにリスク評価へ翻訳させます。一覧だけ見ても重要度は分からないので、ここで線引きを頼みます。
次の「直接import一覧」を見て、AuthService.ts を変更する場合の影響範囲を high / medium / low で評価してください。
high は認証・課金・権限・永続化・公開APIに関わるもの。
各レベルで、追加で読むべきテストと設定ファイルも挙げてください。
完璧な解析じゃありません。動的importや文字列で組み立てるパスは拾えない。でも「ここを触ると課金まわりに飛び火するな」と気づければ、それで目的の8割は達成です。残りはテストを走らせて確かめればいい。
入口から出口まで、実行経路を「表」で追わせる
ファイル一覧をいくら眺めても、実行時の流れは見えません。リクエストが来て、middleware を通り、route で受けて、service で処理して、DB に書く——この縦の流れを追うのが読解の核心です。
まず流れの部品がどこにあるかを拾います。
# サーバ側の処理の節目(middleware / route / controller / service / repository)
rg -n "middleware|loader|action|controller|handler|route|repository|service" \
src apps packages -g '*.ts' -g '*.tsx' -g '*.js'
# データに触る箇所(fetch や ORM の呼び出し)
rg -n "fetch\(|axios|prisma|drizzle|sequelize|typeorm" \
src apps packages -g '*.ts' -g '*.tsx' -g '*.js'
ここでAIに頼むのは、感想ではなく経路表です。表で出させると、抜けが一目で分かる。良い出力と危ない出力の差はこのくらいあります。
| 観点 | 良い出力 | 危ない出力 |
|---|---|---|
| 入口 | POST /login → auth route → AuthService.login | 「auth周辺をなんとかして」だけ |
| 状態変更 | Cookie / session / DB / cache のどれが変わるか | 状態変更に触れていない |
| 失敗時 | 例外時の戻り値・ログ・監査イベント | 成功パスしか書かない |
| テスト | 既存テスト名と、足りていないテスト | 「テストを追加しましょう」だけ |
プロンプトはこの形に固定しています。
login処理の実行経路を、入口から永続化まで1枚の表にしてください。
列は「順番 / ファイル / 関数orクラス / 状態変更 / 失敗時の動き / 読むべきテスト」。
不明な箇所は推測で埋めず、追加で読むべきファイル名を書いてください。
「失敗時の動き」を列に入れているのが地味な肝です。AIは放っておくと成功パスだけ説明して、エラー処理を素通りする。でもバグも事故も、たいてい失敗パスに住んでいます。列を一つ足すだけで、AIが嫌でもそこを見にいくようになります。
初回は「読むだけ」に縛る。編集権限を渡さない
探索の初日に、AIへ編集権限を渡すのは早すぎます。所有境界も実行経路も把握していない状態で「直して」と言うと、AIは賢そうに大量の差分を作り、レビューで全部ひっくり返ります。
なので初回は読み取り専用に縛ります。Claude Codeなら-p(print mode)で非対話の出力にして、プロンプト側でも編集を禁止します。
claude -p "
読み取り専用で調査してください。ファイル編集・依存追加・テスト実行はしないこと。
目的: 認証機能の実装範囲と変更リスクを把握する。
出力は次の順で:
1. 入口ファイル
2. 主要なデータモデル
3. 直接依存と間接依存
4. high / medium / low のリスク地図
5. まだ確証がない仮説(必ず『仮説』と明記)
制約: ファイル名と根拠を必ず書く。大きなファイルを全文引用しない。
"
Codex を使うなら、非対話のcodex execがデフォルトで read-only sandbox なので、初回の棚卸しにそのまま使えます。
codex exec "Read only. 認証機能の入口・データモデル・依存・リスクを整理して。ファイルは編集しないこと。"
「仮説は仮説と書け」を毎回入れているのは、AIが断定口調で外す事故を防ぐためです。確証のない推測を事実っぽく書かれると、それを信じて次の作業で転びます。「これは仮説です」と明示させるだけで、読む側の警戒レベルが正しく上がる。
調査範囲が広いときは、Claude Codeのサブエージェントに分担させて、要約だけメイン会話へ戻すと軽く回せます。具体的な分け方はサブエージェント活用パターンに書きました。ただし広すぎる依頼を出すと戻ってくる要約も肥大化するので、「API入口だけ」「DBスキーマだけ」と境界を切るのがコツです。
公式の挙動が気になったら、Claude Codeのcommon workflowsに「広い質問から始めて特定領域へ絞る」流れが載っているので、一度目を通しておくと安心です。
会話を重くしない。証拠と判断を分ける
長く調査していると、会話にファイル全文や長いログがどんどん溜まります。すると不思議なことに、AIの判断がだんだん鈍る。読ませた情報が多いほど賢くなりそうなのに、逆です。判断材料の密度が下がるからです。
僕の対策はシンプルで、会話に入れる情報を**「証拠」と「判断」に分ける**こと。ファイル全文や検索結果100件は証拠として重すぎる。先にコマンドで絞って、AIには判断しやすい単位で渡します。
そして調査が一区切りしたら、要約を作って詳細は捨てます。これは記事制作でも開発でも効く習慣です。
ここまでの調査を、次の作業者向けに300字以内で圧縮してください。
残すもの: 確認済みの入口 / high riskなファイル / 変更禁止の境界 / 未確認の仮説 / 次に打つコマンド
捨てるもの: すでに否定された仮説 / 長いログ / 読む必要のない生成物
この引き継ぎメモを CLAUDE.md に貼っておくと、翌日の自分も、相談相手も、同じ前提から再開できます。CLAUDE.md の書き方はベストプラクティスにまとめています。
よくある質問
Q. rgが無い環境です。grepでも代用できますか?
できますが、除外指定が手間です。grep -rn "pattern" . --exclude-dir=node_modules --exclude-dir=distのように毎回書く必要があります。rgは.gitignoreを自動で尊重するので、巨大リポジトリでは導入する価値が大きいです。
Q. AIに最初から「このバグ直して」と頼むのは何がダメなんですか? 所有境界と実行経路を知らないまま編集が始まるからです。古いテストや別チームの領域、生成ファイルまで会話に巻き込まれ、差分が膨らんでレビューで止まります。まず読み取り専用で経路とリスクを固めてから編集に入ると、手戻りが激減します。
Q. 依存スクリプトは動的importを拾えませんよね?
拾えません。import()の引数を文字列で組み立てる箇所や、設定ファイル経由の参照は漏れます。あくまで「触ると波及しそうな範囲の当たりをつける」道具です。最終確認はテスト実行に任せてください。
Q. サブエージェントはいつ使うべきですか? 調査範囲が広く、かつ境界を明確に切れるときだけです。「API入口」「DBスキーマ」「UIルート」のように独立した領域なら並列で効きます。逆に曖昧な依頼を投げると、戻る要約が大きくなってメイン会話が重くなり、本末転倒です。
Q. Codexでも同じ手順が使えますか?
使えます。codex execはデフォルトが read-only sandbox なので初回探索に向いています。検索の段階化も依存追跡も考え方は共通で、プロンプトをそのまま流用できます。
実際に試した結果
冒頭の「ログイン処理どこ?」を10分言い続けたリポジトリ、その後どうなったか。検索を5段階に分けて、who-imports.mjsでAuthServiceの呼び出し元を洗い、経路表を作らせたら、入口からDBまでが30分で見えました。前は半日かけて「なんとなく分かった気」だった作業です。
効いたのは順番でした。先に探し方を決めて、結果はAIに整理させ、編集は最後。この順を守ると、Claude CodeもCodexも「触るべきファイル」を自分から絞ってくれます。逆に最初から「直して」と頼んだ日は、古いテストや別チームの領域まで話が散って、コンテキストが膨らんで終わりました。
巨大コードベースは、頭から読むものじゃない。問いを立てて、線をたどる。たぶんこれが一番の近道です。実リポジトリ前提で読み方とルールを固めたいチームはClaude Code研修・導入相談から、まず手元の手順を揃えたい人は教材一覧から始めてみてください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeに1ファイルだけ直させる指示文のつくり方
「もっと良くして」で40行も変えられた失敗から学んだ、触る範囲・検証・戻し方をセットにしたClaude Code用の依頼文テンプレートを紹介します。
Claude Code の権限拒否から復旧する: 止まった理由を次の安全手順に変える
Claude Code のコマンドが拒否されたとき、焦って許可を広げずに、拒否理由、代替手順、証拠コマンド、再試行条件へ分解する方法。
Claude Codeにビルド→スモークテスト→自動修正を回させる足場の作り方
最小スモークテストの選び方、失敗ログを食わせて直させるループ、回数上限と確認ゲートで暴走を止める方法を、コピペで動くコード付きで紹介します。