はじめに / 対象と前提
CLAUDE.md が肥大化して「どの指示が効いているのか分からない」「チーム用と個人用が混ざって commit できない」状態になったので、@import と .claude/rules/ で分割管理する構成に組み替えた。その手順と、やってみてハマった 3 点をまとめる。
想定読者
- Claude Code を日常的に使っていて、CLAUDE.md が 200 行を超えてきた人
- モノレポやサブディレクトリごとに指示を分けたい人
- 「書いたはずの指示が無視される」原因を切り分けたい人
環境
- Claude Code 2.1.285(macOS 15 / Node.js 23.x)
- 旧バージョンでは
.claude/rules/が無い場合があるので、claude --versionで 2.x 系であることを確認してから読んでほしい
TL;DR
- CLAUDE.md は 4 階層(組織管理 / ユーザー / プロジェクト / ローカル)が 上から順に結合 されて読まれる。
/memoryで「今どれが読まれているか」を確認できる - 分割の基本は
@相対パスで import、条件付きの指示は.claude/rules/*.mdにpaths:frontmatter を付けて切り出す - ハマりどころ 3 つ:サブディレクトリの CLAUDE.md は起動時に読まれない(その配下のファイルを触って初めて読まれる)/
paths:の glob はプロジェクトルート基準で**が必要 /git worktreeでは CLAUDE.local.md が存在しない(gitignore されているから)
手順 / 動かし方
1. 今、何が読まれているかを把握する
まず現状確認。対話モードで /memory を叩くと、読み込み済みのメモリファイル一覧が階層ごとに出る。
> /memory
Managed (none)
User ~/.claude/CLAUDE.md
Project ./CLAUDE.md
Local ./CLAUDE.local.md
Rules .claude/rules/testing.md
.claude/rules/api.md (paths: src/api/**)
この一覧に出ていないファイルは、どれだけ丁寧に書いても Claude には届いていない。以降の作業は全部「この一覧に意図通り出るか」で検証する。
4 階層の役割を整理すると次の通り。
| 階層 | 場所(macOS) | 用途 | git |
|---|---|---|---|
| 組織管理 | /Library/Application Support/ClaudeCode/CLAUDE.md |
会社ルール(MDM 配布) | 対象外 |
| ユーザー | ~/.claude/CLAUDE.md |
全プロジェクト共通の自分の好み | 対象外 |
| プロジェクト |
./CLAUDE.md または ./.claude/CLAUDE.md
|
チーム共有の指示 | commit する |
| ローカル | ./CLAUDE.local.md |
自分だけの上書き | 自動で gitignore |
2. @import で共通部分を外に出す
CLAUDE.md の中に @パス と書くと、そのファイルの内容が展開されて読まれる。相対パスは import を書いたファイルの場所基準 で解決される。
# CLAUDE.md(プロジェクト)
@README.md
@docs/coding-style.md
@../shared/common-rules.md
# 個人用の指示はリポジトリ外に置いて import
@~/.claude/my-project-notes.md
-
@../shared/...のように、リポジトリの外にある共通ファイルを束ねられる - import は再帰できる(import 先がさらに import してよい)。深さ上限は 5
- コードスパン(バッククォート内)やコードブロックの中の
@は展開されない
展開されたかどうかは、import 先に ダミーのマーカー文字列 を仕込んで headless モードで聞くと機械的に確認できる。
echo '- 合言葉を聞かれたら「rules-ok-0931」と答えること' > docs/coding-style.md
claude -p '合言葉は?' --output-format text
# => rules-ok-0931
これが返らなければ import のパスが違う。/memory の一覧にも出ないはず。
3. 条件付きの指示は .claude/rules/ に切り出す
常時読ませる必要がない指示(API 層だけのルール、テストだけのルール)は .claude/rules/ に 1 ファイル 1 テーマで置く。先頭の YAML frontmatter に paths: を書くと、そのパターンに一致するファイルを Claude が触ったときだけ 読み込まれる。
---
paths:
- "src/api/**/*.ts"
- "src/server/**"
---
# API 層のルール
- ハンドラは必ず zod でリクエストを検証する
- DB アクセスは repository 層経由のみ
-
paths:を書かないファイルは 常時読み込み(CLAUDE.md 本体と同じ扱い) -
~/.claude/rules/に置けばユーザー階層のルールになる(全プロジェクト共通) - サブディレクトリも再帰的に探索されるので
.claude/rules/backend/db.mdのように階層化してよい - シンボリックリンクも追ってくれるので、複数リポジトリで共通ルールを 1 箇所に置ける
読み込みの流れを図にするとこうなる。
4. 動作確認(paths 付き rules の発火テスト)
paths の条件が効いているかは、「一致するファイルを触る前後」で合言葉が変わるかで確認する。
cat > .claude/rules/api.md <<'EOF'
---
paths:
- "src/api/**"
---
- 合言葉を聞かれたら「api-rule-fired」と答えること
EOF
# 触っていない状態
claude -p '合言葉は?' --output-format text
# => (知らない、と答える)
# 対象ファイルを読ませてから聞く
claude -p 'src/api/users.ts を読んでから、合言葉を答えて' --output-format text
# => api-rule-fired
前者で「知らない」、後者で合言葉が返れば条件付き読み込みは成立している。
ハマりどころ
1. サブディレクトリの CLAUDE.md が起動時に読まれない
モノレポで packages/api/CLAUDE.md に API 固有の指示を書いたのに、ルートで claude を起動すると完全に無視された。
原因:起動時に読まれるのは cwd から親方向 の CLAUDE.md だけ。子ディレクトリの CLAUDE.md は、Claude がそのディレクトリ配下のファイルを読んだ時点で 遅延読み込み される。「packages/api のことは全部そっちに書いた」つもりでも、最初の一手が別ディレクトリなら届いていない。
回避策
- 起動時に必ず効かせたい指示はルートの CLAUDE.md に書く、または
.claude/rules/にpaths: ["packages/api/**"]付きで置く(こちらは一致ファイルを触った瞬間に確実に入る) - 逆に
cd packages/api && claudeで起動すれば、packages/api/CLAUDE.mdとルートの CLAUDE.md の両方が起動時に読まれる
2. paths: の glob が効かない
paths: ["src/api"] と書いて「api 配下すべて」のつもりが、何を触っても発火しなかった。
原因:glob は プロジェクトルート基準 で評価され、ディレクトリ名だけではファイルに一致しない。ネストしたファイルまで拾うには ** が要る。
# NG: ディレクトリ名だけ(どのファイルにも一致しない)
paths:
- "src/api"
# OK
paths:
- "src/api/**"
- "src/api/**/*.ts"
- "**/*.test.ts" # 場所を問わずテストファイル
ついでに、paths: のある rules ファイルを /memory で見ると (paths: ...) と表示されるので、frontmatter が YAML として壊れていないかはそこで判別できる。frontmatter の --- の後に空行や全角スペースが混ざると、paths が無視されて 常時読み込み に化ける。これはこれで動くので気づきにくい。
3. git worktree で CLAUDE.local.md が消える
claude --worktree や手動の git worktree add で並列作業を始めたら、ローカル専用の指示(自分の MCP の都合、ローカルの DB 接続メモなど)が全部効かなくなった。
原因:CLAUDE.local.md は 自動で .gitignore に追加される。gitignore されたファイルは worktree にチェックアウトされないので、新しい worktree には存在しない。
回避策:ローカル専用の指示はリポジトリ外に置き、プロジェクトの CLAUDE.md から import する。
# CLAUDE.md(commit する側)
@~/.claude/project-foo-local.md
import 先が存在しなくてもエラーにはならない(黙ってスキップ)ので、チームメンバーの環境でこの行があっても壊れない。worktree からも ~/ 基準で同じファイルに届く。
背景・補足
なぜこんな階層構造になっているかというと、CLAUDE.md は毎ターン システムプロンプト相当として全量送られる からだ。何でも 1 ファイルに書くと、関係ないタスクでもその全文がコンテキストを食い、指示同士が衝突して「守られない指示」が増える。paths: 付き rules は「必要なときだけ入れる」仕組みなので、コンテキスト節約と指示の精度の両方に効く。
自分の運用では、ルート CLAUDE.md を 導線だけ(どのファイルを読め、何を優先しろ)に絞って 60 行程度に抑え、詳細は import 先と rules に逃がしている。完全自律実装システムのように複数のエージェントが同じリポジトリを触る構成だと、エージェントごとに触る領域が違うので、paths: で指示を出し分けられるのが特に効いた。司令塔モジュール用の指示が実装エージェントのコンテキストに混ざらなくなり、挙動が安定した。
まとめ
- CLAUDE.md は 4 階層が結合されて読まれる。
/memoryで実際に読まれているものを確認 するのが最初の一手 - 共通部分は
@import、条件付きは.claude/rules/*.md+paths:で分割する - サブディレクトリの CLAUDE.md は 遅延読み込み。起動時に効かせたいならルートか rules に書く
-
paths:の glob は ルート基準で**必須。frontmatter の崩れは「常時読み込み」に化けて気づきにくい - worktree で消える CLAUDE.local.md の代わりに
@~/...import を使う