본문 바로가기
Programing & OS

dokuwiki에서 obsidian까지 LLM wiki 만들기 (2) Feat Claude

by 자루스 2026. 4. 11.

 

Andrej Karpathy가 2026년 4월, LLM으로 개인 지식 베이스를 구축하는 방법을 공유했다. 논문과 아티클을 raw/에 쌓으면 LLM이 알아서 위키로 컴파일한다는 아이디어였다. 나는 그 아이디어를 Claude Code와 Python으로 직접 구현해봤다.

왜 만들었나

Karpathy의 워크플로우는 간단하다. 원본 자료를 raw/ 디렉토리에 넣으면 LLM이 읽고 마크다운 위키로 "컴파일"한다. RAG 없이, 벡터 DB 없이, 인덱스 파일과 문서 요약만으로 충분하다고 했다.

그런데 그가 "스크립트를 엮어 만든 임시방편적인 솔루션"이라 부른 그 파이프라인을 실제로 구현하면 어떻게 될까. 나는 Castle이라는 이름으로 직접 만들어봤다.


시스템 구조

castle/
├── CLAUDE.md          # Claude Code 운영 지침 (5가지 작동 모드)
├── raw/               # 임시메모 — 사람이 수집한 원본
│   └── .processed   # 처리 완료 목록 (incremental build)
├── wiki/              # 영구메모 — LLM이 컴파일한 결과
│   ├── _index.md    # 마스터 인덱스 (토큰 절감 핵심)
│   ├── _log.md      # 컴파일 로그
│   └── [클러스터]/  # 개념별 서브디렉토리
├── archive/          # 처리된 raw 보관 (삭제 금지)
├── output/           # Q&A 결과물, 린트 리포트
└── tools/
    ├── compile.py    # raw/ → wiki/ 컴파일
    ├── search.py     # 인덱스 기반 검색엔진
    ├── lint.py       # 상태 점검
    └── doku2obsidian.py # DokuWiki 자동 변환

핵심 원칙은 세 가지다.

1
사람은 raw/에 수집만 한다. 정리, 연결, 요약은 전부 LLM이 한다. 제텔카스텐으로 치면 임시메모(Fleeting Notes)를 넣는 단계만 사람이 담당한다.
2
프론트매터로 토큰을 1/10로 줄인다. 각 영구메모에 summary 필드를 캐싱해두면 LLM이 전문을 읽지 않고 _index.md만 훑어도 전체 맥락을 파악할 수 있다.
3
archive로 보내되 삭제하지 않는다. Karpathy는 raw/를 영구 보존하고, 루만은 임시메모를 폐기했다. Castle은 중간을 택했다. Archive 플래그로 활성 탐색 공간에서만 제외한다.

만든 도구들

① compile.py — 증분 컴파일러

raw/에 새 파일이 들어오면 .processed 목록과 대조해 미처리 파일만 골라낸다. 소스코드 빌드의 incremental build와 같은 구조다. LLM이 delta만 처리하므로 위키가 커져도 매번 전체를 재처리하지 않는다.

python tools/compile.py              # 미처리 파일 전체 처리
python tools/compile.py --dry-run    # 처리 대상만 미리보기
python tools/compile.py --file raw/파일.md  # 특정 파일만

컴파일 결과 각 영구메모에는 아래 프론트매터가 자동으로 붙는다.

---
zettel: 0001a1
title: "제목"
summary: "한 줄 요약 — _index.md에 캐싱"
tags: [llm, zettelkasten]
links: []
backlinks: []
created: 2026-04-10
source: "raw/원본파일.md"
---
zettel 코드 설계: NNNN[a-z][0-9] 형식 (예: 0001a1, 0001b1). 순서번호(4자리) + 가지(알파벳) + 파생(숫자). 새 독립 개념은 새 번호, 기존 개념의 파생은 같은 번호에 가지를 늘린다. 루만의 제텔카스텐 인덱싱 방식을 그대로 따랐다.

② search.py — 2단계 검색엔진

질문이 들어오면 먼저 _index.md의 요약과 태그로 1차 필터링한다. 그다음 필터링된 파일만 전문을 읽는다. LLM에 위키 전체를 던지는 대신, 관련 가능성이 있는 파일만 컨텍스트에 넣는 구조다.

python tools/search.py --query "RAG 인덱스"         # 인덱스 기반 검색
python tools/search.py --query "드래곤" --full       # 전문 검색 (폴백 포함)
python tools/search.py --tag "novel"                 # 태그 필터링
python tools/search.py --list                        # 전체 노트 목록

인덱스에서 매칭이 없으면 자동으로 전문 검색(full scan)으로 폴백한다. "RAG 없이 충분하다"가 아니라 "인덱스가 RAG를 경량으로 대체한다"는 것이 정확한 표현이다.

③ lint.py — 자가 치유 점검

위키가 커질수록 조용히 망가지는 것들이 생긴다. lint.py는 주기적으로 이를 잡아낸다.

점검 항목 설명
고립 노트 어디서도 링크되지 않는 노트
끊어진 링크 [[링크]]가 실제 파일로 연결되지 않음
중복 zettel 같은 코드가 두 개 이상 존재
summary 누락 프론트매터에 요약 없는 노트
archive 미완료 .processed에 있지만 archive/로 이동 안 된 파일
python tools/lint.py           # 전체 점검
python tools/lint.py --fix     # 자동 수정 가능한 항목 처리
python tools/lint.py --report  # output/에 리포트 저장

④ doku2obsidian.py — 포맷 자동 감지·변환

raw/에 DokuWiki로 작성된 문서를 넣으면 compile.py가 자동으로 감지해 Obsidian 마크다운으로 변환한 뒤 컴파일한다. 별도로 신경 쓸 필요가 없다.

감지 기준은 7가지 패턴의 점수 합산이다. 3점 이상이면 DokuWiki로 판정한다.

점수 패턴 예시
3점 헤딩 ====== 제목 ======
3점 테이블 헤더 ^ 항목 ^ 내용 ^
3점 이미지 {{ :path:image.png?400|}}
2점 코드블록 <code>...</code>
2점 고정폭 ''텍스트''
1점 이탤릭 //텍스트//
1점 들여쓰기 목록   * 항목

<code> 블록은 내용에 따라 다르게 변환된다. 대화문이면 > [!quote] 콜아웃, 아스키 다이어그램이면 코드블록, 짧은 개념어면 인용 블록으로.

python tools/doku2obsidian.py 파일.md --check   # 감지만 (점수 출력)
python tools/doku2obsidian.py --scan            # raw/ 전체 스캔
python tools/doku2obsidian.py --scan --convert  # 스캔 + 일괄 변환

실제 사용 흐름

1
아티클, 논문, 소설 캐릭터 설명 등 원본 파일을 raw/에 넣는다. DokuWiki든 일반 마크다운이든 상관없다.
2
python tools/compile.py 실행. DokuWiki 파일은 자동 변환 후 컴파일. 클러스터(폴더)도 키워드 기반으로 자동 분류된다. 소설 캐릭터는 novel/characters/, LLM 관련 노트는 ai-llm/으로.
3
python tools/search.py --query "키워드"로 검색. 또는 Claude Code를 열고 /질문 궁금한 것을 입력하면 CLAUDE.md의 지침에 따라 _index.md부터 읽고 관련 노트를 찾아 답한다.
4
주기적으로 python tools/lint.py로 상태 점검. 고립된 노트, 끊어진 링크를 잡아내고 output/에 리포트를 저장한다.

Karpathy 방식과 뭐가 다른가

  Karpathy 원본 Castle
raw 보관 영구 보존 archive/ 이동 (삭제 금지)
인덱싱 중앙집중식 index.md 각 노트 프론트매터 분산 캐싱
클러스터 수동 디렉토리 설계 키워드 기반 자동 분류
포맷 지원 마크다운 전용 DokuWiki 자동 감지·변환 포함
상태 점검 수동 또는 LLM 요청 lint.py 자동화
토큰 전략 인덱스 전체 독해 summary 필드로 1차 필터링

Karpathy 본인이 "스크립트를 엮어 만든 임시방편적인 솔루션"이라 인정했다. Castle은 그 임시방편을 조금 더 체계적으로 만든 것에 가깝다. 핵심 아이디어는 그의 것이다.

한계: 이 시스템도 문서가 수천 개를 넘으면 인덱스 파일 자체가 비대해진다. Karpathy가 지적한 스케일 문제는 Castle도 비켜가지 못한다. 그 시점이 되면 벡터 검색을 붙여야 한다. Notion AI가 내부적으로 벡터 검색을 쓰는 이유가 바로 이것이다.

CLAUDE.md — Claude Code를 IDE로 쓰는 방법

Castle 디렉토리에 CLAUDE.md를 두면 Claude Code가 매번 자동으로 읽는다. 여기에 5가지 작동 모드를 정의했다.

모드 명령 동작
컴파일 /컴파일 raw/ delta 처리 → wiki/ 영구메모 생성
탐색 /탐색 [노트제목] 노트 분석 + wiki/ 연결 후보 5개 제안
Q&A /질문 [질문] _index.md 기반 질의응답 → output/ 저장
린트 /린트 불일치·고립·누락 탐지 및 수정
아카이브 /아카이브 처리 완료 raw 파일 archive/로 이동

Claude Code가 이 지침을 읽으면 "사람은 수집만, 나머지는 내가 한다"는 원칙대로 작동한다. Karpathy가 말한 LLM 주도 위키 관리가 실제로 돌아가는 방식이다.