회고 & 컬럼/쿠고랑 개발일지

[Python/AEO] llms.txt와 자동 생성 주제 색인으로 220개 항목 저장소를 AI가 바로 읽는 문서로 바꾸기

kugorang 2026. 11. 14. 19:59

TL;DR (세 줄 요약)

  1. 개발 팁을 모아 둔 GitHub 저장소 kugorang/ai-dev-knowledge에는 글 항목 220개가 월별 파일 3개(합계 163,946바이트)에 나뉘어 저장되어 있습니다. 이 가운데 2026년 11월 파일 하나만 해도 항목 185개, 144,118바이트나 되어서, AI 프로그램이나 검색 시스템이 글 한 개만 찾고 싶어도 큰 파일 전체를 통째로 내려받아 읽어야 했습니다.
  2. 2026-11-14에 전체 파일 목록을 알려 주는 안내 문서 llms.txt(3,718바이트), 도구와 주제별로 글 링크를 모아 놓은 색인 페이지 9개와 전체 목차 1개(합계 168,644바이트), 그리고 이 색인 페이지들을 자동으로 만들어 주는 파이썬 프로그램 scripts/build-index.py(280줄, 파이썬 기본 기능만 사용)를 새로 넣었습니다.
  3. python3 scripts/build-index.py --check 명령어를 실행하면 색인 페이지가 최신 글과 어긋났는지 자동으로 검사합니다. 실제 검사 결과 색인에 들어 있는 링크 473개가 모두 원래 글의 제목 위치와 정확히 연결되었고(깨진 링크 0개), 검사 시간은 5번 재었을 때 가운데 값 기준으로 72.4ms(0.0724초)가 걸렸습니다.


1. 무엇이 문제였는가: 글 185개가 파일 하나에 144,118바이트로 뭉쳐 있었다

kugorang/ai-dev-knowledge는 AI 코딩 도구(Anthropic의 Claude Code, OpenAI의 ChatGPT·Codex, Cursor, xAI의 Grok Bot 등)를 쓰는 실전 팁을 한국어 2~3문장으로 짧게 정리해 모아 둔 공개 GitHub 저장소입니다. 새 글은 매달 하나씩 만드는 월별 마크다운 파일(entries/YYYY-MM.md)에 계속 쌓입니다.

여기서 AEO는 검색 엔진뿐 아니라 ChatGPT나 Claude 같은 AI 대화 프로그램이 질문에 답할 때 내 문서를 정확히 찾아 읽도록 문서 구조를 맞추는 작업을 말합니다. LLM은 글을 읽고 대답을 만들어 내는 대규모 언어 모델(AI)을 뜻합니다. 그리고 바이트(byte)는 컴퓨터가 글자 한 개를 저장할 때 재는 크기 단위입니다(영어 알파벳 1글자는 1바이트, 한글 1글자는 보통 3바이트를 차지합니다).

2026-11-14 19시(KST)에 저장소 파일들을 내 컴퓨터로 내려받아 직접 크기를 재어 보았습니다.

파일 이름 들어 있는 글 수 파일 크기(바이트) 글 제목(##) 수
entries/2026-10.md 7 3,471 7
entries/2026-11.md 185 144,118 185
entries/bookmarks.md 28 16,357 28
합계 220 163,946 220

2026년 11월 파일 하나에만 글이 185개, 크기가 144,118바이트나 들어 있습니다. 글 하나당 평균 크기는 144,118 ÷ 185 = 779.0바이트입니다.

왜 이 구조가 불편할까요? 사람은 웹 브라우저에서 Ctrl + F 단축키를 눌러 원하는 단어로 바로 건너뛸 수 있습니다. 하지만 인터넷 문서를 자동으로 수집해 가는 프로그램(크롤러)이나 AI는 파일 안의 글 한 편만 보고 싶어도 144,118바이트짜리 파일 전체를 처음부터 끝까지 다 내려받아 읽어야 합니다. 게다가 저장소를 처음 열면 보이는 소개 파일(README.md)도 1,204바이트짜리 짧은 글뿐이라서, 어떤 파일 안에 어떤 주제의 글이 들었는지 미리 알려 주는 목록이 전혀 없었습니다.

이 문제를 해결하려고 2026-11-14 하루 동안 아래 다섯 번의 변경 기록(커밋, GitHub 저장소에 수정 내용을 확정해 저장하는 단위)을 올렸습니다.

커밋 번호 저장 시각(UTC) 한 일 바뀐 줄 수
98969df 03:47 색인을 만드는 프로그램과 주제별 색인 페이지 10개 추가 11개 파일, 861줄 추가
1d2ae33 03:48 README에 읽는 대상, 주제 색인 링크, FAQ, 재생성 방법 추가 71줄 추가, 2줄 삭제
a3b17e3 03:48 AI 전용 전체 안내 문서 llms.txt 추가 39줄 추가
ecde7ba 05:22 Grok Bot 사용 팁 문서 추가 및 README·llms.txt에 연결 162줄 추가
20593d5 05:22 Grok Bot 색인 페이지 맨 위에 팁 문서 링크 연결 11줄 추가, 3줄 삭제

다섯 번의 변경을 모두 합치면 총 14개 파일이 바뀌었고, 1,141줄이 추가되고 2줄이 삭제되었습니다.


2. llms.txt: 3,718바이트로 저장소 전체 구조를 먼저 알려 주는 안내 파일

llms.txt는 저장소 가장 바깥 폴더에 두는 일반 텍스트 파일입니다. AI가 이 저장소를 처음 열었을 때 방대한 파일들을 무작정 다 읽지 않고, 어떤 주소로 들어가면 원하는 문서를 바로 볼 수 있는지 목록과 한 줄 설명만 적어 둔 파일입니다.

현재 올라간 llms.txt는 총 43줄, 3,718바이트이며 아래 모양으로 적혀 있습니다.

# AI Dev Knowledge

> A Korean-language knowledge base of practical tips, updates and workflows ...

- Language: entries and summaries are in Korean; index pages have Korean and English intros.
- Audience: developers (client and server), artists, game designers and QA who build apps, web and games with AI.
- Entry format: a `## title` heading followed by `- 도구:` (tool), `- 날짜:` (date), ...
- Updates: new entries are added to the monthly file about twice a day (around 00:00 and 12:00 KST).
- Topic pages under topics/ are generated by scripts/build-index.py ...

## Docs
- [README](https://raw.githubusercontent.com/.../README.md): Overview, audience, entry format, FAQ and how to regenerate the indexes
...
## Topics by tool
- [Claude Code](https://raw.githubusercontent.com/.../topics/claude-code.md): Entries about Anthropic's Claude Code
...

맨 첫 줄에는 # 제목 한 개, 두 번째 줄에는 > 인용 표시로 전체 요약 한 줄을 씁니다. 그 아래에는 ## 묶음 제목과 - [문서 이름](주소): 설명 목록이 이어집니다. 이 형식은 llms.txt를 쓰는 여러 프로젝트에서 공통으로 맞춰 쓰는 문서 모양입니다.

여기서 중요한 선택이 하나 있습니다. llms.txt 안에 적은 링크 주소를 일반 웹 화면 주소(github.com/...)가 아니라 파일 원본 주소(raw.githubusercontent.com/...)로 적었습니다. 왜 그랬을까요?

일반 웹 화면 주소(github.com/...)로 접속하면 글 본문만 오는 것이 아니라, 화면 위쪽 메뉴, 파일 목록, 버튼, 디자인 코드(HTML과 자바스크립트)까지 전부 함께 전송됩니다. 반면 파일 원본 주소(raw.githubusercontent.com/...)로 접속하면 꾸미는 코드 없이 글자 본문(text/plain)만 깔끔하게 전송됩니다. 실제 컴퓨터 터미널에서 요청을 보내 확인한 결과입니다.

$ curl -sSI https://raw.githubusercontent.com/kugorang/ai-dev-knowledge/main/llms.txt
HTTP/2 200
content-type: text/plain; charset=utf-8
etag: "70f390a334bd2d319f0fc69f7e50fe9efb1f82ec54a5c4789782d665a32c21c1"

$ curl -sS -o /dev/null -w "%{http_code} %{size_download} %{time_total}\n" <같은 주소>
200 3718 0.034118

확인 결과 응답 코드는 200(정상 성공), 받은 크기는 3,718바이트, 걸린 시간은 0.034초였습니다. 저장소 안 모든 파일을 합친 크기가 367,719바이트이므로, llms.txt는 전체 크기의 1.0%밖에 안 되는 작은 용량으로 전체 문서 목록을 알려 줍니다.

다만 주의할 점이 있습니다. llms.txt는 전 세계가 법이나 공식 표준으로 의무화한 규칙이 아니라, llmstxt.org 제안에서 시작되어 여러 개발자가 자발적으로 쓰는 관행입니다. 따라서 이 파일을 올렸다고 해서 모든 검색 엔진이나 AI가 100% 자동으로 이 파일을 먼저 읽어 간다고 단정할 수는 없습니다. 지금까지 직접 확인된 사실은 "원본 주소로 요청했을 때 오류 없이 200 응답과 3,718바이트 순수 텍스트를 돌려준다"까지입니다. 실제로 어떤 AI 봇이 이 파일을 몇 번 가져갔는지 알려면 서버 접속 기록(로그)을 보아야 하는데, GitHub가 대신 서버를 운영해 주는 무료 저장소에서는 그 접속 기록을 받아 볼 수 없습니다.


3. 색인 생성기: 글 읽기 → 위치 ID 계산 → 날짜 정렬 → 분류 → 파일 저장

색인 페이지를 손으로 일일이 만들지 않고 자동으로 만들어 주는 파이썬 프로그램 scripts/build-index.py는 전체 280줄, 파일 크기 10,485바이트입니다. 외부에서 따로 설치해야 하는 도구 없이, 파이썬 3에 기본으로 들어 있는 네 가지 기능(re, sys, unicodedata, pathlib)만 불러와 동작합니다.

이 프로그램이 실행되는 순서는 다섯 단계입니다.

  1. entries/ 폴더에서 월별 파일(YYYY-MM.md)과 bookmarks.md를 찾습니다. 월별 파일은 글 제목이 ##(우물 정 자 2개)으로 시작하고, bookmarks.md는 글 제목이 ###(우물 정 자 3개)으로 시작합니다. 파일마다 제목 크기가 다르므로 entry_files() 함수가 (파일 경로, # 개수) 쌍으로 알려 줍니다.
  2. 각 파일을 첫 줄부터 마지막 줄까지 한 줄씩 읽으면서 글 제목과 상세 항목(도구, 날짜, 요약, 출처)을 뽑아냅니다.
  3. 각 글 제목으로 바로 건너뛸 수 있는 위치 주소(앵커 ID)를 GitHub과 똑같은 규칙으로 계산합니다.
  4. 뽑아낸 글들을 날짜 순서대로 최신 글이 맨 위에 오도록 줄 세웁니다.
  5. 사용하는 도구(5개)와 주제(4개)별로 글 링크를 다시 묶어 마크다운 문서로 만든 뒤 topics/ 폴더에 저장합니다.

3.1 한 줄씩 읽으면서 글 제목과 항목을 뽑는 부분

HEADING_RE = re.compile(r"^(#{1,6})[ \t]+(.*?)(?:[ \t]+#+)?[ \t]*$")
FIELD_RE = re.compile(r"^- (도구|날짜|요약|출처):\s*(.*)$")

def parse_file(path, level):
    slugger = Slugger()
    entries = []
    current = None
    in_fence = False
    for line in path.read_text(encoding="utf-8").splitlines():
        if line.lstrip().startswith(("```", "~~~")):
            in_fence = not in_fence
            continue
        if in_fence:
            continue
        heading = HEADING_RE.match(line)
        if heading:
            anchor = slugger.slug(plain_heading(heading.group(2)))
            current = None
            if len(heading.group(1)) == level:
                current = {"title": heading.group(2).strip(), "anchor": anchor,
                           "file": path.name}
                entries.append(current)
            continue
        field = FIELD_RE.match(line)
        if field and current is not None:
            current.setdefault(field.group(1), field.group(2).strip())
    return [e for e in entries if "도구" in e and "요약" in e and "날짜" in e]

코드를 한 부분씩 풀어서 보면 왜 이렇게 짰는지 바로 알 수 있습니다.

  • HEADING_RE는 줄 맨 앞에 # 기호가 1개에서 6개 사이로 나오고 뒤에 빈칸이 이어지는지 검사하는 규칙(정규표현식, 정해진 글자 패턴을 찾는 도구)입니다. len(heading.group(1)) == level로 # 개수를 확인하기 때문에, 진짜 글 제목(##)이 아닌 문서 맨 위의 큰 제목(#)이나 본문 안의 작은 제목(####)이 실수로 글 목록에 섞여 들어가지 않습니다.
  • in_fence는 코드 예시 칸 안쪽을 건너뛰기 위해 지금 코드 칸 안인지 밖인지 기억하는 상태 변수입니다. 마크다운 문서에서 줄이 ``` 또는 ~~~로 시작하면 그 아래부터 닫히는 기호가 나올 때까지는 프로그램 코드 예시 칸입니다. 이 상태 변수 덕분에 예시 코드 안에 적어 둔 # 제목 문자열을 실제 글 제목으로 착각해 뽑아 오는 실수를 막습니다.
  • setdefault는 같은 이름의 항목이 두 번 적혀 있어도 맨 처음 나온 값만 남기는 기능입니다. 만약 글 하나에 - 요약: 줄이 실수로 두 번 들어가더라도, 뒤의 줄이 첫 번째 요약을 덮어쓰지 않고 지켜 줍니다.
  • 맨 마지막 줄(return ...)은 필수 항목 검사입니다. 도구, 요약, 날짜 세 가지가 모두 적힌 글만 정상 항목으로 인정해 남깁니다. 2026-11-14 측정에서 220개 제목을 읽어 220개 모두 통과했고, 버려진 항목은 0개였습니다.

3.2 위치 주소(앵커): GitHub 화면과 한 글자도 틀리지 않게 계산하기

색인 페이지에 들어 있는 링크 [글 제목](../entries/2026-11.md#위치주소)를 눌렀을 때 그 글이 있는 정확한 높이로 화면이 이동하려면, # 뒤에 붙는 위치 주소(앵커, 웹 페이지 안의 특정 지점을 지정하는 식별 이름)가 GitHub 웹사이트가 자동으로 만드는 식별 이름과 똑같아야 합니다. 그래서 GitHub이 쓰는 변환 규칙을 그대로 파이썬 코드로 옮겼습니다.

class Slugger:
    """GitHub-compatible heading anchors (same rules as github-slugger)."""

    def __init__(self):
        self.seen = {}

    @staticmethod
    def base(text):
        kept = []
        for ch in text.lower():
            cat = unicodedata.category(ch)
            if ch in " -" or cat[0] in "LMN" or cat == "Pc":
                kept.append(ch)
        return "".join(kept).replace(" ", "-")

    def slug(self, text):
        original = self.base(text)
        result = original
        while result in self.seen:
            self.seen[original] += 1
            result = f"{original}-{self.seen[original]}"
        self.seen[result] = 0
        return result

unicodedata.category(ch)는 컴퓨터가 전 세계 모든 글자(유니코드)에 붙여 둔 분류 기호 두 글자를 알려 주는 기능입니다. 앞 글자가 L이면 한글·영어 같은 일반 글자, N이면 숫자, M이면 글자에 붙는 발음 기호이고, Pc는 밑줄(_) 같은 연결 기호입니다. 이 조건에 맞지 않는 마침표(.), 쉼표(,), 물음표(?), 따옴표 등은 모두 지우고, 영어는 모두 소문자로 바꾼 뒤 빈칸을 -(하이픈)으로 바꿉니다.

한글은 컴퓨터 분류표에서 L(일반 글자)에 들어가므로 지워지지 않고 그대로 남습니다. 실제 제목 하나로 계산해 본 결과입니다.

  • 원래 제목: Grok Bot 하네스 최적화로 같은 요금제에서 사용량 여유 증가
  • 만들어진 위치 주소: grok-bot-하네스-최적화로-같은-요금제에서-사용량-여유-증가
  • 위치 주소 글자 수: 36자

한글이 들어간 위치 주소는 사람이 읽기는 좋지만, 웹 브라우저나 프로그램이 인터넷 주소창으로 요청을 보낼 때는 한글을 %ED%95%98... 형태의 영어·숫자 기호로 바꾸는 과정(퍼센트 인코딩)을 거칩니다. 위 36자 위치 주소를 이 형태로 바꾸면 204자로 늘어납니다. 도구마다 이 긴 주소를 처리하는 방식이 조금씩 다를 수 있으므로, 링크를 다 만든 다음에는 실제로 모든 위치 주소가 짝이 맞는지 자동으로 검사해야 합니다. 그 검사 방법은 아래 5절에서 다룹니다.

self.seen 목록은 한 파일 안에 똑같은 제목이 두 번 나올 때 뒤에 -1, -2 번호를 붙여 서로 겹치지 않게 구분하는 장치입니다. 2026-11-14 측정에서는 세 파일 전체에 중복 제목이 0건이어서 이 부분이 작동할 일이 없었지만, 앞으로 글이 쌓이면서 같은 제목이 생길 수 있으므로 코드는 그대로 두었습니다.

3.3 도구별 분류: 쉼표로 나누고 Codex는 ChatGPT와 따로 세기

def tool_slugs(raw):
    tokens = [t.strip().lower() for t in raw.split(",")]
    has_codex = any("codex" in t for t in tokens)
    found = set()
    for t in tokens:
        if "claude code" in t:
            found.add("claude-code")
        if "codex" in t:
            found.add("codex")
        if "cursor" in t:
            found.add("cursor")
        if "grok" in t:
            found.add("grok-bot")
        if "chatgpt" in t or (("openai" in t or "gpt" in t) and not has_codex):
            found.add("chatgpt")
    return found

글의 - 도구: 줄에는 예를 들어 Claude Code, Codex와 같이 여러 도구 이름을 쉼표(,)로 이어 적습니다. 2026-11-14 기준 저장소 전체를 확인해 보니 서로 다른 도구 조합이 56가지나 되었습니다.

여기서 has_codex라는 확인 값을 따로 둔 이유가 있습니다. Codex는 OpenAI 회사가 만든 코딩 도구입니다. 만약 이 확인 값이 없으면 - 도구: Codex, OpenAI라고 적힌 글이 Codex 페이지에도 들어가고 openai라는 글자 때문에 ChatGPT 페이지에도 중복으로 들어가서, 실제 ChatGPT 관련 글 숫자가 부풀려집니다. 그래서 같은 줄에 codex가 하나라도 있으면 단순히 openai나 gpt라는 단어가 보여도 ChatGPT 페이지에는 넣지 않도록 막았습니다. ChatGPT라고 직접 적은 글만 ChatGPT 페이지에 들어갑니다.

3.4 주제별 분류: 제목과 요약에서 정해진 단어 찾기

def theme_slugs(entry):
    haystack = f"{entry['title']} {entry['요약']}".lower()
    return {th["slug"] for th in THEMES
            if any(re.search(k, haystack) for k in th["keywords"])}

주제 분류는 글의 제목과 요약을 합친 문자열에서 미리 정한 단어 패턴(정규표현식)이 있는지 검사합니다. 예를 들어 아트·영상 주제를 찾는 규칙에는 픽셀(?! 좌표)가 들어 있습니다. 여기서 (?! 좌표)는 "픽셀 바로 뒤에 좌표라는 말이 따라오지 않는 경우만 고른다"는 조건입니다. 이 조건 덕분에 화면 버튼 위치를 말하는 픽셀 좌표계라는 글이 잘못해서 아트·영상 페이지로 분류되는 일을 막습니다. 또 영상 ?(생성|제작|편집|광고|지시|만들|파이프라인) 조건을 써서 띄어쓰기가 없는 영상생성과 띄어쓰기가 있는 영상 생성 두 가지를 모두 놓치지 않고 잡아냅니다.

3.5 날짜 순서로 줄 세우기: 날짜가 같으면 원래 파일 순서 지키기

DATE_RE = re.compile(r"(\d{4}-\d{2}-\d{2})(?:\s+(\d{2}:\d{2}))?")
SENTENCE_RE = re.compile(r"(.+?[.!?][\"'”’)]*)(?=\s|$)")

def date_key(raw):
    found = DATE_RE.findall(raw)
    if not found:
        return ("", "")
    return max((d, t) for d, t in found)

- 날짜: 줄에는 2026-11-11처럼 날짜만 적은 글도 있고 2026-11-11 22:15처럼 시각까지 적은 글도 있습니다. findall은 한 줄에 들어 있는 모든 날짜와 시각을 뽑아내고, max는 그중 가장 최근 날짜를 고릅니다. 날짜가 아예 없는 글은 빈 문자열 ("", "")이 되어서 정렬했을 때 가장 뒤쪽(맨 아래)으로 내려갑니다.

정렬 코드는 entries.sort(key=lambda e: (e["sort"], e["order"]), reverse=True)입니다. 여기서 order는 원래 파일에서 위에서 몇 번째로 읽었는지를 기록한 번호입니다. 파이썬의 sort 기능은 기준 값이 같을 때 원래 순서를 흐트러뜨리지 않고 그대로 지켜 주는 성질(안정 정렬)이 있어서, 날짜가 같은 글끼리는 원래 기록한 순서가 깔끔하게 유지됩니다.

색인 페이지 각 줄에 붙는 짧은 설명은 first_sentence() 함수가 요약의 맨 첫 문장만 잘라 붙입니다. 마침표(.), 물음표(?), 느낌표(!) 뒤에 빈칸이나 줄 끝이 오는 첫 지점까지를 자르고, 문장 끝에 닫는 따옴표가 붙어 있으면 그것까지 함께 가져옵니다.


4. 늘 같은 입력에는 늘 같은 출력: --check로 색인이 낡았는지 바로 잡기

색인 생성기는 파일을 고칠 때마다 결과를 새로 계산해서 기존 topics/ 파일 내용과 글자 하나까지 비교합니다. 완전히 같으면 파일을 건드리지 않고, 한 글자라도 다르면 새 파일로 덮어씁니다. 이때 --check 옵션을 붙여서 실행하면 파일을 고치지 않고 "지금 색인이 최신 상태인가"만 검사한 뒤, 어긋났을 때만 오류 번호 1을 돌려줍니다.

def main():
    check = "--check" in sys.argv[1:]
    stale = []
    for name, content in build().items():
        path = TOPICS_DIR / name
        if path.exists() and path.read_text(encoding="utf-8") == content:
            continue
        stale.append(name)
        if not check:
            TOPICS_DIR.mkdir(exist_ok=True)
            path.write_text(content, encoding="utf-8")
    verb = "out of date" if check else "updated"
    print(f"{verb}: {', '.join(stale) if stale else 'nothing'}")
    return 1 if check and stale else 0

만들어진 색인 페이지 맨 첫 줄에는 <!-- generated by scripts/build-index.py; do not edit -->라는 안내 문장이 들어갑니다. 웹 화면에는 보이지 않지만 파일을 여는 사람에게 "이 파일은 프로그램이 자동으로 만든 파일이니 손으로 직접 고치지 말라"고 알리는 표시입니다.

실제로 컴퓨터 터미널에서 --check 검사가 작동하는지 세 단계로 시험해 보았습니다.

$ python3 scripts/build-index.py --check
out of date: nothing
$ echo $?
0

# 일부러 어긋난 상태 만들기: topics/mcp.md 끝에 테스트용 한 줄을 추가한 뒤 검사
$ printf '\nstale\n' >> topics/mcp.md
$ python3 scripts/build-index.py --check
out of date: mcp.md
$ echo $?
1

# 추가했던 줄을 지우고 원래대로 되돌린 뒤 다시 검사
$ python3 scripts/build-index.py --check
out of date: nothing
$ echo $?
0

세 번째 결과가 가장 중요합니다. 시험 삼아 고쳤던 줄을 다시 지우면 검사 결과가 곧바로 0(정상)으로 돌아옵니다. 이 프로그램은 실행할 때마다 달라지는 값(현재 시각이나 무작위 숫자)을 전혀 쓰지 않고 오직 entries/ 문서 글자만 읽어서 결과를 만들기 때문에, 같은 입력을 넣으면 언제 실행해도 100% 똑같은 출력이 나옵니다.

실행 속도는 5번 연속 실행해서 재어 보니 가운데 값 72.4ms, 가장 빠를 때 72.1ms, 가장 느릴 때 76.8ms였습니다. 0.08초도 걸리지 않기 때문에 코드를 저장할 때마다 자동 검사(CI, 코드를 올릴 때마다 자동으로 시험을 돌리는 장치)에 걸어 두어도 부담이 전혀 없고, 누군가 새 글을 추가하고 색인 재생성을 깜빡하면 자동 검사가 오류 번호 1을 내며 저장을 막아 줍니다.


5. 실제 결과: 파일 10개, 링크 473개, 깨진 링크 0개

2026-11-14에 색인 생성기를 실행해 직접 측정한 전체 수치입니다.

측정 항목 실제 결과
입력 글 수 220개 (2026-10 파일 7개, 2026-11 파일 185개, bookmarks 파일 28개)
입력 파일 합계 크기 163,946바이트
새로 만든 색인 파일 10개 (도구별 5개, 주제별 4개, 전체 색인 목차 1개)
색인 파일 합계 크기 168,644바이트
색인에 들어간 링크 줄 수 473개 (글 1개당 평균 2.15번 링크)
끊어진(깨진) 위치 주소 0개
색인 생성 프로그램 크기 280줄, 10,485바이트
검사 실행 시간 5회 가운데 값 72.4ms
llms.txt 크기 43줄, 3,718바이트

도구 페이지와 주제 페이지마다 몇 개씩 글이 묶였는지도 확인했습니다.

도구 색인 페이지 묶인 글 수 주제 색인 페이지 묶인 글 수
Claude Code 101 에이전트·스킬 158
Codex 46 MCP 36
Cursor 36 아트·영상 32
ChatGPT / OpenAI 15 게임·Unity 27
Grok Bot 22

깨진 링크 0개는 사람이 일일이 눌러 본 것이 아니라 검사 스크립트로 전수 대조한 결과입니다. 생성기가 만든 473개 링크에서 파일 이름과 # 뒤 위치 주소를 모두 떼어 낸 뒤, 실제 원본 파일 안에 똑같은 위치 주소가 존재하는지 하나도 빠짐없이 비교했습니다.

만들어진 색인 페이지 하나가 실제로 어떻게 보이는지 예시입니다(topics/grok-bot.md).

<!-- generated by scripts/build-index.py; do not edit -->

# Grok Bot

Grok Bot 운영·설정·사용량 관련 항목 모음입니다.

Entries about running, configuring and budgeting Grok Bot.

관련 가이드 / Related guides: [Grok Bot 사용 팁](../guides/grok-bot-tips.md)

항목 22개, 최신순 / 22 entries, newest first.

- [Grok Bot 하네스 최적화로 같은 요금제에서 사용량 여유 증가](../entries/2026-11.md#grok-bot-하네스-최적화로-같은-요금제에서-사용량-여유-증가) (2026-11-14): Grok Bot 팀이 이번 주 하네스 최적화를 몇 가지 배포해, 같은 요금제에서 쓸 수 있는 양이 평균 약 7% 늘고 루틴 실행은 최대 약 15%까지 늘어난다고 밝혔다.

위쪽의 관련 가이드 한 줄은 20593d5 커밋에서 추가한 기능입니다. 도구 설정 목록에 guides 항목을 적어 두면 그 도구 페이지에만 가이드 문서 링크를 자동으로 달아 줍니다. 현재는 Grok Bot 페이지에만 11가지 팁을 정리한 Grok Bot 사용 팁 문서가 연결되어 있습니다.


6. 앞으로 고쳐야 할 남은 문제 세 가지

6.1 에이전트·스킬 페이지가 전체 220개 중 158개를 잡아 버린다 (71.8%)

주제 키워드 목록에 에이전트, agent, 스킬, skill, 하네스, harness 단어가 모두 들어 있어서, 글 요약에 이 단어 중 하나만 스쳐도 전부 에이전트·스킬 페이지에 들어가 버립니다. 158 ÷ 220 = 71.8%나 되기 때문에, 원하는 글을 좁혀 찾는 색인으로는 범위가 너무 넓습니다.

해결 방법은 세 가지 중 하나입니다. 첫째, 키워드마다 점수를 매겨 일정 점수 이상일 때만 넣습니다. 둘째, 글을 쓸 때 - 태그: 줄을 직접 적고 생성기는 그 태그만 읽게 바꿉니다. 셋째, - 도구: 줄처럼 미리 정한 주제 이름 목록에서만 고르게 바꿉니다. 어느 방법을 쓰든 글 쓰는 형식이 바뀌므로 기존 220개 글도 함께 손봐야 합니다. 이번 작업에서는 문제의 비율만 정확히 재 두었습니다.

6.2 글 하나가 평균 2.15번 중복으로 링크된다

473 ÷ 220 = 2.15번입니다. 예를 들어 Claude Code, Codex가 적힌 글은 Claude Code 페이지, Codex 페이지, 에이전트·스킬 페이지 세 곳에 모두 나타납니다. 검색하는 쪽에서 같은 글이 여러 페이지에 반복해서 보일 수 있습니다. 가장 대표가 되는 페이지 한 곳에만 전체 설명과 링크를 두고, 나머지 페이지에는 제목만 남기면 중복을 줄일 수 있습니다.

6.3 주제 페이지 파일도 시간이 지나면 계속 커진다

현재 가장 큰 주제 페이지는 에이전트·스킬 55,399바이트, 그다음은 Claude Code 35,789바이트입니다. 새 글이 하루 2번, 한 번에 3~5개씩 꾸준히 쌓이면 한 달에 200개 안팎이 늘어납니다. 그러면 나중에는 주제 페이지 자체도 너무 커집니다. 이를 막으려면 주제 페이지를 월별로 쪼개거나(topics/claude-code/2026-11.md), 주제 페이지에는 최근 30일 글만 남기고 오래된 글은 월별 파일 링크로 연결하는 방식으로 바꾸어야 합니다. 반면 llms.txt는 전체 목차만 적기 때문에 글이 늘어나도 3,718바이트 크기 근처에서 계속 작게 유지됩니다.


7. 자주 묻는 질문 (FAQ)

Q1. llms.txt 파일을 올리면 검색 엔진과 AI가 실제로 바로 읽어 가나요?
파일을 올려 두는 것과 실제로 읽어 가는 것은 별개입니다. llms.txt는 공식 표준이 아니라 개발자들이 자발적으로 쓰는 제안 규칙이기 때문에, 어떤 AI 서비스가 이 파일을 요청할지는 그 서비스 회사가 정합니다. 이 저장소에서 확인된 사실은 원본 주소로 요청했을 때 오류 코드 200과 순수 텍스트 3,718바이트가 정상 응답으로 온다는 것까지입니다. 실제로 어떤 봇이 몇 번 읽었는지 확인하려면 GitHub 저장소가 아니라 직접 운영하는 웹 서버에 파일을 올리고 접속 기록을 확인해야 합니다.

Q2. 색인 페이지를 파이썬 프로그램 없이 사람이 손으로 직접 쓰면 안 되나요?
손으로 쓰면 새 글을 하나 올릴 때마다 원본 파일과 색인 페이지를 매번 두 군데 이상 고쳐야 합니다. 한쪽만 고치고 다른 쪽을 깜빡하면 색인과 실제 파일 내용이 서로 어긋납니다. 자동 생성 프로그램이 있으면 원본 글(entries/)만 고친 뒤 프로그램 한 번만 돌리면 되고, --check 명령어가 빠진 곳이 없는지 바로 잡아 줍니다. 다만 분류 규칙(TOOLS, THEMES)을 바꾸면 220개 글의 분류가 한꺼번에 바뀌므로, 규칙을 고칠 때는 반드시 변경 기록과 --check 결과를 함께 확인해야 합니다.

Q3. 게임 개발 프로젝트에도 이 방식을 그대로 쓸 수 있나요?
네, 그대로 쓸 수 있습니다. 실제로 이 저장소의 게임·Unity 주제 페이지에는 이미 27개 글이 묶여 있습니다. 글에 Unity 6 (Unity 6.3 등 마이너 버전)처럼 엔진 버전까지 또렷하게 적어 두면 요약 문장에 버전 이름이 남아 검색에 잘 걸립니다. 다만 지금 프로그램은 글자 키워드만 찾기 때문에 버전 숫자별로 페이지를 따로 나누지는 않습니다. 버전별 페이지가 필요하면 - 도구: 줄에 버전을 적고 프로그램의 TOOLS 목록에 새 페이지를 추가하면 됩니다.


8. 엔지니어링 원칙: 코드 수정 ≠ 모의 시험 ≠ 실기기 검증 ≠ 운영 반영

쿠고랑 (kugorang) 개발 원칙에서는 네 단계를 엄격히 나누어 확인합니다. 이번 작업에서도 각 단계가 어디까지 확인되었는지 구분했습니다.

  • 코드 수정: scripts/build-index.py를 작성하고 색인 페이지 10개를 실제로 만들어 낸 단계입니다.
  • 모의 시험: python3 scripts/build-index.py --check가 out of date: nothing과 종료 코드 0을 내는 것을 확인한 단계입니다. 여기서는 프로그램이 계산한 글자와 저장된 파일이 일치하는지만 확인됩니다.
  • 실기기 검증: 생성된 473개 링크의 위치 주소를 실제 글 제목 목록과 전수 비교해 깨진 링크 0개를 확인하고, llms.txt 원본 주소에 실제 인터넷 요청을 보내 응답 코드 200과 3,718바이트를 받은 단계입니다. 사람이 웹 브라우저에서 473개 링크를 일일이 마우스로 눌러 본 것은 아니므로 여기까지가 자동 검증으로 확인한 범위입니다.
  • 운영 반영: GitHub 저장소에 커밋과 푸시까지 마쳐 누구나 볼 수 있게 공개한 상태입니다. 다만 이 문서를 별도의 개인 웹 서버에 올리는 작업은 아직 하지 않았습니다.

이 글에 적힌 모든 숫자는 2026-11-14 KST에 저장소를 내려받아 직접 측정한 값이며, 실제 서버 방문자 수나 트래픽 통계는 포함하지 않았습니다.


작성: 쿠고랑 (kugorang) — 2026-11-14

728x90
728x90