지난 글에서 가장 크고 활발한 obsidian-llm-wiki를 봤다. 이번엔 스타 수는 훨씬 적지만(4개), 소스를 열어보니 진지하게 만들고 있다는 게 바로 느껴진 AI Wiki(ikeniborn)다.
9월 2일까지도 커밋되던 작은 프로젝트
9월 2일, GitHub API로 확인하니 4스타·0포크인데, 마지막 push도 바로 그날이었다. 스타 수와 실제 개발 강도가 꼭 비례하지 않는다는 걸 보여주는 사례였다.
docs/superpowers 폴더 — 같은 방식으로 만들어지고 있다
리포를 열어보고 놀란 게 하나 있었다. docs/superpowers/(plans,
specs, notes, intents 폴더까지) 구조가 그대로 있었다. 이건 Claude
Code의 superpowers 스킬(브레인스토밍 → 설계 문서 → 계획 → 서브에이전트
실행)로 개발할 때 생기는 산출물 구조다. 지금 이 플러그인도 같은
워크플로로 만들어지고 있으니, 겉보기엔 전혀 다른 프로젝트인데
개발 방법론 자체는 이미 같은 계보였다.
가장 실질적인 발견 — local.json 분리
main.ts를 읽다가 이 문장을 봤다.
“Rewrite local.json keeping only local-specific fields.” “Scrub apiKey from synced data.json — sensitive.”
Obsidian 플러그인의 data.json은 Obsidian Sync나 CouchDB(LiveSync)
같은 볼트 동기화 대상에 그대로 포함된다. AI Wiki는 API 키·프록시
인증정보처럼 민감한 값을 data.json과 분리된 local.json(디바이스
로컬에만 남고 동기화 안 됨)에 따로 저장한다. 실제로 코드에 마이그레이션
함수(migrateToLocalV1, migrateToLocalV2)까지 있어서, 예전 버전
사용자의 키도 자동으로 옮겨준다.
이걸 보고 지금 만드는 플러그인의 settings.ts를 다시 열어봤다 —
apiKey, openaiApiKey, claudeApiKey가 전부 평범한 설정 필드로
data.json에 저장되고 있었다. CouchDB로 볼트를 동기화하고 있는
지금 구성에서는, 이 키들이 그대로 동기화 서버로 올라가고 있다는
뜻이다. 이름 고민보다 훨씬 급하게 손볼 부분을 여기서 찾았다.
마이그레이션을 아홉 겹으로 쌓았다
onload() 안에 순차 마이그레이션 함수가 거의 아홉 개 가까이
쌓여 있었다(migrateJsonlDomainStorage, migrateIndexFormat,
migrateDropSections, migrateOkfFrontmatter…). 각각 개별
try/catch로 감싸서, 하나가 실패해도 Notice로만 알리고 나머지
마이그레이션은 계속 진행한다. 스키마가 자주 바뀌는 프로젝트에서
업그레이드 안전성에 신경을 많이 쓴 흔적이다.
컨트롤러 패턴 — main.ts는 얇게
obsidian-llm-wiki가 믹스인으로 main.ts를 부풀렸다면, AI Wiki는
반대다. 실제 로직(ingest/query/lint/init/exportOkf)을 전부
WikiController라는 별도 클래스에 몰아넣고, main.ts는 그
컨트롤러를 만들어서 커맨드에 연결만 하는 얇은 오케스트레이터다.
지금 만드는 플러그인의 main.ts(157줄, GeminiChatView에 위임)
구조가 오히려 이쪽에 더 가까웠다.
상태바에 LLM 신뢰도를 실시간으로 보여준다
structuralErrorCounter라는 구독 가능한 스토어가 있고, 이걸
상태바에 연결해서 “Schema: 0/0”처럼 구조화 출력이 검증 통과/재시도/
실패한 개수를 실시간으로 보여준다. LLM이 스키마에 안 맞는 응답을
얼마나 자주 내는지 사용자가 항상 볼 수 있게 만든 UI였다 — 지금
만드는 플러그인엔 이런 상시 노출 지표가 없다.
배운 점
스타 수만 보고 넘어갔으면 놓쳤을 플러그인이었다. 실제로 코드를 열어보니 규모는 작아도 보안(local.json 분리)과 마이그레이션 안전성 양쪽에서 가장 신경 쓴 흔적이 많았다 — 특히 API 키 분리는 지금 만드는 플러그인이 바로 따라 해야 할 정도로 실질적인 차이였다.