altair823 3cd5117a7e feat(p3-3): kb-store-vector — LanceDB VectorStore + V003 embedding status
First VectorStore implementation. Per-model Lance tables under
config.storage.vector_dir, two-phase upsert (SQLite-pending → Lance
MergeInsert → SQLite-committed) with crash-safe retry, search via
cosine distance with the spec's score-shift (preserves negative
similarity ranking signal that clamping would crush).

V003 migration:
- Adds status (CHECK constraint pending|committed|tombstone, default
  pending) and vector_committed columns to embedding_records.
- BEFORE DELETE trigger on chunks flips dependent rows to tombstone.
  Currently overshadowed by V001's ON DELETE CASCADE FK; trigger UPDATE
  runs first then row vanishes via CASCADE. Spec-faithful tombstone
  preservation requires recreating embedding_records to drop the
  CASCADE — deferred to a P+ migration since no production rows exist
  yet (P3-3 is the first writer). V003 SQL comment explains.

LanceVectorStore:
- ensure_table is idempotent: opens existing or creates with the
  Arrow schema (chunk_id, doc_id, embedding FixedSizeList<Float32,
  dim>, model_id, embedding_version, text, heading_path, created_at).
- IndexId computed via id_for_index with collection="chunk_embeddings",
  index_kind="flat", params_hash = blake3(descriptor JSON). Schema
  bumps automatically rotate the IndexId.
- upsert: phase-1 INSERT OR REPLACE INTO embedding_records (status=
  'pending') in a single SQLite tx; phase-2 Lance MergeInsert keyed
  on chunk_id (idempotent re-run); phase-3 UPDATE status='committed',
  vector_committed=1. If phase-2 fails the rows stay 'pending' and
  the next upsert call retries idempotently.
- search joins embedding_records WHERE status='committed' so partial-
  write rows never surface. Cosine distance from Lance ∈ [0, 2] →
  similarity = 1 - distance ∈ [-1, 1] → score = (similarity + 1)/2 ∈
  [0, 1]. NaN coerced to 0 with tracing::warn. Filter by SearchFilters
  via SqliteStore::filter_chunks (added in this commit).
- Sync trait + async LanceDB bridged by an embedded current-thread
  tokio runtime. Doc-comment on the struct flags the "do NOT call
  from inside another tokio runtime" panic (block_on cannot nest).
  kb-app's job scheduler is sync today.

kb-store-sqlite additions:
- pub fn put_embedding_records_pending(&[EmbeddingRecordRow]) — phase-1
  INSERT OR REPLACE (status='pending', vector_committed=0).
- pub fn mark_embedding_records_committed(&[EmbeddingId]) — phase-3
  single UPDATE … WHERE embedding_id IN (?, ?, …) via
  params_from_iter, guarded by WHERE status='pending' so tombstones
  don't get clobbered.
- pub fn filter_chunks(&[ChunkId], &SearchFilters) → Vec<ChunkId>
  consolidates the JOIN against documents/document_tags/
  embedding_records + path_glob via globset. Lets kb-store-vector
  honor SearchFilters without depending on rusqlite or globset
  directly. (kb-search's filter logic is structurally different —
  interleaved with the FTS5 SELECT — so it stays as-is for now;
  consolidation is a P+ refactor.)
- 4 new unit tests cover the phase-1 round-trip, empty batch,
  replay reset of pending rows, and the WHERE-status-pending guard.

Tests:
- 9 lib unit tests in kb-store-vector covering paths/sanitization,
  arrow_batch dim validation + descriptor hash, bm25-style cosine
  score shift math.
- 4 new kb-store-sqlite unit tests on filter_chunks (committed-only,
  tags/lang/trust/path_glob, order preservation, empty input).
- 4 new kb-store-sqlite unit tests on the embedding_records helpers.
- 8 integration tests in upsert_search.rs and 1 snapshot test marked
  #[ignore = "requires AVX-capable hardware (LanceDB)"]. They invoke
  require_avx_or_panic() at the top of each body so a missing-AVX
  --ignored run fails loudly instead of silently passing. This dev
  host (qemu64 model) lacks AVX so these were NOT exercised end-to-
  end here — first CI lane on AVX hardware will validate them.
- Snapshot fixture tests/fixtures/vector/run-1.json is a placeholder
  with an _comment marker. Snapshot test panics until the placeholder
  is replaced via KB_UPDATE_SNAPSHOTS=1 on AVX hardware.
- Workspace 241 passed, 19 ignored, 0 failed; cargo clippy --workspace
  --all-targets -- -D warnings clean.

Allowed deps respected (kb-core, kb-config, kb-store-sqlite, lancedb,
arrow + arrow-array + arrow-schema, serde, serde_json, tracing,
thiserror) plus forced waivers — anyhow (trait return type), tokio
+ futures (LanceDB async-only API), blake3 (params_hash). rusqlite
and globset are NOT direct deps of kb-store-vector — confirmed via
cargo metadata --no-deps. rusqlite stays in [dev-dependencies] for
the test fixture seeder only.

Out of scope: IVF/PQ index tuning (P+), image vectors (P6), kb-app
embed_index orchestration (P3-4 facade).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 10:01:31 +00:00
2026-04-27 23:50:39 +00:00

kb — Local-first Knowledge Base

상태: spec 동결 단계. 코드 0줄. 30 component task spec 모두 작성/리뷰/머지 완료. 다음 단계 = P0 구현.

kb 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지 / 음성을 한 곳에 색인하고, 의미 검색 + citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed / whisper.cpp) 에서 돌아간다.

대상 하드웨어: M4 48GB MacBook 1대, 사용자 1명.


무엇인가

명령 동작
kb init XDG 경로에 데이터 디렉토리 + config.toml 생성
kb ingest <path> Markdown/PDF/이미지/음성 색인 (idempotent)
kb search "<query>" hybrid (lexical + vector) top-k 검색 — citation 포함
kb ask "<query>" RAG 답변 + 근거 인용. 근거 부족 시 거절
kb inspect doc/chunk <id> 디버그용 raw record 보기
kb doctor 설정/모델/DB 헬스 체크
kb eval run / compare golden query 회귀 측정 (chunker/모델 교체 평가)

기계 친화 모드: 모든 명령에 --json 플래그. 출력은 frozen wire schema v1 (schema_version 필드 항상 포함).


핵심 결정 (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:14b-instruct)
음성 ASR whisper.cpp (via whisper-rs)
OCR Tesseract (default) + macOS Apple Vision sidecar (feature gate)
TUI Ratatui + crossterm
Desktop Tauri 2 + pdfjs-dist (native PDF render backend 금지)
citation 형식 URI fragment (path#L12-L34, W3C Media Fragments)
ID 생성 blake3(canonical_json(tuple))[..32] hex
layout XDG (~/.local/share/kb/, ~/.config/kb/, …)

전체는 docs/superpowers/specs/2026-04-27-kb-final-form-design.md 참조.


의존성 그래프

kb-cli, kb-tui, kb-desktop
   └─> kb-app
         ├─> kb-source-fs
         ├─> kb-parse-md / kb-parse-pdf / kb-parse-image / kb-parse-audio
         │     └─> kb-parse-types
         ├─> kb-normalize
         │     └─> kb-parse-types
         ├─> kb-chunk
         ├─> kb-store-sqlite
         ├─> kb-store-vector
         ├─> kb-embed-local
         ├─> kb-search
         ├─> kb-llm-local
         ├─> kb-rag
         ├─> kb-eval
         └─> kb-config
              └─> kb-core (모두 의존)

UI → store/llm/parse 직접 의존 금지. 모든 user-facing 진입은 kb-app facade 만 통한다 (design §8).


Phase 로드맵

Phase 내용 핵심 산출 crate 선행
P0 Workspace 뼈대 + 도메인 계약 + ID recipe kb-core, kb-parse-types, kb-config, kb-app, kb-cli
P1 Markdown ingestion (walk → parse → chunk → SQLite) kb-source-fs, kb-parse-md, kb-normalize, kb-chunk, kb-store-sqlite P0
P2 SQLite FTS5 lexical 검색 + citation kb-search (lexical) P1
P3 Local embedding + LanceDB + hybrid (RRF) kb-embed, kb-embed-local, kb-store-vector, kb-search P2
P4 Local LLM + RAG + grounded answer kb-llm, kb-llm-local, kb-rag P3
P5 Golden query / regression eval kb-eval P4
P6 이미지 ingestion (OCR + caption) kb-parse-image P5
P7 PDF text + page citation kb-parse-pdf P5
P8 음성 transcription + timestamp citation kb-parse-audio P5
P9 TUI + desktop app kb-tui, kb-desktop P5

P0P5 직렬. P6P9 P5 이후 병렬 가능.

각 phase 는 component-level 단위로 더 분해되어 있다 (총 30 component task). 자세한 분해는 tasks/INDEX.md.


디렉토리 구조

kb/
├── README.md                                       # 이 파일
├── kb_local_rust_report.md                         # 최초 설계 보고서 (방향성 + 근거)
├── docs/
│   ├── superpowers/
│   │   ├── specs/
│   │   │   └── 2026-04-27-kb-final-form-design.md  # frozen design (12 sections)
│   │   └── plans/
│   │       └── 2026-04-27-task-decomposition.md    # task 분해 implementation plan
│   ├── spec/                                       # P0 에서 생성 — 도메인 모델 / ID / module-boundary 등 stub
│   └── wire-schema/v1/                             # P0 에서 생성 — JSON Schema 7개 (citation, search_hit, answer, …)
├── tasks/
│   ├── INDEX.md                                    # phase 인덱스 + component task 트리
│   ├── _template.md                                # task spec 작성 템플릿
│   ├── phase-0-skeleton.md … phase-9-ui.md         # phase epic (high-level)
│   ├── p0/p0-1-skeleton.md                         # component task (1 spec)
│   ├── p1/p1-1 … p1-6                              # component tasks (6)
│   ├── p2/p2-1, p2-2                               # (2)
│   ├── p3/p3-1 … p3-4                              # (4)
│   ├── 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/                                         # P0 에서 생성 — Rust crates
│   ├── kb-core/
│   ├── kb-parse-types/
│   ├── kb-config/
│   ├── kb-app/
│   └── kb-cli/
└── fixtures/                                       # P0 에서 생성 — 테스트 fixture 트리
    ├── markdown/  source-fs/  search/  embed/  vector/
    ├── rag/  eval/  image/  pdf/  audio/
    └── …

빌드 + 실행 (P0 완료 후)

# build
cargo build --release

# 첫 실행
./target/release/kb init

# config 손보고
${EDITOR:-vi} ~/.config/kb/config.toml

# 색인
./target/release/kb ingest ~/KnowledgeBase

# 검색
./target/release/kb search "Markdown chunking 규칙"

# 질문
./target/release/kb ask "내 KB 설계에서 저장소 전략은?"

현재는 P0 미시작 — 위 명령 모두 동작하지 않는다. spec 만 동결됐다.


비-목표 (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 kb:// protocol handler — P+

외부 AI 통합

kb--json 모드 + frozen wire schema v1 은 외부 자동화의 stable contract. 가능한 통합:

  1. Claude Code / Codex skill — 얇은 wrapper (kb search --json / kb ask --json 호출). ~50 lines.
  2. MCP serverkb-mcp binary (stdio JSON-RPC) 가 kb-app facade 를 1:1 노출. Claude Desktop / Cursor / Zed 등 공유.
  3. HTTP wrapperkb serve --bind 127.0.0.1:7711 (P+, local-only 가치 깨므로 신중).

기여 / 작업 흐름

이 repo 는 단일 사용자 프로젝트지만 spec 변경 절차는 명문화되어 있다.

  1. frozen design 변경docs/superpowers/specs/2026-04-27-kb-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 는 재처리 필요.

라이선스

미정 (frozen design 에는 MIT OR Apache-2.0 가 workspace.package 의 license 필드로 권장됨; P0 lock 시 결정).


참고

Description
Local-first Rust knowledge base — frozen design + 30 component task specs
Readme 42 MiB
Languages
Rust 100%