From 6bfa9795c669da5dc65037d8426c81e04a4499c8 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 2 May 2026 13:51:51 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20split=20user-facing=20docs=20by=20audie?= =?UTF-8?q?nce=20=E2=80=94=20README=20narrow=20+=20HANDOFF=20=EC=A7=84?= =?UTF-8?q?=EC=B2=99=EB=8F=84=20+=20ARCHITECTURE=20=EB=82=B4=EB=B6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 결정 (2026-05-02): \"README.md는 사용자가 가장 빠르게 이 앱을 사용할 수 있도록 하는 내용만 포함하자. mermaid 다이어그램으로 논리적인 아키텍처 다이어그램 하나 정도만 들어가면 충분할 것 같아\". 세 문서로 분리, audience 겹치지 않음: 1. **README.md (narrow)** — 사용자 first stop. Quick start / 명령 표 / Mermaid 1개 (논리 아키텍처) / Configuration pointer / 비-목표 / 라이선스. 진척도 / crate 그래프 / 디렉토리 트리 / 핵심 결정 표 모두 빠짐. 2. **HANDOFF.md (신규)** — phase-level 진척 dashboard. Phase status table, component count (33), \"다음 task 후보\" (P9-2/3/4/5, P8 보류), 머지 후 발견된 deviation 짧은 요약 (P3-5/P4-3 --config, P6-2 OCR, P6-3 caption, P7-2 chunk_id, P7-3 storage UNIQUE, P9-1 ratatui generic). 본문 detail 은 tasks/HOTFIXES.md. 3. **docs/ARCHITECTURE.md (신규)** — crate 의존성 그래프, 디렉토리 트리, 핵심 기술 결정 표, 외부 AI 통합 절. README 의 Mermaid 가 여기로 링크. CLAUDE.md 의 \"User-facing docs\" 절 갱신: - 세 문서 audience 분리 명시. - implementation PR 이 셋 다 sync 의무, spec PR 은 안 건드림. - 갱신 trigger 별 (CLI / TUI / Configuration / phase epic / crate 추가 / load-bearing deviation) 어느 문서를 손대는지 매핑. - Out of scope (HOTFIXES detail / version cascade / per-task spec rationale) 어디에도 안 적힘 명시. CLAUDE.md `## Project` 절도 새 문서 layout 반영. 18 crates → ~20 crates. Memory feedback 갱신 (`feedback_readme_sync_rule.md`) — 미래 conversation 에서 자동 적용. Co-Authored-By: Claude Opus 4.7 (1M context) --- CLAUDE.md | 39 ++++-- HANDOFF.md | 53 ++++++++ README.md | 288 +++++++++++++++---------------------------- docs/ARCHITECTURE.md | 130 +++++++++++++++++++ 4 files changed, 310 insertions(+), 200 deletions(-) create mode 100644 HANDOFF.md create mode 100644 docs/ARCHITECTURE.md diff --git a/CLAUDE.md b/CLAUDE.md index adbf26d..faa4ce0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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). -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,19 +81,31 @@ 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) +## User-facing docs (README + HANDOFF + ARCHITECTURE) -`README.md` is the user's first stop. Every PR that adds or changes user-visible surface MUST update the README in the same PR — never as a follow-up. The three surfaces: +Three sibling docs split the audience. Every implementation PR (`feat/*`) keeps them in sync; spec PRs (`spec/*`) don't touch any of the three. -- **CLI** — any new `kebab `, new flag, new `--json` field, or changed exit-code semantics. Update the command table near the top of the README and add an entry to the "빌드 + 실행" section if the new flow needs a different invocation pattern. -- **TUI** — any new pane, key binding, or run-time behavior visible to a `kebab tui` user. The README's TUI section names what the shell can do today; a new pane (search / ask / inspect / desktop) flips its row from ⏳ to ✅ and adds the key bindings users need to know. -- **Configuration** — any new `config.toml` field, env var (`KEBAB_*`), default value change, or XDG path. Update the config example block in `docs/SMOKE.md` AND the README's "핵심 결정" / "빌드 + 실행" rows that reference it. +**[README.md](README.md) — end user.** Stays narrow. The three surfaces a user touches: -The README is also the place where the phase status table lives — flip the relevant row's status (⏳ → ✅) when a phase epic completes. `tasks/INDEX.md` tracks per-component progress; the README tracks per-phase user-visible promises. +- **CLI** — new `kebab `, 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`. -Spec PRs (`spec/*` branches) do not touch the README. Implementation PRs (`feat/*`) do. If a feature ships behind a flag that's off-by-default, mention the flag explicitly so a user reading only the README knows the surface exists but is gated. +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. -Out of scope for the README: HOTFIXES detail (those live in `tasks/HOTFIXES.md`), version cascade mechanics (CLAUDE.md owns those), per-task spec rationale (those live in `tasks/p/`). +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/`). + +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 diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..d2e1b09 --- /dev/null +++ b/HANDOFF.md @@ -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 후보\" 만 담는다. + +## 한 줄 요약 + +P0–P5 + 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 로드맵 + +| 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 ` 를 honor 하려면 `kebab_app::*_with_config` companion 을 호출해야 함. 두 번 같은 모양으로 회귀했음. +- **P6-2 OCR 기본 엔진** — spec literal 의 Tesseract 가 시스템 dep 부담으로 거부됨, Ollama vision LM 으로 대체. `OcrEngine` trait 그대로라 future swap 가능. +- **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` 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). diff --git a/README.md b/README.md index 9c10af7..bee9f28 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,8 @@ # kebab — Local-first Knowledge Base -> **상태:** P0–P5 + P6 + P7 + P9-1 (Library 패널) 구현 완료 + post-merge hotfix 다수 (storage UNIQUE bug, vector store orphan cleanup, chunker_version per-medium 등). `kebab ingest` 가 markdown / image / PDF 모두 처리, `kebab search --mode {lexical,vector,hybrid}` / `kebab ask` 가 매체 가로질러 결과 + page citation 반환. `kebab tui` 가 Library 패널 제공 (search / ask / inspect 패널은 P9-2/3/4 예정). 다음 단계 = P9-2 (TUI search) 또는 P8 (audio — 시스템 dep brainstorm 필요해 보류). 자세한 진행 상황은 [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명. - ---- - -## 무엇인가 - -| 명령 | 동작 | 상태 | -|------|------|------| -| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 | ✅ P0 | -| `kebab ingest []` | Markdown / 이미지 / PDF 색인 (idempotent). 음성은 P8 (보류). | ✅ P3-5 / P6-4 / P7-3 | -| `kebab search --mode {lexical,vector,hybrid} ""` | 검색 — citation 포함, hybrid는 RRF fusion. PDF chunk 의 citation 은 page 단위. | ✅ P3-5 / P7-3 | -| `kebab list docs` | 색인된 문서 목록 | ✅ P3-5 | -| `kebab inspect doc ` / `kebab inspect chunk ` | raw record 보기 | ✅ P3-5 | -| `kebab ask ""` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요. | ✅ P4-3 | -| `kebab doctor` | 설정/모델/DB 헬스 체크 | ✅ P0 | -| `kebab tui` | Ratatui 셸 — Library 패널 (search/ask/inspect 패널은 P9-2/3/4 예정) | ✅ P9-1 | -| `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 (보류, 시스템 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) — P6-3 | -| PDF parser | `lopdf` per-page 텍스트 추출, `chunker_version = "pdf-page-v1"` 가 PDF 자산에 하드코딩 (HOTFIXES P7-3) — P7-1/7-2/7-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`, 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 ` 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 | ✅ 완료 (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 이후 병렬 가능. - -각 phase 는 component-level 단위로 더 분해되어 있다 (총 33 component task — P3-5 / P6-4 / P7-3 / 등 후속 추가 포함). 자세한 분해는 [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-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 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-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 트리 -``` - ---- - -## 빌드 + 실행 +## Quick start ```bash # build @@ -162,7 +11,7 @@ cargo build --release # 첫 실행 — XDG 경로에 config.toml 생성 ./target/release/kebab init -# config 손보고 — `[workspace] include` 에 *.md / *.png / *.pdf 등 추가, OCR 활성화 등 +# config 손보고 — `[workspace] include` 에 *.md / *.png / *.pdf 등 추가, 모델 endpoint 등 ${EDITOR:-vi} ~/.config/kebab/config.toml # 색인 (Markdown / 이미지 / PDF 모두 한 번에) @@ -178,56 +27,115 @@ ${EDITOR:-vi} ~/.config/kebab/config.toml ./target/release/kebab tui ``` -워크스페이스를 격리해서 직접 돌려보는 패턴은 [docs/SMOKE.md](docs/SMOKE.md) 참조 — `--config ` 로 임시 디렉토리에 격리된 KB 를 만들 수 있다. 이미지 / PDF fixture 가 필요하면 두 example 바이너리 (`cargo run --release --example gen_smoke_pdf -p kebab-parse-pdf` / `gen_smoke_png -p kebab-parse-image`) 로 reportlab / qpdf 같은 시스템 dep 없이 in-tree 생성 가능. +격리된 임시 워크스페이스로 돌려보는 절차는 [docs/SMOKE.md](docs/SMOKE.md) — `--config ` 로 분리. 이미지 / PDF fixture 가 필요하면 두 example 바이너리 (`cargo run --release --example gen_smoke_pdf -p kebab-parse-pdf` / `gen_smoke_png -p kebab-parse-image`) 로 시스템 dep 없이 in-tree 생성 가능. ---- +## 명령 -## 비-목표 (frozen design §11 / §0) +| 명령 | 동작 | +|------|------| +| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 | +| `kebab ingest []` | Markdown / 이미지 / PDF 색인 (idempotent) | +| `kebab search --mode {lexical,vector,hybrid} ""` | 검색. hybrid는 RRF fusion, citation 포함 | +| `kebab list docs` | 색인된 문서 목록 | +| `kebab inspect doc ` / `kebab inspect chunk ` | raw record 보기 | +| `kebab ask ""` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요 | +| `kebab doctor` | 설정/모델/DB 헬스 체크 | +| `kebab tui` | Ratatui 셸 (Library 패널 v1, search/ask/inspect 패널 진행 중) | +| `kebab eval run / compare` | golden query 회귀 측정 | -- 다중 사용자 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+ +모든 명령에 `--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"] + end + + subgraph App["Facade"] + app["kebab-app"] + end + + subgraph Pipeline["도메인 + 파이프라인"] + 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 + + 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 ` 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/p--.md` 생성. `contract_sections` 에 design doc 섹션 명시. `Allowed/Forbidden dependencies` 는 design §8 module-boundary 표 따름. -3. **구현** — component task 1개당 sub-agent 1세션 권장. `cargo test -p ` + 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) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..ad5dd35 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -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) + ├─> 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 ` 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+