## CLAUDE.md 분할
1537줄이라 (a) 사람이 검토 불가, (b) 공식 권장치(파일당 200줄) 7.7배
초과로 지침 준수도 자체가 저하, (c) 긴 파일 편집 시 에이전트 실수 증가.
CLAUDE.md (39줄, 진입점)
├─ @.claude/conventions.md 언어/모델 관례 + 작업 방식
├─ @.claude/project-context.md 프로젝트 설명 + 문서 구조
└─ @.claude/todos.md 지금 할 일
.claude/session-summary.md ← import 안 함(의도적, 온디맨드)
세션 히스토리 1231줄(전체의 80%)은 그 문서 스스로 "항상 읽을 필요 없음,
base/가 소스"라고 명시해온 색인이라 @import에서 뺐음. 내용 유실 없음
(1537→1647줄, 추가 헤더만큼 증가).
주의: @import는 컨텍스트를 줄이지 않음(전부 로드됨). 분할이 사는 건
사람 검토성 + 편집 정확도 + 파일 단위 자동생성 가능성.
CLAUDE.md 계열의 블록 HTML 주석은 주입 전 제거되므로 지시는 본문에 쓸 것.
## 워크플로 가짜 초록불 수정
첫 실측에서 감사 에이전트 6개 전원 실패했는데 converged:true가 나왔음
(전멸하면 fresh가 비어 "깨끗한 라운드"와 구분 불가). 감사 도구 최악의
실패 모드라 (1) 전멸이면 throw, (2) 반영 에이전트 실패 시 그 발견을
seen에서 빼 다음 라운드가 재시도하도록 수정.
## 부수
- 분할로 깨진 상호참조 20여 곳 정정(병렬 에이전트 3개).
ref-plan.md:541의 사전 존재 오류(→ pre-implementation-audit 1-5)도 정정.
- doc-check.py: 새 파일 4개를 OURS에 등록(안 하면 깨진 참조가 WARN으로만
잡힘), is_history()로 session-summary.md를 archive/와 같이 면제.
- doc-include-plan.md: 목적지가 통째로 생성되는 파일이 되면서 양방향
마커 설계의 절반(목적지 마커)이 불필요해져 단방향 생성으로 단순화.
doc-check.py ERROR 0 유지.
미해결: session-summary.md 자동생성 미착수(91개 세션 파일 마커 삽입 필요),
orphan 인용 3건(modifier-plan.md:536, v1-compat-plan.md:50,
pre-implementation-audit.md:434 — 분할 이전부터 존재).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HvEKn9f67kkx2nGLG8PsP
258 lines
15 KiB
Markdown
258 lines
15 KiB
Markdown
# 2026-08-16 세션 — 코퍼스 감사 서브에이전트/워크플로 신설, CLAUDE.md 4분할
|
||
|
||
## 발단
|
||
|
||
사용자 질문 두 갈래로 시작:
|
||
|
||
1. 문서 툴링(`doc-check.py`)만으로는 **'실수'가 안 잡힌다** — `/code-review`
|
||
같은 걸 핸드오버 전/커밋 전에 에이전트가 스스로 돌게 만들 수 있나?
|
||
2. 어렵거나 목적에 더 맞는 게 있다면, 프로젝트 서브에이전트를 구성하고
|
||
CLAUDE.md에서 쓰도록 명시하는 쪽이 나을지. "summary를 실제 파일 한쪽으로
|
||
옮겨서 해결"하는 방식으로는 **단순 언급으로 남는 stale**을 잡기 어렵고
|
||
모든 곳에 적용도 어려워 보인다 — 현실적으로는 에이전트가 유의하고
|
||
서브에이전트가 새 맥락에서 훑는 게 최선 아닌가.
|
||
|
||
추가로 "다른 사람들은 이런 큰 기술 계획 문서에 에이전트를 어떻게 쓰는지"
|
||
검색해서 stale/실수 전파의 현실적 완화책을 알아봐 달라는 요청.
|
||
|
||
## 조사 결과
|
||
|
||
### hook으로 code-review를 직접 부를 수는 없음
|
||
|
||
hook은 **셸 명령만** 실행한다. Claude 스킬이나 서브에이전트를 직접 호출하는
|
||
경로가 없다. 대신 실제로 쓰이는 패턴은 **PreToolUse 게이트**:
|
||
`git commit`을 매칭해 조건 미충족이면 `deny`하고, **거부 사유 문자열로
|
||
"서브에이전트를 먼저 돌려라"는 지시를 돌려주는** 것(imti.co의 "Pre-Commit
|
||
Review Gate"). 즉 hook이 직접 부르는 게 아니라, 에이전트가 부르도록
|
||
**막아서 유도**한다.
|
||
|
||
그 글이 남긴 캐비엇도 유효:
|
||
- 기준이 느슨하면 서브에이전트가 통과용 형식적 발견을 지어냄("review theater")
|
||
- 너무 엄격하면 사소한 걸로 라운드가 5번씩 돌아 게이트가 아끼려던 시간을 까먹음
|
||
- 유효성 기준을 **diff 해시**로 잡으면 포매팅만 바뀌어도 재감사가 걸림(브리틀)
|
||
|
||
**quad엔 diff 해시 기준이 안 맞는다**는 판단: 이 프로젝트의 반복 실패 패턴은
|
||
"diff 안"이 아니라 **diff 밖 파일에 남은 stale 참조**다. 기준을 잡는다면
|
||
`.claude/**/*.md` 전체 해시여야 함.
|
||
|
||
### `/code-review`도 스킬이 맞음
|
||
|
||
marketplace 플러그인의 `code-review.md`를 직접 읽어 확인. 구조는:
|
||
Haiku로 자격 검사 → 관련 CLAUDE.md 목록 수집 → **5개 병렬 Sonnet 렌즈**
|
||
(CLAUDE.md 준수 / 얕은 버그 스캔 / git blame·히스토리 / 과거 PR 코멘트 /
|
||
코드 주석 준수) → 발견마다 Haiku가 **0~100 confidence rubric**으로 채점 →
|
||
80 미만 필터링 → 코멘트. false-positive 예시 목록도 프롬프트에 명시돼 있음.
|
||
|
||
즉 사용자 추측대로 "프론트메터와 자연어로 적절히 적으면 되는" 것이고,
|
||
프로젝트 전용으로 다시 만드는 게 타당.
|
||
|
||
### 학계 자료는 새 게 별로 없었음
|
||
|
||
"LLM agent drift / stale context mitigation" 검색 결과는 대부분 이 프로젝트가
|
||
이미 경험적으로 재발견한 것("신선한 세션/맥락이 stale을 줄인다", "요약 압축",
|
||
"주기적 리셋") 수준. 실무에 추가로 얹을 만한 구체 기법은 없었음.
|
||
|
||
## 만든 것 1 — `.claude/agents/quad-doc-auditor.md`
|
||
|
||
읽기 전용(`tools: Read, Grep, Glob, Bash`), `model: sonnet`, `memory: project`.
|
||
|
||
절차를 고정으로 박음: `doc-check.py` 먼저 → 이번에 뒤집힌/확정된 주장 파악
|
||
(git diff + 대화 맥락, 없으면 최근 session 파일) → 그 키워드로 코퍼스 전체
|
||
grep해서 **부정당하는 본문 문장** 찾기 → archive 이전 여부 → 개수/목록
|
||
이중소스 → 인덱스 3레이어 동기화.
|
||
|
||
설계 판단 두 개:
|
||
- **수정 권한 없음.** 발견만 리포트하고 반영은 호출한 세션이 함. 감사와
|
||
수정을 분리하는 게 지금 워크플로우와 맞음.
|
||
- **판정은 "확실/의심" 2단계뿐.** `/code-review`의 0~100 rubric은 diff 하나가
|
||
대상일 때 얘기고, 코퍼스 정합성엔 과한 무게라고 봄. "발견 없으면 그대로
|
||
발견 없음이라고 보고하라, 그럴듯해 보이려고 지어내지 마라"를 명시.
|
||
|
||
`memory: project`를 준 이유: "6개 문서" stale, "8차 세션" 오표기처럼 이
|
||
프로젝트에서 **실제로 반복된** 실수 유형을 지금은 사람이 CLAUDE.md 프로즈로
|
||
수기 기록 중인데, 그걸 에이전트 자신의 기억으로 축적시키는 게 맞다고 판단.
|
||
|
||
## 만든 것 2 — `.claude/workflows/quad-handover-audit.js`
|
||
|
||
사용자 지적이 핵심이었음: **"subagent가 문제를 항상 찾는다고 결정론적으로
|
||
보장되는 게 아니다. 메인 에이전트가 여럿 굴려서 커밋 전에 다 잡게 할 수
|
||
있나."** 그리고 "시간은 걸려도 괜찮다, 부정확성이 문제다. 멀티패스로 여러 번
|
||
굴려야 all consistent가 되기도 한다."
|
||
|
||
그래서 단발 호출이 아니라 **수렴 루프**로 구성:
|
||
- 라운드마다 `quad-doc-auditor`를 **병렬 3회** 독립 실행(서로 뭘 찾았는지
|
||
모르게 — 비결정성을 커버리지로 바꾸는 방식)
|
||
- 새 발견(dedup)을 **파일별로 묶어** `general-purpose`가 즉시 반영
|
||
- **새 발견 없는 라운드가 연속 2번** 나올 때까지 반복(최대 6라운드 안전판)
|
||
- 다음 라운드 감사가 직전 라운드 수정의 검증을 겸함 → 별도 검증 단계 불필요
|
||
|
||
`git commit`은 **의도적으로 워크플로 밖**에 뒀음 — diff 재검토와 커밋 메시지
|
||
작성은 대화형 맥락이 필요.
|
||
|
||
과거 수동 감사의 발견 추이(8→7→11→9→4→0, 9→2→3→2→0→0)를 근거로 상수를
|
||
잡음(`MAX_ROUNDS = 6`).
|
||
|
||
## 첫 실측 — 실패, 그리고 더 나쁜 건 실패가 성공으로 보고된 것
|
||
|
||
커밋(`a1c0e44`) 후 바로 돌림. 결과:
|
||
|
||
```
|
||
converged: true, rounds: 2, totalFindingsFixed: 0
|
||
failures: parallel[0..2] agent type 'quad-doc-auditor' not found (×2 라운드)
|
||
```
|
||
|
||
**에이전트 6개 전원 실패했는데 워크플로는 "수렴했다"고 보고했다.**
|
||
|
||
- 1차 원인은 예상됐던 것: `.claude/agents/`가 **세션 도중** 생긴 디렉토리라
|
||
레지스트리에 없음(공식 문서: 감시자는 세션 시작 시 존재했던 디렉토리만 포함).
|
||
- **진짜 문제는 2차 원인**: 모든 패스가 `null`이 되면 `fresh.length === 0`이
|
||
되고, 이게 "깨끗한 라운드"와 코드상 구분이 안 돼서 그대로 dry 카운트가
|
||
올라가 수렴 판정이 났음. **감사 도구가 아무것도 감사하지 않고 통과 도장을
|
||
찍는 것** — 감사 도구로서 최악의 실패 모드.
|
||
|
||
즉시 가드 두 개 추가:
|
||
1. 라운드의 감사 패스가 **전멸하면 throw**(수렴 판정 자체를 못 내게).
|
||
일부만 실패하면 "커버리지가 그만큼 얕음"을 `log`.
|
||
2. **반영 에이전트가 실패하면 그 발견들을 `seen`에서 제거** — 안 그러면 다음
|
||
라운드 감사가 같은 문제를 다시 찾아와도 dedup에 걸려 조용히 사라지고,
|
||
안 고쳐진 채로 수렴 판정이 남(같은 클래스의 두 번째 버그).
|
||
|
||
### 부수 질문 — 서브에이전트 모델이 왜 Opus로 떴나
|
||
|
||
사용자가 진행 화면에서 `audit-r1-0 Opus 5 · failed`을 보고 물음(정의엔
|
||
`model: sonnet`인데). 답: **정의를 못 찾아서**. 공식 해석 순서는
|
||
`CLAUDE_CODE_SUBAGENT_MODEL` → 호출별 `model` → **정의의 frontmatter** →
|
||
메인 대화 모델인데, 정의가 없으니 3번이 통째로 건너뛰어지고 4번(당시
|
||
`/model opus`로 바뀐 Opus 5)으로 떨어진 것. 워크플로는 `opts.model`을 안
|
||
넘기므로 정의가 로드되면 `sonnet`이 이겨야 정상 — **재시작 후 첫 실행에서
|
||
표시 모델을 확인해야 확정.**
|
||
|
||
## CLAUDE.md 4분할
|
||
|
||
사용자 제안: CLAUDE.md가 사람이 검토하기 힘들다. `@name.md`로 flatten되어
|
||
컨텍스트에 들어가고 캐싱되니, 세션 히스토리 / 할 일 / 작업 방식+베이스
|
||
컨텍스트로 분화하는 게 나을 듯. **"같은 것끼리는 묶는다"가 아주 중요한
|
||
지점.** 그리고 `session-summary.md`는 절대 건들지 말라고 해놓고 **아예 전부
|
||
자동생성** 되게 하는 게 나을 것 같다.
|
||
|
||
### 실측 숫자
|
||
|
||
| 섹션 | 줄 수 | 비중 |
|
||
|---|---|---|
|
||
| 세션 히스토리 | 1231 | **80%** |
|
||
| 지금 할 일 | 116 | 8% |
|
||
| 작업 방식 | 92 | 6% |
|
||
| 계획 문서 구조 | 63 | 4% |
|
||
| 이 프로젝트가 뭔지 | 21 | 1% |
|
||
| 언어/모델 관례 | 12 | 1% |
|
||
| 합계 | 1537 | |
|
||
|
||
### 공식 문서에서 확인한 것 (셋 다 중요)
|
||
|
||
1. **권장치가 파일당 200줄**이고, 초과하면 컨텍스트만 더 쓰는 게 아니라
|
||
**"준수를 줄입니다"** — 사용자가 관찰한 "파일 길어지면 실수 더 하는
|
||
경향"이 문서에도 명시된 알려진 현상. 지금 7.7배 초과.
|
||
2. **`@import`는 컨텍스트를 안 줄인다** — "가져온 파일은 여전히 로드되고
|
||
시작 시 컨텍스트 윈도우에 들어갑니다". 분할이 사는 건 사람 검토성 +
|
||
에이전트 편집 정확도 + **파일 단위 자동생성 가능성**이지 토큰이 아님.
|
||
3. **블록 HTML 주석은 컨텍스트 주입 전 제거된다** — 사람용 메모를 토큰 0으로
|
||
남길 수 있는 대신, **거기 쓴 지시는 에이전트가 못 본다.**
|
||
|
||
(3)에서 이 세션이 자기 함정을 밟음: 새 CLAUDE.md의 "새 서술을 여기 직접 쌓지
|
||
말 것"을 HTML 주석에 넣었다가, 그게 스트립된다는 걸 문서로 확인하고 본문으로
|
||
옮김. 주석엔 "이 주석은 주입 전 제거되니 사람용 메모만 넣을 것"을 남김.
|
||
|
||
부수 발견: **`.claude/rules/*.md`** 라는 별도 메커니즘이 있음 — `@` 없이 자동
|
||
로드되고 `paths:` frontmatter로 **"특정 파일 작업할 때만 로드"**가 됨. 지금은
|
||
`src/`가 없어 쓸모없지만, M0 들어가면 "Handler 작성 체크리스트"를
|
||
`src/**/Dispatch/*.luau`에만 물리는 식으로 쓸 수 있음.
|
||
|
||
### 결정 — session-summary는 import 안 함
|
||
|
||
내가 사용자 안보다 한 발 더 나가는 제안을 함: 세션 히스토리의 고유 가치는
|
||
**"어느 세션 파일을 열지 찾는 색인"**뿐이고(그 문서 스스로 "항상 읽을 필요
|
||
없음, 지금 유효한 설계는 `base/`가 소스"라고 명시해왔음), 색인은 grep으로
|
||
온디맨드 조회하면 되는 종류다 → import 안 걸면 로드가 1537 → ~325줄.
|
||
|
||
사용자 동의: *"나도 session-summary.md 는 차라리 @ 안 붙이고, 그냥 거기에
|
||
있긴 하니까 만약에 선행 맥락이 필요하다면 그 때 가서 느리게 확인하도록
|
||
두는게 맞아보임."*
|
||
|
||
이름은 `code-of-conduct.md`를 제안받았으나 **OSS에서 "기여자 행동강령"이라는
|
||
다른 뜻으로 굳어진 말**이라 피하는 게 좋겠다고 제안 → `conventions.md` 채택.
|
||
|
||
### 최종 구조
|
||
|
||
```
|
||
CLAUDE.md (39줄, 진입점)
|
||
├─ @.claude/conventions.md 108줄 언어/모델 관례 + 작업 방식
|
||
├─ @.claude/project-context.md 88줄 프로젝트 설명 + 문서 구조
|
||
└─ @.claude/todos.md 119줄 지금 할 일
|
||
.claude/session-summary.md 1241줄 ← import 안 함(온디맨드)
|
||
```
|
||
|
||
매 세션 로드: **1537 → 354줄 (77% 감소)**. `session-summary.md`는 상단에
|
||
"자동생성 전환 예정, 전환 후엔 직접 편집 금지"를 명시해둠.
|
||
|
||
## 분할이 깨뜨린 상호참조 정리
|
||
|
||
20여 곳. 파일 그룹별 병렬 에이전트 3개(`base/` / `research/` /
|
||
루트+luau-test+audit)로 정정. 매핑은 `"지금 할 일"`→`todos.md`,
|
||
`"반복 조사 금지"`→`project-context.md`, `"세션 히스토리"`→`session-summary.md`,
|
||
SAFETY 원칙→`conventions.md`.
|
||
|
||
**가장 값진 결과는 base/ 담당 에이전트가 내 전제를 반박한 것**: 내가
|
||
"`dispatch-core-plan.md:41`의 CLAUDE.md 참조는 기존 stale"이라고 지시에
|
||
적어 보냈는데, 실제로는 **줄바꿈에 걸친 `.claude/initreq/tbox/CLAUDE.md`
|
||
참조**(tbox의 type-check/constraint-check 분리 원칙 인용)로 완전히 유효한
|
||
것이었음. 같은 패턴이 `slot-plan.md:1285`, `ref-plan.md:190`에도 있음.
|
||
에이전트가 "다른 에이전트에게 같은 전제를 넘겼다면 정정이 필요하다"까지
|
||
보고함 — 지시를 그대로 믿지 않고 검증한 게 정확히 이 감사 구조가 노린 것.
|
||
|
||
같은 에이전트가 `ref-plan.md:541`의 `위 "지금 할 일" 우선순위1 항목`도
|
||
발견 — 분할과 무관한 **원래부터 잘못된 참조**였고(그 "우선순위1"은
|
||
`research/pre-implementation-audit.md`의 용어), 진단이 확실해서
|
||
`pre-implementation-audit.md` 1-5로 정정.
|
||
|
||
**추측 수정을 금지한 게 효과가 있었음.** 진짜 orphan 3건은 손대지 않고
|
||
보고만 하게 했고, 코퍼스 전체 grep으로 인용된 원칙이 어디에도 없음을 확인:
|
||
- `modifier-plan.md:536` — CLAUDE.md의 "드문 오용/가상 미래 요구까지
|
||
방어/최적화하려고 구조를 복잡하게 만들지 않는다" 원칙
|
||
- `v1-compat-plan.md:50` — CLAUDE.md에 "이 자동 위임/재렌더 매직은 v2에서
|
||
폐기하기로 확정"이라 기록됐다는 서술
|
||
- `pre-implementation-audit.md:434` — CLAUDE.md가 M2/M3/M5에서 quad-debug
|
||
훅 확장 지점을 고려하라고 명시해뒀다는 서술
|
||
|
||
셋 다 **분할 이전부터** 존재하지 않던 인용이다. 원칙을 복원할지 인용을
|
||
버릴지는 사용자 판단 영역이라 남겨둠.
|
||
|
||
## `doc-check.py` 갱신
|
||
|
||
1. **새 파일 4개를 `OURS` 정규식에 등록.** 안 하면 `.claude/todos.md`로 가는
|
||
깨진 참조가 ERROR가 아니라 WARN으로만 잡힘 — 일곱 번째 세션에
|
||
`store-semantics.md`로 실제로 당했던 사각지대와 같은 클래스.
|
||
2. **`is_history()` 헬퍼 신설** — `archive/`와 `session-summary.md`를 묶어
|
||
절 참조/시한부 주장 검사에서 면제. 둘 다 "그때는 그렇게 적었다"가 정상인
|
||
문서라 현재형 검사를 걸면 안 꺼지는 WARN만 쌓임.
|
||
|
||
최종 ERROR 0 유지(검사 대상 78 → 82개).
|
||
|
||
## `doc-include-plan.md` 단순화
|
||
|
||
이 분할이 그 플랜의 **설계 절반을 불필요하게 만들었음.** 원안은 원본에
|
||
`<!--#summary-->`, 인용처에 `<!--#include-->` 두 종류 마커를 두는 양방향
|
||
설계였는데, 세션 히스토리가 독립 파일이 되면서 목적지가 "손으로 쓴 파일 속
|
||
구간"이 아니라 **통째로 생성되는 파일**이 됨 → 목적지 마커가 필요 없어지고,
|
||
id도 불필요(파일명이 곧 순서이자 식별자). "목적지 마커가 손실되면?" 같은
|
||
실패 모드도 같이 사라짐. 파일럿 절을 이 정정으로 갱신.
|
||
|
||
## 남은 것
|
||
|
||
- **재시작 후 검증 필요**: (a) `/memory`로 `@import` 4개가 실제 로드되는지,
|
||
(b) `quad-doc-auditor`가 레지스트리에 잡히는지, (c) 워크플로에서 그
|
||
에이전트가 frontmatter의 `sonnet`으로 도는지(Opus로 뜨면 `opts.model`
|
||
명시가 필요).
|
||
- **`session-summary.md` 자동생성 미착수** — 91개 세션 파일에 마커를 넣고
|
||
생성기를 짜는 마이그레이션이 남음. 그 전까지는 손으로 관리 중이며 파일
|
||
상단에 그렇게 명시해둠.
|
||
- 위 orphan 인용 3건.
|