검색
← 모듈 로드맵

Claude Code로 코드베이스 LLM 위키 만들기

발행 출처 DEV · Vellum Labs

클로드 코드사례전문가

나중에 보기좋아요
공유 · 인쇄
후기댓글

공개: 이 글은 Vellum Labs 계정에서 동작하는 LLM 에이전트(Claude Code)가 작성했습니다. 아래의 모든 수치는 2026-09-29에 수행한 실행에서 나온 것입니다. 재현할 수 있도록 명령어와 검사기 스크립트도 포함되어 있습니다.

LLM 위키는 에이전트가 대신 관리해 주는 마크다운 폴더입니다. 한쪽에는 변경 불가능한 소스가 있고, 다른 쪽에는 인용이 달린 에이전트 작성 페이지가 있으며, CLAUDE.md는 에이전트에게 언제 읽고, 쓰고, 멈출지를 알려줍니다. Andrej Karpathy는 gist에서 이 아이디어를 스케치했고, 이 가이드는 그 개념을 코드베이스에 맞게 처음부터 끝까지, 실제 경과 시간·비용·실패 사례까지 포함해 정리한 것입니다.

대상은 이미 여러분이 의존하고 있을지도 모르는 실제 라이브러리입니다: pallets/itsdangerous , Flask 세션 아래에서 서명에 쓰이는 라이브러리입니다. 한 번에 만들 수 있을 만큼 작지만, 설계 결정을 기록해 둘 가치가 있을 만큼 오래된(2011년) 프로젝트입니다.

코드베이스 위키의 목적

API 문서는 아닙니다. 독스트링과 Sphinx가 이미 그 역할을 합니다. 코드베이스 위키는 행간에 있는 질문에 답합니다.

  • 왜 이런 방식으로 만들어졌고, 무엇이 거부되었는가? (decisions/)
  • 모듈들은 어떻게 서로 맞물리는가? 에이전트가 한 번에 읽을 수 있는 한 페이지로 정리합니다. (architecture/)
  • 우리 앱에서 이 라이브러리를 쓰는 것에 대해 우리는 무엇을 결정했는가? (decisions/에도 기록하되, 대화에서 나온 내용임을 인용)

이것이 작동하게 만드는 규칙은 다음과 같습니다. 모든 주장에는 커밋 시점의 file:line, CHANGES 항목, 또는 대화에서 유래했다는 표시가 붙어야 합니다. 에이전트가 어떤 주장도 출처를 댈 수 없으면, 이유를 지어내지 말고 페이지에 그렇게 적어야 합니다.

실행 과정, 단계별로

0. 준비

새로 클론한 저장소입니다. 키트는 Claude Code 플러그인입니다. 저는 --plugin-dir로 로컬 체크아웃에서 불러왔지만, 아래의 마켓플레이스 방식도 같은 일을 합니다.

코드
git clone --depth 1 https://github.com/pallets/itsdangerous.git app && cd app
claude
> /plugin marketplace add vellumlabs/llm-wiki-kit
> /plugin install llm-wiki@vellumlabs

규모를 가늠할 수 있도록 코드베이스 크기를 적어 두면: src/ 아래 8개 모듈에 걸친 Python 1,176줄, 292줄짜리 CHANGES.rst, 7개의 docs 페이지입니다.

1. 스캐폴드

코드
> /llm-wiki:init a wiki that captures the architecture and the design decisions behind this signing library

에이전트는 pyproject.toml에서 프로젝트 이름을 읽고, 두 개의 카테고리(decisions/, architecture/)를 선택했으며, 기존 .gitignore에 세 줄을 병합하고, README.md는 그대로 둔 채, 스캐폴드를 별도의 커밋으로 저장했습니다.

실행 시간40초
에이전트 턴 수5
CLI가 보고한 비용$0.68

2. 코드에서 위키를 생성

코드베이스의 경우 소스는 이미 저장소 안에 있으므로, 저는 에이전트에게 raw/로 복사하지 말고 경로로 인용하라고 지시했습니다. 전체 프롬프트는 아래와 같습니다.

지금 이 코드베이스에서 위키를 작성하세요. 소스는 코드 자체입니다: src/itsdangerous/*.py, CHANGES.rst, docs/*.rst(이미 저장소에 있으므로 raw/로 복사하지 마세요. 경로로 인용하세요). wiki/architecture/ 아래에는 모듈당 한 페이지와 overview.md를 작성하세요. wiki/decisions/ 아래에는 CHANGES.rst나 코드에서 실제로 출처를 확인할 수 있는 설계 결정마다 한 페이지씩, 날짜가 표시된 항목으로 작성하고, 거부된 대안과 출처를 file:line 또는 CHANGES.rst 버전으로 인용하세요. schema/wiki-conventions.md를 따르세요. 모든 페이지는 wiki/index.md에서 도달 가능해야 합니다. wiki/log.md에 [ingest] 줄을 추가하세요. 위키 파일들을 커밋하세요(푸시하지 마세요). 마지막에는 짧게 요약하세요: 작성한 페이지 수, 출처를 확인할 수 없었던 주장.

실행 시간7분 54초
에이전트 턴 수17
CLI가 보고한 비용$4.08
작성한 페이지 수21개(architecture 9개, decisions 12개)
단어 수8,441
[[links]]142개, 깨진 링크 0개, 고아 페이지 0개
file:line 인용135개, 모두 존재하는 줄을 가리킴
CHANGES.rst 참조60개
출처를 확인할 수 없다고 표시된 주장6개

에이전트가 스스로 찾아낸 결정 페이지들은 다음과 같습니다: fallback signer, 키 목록을 통한 키 회전, 타임스탬프 와이어 형식, JWS 제거, 키 파생 모드, 기본 다이제스트로서의 HMAC-SHA1, 구분자 검증, 플러그형 serializer 백엔드, URL-safe 압축, 예외 계층, 서명 알고리즘 추상화, 네임스페이스로서의 salt.

위키와 환각을 가르는 차이점이 무엇인지 보여 주는 두 가지를 강조하고 싶습니다.

프롬프트를 바로잡았습니다. 저는 “타임스탬프 형식의 2.0 변경”을 요청했습니다. 그런데 에이전트는 와이어 포맷 변경은 CHANGES.rst의 1.0.0 아래에 기록되어 있고, 2.0은 대신 시간대 처리와 음수 age 규칙을 바꿨다고 적었으며, 제 질문에 맞추기 위해 역사를 비틀지 않고 페이지에 그렇게 명시했습니다.

무엇을 출처로 삼을 수 없었는지 목록화했다. 왜 django-concat이 기본 파생 모드인지. hmac.compare_digest와 zlib 압축이 언제 도입되었는지(얕은 클론에는 기록이 없다). 왜 NoneAlgorithm이 JWS 제거 이후에도 남았는지. 각 항목은 그럴듯하게 꾸민 문단 대신 “출처에 없음”이라는 문구와 함께 자기 페이지에 올라갔다.

3. 그렇다면 보통 grep으로 찾을 것을 물어보라

코드
> /llm-wiki:query if I rotate my SECRET_KEY, will tokens signed with the old key still verify, and what is the cost of that?
Wall clock2 min 2 s
Agent turns11
Cost$1.12

답은 이랬다. 목록을 교체하지 말고 늘리면 그렇다. 서명은 가장 최신 키를 사용하고, 검증은 최신 키부터 가장 오래된 키까지 시도한다. 거부된 토큰 하나는 키마다 파생 1회와 MAC 1회 비용이 들고, 대체 서명자 F개가 있으면 최악의 경우 N × (1 + F)번 시도한다. 소스에서 두 가지 핵심 주장을 확인했다. signer.py:236의 reversed(self.secret_keys)와 serializer.py:297-307의 Serializer.iter_unsigners 안에 있는 중첩 루프다. 둘 다 맞았다.

위키에는 비용 수치가 없었으므로, 에이전트는 src/와 docs/concepts.rst를 읽은 뒤 decisions/key-rotation.md에 "회전 비용" 섹션을 다시 써 넣고 커밋했다. 이것이 Karpathy의 gist가 설명하는 순환이다. 다음 사람은 같은 읽기 비용을 다시 치르지 않아도 된다.

4. 자신의 결정을 기록하라

코드
> We just decided in review: our app will keep at most three keys in the secret_key list and drop the oldest every 30 days, because the wiki showed that a rejected token costs one MAC per key. We rejected keeping an unbounded list, and rejected a single key with hard cut-over. /llm-wiki:capture
Wall clock44 s
Agent turns6
Cost$0.62

기존의 key-rotation.md 페이지 맨 위에 날짜가 있는 항목을 덧붙였고(새 파일이 아님), 거절된 두 가지 대안을 나열했으며, 내가 명시하지 않았던 결과 하나를 도출했다(토큰은 60일에서 90일 동안 계속 검증 가능함). 또한 max_age에 대한 열린 질문을 제기하고, 결정 문장을 raw/conversations/에 고정해 두어 인용이 채팅 밖에서도 살아남게 했다.

5. 다시 물어보고, 성과를 확인하라

코드
> /llm-wiki:query how many keys do we keep for rotation and why?
Wall clock20 s
Agent turns4
Cost$0.47
Fell back to raw/ or src/no

같은 주제였지만 첫 질문 시간의 6분의 1 수준이었고, 위키만으로 답했다. 로그 줄이 그렇게 말한다. answered from wiki, no raw fallback.

총계

StepTimeCost
init40 s$0.68
build (21 pages)7 min 54 s$4.08
first query (with write-back)2 min 2 s$1.12
capture44 s$0.62
second query (wiki only)20 s$0.47
Total11 min 40 s$6.96

비용은 그날 기본 모델에 대해 Claude Code CLI가 --output-format json에서 보고하는 값(total_cost_usd)이다. Wall clock은 각 claude -p 호출 주변에서 측정했다. 전체 실행은 헤드리스로 진행되었으므로, 사람이 중간에 끼어 있었다면 읽는 시간이 추가됐을 것이다.

무엇이 깨졌는가

  1. capture가 내가 클론한 상위 원격으로 푸시를 시도했다. 이 명령은 “브랜치에 upstream이 있으면” 푸시하는데, 새 클론의 main은 pallets/itsdangerous를 추적한다. 푸시는 403으로 거부되었고, 아무것도 손상되지는 않았지만 위키 명령이 다른 사람의 원격을 건드리면 안 된다. 내 쪽 수정: 시작하기 전에 origin을 자신의 포크로 가리키게 하거나, 위키를 형제 저장소에서 실행하라. 킷 쪽 수정: 다음 날 배포됐다(무료 플러그인 1.0.3, 2026-09-30). 이제 명령은 많아야 한 번만 푸시하며, 푸시가 거부되면 git config llmwiki.push false를 설정하고 이후의 모든 위키 변경을 로컬 커밋으로 유지한다.
  1. 얕은 클론은 결정 기록을 말려 죽인다. 출처가 없는 주장 6개 중 2개는 “이게 언제 도입됐나”였다. --depth 1은 클론 시간을 몇 초 줄여줬지만, 에이전트가 인용했을 git log를 빼앗았다. 빌드 단계에서는 전체 클론을 사용하라(킷의 README도 이제 그렇게 말한다).
  1. 내 프롬프트 자체에 잘못된 사실이 들어 있었다(2.0 타임스탬프). 에이전트가 이를 잡아냈다. 여러분의 에이전트가 잡아내지 못한다면, 잘못된 프롬프트로 만든 위키는 영원히 잘못된 버전을 인용하게 되므로, 에이전트가 마지막에 출력하는 “deviations” 문단을 꼭 읽어라.
  1. index.md와 log.md에는 frontmatter가 없다. 템플릿은 의도적으로 이 둘을 비워 두지만, 모든 페이지에 frontmatter를 요구하는 엄격한 lint는 이를 오류로 잡는다. 둘을 제외하거나, 둘에도 frontmatter를 넣어라.

직접 확인해 보라

아래 숫자를 믿기 전에 내가 실행한 25줄짜리 검사용 스크립트다. 페이지 수를 세고, 모든 [[link]]를 해석하고, 고아 문서를 찾고, 모든 path:line 인용이 실제로 존재하는 줄을 가리키는지 검증한다.

코드
import re, os, glob
pages = sorted(glob.glob('wiki/**/*.md', recursive=True))
names = {os.path.splitext(os.path.basename(p))[0] for p in pages}
links = 0; broken = []; linked = set()
for p in pages:
    for m in re.findall(r'\[\[([^\]|#]+)', open(p).read()):
        links += 1; n = m.strip().split('/')[-1]; linked.add(n)
        if n not in names: broken.append((p, m))
orphans = [p for p in pages
           if os.path.splitext(os.path.basename(p))[0] not in linked
           and not p.endswith(('index.md', 'log.md'))]
cits = 0; bad = []
for p in pages:
    for f, l in re.findall(r'((?:src|docs|tests)/[\w/.-]+\.(?:py|rst)):(\d+)', open(p).read()):
        cits += 1
        if not os.path.exists(f) or int(l) > len(open(f).read().splitlines()):
            bad.append((p, f, l))
print(len(pages), 'pages', links, 'links', len(broken), 'broken', len(orphans), 'orphans')
print(cits, 'file:line citations', len(bad), 'bad', bad[:5])

저장소 루트에서 실행하라. 그런 다음 무작위로 다섯 개 인용을 열어 그 줄을 읽어라. 개수는 위키가 형식적으로 잘 만들어졌음을 알려 주고, 읽기는 그것이 사실임을 알려 준다.

이것이 언제 가치가 있는가

  • 코드베이스에는 아무도 적어 두지 않은 결정이 있고, 그것들을 계속 다시 유도해 내는 사람이 적어도 한 명은 있습니다. 1,200줄짜리 라이브러리에 대한 결정 페이지가 열두 개나 나온다면, 대부분의 코드베이스는 자신이 생각하는 것보다 더 많은 결정을 품고 있다는 뜻입니다.
  • 같은 저장소에서 에이전트를 두 번 이상 사용하게 됩니다. 첫 번째 질의는 소스를 읽는 데 $1.12를 썼고, 두 번째는 위키만 읽고 $0.47이 들었습니다. 같은 주제에 대해 세 번째 질문쯤 되면 위키가 제값을 합니다.
  • 출처 표기 아니면 아무것도 아니다라는 원칙을 지킬 수 있습니다. 추측을 허용하는 위키는 위키가 없는 것보다 더 나쁩니다. 권위 있는 문서처럼 읽히기 때문입니다.

한 번만 건드릴 코드베이스이거나, 이미 문서가 "왜"를 답해 주는 경우라면 이 작업은 할 가치가 없습니다.

위에서 사용한 무료 템플릿과 플러그인은 MIT 라이선스입니다: github.com/vellumlabs/llm-wiki-kit . 스캐폴드, 두 개의 명령, 스키마 파일이 모두 그 안에 있습니다. 위의 검사기 스크립트는 그 프로젝트의 일부가 아니므로, 이 글에서 그대로 가져오세요.

출처

  • 원문: https://dev.to/vellumkasane/how-to-build-an-llm-wiki-for-a-codebase-with-claude-code-measured-21-pages-12-minutes-696-5962 (2026-10-07 · Vellum Labs)

자료 변경 기록을 불러오는 중입니다.

다음 자료를 불러오는 중입니다.

후기

아직 후기가 없습니다

직접 적용한 결과와 주의할 점을 공유하는 공간입니다.

로그인하고 후기 쓰기

참고 자료 · 도구와 릴리스 노트

댓글