Files
kebab/README.md
altair823 7a49c8a29b feat(kebab-normalize): p9-fb-07 markdown title fallback chain
`kebab-normalize::derive_title(frontmatter_title, blocks, file_stem)` 가
다음 단계로 비어있지 않은 첫 결과를 사용:

1. frontmatter `title` (trim 후)
2. 첫 H1 텍스트
3. 첫 H2 텍스트
4. 첫 Paragraph (Quote / List / Code / Table / ImageRef 제외) 의 첫 80 자
5. 파일 stem (확장자 제외)
6. (sentinel) `"untitled"` — 위 다섯 단계가 모두 blank 인 병적 케이스

선택된 문자열은 NFC 정규화. 빈 문자열은 절대 반환하지 않음.

`build_canonical_document` 가 metadata lift 직후 helper 호출. 기존 단순
lift 로직 (metadata.user["title"] → CanonicalDocument.title) 은 fallback
chain 의 1 단계 입력으로 자리 이동.

`KEBAB_PARSE_MD_VERSION` 상수를 `pulldown-cmark-0.x` → `md-frontmatter-v2`
로 bump. parser_version 변경 → §4.2 doc_id 입력 변화 → 기존 markdown
doc 의 `doc_id` 갱신, 다음 ingest 시 idempotent upsert 로 자동 재처리
(design §9 cascade). `kebab-store-sqlite` 의 snapshot fixture 도 같은
literal 로 갱신.

기존 M7 정책 ("metadata.user[\"title\"] = '' 가 빈 title 로 lift") 은
폐기. 빈 문자열 입력은 fallback chain 을 타고 file stem 까지 떨어진다.
spec p9-fb-07 line 37: "빈 문자열 반환 금지".

테스트 (kebab-normalize):
- 8 개 단위 테스트 (각 fallback 단계 + NFC + sentinel)
- `build_canonical_document` 통합 테스트 2 개 (H1 / file stem)
- 기존 M7 테스트 2 개를 새 정책에 맞춰 갱신

문서:
- README: `kebab ingest` 행에 "title 자동 채움" 안내 + 기존 doc 도
  다음 ingest 에서 갱신
- HANDOFF: 2026-05-03 머지 후 발견 entry
- spec status: `planned` → `in_progress`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:22:34 +00:00

179 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# kebab — Local-first Knowledge Base
`kebab` 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지를 한 곳에 색인하고, 의미 검색 + page-단위 citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed) 에서 돌아간다. 대상 하드웨어: M4 48GB MacBook 1대, 사용자 1명.
## 사전 요구
- **Rust toolchain** ≥ 1.85 (workspace 가 edition 2024 + resolver 3 사용). [rustup](https://rustup.rs) 권장.
- **Ollama** — `kebab ask` 와 이미지 OCR/caption 가 사용. `https://ollama.com/download` 에서 설치 후 `ollama serve` 실행. 기본 LLM 은 gemma4 계열 (`ollama pull gemma4:e4b`) — OCR / caption 도 같은 family 라 모델 하나만 pull 하면 됨. 더 큰 variant 원하면 `gemma4:26b` 등으로 config override. config 의 `[models.llm].endpoint` 에 host:port 명시.
- **빌드 디스크** — 첫 빌드 시 `target/` 가 610 GB (Lance + DataFusion + fastembed). 여유 확인.
- **fastembed 모델** — 첫 `kebab ingest``multilingual-e5-small` (~470 MB) 자동 다운로드.
## 설치
표준 경로는 `cargo install``~/.cargo/bin/kebab` 가 PATH 에 있는지만 확인하면 끝.
```bash
# 1) repo clone
git clone https://gitea.altair823.xyz/altair823-org/kebab.git
cd kebab
# 2) binary 빌드 + 설치 (~/.cargo/bin/kebab)
cargo install --path crates/kebab-cli --locked
# 3) PATH 확인 (아직 추가 안 했으면 ~/.bashrc / ~/.zshrc 에 추가)
which kebab # → /Users/<you>/.cargo/bin/kebab 같은 경로
kebab --version # → kebab 0.1.0
```
git URL 직접 install 도 가능 (clone 없이):
```bash
cargo install --git https://gitea.altair823.xyz/altair823-org/kebab.git --bin kebab --locked
```
업데이트는 `git pull && cargo install --path crates/kebab-cli --locked --force` 또는 git URL 형식의 경우 `cargo install --git ... --force`.
제거는 `cargo uninstall kebab-cli`. 이 명령은 binary 만 지우고 워크스페이스 데이터는 그대로 남는다. 데이터까지 정리하려면 `kebab reset --all --yes` (config + data + cache + state 4 개 XDG 경로 모두 wipe — **irreversible**, 재시작 시 `kebab init` 다시 실행). 부분 wipe 는 `kebab reset --data-only` (config 보존), `kebab reset --vector-only` (Lance + `embedding_records` 만, 다음 ingest 가 re-embed) 등.
## Quick start
```bash
# 첫 실행 — XDG 경로에 데이터 디렉토리 + config.toml 생성
kebab init
# config 손보고 — `[workspace] include` 에 *.md / *.png / *.pdf 등 추가, 모델 endpoint 등
${EDITOR:-vi} ~/.config/kebab/config.toml
# 색인 (Markdown / 이미지 / PDF 모두 한 번에)
kebab ingest
# 검색 (citation 의 source_span 이 매체별로 line / region / page)
kebab search "Markdown chunking 규칙" --mode hybrid
# 질문 (Ollama 필요, PDF 인용 시 page 번호 surface)
kebab ask "내 KB 설계에서 저장소 전략은?"
# Ratatui 셸 (Library + Search + Ask + Inspect 패널, desktop 진행 중)
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). TTY 에서는 stderr 진행 바, non-TTY (CI / pipe) 는 stderr 한 줄씩, `--json` 은 stdout 에 `ingest_progress.v1` 라인 streaming 후 마지막에 `ingest_report.v1`. Ctrl-C 한 번이면 현재 asset 마무리 후 abort (부분 commit 보존, idempotent re-run), 두 번째 Ctrl-C 는 hard exit. Markdown title 이 frontmatter 에 없어도 첫 H1 → H2 → 첫 paragraph 80 자 → 파일명 순으로 자동 채움 (parser_version `md-frontmatter-v2`) — 기존 색인된 doc 도 다음 ingest 에서 새 title 로 갱신 |
| `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>" [--show-citations / --hide-citations]` | RAG 답변 + 근거 인용. 답변 후 `근거:` block 으로 full path / line range / score 한 줄씩 (default ON — `--hide-citations` 로 끄기, pipe 시 유용). 근거 부족 시 거절. Ollama 필요 |
| `kebab doctor` | 설정/모델/DB 헬스 체크 |
| `kebab tui` | Ratatui 셸 (Library + Search + Ask + Inspect 패널, desktop 진행 중). Library 에서 `r` 키로 background ingest 시작 — 화면 하단 status bar 가 진행 표시, 완료/abort 시 final 라인 잠시 유지 후 자동 hide. ingest 진행 중 `Esc` / `Ctrl-C` 가 cancel signal (그 외에는 quit). Ask 패널은 multi-turn — 같은 conversation 안에서 Q1/A1, Q2/A2 transcript 누적, 다음 질문이 이전 턴을 history 로 받아 답변. `Ctrl-L` 로 새 conversation 시작 |
| `kebab reset [--all / --data-only / --vector-only / --config-only] [--yes]` | XDG 데이터 wipe. **Irreversible.** TTY 면 confirm prompt, 아니면 `--yes` 필수. `--vector-only` 는 SQLite `embedding_records` 도 함께 truncate (orphan 방지) |
| `kebab eval run / compare` | golden query 회귀 측정 |
모든 명령에 `--json` 플래그. 출력은 frozen wire schema v1 (`schema_version` 항상 포함, 예: `ingest_report.v1`, `ingest_progress.v1`, `search_hit.v1`, `answer.v1`, `doctor.v1`, `reset_report.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 <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 통합
`--json` 출력 + frozen wire schema v1 가 stable contract. 통합 옵션:
- **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 가치 신중).
## 비-목표
다중 사용자 SaaS / K8s / 원격 vector DB / enterprise RBAC / 실시간 협업 / 모든 파일 포맷의 완벽한 parsing / agent 임의 파일 수정 / multi-workspace / LLM-as-judge eval / CLIP 시각 embedding / `kebab://` protocol handler — frozen 설계 §11 / §0 참조.
## 라이선스
`MIT OR Apache-2.0` (workspace `Cargo.toml``license` 필드).
## 참고
- 진척도: [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)
- 머지 후 hotfix 로그: [tasks/HOTFIXES.md](tasks/HOTFIXES.md)
- Smoke 절차: [docs/SMOKE.md](docs/SMOKE.md)