LLM이 길을 잃지 않도록 — iEvent 프로젝트의 wiki 구성 방식

iEvent는 iOS 앱, FastAPI 백엔드, React 어드민, 스케줄러까지 4개 서브시스템으로 구성된 프로젝트다. 한 세션에서 여러 영역을 넘나들다 보면 LLM이 맥락을 잃거나 불필요한 파일을 모두 읽어 토큰을 낭비하는 일이 반복됐다. 이 문제를 해결하기 위해 wiki/ 디렉토리를 중심으로 한 LLM 전용 문서 구조를 설계했다.

핵심 원칙: INDEX.md 단일 진입점

wiki/INDEX.md 상단에는 LLM을 위한 사용 규칙이 명시돼 있다.

LLM 사용 규칙: 이 파일만 먼저 읽고, 관련 항목만 추가 열람할 것.
전체 파일 일괄 읽기 금지.

모든 세션은 반드시 INDEX.md 하나를 먼저 읽는 것으로 시작한다. INDEX.md는 전체 wiki 파일의 목차이자 각 파일의 한 줄 요약을 담고 있다. LLM은 이 인덱스만 보고 지금 작업과 관련된 파일 1~3개만 골라 읽는다. 전체 wiki를 일괄로 읽는 행위는 금지다.

wiki 디렉토리 구조

wiki/
├── INDEX.md              ← LLM 진입점 (항상 먼저 읽는 파일)
├── architecture/         ← 시스템 구조, 기술 스택, 데이터 흐름, 테스트 전략
├── features/             ← 기능별 스펙 (온보딩, 홈, 스케줄, 알림장 등)
├── design/               ← UI/UX 원칙, 컬러 시스템, 와이어프레임
├── data/                 ← DB 스키마
├── flows/                ← 사용자 여정
└── decisions/            ← ADR (Architecture Decision Records)

파일 수는 30개를 넘지만 LLM이 한 번에 읽는 파일은 최대 3개다. INDEX.md가 필터 역할을 한다.

Why 중심 문서화 원칙

각 wiki 파일은 “무엇을 했는가(What)”가 아니라 “왜 그렇게 결정했는가(Why)”를 기록한다. decisions/ 디렉토리가 이 역할을 담당한다. 예시:

  • decisions/server-first-migration.md — LocalFirst에서 Server-First로 전환한 이유 (serverId 충돌, 계정 전환 복잡도 해소)
  • decisions/tca-architecture.md — TCA 채택 이유, ifLet 체이닝 타임아웃 해결 패턴
  • decisions/apple-ai-strategy.md — 온디바이스 AI를 선택한 이유 (개인정보 보호, 오프라인 동작)

코드 자체는 git에 있고, “왜 이 방식을 골랐는지”는 wiki에 있다. 이 분리가 LLM이 맥락 없이 기존 결정을 번복하는 실수를 막아준다.

실시간 변경 이력 패턴

INDEX.md의 각 항목 요약에는 [2026-06-XX] 날짜 태그로 최신 변경 사항을 인라인으로 기록한다.

| [[architecture/data-flow]] | Server-First 정책 전환 —
  [2026-06-16] POST/PUT/DELETE 서버 우선·실패 시 throw,
  GET은 LocalDB 즉시 반환+백그라운드 pull |

별도 Changelog 파일 없이, 인덱스 한 줄 안에 날짜와 변경 내용이 함께 적힌다. LLM이 “가장 최근에 어떤 결정이 바뀌었는지”를 INDEX.md만 읽고 파악할 수 있다.

CLAUDE.md / GEMINI.md 이중 지원

프로젝트 루트에는 .CLAUDE.mdGEMINI.md 두 파일이 공존한다. Claude Code와 Gemini CLI 모두 이 프로젝트에서 사용하기 때문이다. 두 파일 모두 동일한 원칙을 공유한다.

  • 작업 시작 전 wiki/INDEX.md 읽기
  • 관련 파일 1~3개만 선택 열람
  • 소스 검색 전 codegraph MCP 우선 사용
  • /doc 커맨드는 자동 실행 금지 (수동 호출만 허용)

LLM이 달라도 동일한 컨텍스트 로딩 전략을 따르게 해서, 어떤 도구를 쓰든 일관된 작업 품질을 유지한다.

온디바이스 AI: Vision OCR + Foundation Models

iEvent의 알림장 스캔 기능은 서버 AI 없이 기기 위에서 완전히 동작한다.

사진(Data)
  → VNRecognizeTextRequest (Vision OCR)
  → raw text
  → LanguageModelSession + @Generable (Foundation Models)
  → AIParseSuccess { items, summary }

아이의 학교 알림장은 개인정보가 민감하다. 서버로 이미지를 업로드하지 않고 기기 위에서 처리함으로써 프라이버시를 보호하고, 네트워크 없는 환경에서도 동작하게 했다.

Foundation Models는 iOS 26+에서만 동작하므로, iOS 18 최소 타겟과 공존하기 위해 @available(iOS 26, *) 래퍼 패턴을 사용한다. TCA Action enum에는 FoundationModels 타입이 노출되지 않도록 도메인 타입(AIParseSuccess)으로 변환 후 반환한다.

서버 LLM: AI 브리핑 파이프라인

온디바이스 AI와 별개로, 매일 아침 가족에게 오늘 일정 브리핑을 푸시하는 기능은 서버 LLM을 사용한다. scheduler/llm/local_llm_client.py가 그 역할을 한다.

class LocalLLMClient:
    async def chat(self, system_prompt: str, user_message: str) -> str:
        payload = {
            "model": self.model,
            "messages": [
                {"role": "system", "content": system_prompt},
                {"role": "user",   "content": user_message},
            ],
            "thinking": {
                "type": "enabled",
                "budget_tokens": self.thinking_budget,
            },
        }
        response = await self._client.post(
            f"{self.base_url}/chat/completions", json=payload
        )
        ...

OpenAI-compatible /chat/completions 엔드포인트를 호출하는 얇은 클라이언트다. thinking.budget_tokens를 지원해 추론 모델도 사용할 수 있다. 스케줄러는 매일 07:30과 21:00에 이 클라이언트를 통해 오늘/내일 브리핑을 생성하고 APNs로 전송한다.

정리

목적방식
LLM 컨텍스트 로딩INDEX.md 단일 진입 → 관련 파일 1~3개만 선택
결정 기록decisions/ ADR — Why 중심, 날짜 인라인 태그
다중 LLM 지원.CLAUDE.md + GEMINI.md 동일 원칙 공유
온디바이스 AIVision OCR + Foundation Models (iOS 26, 개인정보 보호)
서버 LLMLocalLLMClient — OpenAI-compatible, thinking budget 지원

wiki 구조 자체가 LLM의 동작 방식에 맞게 설계돼 있다. “전체를 읽을 수 없으니 인덱스를 먼저 읽어라”는 제약을 문서 설계에 내재화한 것이다.

Leave a Reply

Your email address will not be published. Required fields are marked *