지난 글에서 Claude Code 의 세부 설정은 별도 글로 미뤘다. 그 별도 글이 이 글이다.
처음 Claude Code 를 커스터마이징하려고 열어보면 표면이 흩어져 있다. settings.json, CLAUDE.md, slash commands, subagents, hooks, plugins — 같은 의도를 담을 자리가 여러 곳이라 “어디에 넣어야 하는가” 부터 막힌다. 한 파일에 다 몰면 CLAUDE.md 가 비대해지고, 나누면 어느 설정이 언제 활성화되는지 추적이 안 된다.
축 하나만 잡으면 이 문제가 거의 사라진다. 언제 개입하는가. 이 글은 그 축으로 설정을 네 레이어로 정돈하는 이야기다.
레이어 구조
| 레이어 | 언제 개입하나 | 책임 |
|---|---|---|
| CLAUDE.md + Rules | 항상 (매 턴 컨텍스트 로드) | 컨벤션·가드라인 암묵지 |
| Agents | Skill 또는 모델이 위임할 때 | 컨텍스트 격리된 전문 역할 |
| Skills | 내가 호출할 때 | 재사용 가능한 workflow |
| Hooks | 도구 이벤트 전/후 자동 | 검증·자동화·가드레일 |
이 표가 글의 전부라고 해도 된다. 나머지는 각 레이어가 이 축 위에서 어떻게 자리 잡는지, 그리고 하나의 워크플로우에서 네 레이어가 어떻게 맞물리는지다.
CLAUDE.md + Rules
Claude 가 매 턴 컨텍스트에 싣고 시작하는 지식이다. 호출하지 않아도 항상 적용된다. 두 계층으로 나뉜다.
CLAUDE.md 는 프로젝트/유저 단위의 최상위 컨텍스트다. 프로젝트 루트(./CLAUDE.md), .claude/CLAUDE.md, ~/.claude/CLAUDE.md 에 둘 수 있고, 여럿이면 계층 순으로 병합된다. 내 CLAUDE.md 에는 언어 독립적인 행동 규칙이 들어간다.
# CLAUDE.md (발췌)
- 접근법 거부 시: 즉시 멈추고 방향을 물어볼 것
- 변경 범위: 명시적으로 요청된 것만 변경할 것
- 커밋 금지: 명시적으로 요청받기 전까지 절대 커밋하지 말 것
- 접근법 사전 제안: 3개 이상 파일을 수정하거나 아키텍처에 영향을 주는 변경은
코드 수정 전에 접근법을 먼저 제안하고 승인 후 실행할 것
rules/*.md 는 언어·도메인별로 쪼개진 하위 규칙이다. ~/.claude/rules/ 또는 .claude/rules/ 에 .md 로 두면 재귀 탐색되어 로드된다. paths frontmatter 를 쓰면 특정 파일 패턴에만 적용되는 scoped rule 도 가능하다.
# rules/go.md (발췌)
- Get prefix 금지: GetName() ❌ → Name() ✅
- 에러 wrapping: return err 금지. fmt.Errorf("context: %w", err)
- panic 금지 (라이브러리): main/테스트에서만 허용
# rules/typescript.md (발췌)
- Destructuring 우선: 함수 파라미터 ({ server, db }: Config)
- 중괄호 필수: if (x) return; ❌ → if (x) { return; } ✅
- 데이터 형태 → type, 구현 계약 → interface
- enum 사용: as const 객체 대신 enum
# rules/code-principles.md (발췌, 언어 공통)
- fail-fast: validation 실패 시 즉시 raise/throw
- 조기 반환: 중첩 if 대신 Guard Clause
- 불변성 우선: mutation 필요 시 명시적 범위 한정
- 순수 함수 우선: side effect 는 호출 경계로 밀어내기
- Any 타입 금지: 제네릭, union, 구체 타입으로 해결
언어 규칙을 CLAUDE.md 에 직접 쓰는 대신 rules/ 에 파일 단위로 나눠두면 CLAUDE.md 가 부풀지 않는다.
구분 포인트: “호출 여부와 무관하게 항상 깔려 있어야 하는가” 가 이 레이어의 기준이다.
Agents
~/.claude/agents/<name>.md 에 정의한다. Skill 이나 모델이 위임하면 별도 컨텍스트 윈도우 에서 실행되고 결과만 돌려준다. 메인 세션에 agent 의 작업 과정이 들어오지 않는 것이 핵심이다. @agent-name 으로 직접 멘션해 호출할 수도 있다.
# agents/architect.md (frontmatter)
name: architect
description: 아키텍처 분석, 디버깅 근본 원인 진단
tools: ["Read", "Grep", "Glob"]
model: opus
내 설정의 agent 목록이다.
| Agent | 역할 | 호출되는 곳 |
|---|---|---|
architect | 구조 분석, 설계 리뷰, 디버깅 | /code Stage Pre |
planner | 작업 분해, 실행 계획 수립 | /code Stage Pre |
code-reviewer | 스펙 준수 + 코드 품질 리뷰 | /code Stage Post, PR review |
security-reviewer | OWASP Top 10 기반 보안 취약점 분석 | /code Stage Post |
database-reviewer | 스키마, 쿼리, 마이그레이션 리뷰 | /code Stage Post (DB 변경 시) |
verify-agent | 빌드 → 타입 → 린트 → 테스트 파이프라인 | /code Stage Post/Fix |
refactor-cleaner | dead code 제거, 코드 정리 | /code Stage Clean |
Skill 은 오케스트레이션, Agent 는 한 책임의 깊은 실행. 다음 Skills 섹션의 다이어그램에서 이들이 어떻게 호출되는지 보인다.
구분 포인트: “메인 컨텍스트와 분리되어야 하는가” 가 Agent 의 기준이다.
Skills
Skills 는 명시적으로 호출하는 워크플로우 레시피다. ~/.claude/skills/<name>/SKILL.md 에 정의하고 /skill-name 으로 부른다. 프롬프트, 허용 도구, 모델 지정이 한 파일에 묶인 단위다. 매번 같은 지시를 타이핑하는 대신 “이 상황에서는 이 skill” 이라는 레시피가 대신 선다.
/code-brainstorming → /code
가장 잘 작동하는 자리는 반복되는 개발 워크플로우다. 내 설정에서 가장 무거운 skill 쌍인 /code-brainstorming 과 /code 를 파이프로 잇는 예시로 본다.
/code-brainstorming 은 구현 전 단계를 담는다. 요구사항 탐색, 분리 여부 판단, 설계 문서(DESIGN.md) + sub-task 별 계획 파일(NN-<task>.md) + 의존성 그래프(_dag.yaml) 를 .claude/plans/<topic>/ 아래에 쌓고, architect 에이전트로 설계 리뷰까지 돌린 뒤 멈춘다. 산출물은 다음 skill 의 입력이다.
flowchart TD
I["아이디어 입력
/code-brainstorming"] --> C["컨텍스트 수집
프로젝트 타입 · CLAUDE.md · git log"]
C --> R["요구사항 탐색
AskUserQuestion 1:1"]
R --> A["접근법 2-3개 제시 + 추천"]
A --> PM["pm-code-agent
분리 판단"]
PM -->|SINGLE| D1["DESIGN.md + _dag.yaml
01-main.md"]
PM -->|SPLIT| D2["DESIGN.md + _dag.yaml
NN-task.md × N"]
D1 --> AR["architect agent 리뷰"]
D2 --> AR
AR -->|NEEDS REVISION| D2
AR -->|APPROVED| S["status
draft → ready"]
S --> O[(".claude/plans/<topic>/")]
/code 는 그 디렉토리를 받아 구현 파이프라인을 자체 실행한다. 여기서 흥미로운 점은 상세 로직을 SKILL.md 본문에 담지 않고 references/stage-*.md 로 분리했다는 것. 필요한 순간에만 Read 로 불러온다. 덕분에 평소에는 컨텍스트에 오케스트레이션만 상주하고, 해당 단계에 진입할 때만 stage 문서가 로드된다.
_dag.yaml 의 sub-task 가 위상정렬된 뒤, 각 sub-task 마다 다섯 stage 를 거친다.
flowchart TD
I[("/code 입력
.claude/plans/<topic>/")] --> L["_dag.yaml 로드
위상정렬 + status 게이트"]
L --> PRE["Stage Pre
architect agent
+ planner agent"]
PRE --> IMP["Stage Impl
병렬 구현
(에이전트 팀)"]
IMP --> POST["Stage Post (병렬)
code-reviewer
security-reviewer
database-reviewer
verify-agent"]
POST -->|FAIL| FIX["Stage Fix
verify-agent
자동 수정"]
FIX --> POST
POST -->|PASS| CLEAN["Stage Clean
refactor-cleaner
agent"]
CLEAN --> DONE["status
ready → done"]
DONE --> N{"다음 sub-task?"}
N -->|있음| PRE
N -->|없음| R["종합 리포트"]
- Pre (
stage-pre.md) — architect + planner 를 순차 호출해 구조 분석과 실행 계획을 수립한다. 결과를 sub-task 문서에## Plan으로 append. - Impl (
stage-impl.md) — 계획을 바탕으로 병렬 구현 을 실행한다. 단순 작업이면 리더가 직접, 복잡하면 팀원 에이전트를 spawn 한다. - Post (
stage-post.md) — code-reviewer, security-reviewer, database-reviewer, verify-agent 를 병렬 호출 해 종합 검증한다. PASS / NEEDS ATTENTION / FAIL 판정이 여기서 나온다. - Fix (
stage-fix.md, 조건부) — Post 가 FAIL 이면 verify-agent 를 돌려 fixable 에러를 자동 수정한 뒤 Post 를 재실행한다.retry-policy.md의 상한(기본 3 회)과 “동일 에러 2 회 연속 → 정체 탐지” 규칙을 따른다. - Clean (
stage-clean.md) — refactor-cleaner 로 dead code, 미사용 import, 중복을 정리한다. 여기서 실패는 non-critical 로 취급해 warning 만 남기고 진행한다. 단, clean 이 빌드를 깨면 Post 재실행에서 잡힌다.
한 sub-task 가 다섯 stage 를 통과하면 해당 NN-<task>.md 의 frontmatter status 가 ready → done 으로 승격된다. 이 전환이 재실행 방지 의 장치다 — 같은 plan 을 다시 /code 로 돌려도 done 인 task 는 스킵되고 남은 것만 실행된다.
이 두 skill 이 Skill 레이어의 기본 패턴을 보여준다. 상태 있는 파일을 입출력으로 주고받는 파이프라인, 그리고 상세 로직을 references 로 분리 해 컨텍스트를 아끼는 구조.
/github-ship — branch 에서 merge 까지
구현이 끝나면 코드를 올려야 한다. /github-ship 은 branch 생성부터 merge 까지를 하나의 파이프라인으로 묶는다. 원래 /git-branch, /git-commit, /github-pr-push 세 skill 로 나눠져 있던 것을 하나로 통합한 결과다.
다섯 phase 를 거친다.
- Branch — 변경 내용의 관심사를 분석해 PR 분리 여부를 결정하고 컨벤션에 맞는 브랜치를 생성한다.
- Commit — staged diff 를 리뷰해 관심사별로 분리하고 컨벤션에 맞춘 메시지를 쓴다.
- Push & PR — 정적 분석(lint/typecheck) 후 push,
gh pr create로 PR 을 올린다. - Review — 변경 규모(TRIVIAL/SMALL/MEDIUM/LARGE)에 따라 리뷰 강도를 조절해 에이전트 병렬 호출. 이슈 발견 시 수정 → push → 재리뷰 루프.
- Merge — 리뷰 이슈가 모두 해결되면 squash/merge 선택 후 머지.
/code 는 전체 sub-task 가 PASS 이면 /github-ship 실행 여부를 자동으로 물어본다. 승인하면 코드 구현부터 PR merge 까지 끊김 없이 이어진다.
별도로 남아 있는 PR 관련 skill 은 둘이다.
/github-pr-review <PR번호>— 이미 올라간 PR 을 심층 리뷰한다.github-ship내부 Phase 4 와 같은 에이전트를 쓰지만 독립 호출용./github-pr-respond— PR 리뷰 코멘트를 순차로 돌며 반영 여부를 확인하고 답변을 게시한다.
CLI 선택 이유
도구를 붙이는 방식은 크게 MCP 서버와 CLI 호출 두 갈래다. git / GitHub 영역에는 양쪽 구현이 다 있다. 그런데 위 skill 들은 전부 gh, git 같은 CLI 를 Bash(...:*) allowed-tools 로 호출한다. 이유는 컨텍스트 절약 이다.
MCP 서버는 연결되는 순간 자기 tool 카탈로그를 컨텍스트에 상주시킨다. tool 하나당 수백 토큰, 서버 하나가 20 여 개 도구를 노출하면 “아무것도 하지 않아도” 수천 토큰이 소비된다. 서버 세 개를 붙이면 타이핑 한 글자 하기 전에 4,000 토큰 이상이 소비된다는 측정이 있다 (Scott Spence). 반면 CLI 는 호출 시점에만 토큰을 소비한다. 실제 비교에서 CLI 는 같은 작업을 두고 MCP 대비 토큰 소모를 약 68% 줄인 것으로 보고되며 (BSWEN — MCP vs CLI), 월 운영 비용 기준으로는 4~32 배 차이 까지 나타났다 (BSWEN — Token usage).
Anthropic 도 이 비용을 인지해 Tool Search 같은 지연 로딩 최적화를 도입했고, MCP 사용 시 에이전트 토큰 소모를 46.9% 줄였다고 밝혔다 (Joe Njenga, Medium). 그럼에도 git 과 GitHub 처럼 이미 성숙한 CLI 가 있는 도메인은 skill + Bash 조합이 여전히 가장 가볍다. github-ship 을 비롯한 git/GitHub skill 들이 MCP 없이 CLI 로만 구성된 이유다.
구분 포인트: “내가 호출할 것인가, 모델이 알아서 부를 수 있는가” 의 두 층이 Skill 레이어 내부의 축이다. disable-model-invocation 플래그가 그 경계를 그린다 — 위험하거나 되돌리기 어려운 쓰기 작업은 잠가두고, 일상적으로 auto-trigger 되어야 하는 것은 풀어둔다.
Hooks
Hooks 는 호출되지 않는다. 도구 이벤트에 반응해서 자동으로 실행된다. settings.json 의 hooks 에 PreToolUse / PostToolUse 로 등록하면, 특정 도구 호출 전후에 셸 스크립트가 개입한다.
Hooks 의 자리는 크게 둘이다.
- 가드레일 — 위험한 명령을 실행 전에 차단한다.
remote-command-guard.sh는 Bash 호출 전(PreToolUse)에 끼어들어rm -rf,curl | sh,/etc/passwd접근 같은 카테고리를 검사하고, 걸리면exit 2로 차단한다. - 자동화 — 파일 수정 후에 포매터를 돌리거나(
format-file.sh), 보안 관련 파일 수정 시 리뷰를 권고하거나(security-auto-trigger.sh), 모든 도구 출력에서 시크릿을 마스킹하는(output-secret-filter.sh) 일이 여기 들어간다. “매번 손으로 하고 싶지 않지만 매번 해야 하는 것” 의 자리다.
Permission allow/deny 는 Hooks 와 짝을 이룬다. settings.json 의 permissions.deny 는 정적 필터다. Bash(*rm -rf*) 같은 패턴을 선언하면 매칭되는 명령은 아예 도구 호출로 넘어가지도 않는다. 그 위에 Hooks 가 동적 검사를 추가한다. 정적 필터로 거르기 어려운 맥락 의존적 위험(특정 경로로의 redirect, 조건부 조합 같은 것)을 스크립트가 판단한다. 정적 선언 + 동적 검사 의 두 층이 한 레이어로 묶여 가드레일 구실을 한다.
구분 포인트: “도구 이벤트에 자동으로 개입해야 하는가” 가 Hooks 의 기준이다. 호출이 없어야 한다는 조건이 Skills/Agents 와 Hooks 를 가른다.
통합 워크플로우
레이어 하나씩 보면 각자의 책임이 선명하지만, 실제로는 하나의 작업 흐름에서 네 레이어가 동시에 실행된다. 한 가지 플로우로 그려본다.
/code-brainstorming "새 인증 모듈 설계"를 친다 → Skill 이 움직인다.- 그 Skill 이 내부에서
architect와planner를 dispatch 한다 → Agents 가 격리 컨텍스트에서 설계 분석과 단계 분해를 한다. - 이 과정 내내 매 턴 컨텍스트에 언어별 규칙과 코드 원칙이 로드되어 있다 → Rules 가 조용히 깔려 있다.
- 설계가 끝나 구현으로 들어가면
/code <plan-dir>이 파일을 쓰기 시작한다 → 매번Edit/Write가 호출된다. - 그때마다 Hooks 가 반응한다.
format-file.sh가 포매터를 돌리고,code-quality-reminder.sh가 에러 핸들링·불변성 점검을 상기시키고, 보안 파일이면security-auto-trigger.sh가 리뷰를 권고한다. - 파이프라인 마지막에
/code는 다시verify-agent를 호출한다 → 빌드·타입·린트·테스트가 격리 실행되어 결과만 돌아온다. - 전체 PASS 이면
/code가/github-ship실행 여부를 묻는다 → 승인하면 다시 Skill 이 움직여 branch → commit → push → review → merge 까지 이어진다.
한 작업에 네 레이어가 전부 참여하지만 각자의 책임은 겹치지 않는다. 내가 부른 것 (Skills), Skill 이 위임한 것 (Agents), 항상 깔려 있던 것 (Rules), 이벤트에 자동으로 반응한 것 (Hooks) — 네 가지는 서로의 자리를 침범하지 않는다.
정리
새 설정을 어디에 넣을지 결정할 때 네 질문만 거치면 된다.
- 항상 깔려야 하는가? → CLAUDE.md + Rules
- 도구 이벤트에 자동 반응해야 하는가? → Hooks
- 호출해서 실행되는 workflow 인가? → Skills
- 메인 컨텍스트와 분리 실행되어야 하는가? → Agents
둘 이상에 걸리면 책임을 쪼갠다. 설정 전체는 .dotfiles/claude/ 를 참고.