docs: CLAUDE.md README sync rule + README 현행화 #43

Merged
altair823 merged 3 commits from docs/readme-sync-rule into main 2026-05-02 13:59:43 +00:00
4 changed files with 366 additions and 196 deletions

View File

@@ -4,9 +4,16 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
Single-user local-first knowledge base + RAG. Rust 2024 workspace, 18 crates, single binary (`kebab`). All inference is local (Ollama + fastembed + whisper.cpp).
Single-user local-first knowledge base + RAG. Rust 2024 workspace, ~20 crates, single binary (`kebab`). All inference is local (Ollama + fastembed + whisper.cpp).

(칭찬) ## Project 절이 세 sibling docs 의 audience + 위치를 한 번에 정리. README/HANDOFF/ARCHITECTURE/spec/INDEX/HOTFIXES 가 한 bullet list 로 위치 + 책임 명시 — Claude Code 가 어느 문서를 어느 audience 에 적어야 하는지 첫 절에서 즉시 결정 가능.

(칭찬) `## Project` 절이 세 sibling docs 의 audience + 위치를 한 번에 정리. README/HANDOFF/ARCHITECTURE/spec/INDEX/HOTFIXES 가 한 bullet list 로 위치 + 책임 명시 — Claude Code 가 어느 문서를 어느 audience 에 적어야 하는지 첫 절에서 즉시 결정 가능.
The high-level overview, dependency graph, phase roadmap, and directory tree all live in [README.md](README.md). Don't restate them — link to that and add only what isn't already there.
The repo's documentation is split by audience — don't duplicate across them:
- **[README.md](README.md)** — first stop for an end user. Quick start, command table, one Mermaid logical-architecture diagram, configuration pointers, license. Stays narrow.
- **[HANDOFF.md](HANDOFF.md)** — phase-level progress dashboard for someone picking the project up. Phase status table, component count, "next task candidates", short summary of post-merge deviations. The README never duplicates this.
- **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** — internal structure: crate dependency graph, directory tree, locked-in technical decisions. The README links here from the Mermaid diagram.
- **[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](docs/superpowers/specs/2026-04-27-kebab-final-form-design.md)** — frozen design contract.
- **[tasks/INDEX.md](tasks/INDEX.md)** — per-component task tree.
- **[tasks/HOTFIXES.md](tasks/HOTFIXES.md)** — dated post-merge deviation log; live source of truth where behavior and the frozen spec disagree.
## Build / test / lint
@@ -74,6 +81,32 @@ The migration from the old `kb` name lives in commits `911fb49 / f1a448d / f9714
`docs/SMOKE.md` walks through running the full pipeline against an isolated TempDir KB via `--config /tmp/kebab-smoke/config.toml`. Use this instead of touching `~/.local/share/kebab/` when verifying a fresh clone or a CLI flag change. Most CLI regressions surface here, not in unit tests (see HOTFIXES.md).
## User-facing docs (README + HANDOFF + ARCHITECTURE)

(칭찬) "User-facing docs (README + HANDOFF + ARCHITECTURE)" 절이 (1) 세 문서의 audience 분리 (2) 갱신 trigger 별 어느 문서를 손대는지 매핑 (3) 셋 다 out-of-scope 인 것 (HOTFIXES detail / version cascade / per-task spec rationale) 명시. PR 작업자가 "내 변경이 어느 문서를 갱신해야 하나?" 를 한 절 lookup 으로 답할 수 있음. spec PR 면제 명시도 그대로 유지.

(칭찬) "User-facing docs (README + HANDOFF + ARCHITECTURE)" 절이 (1) 세 문서의 audience 분리 (2) 갱신 trigger 별 어느 문서를 손대는지 매핑 (3) 셋 다 out-of-scope 인 것 (HOTFIXES detail / version cascade / per-task spec rationale) 명시. PR 작업자가 "내 변경이 어느 문서를 갱신해야 하나?" 를 한 절 lookup 으로 답할 수 있음. spec PR 면제 명시도 그대로 유지.
Three sibling docs split the audience. Every implementation PR (`feat/*`) keeps them in sync; spec PRs (`spec/*`) don't touch any of the three.

(칭찬) "Out of scope for the README" 한 줄이 미래에 README 가 부풀어 오르는 것 차단. HOTFIXES detail / version cascade / per-task spec rationale 모두 "이미 다른 곳에 더 잘 적혀 있다" — 정직 + 분리가 깔끔. 미래 contributor 가 README 에 long-form rationale 을 끼워 넣으려고 시도할 때 거부 사유가 코드 옆에 있음.

(칭찬) "Out of scope for the README" 한 줄이 미래에 README 가 부풀어 오르는 것 차단. HOTFIXES detail / version cascade / per-task spec rationale 모두 "이미 다른 곳에 더 잘 적혀 있다" — 정직 + 분리가 깔끔. 미래 contributor 가 README 에 long-form rationale 을 끼워 넣으려고 시도할 때 거부 사유가 코드 옆에 있음.
**[README.md](README.md) — end user.** Stays narrow. The three surfaces a user touches:
- **CLI** — new `kebab <subcommand>`, flag, `--json` field, or exit-code change. Update the **명령** table and the **Quick start** block if the new flow needs a different invocation.
- **TUI** — new pane, key binding, or run-time behavior visible to a `kebab tui` user. Update the row in the **명령** table and the Mermaid diagram if a new external surface lands.
- **Configuration** — new `config.toml` field, `KEBAB_*` env, default change, or XDG path. Update the **Configuration** section AND the config example block in `docs/SMOKE.md`.
The Mermaid logical-architecture diagram stays the only diagram in the README. If a new media type / external service / store crosses the diagram boundary, update it; otherwise leave it alone.
The README does NOT carry: phase status, component count, post-merge deviations, crate dependency graph, directory tree, locked-in technical decisions. Those live in HANDOFF or ARCHITECTURE.
**[HANDOFF.md](HANDOFF.md) — handing off.** Phase-level progress + next-task candidates. Flip the relevant phase row from ⏳ to ✅ when a phase epic completes. Add a one-line entry under "머지 후 발견된 버그 / 결정 (요약)" when a HOTFIXES entry lands that's load-bearing for someone picking up the project. Per-component progress lives in `tasks/INDEX.md`, not here.
**[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — implementation detail.** Crate dependency graph, directory tree, locked-in technical decisions. Update when:
- A new crate is added — extend the graph + directory tree.
- A locked-in decision flips (e.g. OCR engine default changes per a HOTFIXES entry) — update the table and link the HOTFIXES entry.
- A directory moves — update the tree.
Out of scope for all three: HOTFIXES detail (`tasks/HOTFIXES.md`), version cascade mechanics (CLAUDE.md §Versioning cascade), per-task spec rationale (`tasks/p<N>/`).
If a feature ships behind a flag that's off-by-default, mention the flag explicitly in the README so a user reading only the README knows the surface exists but is gated.
## Remote
Git remote is Gitea: `https://gitea.altair823.xyz/altair823-org/kebab.git`. PRs are created via the Gitea REST API (`POST /repos/altair823-org/kebab/pulls`) — `gh` CLI does not work against this host. Auth uses `~/.netrc` (populated via `git credential fill`).

53
HANDOFF.md Normal file
View File

@@ -0,0 +1,53 @@
# HANDOFF — 진척도
> 새 conversation / 다른 사람이 이어받을 때 \"지금 어디까지 됐고 다음에 뭘 할지\" 의 단일 출처. 사용자 사용법은 [README.md](README.md), 아키텍처는 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), per-component 진행은 [tasks/INDEX.md](tasks/INDEX.md), 머지 후 발견된 버그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md). 이 파일은 \"phase 단위 진척\" + \"다음 task 후보\" 만 담는다.
## 한 줄 요약
P0P5 + P6 + P7 + P9-1 (Library 패널) 머지 완료. `kebab ingest` 가 markdown / image / PDF 모두 처리. `kebab search` / `kebab ask` 가 매체 가로질러 결과 + page citation 반환. `kebab tui` 가 Library 패널 제공. 다음 후보 = P9-2 (TUI search) / P9-3 / P9-4 / P9-5, 또는 보류 중인 P8 (audio) 의 시스템 dep brainstorm.
## Phase 로드맵

(칭찬) "한 줄 요약" 한 단락이 인수자가 새 conversation 시작할 때 정확히 필요한 정보 (지금 어디까지 + 다음 후보) 를 압축. P5/P6/P7/P9-1 머지 / P8 보류 / P9-2~5 candidate / 사용자 패턴 (책+PDF) 까지 한 페이지 lookup 으로 답.

(칭찬) "한 줄 요약" 한 단락이 인수자가 새 conversation 시작할 때 정확히 필요한 정보 (지금 어디까지 + 다음 후보) 를 압축. P5/P6/P7/P9-1 머지 / P8 보류 / P9-2~5 candidate / 사용자 패턴 (책+PDF) 까지 한 페이지 lookup 으로 답.
| Phase | 내용 | 핵심 산출 crate | 선행 | 상태 |
|-------|------|----------------|------|------|
| **P0** | Workspace 뼈대 + 도메인 계약 + ID recipe | `kebab-core`, `kebab-parse-types`, `kebab-config`, `kebab-app`, `kebab-cli` | | ✅ 완료 |
| **P1** | Markdown ingestion (walk → parse → chunk → SQLite) | `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-sqlite` | P0 | ✅ 완료 |
| **P2** | SQLite FTS5 lexical 검색 + citation | `kebab-search` (lexical) | P1 | ✅ 완료 |
| **P3** | Local embedding + LanceDB + hybrid (RRF) + kebab-app wiring | `kebab-embed`, `kebab-embed-local`, `kebab-store-vector`, `kebab-search` | P2 | ✅ 완료 |
| **P4** | Local LLM + RAG + grounded answer | `kebab-llm`, `kebab-llm-local`, `kebab-rag` | P3 | ✅ 완료 |
| **P5** | Golden query / regression eval | `kebab-eval` | P4 | ✅ 완료 |
| **P6** | 이미지 ingestion (OCR + caption) | `kebab-parse-image` | P5 | ✅ 완료 (4/4 component, OCR/caption Ollama-vision) |
| **P7** | PDF text + page citation | `kebab-parse-pdf` | P5 | ✅ 완료 (3/3 component, page-level chunker + ingest wiring) |
| **P8** | 음성 transcription + timestamp citation | `kebab-parse-audio` | P5 | ⏸ 보류 (whisper-rs 시스템 dep brainstorm 필요) |
| **P9** | TUI + desktop app | `kebab-tui`, `kebab-desktop` | P5 | 🟡 진행 (1/5 component — P9-1 Library 완료, P9-2/3/4/5 예정) |
P0~P5 직렬. P6~P9 P5 이후 병렬 가능.
## Component 카운트
총 33 component task — spec 시점 31 개 + 후속 wiring task 3 (P3-5 / P6-4 / P7-3) 가 머지 시점에 추가됨. per-component 진행 + status 는 [tasks/INDEX.md](tasks/INDEX.md).
## 머지 후 발견된 버그 / 결정 (요약)
머지 후 발견된 모든 deviation / hotfix 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md). 본 요약은 \"누군가가 인수받을 때 알아두면 시간을 많이 절약하는\" 항목만:
- **P3-5 / P4-3 `--config` 누락** — `kebab-cli``--config <path>` 를 honor 하려면 `kebab_app::*_with_config` companion 을 호출해야 함. 두 번 같은 모양으로 회귀했음.
- **P6-2 OCR 기본 엔진** — spec literal 의 Tesseract 가 시스템 dep 부담으로 거부됨, Ollama vision LM 으로 대체. `OcrEngine` trait 그대로라 future swap 가능.

(칭찬) "머지 후 발견된 버그 / 결정 (요약)" 절이 6 deviation 을 한 줄씩 dense 표현. 본 detail 은 HOTFIXES 로 미루지만 "인수자가 알아두면 시간 절약" 만 추출 — --config 회귀 / OCR 엔진 변경 / GenerateRequest.images / chunk_id 충돌 가드 / storage UNIQUE bug 등 미래 contributor 가 "왜 이렇게 됐지?" 를 묻기 전에 답을 가져옴.

(칭찬) "머지 후 발견된 버그 / 결정 (요약)" 절이 6 deviation 을 한 줄씩 dense 표현. 본 detail 은 HOTFIXES 로 미루지만 "인수자가 알아두면 시간 절약" 만 추출 — `--config` 회귀 / OCR 엔진 변경 / GenerateRequest.images / chunk_id 충돌 가드 / storage UNIQUE bug 등 미래 contributor 가 "왜 이렇게 됐지?" 를 묻기 전에 답을 가져옴.
- **P6-3 caption** — `GenerateRequest.images` 필드를 `kebab-core::LanguageModel` trait 에 신설. 기존 caller 모두 `images: Vec::new()` 로 마이그레이션.
- **P7-2 `chunk_id` 충돌** — pdf-page-v1 가 한 페이지 여러 chunk 분할 → 같은 `block_ids` 충돌. per-chunk `policy_hash#c{char_start}` 변형 으로 회피.
- **P7-3 storage UNIQUE bug** — `assets.workspace_path` UNIQUE + `upsert_asset_row``ON CONFLICT(asset_id)` gap 으로 byte 변경 re-ingest 실패. `purge_orphan_at_workspace_path` helper 추가, follow-up PR 으로 vector store orphan cleanup 까지 닫음 (`VectorStore::delete_by_chunk_ids`).
- **P9-1 ratatui 0.28** — spec literal 의 `render_library<B: Backend>` generic 이 ratatui 0.28 의 backend-agnostic Frame 과 어긋나 있어 제거. 테스트 seam `App::populate_library_for_testing` (`#[doc(hidden)]`) 추가.
## 다음 task 후보
- **P9-2 TUI search** — `App.search` slot 채움. Library 의 `/` 가 enable 됨.
- **P9-3 TUI ask** — `App.ask` slot 채움. `?` enable.
- **P9-4 TUI inspect** — `App.inspect` slot 채움. `Enter` enable.
- **P9-5 desktop tauri** — 별도 분기. PDF citation rendering UI 가치 큼.
- **P8 audio brainstorm** — whisper-rs 시스템 dep 받을지 / 외부 transcription endpoint 사용할지 사용자 결정 필요. 사용자 패턴 (책+PDF 위주, audio 의향 없음) 상 후순위.
P9-2/3/4 는 P9-1 의 parallel-safety contract (sub-state slot 패턴) 덕에 병렬 진행 가능 — 같은 `App` 손대지 않음.
## 검증된 운영 동작 (release binary, fastembed enabled)
P7-3 머지 직후 25 시나리오 smoke 통과 — markdown + image + PDF 5 자산 워크스페이스에서 doctor / ingest / list / inspect / search (lex/vec/hybrid) / re-ingest / byte-edit re-ingest / corrupt PDF / RAG ask + page citation 모두. 자세한 시나리오 표는 conversation 기록 참조; 워크스페이스에 직접 돌려보는 절차는 [docs/SMOKE.md](docs/SMOKE.md).

342
README.md
View File

@@ -1,223 +1,177 @@
# kebab — Local-first Knowledge Base
> **상태:** P0P4 구현 완료 (31 component task 중 17 완료) + 3건 post-merge hotfix 적용. `kebab index` / `kebab search --mode {lexical,vector,hybrid}` / `kebab ask` 모두 실 동작. 다음 단계 = P5 (eval suite). 자세한 진행 상황은 [tasks/INDEX.md](tasks/INDEX.md), 머지 후 발견된 버그와 fix는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md).
`kebab` 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지를 한 곳에 색인하고, 의미 검색 + page-단위 citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed) 에서 돌아간다. 대상 하드웨어: M4 48GB MacBook 1대, 사용자 1명.
`kebab` 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지 / 음성을 한 곳에 색인하고, 의미 검색 + citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed / whisper.cpp) 에서 돌아간다.
## 사전 요구
대상 하드웨어: M4 48GB MacBook 1대, 사용자 1명.
- **Rust toolchain** ≥ 1.85 (workspace 가 edition 2024 + resolver 3 사용). [rustup](https://rustup.rs) 권장.

(칭찬) 사전 요구 절이 사용자가 첫 페이지에서 바로 마주칠 4 항목 (Rust toolchain / Ollama / 디스크 / fastembed 모델) 을 한 줄씩 dense 표현. 각 항목에 (1) 무엇이 (2) 왜 필요한지 (3) 어떻게 얻는지 명시 — ollama pull qwen2.5:7b-instruct (텍스트) / gemma4:e4b (vision) 처럼 CLI 명령까지 박아두어 검색 추가 lookup 0.

(칭찬) 사전 요구 절이 사용자가 첫 페이지에서 바로 마주칠 4 항목 (Rust toolchain / Ollama / 디스크 / fastembed 모델) 을 한 줄씩 dense 표현. 각 항목에 (1) 무엇이 (2) 왜 필요한지 (3) 어떻게 얻는지 명시 — `ollama pull qwen2.5:7b-instruct (텍스트) / gemma4:e4b (vision)` 처럼 CLI 명령까지 박아두어 검색 추가 lookup 0.
- **Ollama** — `kebab ask` 와 이미지 OCR/caption 가 사용. `https://ollama.com/download` 에서 설치 후 `ollama serve` 실행. 모델은 `ollama pull qwen2.5:7b-instruct` (텍스트) / `ollama pull gemma4:e4b` (vision) 등. config 의 `[models.llm].endpoint` 에 host:port 명시.
- **빌드 디스크** — 첫 빌드 시 `target/` 가 610 GB (Lance + DataFusion + fastembed). 여유 확인.
- **fastembed 모델** — 첫 `kebab ingest``multilingual-e5-small` (~470 MB) 자동 다운로드.
---
## 설치
## 무엇인가
| 명령 | 동작 | 상태 |
|------|------|------|
| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 | ✅ P0 |
| `kebab ingest [<path>]` | Markdown 색인 (idempotent). PDF/이미지/음성은 P6+. | ✅ P3-5 |
| `kebab search --mode {lexical,vector,hybrid} "<query>"` | 검색 — citation 포함, hybrid는 RRF fusion | ✅ P3-5 |
| `kebab list docs` | 색인된 문서 목록 | ✅ P3-5 |
| `kebab inspect doc <id>` / `kebab inspect chunk <id>` | raw record 보기 | ✅ P3-5 |
| `kebab ask "<query>"` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요. | ✅ P4-3 |
| `kebab doctor` | 설정/모델/DB 헬스 체크 | ✅ P0 |
| `kebab eval run / compare` | golden query 회귀 측정 | ⏳ P5 |
기계 친화 모드: 모든 명령에 `--json` 플래그. 출력은 frozen wire schema v1 (`schema_version` 필드 항상 포함, 예: `ingest_report.v1`, `search_hit.v1`, `answer.v1`, `doctor.v1`).
---
## 핵심 결정 (lock 됨)
| 결정 | 값 |
|------|-----|
| 언어 | Rust 2024 (resolver=3, edition 2024) |
| repo | Cargo workspace (single repo, 함수 호출 기반 모듈러 모놀리스) |
| 원본 저장 | filesystem + blake3 content-addressable copy (대용량은 reference + checksum) |
| metadata | SQLite + FTS5 (lexical search) |
| vector | LanceDB (embedded, model 별 분리 table) |
| Markdown parser | `pulldown-cmark` |
| embedding | `fastembed-rs` (`multilingual-e5-small`, 384d) |
| LLM | Ollama HTTP (default `qwen2.5:7b-instruct` ─ 사용자 환경에 맞춰 `gemma4:26b` 등으로 교체 가능) |
| 음성 ASR | `whisper.cpp` (via `whisper-rs`) — P8 |
| OCR | Tesseract (default) + macOS Apple Vision sidecar (feature gate) — P6 |
| TUI | Ratatui + crossterm — P9 |
| Desktop | Tauri 2 + `pdfjs-dist` (native PDF render backend 금지) — P9 |
| citation 형식 | URI fragment (`path#L12-L34`, W3C Media Fragments) |
| ID 생성 | `blake3(canonical_json(tuple))[..32]` hex |
| RRF fusion_score | `[0, 1]` 정규화 — `2 / (k_rrf + 1)` 로 나눠 mode 간 비교 가능 (post-merge hotfix) |
| layout | XDG (`~/.local/share/kebab/`, `~/.config/kebab/`, …) |
전체는 [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](docs/superpowers/specs/2026-04-27-kebab-final-form-design.md) 참조.
---
## 의존성 그래프
```text
kebab-cli, kebab-tui, kebab-desktop
└─> kebab-app
├─> kebab-source-fs
├─> kebab-parse-md / kebab-parse-pdf / kebab-parse-image / kebab-parse-audio
│ └─> kebab-parse-types
├─> kebab-normalize
│ └─> kebab-parse-types
├─> kebab-chunk
├─> kebab-store-sqlite
├─> kebab-store-vector
├─> kebab-embed-local (kebab-embed trait crate)
├─> kebab-search
├─> kebab-llm-local (kebab-llm trait crate)
├─> kebab-rag
├─> kebab-eval
└─> kebab-config
└─> kebab-core (모두 의존)
```
UI → store/llm/parse 직접 의존 금지. 모든 user-facing 진입은 `kebab-app` facade 만 통한다 (design §8). `kebab-cli``--config <path>` flag 를 honor 하려면 `kebab_app::*_with_config(cfg, …)` companion 을 통해 Config 을 명시적으로 thread 하는 패턴 — 자세한 이유는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md) 의 `--config` 항목.
---
## Phase 로드맵
| Phase | 내용 | 핵심 산출 crate | 선행 | 상태 |
|-------|------|----------------|------|------|
| **P0** | Workspace 뼈대 + 도메인 계약 + ID recipe | `kebab-core`, `kebab-parse-types`, `kebab-config`, `kebab-app`, `kebab-cli` | | ✅ 완료 |
| **P1** | Markdown ingestion (walk → parse → chunk → SQLite) | `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-sqlite` | P0 | ✅ 완료 |
| **P2** | SQLite FTS5 lexical 검색 + citation | `kebab-search` (lexical) | P1 | ✅ 완료 |
| **P3** | Local embedding + LanceDB + hybrid (RRF) + kebab-app wiring | `kebab-embed`, `kebab-embed-local`, `kebab-store-vector`, `kebab-search` | P2 | ✅ 완료 |
| **P4** | Local LLM + RAG + grounded answer | `kebab-llm`, `kebab-llm-local`, `kebab-rag` | P3 | ✅ 완료 |
| **P5** | Golden query / regression eval | `kebab-eval` | P4 | ⏳ 다음 |
| **P6** | 이미지 ingestion (OCR + caption) | `kebab-parse-image` | P5 | ⏳ |
| **P7** | PDF text + page citation | `kebab-parse-pdf` | P5 | ⏳ |
| **P8** | 음성 transcription + timestamp citation | `kebab-parse-audio` | P5 | ⏳ |
| **P9** | TUI + desktop app | `kebab-tui`, `kebab-desktop` | P5 | ⏳ |
P0~P5 직렬. P6~P9 P5 이후 병렬 가능.
각 phase 는 component-level 단위로 더 분해되어 있다 (총 31 component task — P3-5 app-wiring 추가). 자세한 분해는 [tasks/INDEX.md](tasks/INDEX.md). 머지 후 발견된 버그/fix 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md).
---
## 디렉토리 구조
```text
kebab/
├── README.md # 이 파일
├── kebab_local_rust_report.md # 최초 설계 보고서 (방향성 + 근거)
├── docs/
│ ├── superpowers/
│ │ ├── specs/
│ │ │ └── 2026-04-27-kebab-final-form-design.md # frozen design (12 sections)
│ │ └── plans/
│ │ └── 2026-04-27-task-decomposition.md # task 분해 implementation plan
│ ├── SMOKE.md # 로컬 워크스페이스에 직접 돌려보는 절차
│ └── wire-schema/v1/ # JSON Schema 7 (citation, search_hit, answer, …)
├── tasks/
│ ├── INDEX.md # phase 인덱스 + component task 트리
│ ├── HOTFIXES.md # post-merge dated fix 로그
│ ├── _template.md # task spec 작성 템플릿
│ ├── phase-0-skeleton.md … phase-9-ui.md # phase epic (high-level)
│ ├── p0/p0-1-skeleton.md # component task (1)
│ ├── p1/p1-1 … p1-6 # (6)
│ ├── p2/p2-1, p2-2 # (2)
│ ├── p3/p3-1 … p3-5 # (5 — p3-5 = app-wiring, post-spec 추가)
│ ├── p4/p4-1 … p4-3 # (3)
│ ├── p5/p5-1, p5-2 # (2)
│ ├── p6/p6-1 … p6-3 # (3)
│ ├── p7/p7-1, p7-2 # (2)
│ ├── p8/p8-1, p8-2 # (2)
│ └── p9/p9-1 … p9-5 # (5)
├── crates/
│ ├── kebab-core/ kebab-parse-types/ kebab-config/ # 도메인 + 설정 (P0)
│ ├── kebab-source-fs/ # 워크스페이스 walk + checksum (P1-1)
│ ├── kebab-parse-md/ # Markdown frontmatter + blocks (P1-2/3)
│ ├── kebab-normalize/ # ParsedBlock → CanonicalDocument (P1-4)
│ ├── kebab-chunk/ # heading-aware chunker (P1-5)
│ ├── kebab-store-sqlite/ # SQLite + FTS5 (V001/V002/V003) (P1-6, P2-1, P3-3)
│ ├── kebab-search/ # Lexical + Vector + Hybrid retriever (P2-2, P3-4)
│ ├── kebab-embed/ kebab-embed-local/ # Embedder trait + fastembed adapter (P3-1, P3-2)
│ ├── kebab-store-vector/ # LanceDB VectorStore (P3-3)
│ ├── kebab-llm/ kebab-llm-local/ # LanguageModel trait + Ollama adapter (P4-1, P4-2)
│ ├── kebab-rag/ # RAG pipeline (P4-3)
│ ├── kebab-app/ # facade (P0 시그니처 + P3-5 본체)
│ └── kebab-cli/ # binary (P0 → 핫픽스로 --config flag wiring 강화)
├── migrations/ # SQLite refinery V001/V002/V003
└── fixtures/ # 테스트 fixture 트리
```
---
## 빌드 + 실행
표준 경로는 `cargo install``~/.cargo/bin/kebab` 가 PATH 에 있는지만 확인하면 끝.
```bash

(칭찬) 설치 절이 표준 (cargo install --path) + 원격 (cargo install --git) + 검증 (which / --version) + 갱신 (--force) + 제거 + 데이터 정리 까지 한 사이클을 한 절에 정리. 특히 cargo uninstall kebab-cli 가 binary 만 지우고 데이터는 보존 — 사용자가 "왜 데이터가 남아있지?" 라고 묻기 전에 답을 가져옴. ~/.local/share/kebab 등 4 XDG path 명시도 정확.

(칭찬) 설치 절이 표준 (`cargo install --path`) + 원격 (`cargo install --git`) + 검증 (`which` / `--version`) + 갱신 (`--force`) + 제거 + **데이터 정리** 까지 한 사이클을 한 절에 정리. 특히 `cargo uninstall kebab-cli` 가 binary 만 지우고 데이터는 보존 — 사용자가 "왜 데이터가 남아있지?" 라고 묻기 전에 답을 가져옴. `~/.local/share/kebab` 등 4 XDG path 명시도 정확.
# build
cargo build --release
# 1) repo clone
git clone https://gitea.altair823.xyz/altair823-org/kebab.git
cd kebab
# 첫 실행 — XDG 경로에 config.toml 생성
./target/release/kebab init
# 2) binary 빌드 + 설치 (~/.cargo/bin/kebab)
cargo install --path crates/kebab-cli --locked
# config 손보고
${EDITOR:-vi} ~/.config/kebab/config.toml
# 색인
./target/release/kebab ingest
# 검색
./target/release/kebab search "Markdown chunking 규칙" --mode hybrid
# 질문 (Ollama 필요)
./target/release/kebab ask "내 KB 설계에서 저장소 전략은?"
# 3) PATH 확인 (아직 추가 안 했으면 ~/.bashrc / ~/.zshrc 에 추가)
which kebab # → /Users/<you>/.cargo/bin/kebab 같은 경로
kebab --version # → kebab 0.1.0
```
워크스페이스를 격리해서 직접 돌려보는 패턴은 [docs/SMOKE.md](docs/SMOKE.md) 참조 — `--config <path>` 로 임시 디렉토리에 격리된 KB 를 만들 수 있다.
git URL 직접 install 도 가능 (clone 없이):
---
```bash
cargo install --git https://gitea.altair823.xyz/altair823-org/kebab.git --bin kebab --locked
```
## 비-목표 (frozen design §11 / §0)
업데이트는 `git pull && cargo install --path crates/kebab-cli --locked --force` 또는 git URL 형식의 경우 `cargo install --git ... --force`.

(칭찬) Quick start 가 ./target/release/kebabkebab 로 narrow. 사용자가 PATH 통해 호출하는 일반 흐름. kebab doctor 한 줄도 추가 — 첫 ingest 가 실패할 때 trouble-shoot 첫 단계가 코드 옆에 있음.

(칭찬) Quick start 가 `./target/release/kebab` → `kebab` 로 narrow. 사용자가 PATH 통해 호출하는 일반 흐름. `kebab doctor` 한 줄도 추가 — 첫 ingest 가 실패할 때 trouble-shoot 첫 단계가 코드 옆에 있음.
- 다중 사용자 SaaS, K8s 배포, 원격 vector DB
- enterprise RBAC/ABAC, 실시간 협업
- 모든 파일 포맷의 완벽한 parsing
- agent 가 임의로 파일을 수정하는 자동화
- multi-workspace (P+ 후순위)
- LLM-as-judge eval (rule-based `must_contain` 만)
- visual embedding (CLIP) — P+
- desktop app `kebab://` protocol handler — P+
제거는 `cargo uninstall kebab-cli`. 이 명령은 binary 만 지우고 워크스페이스 데이터 (`~/.local/share/kebab/`, `~/.config/kebab/`) 는 그대로 남는다 — 데이터까지 정리하려면 `rm -rf ~/.local/share/kebab ~/.config/kebab ~/.cache/kebab ~/.local/state/kebab`.
---
## Quick start
```bash
# 첫 실행 — XDG 경로에 데이터 디렉토리 + config.toml 생성
kebab init
# config 손보고 — `[workspace] include` 에 *.md / *.png / *.pdf 등 추가, 모델 endpoint 등
${EDITOR:-vi} ~/.config/kebab/config.toml

(칭찬) 핵심 결정 표에 "Image caption" / "PDF parser" 두 행을 신설하면서 HOTFIXES 참조 (P6-2 / P7-3) 를 박아둠. spec literal (Tesseract default, chunker_version 단일값) 과 어긋나는 현실 동작 (Ollama-vision default, chunker_version per-medium) 이 README 만 봐도 정확. "왜 spec 그대로 안 했는지" 의 답은 한 클릭 (HOTFIXES) 떨어져 있어 README 본문은 가볍고 정직.

(칭찬) 핵심 결정 표에 "Image caption" / "PDF parser" 두 행을 신설하면서 HOTFIXES 참조 (P6-2 / P7-3) 를 박아둠. spec literal (Tesseract default, chunker_version 단일값) 과 어긋나는 현실 동작 (Ollama-vision default, chunker_version per-medium) 이 README 만 봐도 정확. "왜 spec 그대로 안 했는지" 의 답은 한 클릭 (HOTFIXES) 떨어져 있어 README 본문은 가볍고 정직.
# 색인 (Markdown / 이미지 / PDF 모두 한 번에)
kebab ingest

(칭찬) Mermaid flowchart 가 "사용자 → UI binary → facade → 4 layer (parse/chunker/embed/retrieve/RAG) → 저장소 + 외부" 의 한 페이지 mental model 을 표현. kebab-app 가 facade 라는 §8 의 boundary 가 그림으로 즉시 보임 + vision OCR/caption 의 점선 표기가 "같은 Ollama endpoint 가 두 용도로 쓰임" 을 적시. 이 한 다이어그램으로 README 가 사용자에게 약속한 "Quick start 시각화" 충족.

(칭찬) Mermaid flowchart 가 "사용자 → UI binary → facade → 4 layer (parse/chunker/embed/retrieve/RAG) → 저장소 + 외부" 의 한 페이지 mental model 을 표현. `kebab-app` 가 facade 라는 §8 의 boundary 가 그림으로 즉시 보임 + vision OCR/caption 의 점선 표기가 "같은 Ollama endpoint 가 두 용도로 쓰임" 을 적시. 이 한 다이어그램으로 README 가 사용자에게 약속한 "Quick start 시각화" 충족.

(suggestion / 미세) 마지막 "설치 없이 dev 흐름" 한 줄이 두 형태 (cargo run --release -p kebab-cli -- <subcommand>cargo build --release && ./target/release/kebab <subcommand>) 를 동시 제공. 두 흐름의 차이 (전자는 cargo 가 매번 dependency check, 후자는 binary 직접 실행 → 빠름) 가 한 줄 코멘트로 박혀 있으면 사용자가 어떤 걸 골라야 하는지 즉시 알 수 있음.

How to apply (선택): "전자는 cargo cache 갱신 자동, 후자는 binary 빠른 직접 실행 — 짧은 single-shot 은 전자, repeated 호출은 후자" 한 줄 추가. 본 PR scope 외 — follow-up 으로 처리 가능.

(suggestion / 미세) 마지막 "설치 없이 dev 흐름" 한 줄이 두 형태 (`cargo run --release -p kebab-cli -- <subcommand>` 와 `cargo build --release && ./target/release/kebab <subcommand>`) 를 동시 제공. 두 흐름의 차이 (전자는 cargo 가 매번 dependency check, 후자는 binary 직접 실행 → 빠름) 가 한 줄 코멘트로 박혀 있으면 사용자가 어떤 걸 골라야 하는지 즉시 알 수 있음. How to apply (선택): "전자는 cargo cache 갱신 자동, 후자는 binary 빠른 직접 실행 — 짧은 single-shot 은 전자, repeated 호출은 후자" 한 줄 추가. 본 PR scope 외 — follow-up 으로 처리 가능.
# 검색 (citation 의 source_span 이 매체별로 line / region / page)
kebab search "Markdown chunking 규칙" --mode hybrid
# 질문 (Ollama 필요, PDF 인용 시 page 번호 surface)
kebab ask "내 KB 설계에서 저장소 전략은?"
# Ratatui 셸 (Library 패널 — j/k 이동, f 필터, q 종료)
kebab tui
# 헬스 체크 (config 경로 / 데이터 디렉토리 쓰기 가능 여부)
kebab doctor
```
격리된 임시 워크스페이스로 돌려보는 절차는 [docs/SMOKE.md](docs/SMOKE.md) — `--config <path>` 로 분리. 이미지 / PDF fixture 가 필요하면 두 example 바이너리 (`cargo run --release --example gen_smoke_pdf -p kebab-parse-pdf` / `gen_smoke_png -p kebab-parse-image`) 로 시스템 dep 없이 in-tree 생성 가능.
설치 없이 dev 흐름으로 돌려볼 때는 `cargo run --release -p kebab-cli -- <subcommand>` 또는 `cargo build --release && ./target/release/kebab <subcommand>`.
## 명령
| 명령 | 동작 |
|------|------|
| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 |
| `kebab ingest [<path>]` | Markdown / 이미지 / PDF 색인 (idempotent) |
| `kebab search --mode {lexical,vector,hybrid} "<query>"` | 검색. hybrid는 RRF fusion, citation 포함 |
| `kebab list docs` | 색인된 문서 목록 |
| `kebab inspect doc <id>` / `kebab inspect chunk <id>` | raw record 보기 |
| `kebab ask "<query>"` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요 |
| `kebab doctor` | 설정/모델/DB 헬스 체크 |
| `kebab tui` | Ratatui 셸 (Library 패널 v1, search/ask/inspect 패널 진행 중) |
| `kebab eval run / compare` | golden query 회귀 측정 |
모든 명령에 `--json` 플래그. 출력은 frozen wire schema v1 (`schema_version` 항상 포함, 예: `ingest_report.v1`, `search_hit.v1`, `answer.v1`, `doctor.v1`).
## 논리 아키텍처
```mermaid
flowchart TB
user(["사용자"])
subgraph UI["UI binary"]
cli["kebab CLI"]
tui["kebab TUI"]

(칭찬) Phase status 표가 / 두 개에서 ⏸ (보류) + 🟡 (진행 중) 두 변종 추가. P8 의 "보류" 와 P9 의 "진행 중 1/5" 가 단순 보다 정확한 사용자 신호 — "P8 은 진행 안 함" / "P9 는 시작했지만 아직 다 안 됨" 두 다른 의미가 한 emoji 로 뭉개지지 않음. 미래 phase status 갱신 시 같은 변종 어휘로 일관.

(칭찬) Phase status 표가 ✅ / ⏳ 두 개에서 ⏸ (보류) + 🟡 (진행 중) 두 변종 추가. P8 의 "보류" 와 P9 의 "진행 중 1/5" 가 단순 ⏳ 보다 정확한 사용자 신호 — "P8 은 진행 안 함" / "P9 는 시작했지만 아직 다 안 됨" 두 다른 의미가 한 emoji 로 뭉개지지 않음. 미래 phase status 갱신 시 같은 변종 어휘로 일관.
end
subgraph App["Facade"]
app["kebab-app"]
end
subgraph Pipeline["도메인 + 파이프라인"]

(칭찬) "31 component task 중 17 완료" → "33 component task — P3-5 / P6-4 / P7-3 / 등 후속 추가 포함" 갱신이 정직. spec 시점에 정의된 31 개 + 머지 시점에 발견된 후속 wiring task 3 개를 합산. 미래 reader 가 "왜 31 인데 머지된 게 더 많지?" 를 묻지 않게.

(칭찬) "31 component task 중 17 완료" → "33 component task — P3-5 / P6-4 / P7-3 / 등 후속 추가 포함" 갱신이 정직. spec 시점에 정의된 31 개 + 머지 시점에 발견된 후속 wiring task 3 개를 합산. 미래 reader 가 "왜 31 인데 머지된 게 더 많지?" 를 묻지 않게.
parse["parse-md / parse-pdf / parse-image"]
chunker["chunker (md-heading-v1, pdf-page-v1)"]
embedder["embedder (fastembed multilingual-e5-small)"]
retriever["retriever (lexical / vector / hybrid RRF)"]
rag["RAG pipeline"]
end
subgraph Store["저장소"]
sqlite[("SQLite + FTS5")]
lance[("LanceDB")]
assets[("asset bytes")]
end
subgraph External["외부"]
fs[("workspace files")]
ollama[("Ollama HTTP")]
end

(칭찬) 비-목표 절 한 단락 압축. 이전의 8-bullet list 대신 한 줄로 "frozen 설계 §11 / §0 참조" 까지 cross-link. README 의 narrow scope (사용 surface 만) 와 일치하면서도 사용자에게 "이건 안 한다" 의 contract 는 보존.

(칭찬) 비-목표 절 한 단락 압축. 이전의 8-bullet list 대신 한 줄로 "frozen 설계 §11 / §0 참조" 까지 cross-link. README 의 narrow scope (사용 surface 만) 와 일치하면서도 사용자에게 "이건 안 한다" 의 contract 는 보존.
user --> cli
user --> tui
cli --> app
tui --> app
app --> parse
app --> chunker
app --> embedder
app --> retriever
app --> rag
fs --> parse
parse -. vision OCR / caption .-> ollama
parse --> sqlite
parse --> assets
chunker --> sqlite
embedder --> lance
retriever --> sqlite
retriever --> lance
rag --> retriever
rag --> ollama
```
`kebab-app` 가 facade — UI binary 가 store / parse / search / llm / rag 를 직접 참조하지 않는다 (frozen 설계 §8). 자세한 crate-level 의존성 + 디렉토리 + 핵심 기술 결정은 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
## Configuration
- `~/.config/kebab/config.toml``kebab init` 가 XDG 경로에 생성. `[workspace] include`, `[storage]`, `[chunking]`, `[models.embedding]`, `[models.llm]`, `[image.ocr]`, `[image.caption]`, `[search]`, `[rag]` 절.
- `--config <path>` flag — 임시 워크스페이스 / 격리 테스트 시 사용. CLI / TUI 모두 honor.
- `KEBAB_*` env — 일부 키 override (`KEBAB_RAG_SCORE_GATE`, `KEBAB_EVAL_GOLDEN`, `KEBAB_COMMIT_HASH` 등).
- XDG layout: `~/.config/kebab/`, `~/.local/share/kebab/`, `~/.cache/kebab/`, `~/.local/state/kebab/`.
config 예시는 [docs/SMOKE.md](docs/SMOKE.md) 의 `/tmp/kebab-smoke/config.toml` 블록 참조.
## 외부 AI 통합
`kebab``--json` 모드 + frozen wire schema v1 은 외부 자동화의 stable contract. 가능한 통합:
`--json` 출력 + frozen wire schema v1 stable contract. 통합 옵션:
1. **Claude Code / Codex skill**얇은 wrapper (`kebab search --json` / `kebab ask --json` 호출). ~50 lines.
2. **MCP server**`kebab-mcp` binary (stdio JSON-RPC) 가 `kebab-app` facade 1:1 노출. Claude Desktop / Cursor / Zed 등 공유.
3. **HTTP wrapper**`kebab serve --bind 127.0.0.1:7711` (P+, local-only 가치 깨므로 신중).
- **Claude Code / Codex skill** — `kebab search --json` / `kebab ask --json` 호출하는 ~50줄 wrapper.
- **MCP server** — stdio JSON-RPC `kebab-app` facade 1:1 노출.
- **HTTP wrapper** — `kebab serve --bind 127.0.0.1:7711` (P+, local-only 가치 신중).
---
## 비-목표
## 기여 / 작업 흐름
이 repo 는 단일 사용자 프로젝트지만 spec 변경 절차는 명문화되어 있다.
1. **frozen design 변경**`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 가 단일 contract. 변경 시 영향 받는 component task 모두 동시 갱신 필요. PR 1개로 묶기.
2. **새 component task 추가**`tasks/_template.md` 복사 후 `tasks/p<phase>/p<phase>-<n>-<name>.md` 생성. `contract_sections` 에 design doc 섹션 명시. `Allowed/Forbidden dependencies` 는 design §8 module-boundary 표 따름.
3. **구현** — component task 1개당 sub-agent 1세션 권장. `cargo test -p <crate>` + DoD 체크리스트 통과. PR 으로 머지.
4. **버전 변경**`parser_version` / `chunker_version` / `embedding_version` 등 변경은 design §9 의 cascade rule 따름. 영향 받는 record 는 재처리 필요.
5. **post-merge 핫픽스** — 머지 후 발견된 버그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md) 에 dated entry 추가 + 영향 받는 task spec 의 `Risks/notes` 에 cross-link 한 줄 추가. 원래 spec 본문은 frozen 으로 두고 HOTFIXES.md 가 live source of truth.
---
다중 사용자 SaaS / K8s / 원격 vector DB / enterprise RBAC / 실시간 협업 / 모든 파일 포맷의 완벽한 parsing / agent 임의 파일 수정 / multi-workspace / LLM-as-judge eval / CLIP 시각 embedding / `kebab://` protocol handler — frozen 설계 §11 / §0 참조.
## 라이선스
미정 (frozen design 에는 `MIT OR Apache-2.0` workspace.package 의 license 필드로 권장됨; P0 lock 시 결정).
---
`MIT OR Apache-2.0` (workspace `Cargo.toml``license` 필드).
## 참고
- 최초 설계 보고서: [kebab_local_rust_report.md](kebab_local_rust_report.md)
- Frozen design: [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](docs/superpowers/specs/2026-04-27-kebab-final-form-design.md)
- Task 분해 plan: [docs/superpowers/plans/2026-04-27-task-decomposition.md](docs/superpowers/plans/2026-04-27-task-decomposition.md)
- 진척도: [HANDOFF.md](HANDOFF.md)
- 아키텍처: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
- Frozen 설계: [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](docs/superpowers/specs/2026-04-27-kebab-final-form-design.md)
- Task 인덱스: [tasks/INDEX.md](tasks/INDEX.md)
- Post-merge 핫픽스 로그: [tasks/HOTFIXES.md](tasks/HOTFIXES.md)
- Smoke recipe: [docs/SMOKE.md](docs/SMOKE.md)
- 머지 후 hotfix 로그: [tasks/HOTFIXES.md](tasks/HOTFIXES.md)
- Smoke 절차: [docs/SMOKE.md](docs/SMOKE.md)

130
docs/ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,130 @@
# Architecture
> kebab 의 내부 구조 — crate 의존성, 디렉토리, 핵심 기술 결정. 사용자 사용법은 [README.md](../README.md), 진척도는 [HANDOFF.md](../HANDOFF.md), frozen 설계 계약은 [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](superpowers/specs/2026-04-27-kebab-final-form-design.md), 머지 후 발견된 deviation 은 [tasks/HOTFIXES.md](../tasks/HOTFIXES.md).
## 한 줄
Cargo workspace, 함수 호출 기반 모듈러 모놀리스. UI binary (`kebab-cli`, `kebab-tui`, 미래 `kebab-desktop`) 가 facade crate (`kebab-app`) 만 참조. 도메인 / 파이프라인 / 저장소 / 외부 어댑터가 명확한 boundary 로 분리.
## 핵심 기술 결정 (lock 됨)
| 결정 | 값 |
|------|-----|
| 언어 | Rust 2024 (resolver=3, edition 2024) |
| repo | Cargo workspace (single repo, 함수 호출 기반 모듈러 모놀리스) |
| 원본 저장 | filesystem + blake3 content-addressable copy (대용량은 reference + checksum) |
| metadata | SQLite + FTS5 (lexical search) |
| vector | LanceDB (embedded, model 별 분리 table) |
| Markdown parser | `pulldown-cmark` |
| embedding | `fastembed-rs` (`multilingual-e5-small`, 384d) |
| LLM | Ollama HTTP (default `qwen2.5:7b-instruct` ─ 사용자 환경에 맞춰 `gemma4:26b` 등으로 교체 가능) |
| 음성 ASR | `whisper.cpp` (via `whisper-rs`) — P8 보류, 시스템 dep brainstorm 후 |
| OCR | Ollama vision LM (default `gemma4:e4b`) — `OcrEngine` trait 으로 Tesseract / Apple Vision 등 future swap (HOTFIXES P6-2) |
| Image caption | Ollama vision LM, runtime gate `image.caption.enabled` (default OFF) |
| PDF parser | `lopdf` per-page 텍스트, `chunker_version = "pdf-page-v1"` 가 PDF 자산에 하드코딩 (HOTFIXES P7-3) |
| TUI | Ratatui + crossterm — P9-1 Library 패널, P9-2/3/4 진행 예정 |
| Desktop | Tauri 2 + `pdfjs-dist` (native PDF render backend 금지) — P9-5 |
| citation 형식 | URI fragment (`path#L12-L34` / `path#p=12` / `path#xywh=0,0,100,50`, W3C Media Fragments) |
| ID 생성 | `blake3(canonical_json(tuple))[..32]` hex |
| RRF fusion_score | `[0, 1]` 정규화 — `2 / (k_rrf + 1)` 로 나눠 mode 간 비교 가능 (post-merge hotfix) |
| layout | XDG (`~/.local/share/kebab/`, `~/.config/kebab/`, …) |
전체 frozen 설계는 [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](superpowers/specs/2026-04-27-kebab-final-form-design.md) 12 sections 참조.
## crate 의존성 그래프
```text
kebab-cli, kebab-tui, kebab-desktop
└─> kebab-app
├─> kebab-source-fs
├─> kebab-parse-md / kebab-parse-pdf / kebab-parse-image / kebab-parse-audio
│ └─> kebab-parse-types
├─> kebab-normalize
│ └─> kebab-parse-types
├─> kebab-chunk
├─> kebab-store-sqlite
├─> kebab-store-vector
├─> kebab-embed-local (kebab-embed trait crate)

(칭찬) crate 의존성 그래프 + 디렉토리 트리 + 핵심 결정 표 한 곳에 묶여 있어 "내부 구조" 의 single source. 핵심 결정 표가 P6-2 / P7-3 의 spec literal 과 다른 항목 (OCR Ollama-vision / chunker_version per-medium) 에 HOTFIXES 참조 박아둔 게 정직 — 미래 reader 가 "왜 spec 그대로 안 했지?" 를 한 클릭으로 찾음.

(칭찬) crate 의존성 그래프 + 디렉토리 트리 + 핵심 결정 표 한 곳에 묶여 있어 "내부 구조" 의 single source. 핵심 결정 표가 P6-2 / P7-3 의 spec literal 과 다른 항목 (OCR Ollama-vision / chunker_version per-medium) 에 HOTFIXES 참조 박아둔 게 정직 — 미래 reader 가 "왜 spec 그대로 안 했지?" 를 한 클릭으로 찾음.
├─> kebab-search
├─> kebab-llm-local (kebab-llm trait crate)
├─> kebab-rag
├─> kebab-eval
└─> kebab-config
└─> kebab-core (모두 의존)
```
UI → store/llm/parse 직접 의존 금지. 모든 user-facing 진입은 `kebab-app` facade 만 통한다 (frozen 설계 §8). `kebab-cli``--config <path>` flag 를 honor 하려면 `kebab_app::*_with_config(cfg, …)` companion 을 통해 Config 을 명시적으로 thread 하는 패턴 — 자세한 이유는 [tasks/HOTFIXES.md](../tasks/HOTFIXES.md) 의 `--config` 항목.
## 디렉토리 구조
```text
kebab/
├── README.md # 사용자 첫 stop (사용법 / Quick start / Mermaid)
├── HANDOFF.md # 진척도 (phase status / 다음 task)
├── kebab_local_rust_report.md # 최초 설계 보고서 (방향성 + 근거)
├── docs/
│ ├── ARCHITECTURE.md # 이 파일
│ ├── SMOKE.md # 로컬 워크스페이스 직접 돌려보는 절차
│ ├── superpowers/
│ │ ├── specs/
│ │ │ └── 2026-04-27-kebab-final-form-design.md # frozen design (12 sections)
│ │ └── plans/
│ │ └── 2026-04-27-task-decomposition.md # task 분해 implementation plan
│ └── wire-schema/v1/ # JSON Schema 7 (citation, search_hit, answer, …)
├── tasks/
│ ├── INDEX.md # phase 인덱스 + component task 트리
│ ├── HOTFIXES.md # post-merge dated fix 로그
│ ├── _template.md # task spec 작성 템플릿
│ ├── phase-0-skeleton.md … phase-9-ui.md # phase epic (high-level)
│ ├── p0/p0-1-skeleton.md # component task (1)
│ ├── p1/p1-1 … p1-6 # (6)
│ ├── p2/p2-1, p2-2 # (2)
│ ├── p3/p3-1 … p3-5 # (5 — p3-5 = app-wiring, post-spec 추가)
│ ├── p4/p4-1 … p4-3 # (3)
│ ├── p5/p5-1, p5-2 # (2)
│ ├── p6/p6-1 … p6-4 # (4 — p6-4 = image-ingest-wiring 후속 추가)
│ ├── p7/p7-1 … p7-3 # (3 — p7-3 = pdf-ingest-wiring 후속 추가)
│ ├── p8/p8-1, p8-2 # (2 — 보류)
│ └── p9/p9-1 … p9-5 # (5)
├── crates/
│ ├── kebab-core/ kebab-parse-types/ kebab-config/ # 도메인 + 설정 (P0)
│ ├── kebab-source-fs/ # 워크스페이스 walk + checksum (P1-1)
│ ├── kebab-parse-md/ # Markdown frontmatter + blocks (P1-2/3)
│ ├── kebab-normalize/ # ParsedBlock → CanonicalDocument (P1-4)
│ ├── kebab-chunk/ # heading-aware + pdf-page-v1 chunker (P1-5, P7-2)
│ ├── kebab-store-sqlite/ # SQLite + FTS5 (V001/V002/V003) (P1-6, P2-1, P3-3)
│ ├── kebab-search/ # Lexical + Vector + Hybrid retriever (P2-2, P3-4)
│ ├── kebab-embed/ kebab-embed-local/ # Embedder trait + fastembed adapter (P3-1, P3-2)
│ ├── kebab-store-vector/ # LanceDB VectorStore (P3-3, P7-3 follow-up)
│ ├── kebab-llm/ kebab-llm-local/ # LanguageModel trait + Ollama adapter (P4-1, P4-2)
│ ├── kebab-rag/ # RAG pipeline (P4-3)
│ ├── kebab-eval/ # golden query runner + metrics (P5-1, P5-2)
│ ├── kebab-parse-image/ # ImageExtractor + Ollama OCR + caption (P6)
│ ├── kebab-parse-pdf/ # lopdf per-page text extractor (P7-1)
│ ├── kebab-app/ # facade (P0 시그니처 + P3-5/P6-4/P7-3 본체)
│ ├── kebab-tui/ # Ratatui shell + Library 패널 (P9-1)
│ └── kebab-cli/ # binary (P0 → 핫픽스로 --config flag wiring 강화)
├── migrations/ # SQLite refinery V001/V002/V003
└── fixtures/ # 테스트 fixture 트리
```
## 외부 AI 통합
`--json` 플래그 가 모든 명령에 붙어 frozen wire schema v1 (`schema_version` 항상 포함) 을 출력. 외부 도구는 wire 만 의존하면 됨:
1. **Claude Code / Codex skill** — 얇은 wrapper (`kebab search --json` / `kebab ask --json` 호출). ~50 lines.
2. **MCP server**`kebab` 를 stdio MCP server 로 wrap. 모든 LLM client 가 자동으로 사용.
3. **HTTP wrapper**`kebab serve --bind 127.0.0.1:7711` (P+, local-only 가치 깨므로 신중).
wire schema 자체는 [docs/wire-schema/v1/](wire-schema/v1/).
## 비-목표 (frozen design §11 / §0)
- 다중 사용자 SaaS, K8s 배포, 원격 vector DB
- enterprise RBAC/ABAC, 실시간 협업
- 모든 파일 포맷의 완벽한 parsing
- agent 가 임의로 파일을 수정하는 자동화
- multi-workspace (P+ 후순위)
- LLM-as-judge eval (rule-based `must_contain` 만)
- visual embedding (CLIP) — P+
- desktop app `kebab://` protocol handler — P+