quad/.claude/session/2026-08-16-03-doc-check-section-convention.md
qwreey 40a5daf694
tooling: 절 인용 규약 신설 + doc-check 절 참조를 ERROR 게이트로 승격
사용자 제기 — 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
2026-08-16 10:47:29 +09:00

270 lines
17 KiB
Markdown

# 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`의 몫.
## 우선순위 제안과 사용자 결정
1. 절 인용 규약 + ERROR 승격 (WARN의 91%)
2. 세션 지칭 표기 (grep을 깨뜨림)
3. 날짜 마커 라벨 어휘 닫기
사용자 결정: **1+2 채택, 3 기각.** 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에 이미 이행된 정정을 아직 "정정 대상"이라
부르고 있었다.
## 검사기 보완 — 조용한 통과 구멍 둘
에이전트들이 검사기 자체의 결함을 짚었다.
1. **인용이 60자를 넘으면 `REF`가 매칭 자체를 안 해 검사에서 조용히
빠져나갔다.** 위양성보다 나쁜 종류라 상한을 160자로 넓힘. 그러자 3건이
새로 드러났는데 전부 **줄바꿈 때문에 원문에 없는 공백이 인용에 끼어든**
케이스여서, 비교를 공백 무시로 바꿔 순증 작업량 0으로 닫음.
2. **선두 상태/날짜 태그**(`**[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건이 나왔다.
둘 다 **이번 세션이 새로 생산한 산문**의 문제였다.
1. **`base/brand-plan.md`에 이번에 새로 쓴 문장이 "세션은 파일 ID로
지칭할 것" 규약을 어겼다** — 같은 커밋에서 만든 규약을 같은 커밋의 새
문장이 위반. `2026-08-07 여덟 번째 세션` → 세션 파일 ID로 교정했고,
이번에 건드린 다른 두 줄(`README.md`의 `tools/` 행, `lifecycle-pattern.md`)도
규약의 "만지는 김에 같이 고친다" 조항대로 처리했다.
2. **`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:` 필드가 그대로
반영되지 않는 기존 미해결 항목) 이런 규약이 유일한 방어선이다.