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.md와 GEMINI.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 동일 원칙 공유 |
| 온디바이스 AI | Vision OCR + Foundation Models (iOS 26, 개인정보 보호) |
| 서버 LLM | LocalLLMClient — OpenAI-compatible, thinking budget 지원 |
wiki 구조 자체가 LLM의 동작 방식에 맞게 설계돼 있다. “전체를 읽을 수 없으니 인덱스를 먼저 읽어라”는 제약을 문서 설계에 내재화한 것이다.