좋은 CLAUDE.md는 README가 아니다. Claude가 코드에서 유추 못 하는 invariant만 담는다. 써야 할 6개, 빼야 할 4개, 5가지 질문.
내 Pickful 프로젝트에는 탈중앙 커뮤니티 배심원 시스템, x402 암호화폐 결제, Sign-In with Ethereum, 다중 데이터베이스, 실시간 푸시 — 지난 1~2년 사이에 등장한 기술 스택이 전부 들어가 있다. Claude는 이 기능들을 빠르고 깔끔하게 구현한다.
그런데 프로젝트의 CLAUDE.md를 열어보면 배심원 시스템과 x402라는 단어는 한 번도 등장하지 않는다.
이건 누락이 아니다. CLAUDE.md의 역할은 "기능을 설명하는 것"이 아니라, Claude가 코드를 한 번 읽어서는 절대로 알아낼 수 없는 것을 적는 것이다.
CLAUDE.md를 처음 쓰는 사람은 대개 README처럼 핵심 기능 하나하나를 설명하려 든다.
이런 내용은 Claude가 topic_review_service.rb / x402.rb / like_points_service.rb를 여는 편이 당신이 쓴 것보다 더 정확하다. 천 자짜리 비즈니스 로직 설명을 Claude는 코드에서 수백 토큰이면 읽고, 해석 왜곡도 없다 — 코드는 사실, 설명은 2차 자료다.
Claude가 정말로 넘어지는 지점은 아래 6가지다.
Pickful의 CLAUDE.md에는 이런 줄들이 있다:
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
모든 줄이 기본 추측에 맞서고 있다. Rails 프로젝트를 보면 Claude는 기본적으로 이렇게 가정한다:
CLAUDE.md에 적어두지 않으면, Claude에게 새로운 JS 기능을 추가시켰을 때 Webpacker를 설치하고 package.json을 고치고 bundler 설정을 쓸 가능성이 크다 — 모두 틀렸고, 조용히 틀린다 (앱은 돌아가지만 자산 파이프라인이 오염된다).
CLAUDE.md의 이 몇 줄은 Claude에게 말하고 있다: 추측하지 마라, 이미 결정됐다.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
문장은 평범하지만 막아주는 함정은 하룻밤 분량일 수 있다. Rails 8의 기본 멀티 DB는 새 동작이고, Claude는 몇 개 DB를 쓰는지 자발적으로 확인하지 않는다. 별 상관없어 보이는 migration이 잘못된 DB에 착지해도 개발 중에는 에러가 안 난다 (4개 다 PostgreSQL, schema는 어디서든 통과). 하지만 프로덕션에서 Solid Queue의 job 테이블이 primary 백업에 섞이거나, primary의 모델이 cache DB를 조회하는 일이 벌어지고 — 이런 버그는 시간이 지나야 수면 위로 올라온다.
CLAUDE.md의 두 줄 vs. 하루치 프로덕션 디버깅.
/p-{slug} - Short post URLs (4-5 char alphanumeric)
/t-{slug} - Topic URLs (3-4 char alphanumeric)
/s-{code} - Short URL redirects (3-4 char alphanumeric)
/r-{referral} - Referral links
라우트 자체는 Claude가 routes.rb에서 읽을 수 있다. 하지만 길이 관례(4-5자, 3-4자)는 모델이나 service의 slug 생성 로직에 묻혀 있다. 새 단축 URL 타입을 추가시키면 Claude는 6자리, UUID 스타일, 또는 순수 숫자로 slug를 만들 가능성이 크다 — 전체 시스템의 시각 언어와 어긋난다.
이런 "관례"의 특징: 위반해도 에러 안 나지만, 나중에 코드를 보는 사람이 "뭔가 이상하다"고 느낀다. 반드시 적어야 한다.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
두 숫자 모두 코드 어딘가(User#vip?, Post#hot? scope)에 있다. 문제는 Claude가 관련 기능을 건드릴 때 — 포인트 보상 조정, "곧 VIP" 알림 추가, "핫 포스트 고정" 크론 작성 — 다른 위치의 임계값을 자발적으로 맞추지 않는다는 점이다.
결과: 어떤 작업 완료에 500점을 주고 카피는 "VIP가 될 수 있다"고 쓴다 (실제 400점이면 충분). 새 기능 시드 데이터에 좋아요를 부족하게 넣어서 15 임계값을 영원히 못 넘긴다.
Claude의 코딩 능력은 강하지만 시스템 전체의 숫자 감각은 없다. 핵심 임계값을 CLAUDE.md에 넣으면 대화 시작부터 "400과 15는 특별한 숫자"임을 알고 출발한다.
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
이 줄의 역할은 내비게이션, 설명이 아니다.
이게 없으면 Claude가 새 권한 체크를 추가할 때 세 가지 가능성이 있다:
unless current_user.admin? 박아서 쓰기authorize? 메서드 추가"Pundit policies in app/policies/" 한 줄이 있으면 Claude는 매번 app/policies/에 policy 파일을 추가해서 스타일이 통일된다.
한 줄로 Claude의 매번의 "탐정 작업"을 없앤다.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
새 기능 추가 시 Claude 기본값은:
하지만 당신의 프로젝트가 실제로 필요한 건:
이런 "프로젝트 범위 외부 제약"을 어기면 번역 보충, 테스트 재작성 같은 자잘한 후처리가 쏟아진다. CLAUDE.md에 적으면 "매번 반드시 해야 하는 것들"을 한 번에 못 박아 둔다.
"반드시 써야 하는 것"만큼 중요한 게 "쓰지 말 것"이다. 아래 유형은 보이는 족족 지워라:
1. 기능 설명
"배심원 시스템: 사용자는 위반 콘텐츠를 신고할 수 있고, 신고된 콘텐츠는 공개 투표 단계로 진입하며, 배심원은..."
→ Claude가 topic_review_service.rb를 열어서 당신이 쓴 것보다 더 정확히 읽는다. 매번 새 대화에 이걸 밀어넣는 건 순수 낭비.
2. 디렉토리 구조 / Gemfile에서 바로 보이는 것
"app/models/에는 ActiveRecord 모델", "Rails 8 사용", "DB는 PostgreSQL"
→ Claude가 프로젝트 루트와 Gemfile을 한 번 훑으면 안다.
3. 일반적인 프로그래밍 상식
"Controllers should be thin, delegate to services", "N+1 쿼리 피하기", "주요 기능 테스트 커버"
→ 이미 Claude의 훈련 데이터에 있다. 당신 프로젝트가 비정상적일 때만 쓴다 — 예를 들어 "우리는 일부러 service 계층을 안 쓴다, 로직은 controller에 둔다".
4. 현재 작업의 문맥
"지금 결제 시스템을 리팩토링 중, 초점은..."
→ 이건 대화 문맥이지 프로젝트 사실이 아니다. CLAUDE.md에 넣으면 다른 모든 대화를 오염시킨다.
"나도 할 수 있다"는 증거를 주장보다 앞에 두자. 위 섹션을 다 쓰고 나서 자신의 5가지 질문으로 Pickful의 CLAUDE.md — 238줄 — 를 한 바퀴 돌렸다. 결과: 대략 절반이 낭비였다.
잘라내야 할 부분 (약 120줄):
개발 명령어 섹션의 대부분 (70줄 → 10줄): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman은 전부 표준 Rails 명령어, Claude 훈련 데이터 안의 것. 프로젝트 고유 세 개만 남긴다: bin/jobs (Solid Queue worker), bin/importmap pin (ImportMap 전용), bin/kamal deploy.
Core Domain Models 리스트 (35줄 → 싹 삭제): 20개 모델 이름과 역할을 쭉 나열하는 건 가장 전형적인 "README 스타일 CLAUDE.md" — Claude가 ls app/models/를 돌리거나 모델 파일 하나만 읽어도 안다. 매 대화에 밀어 넣는 건 순수 낭비.
Tech Stack의 표준 항목 (28줄 → 8줄): Rails 8 / Devise / Pundit / Tailwind / pg_search는 전부 Gemfile에서 바로 읽을 수 있다. 반직관적인 것만 남긴다: Propshaft / ImportMap / Lexxy / x402-rails / Grover.
흩어진 일반 프로그래밍 상식 몇 줄: "Controllers should be thin", "Use app/jobs/ for async processing", request specs / model specs가 각각 뭘 테스트하는지 — Claude가 기본적으로 하는 것들. 토큰만 잡아먹는다.
남은 약 100줄: URL 라우팅 관례, 4 DB 분업, VIP 400 / Hot 15 두 개의 단단한 임계값, Lexxy override, 반기본 아키텍처 선택, Pundit 내비, 로케일 리스트, FactoryBot (not fixtures).
하지만 더 중요한 것: 아직 쓰지 않은 invariant는 무엇인가?
감사하면서 깨달은, 추가해야 하는데 한 번도 추가하지 않은 몇 가지:
238줄을 100~120줄로 줄이고, 이전에 빠뜨렸던 5~10줄의 핵심 invariant를 추가한다 — 이게 "올바른 밀도"에 가까운 CLAUDE.md다.
이건 튜토리얼이 아니라 내 자신의 프로젝트에 대한 감사다. 나도 제대로 못 썼다. CLAUDE.md의 올바른 형태는 계속 지우고 계속 더하는 것 — 프로젝트가 조금이라도 성숙해지면 CLAUDE.md는 점점 더 짧고, 더 단단해져야 한다.
CLAUDE.md에 뭘 추가하려 할 때마다 이 체크리스트를 돌린다:
5가지를 다 통과하는 규칙만 남긴다; 하나라도 답이 안 나오는 건 지우거나 다시 쓴다.
CLAUDE.md의 올바른 용법은 "프로젝트 소개"가 아니라, 너와 코드베이스 사이에서 Claude가 아무리 읽어도 채우지 못하는 암묵지를 압축하는 것 — 비정상적인 선택, 보이지 않는 임계값, 기본값에 반하는 관례, 프로젝트 범위의 외부 제약.
추가하는 모든 줄은 이 질문에 답해야 한다: "이게 Claude가 코드에서 못 읽어내는 것인가?" 못 읽어낸다 — 남긴다. 읽어낸다 — 지운다.
이렇게 쓴 CLAUDE.md는 보통 초안보다 절반 이상 짧아진다. 그러나 매 대화에 대한 가치는 수천 자 기능 설명보다 압도적으로 크다. 그리고 그것은 늘 "아직 다 못 썼다" — 새 기능을 낼 때마다 빠뜨린 invariant가 드러나고, 예전에 썼던 문단은 이제 지울 수 있게 된다. 지우고 더하고, 지우고 더한다 — 그게 CLAUDE.md의 일상 유지보수다.