Claude Code의 CLAUDE.md를 @import와 .claude/rules/로 분할 관리하는 구현 절차 — 서브디렉토리의
요약
Claude Code의 CLAUDE.md 파일을 `@import`와 `.claude/rules/`를 활용하여 모듈화하고 관리하는 방법을 설명합니다. 이 가이드는 대규모 지시 파일 관리가 어려워진 개발자들을 위해, 여러 계층(조직, 사용자, 프로젝트, 로컬)에 걸쳐 규칙을 분리하고 적용하는 실질적인 절차와 3가지 함정사항을 다룹니다.
핵심 포인트
- CLAUDE.md는 4개 계층(조직/사용자/프로젝트/로컬)으로 구성되며 순차적으로 결합됩니다.
- 지시 분할은 `@상대경로` import와 `.claude/rules/*.md`의 `paths:` frontmatter를 사용합니다.
- 서브디렉토리의 CLAUDE.md는 해당 하위 파일을 직접 건드려야만 읽힙니다.
- import 경로는 프로젝트 루트 기준에서 `**` glob을 사용하는 것이 중요하며, 깊이 제한은 5입니다.
CLAUDE.md 파일이 너무 커져서 '어떤 지시가 적용되고 있는지 알 수 없다', '팀용과 개인용이 섞여 커밋할 수 없다'는 상황에 처하자, @import와 .claude/rules/를 이용해 분할 관리하는 구성으로 재편했다. 그 절차와 직접 경험하며 부딪혔던 3가지 함정사항을 정리한다.
예상 독자
- Claude Code를 일상적으로 사용하며 CLAUDE.md가 200줄을 넘긴 사람
- 모노레포(monorepo)나 서브디렉토리별로 지시를 나누고 싶은 사람
- '작성한 지시가 무시되는' 원인을 분리하고 싶은 사람
환경
-
Claude Code 2.1.285 (macOS 15 / Node.js 23.x)
-
구 버전에서는
.claude/rules/가 없는 경우가 있으므로,claude --version으로 2.x 계열인지 확인한 후 읽어주었으면 한다. -
CLAUDE.md는 4개 계층(조직 관리 / 사용자 / 프로젝트 / 로컬)이 위에서부터 순차적으로 결합되어 읽힌다.
/memory로 '현재 무엇이 읽히고 있는지'를 확인할 수 있다. 분할의 기본은, 조건부 지시는@상대경로로 import를 붙여 분리하는.claude/rules/*.md에paths:frontmatter를 사용하는 것이다. -
함정사항 3가지:
- 서브디렉토리의 CLAUDE.md는 시작 시 읽히지 않는다(해당 하위 파일을 건드려야 비로소 읽힌다).
paths:의 glob은 프로젝트 루트 기준에서**가 필요하다(gitignore 되어 있어도).git worktree에서는 CLAUDE.local.md가 존재하지 않는다.
먼저 현재 상황을 확인해 보자. 대화 모드에서 /memory를 실행하면, 읽어 들인 메모리 파일 목록이 계층별로 나온다.
> /memory
Managed (none)
User ~/.claude/CLAUDE.md
...
이 목록에 나와 있지 않은 파일은 아무리 정성껏 작성해도 Claude에게 전달되지 않는다. 이후의 작업은 전부 '이 목록에 의도대로 나오는지'로 검증한다.
4개 계층의 역할을 정리하면 다음과 같다.
| 계층 | 위치(macOS) | 용도 |
|---|---|---|
| 조직 관리 | /Library/Application Support/ClaudeCode/CLAUDE.md | 회사 규칙 (MDM 배포) |
| 사용자 | ~/.claude/CLAUDE.md | 모든 프로젝트 공통의 자신의 선호도 |
| 프로젝트 | ./CLAUDE.md 또는 ./.claude/CLAUDE.md | 팀 공유 지시 |
| 로컬 | ./CLAUDE.local.md | 자신만 덮어쓰는 내용 |
CLAUDE.md 안에 @경로라고 쓰면, 해당 파일의 내용이 전개되어 읽힌다. 상대 경로는 import를 작성한 파일의 위치 기준으로 해결된다.
# CLAUDE.md(프로젝트)
@README.md
@docs/coding-style.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 목록에도 나오지 않을 것이다.
항상 읽히도록 할 필요가 없는 지시(API 계층만의 규칙, 테스트만의 규칙)는 .claude/rules/에 1 파일당 1 주제로 배치한다. 맨 앞의 YAML frontmatter에 paths:
이렇게 작성하면, 해당 패턴에 일치하는 파일을 Claude가 건드릴 때만 로드된다.
---
paths:
- "src/api/**/*.ts"
...
paths:
을 쓰지 않은 파일은 상시 로드(CLAUDE.md 본체와 동일하게 취급)
~/.claude/rules/
에 두면 사용자 계층의 규칙이 된다(모든 프로젝트 공통) - 서브디렉토리도 재귀적으로 탐색되므로
.claude/rules/backend/db.md
처럼 계층화해도 좋다 - 심볼릭 링크도 따라가 주므로, 여러 리포지토리에서 공통 규칙을 한 곳에 둘 수 있다.
로드 흐름을 다이어그램으로 그리면 다음과 같다.
paths의 조건이 작동하는지는 '일치하는 파일을 건드리기 전후'에 암호 같은 것이 바뀌는지로 확인한다.
cat > .claude/rules/api.md <<'EOF'
---
paths:
...
전자의 경우 '모른다', 후자의 경우 암호가 돌아오면 조건부 로드는 성립한 것이다.
모노레포에서 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 둘 다 시작 시 로드된다.
paths: ["src/api"]
라고 써서 'api 하위 전부'인 줄 알았는데, 무엇을 건드려도 발동하지 않았다.
원인: glob은 프로젝트 루트 기준으로 평가되며, 디렉토리 이름만으로는 파일과 일치하지 않는다. 중첩된 파일까지 잡으려면 **
가 필요하다.
# NG: 디렉토리 이름만 (어떤 파일과도 일치하지 않음)
paths:
- "src/api"
...
덧붙여, paths:
이 있는 rules 파일을 /memory
에서 보면 (paths: ...)
이라고 표시되므로, frontmatter가 YAML로서 깨지지 않았는지는 거기서 판별할 수 있다. frontmatter의 ---
뒤에 공백이나 전각 공백이 섞이면 paths가 무시되어 상시 로드로 변질된다. 이것도 작동하기 때문에 알아차리기 쉽지 않다.
claude --worktree
나 수동의 git worktree add
로 병렬 작업을 시작하면, 로컬 전용 지시사항(자신의 MCP 사정, 로컬 DB 연결 메모 등)이 전부 작동하지 않게 되었다.
원인: CLAUDE.local.md는 자동으로 .gitignore에 추가된다. gitignore된 파일은 worktree에 체크아웃되지 않으므로, 새로운 worktree에는 존재하지 않는다.
회피책: 로컬 전용 지시사항은 리포지토리 외부에 두고, 프로젝트의 CLAUDE.md에서 import 한다.
# CLAUDE.md(커밋하는 쪽)
@~/.claude/project-foo-local.md
import할 곳이 존재하지 않아도 에러가 나지 않는다(조용히 스킵)는 장점이 있어, 팀원 환경에서 이 줄이 있어도 망가지지 않는다. worktree에서도 ~/
기준으로 같은 파일에 도달한다.
왜 이렇게 계층 구조가 되어 있는지냐면, CLAUDE.md는 매 턴 시스템 프롬프트 상당으로 전체 내용이 전송되기 때문이다. 무엇이든 하나의 파일에 쓰면, 관계없는 태스크에서도 그 전문이 컨텍스트를 차지하고, 지시사항끼리 충돌하여 '지켜지지 않는 지시'가 늘어난다. 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 대신 @~/...를 사용하는 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기