이번 시간에는 CLAUDE.md 같은 지침 파일에서 지워도 되는 규칙을 골라내는 방법과, 지운 뒤에도 남은 규칙이 지켜지는지를 canary라는 판정용 규칙으로 확인하는 방법을 실측 결과로 배워가는 시간입니다.
지침 파일은 오래 쓸수록 길어지죠. 그런데 줄이려고 하면 "이걸 지우면 Claude가 규칙을 안 지키지 않을까?" 하는 걱정이 먼저 들어요.
이 글은 그 걱정을 느낌이 아니라 명령으로 판정하는 방법을 다룰게요.
매 요청마다 이미 실려 있는 몫부터 봅니다
Claude Code에서 /context를 입력하면 지금 컨텍스트가 어디에 얼마나 쓰이고 있는지를 항목별로 보여 줘요. 항목은 이런 것들입니다.
- System prompt: Claude Code가 넣는 기본 지시문
- System tools, MCP tools: 모델이 쓸 수 있는 도구의 정의
- Memory files: CLAUDE.md 같은 지침 파일
- Skills: 설치된 스킬의 이름과 설명
- Messages: 지금까지 주고받은 대화 이력
여기서 시스템 프롬프트랑 도구 정의는 우리가 거의 손을 못 대는 부분이에요. Messages는 작업하면서 쌓이는 몫이라 이 글에서는 다루지 않고요.
이 글의 과녁은 지침 파일입니다. CLAUDE.md는 세션을 시작할 때 작업 디렉터리와 그 상위 디렉터리, 그리고 홈의 ~/.claude/CLAUDE.md에서 자동으로 읽히고, 그 뒤 모든 요청에 통째로 실려요.
이게 왜 과녁이냐면, 매 요청에 실리는데 내용 전부를 우리가 직접 썼기 때문이에요. 즉 우리가 마음대로 줄일 수 있는 영역이라는 겁니다.
비대한 지침 파일을 하나 뜯어봅니다
예를 들어서 오래 운영된 프로젝트에 이런 지침 파일이 있다고 해볼게요. 티켓 관리 서비스와 측정 도구를 함께 담은 Python 저장소의 지침이고, 전부 75줄이에요. 이 파일을 rules-fat.md라고 할게요.
이 글에서는 ##로 시작하는 소제목 하나와 그 아래 목록을 "절"이라고 부를게요. 아래는 75줄 중 일곱 개 절을 그대로 옮긴 발췌입니다. 아마 어디서 많이 본 느낌일 거예요.
## 코드 스타일
- 파이썬 표준 라이브러리만 사용한다. 외부 패키지를 추가하지 않는다.
- 타입 힌트를 사용한다.
- 함수는 한 가지 일만 한다.
- 변수 이름은 의미를 담는다. 임시 변수라도 x, tmp 같은 이름을 피한다.
- 코드에는 꼭 필요한 주석만 남긴다.
- 문자열은 작은따옴표보다 큰따옴표를 우선한다.
- import는 표준 라이브러리, 로컬 순서로 정렬한다.
- 파이썬 파일의 들여쓰기는 스페이스 4칸이다. 탭을 쓰지 않는다.
- 한 줄은 100자를 넘지 않는다.
- 클래스 이름은 PascalCase, 함수와 변수는 snake_case를 쓴다.
## 코드 스타일 (추가)
- 들여쓰기는 스페이스 4칸을 쓴다.
- 외부 라이브러리 설치는 금지한다. 표준 라이브러리로 해결한다.
- 이름 규칙: 클래스는 PascalCase, 함수는 snake_case.
## 커밋 규칙
- 커밋 메시지는 반드시 "작업:"으로 시작한다.
- 커밋은 작게 나눈다.
- 커밋 전에 diff를 확인한다.
- 커밋 메시지는 한국어로 쓴다.
- 커밋 메시지 제목은 50자를 넘지 않는다.
## 테스트
- 파이썬 코드를 수정하면 반드시 .venv/bin/python -m pytest -q tests/baseline 로 확인한다.
- 테스트를 지우지 않는다.
- 실패하는 테스트를 통과시키기 위해 테스트 쪽을 고치지 않는다.
- 새 기능에는 테스트를 함께 만든다.
## 보고
- 작업이 끝나면 보고는 세 줄 이내로 한다.
- 보고에는 바꾼 파일과 이유를 담는다.
## 예전 배포 절차 (2024년 문서에서 복사)
- 배포 전 스테이징 서버에서 smoke 테스트를 돌린다. 스테이징 주소는 사내 위키를 참조한다.
- 배포는 금요일에 하지 않는다.
- 배포 담당자는 로테이션 시트를 확인한다.
- 배포 후 30분간 대시보드를 관찰한다.
- 문제가 생기면 롤백 스크립트 deploy/rollback.sh 를 실행한다.
- 배포 공지는 슬랙 #deploy 채널에 남긴다.
## 프런트엔드 규칙 (이 저장소에는 프런트엔드가 없다)
- 컴포넌트는 함수형으로 작성한다.
- 상태 관리는 최소화한다.
- CSS는 모듈 방식을 쓴다.
- 이미지 자산은 assets 폴더에 둔다.
발췌에 없는 나머지는 개요, 데이터베이스 마이그레이션, 리뷰 규칙, 문서화, 협업 규칙 절이에요. 자 이 파일을 같이 뜯어보죠. 크게 네 부류가 섞여 있습니다.
첫 번째는 진짜 지켜야 하는 규칙이에요.
테스트 절을 봐 보세요. "파이썬 코드를 수정하면 반드시 pytest로 확인한다", "실패하는 테스트를 통과시키기 위해 테스트 쪽을 고치지 않는다" 같은 것들이요. 커밋 절의 "커밋 메시지는 반드시 작업:으로 시작한다"도 마찬가지고요.
두 번째는 중복입니다.
코드 스타일 절이랑 그 아래 코드 스타일 (추가) 절을 같이 봐 보세요. 위에 "들여쓰기는 스페이스 4칸이다"가 있는데, 아래에도 "들여쓰기는 스페이스 4칸을 쓴다"가 또 있어요. 이름 규칙도, 외부 라이브러리 금지도 두 번 적혀 있고요.
이게 왜 생기냐면, 누가 규칙을 추가할 때 위에 이미 있는지 확인하지 않고 절을 새로 만들었기 때문이에요. 실무에서 정말 많이 보이는 패턴입니다.
세 번째는 이 프로젝트랑 상관없는 규칙이에요.
프런트엔드 규칙 절을 봐 보세요. 이 저장소에 프런트엔드 코드가 있나요? 없죠. 심지어 절 제목에 "이 저장소에는 프런트엔드가 없다"라고 적어 놨어요.
발췌 밖의 절도 비슷해요. 이 저장소에는 마이그레이션 파일이 없는데 데이터베이스 마이그레이션 절이 다섯 줄 있고, 문서화 절에는 "아키텍처 결정은 docs/adr 폴더에 남긴다. (이 폴더는 아직 없다)"가, 협업 규칙 절에는 "PR 설명에는 스크린샷을 첨부한다. (프런트엔드 전용 규칙이었음)"이 있어요.
네 번째는 낡은 규칙입니다.
예전 배포 절차 절은 제목부터 "2024년 문서에서 복사"예요. 롤백 스크립트 경로가 적혀 있는데 저장소에는 그런 파일이 없어요. 지금은 쓰지 않는 절차가 그대로 남아 있는 겁니다.
자 그러면 여기서 매 요청마다 실려야 하는 건 뭘까요? 첫 번째 부류뿐입니다. 나머지 셋은 모델이 읽어 봤자 쓸 일이 없는데 토큰만 먹고 있는 거예요.
그런데도 왜 이렇게 자랐을까요? 지침 파일은 추가하는 사람만 있고 지우는 사람이 없기 때문입니다.
지켜졌는지를 명령으로 판정합니다
이걸 정리하면 얼마나 줄어드는지, 그리고 뭐가 위험한지를 재 볼 거예요. 그런데 문제가 하나 있어요. "규칙을 잘 지키고 있는지"는 어떻게 잴까요? 느낌으로 판단하면 안 되잖아요.
그래서 canary를 씁니다. canary는 지침 안에 심어 둔 규칙 중에서, 지켜졌는지를 명령 하나로 판정할 수 있는 규칙을 말해요.
위 파일에는 canary가 세 개 들어 있어요.
- 커밋 메시지는 "작업:"으로 시작한다:
git log로 판정해요. - 수정하면 반드시 기준 테스트를 돌린다: 세션 로그에 pytest 실행이 남았는지로 판정해요.
- 보고는 세 줄 이내: 마지막 답의 줄 수로 판정해요.
즉 판정 기준을 우리가 직접 만들어서 지침에 심어 둔 거예요.
측정은 같은 과제를 비대한 지침으로 세 번, 정리한 지침으로 세 번 돌리는 방식으로 했어요. 과제는 티켓 서비스 코드(app/service.py)의 설정값 WIP_LIMIT을 바꾸고 커밋까지 하는 것입니다. 실행 한 번은 이 명령이에요.
claude --safe-mode --permission-mode acceptEdits --append-system-prompt-file rules-fat.md \
-p "app/service.py의 WIP_LIMIT을 3에서 5로 바꾸고 커밋까지 해 줘." \
--output-format json --max-turns 15 > fat.json
옵션은 이런 뜻이에요.
--safe-mode: CLAUDE.md, 스킬, 플러그인, 훅, MCP 서버 같은 사용자 설정을 끄고 엽니다. 인증, 모델 선택, 내장 도구, 권한은 그대로예요. 제 환경의 설정이 측정에 섞이지 않게 하려고 썼어요.--append-system-prompt-file rules-fat.md: 이 측정의 변인입니다. 지침 파일을 읽어서 시스템 프롬프트 뒤에 붙여 줘요. safe 모드가 꺼 버린 CLAUDE.md가 하던 역할을 파일을 지정해 대신 채우는 거라, 이 파일 이름만 바꾸면 지침만 다른 비교가 됩니다.--permission-mode acceptEdits: 과제가 파일을 고쳐야 해서, 수정할 때마다 확인을 묻지 않고 허용해요.-p,--output-format json: 대화 화면 없이 한 번 실행하고 결과를 JSON으로 받아요.--max-turns 15: 모델과 주고받는 왕복 수의 상한이에요. 답이 안 나올 때 무한정 돌지 않게 막아 둡니다.
실제 CLAUDE.md와 실리는 자리는 조금 달라요. CLAUDE.md는 시스템 프롬프트 뒤에 사용자 메시지로 들어가고, 이 옵션은 시스템 프롬프트 끝에 붙습니다. 그래도 둘 다 매 요청 앞부분에 통째로 실린다는 점은 같아서, 지침 크기와 준수를 비교하는 데는 같은 원리로 볼 수 있어요.
실행이 끝나면 첫 번째 canary는 이 명령 하나로 판정돼요.
git log -1 --format=%s
제 실행 중 한 번은 이렇게 나왔어요.
작업: WIP_LIMIT을 3에서 5로 상향
메시지 문구는 실행할 때마다 다르고, 보는 건 "작업:" 접두사 하나예요. 느낌이 아니라 판정인 거죠.
판정이 끝나면 실행 전에 기억해 둔 커밋으로 git reset --hard 해서 다음 실행을 같은 출발점에서 시작하면 됩니다. 커밋 하나를 지우는 게 아니라 실행 전 지점으로 되돌리는 방식이라, 에이전트가 커밋을 안 했더라도 안전합니다.
정리본과 비교해봅니다
이제 정리본입니다. 중복은 하나로 합치고, 이 저장소랑 무관한 절이랑 낡은 절은 지웠어요. canary 판정에 쓰지 않는 세부 규칙(타입 힌트, 한 줄 100자, 커밋 제목 50자 같은 것)과 개요·리뷰 규칙 절도 함께 덜어냈고요. 대신 canary 세 개는 반드시 남겨야겠죠. 정리한 rules-lean.md 전체입니다.
# 프로젝트 지침
## 코드
- 파이썬 표준 라이브러리만 사용한다. 외부 패키지 금지.
- 클래스는 PascalCase, 함수·변수는 snake_case. 들여쓰기 스페이스 4칸.
## 커밋
- 커밋 메시지는 반드시 "작업:"으로 시작하고 한국어로 쓴다.
## 테스트
- 파이썬 코드를 수정하면 반드시 .venv/bin/python -m pytest -q tests/baseline 로 확인한다.
- 테스트를 지우거나 테스트 쪽을 고쳐 통과시키지 않는다.
## 보고
- 작업이 끝나면 보고는 세 줄 이내. 바꾼 파일과 이유를 담는다.
75줄이 15줄이 됐어요. 75 − 15 = 60줄을 지운 거죠. 커밋 절, 테스트 절, 보고 절에 canary 세 개가 그대로 살아 있는 것도 확인해 보세요.
같은 과제를 이 파일로도 세 번 돌렸어요. 명령은 지침 파일 이름과 저장할 JSON 파일 이름만 다르고 나머지는 같습니다.
토큰은 결과 JSON의 usage에서 입력 세 항목을 더해서 비교했어요.
input_tokens: 캐시를 거치지 않고 처리한 입력cache_creation_input_tokens: 이번에 캐시에 새로 쓴 입력cache_read_input_tokens: 캐시에서 읽어 온 입력
세 항목을 따로 보지 않고 더하는 이유가 있어요. 지침을 바꾸면 같은 내용이 캐시에 새로 올라가기도 하고 캐시에서 읽히기도 하면서, 같은 양이 세 항목 사이에서 자리를 옮깁니다. 그래서 하나만 보면 줄었는지 늘었는지를 반대로 읽을 수 있어요.
다만 합은 양이지 비용은 아니에요. 캐시 쓰기는 기본 입력 단가의 1.25배(5분 캐시) 또는 2배(1시간 캐시), 캐시 읽기는 0.1배이고 일부 최신 모델은 그보다 더 싸요. 이 측정은 지침이 차지하는 양을 보려는 거라 합으로 비교했습니다.
하나 더 알아 두실 점이 있어요. 실행 한 번 안에서 Claude Code는 파일을 읽고, 고치고, 테스트를 돌리고, 커밋하느라 모델과 여러 번 주고받아요. 왕복마다 지침을 포함한 앞부분이 다시 실리고, usage는 그 실행의 왕복들을 모두 더한 값이에요.
그래서 아래 숫자는 요청 하나의 입력이 아니라, 여러 왕복으로 과제 한 번을 끝낼 때까지의 입력 합입니다.
이 부분은 제가 실측한 결과를 기반으로 알려드릴게요. 조건마다 세 번 돌린 입력 합의 중앙값이에요.
- 비대한 지침(75줄): 169,711토큰
- 정리한 지침(15줄): 136,527토큰
계산하면 169,711 − 136,527 = 33,184토큰이고, 33,184 ÷ 169,711 ≈ 0.196이에요. 과제 한 번 실행의 입력 합이 약 20% 줄었습니다.
이 차이에는 지침이 왕복마다 다시 실린 몫과, 실행마다 달라질 수 있는 왕복 수가 함께 섞여 있어요. 그래서 요청 하나당 얼마가 줄었는지로 나눠 읽지는 않았어요. 조건당 세 번이라 다시 돌리면 숫자도 달라질 수 있습니다.
그래도 방향은 분명해요. 지침은 매 요청마다 실리니깐, 지침이 짧아진 몫의 차이는 이번 과제 한 번에서 끝나지 않고 이 저장소에서 앞으로 돌리는 작업마다 다시 생겨요. 그 크기는 작업마다 왕복 수에 따라 달라요.
자 그럼 남은 canary 두 개도 판정해 보죠.
보고가 세 줄 이내였는지는 결과 JSON의 result 필드를 보면 됩니다. 마지막 답이 여기에 들어 있어서, 세 줄 안에서 끝났으면 지켜진 거고 문단이 길게 늘어져 있으면 안 지켜진 거예요.
그리고 permission_denials도 같이 보세요. 권한 때문에 막힌 도구 호출이 여기에 적히고, 비어 있어야 정상이에요.
이게 왜 중요하냐면, 커밋 접두사가 안 맞게 나왔을 때 원인이 둘로 갈리기 때문입니다. 이 항목이 비어 있으면 지침을 안 지킨 거예요. 반대로 커밋 명령이 여기 적혀 있으면 지침 문제가 아니라 권한 설정 문제고, 그러면 지침이 아니라 설정을 고쳐야 합니다.
마지막 canary는 테스트를 실제로 돌렸는지인데, 이건 결과 JSON에 안 나와요. 어떤 도구를 호출했는지는 세션 로그에 남습니다.
FAT_LOG="$(find "$HOME/.claude/projects" -type f -name "$(jq -r .session_id fat.json).jsonl" -print -quit)"
grep -o '"command":"[^"]*pytest[^"]*"' "$FAT_LOG"
첫 줄은 결과 JSON에서 session_id 값을 꺼내서, 그 이름으로 된 세션 로그 파일을 찾아요. 둘째 줄은 그 로그에서 에이전트가 실제로 실행한 명령 중에 pytest가 들어간 것만 뽑습니다.
한 줄이라도 나오면 테스트를 돌린 거고, 아무것도 안 나오면 안 돌린 거예요. 지침에 적어 둔 pytest 문구가 여기에 걸릴 걱정은 안 하셔도 돼요. 얹은 지침은 세션 로그에 저장되지 않고, 뽑는 건 실행된 명령뿐이니까요.
이렇게 canary 세 개를 전부 명령과 필드로 판정했어요. 결과는 양쪽 다 9번 중 9번이었습니다. canary 3개를 3번씩, 모두 9번 판정해서 9번 다 지켜졌다는 뜻이에요.
60줄을 지웠는데 지켜야 할 것은 그대로 지켜진 거죠. 다만 과제 하나, 규칙 셋, 9번 판정이라는 범위 안의 결과예요. 줄여도 덜 지키지 않았다는 것이지, 어떤 지침이든 줄여도 된다는 보장은 아닙니다.
지침 길이와 준수율의 관계가 더 궁금하면 CLAUDE.md가 1,500줄이면 Claude는 규칙을 놓칠까? 글을 참고하세요.
정리하다가 규칙을 지우면 조용히 사라집니다
자 그러면 반대로, 정리하다가 실수를 하면 어떻게 될까요?
정리본에서 커밋 절을 통째로 지운 버전으로 같은 과제를 세 번 돌려 봤어요. 결과는 예상하신 대로입니다. 커밋 canary가 세 번 중 한 번도 지켜지지 않았어요(0/3). 커밋 메시지가 "작업:" 없이 나옵니다.
그런데 여기서 무서운 게 뭐냐면, 아무 오류도 안 난다는 거예요.
작업은 성공하고, 테스트도 돌고, 보고도 정상적으로 옵니다. 규칙 하나만 조용히 사라진 채로요.
그래서 지침 정리는 감으로 하시면 안 되고, 절차로 하셔야 해요.
- 지침을 네 부류로 나눕니다. 유효한 규칙, 중복, 무관, 낡음. 그리고 뒤의 셋을 지웁니다. 남은 규칙 중에도 작업과 판정에 쓰지 않는 세부 규칙은 함께 덜어낼 수 있어요.
- 정리 전과 후에 같은 과제를 돌려서 canary가 지켜지는지를 명령으로 비교합니다.
- 토큰은 줄고 canary는 그대로여야 통과입니다.
정리
범위: claude -p를 safe 모드로 열고 지침 파일을 --append-system-prompt-file로 얹어, 커밋까지 하는 수정 과제 하나를 조건당 3회 돌린 실측.
- 지침 파일에는 유효한 규칙, 중복, 무관한 규칙, 낡은 규칙이 섞여 자라요. 매 요청에 실려야 하는 건 유효한 규칙뿐이니 나머지 셋을 지우고, 쓰지 않는 세부 규칙도 덜어내세요.
- 지침을 정리해 75줄을 15줄로 줄이자 과제 한 번 실행의 입력 합 중앙값이 169,711에서 136,527토큰으로, 33,184토큰(약 20%) 줄었어요. 여러 왕복을 더한 값이라 요청 하나당 절감으로 읽지는 마세요.
- 심어 둔 canary 3개는 비대한 지침과 정리한 지침 모두 9번 중 9번 지켜졌어요. 조건당 3회의 결과이니, 자기 지침을 줄일 때는 직접 재 보세요.
- 커밋 절을 지운 정리본에서는 커밋 canary가 0/3이었고 오류는 하나도 없었어요. 규칙은 조용히 사라지니 정리 전후로 canary를 명령으로 판정하세요.
- canary는
git log -1 --format=%s, 결과 JSON의result와permission_denials, 세션 로그의 pytest 실행으로 판정해요.permission_denials가 비어 있지 않으면 지침보다 권한 설정을 먼저 보세요.
이 시리즈의 다음 글에서는 버릴 수는 없지만 가끔만 쓰는 규칙을 스킬로 빼면 정말 싸지는지를 재 볼게요.