Files
kebab/docs/components
altair823 58386c4445 feat(chunk): md-heading-v2 — 예산 초과 청크 일반 분할 (oversize-chunk split)
v1 의 "블록 미분할" 한계를 일반화. 거대 list/code/table/paragraph 가 한 청크로
임베더 컨텍스트를 초과해 임베딩이 통째로 실패하던 문제를 해소한다. v2 는 v1 과
모든 출력이 동일하되, 청크의 실제 임베드 text 크기(`text.len()/3`)가
`max_chunk_tokens`(신규 config, byte/3, default 4000)를 넘는 청크만 줄(`\n`)
경계로, 단일 거대 줄은 UTF-8 char 경계로 잘라 각 조각이 예산 이하가 되게 한다.

- 판정 기준은 저장 `token_estimate` 가 아니라 실제 `text` 길이: ImageRef/AudioRef
  청크는 image-only 규약으로 token_estimate=0 이지만 OCR/caption text 는 클 수
  있다(빽빽한 스크린샷). 도그푸딩 일치 재테스트에서 이 image-OCR 구멍 발견·수정.
- 분할 조각 chunk_id 는 동일 block_ids 를 공유하므로 id-input 해시에 `#seg{i}`
  접미사로 충돌 회피(저장 policy_hash 는 bare — pdf-page-v1 의 `#L` 레시피 동형).
- `max_chunk_tokens` 는 v2 의 policy_hash 에만 fold(공유 ChunkPolicy 미변경 →
  코드/PDF 청커 cascade 무영향). 값 변경 시 markdown 만 재청크.
- 미분할 청크는 v1 과 byte-identical. `chunker_version` v1→v2 → 다음 plain
  `kebab ingest` 에서 markdown 1회 자동 재청크(--force 불필요, 코드/PDF 무영향).

동기: strict 임베더(AMD Lemonade `/api/embed`)는 oversize 입력을 truncate 아닌
거부(`500 too large`) — ollama 가 조용히 truncate 하던 걸 청커가 애초에 안 만들게.

검증(실험 KB, arctic-embed-l-v2 @ Lemonade): v2 전 620 중 2 doc(거대 jira list
블록) 임베드 실패 → v2 후 620/620 errors=0, 7114 청크 전부 ≤ 4000. 사용자 실
config 일치 재테스트(이미지 OCR + PDF OCR paddle-onnx ON)에서 image-OCR 구멍
발견·수정 후 dense 이미지 OCR 텍스트가 budget 초과 시 분할(token_estimate=0 →
1청크였던 것이 실제 text 기준 다중 청크로) 실증. frozen 설계 doc / frozen p1-5
spec 미변경(설계 §9 가 md-heading-v2 라벨 bump 를 변경 메커니즘으로 명시).

known limitation: PDF 는 별도 청커 pdf-page-v1.1 이라 이 split 미적용(후속 후보).

Cargo.toml 0.29.0 → 0.30.0 (신규 config 키 + 청커 동작 변경 = pre-1.0 minor +
도그푸딩 트리거). docs cascade: HOTFIXES / release-notes-v0.30.0-draft /
plan(2026-06-24) / normalize-chunk README / README / SMOKE / HANDOFF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
2026-06-24 01:10:53 +00:00
..

Components

책임 단위 그룹별 contributor 향 상세. 사용자 향 grand picture 는 README.md, 상위 crate 의존 그래프 + 디렉토리 구조 + locked-in 결정은 docs/ARCHITECTURE.md, 진척도는 HANDOFF.md, per-task spec 은 tasks/INDEX.md.

각 그룹 페이지는 동일 템플릿: 구성 crate / 구조 다이어그램 / data flow 다이어그램 / 주요 type / 외부 의존 / 핵심 결정 (HOTFIXES + spec 의 "왜") / 관련 spec / HOTFIXES.

그룹 wiring

12 그룹 간 호출/의존 흐름. 점선 = Foundation 이 모두에 의존. UI 는 App facade 만 통해 다른 그룹 도달.

flowchart TB
    subgraph Surfaces ["UI surface"]
        UI["UI<br/>(cli + tui)"]
    end
    subgraph Orchestration ["orchestration"]
        AppFacade["App facade<br/>(kebab-app)"]
        RAG["RAG"]
        Eval["Eval"]
    end
    subgraph IngestPipe ["ingest pipeline"]
        Source["Source"]
        Parse["Parse"]
        NormChunk["Normalize+Chunk"]
    end
    subgraph IndexQuery ["index + retrieval"]
        Embed["Embed"]
        Store["Store"]
        Search["Search"]
    end
    subgraph Generation ["generation"]
        LLM["LLM"]
    end
    Foundation["Foundation<br/>(core + parse-types + config)"]

    UI --> AppFacade
    AppFacade --> Source --> Parse --> NormChunk
    NormChunk --> Store
    NormChunk --> Embed --> Store
    AppFacade --> Embed
    AppFacade --> Search
    Search --> Store
    Search --> Embed
    AppFacade --> RAG
    RAG --> Search
    RAG --> LLM
    RAG --> Store
    AppFacade --> Eval
    Eval --> AppFacade
    Eval --> Store

    Foundation -.-> Source
    Foundation -.-> Parse
    Foundation -.-> NormChunk
    Foundation -.-> Embed
    Foundation -.-> Store
    Foundation -.-> Search
    Foundation -.-> LLM
    Foundation -.-> RAG
    Foundation -.-> AppFacade
    Foundation -.-> UI
    Foundation -.-> Eval

그룹 목록

그룹 역할 페이지
Foundation 도메인 type + 설정 + parser IR. 모든 crate 의 zero-dep 토대. foundation/
Source 워크스페이스 walk + .kebabignore + BLAKE3 checksum → RawAsset. source/
Parse bytes → ParsedBlock (md) 또는 CanonicalDocument (pdf/image). OCR + caption 어댑터. parse/
Normalize+Chunk ParsedBlockCanonicalDocument lift (markdown only) + 모든 미디어 → Vec<Chunk> (md/pdf 변종 chunker). normalize-chunk/
Store SQLite (V001-V005, FTS5, jobs, chat sessions) + LanceDB (per-model vector 테이블) two-phase write. store/
Embed Embedder trait + fastembed-rs 어댑터 (multilingual-e5-small 384d). embed/
Search lexical (FTS5 BM25) + vector (ANN) + hybrid (RRF) — Retriever trait 3 변종. search/
LLM LanguageModel trait + Ollama HTTP 어댑터 (gemma4:e4b default). streaming + cancel-safe. llm/
RAG retrieve → gate → pack → generate → cite-validate → persist 9 stage pipeline. multi-turn 지원. rag/
App facade kebab-app — 모든 UI binary 의 유일한 진입점. *_with_config companion 패턴. app-facade/
UI kebab-cli (--json wire envelope) + kebab-tui (4 패널 + Mode machine + cheatsheet). ui/
Eval golden query 회귀 평가 + run-vs-run compare. must_contain rule-based. eval/

진입 가이드

처음 읽는다면 (의존성 따라 bottom-up):

  1. Foundation — 다른 모든 페이지가 참조하는 type 정의. AssetId / DocumentId / Chunk / Citation / 5 version 등.
  2. Source → Parse → Normalize+Chunk → Store — ingest pipeline 흐름.
  3. Embed → Search — retrieval.
  4. LLM → RAG — generation.
  5. App facade — 위 전부 wiring.
  6. UI — facade 위.
  7. Eval — 독립.

특정 작업 별 진입:

  • 새 미디어 타입 추가 (예: epub) — Parse → Normalize+Chunk → Store (chunker_version) → App facade (라우팅).
  • 새 retrieval 모드 — Search → App facade (mode dispatch) → UI (--mode flag).
  • 새 LLM 어댑터 — LLM (trait crate, 새 type 금지) + 새 kebab-llm-<provider> crate → App facade (config provider switch).
  • TUI 신규 pane — UI 만. Mode + Theme + InputBuffer 재사용.

다이어그램 제약

각 그룹 페이지의 다이어그램은 mermaid (Gitea / GitHub 자동 렌더). 페이지 별 최소 2개 — 구조 (type/trait/struct 관계) + data flow (입출력 흐름). 실제 코드와 시그니처 일치 — 작성 시 crates/kebab-<name>/src/lib.rs 직접 읽음 (추측 금지).