사용자 제기 — doc-check.py가 정규식으로 결정론적 판정을 하는데 표기가 흔들리면 문제가 커지니, 정규식을 늘리기보다 "예상 가능 범위"를 컨벤션으로 좁히는 게 싸지 않냐. 실측해보니 날짜 표기는 이미 100% 균일해서 고칠 게 없었고(강제 장치 없이), 드리프트는 절 인용 쪽이었다 — WARN 86건 중 78건(91%)이 절 참조 불일치. 핵심은 그 78건이 코퍼스가 지저분한 게 아니라 **검사기가 못 읽는 것**이었다는 점이다. 이 코퍼스는 `**볼드**` 줄을 하위 절로 쓰는데 headings()가 `#`만 봤다. ## 동작 변화 (문서 정정으로만 보이지만 게이트가 바뀐다) - 절 참조 불일치가 **WARN → ERROR**. 이제 절 인용 오류가 커밋을 막는다. - 절 인식이 `#` 헤딩 + `**볼드**` 절로 확장. 단 볼드는 **빈 줄 다음이나 리스트 항목 머리**만 인정 — 문단이 줄바꿈되며 우연히 줄머리에 걸린 강조를 절로 오인하던 걸 커밋 전 감사가 잡아 조였다. - 인용 길이 상한 60→160자. 60자를 넘으면 매칭 자체가 안 걸려 검사에서 **조용히** 빠져나갔음(위양성보다 나쁜 구멍). - 비교를 공백 무시로(줄바꿈 인용 대응), 선두 장식·상태/날짜 태그 정규화, `initreq/` 대상 인용은 절 검사 면제(읽기 전용 외부 원본). ## 규약 `conventions.md`에 "문서 표기 규약" 절 신설 — 절 인용 규약(의역 금지, 헤딩은 부분문자열/볼드는 앞부분일치, 태그로 닫히는 볼드 캐비엇, blockquote 함정), 세션은 산문 서수 말고 파일 ID로 지칭. 날짜 마커 라벨 어휘 닫기는 사용자 판단 으로 기각(기계 검사 대상이 아니라 읽는 쪽 판단 재료). ## 결과 절 참조 불일치 78 → 0. 36건은 검사기 수정으로 사라졌고(애초에 위양성), 42건은 인용을 실제 절 제목으로 손으로 고쳤다. 마지막 15건은 서브에이전트 3개에 병렬 위임해 추적 — **설계 서술이 유실된 건은 0건**, 대부분 코드 주석·본문 산문· 주제명처럼 애초에 절이 아닌 걸 절로 인용해온 것이었다. 부수로 드러나 같이 고친 것: onchange-plan이 9차 분할 때 일부러 안 옮긴 절을 잘못된 파일로 가리키던 것, brand-plan이 이미 이행된 정정을 "정정 대상"이라 부르던 것, ROADMAP의 blockquote가 인용 줄바꿈 때문에 깨져 있던 것, pre-implementation-audit의 해소된 항목이 "아직 안 고침" 절에 남아 있던 것 (사용자 결정으로 "이미 고침"으로 이동). 커밋 전 감사 4라운드(에이전트 8개)를 돌렸고, 발견 추이는 2→2→1→0이다. 매 라운드 발견이 "직전 라운드 수정이 만든 새 결함"이었던 게 특징 — 규약을 세우는 커밋이 그 규약의 첫 위반자가 된다는 걸 실측으로 확인했다. 상세는 .claude/session/2026-08-16-03-doc-check-section-convention.md. 부수: __pycache__를 .gitignore에 추가하고 추적 해제(32e9db0에 실수로 딸려 들어가 있었음). quad-doc-auditor에 작업 트리를 바꾸는 git 명령 금지 규약 추가 — 감사자가 git stash를 걸어 메인 세션 스테이지가 반복적으로 풀렸다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011zk7XHkSfiBfdPLZUQHdZf
17 KiB
2026-08-16 세 번째 세션 — 표기 컨벤션으로 doc-check 정규식 줄이기
전 세션(session/2026-08-16-02-audit-tooling-verification-and-first-real-run.md)
인수인계를 받고 시작. 설계는 안 건드림, 문서 툴링만.
사용자 문제 제기
현행 md 툴링 py 상 날짜 적는거 형식 같은것도, 강제화 하게 하는게 맞다 생각이 드는듯. doc-check.py 자체가 regex 를 여럿 사용해 정적으로, 결정론적이게 분석하는데, 형식이 많이 왔다 갔다 하면 문제가 커져. 여러 regex 를 추가해가며 하는것도 좋지만, '예상 가능 범위' 를 어느정도 컨벤션으로 정하는게 좋다 생각하는데(가장 비용이 쌈), 어떻게 봄?
실측 — 날짜는 이미 문제가 아니었다
전수 조사:
20NN-NN-NN 형태 1864건 / 1864건 (100%)
다른 구분자(. 또는 /) 0건
비-ASCII 공백 혼입 0건
대괄호 마커 383건
강제 장치가 없는데 100% 균일. 사용자 직감("싼 컨벤션이 통한다")을 반증하는 게 아니라 오히려 뒷받침하는 데이터라, 이걸 근거로 삼되 드리프트가 실제로 있는 곳으로 방향을 돌렸다.
doc-check.py 당시 출력: 라이브 문서 88개, ERROR 0, WARN 86건 —
그중 절 참조 불일치가 78건(91%). 날짜 없는 주장은 3건뿐.
재검증 시 주의: 이 78은 실제 작업 트리 기준이다.
git archive HEAD같은 걸로 격리 체크아웃해 돌리면 72가 나오는데, 그건 아카이브에 gitignore된.claude/initreq/가 안 들어가서raw-userinput.md를 가리키는 인용들이 "절 참조 불일치"가 아니라 "깨진 파일 참조"로 분류되기 때문이다 (커밋 전 4라운드 감사가 실제로 이 차이에 걸렸다). 숫자를 대조할 땐 트리를 갈아엎지 말고git show HEAD:<경로>로 파일 단위 비교를 쓸 것.
프레이밍 교정 — 컨벤션은 정규식의 대안이 아니다
사용자가 "regex를 늘릴까 / 컨벤션을 정할까"의 양자택일로 물었지만, 실제
관계는 컨벤션이 정규식을 열거형에서 앵커형으로 바꿔 작게 만드는 것이다.
DATED가 "±3줄 안에 아무 날짜나 있으면 통과"라는 느슨한 판정을 하는 이유가
정확히 앵커할 표준 마커가 없어서다.
동시에 컨벤션이 못 고치는 영역도 명확히 했다 — 검사는 두 종류다.
- (A) 이스케이프 해치형: 도구가 위험 패턴을 잡고 사람이 표준 마커로
잠재움(
REF절 인용,DATED,OURS). 컨벤션이 곧바로 이득. - (B) 탐지형: 도구가 위험한 문장을 먼저 찾아야 함(
TEMPORAL의 6개 한국어 표현,'아직 반영 안 됨'리터럴). 컨벤션이 원리적으로 못 고침 — "시한부 주장에 날짜를 붙여라"를 정해도 정규식은 날짜를 안 붙인 문장을 못 찾는다. 이 잔여가quad-doc-auditor의 몫.
우선순위 제안과 사용자 결정
- 절 인용 규약 + ERROR 승격 (WARN의 91%)
- 세션 지칭 표기 (grep을 깨뜨림)
- 날짜 마커 라벨 어휘 닫기
사용자 결정: 1+2 채택, 3 기각. 3에 대한 논거가 날카로웠음 —
- 의 경우 사실상 에이전트가 순서 섞였을 때 최신의, 옳은 요소 선택에 도움이 되는 정보에 가깝지 이게 warn 을 만들지는 못할듯.
즉 라벨은 기계 검사 대상이 아니라 읽는 쪽(에이전트)의 판단 재료다. 어휘를
닫아도 검사가 하나도 안 생기므로 비용만 든다. 3에 딸려 있던 DATED 앵커
좁히기만은 유일하게 WARN을 만드는 항목이라 성격이 다르지만, 지금 그 위음성이
아프다는 증거가 없어 같이 보류.
핵심 발견 — 78건은 대부분 "코퍼스가 지저분한 것"이 아니라 "검사기가 못 읽는 것"이었다
dispatch-core-plan.md는 # 헤딩이 11개뿐인데 다른 문서들이 인용하는
"우선순위 동률/매치 실패 처리", "핸들러 내부 상태 저장", "props 순회 순서"는
전부 실재한다 — **볼드**로 시작하는 줄로. headings()가 # 줄만
수집하니 이게 통째로 안 보였다.
그런데 볼드를 통째로 절 제목으로 인정하면 검사가 장식이 된다:
| 파일 | # 헤딩 |
볼드(전체) | 볼드(줄머리) |
|---|---|---|---|
dispatch-core-plan.md |
11 | 264 | 130 |
slot-plan.md |
40 | 331 | 162 |
architecture.md |
9 | 60 | 23 |
세 규칙을 실측 대조:
A 느슨 볼드 + 부분문자열 해소 30
B 줄머리 볼드 + 부분문자열 해소 32
C 줄머리 볼드 + 앞부분일치 해소 26 → 선두 장식(⚠️) 정규화 추가 후 30
C가 A와 해소 건수는 같으면서 매칭 표면만 좁다. C 채택. # 헤딩은
부분문자열, 볼드 절은 앞부분일치라는 비대칭이 핵심 — 헤딩은 명시적 절
마커라 오인 위험이 없고, 볼드는 강조와 구분이 안 되니 더 엄격해야 한다.
부수로 1. **…**(번호 리스트 볼드)도 절로 인정하도록 넓힘 —
conventions.md의 핸드오버 체크리스트가 정확히 그 형태고 다른 문서가 그걸
절로 인용한다.
initreq/ 면제
raw-userinput.md는 .claude/initreq/에 있는 읽기 전용 원본 요청이고, 스스로
"진짜 생각의 흐름 대로 작성되어있으며 정리가 없음"이라 밝힌 문서다. 여길
가리키는 인용은 절 제목이 아니라 원문 문장을 따오는 것이라 헤딩 대조가 애초에
무의미 → 폴더 범주로 면제. (is_history()를 넓히는 건 오답이었음 — 그건
인용하는 쪽 문서를 면제하는 스위치인데 여기선 인용 대상이 문제였다.)
결과
절 참조 WARN 78 → 42 (검사기 수정) → 23 (인용 19건 교정)
→ 15 (인용 8건 추가 교정) → 0 (추적 후 15건 교정)
그리고 WARN → ERROR 승격
교정한 인용은 대부분 "실제 절이 명확히 존재하는데 의역돼 있던 것". 대표적으로
architecture.md의 복사(clone) 구현 지양을 "복사 구현 지양"으로,
이벤트 핸들러는 self(Instance)를 받지 않는다를 "self를 받지 않는다"로 줄여
쓴 것들. operator-sugar-plan.md는 tween-plan.md가 research/→base/로
승격된 걸 안 따라와서 경로도 같이 고침.
큰따옴표가 든 절 제목은 인용 문법("...")을 깨므로 앞부분만 인용하는 규칙이
여기서 나왔다(pluggable 디스패치 엔진 자체도 "인터페이스"로 base가 소유).
마지막 15건 — 서브에이전트 3개에 병렬 위임
"그 절이 어디로 갔는가"를 따져야 해서 읽을 양이 많았고, 메인 컨텍스트를
지키려고 대상 파일별로 3등분해 위임했다(읽기 전용, 발견만 리포트).
총평: 설계 서술이 유실된 건은 0건. 대부분 "애초에 절이 아닌 것을 절로
인용해온 것"이었다 — 코드 주석 한 줄(클로저 생존이 곧 gchold 생존),
본문 산문(데이터를 테이블에 직접 두고), 앞으로 쓸 문서의 주제명
(왜 quad를 쓰는가), 그냥 의역(bind는 유일 슬롯).
부수로 드러난 것 — onchange-plan.md가 9차 분할 때 일부러 안 옮긴 절을
event-plan.md로 잘못 가리키고 있었고(실제 위치는 bind-system-plan.md),
brand-plan.md는 2026-08-07에 이미 이행된 정정을 아직 "정정 대상"이라
부르고 있었다.
검사기 보완 — 조용한 통과 구멍 둘
에이전트들이 검사기 자체의 결함을 짚었다.
- 인용이 60자를 넘으면
REF가 매칭 자체를 안 해 검사에서 조용히 빠져나갔다. 위양성보다 나쁜 종류라 상한을 160자로 넓힘. 그러자 3건이 새로 드러났는데 전부 줄바꿈 때문에 원문에 없는 공백이 인용에 끼어든 케이스여서, 비교를 공백 무시로 바꿔 순증 작업량 0으로 닫음. - 선두 상태/날짜 태그(
**[2026-08-16 신설]** 제목…)가 볼드 절 제목의 앞부분을 차지해 어떤 제목형 인용도 매칭 불가였다. 태그는 시간이 지나면 바뀌는 메타 표시라 제목의 일부가 아니므로 양쪽에서 벗겨내고 비교. 이건 ERROR 승격 직후 게이트가 내가 방금 쓴 인용 두 개를 잡아서 발견됐다.
인용을 짧게 자르다 문장을 깬 것들 — 프로즈 패스에서 회수
정규식이 통과해도 의미는 깨질 수 있다. 커밋 전 diff를 "인용 매칭"이 아니라 "문장 읽기" 모드로 다시 훑어 회수한 것들:
ROADMAP.md의 blockquote를 통째로 깨뜨렸었다 — 인용이>줄바꿈에 걸쳐 있어 마커가 인용문 안에 들어가 있었고, 그걸 지우면서 인용 블록이 무너졌다. 규약에 함정으로 명시.- 절 제목을 명사구 자리에 그대로 끼워 문법이 깨진 문장 5건
(
"pluggable 디스패치 엔진 자체도"는 원칙에…)."…" 절이 세운 원칙형태로 교정하고 이것도 규약에 넣음. - 축약하다 주장이 빠진 인용 1건(
README.md의 "빈 자리 아님" 결론).
1라운드 감사 — 규칙 자체의 정밀도 구멍
두 번째 감사자가 더 중요한 걸 짚었다 — attribute-plan.md와
dispatch-core-plan.md가 인용한 architecture.md의
**pluggable 디스패치 엔진 자체도 …**는 절이 아니다. 한 문단이
줄바꿈되면서 강조 스팬이 우연히 줄머리에 걸린 자리이고, 진짜 절은 두 줄 위의
**패키지 경계**다. 같은 원문을 인용하는 slot-plan.md:209는 이미 올바르게
"패키지 경계"를 쓰고 있어서, 같은 diff 안에 옳은 인용과 우연한 인용이
공존하고 있었다.
즉 "줄머리 볼드"라는 규칙만으로는 절과 랩된 강조를 못 가른다. 진짜 절 제목은
빈 줄 다음에 오거나 리스트 항목 머리라는 성질이 있어서 그걸 조건으로
추가했다. 실측 결과 정확히 그 가짜 인용 2건만 깨지고 나머지 46건은 그대로
통과했고, 볼드 후보 풀은 dispatch-core-plan.md 기준 131→104로 좁아졌다.
위양성 0으로 정밀도만 올린 셈.
교훈: 이 규칙은 "사람이 절이라고 인식하는 것"을 근사하는 휴리스틱이라, 근사가 어긋나는 자리는 실제 인용을 감사해서만 드러난다. 검사기가 통과시켰다고 그게 절인 건 아니다.
1라운드 감사 — 이 세션 자신의 실수
pre-implementation-audit.md의 "아직 안 고침" 절에 있는 State<Modifier>
비일관성 항목이 해소된 것으로 보여 **[해소됨 — 2026-08-16 확인]** 마커를
달았는데, 바로 다음 문단에 2026-08-09 세션이 단 **[완전 해소, 2026-08-09 세션]**이 이미 있었다. 중복일 뿐 아니라 해소 시점을 일주일 뒤로 잘못
읽히게 만들어 되돌렸다. 서브에이전트(추적 담당)가 "후임 절이 옛 서술을
뒤집었다"는 근거를 정확히 줬지만 그 항목이 이미 해소 표시를 달고 있다는
사실은 안 봤고, 메인 세션도 그 문단 아래를 안 읽었다 — 근거가 맞아도
"이미 처리돼 있지 않은가"를 따로 확인해야 한다는 교훈.
이 항목이 해소 표시를 달고도 "아직 안 고침" 헤더 아래 남아 있는 헤더-내용
불일치는 이 세션 이전부터 있던 별개 부채라 임의로 재배치하지 않고 사용자에게
물었고, "이미 고침" 절로 옮기라는 결정을 받아 반영했다. 그 헤더 아래엔
이제 진짜 미해소인 Destroying 훅 건만 남는다. 감사 세 라운드가 전부 이
자리를 지목했는데, 세 번 다 "고칠지 말지가 판단 문제"라 서브에이전트는
결론을 못 냈다 — 메인 세션만 사용자에게 물을 수 있다는 게 이 절차의 핵심인
이유다.
2라운드 감사 — "규칙을 세운 세션이 그 규칙을 어긴다"
1라운드 발견을 반영한 뒤 2라운드를 돌렸더니 또 실질 발견 2건이 나왔다. 둘 다 이번 세션이 새로 생산한 산문의 문제였다.
base/brand-plan.md에 이번에 새로 쓴 문장이 "세션은 파일 ID로 지칭할 것" 규약을 어겼다 — 같은 커밋에서 만든 규약을 같은 커밋의 새 문장이 위반.2026-08-07 여덟 번째 세션→ 세션 파일 ID로 교정했고, 이번에 건드린 다른 두 줄(README.md의tools/행,lifecycle-pattern.md)도 규약의 "만지는 김에 같이 고친다" 조항대로 처리했다.research/framework-comparison-findings.md의 수정이 "고침"이 아니라 "검사망 회피"였다 —"왜 quad를 쓰는가"인용을 문법만 비껴가게 고쳐서 검사 대상에서 통째로 빠졌는데, 감사자가REF정규식으로group(3)이None이 되는 걸 직접 확인해 잡았다. 같은 diff의 다른 두 회피 (CLAUDE.md의 "세션 히스토리",옛 Modifier는 핸들러 계층을 모름)는 과거형으로 참인 사실을 서술하니 정당하지만, 이건 대상 문서에 근거가 없는 미래형 주장이었다는 게 판정 근거. 실제 절(초심자 (getting-started) core loop/quadnomicon)을 가리키도록 고쳤다.
부수로, conventions.md에 박아둔 세션 서수 분포 숫자(971/68/8)가
재현되지 않았다 — 내 집계는 session/까지 포함한 전체 grep이었고 감사자는
doc-check.py가 훑는 88개 라이브 문서 기준이었다. 집계 범위를 안 적은
숫자를 매 세션 로드되는 파일에 박은 것 자체가 이번에 세운 "개수는 소스
하나만" 원칙 위반이라 정성적 서술로 바꿨다. 같은 이유로 날짜 균일 수치도
conventions.md에서 빼고 이 파일을 가리키게 했다.
교훈: 규약을 세우는 커밋이 그 규약의 첫 위반자가 된다. 규약을 쓴 세션은 자기 diff를 그 규약으로 한 번 되짚어야 하고, 그걸 자기 self-audit 으로는 잘 못 한다(이번에도 둘 다 서브에이전트가 잡았다).
3라운드 감사 — 수정이 만든 새 결함, 그리고 사각지대 하나
2라운드 수정을 반영하고 3라운드를 돌렸더니 내가 2라운드에 만든 마크다운
버그가 나왔다. "cleanup" 용어 인용을 규약대로 고치려고 볼드 스팬을
쪼개다 짝 없는 **를 남겼다 — lifecycle-pattern.md의 ** 개수가
144(짝수)→145(홀수)로 바뀐 걸 감사자가 교차 확인해 잡았다. 렌더러에 따라
그 뒤 리스트의 볼드 페어링까지 밀려 깨질 수 있는 실제 결함이었다. 복구한 뒤
변경 파일 전체에 같은 검사를 기계적으로 돌려, 내 변경이 짝을 뒤집은 파일은
그것 하나뿐임을 확인했다(다른 홀수 파일들은 HEAD에서도 이미 홀수 — 코드블록
안의 ** 때문).
구조적 사각지대 하나가 같이 드러났다: - **[해소됨, …]** 실제 제목 — …
처럼 볼드가 상태 태그에서 닫히고 진짜 제목이 볼드 밖 평문이면
(archive/question-resolved.md가 이 형태) 검사기는 [해소됨, …]만 절
제목으로 본다. 절 참조가 ERROR로 승격된 뒤라, 이걸 모르고 규약대로 큰따옴표
인용을 하면 실재하는 서술인데도 커밋이 막힌다. 규약에 캐비엇으로 명시하고
"새로 쓸 땐 태그와 제목을 한 볼드 안에" 지침을 넣었다. 게다가 이 세션이
신설한 "세션은 파일 ID로 지칭" 관례가 **[태그, 세션ID]** 형태의 리드를
늘리는 방향이라 재생산될 구조였다는 지적까지 붙었다.
보류한 것 하나 — # 헤딩 검사가 부분문자열이라 "확정" 같은 짧은
인용은 같은 파일의 무관한 헤딩에 걸려 통과한다(slot-plan.md엔 "확정"이 든
헤딩이 6개). 다만 감사가 실제 오매칭 사례를 하나도 못 찾았고, 사용자와
같이 conventions.md의 "드문 오용이나 가상의 미래 요구까지 방어/최적화하려고
구조를 복잡하게 만들지 않는다" 원칙에 따라 지금은 손대지 않기로 했다.
감사자가 git stash를 쓴다 — 실동에서 드러난 위험
.pyc 추적 해제(git rm --cached)가 두 번 되돌아갔다. 원인은 감사자들이
HEAD와 대조하려고 git stash를 건 것 — 커밋 안 된 작업 트리에서 그걸
하면 메인 세션의 스테이지가 풀리고, pop이 실패하면 세션 작업을 통째로 날릴
수 있다. 이번엔 유실이 없음을 확인했고, .claude/agents/quad-doc-auditor.md에
작업 트리를 바꾸는 git 명령 금지와 대안(git show HEAD:<경로> 등)을 규약으로
넣었다. "읽기 전용"을 도구 목록으로 못 거는 이상(tools: 필드가 그대로
반영되지 않는 기존 미해결 항목) 이런 규약이 유일한 방어선이다.