# Design: wiki-structure-lint — 결정론적 문서 구조 린터 - 날짜: 2026-06-01 - 대상: `.claude/hooks/wiki_structure_lint.py` (신규), `/depth` 연동, (2차) PostToolUse hook - 목적: 위키 문서가 **자기 `source_type` 템플릿의 양식을 따르는지** 결정론적으로(파이썬) 검증. "templates대로 안 된 문서"를 코퍼스 전수로 색출하고, P3(옵시디언 링크 문법)와 depth 게이트의 기계적 사전체크를 한 도구로 묶는다. --- ## 0. 배경 / 왜 결정론적 레이어인가 `branch-depth-gate`(LLM 의미 게이트, 별도 스펙)를 설계하다, 검증을 결정론(파이썬) vs 의미(LLM)로 나누는 게 자연스럽다는 결론. 파이썬은 **틀·양식 + 기계적으로 판별 가능한 것**(섹션 존재, 링크 문법, 앵커 실재)만 보고, 의미(claim 깊이 L0/L1, 조건의 진위)는 LLM 감사기가 본다. 여기에 사용자 관찰이 더해짐: **"지금 문서들이 templates대로 안 되어 있는 게 매우 많다."** → 결정론 레이어를 *먼저* 짓는 게 즉시 가치(비적합 문서 색출) + 싼 게이트 우선. 기존 자산과의 경계: - `wiki_claim_gate.py` (PreToolUse hook, 유지): 특정 표(Claims Extracted 등) 존재를 write 시점에 강제. 본 린터는 이를 일반화(전 source_type · 전 섹션). - `/lint` (LLM, 유지): 의미 품질(과장·canonical 우회 등). 본 린터는 결정론 구조만. ## 1. 검증 3군 (전부 이진 PASS/FAIL — 심각도 단계 없음) 문서당 결과는 `PASS` 또는 `FAIL + 사유 목록`. 사용자 결정: "양식대로 안 되어있으면 불통." ### C1. 템플릿 적합성 문서는 자기 `source_type` 템플릿의 **필수 + 무마커 섹션 전부** + **frontmatter 키 전부**를 가져야 PASS. - `(있다면)`·`(있을 때)`·`(전용)`·`(infra 전용)` 등 **optional 마커 섹션**은 없어도 PASS. - **무마커 섹션 = 필수**로 간주(양식이므로). - 템플릿에 없는 **추가 섹션은 허용**(불통 아님). - `source_type`이 어느 템플릿과도 매칭 안 되면 → 불통(`UNMAPPED_SOURCE_TYPE`). ### C2. 옵시디언 링크 문법 (= P3 흡수) - `` `[[...]]` `` 인라인 코드/백틱에 싸인 위키링크 → 불통(옵시디언이 링크로 렌더 안 함). - `[[경로]]` 타깃 파일 부재 → 불통(`BROKEN_LINK`). - `[[파일#앵커]]` 의 앵커가 대상에 부재 → 불통(`DANGLING_ANCHOR`, best-effort: 헤딩 또는 Claim ID 토큰 검색). - fenced code block(```` ``` ````) 내부의 `[[...]]` 는 예시이므로 **스킵**(false positive 방지). 인라인 백틱은 검출. ### C3. depth 구조 사전체크 (branch-note 만) - Decision Evidence Map 의 `선택 조건` 셀 비어 있음 → 불통. - `## 엣지·실패·의존` 섹션 없음/내용 없음 → 불통. - (신규 템플릿 적용 브랜치에 발동. 기존 80개는 C1에서 "섹션 없음"으로 먼저 걸림.) ## 2. source_type → 템플릿 매핑 (거의 자동 도출) 해석 순서 (2026-06-01 정리 후 — **모든 템플릿이 frontmatter source_type 를 직접 선언**, fallback 제거됨): 1. 각 템플릿 frontmatter `source_type:` 파싱. **다중값은 `|`/`,` 구분** → `raw-source-template: official-doc | company-tech-blog | personal-blog`. 2. `daily-task` 는 문서 frontmatter `track:`(develop/infra)으로 분기. 3. (보조) `## source_type 허용값` 섹션 파싱은 backward 호환용으로 유지. 4. 위 어디에도 안 걸리는 source_type → `UNMAPPED_SOURCE_TYPE` 불통. 명시 결과 (빈 source_type 4종 해소): `concept-template ← llm-generated`, `raw-source-template ← official-doc|company-tech-blog|personal-blog`, `interview-template ← interview`, `source-summary-template ← source-summary`(둘 다 wiki/concepts 층이라 구분 필수). 매핑 SSOT 가 전부 템플릿 frontmatter 안에 있음(스크립트 상수 fallback 0). **nav/hub/meta 제외**: layer 최상위 직속 파일(`wiki/llm-wiki.md`·`wiki/log.md`) + `README.md`/`log.md`/`index.md` 는 템플릿 콘텐츠가 아니므로 린트 스킵. frontmatter 없는 스텁은 섹션 누락 도배 대신 `NO_FRONTMATTER` 1건으로 표면화. ## 3. 헤더 정규화 매칭 번역·괄호주석·순서 차이를 흡수: - `## Parent / 부모 (필수)` → 선두 `## ` 제거 → 괄호 `(...)` 제거 → `/` 분리 → 각 토큰 strip+소문자 → `{parent, 부모}`. - 문서 헤더도 같게 정규화. **순서 무관 집합 비교.** 한쪽(한글/영문)만 있어도 매칭. - optional 판정: 원본 헤더에 optional 마커 정규식(`있다면|있을 때|있으면|전용|optional`) 포함 여부. ## 4. CLI / 출력 / 통합 - **표준 라이브러리만** (claim_gate.py 스타일, 의존성 0). frontmatter 는 라인 파싱(`^key:`). - 호출: - `python3 .claude/hooks/wiki_structure_lint.py --file ` — 단일. `/depth` 사전체크·hook용. - `python3 .claude/hooks/wiki_structure_lint.py --all` — `raw/`·`wiki/` 전수 스윕. - 출력: 문서별 `PASS` / `FAIL` + 사유(rule code · line · 설명). `--all` 은 끝에 요약(전체 N, FAIL M, source_type별 집계). - exit code: FAIL 있으면 ≠ 0 (hook/CI 게이트용). - `/depth` 연동: 먼저 `--file` 결정론 검사 → 구조 불통이면 그것부터 보고, 통과 시 LLM `branch-depth-auditor` 로. - (2차) PostToolUse hook: 저장 시 C2 자동 검출(P3 목적). 1차는 CLI만. ## 5. 검증 / 수용 기준 - `--all` 이 정상 종료하고 source_type별 집계를 출력. - 알려진 비적합(`personal-blog`·`meta`·`error` source_type)이 `UNMAPPED_SOURCE_TYPE` 로 잡힘. - 의도적으로 백틱 래핑한 테스트 링크가 C2 로 잡힘. - 잘 정비된 문서 1개(예: 최근 branch-note)가 PASS. - false positive 점검: fenced code block 내 예시 `[[...]]` 가 BROKEN 으로 안 잡힘. ## 6. 자동수정(autofix) — 백틱 래핑만 사용자 결정으로 **안전한 한 종류만** 자동수정 도입: 순수 `` `[[...]]` `` 래핑 → `[[...]]`. - `--fix` (기본 dry-run, 변경 미리보기) / `--fix --apply` (실제 기록). - fenced code block 내부·혼합 코드 스팬(`` `foo [[x]] bar` ``)은 **건드리지 않음**(보수적). - git 미사용 환경이므로 `--apply` 전 **폴더 백업 필수**. - 2026-06-01 1회 실행 결과: 388파일/3117곳 수정, 백업 `llm-wiki-backup-20260601-214319`. 비목표: - 그 외 autofix 없음(누락 섹션·frontmatter 는 내용 생성 불가 → 마이그레이션 영역). - 의미 품질 판정 없음 — LLM `/lint`·`branch-depth-auditor` 담당. - 멀티 CLI 전파(Codex/Gemini) 범위 밖. ## 6.1 알려진 한계 (구현 중 보정) - 링크 타깃 해석: `[[x.md]]` 의 `.md` suffix 허용, templates/ 도 유효 타깃으로 인덱싱(초기 FP 2종 수정 완료). - placeholder 링크(`[[X]]`·`[[URL]]`·`[[double bracket]]` 등 예시 텍스트)가 BROKEN_LINK 로 잡힘 — 향후 예시 라인 스킵 규칙으로 보정 여지. - 템플릿 드리프트(MISSING_SECTION 571·MISSING_FRONTMATTER 89)는 본 린터의 검출 대상이되, 해소는 **문서→템플릿 마이그레이션**(별도 사이클). 린터가 그 진척도 측정기 역할. ## 7. 산출물 | 파일 | 작업 | |---|---| | `.claude/hooks/wiki_structure_lint.py` | 신규 (C1+C2+C3, CLI 2모드) | | `/depth` (depth 게이트 구현 시) | 사전체크로 본 린터 호출 | | (2차) `.claude/settings.json` | PostToolUse hook 등록 (C2 자동) |