사내 Claude Code 세미나 2주차 회고: 프로젝트의 두뇌를 Claude에게 전달하는 법
세미나 2주차를 마치고 가장 인상 깊었던 장면은, 같은 "Product 엔티티 만들어줘"라는 한 줄이 컨텍스트의 유무에 따라 두 명의 다른 개발자가 작성한 것처럼 다른 결과를 내는 라이브 데모였다. 참석자들의 반응이 그날 가장 컸다.
지난주 1주차에서 "Claude Code는 결국 매 요청마다 새로 조립되는 한 덩어리 JSON"이라는 멘탈 모델을 깔아두었다. 이번 주는 그 system 프롬프트 안에서 우리가 직접 통제할 수 있는 두 개의 슬롯, 즉 CLAUDE.md와 자동 메모리를 본격적으로 다뤘다. 이번 글은 그 발표를 백엔드 개발자 시점으로 풀어 적은 회고다.
1. 출발 명제: 매 세션은 새 컨텍스트로 시작한다
Claude Code 세션은 stateful해 보이지만, 그 연속성은 messages 배열의 누적일 뿐 세션 간에는 아무것도 남지 않는다. 새 세션이 시작되면 컨텍스트 윈도우는 백지에서 출발한다. 그러면 어제 합의한 컨벤션, 우리 팀의 빌드 명령, "BigDecimal 써라"라는 약속은 어디로 가는가? 두 군데로 흩어진다.
| CLAUDE.md | 자동 메모리 | |
|---|---|---|
| 작성자 | 개발자 (사람) | Claude 자신 |
| 들어가는 내용 | 지침·규칙·컨벤션 | 학습·패턴·합의 |
| 범위 | 프로젝트 / 사용자 / 조직 | 작업 트리(worktree)당 |
| 공유 | git에 커밋 가능 | 머신 로컬 (공유 안 됨) |
| 로드 | 매 세션 시작 시 전체 | 매 세션 시작 시 MEMORY.md 처음 200줄 |
발표에서 던졌던 비유: CLAUDE.md는 팀 위키에 적어둔 개발 가이드, 자동 메모리는 Claude가 포스트잇에 적어둔 메모. 둘 다 매 세션 시작에 자동으로 읽지만, 누가 작성하고 누가 책임지느냐가 다르다.
2. 같은 질문, 다른 결과: 라이브 데모로 확인한 컨텍스트의 위력
이번 세미나에서 가장 시간을 많이 들였고, 가장 반응이 좋았던 부분이다. 컨텍스트 없이 그냥 "Product 엔티티 만들어줘"를 요청하면 이런 코드가 나온다.
// CLAUDE.md 없을 때
@Data
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
private double price; // 금액에 double, BaseEntity 미상속
}Lombok @Data, 가격에 double, 검증 없음, DTO 분리 없음, BaseEntity 미상속. 딱 처음 Spring을 배울 때 짜는 코드 가 나온다. 같은 요청을 CLAUDE.md가 있는 환경에서 돌렸다.
// CLAUDE.md 있을 때
@Entity
@Table(name = "products")
public class Product extends BaseEntity {
@Id @GeneratedValue(IDENTITY)
private Long id;
@NotBlank
private String name;
@Positive
private BigDecimal price;
}
// + record DTO 자동 분리, Hexagonal 패키지 구조 반영같은 한 줄 요청, 너무 다른 결과물. 데모를 본 직후 한 챕터원이 "이 정도면 입사 후 시니어 시니어 코드 리뷰를 두세 번 받은 뒤 짜는 코드 아닌가요?"라고 했다. 정확히 그게 핵심이다. CLAUDE.md는 그 팀의 코드 리뷰 누적분을 한 파일에 응축해 둔 것이라고 설명했다.
3. CLAUDE.md를 어디에 둘 것인가: 멀티모듈에서 빛난다
CLAUDE.md는 한 곳이 아니다. 4가지 위치에 둘 수 있고, 더 구체적인 위치가 더 광범위한 위치를 이긴다. 백엔드 멀티모듈 프로젝트에서 이 우선순위는 거의 모듈 격리 정책처럼 작동한다.
/Library/Application Support/ClaudeCode/CLAUDE.md ← 조직 정책 (제외 불가)
~/.claude/CLAUDE.md ← 사용자 모든 프로젝트
./CLAUDE.md 또는 .claude/CLAUDE.md ← 프로젝트 (git 공유)
./module-api/CLAUDE.md ← 모듈별 (지연 로드)발표에서 들었던 Spring Boot 멀티모듈 예시:
my-spring-app/
CLAUDE.md # 공통: Hexagonal, Java 21, Gradle
module-api/CLAUDE.md # API 전용: REST 컨벤션, DTO 규칙
module-batch/CLAUDE.md # Batch 전용: Chunk 크기, Reader/Writer 패턴
module-domain/CLAUDE.md # Domain 전용: Entity, VO 규칙module-api/ 안에서 작업을 시작하면 module-api/CLAUDE.md와 루트 CLAUDE.md가 함께 로드되고, module-batch/CLAUDE.md는 Claude가 해당 디렉토리의 파일을 만질 때 비로소 로드된다. 모놀리식 단일 파일에 모든 규칙을 욱여넣지 말고, 모듈의 책임 경계대로 CLAUDE.md를 나누자는 게 이번 주차의 가장 실용적인 결론이었다.
다른 팀 모듈의 CLAUDE.md가 잘못 끌려 들어오는 게 걱정된다면 claudeMdExcludes로 차단할 수 있다.
// .claude/settings.local.json
{
"claudeMdExcludes": [
"**/module-payment/CLAUDE.md",
"**/module-legacy/.claude/rules/**"
]
}4. 효과적인 작성법: 200줄 이하 · 구체적이고 · 검증 가능하게
발표에서 가장 강조한 세 가지 원칙이다.
- 200줄 이하: 길면 토큰을 낭비하고, 또 부풀려진 파일은 Claude가 규칙을 무시하게 만든다. 시니어 코드 리뷰에서 1500자짜리 PR 코멘트가 한 줄도 반영 안 되는 이유와 같다.
- 마크다운 구조:
# 빌드,# 아키텍처,# 컨벤션처럼 섹션을 나눠야 검색·갱신이 쉽다. - 검증 가능한 구체성: "코드를 잘 짜세요" 대신 "금액은 BigDecimal, Lombok @Data 금지, record DTO 사용" 처럼 적는다.
스스로에게 던지는 질문도 알려줬다.
각 줄에 대해 "이 줄을 빼면 Claude가 @Data를 쓸까? double로 가격을 처리할까?"를 물어본다. 답이 "아니오"라면 그 줄은 지운다.
세미나에서 가장 자주 받은 한 가지 질문: "그래도 IMPORTANT 강조를 박으면 더 잘 지키지 않나요?" 답: 그렇다. 다만 "IMPORTANT" 남발은 그 무게를 가볍게 만든다. 정말 어기면 안 되는 줄에만 박는다. 한 파일에 한두 개가 적당하다.
5. @import와 .claude/rules/: CLAUDE.md를 분할 정복
CLAUDE.md가 어느 순간 200줄을 넘어가기 시작하면 두 가지 무기를 꺼낸다.
@import 구문: 다른 파일을 끌어와 합칠 수 있다. 최대 5단계까지 재귀.
# CLAUDE.md
프로젝트 구조는 @README.md를 참조
Gradle 명령 목록은 @build.gradle.kts를 참조
API 설계 규칙은 @docs/api-conventions.md를 참조
# 개인 설정 (git에 안 올라감)
- @~/.claude/my-spring-preferences.md.claude/rules/: 경로별 frontmatter로 특정 파일을 만질 때만 활성화되는 규칙을 분리한다.
# .claude/rules/entity-rules.md
---
paths:
- "**/domain/**/entity/**/*.java"
- "**/domain/**/model/**/*.java"
---
# Entity 작성 규칙
- BaseEntity를 반드시 상속 (createdAt, updatedAt 자동 관리)
- Lombok @Data 금지 → @Getter + @NoArgsConstructor(access = PROTECTED)
- @Table(name = "복수형") 명시
- 금액: BigDecimal, 날짜: LocalDateTime
- Bean Validation 어노테이션 필수 (@NotBlank, @Positive 등)# .claude/rules/controller-rules.md
---
paths:
- "**/adapter/in/web/**/*.java"
---
# Controller 규칙
- Entity 직접 반환 금지 → record DTO로 변환
- 응답은 ApiResponse<T> 래핑
- @Valid로 요청 DTO 검증
- 예외 처리는 @RestControllerAdvice에 위임이 패턴은 백엔드 개발자에게 너무도 익숙하다. AOP의 pointcut 같은 발상이다. "이런 경로의 파일을 만질 때만 이 규칙을 끼워라"라는 횡단 관심사 분리. 발표 중 누군가 정확히 그 단어를 꺼냈을 때, 모든 사람의 머리에 그림이 그려진 게 보였다.
6. 자동 메모리: Claude가 스스로 적는 포스트잇
CLAUDE.md가 사람이 적는 위키라면, 자동 메모리는 Claude가 세션 중에 배운 것을 스스로 적어두는 메커니즘이다. 위치는 머신 로컬이고 git에 올라가지 않는다.
~/.claude/projects/<project>/memory/
├── MEMORY.md # 인덱스 (앞 200줄만 매 세션 자동 로드)
├── debugging.md # 디버깅 패턴
└── conventions.md # 코딩 컨벤션작동 흐름은 단순하다.
[세션 1]
개발자: "mvn 말고 gradle로 테스트 실행해줘"
개발자: "@Data 쓰지 마, @Getter만 써"
↓
Claude가 MEMORY.md에 자동 기록
- ./gradlew test 사용
- Lombok @Data 대신 @Getter
- AssertJ assertThat 선호
[세션 2: 다음 날]
개발자: "Order 엔티티 만들어줘"
↓
Claude: @Getter 사용 (not @Data), BigDecimal 가격, Bean Validation 포함
이전 수정사항이 자동 반영된 코드여기서 강조한 분리: MEMORY.md는 인덱스다. 개별 메모리는 별도 파일로 있고, Claude가 필요할 때만 직접 읽는다. 즉 자동 메모리도 1주차에서 다룬 Skill 3단계 로딩과 같은 발상이다. 모든 것을 항상 들고 다니지 말고, 인덱스만 들고 다니다가 필요할 때 읽자.
사용자가 직접 적게 만드는 법
발표에서 흥미로워했던 부분: 자동 메모리는 사실 사용자가 부를 수도 있다.
- "이 프로젝트는 Testcontainers로 PostgreSQL 테스트한다는 것을 기억해줘" → 자동 메모리에 저장
- "이것을 CLAUDE.md에 추가해줘" → CLAUDE.md에 기록
- "/memory" 명령으로 로드된 파일 확인, 토글, 직접 편집 가능
이 분리를 알면 "팀 공유할 규칙인가? CLAUDE.md. 내 머신에만 쌓이는 학습인가? 자동 메모리."로 머릿속 라우팅이 깔끔해진다.
7. 트러블슈팅: 실전에서 가장 자주 부딪치는 4가지
발표 마지막 섹션. 실제로 사내에서 자주 보고된 문제들이다.
- "Claude가 @Data를 계속 써요": CLAUDE.md 규칙이 모호하거나 미로드.
/memory로 확인 후, "Lombok @Data 사용 금지. IMPORTANT: @Getter + @NoArgsConstructor(access = PROTECTED)만 사용"처럼 구체화. - "경로별 규칙이 적용 안 돼요": paths glob 패턴이 실제 경로와 불일치.
InstructionsLoadedHook으로 로드 시점 추적.**/entity/**vs**/domain/**차이를 확인. - "/compact 후에 규칙이 사라져요": 대화 중에 말로만 전달한 규칙은
/compact시 휘발한다. 반복 지킬 규칙은 반드시 CLAUDE.md에 기록. CLAUDE.md는/compact후 디스크에서 다시 읽힌다. - "CI에서 절대 어기면 안 되는 규칙이 무시돼요": CLAUDE.md는 사용자 메시지 레벨이라 모호하면 무시될 수 있다. 시스템 프롬프트 레벨로 박아 넣으려면
--append-system-prompt사용.
claude -p "리팩토링해줘" \
--append-system-prompt "Entity를 Controller에서 직접 반환하지 마세요"이 한 줄은 사내 CI에 Claude를 붙이려고 시도 중인 팀에게 특히 유용했다. CI는 사람이 말로 보정하지 못하니, 시스템 프롬프트 레벨로 못 박아두는 안전장치가 반드시 필요하다.
8. 시니어의 시선으로 정리하는 인사이트
① CLAUDE.md는 "팀의 코드 리뷰 누적분"을 한 파일에 응축한 것
신입에게 매번 같은 코멘트를 다는 일이 사라진다. 단, 그 코멘트가 구체적이고 검증 가능할 때만. "Lombok @Data 금지" 같은 줄 한 줄이, 일주일에 두세 번 반복되던 PR 코멘트를 영구히 사라지게 한다. 시니어 입장에서 CLAUDE.md를 잘 쓰는 것은 자기 시간을 가장 크게 되찾는 자동화 다.
② 멀티모듈에서는 CLAUDE.md도 모듈 책임 경계대로 분리한다
루트엔 공통, 각 모듈엔 그 모듈의 도메인 규칙. module-batch의 Chunk 크기 규칙이 module-api의 컨트롤러 생성에 끼어드는 일이 없게. 이건 단순히 토큰 절약이 아니라 컨텍스트 오염 방지의 문제다.
③ paths frontmatter는 LLM 시대의 AOP다
특정 파일을 만질 때만 활성화되는 횡단 규칙. Entity / Controller / Test / Batch 각각의 컨벤션을 글로벌 규칙으로 박지 말고, 그 파일을 건드릴 때만 적용되는 규칙으로 분리하자. 백엔드 시니어에겐 너무도 익숙한 패턴이라, 발표장에서 가장 빠르게 합의에 도달한 부분이었다.
④ "팀 공유 = CLAUDE.md / 머신 로컬 = 자동 메모리" 라우팅
이걸 머릿속에 깔아두면, "이 규칙을 어디 적어야 하지?"라는 질문이 사라진다. 팀이 같이 따라야 하면 CLAUDE.md, 내 머신 습관이면 자동 메모리. 두 메커니즘은 경쟁자가 아니라 역할 분담이다.
⑤ CI에 Claude를 붙일 때는 반드시 --append-system-prompt로 못 박아라
CLAUDE.md의 모호성 허용은 사람과 대화할 때의 미덕이지, 자동화에 적합한 특성은 아니다. 자동화 경로에서는 강제력이 더 강한 시스템 프롬프트 레벨로 올려두는 것이 정답이다.
마무리: 컨텍스트를 설계한다는 것은 팀의 표준을 설계하는 일이다
발표를 정리하면서 다시 한번 또렷해진 한 줄.
CLAUDE.md를 잘 쓰는 일은 결국 우리 팀의 코드 표준을 다시 한 번 정의하는 일이다.
신입에게 줄 가이드, PR에서 매번 반복하는 코멘트, "이건 우리 팀은 이렇게 짜요"라고 입으로만 전해지던 약속들. 그걸 모두 한 파일에 정착시키는 작업은 곧 팀의 암묵지를 형식지로 옮기는 일이다. 그 결과물의 첫 번째 수혜자는 사람이 아니라 Claude지만, 일이 끝나면 가장 크게 남는 자산은 결국 우리 팀의 글이다.
다음 주는 Hooks 편이다. CLAUDE.md가 "Claude에게 무엇을 전하느냐"의 영역이었다면, Hooks는 "Claude의 모든 동작 시점에 어떻게 끼어드느냐"의 영역이다. 1주차에서 깔아둔 모델 루프의 그림 위에 정확히 얹어 설명할 수 있을 것 같다. 또 한 주 잘 준비해서 만나자고 인사하며 회의실을 나왔다.