quad/.claude/session/2026-08-16-01-subagent-audit-and-claudemd-split.md
qwreey 8aeec7644f
docs: CLAUDE.md 4분할(매 세션 로드 1537→354줄), 워크플로 가짜 초록불 수정
## 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
2026-08-16 01:35:02 +09:00

258 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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건.