## 2026-05-02 — Wiki: Karpathy LLM-Wiki 정렬 (auto 검색 + 토큰 안전 + 디스크 익스포트 + Activity 로그) + 인제스트 파이프라인 강화

### 배경

위키를 "compounding artifact" 표준으로 끌어올리는 작업 — 주입을 토큰 안전 + 쿼리 기반으로 만들고, Lint에 진짜 maintenance 루프를 부여하고, 위키를 git/Obsidian에 살릴 수 있는 portable Markdown으로 노출하고, LLM이 노트에 한 일을 사용자가 볼 수 있게 surface. 동시에 인제스트 파이프라인은 긴 소스를 chunk로 쪼개고, debug logger와 통합하고, 결과/스킵 사유를 사이드바 토스트와 모달에 띄우고, LLM 호출 중 모달이 닫히는 race를 막음. 위키 사이드바는 고정 208px가 길어진 파일명을 자르던 문제 해결을 위해 resizable로.

### 주요 변경

#### 1. Wiki를 Karpathy LLM-Wiki 패턴에 정렬 (`c1f38df`)

10개 incremental commits를 한 번에 묶음. Query / Token safety / Maintenance / Activity / Portability / Discoverability / Image attachments / Defense in depth.

- **Query**: 신규 `auto` 인제션 모드. 매 todo/discussion 직전 one-shot headless retrieval로 관련 노드 픽 (기존 blunt `all` / 손-큐레이션 `selected` 외 추가). `memory-retriever.ts` 신규 — title/body/tags 기반 candidate 추출 → LLM에 점수화/선택 위임
- **Token safety**:
  - `buildNodeSummary` — full lines를 cap, fallback으로 title-only listing
  - Lint는 큰 위키를 chunk로 분할 (silent truncate 대신)
  - 인제션 preview 모달이 char/token 추정치 표시
- **Maintenance**:
  - Lint issue별 인라인 fix 액션: 중복은 merge / orphan은 link/delete / stale은 delete
  - 신규 merge 엔드포인트 — `POST /api/memory/nodes/:keepId/merge` — edge redirect, wikilink rewrite cascade, tag union을 atomic하게
- **Activity 로그**:
  - 신규 `memory_logs` 테이블 (id, project_id, event_type, severity, source_type, source_id, source_title, message, metadata, created_at) — ingest/lint/retrieve/merge 이벤트 기록
  - Wiki 탭에 **Activity** 서브탭 — 이벤트 타입 필터 + severity 막대 + 메타데이터 펼침
- **Portability (Markdown 익스포트)**:
  - DB → `.clitrigger/wiki/<entity>/<slug>.md` 일방향 export. YAML frontmatter (id, tags, edges, source_path)
  - 신규 `wiki-exporter.ts` 서비스 + `POST /api/projects/:id/memory/export` 라우트
  - 읽기 전용 **Disk diff** — Obsidian 등에서 디스크 변경된 파일을 surface (양방향 sync 아님)
  - 명시적 **Rebuild** 액션 — DB로 디스크 덮어쓰기
- **Discoverability (System 노드)**:
  - 신규 `wiki-index.ts` — schema + 자동 maintained Index 노드를 별도 "System" 사이드바 섹션으로
  - "edits are overwritten" 배너로 사용자가 수정해도 덮어씌어짐을 명시
  - System 노드는 entry group / retrieval candidate / lint / mode='all' 인제션에서 제외
- **이미지 첨부**:
  - 본문 에디터에 paste/drop → `.clitrigger/wiki-assets/`에 업로드 + 상대 markdown 경로 삽입 → 프로젝트와 함께 이동, Obsidian에서도 resolve
  - `POST /api/projects/:id/memory/assets` (upload) + `GET /api/projects/:id/memory/assets/:filename` (serve)
- **Defense in depth**:
  - `getMemoryNodesByIds`가 SQL 단에서 `projectId` 요구하도록 보강 (cross-project leak 방지)
  - `.gitignore`를 `.clitrigger/raw/`로 좁혀 사용자가 wiki/ 폴더는 commit 가능하도록 opt-in

#### 2. Wiki UI: 사이드바 resizable + 모달 dismiss 가드 (`f5b18ca`, `2755c88`)

- **Wiki 사이드바 resizable (`f5b18ca`)**: 고정 208px가 길어진 entry/source 파일명을 자르던 문제. `MemoryList.tsx` — 정적 `w-52` → state-driven inline width (160-560px clamp, default 208px). 사이드바와 graph/editor pane 사이에 `WikiResizer` 드래그 핸들. localStorage 키 `clitrigger:wiki:sidebar-w`에 width 영속. right content에 `min-w-0` 추가해 flex shrink가 정상 작동
- **인제스트/Lint 모달 dismiss 차단 (`2755c88`)**: LLM 호출 중 backdrop 클릭/Esc로 모달이 닫히면 fetch는 백그라운드에서 계속되고 `running` 가드는 unmount된 컴포넌트와 함께 사라져 다시 열면 같은 소스에 대해 두 번째 LLM 호출이 가능해지는 race. `IngestModal`/`LintModal`에 `disableBackdropClose={running}` + `disableEscClose={running}` 전달 — 명시적 Cancel 버튼만 close path (이미 `running`로 게이팅)

#### 3. 인제스트 파이프라인 강화 — 청킹/디버그/브로드캐스트/스킵 사유 (`535761b`, `5ae0156`, `94b03b2`, `6a36df1`)

WIP 시리즈로 묶인 4개 변화. 인제스트 작업이 길고 실패 가능성이 있으니 가시성/안정성을 통째로 끌어올림.

- **긴 소스 chunk 분할 (`535761b`)**: >8000 chars 소스를 paragraph boundary에서 split (max 4 chunks of 7000). chunk 사이에 nodes를 re-fetch해서 dedup이 직전 chunk의 추가를 보도록. prompt에 chunk preamble/note 주입, debug session도 part별 태깅. created/updated/edges 카운트는 chunks 간 합산, parse 실패 시 last raw 노출
- **memory-ingest/lint CLI를 debug logger에 tee (`5ae0156`)**: project.debug_logging이 켜져 있을 때 ingestSource/lintWiki에서 debugLogger 세션 open. synthetic todoId `mem-{kind}-{sourceType}-{sourceId|ts}`로 entry가 real todo log와 함께 정렬되도록 reuse. stdin/stdout/stderr tee + close/error/timeout/stdin-write fail 모두 finalize
- **자동-인제스트 결과 사이드바 broadcast (`94b03b2`)**: 신규 `memory:ingest-finished` WS event (counts/skip reasons/error). todo/discussion auto-인제스트를 `runAutoIngestAndBroadcast`로 wrap. Sidebar가 success/empty/parseFailed/error 토스트 표시 (ko/en i18n)
- **Skip 사유 결과 모달에 surface (`6a36df1`)**: 서버가 skipped breakdown (parse fail, dup, unique, bad id/edge) 반환. 아무것도 적용 안 됐을 때 `IngestModal`에 breakdown + raw response snippet 렌더. 스킵 사유 + no-op 상태에 ko/en 키 추가

#### 4. Raw 소스 파일 삭제 (`bc9b43f`)

- Raw 스냅샷이 위키 큐레이션 후 누적되는데 정리 UI가 없었음. `RawFileViewer` 헤더에 Trash2 버튼 — 파일 삭제 + 그 파일에서 derive된 모든 위키 노드의 `source_path`를 null로
- **노드 자체는 보존**, source link만 끊음. confirm dialog가 unlink될 derived node 개수 명시
- **DELETE /api/projects/:id/memory/raw-files** 라우트 (path-traversal guard) — derived node를 **먼저** unlink한 다음 파일 삭제 (partial FS 실패 시 dangling source_path 방지)
- 클라이언트 `deleteWikiRawFile` API + 부모 (MemoryList)에서 `onDeleted` → 선택된 raw path 정리 + graph/raw file list 재로드

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/server/services/memory-retriever.ts` | NEW — auto 모드용 candidate retrieval + LLM 점수화 |
| `src/server/services/wiki-exporter.ts` | NEW — DB → `.clitrigger/wiki/<entity>/<slug>.md` 익스포트 + frontmatter |
| `src/server/services/wiki-index.ts` | NEW — System 노드 (Schema + Index) auto-maintain |
| `src/server/services/memory-ingest.ts` | chunk 분할 (>8000 chars), debug logger tee, skip breakdown 반환 |
| `src/server/services/memory-injector.ts` | buildNodeSummary cap + title-only fallback |
| `src/server/services/memory-inject-hook.ts` | auto 모드 분기, 토큰 추정치 |
| `src/server/db/schema.ts` | 신규 `memory_logs` 테이블 + idx_memory_logs_project |
| `src/server/db/queries.ts` | getMemoryNodesByIds(projectId) + memory_logs CRUD |
| `src/server/routes/memory.ts` | merge / assets (POST+GET) / disk-diff / export / logs / raw-files DELETE |
| `src/client/src/components/MemoryList.tsx` | 사이드바 resizer, Activity 서브탭, RawFileViewer Trash 버튼 |
| `src/client/src/components/MemoryInjectControl.tsx` | auto 모드 옵션, 토큰/char 추정치 표시 |
| `src/client/src/components/Sidebar.tsx` | memory:ingest-finished 토스트 dispatcher |
| `src/client/src/api/memory.ts` | 신규 endpoint wrapper (merge/export/assets/disk-diff/logs) |
| `src/client/src/i18n.tsx` | wiki.* 키 + 스킵 사유/no-op 키 + Activity 키 + ingest-finished 토스트 키 |
| `.gitignore` | `.clitrigger/` → `.clitrigger/raw/`로 좁힘 (wiki/는 commit 가능) |

### 아키텍처 결정

1. **세 번째 인제션 모드 `auto`**: `all`은 토큰 폭증, `selected`는 휴먼 큐레이션 비용. headless LLM 호출로 todo/discussion 텍스트와 매칭되는 노드만 선택해서 두 단점을 동시에 회피. 비용은 retrieval 한 번이지만 본 prompt가 짧아져서 net 절감
2. **DB → Markdown 단방향 + Disk diff**: 양방향 sync는 cycle/충돌 처리가 복잡. 일방향 export + 외부 변경을 surface만 + 명시적 Rebuild 액션이라는 보수적 디자인. Obsidian/git에 commit 가능한 구조는 보장하면서도 truth source는 DB에 고정
3. **System 노드를 별도 섹션 + 인제션 제외**: schema/index가 일반 entry group/all 인제션에 섞이면 사용자 노트 신호가 dilute. "edits are overwritten" 배너로 사용자가 수정 시도하지 않도록 명시
4. **인제스트 chunk 사이 nodes re-fetch**: chunk 1이 만든 노드를 chunk 2 dedup이 보지 못하면 같은 노드를 두 번 만들 수 있음. 매 chunk 직전 nodes 재로드로 누적 dedup
5. **모달 dismiss 가드는 `running` flag 기반**: 사용자가 의도적으로 cancel은 가능 (Cancel 버튼이 fetch abort까지 함). backdrop/Esc만 차단해서 실수로 LLM 작업을 잃지 않도록

---
