소개
다양한 한국어 유형을 보여주는 표
명리 상담을 10년, 자료 정리를 그보다 오래 해왔습니 다. 그런데 최근에야 이상한 것을 알아차렸습니다. 같은 개념을 매번 다시 설명하고 있었다는 겁니다. 을축일주를 설명한 대화가 분명 있었는데, 반년 뒤에는 그게 어느 대화였는지 찾지 못해 처음부터 다시 씁니다.
더 심각한 것은 그 다음입니다. 다시 쓴 설명이 어느 계통의 것인지 적혀 있지 않았습니다. 한바둑 프로젝트는 3대 계통의 서술을 따로 관리하는 것이 전제인데, 정리 노트에는 계통 표기가 없었습니다. 계통이 다른 설명이 한 문단에 섞여도 그것을 잡아낼 자리가 없었던 겁니다.
이 문제를 정확히 말해주는 문장을 최근에 읽었습니다.
대화는 시간순으로 쌓이고 지식은 주제와 관계로 조직되므로, 쌓이는 것과 축적되는 것은 서로 다른 일이 된다.
고려대 안준용 교수의 『LLM-Wiki: 지식이 축적되는 AI 작업 환경 만들기』 들어가며에 나오는 대목입니다. 제가 겪던 것이 정확히 이것이었습니다. 대화는 충분히 쌓였는데 축적된 것은 없었습니다.
그래서 이번에 하려던 것은 두 가지입니다.
한바둑 명리 지식을 담을 위키 폴더 구조를 실제로 만든다
계통 분리 규칙을 말이 아니라 코드로 지키게 한다
두 번째가 핵심입니다. 앞선 사례글 5편에서 이미 배운 것이 있습니다 — 프롬프트에 적어 둔 금지 사항은 길어질수록 오히려 덜 지켜집니다. 계통을 섞지 말라고 지침에 아무리 써도, 그것이 판정되지 않으면 규칙이 아니라 부탁입니다.
진행 방법
3-1. 사용한 도구
도구
쓴 곳
Claude (대화)
구조 설계 논의, 규약 초안, 검증기 작성
Python 3
서식 검증기 check_wiki.py
Markdown + YAML front-matter
문서 형식
참고 자료
chaek.org 『LLM-Wiki』 (안준용)
3-2. 먼저 갈렸던 판단 — 계통을 폴더로 둘 것인가, 필드로 둘 것인가
이게 이번 작업에서 제일 오래 고민한 지점입니다. 두 안이 있었습니다.
A안. 계통을 최상위 폴더로
계통A/원본/ 계통A/정리/ 계통A/종합/
계통B/원본/ 계통B/정리/ 계통B/종합/물리적으로 섞일 수가 없다는 게 장점입니다. 대신 같은 개념 문서가 계통 수만큼 흩어져서, "일주에 대해 우리가 아는 것 전부"를 한 번에 볼 수 없게 됩니다.
B안. 층을 최상위로, 계통은 문서 필드로
20_정리/일주/을축일주.md (front-matter에 gyetong: A)개념 축으로 모이지만, 파일시스템이 혼입을 막아주지 못합니다.
결론은 하이브리드였습니다. 10_원본/만 계통별로 물리 분리하고, 그 위 층은 개념 폴더 + 계통 필드로 두되 분리는 검증기가 강제합니다.
이유는 되돌릴 수 있느냐였습니다. 원본 혼입은 RAG 인덱싱에 바로 들어가서 되돌리기가 가장 어렵습니다. 반면 정리 문서의 계통 오기는 필드 하나 고치면 됩니다. 위험이 큰 곳만 폴더로 막고, 나머지는 코드로 막는 쪽을 골랐습니다.
3-3. 폴더 구조
hanbadook-wiki/
├── 00_규약/ # 이 위키의 헌법. 수정에 승인 필요
│ ├── AGENTS.md # 에이전트 지침 (전문은 3-4에)
│ ├── 문서서식.md # front-matter 필수 필드
│ ├── 계통정의.md # 3대 계통 판별 신호 (아직 미확정)
│ ├── ingest절차.md # 6단계 + 분모·분자 보고
│ └── check_wiki.py # 서식 검증기
├── 10_원본/ # 계통별 물리 분리
│ ├── 계통A/slides/
│ ├── 계통B/slides/
│ └── 계통C/slides/
├── 20_정리/ # 개념 1 : 문서 1
│ ├── 일주/ 격국/ 용신/ 통변/
├── 30_종합/
│ ├── 대비/ # 계통 복수값이 허용되는 유일한 폴더
│ ├── 논점/ 사례/
├── 40_질의/ # 질문 → 근거 → 답 → 환류
│ ├── open/ closed/
├── 50_영수증/
│ ├── ingest/ 실행/ ASK/
└── 90_보류/ # 멈춘 것들이 사라지지 않게
├── 회수실패/ 계통상충/90_보류/를 따로 둔 것은 앞선 8편에서 정한 fail-closed 원칙 때문입니다. 회수하지 못한 것, 계통을 가르지 못한 것이 조용히 사라지지 않고 대기열에 남아야 다음에 채울 수 있습니다.
3-4. 에이전트 지침 전문 (00_규약/AGENTS.md)
markdown
# 한바둑 LLM-Wiki 에이전트 지침 v1.0
너는 한바둑 명리 지식 위키를 읽고 갱신하는 작업 에이전트다.
아래는 부탁이 아니라 판정 기준이다. 어길 수 없는 것은 check_wiki.py가 막는다.
## 1. 계통 분리 — 가장 먼저 지킬 것
- 이 위키는 3대 계통의 서술을 따로 관리한다. 계통이 다르면 같은 용어라도 다른 문서다.
- 문서를 만들거나 고치기 전에 gyetong 필드를 먼저 정한다. 정하지 못하면 만들지 않는다.
- 계통을 판별할 수 없으면 추측하지 말고 멈추고 사람에게 묻는다.
- 계통 판별 근거는 00_규약/계통정의.md의 판별 신호만 쓴다. 문체나 인상으로 정하지 않는다.
- 여러 계통을 한 문서에 담는 것은 30_종합/대비/에서만 허용되며, 이때도 계통별로 절을 나눈다.
## 2. 근거 — 없으면 쓰지 않는다
- 모든 주장에는 slide ID가 붙어야 한다. ID 없는 문장은 위키에 넣지 않는다.
- 원문을 옮기지 않는다. 인용은 한 문장 이내로 하고 나머지는 ID로 가리킨다.
- 근거를 찾지 못하면 status: 확인못함으로 두고 본문을 비운다.
빈칸을 그럴듯한 설명으로 메우는 것이 이 위키에서 가장 큰 사고다.
## 3. 멈추는 조건 — 다음 중 하나라도 해당하면 작업을 중단하고 보고한다
- 계통을 판별할 수 없다
- 근거 slide를 회수하지 못했다
- 기존 문서와 내용이 충돌한다
- 요청이 문서 10건 이상을 한 번에 바꾸라고 한다
- 되돌릴 수 없는 조작(파일 삭제, 대량 이동)이 필요하다
멈출 때는 50_영수증/ASK/에 사유 코드와 함께 한 건을 남긴다.
## 4. 권한 경계 — 되돌릴 수 있는가
| 작업 | 권한 |
| --- | --- |
| 문서 읽기, 검색 | 자유 |
| 새 문서 만들기 | 자유 (검증기 통과 시) |
| 기존 문서 수정 | 자유 (version 올리고 이전 값 영수증에 기록) |
| 문서 삭제, 대량 이동, 폴더 구조 변경 | 승인 필요 |
| 00_규약/ 수정 | 승인 필요 |
기준은 대가의 크기가 아니라 복원 가능성이다. 되돌릴 수 없으면 승인을 받는다.
## 5. 보고 형식 — 분모와 분자를 함께
작업을 마치면 다음 형식으로만 보고한다. 넣은 건수만 세지 않는다.
대상 N건 · 적재 n건 · 제외 m건
제외 사유: [코드] 건수 ...
보류: 회수실패 x건 / 계통상충 y건
## 6. 하지 않는 것
- 검증기를 우회하거나 규약 파일을 고쳐서 통과시키지 않는다
- 성능이나 편의를 이유로 계통 필터를 풀지 않는다
- 사람이 내린 계통 선택을 재검색 결과로 덮어쓰지 않는다3-5. 문서 서식
yaml
---
doc_id: J-0007 # 층 접두 + 일련번호
title: 을축일주
layer: 정리 # 원본 | 정리 | 종합 | 질의
gyetong: A # 단일값. 30_종합/대비/ 에서만 배열 허용
concept: 일주/을축 # 개념 1 : 문서 1
evidence: # 근거 slide ID. 비면 status는 확인못함
- 6082
- 6091
status: 확인함 # 확인함 | 확인못함 | 확인불가
verified_by: 하민
verified_at: 2026-08-06
version: v1.0
---status를 세 값으로 나눈 것은 앞선 8편에서 얻은 것입니다. 확인함·확인못함·확인불가를 한 단어로 뭉뚱그려 쓰는 동안에는 구멍이 보이지 않았습니다.
3-6. 검증기 전문 (00_규약/check_wiki.py)
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""한바둑 LLM-Wiki 문서 서식 검증기 v1.0
규약(00_규약/문서서식.md)을 코드로 판정한다.
통과 · 차단 두 값만 두고, 차단에는 반드시 사유 코드를 남긴다.
"""
import sys, pathlib, re
REQUIRED = ["doc_id", "title", "layer", "gyetong", "concept",
"evidence", "status", "verified_by", "verified_at", "version"]
LAYERS = {"원본", "정리", "종합", "질의"}
STATUS = {"확인함", "확인못함", "확인불가"}
MULTI_OK = "30_종합/대비" # 계통 배열이 허용되는 유일한 경로
EXCLUDE = ("00_규약", "50_영수증", "90_보류")
def parse_front_matter(text):
m = re.match(r"^---\n(.*?)\n---\n", text, re.S)
if not m:
return None
fm, key = {}, None
for line in m.group(1).split("\n"):
if re.match(r"^\s*-\s+", line) and key:
fm.setdefault(key, [])
if not isinstance(fm[key], list):
fm[key] = []
fm[key].append(line.split("-", 1)[1].strip())
elif ":" in line:
key, val = line.split(":", 1)
key, val = key.strip(), val.strip()
fm[key] = val if val else []
return fm
def check(path, root):
"""→ list of (code, message). 빈 리스트면 통과."""
errs = []
fm = parse_front_matter(path.read_text(encoding="utf-8"))
if fm is None:
return [("FM-000", "front-matter 없음")]
for k in REQUIRED:
if k not in fm:
errs.append(("FM-001", f"필수 필드 누락: {k}"))
if errs:
return errs
if fm["layer"] not in LAYERS:
errs.append(("FM-002", f"layer 값 오류: {fm['layer']}"))
if fm["status"] not in STATUS:
errs.append(("FM-003", f"status 값 오류: {fm['status']}"))
# 계통 단일값 규칙
rel = str(path.relative_to(root))
multi = isinstance(fm["gyetong"], list) or "," in str(fm["gyetong"])
if multi and not rel.startswith(MULTI_OK):
errs.append(("GT-001", f"계통 복수값은 {MULTI_OK}/ 에서만 허용"))
if not multi and rel.startswith(MULTI_OK):
errs.append(("GT-002", "대비 문서인데 계통이 하나뿐"))
# 근거와 상태의 정합
ev = fm["evidence"] if isinstance(fm["evidence"], list) else []
if not ev and fm["status"] == "확인함":
errs.append(("EV-001", "근거 없이 확인함으로 표시됨"))
if ev and fm["status"] == "확인못함":
errs.append(("EV-002", "근거가 있는데 확인못함으로 표시됨"))
# 개념 1 : 문서 1
if fm["layer"] == "정리" and "," in str(fm["concept"]):
errs.append(("CP-001", "한 문서에 개념이 둘 이상"))
return errs
def main(root):
root = pathlib.Path(root)
docs = sorted(p for p in root.rglob("*.md")
if not any(x in str(p) for x in EXCLUDE))
passed, blocked = 0, []
for p in docs:
errs = check(p, root)
if errs:
blocked.append((p.relative_to(root), errs))
else:
passed += 1
print(f"검사 {len(docs)}건 · 통과 {passed} · 차단 {len(blocked)}")
for rel, errs in blocked:
print(f"\n ✗ {rel}")
for code, msg in errs:
print(f" [{code}] {msg}")
return 1 if blocked else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "."))3-7. 첫 실행 — 샘플 6건으로 검증기 확인
일부러 통과할 문서 3건과 막혀야 할 문서 3건을 만들어 돌렸습니다.
$ python3 00_규약/check_wiki.py .
검사 6건 · 통과 3 · 차단 3
✗ 20_정리/격국/정관격.md
[GT-001] 계통 복수값은 30_종합/대비/ 에서만 허용
✗ 20_정리/일주/갑자일주.md
[FM-001] 필수 필드 누락: concept
[FM-001] 필수 필드 누락: evidence
[FM-001] 필수 필드 누락: status
[FM-001] 필수 필드 누락: verified_by
[FM-001] 필수 필드 누락: verified_at
[FM-001] 필수 필드 누락: version
✗ 20_정리/통변/육친론.md
[EV-001] 근거 없이 확인함으로 표시됨세 건 모두 의도한 사유 코드로 막혔습니다. 특히 EV-001이 제가 가장 원했던 것입니다 — 근거 없이 "확인함"으로 표시한 문서를 사람이 아니라 코드가 잡아냅니다.
결과와 배운 점
4-1. 시행착오 — 검사 범위를 잘못 잡았다
첫 실행에서 예상하지 못한 것이 하나 걸렸습니다.
✗ 50_영수증/ingest/2026-08-06_샘플.md
[FM-000] front-matter 없음영수증 파일이 문서로 검사된 겁니다. 영수증은 위키 문서가 아니라 실행 기록인데, .md 확장자만 보고 전부 검사 대상에 넣었습니다.
작은 실수지만 배운 게 있습니다. 검증기를 만들 때 "무엇을 막을지"보다 "무엇을 검사 대상으로 볼지"를 먼저 정해야 합니다. 여기서 제가 처음 든 유혹은 영수증에도 front-matter를 붙이자는 것이었습니다. 그러면 검증기는 통과하지만, 실행 기록이 위키 문서 흉내를 내게 됩니다. 고쳐야 할 것은 문서가 아니라 검사 범위였습니다.
EXCLUDE = ("00_규약", "50_영수증", "90_보류") 한 줄로 해결했습니다.
4-2. 계통을 폴더로 올리지 않은 것이 이번의 핵심 판단
처음엔 계통을 최상위 폴더로 두려 했습니다. 물리적으로 못 섞이니 안전해 보였거든요.
그런데 그렇게 하면 "일주에 대해 우리가 아는 것 전부"를 한 번에 볼 수 없습니다. 위키의 목적 자체가 흩어진 것을 모으는 건데, 계통을 폴더로 올리면 개념이 계통 수만큼 흩어집니다.
그래서 위험이 가장 큰 원본 층만 폴더로 막고, 나머지는 검증기로 막았습니다. 기준은 되돌릴 수 있는가였습니다. 이 기준은 참고한 책에서 가져온 건데, 앞선 8편에서 제가 쓰던 "틀렸을 때의 대가"보다 판정하기가 훨씬 쉽습니다. "이게 Full인가 Max인가"는 다투게 되지만 "되돌릴 수 있나"는 대개 답이 하나입니다.
4-3. 분모·분자 보고 — 이번에 가장 실용적인 수확
책에서 배치 ingest를 다루면서 제외 검사와 함께 분모·분자를 보고하라는 대목이 있습니다. 읽고 나서 제 기존 절차를 다시 봤더니, 슬라이드 대역을 SELECT COUNT(*)로 실측한다고 만 해두고 넣은 건수만 세고 있었습니다.
대상 20건 · 적재 17건 · 제외 3건
제외 사유: [EX-표지] 2 · [EX-중복] 1
보류: 회수실패 0건 / 계통상충 0건넣은 건수만 세면 성과 보고가 되고, 제외 건수를 함께 세야 현황 보고가 됩니다. 한 줄 차이인데 의미가 완전히 다릅니다.
4-4. 가장 서늘했던 문장
책의 마지막 절에 이런 경고가 있습니다. 위키가 잘 만들어져 있을수록 읽는 사람은 그것을 더 쉽게 믿고, 그래서 잘못된 정리 하나가 더 멀리 퍼진다는 겁니다.
이게 제일 무섭습니다. 명리는 검증이 어려운 분야입니다. 정리된 문서가 늘어날수록 그 안의 문장이 검증을 거친 것처럼 보이는데, 사실 검증한 사람은 아무도 없을 수 있습니다.
그래서 status 필드에 확인불가를 남겨뒀습니다. 확인 못 한 것과 확인이 불가능한 것은 다릅니다. 앞의 것은 다음에 채우면 되고, 뒤의 것은 영원히 채워지지 않을 수 있다는 표시입니다.
4-5. 도움이 필요한 부분
계통 판별 신호를 어떻게 확정할지가 아직 막혀 있습니다. 00_규약/계통정의.md를 열어보면 표가 비어 있습니다.
markdown
| 계통 | 임시 표기 | 판별 신호 (작성 예정) |
| --- | --- | --- |
| 계통A | A | 고유 용어 목록 · source 범위 |일부러 비워뒀습니다. 채울 근거가 아직 없어서요. 문제는 명리 용어가 음가가 가까운 쌍이 둘 다 실재하는 경우가 많고, 계통 고유 용어라고 생각한 것이 실은 공용어인 경우가 있다는 점입니다. 소리로는 좁혀져도 어느 계통인지는 그 대목의 논지를 알아야 갈립니다.
비슷한 문제(용어가 계통/학파별로 갈리는 지식을 위키로 관리)를 겪으신 분이 계시면 어떻게 판별 기준을 세우셨는지 듣고 싶습니다.
< 앞으로의 계획 >
1단계 (다음 주) — 실제 자료로 첫 ingest 1회 강의 하나에서 슬라이드 20장만 골라 절차대로 넣어봅니다. 확인할 것은 셋입니다.
양성:
대상 20 · 적재 n · 제외 m · 사유별 내역이 한 줄로 보고되는가음성: 서로 다른 계통의 정리가 한 문서에 접히지 않았는가
판정: 임의의 주장 1건에서 slide ID까지 거슬러 올라갈 수 있는가
2단계 — N2 라우팅이 위키를 읽게 한다 지금 위키는 사람만 읽습니다. 이게 실제로 값을 하려면 계통 라우팅 노드(N2)가 판단할 때 위키 문서를 근거로 들어야 합니다. 위키 없이 답할 때와 비교해 근거 ID가 실제로 늘었는지가 판정 기준입니다.
3단계 — 계통정의.md 채우기 1·2단계에서 쌓인 실제 사례로 판별 신호를 역산합니다. 지금 추측으로 채우면, 그 추측이 이후의 모든 라우팅에 들어갑니다.
당분간 하지 않을 것도 적어둡니다. 그래프 시각화, 외부 서비스 연동, 자동 요약 — 전부 지금은 이릅니다. 문서 다섯 장이 실제로 라우팅에 참조되기 전에 시각화를 붙이면, 앞선 6편에서 경계했 던 "측정 없는 차단"과 같은 종류의 착시가 생깁니다. 잘 그려진 그래프가 검증된 지식처럼 보이니까요.
도움 받은 글 (옵션)
안준용(고려대), 『LLM-Wiki: 지식이 축적되는 AI 작업 환경 만들기』 — https://chaek.org/books/llm-wiki-for-scientists/README
특히 「들어가며」의 쌓이는 것과 축적되는 것의 구분, 6장의 분모·분자 보고, 13장의 되돌릴 수 있는가라는 권한 기준, 그리고 마지막 절의 "잘 정리된 위키일수록 잘못된 정리가 더 멀리 퍼진다"는 경고에서 큰 도움을 받았습니다.
한바둑 헤르메스 사례글 5편 — Proof Artifact & Code as Gate (규칙은 부탁이 아니라 게이트)
한바둑 헤르메스 사례글 8편 — 승인 지점과 실패 수정 규칙 (fail-closed, 판정 어휘 분리)