배포본의 자바 규칙은 50개 SKILL.md에 있었다. 스킬은 이름과 설명만 먼저 들어오고 본문은 Claude가 부르기로 판단해야 들어온다. Controller를 새로 만들면서 DTO 표준을 안 부르면 그 코드는 표준 없이 쓰인다.
// 지금 — 규칙이 들어오는지는 모델 판단에 달렸다
Write src/.../api/OrderController.java → 스킬을 부를까? (안 부르면 끝)
// 바꾼 뒤 — 경로가 규칙을 건다
Read src/.../api/OrderController.java → 그 경로의 rules 가 본문째 들어온다
Write src/.../api/NewController.java → 훅이 막고 읽을 rules 를 알려 준다
이 글은 어떤 규칙을 어느 통로에 실을지를 정한다. 무엇을 규칙으로 할지는 각 표준 글이 정한다.
먼저: 통로마다 들어오는 조건이 다르다
공식 문서와 claude -p 실측으로 확인한 조건이다(2026-10-01, Claude Code).
| 통로 | 들어오는 때 | 확인 |
|---|---|---|
CLAUDE.md |
매 세션 시작, 통째로 | 문서. 권장 200줄 이하 |
.claude/rules/*.md + paths: |
그 경로의 파일을 Read 할 때 본문째 | 실측 — Read 2/2, Read 후 Edit 2/2 |
| 같은 rules | 새 파일 Write, 폴더 Glob·Grep |
실측 — 안 들어옴 각 2/2 |
.claude/skills/*/SKILL.md |
이름·설명만 먼저, 본문은 호출 때 | 문서 |
훅 additionalContext |
도구 호출 직전 | 실측 — 1만 자 넘으면 앞 2KB만 보인다(1만 4천 자로 잼) |
| 훅 평문 출력(PreToolUse) | 사람 화면에만 찍힌다 | 실측 — Claude에게 안 닿는다(#175) |
압축이 지나간 뒤에도 Read 하면 rules가 다시 본문째 들어왔다(실측 1회). 압축 경계는 대화 기록에 "subtype":"compact_boundary" 한 줄로 남는다.
원칙 1. 코딩 표준은 rules에, 작업 절차는 skills에 둔다
정해야 했던 건 이거였다 — 50개 표준을 어느 통로에 실을 것인가.
안 A — 전부 skills에 둔다(지금). 한 통로라 단순하다. 버린 이유는 「파일을 쓸 때마다 걸려야 하는 규칙」이 모델 판단에 묶인다는 것이다. 안 부른 날 규칙이 없다.
안 B — 전부 CLAUDE.md에 둔다. 판단 없이 확실하다. 버린 이유는 매 요청 25~30k 토큰이라는 것이다. 문서 권장(200줄)의 몇 배다.
안 C — rules는 「이 SKILL.md를 읽어라」 안내판만 두고 본문은 skills에 남긴다. 본문을 안 옮겨도 된다. 버린 이유는 잘못 둔 자리를 고치지 않고 우회로를 하나 더 낸 것이라서다. 코드 규칙이 여전히 「할 일로 걸리는」 통로에 있다.
골랐다 — 무엇이 거느냐로 가른다. 「이 파일의 코드는 이래야 한다」는 경로가 걸어야 하니 rules, 「이 일은 이 순서로 한다」는 할 일이 걸어야 하니 skills다. A·C가 못 지키는 「쓸 때 판단 없이」를 지키고, B가 못 지키는 「필요한 경로에서만」을 지킨다.
.claude/rules/repository-design-standard.md ← 코드 모양. paths: 로 경로가 건다
---
description: Repository 인터페이스와 구현체 설계 규칙 …
paths:
- "**/domain/**/*.java"
- "**/infra/**/*.java"
---
.claude/skills/commit-standby-standard/SKILL.md ← 순서. description 으로 모델이 부른다
.claude/rules/schema-migration-standard.md ← 섞인 표준은 같은 슬러그로 둘 다
.claude/skills/schema-migration-standard/SKILL.md
| 분류 | 개수 |
|---|---|
| rules만 | 36 (자바 25 · 프론트 9 · 테스트 2) |
| skills만 | 5 (커밋 대기 · 에이전트 운영 · 다층 에이전트 · Figma 리뷰 · 테스트 냄새 어휘) |
| 둘 다 (같은 슬러그로 쪼갬) | 9 |
대신 섞인 표준 9개는 한 슬러그가 두 파일이 된다. 「Entity를 바꾸면 같은 커밋에 마이그레이션」처럼 경계에 선 줄은 사람이 어느 쪽인지 골랐다. 가르는 질문은 하나다 — 「이 파일을 쓸 때 코드가 지켜야 하는 모양인가, 이 일을 할 때 거치는 순서인가.」
1단 CLAUDE.md의 자바 핵심 15줄은 rules와 겹치지만 그대로 둔다. 늘 들어오는 안전망이고, 15줄이라 매 요청 비용이 작다.
원칙 2. rules 본문은 SKILL.md에서 옮기고, Copilot 지침은 rules에서 뽑는다
정해야 했던 건 이거였다 — 같은 일을 하는 Copilot 지침(applyTo)과 rules 중 무엇이 원본인가.
안 ㄱ — rules가 원본, Copilot 지침은 생성한다. 표준을 쓰는 도구가 Claude라 고치는 곳이 원본이 된다.
안 ㄴ — Copilot 지침이 원본, rules를 생성한다. 이미 50개가 있고 경로도 적혀 있다. 버린 이유는 지침이 SKILL.md를 손으로 줄인 요약본이었다는 것이다. 45개를 내용으로 대조하니 지침에만 있는 규칙은 0건, 지침이 조건을 떨어뜨린 곳이 4건이었다.
| 표준 | 지침(Copilot) | SKILL.md |
|---|---|---|
| exception-taxonomy | 저장 전 항상 조회 | 원인을 못 가를 때만 |
| exception-taxonomy | StepExecutionListener만 |
ItemWriteListener도 |
| swagger-documentation | @Min·@Max만 |
@DecimalMin·@DecimalMax도 |
| database-instant-storage | 모든 Instant 절삭 |
Clock·Adapter 시점만 |
안 ㄷ — 둘 다 손으로 둔다. 그 4건이 생긴 모양이다.
골랐다 — 안 ㄱ. rules 본문은 SKILL.md를 그대로 옮긴다(짧은 경계 이유까지). Copilot도 이제 같은 본문을 받는다. 대신 Copilot이 싣는 양이 늘었다. 절차만 있는 표준 5개의 지침은 생성하지 않고 손으로 둔다 — applyTo: "**"라 모든 요청에 긴 절차 문서가 실린다.
원칙 3. 새 파일 Write만 훅이 막고, 걸리는 rules를 읽게 한다
정해야 했던 건 이거였다 — rules가 닿지 않는 새 파일 자리를 무엇이 메우나.
안 A — 훅이 rules 본문을 additionalContext로 넣는다. 왕복이 없다. 버린 이유는 상한이다. infra 파일 하나에 걸리는 rules가 21개, 4만 자를 넘는다. 1만 자를 넘기면 앞 2KB만 보이고 나머지가 조용히 빠졌다(실측).
안 B — 「같은 폴더의 기존 파일을 아무거나 Read하라」고 시킨다. rules 통로를 그대로 쓴다. 버린 이유는 새 도메인의 첫 파일에서 읽을 것이 없어 막다른 길이 된다는 것이다.
안 C — Write와 Edit를 둘 다 막는다. 고칠 때도 「읽었나」를 확인해 빈틈이 없다. 처음엔 이걸로 정했다. 버린 이유는 rules가 본문을 담게 되자 할 일이 없어졌다는 것이다. Claude Code는 Edit 전에 그 파일의 Read를 요구하고, 그 Read가 rules를 본문째 넣는다. 훅이 다시 보는 것은 같은 일을 두 번 하는 것이다.
골랐다 — 훅(standards-read-guard)이 새 파일 Write만 막고, 걸리는 rules 파일 목록을 알려 준다. 읽고 다시 쓰면 통과한다. A와 달리 상한이 없고, B와 달리 어느 경로에서나 되고, C와 달리 이미 메워진 자리를 건드리지 않는다.
Write 새 파일 → 경로가 rules 의 paths 에 걸린다 → 그 rules 를 이 세션에 읽었나?
아니오 → exit 2 「아래 파일을 Read 한 뒤 다시 쓴다: .claude/rules/…」
예 → 통과
이미 있는 파일에 쓰는 Write도 같은 이유로 통과시킨다. 이 판단은 Claude가 전용 도구(Read·Edit·Write)를 쓸 때만 맞는다 — 터미널 명령은 원칙 3-1이 맡는다.
대신 처음 쓰는 새 파일마다 계층별로 한 번 멈춘다. infra는 rules 21개를 읽는다.
원칙 3-1. 터미널 명령이 쓰는 파일도 같은 검사를 건다
정해야 했던 건 이거였다 — Claude가 Bash로 파일을 만들거나 고치면 rules도 훅도 안 걸리는데, 그 자리를 어떻게 하나.
아리맘 백엔드 사본에서 시나리오마다 5번씩 재니 15번 중 6번을 Bash로 다뤘다(#178). 「파일 끝에 한 줄」은 5번 모두 f=…; printf … >> "$f"였고 rules가 0개 들어왔다.
안 ① — 그대로 둔다. 옛 배포본보다 나빠지지는 않는다. 버린 이유는 40%가 빠지면 「판단 없이 확실하게」라는 원칙 1의 목적이 무너진다는 것이다.
안 ② — 1단에 「파일은 전용 도구로만 다룬다」를 두고 Bash 쓰기를 막는다. 통로가 하나로 줄어 단순하다. 버린 이유는 둘이다. 1단은 부탁이라 위 측정처럼 안 지켜질 수 있고, 막으면 빌드 산출물·로그 같은 정상 쓰기까지 막힌다.
골랐다 — 같은 훅이 Bash 명령에서 쓰는 대상 파일을 뽑아, rules 경로에 걸리면 Write와 같은 검사를 한다. 새 파일이든 기존 파일이든 본다 — 터미널로 고치면 Read가 없다. ①과 달리 빠지지 않고, ②와 달리 rules 경로 밖의 쓰기는 건드리지 않는다.
뽑는 모양 > >> tee sed -i cp·mv 의 도착지 Set-Content·Add-Content·Out-File
함께 푸는 것 cd <폴더> && … 의 기준 폴더, f=경로 && … "$f" 의 변수
| 측정 (5번씩) | 고치기 전 | 고친 뒤 |
|---|---|---|
| 기존 파일 끝에 주석 | 0/5 (rules 0) | 5/5 막힘 → rules 26 |
| 새 도메인 파일 | 5/5 | 5/5 |
| DTO 두 개 | 4/5 (1번 Bash 우회) | 완료 6번 모두 막힘, 우회 0 |
대가는 명령의 모양이 끝이 없다는 것이다. 흔한 모양만 잡는다. 스크립트 파일을 실행해서 그 안에서 쓰는 것은 못 본다. 그래서 측정에서 0이 나와도 「완전히 막는다」고 적지 않는다.
원칙 4. 「읽었는가」는 대화 기록의 마지막 압축 경계 뒤 Read로 가른다
정해야 했던 건 이거였다 — 훅이 「읽었다」를 무엇으로 아나.
안 1 — 한 번 막고, 다시 쓰면 통과시킨다. 세션 id로 표시 파일 하나만 두면 된다. 버린 이유는 막는 문구를 무시하고 바로 다시 쓰면 읽지 않고 통과한다는 것이다.
골랐다 — 훅 입력의 transcript_path를 열어, 마지막 compact_boundary 뒤에 그 rules 경로를 연 Read가 있는지 본다. 표시 파일 없이 「읽었다」는 사실 자체를 본다. 압축되면 경계 앞의 Read는 세지 않으니 다시 읽게 된다.
대신 훅이 Write마다 대화 기록을 읽는다. 5MB 기록에서 PowerShell판 0.33초, bash판 0.35초였다. bash판은 처음에 규칙 파일마다 sed·grep을 불러 9초가 걸렸다 — Git Bash에서 프로세스 하나가 30ms를 넘는다. 판정을 awk 한 번으로 묶어 줄였다.
원칙 5. Codex는 아직 스킬로 받는다
정해야 했던 건 이거였다 — 코딩 표준이 .claude/skills에서 빠지면 그 복사본만 받던 Codex는 어떻게 되나.
안 ① — Codex도 이번에 같이 바꾼다. 세 도구가 한 번에 맞춰진다. 버린 이유는 Codex에 경로로 걸리는 규칙이나 쓰기 전 훅이 있는지부터 재야 해서, 이번 변경이 그 조사에 묶인다는 것이다. 훅 입력 형식부터 Claude와 다르다.
안 ③ — Codex는 AGENTS.md 1단에만 기댄다. 생성기가 할 일이 준다. 버린 이유는 코딩 표준 본문이 Codex에서 통째로 사라진다는 것이다.
골랐다 — 안 ②, Codex는 지금 동작을 지킨다. .agents/skills를 rules와 skills를 합친 스킬로 생성한다. 섞인 표준은 절차 본문 뒤에 규칙 본문을 잇는다. 규칙만 있는 표준은 rules의 description으로 스킬 머리를 만든다. ①과 달리 이번 일을 묶지 않고, ③과 달리 Codex를 나쁘게 만들지 않는다. 대신 Codex는 여전히 모델 판단에 묶인다. 경로로 싣는 일은 #177에서 정한다.
원칙 6. 경로는 규칙이 실제로 다루는 파일로 적는다
정해야 했던 건 이거였다 — rules의 paths:를 무엇으로 채우나.
안 1 — Copilot applyTo를 그대로 옮긴다. 이미 관리되던 경로라 판단이 필요 없다. 그대로 쓰지 않은 이유는 두 종류의 어긋남이 있어서다.
| 어긋남 | 예 | 고친 것 |
|---|---|---|
| 너무 넓다 | 프론트 9개가 applyTo: "**" — 자바 파일을 써도 걸린다 |
**/*.{js,jsx,ts,tsx}, CSS 표준은 **/*.css도 |
| 빠졌다 | api-test-contract-table이 @Cell·계약 enum 모양을 정하는데 *Test.java만 |
**/*Contract*.java 추가 |
| 빠졌다 | standards-sync가 「.claude/를 고치지 않는다」면서 그 폴더가 없음 |
**/.claude/**·**/.agents/**·**/.codex/** 추가 |
골랐다 — applyTo에서 출발하되, 규칙 본문이 말하는 파일과 맞춘다. 기준은 「이 규칙 줄이 정하는 파일을 쓸 때 들어오는가」다. Copilot도 이제 같은 paths:에서 applyTo를 받으니, 좁힌 효과가 Copilot에도 간다.
대가는 넓게 걸린 표준은 그만큼 자주 읽힌다는 것이다. **/*.java가 걸린 표준이 14개라, 자바 파일 하나를 처음 쓸 때 그 14개는 늘 따라온다.
판단 기준 정리
| 질문 | 답 | 이유 |
|---|---|---|
| 이 줄을 어디에 두나 | 코드 모양이면 rules, 순서면 skills | 거는 것이 다르다 — 경로 vs 할 일 |
| rules 의 원본은 | .claude/rules/<slug>.md |
표준을 쓰는 도구가 Claude다 |
| Copilot 지침은 | rules 에서 생성 (절차 5개만 손) | 요약본이 조건을 떨어뜨렸다 |
| 새 파일은 | 훅이 막고 rules 를 읽게 한다 | rules 는 Read 때만 들어온다 |
| 훅이 본문을 넣나 | 안 넣는다 | 1만 자 상한에 잘린다 |
| 「읽었다」는 | 마지막 압축 경계 뒤 Read | 다시 쓰기로 뚫리지 않는다 |
| Edit 는 | 안 막는다 | Edit 앞의 Read 가 이미 넣었다 |
| 터미널로 쓰면 | 같은 검사를 건다 | 15번 중 6번이 Bash 였다 |
| Codex 는 | 합친 스킬로 받는다 | 경로 규칙 지원을 확인 못 했다 |
paths: 는 |
규칙이 정하는 파일로 | applyTo 그대로면 넓거나 빠진다 |
| 1단 15줄은 | 둔다 | 늘 들어오는 안전망이고 짧다 |
이 표준을 정하기까지
시작은 짧은 영상 하나였다. 「CLAUDE.md에서 분리할 폴더 4곳」 — rules · skills · hooks · agents. 각 폴더가 언제 들어오는지를 공식 문서로 확인하다가, 우리 배포본은 코딩 표준을 전부 skills에 두고 있다는 게 걸렸다.
훅이 아무 말도 안 하고 있었다. 그 구멍을 메우려던 java-standard-reminder가 평문으로 출력해, claude -p로 재 보니 Claude에게 한 번도 닿지 않았다(#175). JSON additionalContext로 고쳤다.
처음 설계는 rules를 안내판으로 썼다. paths: 규칙이 새 파일에선 안 들어온다는 걸 재고, 「rules → SKILL.md를 읽어라」 안내판과 「SKILL.md를 읽었나」 훅을 만들었다. 훅 본문 주입은 1만 자 상한 실측으로 버렸다. 그 설계 안에서 「경로 → SKILL.md 목록」을 어디 둘지 다섯 안을 비교했다 — 훅 스크립트 안(지금 모양, .ps1·.sh 두 벌), Copilot applyTo 그대로(**가 16개라 자바 파일 하나에 28개를 읽게 됨), SKILL.md 프런트매터의 새 키, 훅 옆 데이터 파일, rules 파일. rules를 골랐고, 목록 내용은 손으로 고르지 않고 applyTo에서 생성하기로 했다. 이 다섯 안은 다음 문단에서 질문 자체가 사라졌다 — rules가 목록이 아니라 본문을 담게 되면서다.
거기서 방향이 꺾였다. 「rules와 skills는 느낌이 다르다. rules는 그 파일에 적용할 코딩 표준, skills는 업무를 어떻게 하는지 정의하는 프로세스다. 지금은 skills에 둘 다 있고 rules가 skills로 유도하고 있다.」 안내판은 잘못 둔 자리를 그대로 두고 길만 하나 더 낸 것이었다. 50개를 「코드 모양 vs 순서」로 다시 갈랐다.
기록할 곳도 갈렸다. 표준 동기화 표준에 원칙 하나를 붙이면 설명 글이 필요 없어 가볍다. 하지만 그 글의 질문은 「여러 벌을 어떻게 맞추나」이고 이번 질문은 「규칙이 어느 통로로 닿나」라, 한 섹션 한 질문이 깨진다. 3단 구조가 그동안 README에만 있었고 어느 글도 정하지 않았다는 것이 훅이 몇 달 비어 있어도 아무도 몰랐던 이유 중 하나라서, 새 표준으로 세웠다.
옮기기 전에 원본을 대조했다. Copilot 지침이 같은 일을 하고 있어 원본 후보였는데, 대조하니 요약하다 조건을 떨어뜨린 곳이 4건 나왔다. SKILL.md를 원본으로 삼았다.
훅의 일이 줄었다. rules가 본문을 담게 되자 Edit는 Read가 이미 메우고, 남은 빈칸은 새 파일 Write 하나였다. 「Write와 Edit를 둘 다 지킨다」고 먼저 정했던 것을 되돌렸다.
처음 5번 측정에서 Bash 구멍이 크게 드러났다. 한 번 돌렸을 때는 「드문 빈틈」으로 보고 이슈만 열었는데(#178), 5번씩 재니 40%였다. 푸시를 미루고 같은 훅이 터미널 명령도 보게 고쳤다. 명령 모양은 상상하지 않고 실제로 나온 것(f=…; printf >> "$f", cd … && cat > … <<EOF)부터 잡았다. 고치는 중에 bash판에 셋이 더 있었다 — awk -v가 윈도우 경로의 역슬래시를 이스케이프로 먹었고, NR==FNR 기법이 빈 대화 기록에서 판정을 뒤집었고, 대화 기록 속 윈도우 경로의 겹친 구분자를 못 풀었다. PowerShell판으로만 실측하던 동안 숨어 있던 것들이다.
구현하다 셋이 더 잡혔다. 새 훅 .ps1이 BOM 없이 들어가 한글 안내가 깨졌는데, BOM 검사가 .claude 같은 점 폴더를 아예 안 보고 있었다(Dir.glob의 기본값). bash판이 9초 걸렸다. jq가 없는 환경에선 JSON의 \\가 안 풀려 경로가 C://가 되어 아무 규칙도 안 걸렸다. 셋 다 고쳤다.
정리
- 코딩 표준은 rules, 작업 절차는 skills. 거는 것이 경로냐 할 일이냐로 가른다
- rules 가 원본이고 Copilot 지침과 Codex 스킬은 생성물이다. 손으로 줄인 요약본이 조건을 떨어뜨렸다
- 새 파일과 터미널 쓰기만 훅이 막는다. rules 는 Read 도구일 때만 들어오고, 새 파일은 읽을 것이 없고, 터미널 쓰기는 Read 를 거치지 않는다
- 훅은 본문을 넣지 않는다. 1만 자를 넘기면 앞 2KB만 보인다
- 「읽었다」는 대화 기록이 말한다. 마지막 압축 경계 뒤의 Read 만 센다
- Codex 는 아직 스킬이다. 경로로 싣는 방법은 #177 에서 정한다
자신만의 철학을 만들어가는 중입니다.
댓글남기기