From 911fb49550bfa796391ca435ee94b218ea26c290 Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 2 May 2026 03:28:08 +0000 Subject: [PATCH 1/3] =?UTF-8?q?refactor(rename):=20kb=20crates=20=E2=86=92?= =?UTF-8?q?=20kebab=20=E2=80=94=20Cargo=20packages,=20folders,=20Rust=20mo?= =?UTF-8?q?dules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 프로젝트 이름 `kb` → `kebab` rename 의 첫 단계. - workspace `Cargo.toml`: members `crates/kb-*` → `crates/kebab-*`, repository URL `altair823/kb` → `altair823/kebab`. - 18 crate 폴더 rename via `git mv` (history 보존). - 각 crate `Cargo.toml`: `name = "kb-*"` → `"kebab-*"`, path deps `../kb-*` → `../kebab-*`. - 모든 `.rs`: `kb_` snake-case 모듈 path 18 개 (`kb_core`, `kb_config`, `kb_app`, `kb_cli`, `kb_eval`, `kb_search`, `kb_chunk`, `kb_normalize`, `kb_source_fs`, `kb_parse_md`, `kb_parse_types`, `kb_store_sqlite`, `kb_store_vector`, `kb_embed`, `kb_embed_local`, `kb_llm`, `kb_llm_local`, `kb_rag`) → `kebab_` 일괄 sed (단어 경계 \\b 사용해 영어 문장 안의 "kb" 약어 미오염). CLI binary 이름 (`[[bin]] name = "kb"`), 환경변수 `KB_*`, XDG paths, tracing target, 그리고 docs sweep 은 다음 commit 에서. ## 검증 - `cargo check --workspace` clean — 모든 crate 빌드 통과 후 commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- Cargo.lock | 158 +++++++++--------- Cargo.toml | 38 ++--- crates/{kb-app => kebab-app}/Cargo.toml | 32 ++-- crates/{kb-app => kebab-app}/src/app.rs | 20 +-- .../src/doctor_signal.rs | 0 crates/{kb-app => kebab-app}/src/lib.rs | 110 ++++++------ crates/{kb-app => kebab-app}/src/logging.rs | 2 +- .../{kb-app => kebab-app}/tests/ask_smoke.rs | 10 +- .../{kb-app => kebab-app}/tests/common/mod.rs | 6 +- .../tests/fixtures/workspace/intro.md | 0 .../tests/fixtures/workspace/notes/cargo.md | 0 .../tests/fixtures/workspace/notes/python.md | 0 .../tests/ingest_lexical.rs | 40 ++--- .../tests/search_lexical.rs | 28 ++-- .../tests/search_vector.rs | 26 +-- crates/{kb-chunk => kebab-chunk}/Cargo.toml | 8 +- crates/{kb-chunk => kebab-chunk}/src/lib.rs | 2 +- .../src/md_heading_v1.rs | 16 +- .../tests/long_section_snapshot.rs | 8 +- crates/{kb-cli => kebab-cli}/Cargo.toml | 10 +- crates/{kb-cli => kebab-cli}/src/main.rs | 84 +++++----- crates/{kb-cli => kebab-cli}/src/wire.rs | 6 +- crates/{kb-config => kebab-config}/Cargo.toml | 4 +- crates/{kb-config => kebab-config}/src/lib.rs | 0 .../{kb-config => kebab-config}/src/paths.rs | 0 crates/{kb-core => kebab-core}/Cargo.toml | 2 +- crates/{kb-core => kebab-core}/src/answer.rs | 0 crates/{kb-core => kebab-core}/src/asset.rs | 0 crates/{kb-core => kebab-core}/src/chunk.rs | 0 .../{kb-core => kebab-core}/src/citation.rs | 0 .../{kb-core => kebab-core}/src/document.rs | 0 crates/{kb-core => kebab-core}/src/errors.rs | 0 crates/{kb-core => kebab-core}/src/ids.rs | 0 crates/{kb-core => kebab-core}/src/ingest.rs | 0 crates/{kb-core => kebab-core}/src/jobs.rs | 0 crates/{kb-core => kebab-core}/src/lib.rs | 0 crates/{kb-core => kebab-core}/src/media.rs | 0 .../{kb-core => kebab-core}/src/metadata.rs | 0 .../{kb-core => kebab-core}/src/normalize.rs | 0 crates/{kb-core => kebab-core}/src/search.rs | 0 crates/{kb-core => kebab-core}/src/traits.rs | 0 crates/{kb-core => kebab-core}/src/vector.rs | 0 .../{kb-core => kebab-core}/src/versions.rs | 0 .../Cargo.toml | 6 +- .../src/lib.rs | 12 +- .../tests/embed_model.rs | 14 +- .../tests/fixtures/embed/known-sentences.json | 0 crates/{kb-embed => kebab-embed}/Cargo.toml | 6 +- crates/{kb-embed => kebab-embed}/src/lib.rs | 6 +- crates/{kb-embed => kebab-embed}/src/mock.rs | 2 +- .../{kb-embed => kebab-embed}/tests/mock.rs | 2 +- .../tests/reexports.rs | 2 +- crates/{kb-eval => kebab-eval}/Cargo.toml | 10 +- crates/{kb-eval => kebab-eval}/src/compare.rs | 12 +- crates/{kb-eval => kebab-eval}/src/lib.rs | 2 +- crates/{kb-eval => kebab-eval}/src/loader.rs | 14 +- crates/{kb-eval => kebab-eval}/src/metrics.rs | 22 +-- crates/{kb-eval => kebab-eval}/src/runner.rs | 22 +-- crates/{kb-eval => kebab-eval}/src/types.rs | 8 +- .../tests/fixtures/eval/compare-1.json | 0 .../tests/fixtures/eval/run-1.json | 0 .../{kb-eval => kebab-eval}/tests/loader.rs | 2 +- .../tests/metrics_and_compare.rs | 12 +- .../{kb-eval => kebab-eval}/tests/runner.rs | 12 +- .../Cargo.toml | 8 +- .../src/error.rs | 0 .../src/lib.rs | 6 +- .../src/ollama.rs | 12 +- .../tests/construction.rs | 4 +- .../tests/integration.rs | 6 +- .../tests/streaming.rs | 6 +- crates/{kb-llm => kebab-llm}/Cargo.toml | 4 +- crates/{kb-llm => kebab-llm}/src/lib.rs | 6 +- crates/{kb-llm => kebab-llm}/src/mock.rs | 2 +- crates/{kb-llm => kebab-llm}/tests/mock.rs | 2 +- .../{kb-llm => kebab-llm}/tests/reexports.rs | 2 +- .../Cargo.toml | 8 +- .../src/lib.rs | 26 +-- .../tests/normalize_snapshot.rs | 6 +- .../Cargo.toml | 6 +- .../src/blocks.rs | 14 +- .../src/frontmatter.rs | 10 +- .../src/lib.rs | 0 .../tests/blocks_snapshots.rs | 6 +- .../tests/frontmatter_snapshots.rs | 6 +- .../Cargo.toml | 4 +- .../src/lib.rs | 8 +- crates/{kb-rag => kebab-rag}/Cargo.toml | 14 +- crates/{kb-rag => kebab-rag}/src/lib.rs | 2 +- crates/{kb-rag => kebab-rag}/src/pipeline.rs | 14 +- .../{kb-rag => kebab-rag}/tests/common/mod.rs | 8 +- .../{kb-rag => kebab-rag}/tests/pipeline.rs | 10 +- crates/{kb-search => kebab-search}/Cargo.toml | 14 +- .../src/citation_helper.rs | 4 +- .../{kb-search => kebab-search}/src/hybrid.rs | 6 +- .../src/lexical.rs | 16 +- crates/{kb-search => kebab-search}/src/lib.rs | 2 +- .../{kb-search => kebab-search}/src/vector.rs | 10 +- .../tests/common/mod.rs | 14 +- .../tests/fixtures/search/hybrid/run-1.json | 0 .../tests/fixtures/search/lexical/run-1.json | 0 .../tests/hybrid.rs | 4 +- .../tests/lexical.rs | 14 +- .../Cargo.toml | 6 +- .../src/connector.rs | 14 +- .../src/hash.rs | 2 +- .../src/lib.rs | 0 .../src/media.rs | 2 +- .../src/walker.rs | 0 .../tests/snapshot_tree1.rs | 6 +- .../tests/symlink_cycle.rs | 6 +- .../Cargo.toml | 12 +- .../snapshots/ingest_report.snapshot.json | 0 .../src/answers.rs | 4 +- .../src/documents.rs | 132 +++++++-------- .../src/embeddings.rs | 2 +- .../src/error.rs | 0 .../src/eval.rs | 2 +- .../src/filters.rs | 18 +- .../src/fts.rs | 0 .../src/jobs.rs | 62 +++---- .../src/lib.rs | 2 +- .../src/schema.rs | 0 .../src/store.rs | 26 +-- .../tests/asset_writer.rs | 6 +- .../tests/common/mod.rs | 2 +- .../tests/contract_roundtrip.rs | 10 +- .../tests/fts.rs | 2 +- .../tests/idempotency.rs | 14 +- .../tests/ingest_report_snapshot.rs | 4 +- .../tests/jobs.rs | 4 +- .../tests/list_docs.rs | 6 +- .../tests/migration.rs | 2 +- .../Cargo.toml | 8 +- .../src/arrow_batch.rs | 4 +- .../src/lib.rs | 2 +- .../src/paths.rs | 2 +- .../src/store.rs | 14 +- .../tests/common/mod.rs | 8 +- .../tests/fixtures/vector/run-1.json | 0 .../tests/snapshot.rs | 2 +- .../tests/upsert_search.rs | 6 +- tasks/phase-5-evaluation.md | 2 +- 143 files changed, 727 insertions(+), 727 deletions(-) rename crates/{kb-app => kebab-app}/Cargo.toml (51%) rename crates/{kb-app => kebab-app}/src/app.rs (96%) rename crates/{kb-app => kebab-app}/src/doctor_signal.rs (100%) rename crates/{kb-app => kebab-app}/src/lib.rs (90%) rename crates/{kb-app => kebab-app}/src/logging.rs (95%) rename crates/{kb-app => kebab-app}/tests/ask_smoke.rs (82%) rename crates/{kb-app => kebab-app}/tests/common/mod.rs (96%) rename crates/{kb-app => kebab-app}/tests/fixtures/workspace/intro.md (100%) rename crates/{kb-app => kebab-app}/tests/fixtures/workspace/notes/cargo.md (100%) rename crates/{kb-app => kebab-app}/tests/fixtures/workspace/notes/python.md (100%) rename crates/{kb-app => kebab-app}/tests/ingest_lexical.rs (81%) rename crates/{kb-app => kebab-app}/tests/search_lexical.rs (61%) rename crates/{kb-app => kebab-app}/tests/search_vector.rs (75%) rename crates/{kb-chunk => kebab-chunk}/Cargo.toml (84%) rename crates/{kb-chunk => kebab-chunk}/src/lib.rs (91%) rename crates/{kb-chunk => kebab-chunk}/src/md_heading_v1.rs (98%) rename crates/{kb-chunk => kebab-chunk}/tests/long_section_snapshot.rs (97%) rename crates/{kb-cli => kebab-cli}/Cargo.toml (82%) rename crates/{kb-cli => kebab-cli}/src/main.rs (82%) rename crates/{kb-cli => kebab-cli}/src/wire.rs (97%) rename crates/{kb-config => kebab-config}/Cargo.toml (87%) rename crates/{kb-config => kebab-config}/src/lib.rs (100%) rename crates/{kb-config => kebab-config}/src/paths.rs (100%) rename crates/{kb-core => kebab-core}/Cargo.toml (96%) rename crates/{kb-core => kebab-core}/src/answer.rs (100%) rename crates/{kb-core => kebab-core}/src/asset.rs (100%) rename crates/{kb-core => kebab-core}/src/chunk.rs (100%) rename crates/{kb-core => kebab-core}/src/citation.rs (100%) rename crates/{kb-core => kebab-core}/src/document.rs (100%) rename crates/{kb-core => kebab-core}/src/errors.rs (100%) rename crates/{kb-core => kebab-core}/src/ids.rs (100%) rename crates/{kb-core => kebab-core}/src/ingest.rs (100%) rename crates/{kb-core => kebab-core}/src/jobs.rs (100%) rename crates/{kb-core => kebab-core}/src/lib.rs (100%) rename crates/{kb-core => kebab-core}/src/media.rs (100%) rename crates/{kb-core => kebab-core}/src/metadata.rs (100%) rename crates/{kb-core => kebab-core}/src/normalize.rs (100%) rename crates/{kb-core => kebab-core}/src/search.rs (100%) rename crates/{kb-core => kebab-core}/src/traits.rs (100%) rename crates/{kb-core => kebab-core}/src/vector.rs (100%) rename crates/{kb-core => kebab-core}/src/versions.rs (100%) rename crates/{kb-embed-local => kebab-embed-local}/Cargo.toml (85%) rename crates/{kb-embed-local => kebab-embed-local}/src/lib.rs (97%) rename crates/{kb-embed-local => kebab-embed-local}/tests/embed_model.rs (96%) rename crates/{kb-embed-local => kebab-embed-local}/tests/fixtures/embed/known-sentences.json (100%) rename crates/{kb-embed => kebab-embed}/Cargo.toml (90%) rename crates/{kb-embed => kebab-embed}/src/lib.rs (96%) rename crates/{kb-embed => kebab-embed}/src/mock.rs (98%) rename crates/{kb-embed => kebab-embed}/tests/mock.rs (99%) rename crates/{kb-embed => kebab-embed}/tests/reexports.rs (99%) rename crates/{kb-eval => kebab-eval}/Cargo.toml (83%) rename crates/{kb-eval => kebab-eval}/src/compare.rs (98%) rename crates/{kb-eval => kebab-eval}/src/lib.rs (94%) rename crates/{kb-eval => kebab-eval}/src/loader.rs (96%) rename crates/{kb-eval => kebab-eval}/src/metrics.rs (98%) rename crates/{kb-eval => kebab-eval}/src/runner.rs (93%) rename crates/{kb-eval => kebab-eval}/src/types.rs (92%) rename crates/{kb-eval => kebab-eval}/tests/fixtures/eval/compare-1.json (100%) rename crates/{kb-eval => kebab-eval}/tests/fixtures/eval/run-1.json (100%) rename crates/{kb-eval => kebab-eval}/tests/loader.rs (98%) rename crates/{kb-eval => kebab-eval}/tests/metrics_and_compare.rs (98%) rename crates/{kb-eval => kebab-eval}/tests/runner.rs (98%) rename crates/{kb-llm-local => kebab-llm-local}/Cargo.toml (91%) rename crates/{kb-llm-local => kebab-llm-local}/src/error.rs (100%) rename crates/{kb-llm-local => kebab-llm-local}/src/lib.rs (91%) rename crates/{kb-llm-local => kebab-llm-local}/src/ollama.rs (98%) rename crates/{kb-llm-local => kebab-llm-local}/tests/construction.rs (94%) rename crates/{kb-llm-local => kebab-llm-local}/tests/integration.rs (92%) rename crates/{kb-llm-local => kebab-llm-local}/tests/streaming.rs (99%) rename crates/{kb-llm => kebab-llm}/Cargo.toml (90%) rename crates/{kb-llm => kebab-llm}/src/lib.rs (94%) rename crates/{kb-llm => kebab-llm}/src/mock.rs (99%) rename crates/{kb-llm => kebab-llm}/tests/mock.rs (99%) rename crates/{kb-llm => kebab-llm}/tests/reexports.rs (99%) rename crates/{kb-normalize => kebab-normalize}/Cargo.toml (85%) rename crates/{kb-normalize => kebab-normalize}/src/lib.rs (97%) rename crates/{kb-normalize => kebab-normalize}/tests/normalize_snapshot.rs (97%) rename crates/{kb-parse-md => kebab-parse-md}/Cargo.toml (92%) rename crates/{kb-parse-md => kebab-parse-md}/src/blocks.rs (99%) rename crates/{kb-parse-md => kebab-parse-md}/src/frontmatter.rs (99%) rename crates/{kb-parse-md => kebab-parse-md}/src/lib.rs (100%) rename crates/{kb-parse-md => kebab-parse-md}/tests/blocks_snapshots.rs (95%) rename crates/{kb-parse-md => kebab-parse-md}/tests/frontmatter_snapshots.rs (96%) rename crates/{kb-parse-types => kebab-parse-types}/Cargo.toml (82%) rename crates/{kb-parse-types => kebab-parse-types}/src/lib.rs (92%) rename crates/{kb-rag => kebab-rag}/Cargo.toml (70%) rename crates/{kb-rag => kebab-rag}/src/lib.rs (92%) rename crates/{kb-rag => kebab-rag}/src/pipeline.rs (98%) rename crates/{kb-rag => kebab-rag}/tests/common/mod.rs (97%) rename crates/{kb-rag => kebab-rag}/tests/pipeline.rs (99%) rename crates/{kb-search => kebab-search}/Cargo.toml (79%) rename crates/{kb-search => kebab-search}/src/citation_helper.rs (95%) rename crates/{kb-search => kebab-search}/src/hybrid.rs (99%) rename crates/{kb-search => kebab-search}/src/lexical.rs (98%) rename crates/{kb-search => kebab-search}/src/lib.rs (94%) rename crates/{kb-search => kebab-search}/src/vector.rs (98%) rename crates/{kb-search => kebab-search}/tests/common/mod.rs (96%) rename crates/{kb-search => kebab-search}/tests/fixtures/search/hybrid/run-1.json (100%) rename crates/{kb-search => kebab-search}/tests/fixtures/search/lexical/run-1.json (100%) rename crates/{kb-search => kebab-search}/tests/hybrid.rs (99%) rename crates/{kb-search => kebab-search}/tests/lexical.rs (98%) rename crates/{kb-source-fs => kebab-source-fs}/Cargo.toml (84%) rename crates/{kb-source-fs => kebab-source-fs}/src/connector.rs (97%) rename crates/{kb-source-fs => kebab-source-fs}/src/hash.rs (98%) rename crates/{kb-source-fs => kebab-source-fs}/src/lib.rs (100%) rename crates/{kb-source-fs => kebab-source-fs}/src/media.rs (98%) rename crates/{kb-source-fs => kebab-source-fs}/src/walker.rs (100%) rename crates/{kb-source-fs => kebab-source-fs}/tests/snapshot_tree1.rs (97%) rename crates/{kb-source-fs => kebab-source-fs}/tests/symlink_cycle.rs (98%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/Cargo.toml (85%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/snapshots/ingest_report.snapshot.json (100%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/answers.rs (97%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/documents.rs (86%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/embeddings.rs (99%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/error.rs (100%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/eval.rs (99%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/filters.rs (97%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/fts.rs (100%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/jobs.rs (88%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/lib.rs (94%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/schema.rs (100%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/src/store.rs (94%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/asset_writer.rs (97%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/common/mod.rs (98%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/contract_roundtrip.rs (96%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/fts.rs (99%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/idempotency.rs (95%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/ingest_report_snapshot.rs (97%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/jobs.rs (96%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/list_docs.rs (97%) rename crates/{kb-store-sqlite => kebab-store-sqlite}/tests/migration.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/Cargo.toml (93%) rename crates/{kb-store-vector => kebab-store-vector}/src/arrow_batch.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/src/lib.rs (94%) rename crates/{kb-store-vector => kebab-store-vector}/src/paths.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/src/store.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/tests/common/mod.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/tests/fixtures/vector/run-1.json (100%) rename crates/{kb-store-vector => kebab-store-vector}/tests/snapshot.rs (98%) rename crates/{kb-store-vector => kebab-store-vector}/tests/upsert_search.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index 090a277..43199b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3366,27 +3366,27 @@ dependencies = [ ] [[package]] -name = "kb-app" +name = "kebab-app" version = "0.1.0" dependencies = [ "anyhow", "blake3", "dirs 5.0.1", - "kb-chunk", - "kb-config", - "kb-core", - "kb-embed", - "kb-embed-local", - "kb-llm", - "kb-llm-local", - "kb-normalize", - "kb-parse-md", - "kb-parse-types", - "kb-rag", - "kb-search", - "kb-source-fs", - "kb-store-sqlite", - "kb-store-vector", + "kebab-chunk", + "kebab-config", + "kebab-core", + "kebab-embed", + "kebab-embed-local", + "kebab-llm", + "kebab-llm-local", + "kebab-normalize", + "kebab-parse-md", + "kebab-parse-types", + "kebab-rag", + "kebab-search", + "kebab-source-fs", + "kebab-store-sqlite", + "kebab-store-vector", "rusqlite", "serde", "serde_json", @@ -3399,14 +3399,14 @@ dependencies = [ ] [[package]] -name = "kb-chunk" +name = "kebab-chunk" version = "0.1.0" dependencies = [ "anyhow", "blake3", - "kb-core", - "kb-normalize", - "kb-parse-md", + "kebab-core", + "kebab-normalize", + "kebab-parse-md", "serde_json", "serde_json_canonicalizer", "time", @@ -3414,32 +3414,32 @@ dependencies = [ ] [[package]] -name = "kb-cli" +name = "kebab-cli" version = "0.1.0" dependencies = [ "anyhow", "clap", - "kb-app", - "kb-config", - "kb-core", - "kb-eval", + "kebab-app", + "kebab-config", + "kebab-core", + "kebab-eval", "serde_json", ] [[package]] -name = "kb-config" +name = "kebab-config" version = "0.1.0" dependencies = [ "anyhow", "dirs 5.0.1", - "kb-core", + "kebab-core", "serde", "serde_json", "toml", ] [[package]] -name = "kb-core" +name = "kebab-core" version = "0.1.0" dependencies = [ "anyhow", @@ -3453,13 +3453,13 @@ dependencies = [ ] [[package]] -name = "kb-embed" +name = "kebab-embed" version = "0.1.0" dependencies = [ "anyhow", "blake3", - "kb-config", - "kb-core", + "kebab-config", + "kebab-core", "proptest", "serde", "thiserror 2.0.18", @@ -3467,27 +3467,27 @@ dependencies = [ ] [[package]] -name = "kb-embed-local" +name = "kebab-embed-local" version = "0.1.0" dependencies = [ "anyhow", "fastembed", - "kb-config", - "kb-embed", + "kebab-config", + "kebab-embed", "serde_json", "tempfile", "tracing", ] [[package]] -name = "kb-eval" +name = "kebab-eval" version = "0.1.0" dependencies = [ "anyhow", - "kb-app", - "kb-config", - "kb-core", - "kb-store-sqlite", + "kebab-app", + "kebab-config", + "kebab-core", + "kebab-store-sqlite", "rusqlite", "serde", "serde_json", @@ -3499,22 +3499,22 @@ dependencies = [ ] [[package]] -name = "kb-llm" +name = "kebab-llm" version = "0.1.0" dependencies = [ "anyhow", - "kb-core", + "kebab-core", "proptest", ] [[package]] -name = "kb-llm-local" +name = "kebab-llm-local" version = "0.1.0" dependencies = [ "anyhow", - "kb-config", - "kb-core", - "kb-llm", + "kebab-config", + "kebab-core", + "kebab-llm", "reqwest", "serde", "serde_json", @@ -3525,13 +3525,13 @@ dependencies = [ ] [[package]] -name = "kb-normalize" +name = "kebab-normalize" version = "0.1.0" dependencies = [ "anyhow", - "kb-core", - "kb-parse-md", - "kb-parse-types", + "kebab-core", + "kebab-parse-md", + "kebab-parse-types", "serde", "serde_json", "time", @@ -3540,12 +3540,12 @@ dependencies = [ ] [[package]] -name = "kb-parse-md" +name = "kebab-parse-md" version = "0.1.0" dependencies = [ "anyhow", - "kb-core", - "kb-parse-types", + "kebab-core", + "kebab-parse-types", "lingua", "pulldown-cmark", "serde", @@ -3557,24 +3557,24 @@ dependencies = [ ] [[package]] -name = "kb-parse-types" +name = "kebab-parse-types" version = "0.1.0" dependencies = [ - "kb-core", + "kebab-core", "serde", ] [[package]] -name = "kb-rag" +name = "kebab-rag" version = "0.1.0" dependencies = [ "anyhow", "blake3", - "kb-config", - "kb-core", - "kb-llm", - "kb-search", - "kb-store-sqlite", + "kebab-config", + "kebab-core", + "kebab-llm", + "kebab-search", + "kebab-store-sqlite", "regex", "rusqlite", "serde", @@ -3586,16 +3586,16 @@ dependencies = [ ] [[package]] -name = "kb-search" +name = "kebab-search" version = "0.1.0" dependencies = [ "anyhow", "globset", - "kb-config", - "kb-core", - "kb-embed", - "kb-store-sqlite", - "kb-store-vector", + "kebab-config", + "kebab-core", + "kebab-embed", + "kebab-store-sqlite", + "kebab-store-vector", "rusqlite", "serde_json", "tempfile", @@ -3604,14 +3604,14 @@ dependencies = [ ] [[package]] -name = "kb-source-fs" +name = "kebab-source-fs" version = "0.1.0" dependencies = [ "anyhow", "blake3", "ignore", - "kb-config", - "kb-core", + "kebab-config", + "kebab-core", "serde", "serde_json", "tempfile", @@ -3621,17 +3621,17 @@ dependencies = [ ] [[package]] -name = "kb-store-sqlite" +name = "kebab-store-sqlite" version = "0.1.0" dependencies = [ "anyhow", "blake3", "globset", - "kb-chunk", - "kb-config", - "kb-core", - "kb-normalize", - "kb-parse-md", + "kebab-chunk", + "kebab-config", + "kebab-core", + "kebab-normalize", + "kebab-parse-md", "refinery", "rusqlite", "serde_json", @@ -3642,7 +3642,7 @@ dependencies = [ ] [[package]] -name = "kb-store-vector" +name = "kebab-store-vector" version = "0.1.0" dependencies = [ "anyhow", @@ -3651,9 +3651,9 @@ dependencies = [ "arrow-schema", "blake3", "futures", - "kb-config", - "kb-core", - "kb-store-sqlite", + "kebab-config", + "kebab-core", + "kebab-store-sqlite", "lancedb", "rusqlite", "serde", diff --git a/Cargo.toml b/Cargo.toml index 91a6252..b2ece6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,31 @@ [workspace] resolver = "3" members = [ - "crates/kb-core", - "crates/kb-parse-types", - "crates/kb-config", - "crates/kb-source-fs", - "crates/kb-parse-md", - "crates/kb-normalize", - "crates/kb-chunk", - "crates/kb-store-sqlite", - "crates/kb-store-vector", - "crates/kb-search", - "crates/kb-embed", - "crates/kb-embed-local", - "crates/kb-llm", - "crates/kb-llm-local", - "crates/kb-rag", - "crates/kb-app", - "crates/kb-cli", - "crates/kb-eval", + "crates/kebab-core", + "crates/kebab-parse-types", + "crates/kebab-config", + "crates/kebab-source-fs", + "crates/kebab-parse-md", + "crates/kebab-normalize", + "crates/kebab-chunk", + "crates/kebab-store-sqlite", + "crates/kebab-store-vector", + "crates/kebab-search", + "crates/kebab-embed", + "crates/kebab-embed-local", + "crates/kebab-llm", + "crates/kebab-llm-local", + "crates/kebab-rag", + "crates/kebab-app", + "crates/kebab-cli", + "crates/kebab-eval", ] [workspace.package] edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" -repository = "https://github.com/altair823/kb" +repository = "https://github.com/altair823/kebab" version = "0.1.0" [workspace.dependencies] diff --git a/crates/kb-app/Cargo.toml b/crates/kebab-app/Cargo.toml similarity index 51% rename from crates/kb-app/Cargo.toml rename to crates/kebab-app/Cargo.toml index 9fb7dcd..e8fae60 100644 --- a/crates/kb-app/Cargo.toml +++ b/crates/kebab-app/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-app" +name = "kebab-app" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,21 +8,21 @@ repository = { workspace = true } description = "Facade — orchestrates components for kb-cli/tui/desktop" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-source-fs = { path = "../kb-source-fs" } -kb-parse-md = { path = "../kb-parse-md" } -kb-parse-types = { path = "../kb-parse-types" } -kb-normalize = { path = "../kb-normalize" } -kb-chunk = { path = "../kb-chunk" } -kb-store-sqlite = { path = "../kb-store-sqlite" } -kb-store-vector = { path = "../kb-store-vector" } -kb-search = { path = "../kb-search" } -kb-embed = { path = "../kb-embed" } -kb-embed-local = { path = "../kb-embed-local" } -kb-llm = { path = "../kb-llm" } -kb-llm-local = { path = "../kb-llm-local" } -kb-rag = { path = "../kb-rag" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-source-fs = { path = "../kebab-source-fs" } +kebab-parse-md = { path = "../kebab-parse-md" } +kebab-parse-types = { path = "../kebab-parse-types" } +kebab-normalize = { path = "../kebab-normalize" } +kebab-chunk = { path = "../kebab-chunk" } +kebab-store-sqlite = { path = "../kebab-store-sqlite" } +kebab-store-vector = { path = "../kebab-store-vector" } +kebab-search = { path = "../kebab-search" } +kebab-embed = { path = "../kebab-embed" } +kebab-embed-local = { path = "../kebab-embed-local" } +kebab-llm = { path = "../kebab-llm" } +kebab-llm-local = { path = "../kebab-llm-local" } +kebab-rag = { path = "../kebab-rag" } anyhow = { workspace = true } blake3 = { workspace = true } serde = { workspace = true } diff --git a/crates/kb-app/src/app.rs b/crates/kebab-app/src/app.rs similarity index 96% rename from crates/kb-app/src/app.rs rename to crates/kebab-app/src/app.rs index 8156b3e..4080a15 100644 --- a/crates/kb-app/src/app.rs +++ b/crates/kebab-app/src/app.rs @@ -37,16 +37,16 @@ use std::sync::{Arc, OnceLock}; use anyhow::{Context, Result, anyhow}; -use kb_core::{ +use kebab_core::{ Answer, Embedder, IndexVersion, LanguageModel, Retriever, SearchHit, SearchMode, SearchQuery, VectorStore, }; -use kb_embed_local::FastembedEmbedder; -use kb_llm_local::OllamaLanguageModel; -use kb_rag::{AskOpts, RagPipeline}; -use kb_search::{HybridRetriever, LexicalRetriever, VectorRetriever}; -use kb_store_sqlite::SqliteStore; -use kb_store_vector::LanceVectorStore; +use kebab_embed_local::FastembedEmbedder; +use kebab_llm_local::OllamaLanguageModel; +use kebab_rag::{AskOpts, RagPipeline}; +use kebab_search::{HybridRetriever, LexicalRetriever, VectorRetriever}; +use kebab_store_sqlite::SqliteStore; +use kebab_store_vector::LanceVectorStore; /// Facade state — see module docs for lifetime rules. /// @@ -55,7 +55,7 @@ use kb_store_vector::LanceVectorStore; /// ask calls. The OnceLock-backed `embedder` / `vector` fields ensure /// the cold-start cost is paid exactly once per instance. pub struct App { - pub(crate) config: kb_config::Config, + pub(crate) config: kebab_config::Config, pub(crate) sqlite: Arc, /// Memoized embedder — built lazily on first `embedder()` call when /// embeddings are enabled. `OnceLock` keeps the struct `Sync` and @@ -80,7 +80,7 @@ impl App { /// Downstream `LanceVectorStore::new` (called by [`Self::vector`]) /// internally drives a `tokio::Runtime::block_on`, which panics if /// invoked from inside another tokio runtime. - pub fn open_with_config(config: kb_config::Config) -> Result { + pub fn open_with_config(config: kebab_config::Config) -> Result { let sqlite = SqliteStore::open(&config).context("kb-app: open SqliteStore")?; sqlite .run_migrations() @@ -286,7 +286,7 @@ impl App { /// the active config. This token surfaces in `SearchHit.index_version` /// and on snapshot tests; including the chunker version pins it to /// the chunking policy in effect. -fn lexical_index_version(config: &kb_config::Config) -> IndexVersion { +fn lexical_index_version(config: &kebab_config::Config) -> IndexVersion { IndexVersion(format!("lex:{}", config.chunking.chunker_version)) } diff --git a/crates/kb-app/src/doctor_signal.rs b/crates/kebab-app/src/doctor_signal.rs similarity index 100% rename from crates/kb-app/src/doctor_signal.rs rename to crates/kebab-app/src/doctor_signal.rs diff --git a/crates/kb-app/src/lib.rs b/crates/kebab-app/src/lib.rs similarity index 90% rename from crates/kb-app/src/lib.rs rename to crates/kebab-app/src/lib.rs index aefc030..f5e6727 100644 --- a/crates/kb-app/src/lib.rs +++ b/crates/kebab-app/src/lib.rs @@ -23,7 +23,7 @@ //! ## Config seam (`*_with_config`) //! //! Each public free function has a `#[doc(hidden)] pub fn *_with_config` -//! companion that takes a fully-resolved [`kb_config::Config`] directly. +//! companion that takes a fully-resolved [`kebab_config::Config`] directly. //! Three callers go through it: (1) the top-level free functions //! themselves, after `load_config()`; (2) `kb-cli` when the user passes //! `--config ` (CLI builds the Config via @@ -39,16 +39,16 @@ use std::sync::Arc; use anyhow::{Context, anyhow}; use serde::{Deserialize, Serialize}; -use kb_chunk::MdHeadingV1Chunker; -use kb_core::{ +use kebab_chunk::MdHeadingV1Chunker; +use kebab_core::{ Answer, CanonicalDocument, Chunk, ChunkId, ChunkPolicy, ChunkerVersion, Chunker, DocFilter, DocSummary, DocumentId, DocumentStore, Embedder, EmbeddingInput, EmbeddingKind, IngestReport, ParserVersion, RawAsset, SearchHit, SearchQuery, SourceConnector, SourceScope, SourceUri, VectorRecord, VectorStore, }; -use kb_normalize::build_canonical_document; -use kb_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; -use kb_source_fs::FsSourceConnector; +use kebab_normalize::build_canonical_document; +use kebab_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; +use kebab_source_fs::FsSourceConnector; mod app; pub mod doctor_signal; @@ -65,11 +65,11 @@ const KB_PARSE_MD_VERSION: &str = "pulldown-cmark-0.x"; /// Caller-supplied knobs for one [`ask`] invocation. /// -/// Re-exported from [`kb_rag::AskOpts`] (P4-3 owns the type) so kb-cli's -/// `use kb_app::AskOpts` keeps working without churn. The struct gained +/// Re-exported from [`kebab_rag::AskOpts`] (P4-3 owns the type) so kb-cli's +/// `use kebab_app::AskOpts` keeps working without churn. The struct gained /// a `stream_sink` field in P4-3; non-streaming callers (kb-cli today) /// pass `stream_sink: None`. -pub use kb_rag::AskOpts; +pub use kebab_rag::AskOpts; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DoctorReport { @@ -90,10 +90,10 @@ pub struct DoctorCheck { /// Create XDG dirs and write a starter `config.toml`. Idempotent unless /// `force=true` (which overwrites an existing config). pub fn init_workspace(force: bool) -> anyhow::Result<()> { - let cfg_path = kb_config::Config::xdg_config_path(); - let data_dir = kb_config::Config::xdg_data_dir(); - let cache_dir = kb_config::Config::xdg_cache_dir(); - let state_dir = kb_config::Config::xdg_state_dir(); + let cfg_path = kebab_config::Config::xdg_config_path(); + let data_dir = kebab_config::Config::xdg_data_dir(); + let cache_dir = kebab_config::Config::xdg_cache_dir(); + let state_dir = kebab_config::Config::xdg_state_dir(); for d in [ cfg_path.parent().map(PathBuf::from).unwrap_or_default(), @@ -107,11 +107,11 @@ pub fn init_workspace(force: bool) -> anyhow::Result<()> { } } - let workspace_root = expand_tilde(&kb_config::Config::defaults().workspace.root); + let workspace_root = expand_tilde(&kebab_config::Config::defaults().workspace.root); std::fs::create_dir_all(&workspace_root)?; if !cfg_path.exists() || force { - let cfg = kb_config::Config::defaults(); + let cfg = kebab_config::Config::defaults(); let toml_text = toml::to_string_pretty(&cfg)?; std::fs::write(&cfg_path, toml_text)?; } @@ -141,8 +141,8 @@ fn expand_tilde(s: &str) -> PathBuf { /// Callers that already have a Config in hand (CLI honoring `--config`, /// integration tests, TUI session) should bypass this and call the /// matching `*_with_config` helper directly. -fn load_config() -> anyhow::Result { - kb_config::Config::load(None) +fn load_config() -> anyhow::Result { + kebab_config::Config::load(None) } // ── ingest ──────────────────────────────────────────────────────────────── @@ -154,11 +154,11 @@ pub fn ingest(scope: SourceScope, summary_only: bool) -> anyhow::Result anyhow::Result { @@ -205,13 +205,13 @@ pub fn ingest_with_config( let started_at = time::OffsetDateTime::now_utc(); - let mut items: Vec = Vec::new(); + let mut items: Vec = Vec::new(); let mut new_count: u32 = 0; let mut updated_count: u32 = 0; let mut skipped_count: u32 = 0; let mut error_count: u32 = 0; // Aggregate counts surfaced into `ingest_runs` (and tracing). Not - // exposed on `IngestReport` today — `kb_core::IngestReport` is a + // exposed on `IngestReport` today — `kebab_core::IngestReport` is a // wire-stable struct without these fields — but persisting them // means audit tooling and `kb jobs` (P+) can recover the totals // without re-walking the DB. @@ -242,8 +242,8 @@ pub fn ingest_with_config( "kb-app::ingest: per-file fatal" ); error_count = error_count.saturating_add(1); - kb_core::IngestItem { - kind: kb_core::IngestItemKind::Error, + kebab_core::IngestItem { + kind: kebab_core::IngestItemKind::Error, doc_id: None, doc_path: asset.workspace_path.clone(), asset_id: Some(asset.asset_id.clone()), @@ -259,7 +259,7 @@ pub fn ingest_with_config( }; match item.kind { - kb_core::IngestItemKind::New => { + kebab_core::IngestItemKind::New => { new_count = new_count.saturating_add(1); let n = item.chunk_count.unwrap_or(0); chunks_indexed = chunks_indexed.saturating_add(n); @@ -267,7 +267,7 @@ pub fn ingest_with_config( embeddings_indexed = embeddings_indexed.saturating_add(n); } } - kb_core::IngestItemKind::Updated => { + kebab_core::IngestItemKind::Updated => { updated_count = updated_count.saturating_add(1); let n = item.chunk_count.unwrap_or(0); chunks_indexed = chunks_indexed.saturating_add(n); @@ -275,10 +275,10 @@ pub fn ingest_with_config( embeddings_indexed = embeddings_indexed.saturating_add(n); } } - kb_core::IngestItemKind::Skipped => { + kebab_core::IngestItemKind::Skipped => { skipped_count = skipped_count.saturating_add(1) } - kb_core::IngestItemKind::Error => { + kebab_core::IngestItemKind::Error => { error_count = error_count.saturating_add(1) } } @@ -293,9 +293,9 @@ pub fn ingest_with_config( "scope": scope, "summary_only": summary_only, }); - let job_id_res = ::create( + let job_id_res = ::create( &app.sqlite, - kb_core::JobKind::Ingest, + kebab_core::JobKind::Ingest, payload, ); match job_id_res { @@ -312,7 +312,7 @@ pub fn ingest_with_config( "chunks_indexed": chunks_indexed, "embeddings_indexed": embeddings_indexed, }); - if let Err(e) = ::update_progress( + if let Err(e) = ::update_progress( &app.sqlite, &jid, progress, @@ -323,10 +323,10 @@ pub fn ingest_with_config( "kb-app::ingest: JobRepo::update_progress failed" ); } - if let Err(e) = ::finish( + if let Err(e) = ::finish( &app.sqlite, &jid, - kb_core::JobStatus::Succeeded, + kebab_core::JobStatus::Succeeded, None, ) { tracing::warn!( @@ -370,7 +370,7 @@ pub fn ingest_with_config( } }; let run_id = mint_ingest_run_id(&scope_json, started_at); - let row = kb_store_sqlite::IngestRunRow { + let row = kebab_store_sqlite::IngestRunRow { run_id: &run_id, scope_json: &scope_json, scanned: scanned_count, @@ -432,7 +432,7 @@ fn mint_ingest_run_id(scope_json: &str, at: time::OffsetDateTime) -> String { /// vs `JobRepo`) on the same store. Plain `app.sqlite.create(...)` /// would pick one based on inherent vs trait methods; we go through /// `<… as JobRepo>` to be explicit. -type SqliteStoreAlias = kb_store_sqlite::SqliteStore; +type SqliteStoreAlias = kebab_store_sqlite::SqliteStore; /// Process a single asset: read bytes, parse, normalize, chunk, /// persist, embed. Per-asset failures bubble up to the caller for @@ -444,18 +444,18 @@ fn ingest_one_asset( parser_version: &ParserVersion, chunk_policy: &ChunkPolicy, embedder: Option<&Arc>, - vector_store: Option<&Arc>, + vector_store: Option<&Arc>, existing_doc_ids: &std::collections::HashSet, -) -> anyhow::Result { +) -> anyhow::Result { tracing::debug!( target: "kb-app::ingest", path = %asset.workspace_path.0, "processing asset" ); // Only handle Markdown for now; other media types are P6+ work. - if asset.media_type != kb_core::MediaType::Markdown { - return Ok(kb_core::IngestItem { - kind: kb_core::IngestItemKind::Skipped, + if asset.media_type != kebab_core::MediaType::Markdown { + return Ok(kebab_core::IngestItem { + kind: kebab_core::IngestItemKind::Skipped, doc_id: None, doc_path: asset.workspace_path.clone(), asset_id: Some(asset.asset_id.clone()), @@ -472,8 +472,8 @@ fn ingest_one_asset( let path = match &asset.source_uri { SourceUri::File(p) => p.clone(), SourceUri::Kb(_) => { - return Ok(kb_core::IngestItem { - kind: kb_core::IngestItemKind::Skipped, + return Ok(kebab_core::IngestItem { + kind: kebab_core::IngestItemKind::Skipped, doc_id: None, doc_path: asset.workspace_path.clone(), asset_id: Some(asset.asset_id.clone()), @@ -569,7 +569,7 @@ fn ingest_one_asset( .iter() .zip(vectors) .map(|(c, v)| VectorRecord { - embedding_id: kb_core::id_for_embedding( + embedding_id: kebab_core::id_for_embedding( &c.chunk_id, &model_id, &model_version, @@ -592,12 +592,12 @@ fn ingest_one_asset( } let kind = if existing_doc_ids.contains(&canonical.doc_id.0) { - kb_core::IngestItemKind::Updated + kebab_core::IngestItemKind::Updated } else { - kb_core::IngestItemKind::New + kebab_core::IngestItemKind::New }; - Ok(kb_core::IngestItem { + Ok(kebab_core::IngestItem { kind, doc_id: Some(canonical.doc_id.clone()), doc_path: asset.workspace_path.clone(), @@ -613,7 +613,7 @@ fn ingest_one_asset( } /// Convenience: end byte of the frontmatter region (or 0 when absent). -fn fm_span_end(span: Option) -> usize { +fn fm_span_end(span: Option) -> usize { span.map(|s| s.end).unwrap_or(0) } @@ -640,7 +640,7 @@ fn build_body_hints(asset: &RawAsset) -> BodyHints { } /// Build a `ChunkPolicy` from the active config. -fn chunk_policy_from_config(config: &kb_config::Config) -> ChunkPolicy { +fn chunk_policy_from_config(config: &kebab_config::Config) -> ChunkPolicy { ChunkPolicy { target_tokens: config.chunking.target_tokens, overlap_tokens: config.chunking.overlap_tokens, @@ -660,7 +660,7 @@ pub fn list_docs(filter: DocFilter) -> anyhow::Result> { /// ([`list_docs`]), not this. #[doc(hidden)] pub fn list_docs_with_config( - config: kb_config::Config, + config: kebab_config::Config, filter: DocFilter, ) -> anyhow::Result> { let app = App::open_with_config(config)?; @@ -676,7 +676,7 @@ pub fn inspect_doc(id: &DocumentId) -> anyhow::Result { /// ([`inspect_doc`]), not this. #[doc(hidden)] pub fn inspect_doc_with_config( - config: kb_config::Config, + config: kebab_config::Config, id: &DocumentId, ) -> anyhow::Result { let app = App::open_with_config(config)?; @@ -694,7 +694,7 @@ pub fn inspect_chunk(id: &ChunkId) -> anyhow::Result { /// ([`inspect_chunk`]), not this. #[doc(hidden)] pub fn inspect_chunk_with_config( - config: kb_config::Config, + config: kebab_config::Config, id: &ChunkId, ) -> anyhow::Result { let app = App::open_with_config(config)?; @@ -716,7 +716,7 @@ pub fn search(query: SearchQuery) -> anyhow::Result> { /// directly to amortize the embedder / vector-store cold start. #[doc(hidden)] pub fn search_with_config( - config: kb_config::Config, + config: kebab_config::Config, query: SearchQuery, ) -> anyhow::Result> { App::open_with_config(config)?.search(query) @@ -740,7 +740,7 @@ pub fn ask(query: &str, opts: AskOpts) -> anyhow::Result { /// [`App::ask`]. #[doc(hidden)] pub fn ask_with_config( - config: kb_config::Config, + config: kebab_config::Config, query: &str, opts: AskOpts, ) -> anyhow::Result { @@ -761,10 +761,10 @@ pub fn doctor_with_config_path(config_path: Option<&std::path::Path>) -> anyhow: // override first, else XDG default. Report whichever was probed. let cfg_path: PathBuf = match config_path { Some(p) => p.to_path_buf(), - None => kb_config::Config::xdg_config_path(), + None => kebab_config::Config::xdg_config_path(), }; let (config_ok, config_detail, loaded_cfg) = if cfg_path.exists() { - match kb_config::Config::from_file(&cfg_path) { + match kebab_config::Config::from_file(&cfg_path) { Ok(c) => (true, cfg_path.display().to_string(), Some(c)), Err(e) => (false, format!("{} ({e})", cfg_path.display()), None), } @@ -804,7 +804,7 @@ pub fn doctor_with_config_path(config_path: Option<&std::path::Path>) -> anyhow: let merged = c.clone().apply_env(&env); expand_tilde(&merged.storage.data_dir) } - None => kb_config::Config::xdg_data_dir(), + None => kebab_config::Config::xdg_data_dir(), }; let writable = (|| -> anyhow::Result<()> { std::fs::create_dir_all(&data_dir)?; diff --git a/crates/kb-app/src/logging.rs b/crates/kebab-app/src/logging.rs similarity index 95% rename from crates/kb-app/src/logging.rs rename to crates/kebab-app/src/logging.rs index 30865fc..a78c4c9 100644 --- a/crates/kb-app/src/logging.rs +++ b/crates/kebab-app/src/logging.rs @@ -19,7 +19,7 @@ pub enum LogLevel { /// — a second call is a no-op (the second `try_init` is dropped silently /// but the guard is still returned so the caller can keep it alive). pub fn init(level: LogLevel) -> Result { - let log_dir = kb_config::Config::xdg_state_dir().join("logs"); + let log_dir = kebab_config::Config::xdg_state_dir().join("logs"); std::fs::create_dir_all(&log_dir)?; let file_appender = tracing_appender::rolling::daily(&log_dir, "kb.log"); diff --git a/crates/kb-app/tests/ask_smoke.rs b/crates/kebab-app/tests/ask_smoke.rs similarity index 82% rename from crates/kb-app/tests/ask_smoke.rs rename to crates/kebab-app/tests/ask_smoke.rs index 0e47db1..8df4f3f 100644 --- a/crates/kb-app/tests/ask_smoke.rs +++ b/crates/kebab-app/tests/ask_smoke.rs @@ -21,12 +21,12 @@ use common::TestEnv; #[ignore = "requires real Ollama on 127.0.0.1:11434"] fn ask_lexical_smoke() { let env = TestEnv::lexical_only(); - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); - let opts = kb_app::AskOpts { + let opts = kebab_app::AskOpts { k: 5, explain: false, - mode: kb_core::SearchMode::Lexical, + mode: kebab_core::SearchMode::Lexical, temperature: Some(0.0), seed: Some(0), stream_sink: None, @@ -34,10 +34,10 @@ fn ask_lexical_smoke() { // The fixture workspace contains "ownership" content; the model's // citation behavior depends on its training, so we don't assert on // grounded — only that the call returns a structurally-valid Answer. - let answer = kb_app::ask_with_config(env.config.clone(), "ownership", opts) + let answer = kebab_app::ask_with_config(env.config.clone(), "ownership", opts) .expect("ask returns Ok with a real Ollama backend"); // retrieval summary always populated, regardless of grounded path. - assert_eq!(answer.retrieval.mode, kb_core::SearchMode::Lexical); + assert_eq!(answer.retrieval.mode, kebab_core::SearchMode::Lexical); assert!(answer.retrieval.k >= 5); assert!(answer.retrieval.trace_id.0.starts_with("ret_")); } diff --git a/crates/kb-app/tests/common/mod.rs b/crates/kebab-app/tests/common/mod.rs similarity index 96% rename from crates/kb-app/tests/common/mod.rs rename to crates/kebab-app/tests/common/mod.rs index 59b04ce..cded98f 100644 --- a/crates/kb-app/tests/common/mod.rs +++ b/crates/kebab-app/tests/common/mod.rs @@ -12,7 +12,7 @@ use std::path::{Path, PathBuf}; -use kb_config::Config; +use kebab_config::Config; use tempfile::TempDir; /// Test environment: owns a `TempDir` and exposes a `Config` whose @@ -72,8 +72,8 @@ impl TestEnv { } } - pub fn scope(&self) -> kb_core::SourceScope { - kb_core::SourceScope { + pub fn scope(&self) -> kebab_core::SourceScope { + kebab_core::SourceScope { root: self.workspace_root.clone(), include: self.config.workspace.include.clone(), exclude: self.config.workspace.exclude.clone(), diff --git a/crates/kb-app/tests/fixtures/workspace/intro.md b/crates/kebab-app/tests/fixtures/workspace/intro.md similarity index 100% rename from crates/kb-app/tests/fixtures/workspace/intro.md rename to crates/kebab-app/tests/fixtures/workspace/intro.md diff --git a/crates/kb-app/tests/fixtures/workspace/notes/cargo.md b/crates/kebab-app/tests/fixtures/workspace/notes/cargo.md similarity index 100% rename from crates/kb-app/tests/fixtures/workspace/notes/cargo.md rename to crates/kebab-app/tests/fixtures/workspace/notes/cargo.md diff --git a/crates/kb-app/tests/fixtures/workspace/notes/python.md b/crates/kebab-app/tests/fixtures/workspace/notes/python.md similarity index 100% rename from crates/kb-app/tests/fixtures/workspace/notes/python.md rename to crates/kebab-app/tests/fixtures/workspace/notes/python.md diff --git a/crates/kb-app/tests/ingest_lexical.rs b/crates/kebab-app/tests/ingest_lexical.rs similarity index 81% rename from crates/kb-app/tests/ingest_lexical.rs rename to crates/kebab-app/tests/ingest_lexical.rs index 344dee9..f3ae13f 100644 --- a/crates/kb-app/tests/ingest_lexical.rs +++ b/crates/kebab-app/tests/ingest_lexical.rs @@ -9,7 +9,7 @@ use common::TestEnv; fn ingest_then_list_inspects_round_trip() { let env = TestEnv::lexical_only(); let report = - kb_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); // The fixture has 3 markdown files; first ingest should label them // all as New. @@ -27,16 +27,16 @@ fn ingest_then_list_inspects_round_trip() { } // list_docs returns the 3 docs. - let docs = kb_app::list_docs_with_config( + let docs = kebab_app::list_docs_with_config( env.config.clone(), - kb_core::DocFilter::default(), + kebab_core::DocFilter::default(), ) .unwrap(); assert_eq!(docs.len(), 3, "docs: {docs:?}"); // inspect_doc round-trips one of them. let any_doc_id = docs[0].doc_id.clone(); - let canonical = kb_app::inspect_doc_with_config(env.config.clone(), &any_doc_id) + let canonical = kebab_app::inspect_doc_with_config(env.config.clone(), &any_doc_id) .unwrap(); assert_eq!(canonical.doc_id, any_doc_id); assert!(!canonical.blocks.is_empty(), "blocks empty"); @@ -47,20 +47,20 @@ fn ingest_idempotent_on_second_run() { let env = TestEnv::lexical_only(); let r1 = - kb_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); assert_eq!(r1.new, 3); let r2 = - kb_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); // Same files re-ingested — labelled Updated, not duplicated. assert_eq!(r2.scanned, 3, "second scan: {r2:?}"); assert_eq!(r2.new, 0, "second run new should be 0: {r2:?}"); assert_eq!(r2.updated, 3, "second run updated: {r2:?}"); // list_docs still has 3 docs (no duplicates). - let docs = kb_app::list_docs_with_config( + let docs = kebab_app::list_docs_with_config( env.config.clone(), - kb_core::DocFilter::default(), + kebab_core::DocFilter::default(), ) .unwrap(); assert_eq!(docs.len(), 3); @@ -70,7 +70,7 @@ fn ingest_idempotent_on_second_run() { fn ingest_summary_only_drops_items() { let env = TestEnv::lexical_only(); let report = - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); assert_eq!(report.scanned, 3); assert!(report.items.is_none(), "summary-only should null items"); } @@ -82,7 +82,7 @@ fn ingest_records_ingest_runs_row_with_aggregate_counts() { // of every run. `summary_only=true` writes `items_json=NULL`; the // counts MUST still be present. let env = TestEnv::lexical_only(); - let report = kb_app::ingest_with_config(env.config.clone(), env.scope(), true) + let report = kebab_app::ingest_with_config(env.config.clone(), env.scope(), true) .unwrap(); assert_eq!(report.scanned, 3); @@ -137,7 +137,7 @@ fn ingest_provider_none_skips_lance() { // tables under it). let env = TestEnv::lexical_only(); let report = - kb_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), false).unwrap(); assert_eq!(report.errors, 0, "lexical-only run must not error"); assert_eq!(report.new, 3); @@ -170,22 +170,22 @@ fn ingest_provider_none_skips_lance() { #[test] fn list_docs_filters_by_tags_any() { let env = TestEnv::lexical_only(); - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); - let filter = kb_core::DocFilter { + let filter = kebab_core::DocFilter { tags_any: vec!["python".to_string()], ..Default::default() }; - let docs = kb_app::list_docs_with_config(env.config.clone(), filter).unwrap(); + let docs = kebab_app::list_docs_with_config(env.config.clone(), filter).unwrap(); assert_eq!(docs.len(), 1, "expected only the python doc: {docs:?}"); assert!(docs[0].tags.contains(&"python".to_string())); - let rust_filter = kb_core::DocFilter { + let rust_filter = kebab_core::DocFilter { tags_any: vec!["rust".to_string()], ..Default::default() }; let rust_docs = - kb_app::list_docs_with_config(env.config.clone(), rust_filter).unwrap(); + kebab_app::list_docs_with_config(env.config.clone(), rust_filter).unwrap(); // intro.md and notes/cargo.md both tag "rust". assert_eq!(rust_docs.len(), 2, "expected 2 rust docs: {rust_docs:?}"); } @@ -194,8 +194,8 @@ fn list_docs_filters_by_tags_any() { fn inspect_doc_not_found_returns_actionable_error() { let env = TestEnv::lexical_only(); let bogus = - kb_core::DocumentId("0000000000000000000000000000000000000000000000000000000000000000".to_string()); - let err = kb_app::inspect_doc_with_config(env.config.clone(), &bogus).unwrap_err(); + kebab_core::DocumentId("0000000000000000000000000000000000000000000000000000000000000000".to_string()); + let err = kebab_app::inspect_doc_with_config(env.config.clone(), &bogus).unwrap_err(); let msg = format!("{err:#}"); assert!( msg.contains("not found"), @@ -210,10 +210,10 @@ fn inspect_doc_not_found_returns_actionable_error() { #[test] fn inspect_chunk_not_found_returns_actionable_error() { let env = TestEnv::lexical_only(); - let bogus = kb_core::ChunkId( + let bogus = kebab_core::ChunkId( "0000000000000000000000000000000000000000000000000000000000000000".to_string(), ); - let err = kb_app::inspect_chunk_with_config(env.config.clone(), &bogus) + let err = kebab_app::inspect_chunk_with_config(env.config.clone(), &bogus) .unwrap_err(); let msg = format!("{err:#}"); assert!(msg.contains("not found"), "got: {msg}"); diff --git a/crates/kb-app/tests/search_lexical.rs b/crates/kebab-app/tests/search_lexical.rs similarity index 61% rename from crates/kb-app/tests/search_lexical.rs rename to crates/kebab-app/tests/search_lexical.rs index 3391ede..e50f6a3 100644 --- a/crates/kb-app/tests/search_lexical.rs +++ b/crates/kebab-app/tests/search_lexical.rs @@ -5,24 +5,24 @@ mod common; use common::TestEnv; -fn lexical_query(text: &str) -> kb_core::SearchQuery { - kb_core::SearchQuery { +fn lexical_query(text: &str) -> kebab_core::SearchQuery { + kebab_core::SearchQuery { text: text.to_string(), - mode: kb_core::SearchMode::Lexical, + mode: kebab_core::SearchMode::Lexical, k: 10, - filters: kb_core::SearchFilters::default(), + filters: kebab_core::SearchFilters::default(), } } #[test] fn lexical_search_returns_hits_after_ingest() { let env = TestEnv::lexical_only(); - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); // "Ownership" appears as a heading + paragraph in intro.md and // matches FTS5 default tokenizer easily. let hits = - kb_app::search_with_config(env.config.clone(), lexical_query("ownership")) + kebab_app::search_with_config(env.config.clone(), lexical_query("ownership")) .unwrap(); assert!(!hits.is_empty(), "expected ≥1 hit for 'ownership'"); @@ -34,7 +34,7 @@ fn lexical_search_returns_hits_after_ingest() { ); assert_eq!( h.retrieval.method, - kb_core::SearchMode::Lexical, + kebab_core::SearchMode::Lexical, "method label should be Lexical" ); } @@ -43,8 +43,8 @@ fn lexical_search_returns_hits_after_ingest() { #[test] fn lexical_search_empty_query_returns_empty() { let env = TestEnv::lexical_only(); - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); - let hits = kb_app::search_with_config(env.config.clone(), lexical_query(" ")) + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + let hits = kebab_app::search_with_config(env.config.clone(), lexical_query(" ")) .unwrap(); assert!(hits.is_empty(), "blank query must short-circuit empty"); } @@ -52,15 +52,15 @@ fn lexical_search_empty_query_returns_empty() { #[test] fn vector_mode_with_provider_none_errors_clearly() { let env = TestEnv::lexical_only(); - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); - let q = kb_core::SearchQuery { + let q = kebab_core::SearchQuery { text: "ownership".to_string(), - mode: kb_core::SearchMode::Vector, + mode: kebab_core::SearchMode::Vector, k: 10, - filters: kb_core::SearchFilters::default(), + filters: kebab_core::SearchFilters::default(), }; - let err = kb_app::search_with_config(env.config.clone(), q).unwrap_err(); + let err = kebab_app::search_with_config(env.config.clone(), q).unwrap_err(); let msg = format!("{err:#}"); assert!( msg.contains("embeddings disabled") || msg.contains("disabled"), diff --git a/crates/kb-app/tests/search_vector.rs b/crates/kebab-app/tests/search_vector.rs similarity index 75% rename from crates/kb-app/tests/search_vector.rs rename to crates/kebab-app/tests/search_vector.rs index 1ac7df6..5e80e09 100644 --- a/crates/kb-app/tests/search_vector.rs +++ b/crates/kebab-app/tests/search_vector.rs @@ -31,21 +31,21 @@ fn ingest_then_hybrid_search_returns_hits() { let env = TestEnv::with_embeddings(); let report = - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); assert_eq!(report.errors, 0, "no per-file errors: {report:?}"); assert_eq!(report.new, 3); - let q = kb_core::SearchQuery { + let q = kebab_core::SearchQuery { text: "ownership".to_string(), - mode: kb_core::SearchMode::Hybrid, + mode: kebab_core::SearchMode::Hybrid, k: 10, - filters: kb_core::SearchFilters::default(), + filters: kebab_core::SearchFilters::default(), }; - let hits = kb_app::search_with_config(env.config.clone(), q).unwrap(); + let hits = kebab_app::search_with_config(env.config.clone(), q).unwrap(); assert!(!hits.is_empty(), "expected hybrid hits for 'ownership'"); let methods: Vec<_> = hits.iter().map(|h| h.retrieval.method).collect(); assert!( - methods.iter().all(|m| *m == kb_core::SearchMode::Hybrid), + methods.iter().all(|m| *m == kebab_core::SearchMode::Hybrid), "every hit must report method=Hybrid: {methods:?}" ); } @@ -58,22 +58,22 @@ fn ingest_then_vector_search_carries_embedding_model() { let env = TestEnv::with_embeddings(); let report = - kb_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); + kebab_app::ingest_with_config(env.config.clone(), env.scope(), true).unwrap(); assert_eq!(report.errors, 0, "no per-file errors: {report:?}"); assert_eq!(report.new, 3); - let q = kb_core::SearchQuery { + let q = kebab_core::SearchQuery { text: "ownership".to_string(), - mode: kb_core::SearchMode::Vector, + mode: kebab_core::SearchMode::Vector, k: 10, - filters: kb_core::SearchFilters::default(), + filters: kebab_core::SearchFilters::default(), }; - let hits = kb_app::search_with_config(env.config.clone(), q).unwrap(); + let hits = kebab_app::search_with_config(env.config.clone(), q).unwrap(); assert!(!hits.is_empty(), "expected vector hits for 'ownership'"); // Vector mode dispatches through `VectorRetriever` and MUST stamp // each hit with the configured embedding_model id. - let expected = kb_core::EmbeddingModelId(env.config.models.embedding.model.clone()); + let expected = kebab_core::EmbeddingModelId(env.config.models.embedding.model.clone()); for h in &hits { assert_eq!( h.embedding_model, @@ -82,7 +82,7 @@ fn ingest_then_vector_search_carries_embedding_model() { ); assert_eq!( h.retrieval.method, - kb_core::SearchMode::Vector, + kebab_core::SearchMode::Vector, "vector-mode hit must report method=Vector" ); } diff --git a/crates/kb-chunk/Cargo.toml b/crates/kebab-chunk/Cargo.toml similarity index 84% rename from crates/kb-chunk/Cargo.toml rename to crates/kebab-chunk/Cargo.toml index 2238643..be91c3c 100644 --- a/crates/kb-chunk/Cargo.toml +++ b/crates/kebab-chunk/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-chunk" +name = "kebab-chunk" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,7 +8,7 @@ repository = { workspace = true } description = "Chunkers that turn kb-core::CanonicalDocument into kb-core::Chunk batches (§3.5, §4.2, §7.2)" [dependencies] -kb-core = { path = "../kb-core" } +kebab-core = { path = "../kebab-core" } serde_json_canonicalizer = "0.3" blake3 = { workspace = true } anyhow = { workspace = true } @@ -20,7 +20,7 @@ tracing = { workspace = true } # regular deps per design §8 (chunker consumes CanonicalDocument from kb-core # only); `cargo tree -p kb-chunk --depth 1` (default scope, excludes dev-deps) # confirms this. -kb-parse-md = { path = "../kb-parse-md" } -kb-normalize = { path = "../kb-normalize" } +kebab-parse-md = { path = "../kebab-parse-md" } +kebab-normalize = { path = "../kebab-normalize" } serde_json = { workspace = true } time = { workspace = true } diff --git a/crates/kb-chunk/src/lib.rs b/crates/kebab-chunk/src/lib.rs similarity index 91% rename from crates/kb-chunk/src/lib.rs rename to crates/kebab-chunk/src/lib.rs index 4cff5f1..dc4b7c0 100644 --- a/crates/kb-chunk/src/lib.rs +++ b/crates/kebab-chunk/src/lib.rs @@ -1,4 +1,4 @@ -//! `kb-chunk` — chunkers that emit [`kb_core::Chunk`] batches. +//! `kb-chunk` — chunkers that emit [`kebab_core::Chunk`] batches. //! //! Per design §3.5 (Chunk), §4.2 (chunk_id recipe), §7.2 (`Chunker` //! trait), §0 Q3/§14 (chunking priority). diff --git a/crates/kb-chunk/src/md_heading_v1.rs b/crates/kebab-chunk/src/md_heading_v1.rs similarity index 98% rename from crates/kb-chunk/src/md_heading_v1.rs rename to crates/kebab-chunk/src/md_heading_v1.rs index 7626ea5..f9054b7 100644 --- a/crates/kb-chunk/src/md_heading_v1.rs +++ b/crates/kebab-chunk/src/md_heading_v1.rs @@ -1,6 +1,6 @@ //! `md-heading-v1` — heading-aware Markdown chunker. -use kb_core::{ +use kebab_core::{ Block, BlockId, CanonicalDocument, Chunk, ChunkPolicy, Chunker, ChunkerVersion, DocumentId, SourceSpan, id_for_chunk, }; @@ -24,7 +24,7 @@ const POLICY_HASH_HEX_LEN: usize = 16; /// Heading-aware Markdown chunker. /// -/// Implements [`kb_core::Chunker`] for Markdown-derived +/// Implements [`kebab_core::Chunker`] for Markdown-derived /// [`CanonicalDocument`]s. /// /// **Behavior contract** (design §0 / §14, in priority order): @@ -409,7 +409,7 @@ fn estimate_block_tokens(b: &Block) -> usize { } /// Borrow the `CommonBlock` of any [`Block`] variant. -fn common(b: &Block) -> &kb_core::CommonBlock { +fn common(b: &Block) -> &kebab_core::CommonBlock { match b { Block::Heading(h) => &h.common, Block::Paragraph(t) | Block::Quote(t) => &t.common, @@ -424,7 +424,7 @@ fn common(b: &Block) -> &kb_core::CommonBlock { #[cfg(test)] mod tests { use super::*; - use kb_core::{ + use kebab_core::{ AssetId, CodeBlock, CommonBlock, HeadingBlock, ImageRefBlock, Lang, Metadata, Provenance, SourceType, TableBlock, TextBlock, TrustLevel, WorkspacePath, id_for_block, @@ -433,7 +433,7 @@ mod tests { fn make_doc(blocks: Vec) -> CanonicalDocument { CanonicalDocument { - doc_id: kb_core::DocumentId("d".repeat(32)), + doc_id: kebab_core::DocumentId("d".repeat(32)), source_asset_id: AssetId("a".repeat(32)), workspace_path: WorkspacePath::new("notes/test.md".into()).unwrap(), title: "Test".into(), @@ -450,14 +450,14 @@ mod tests { user: Default::default(), }, provenance: Provenance { events: vec![] }, - parser_version: kb_core::ParserVersion("test-parser-0".into()), + parser_version: kebab_core::ParserVersion("test-parser-0".into()), schema_version: 1, doc_version: 1, } } - fn doc_id() -> kb_core::DocumentId { - kb_core::DocumentId("d".repeat(32)) + fn doc_id() -> kebab_core::DocumentId { + kebab_core::DocumentId("d".repeat(32)) } fn span(start: u32, end: u32) -> SourceSpan { diff --git a/crates/kb-chunk/tests/long_section_snapshot.rs b/crates/kebab-chunk/tests/long_section_snapshot.rs similarity index 97% rename from crates/kb-chunk/tests/long_section_snapshot.rs rename to crates/kebab-chunk/tests/long_section_snapshot.rs index 3148300..ef2c982 100644 --- a/crates/kb-chunk/tests/long_section_snapshot.rs +++ b/crates/kebab-chunk/tests/long_section_snapshot.rs @@ -13,13 +13,13 @@ use std::path::PathBuf; -use kb_chunk::MdHeadingV1Chunker; -use kb_core::{ +use kebab_chunk::MdHeadingV1Chunker; +use kebab_core::{ AssetId, AssetStorage, Checksum, ChunkPolicy, ChunkerVersion, Chunker, MediaType, ParserVersion, RawAsset, SourceUri, WorkspacePath, }; -use kb_normalize::build_canonical_document; -use kb_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; +use kebab_normalize::build_canonical_document; +use kebab_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; use serde_json::Value; use time::OffsetDateTime; diff --git a/crates/kb-cli/Cargo.toml b/crates/kebab-cli/Cargo.toml similarity index 82% rename from crates/kb-cli/Cargo.toml rename to crates/kebab-cli/Cargo.toml index ae46920..591b60f 100644 --- a/crates/kb-cli/Cargo.toml +++ b/crates/kebab-cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-cli" +name = "kebab-cli" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -12,9 +12,9 @@ name = "kb" path = "src/main.rs" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-app = { path = "../kb-app" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-app = { path = "../kebab-app" } # kb-eval re-exports `compute_aggregate` / `compare_runs` / # `render_report_md` (P5-2). The DoD calls for these to be reached # "via kb-app", but kb-eval already depends on kb-app (P5-1 runner @@ -22,7 +22,7 @@ kb-app = { path = "../kb-app" } # require kb-app → kb-eval, forming a cycle. We therefore wire # kb-cli → kb-eval directly; documented in # `tasks/p5/p5-2-metrics-compare.md`. -kb-eval = { path = "../kb-eval" } +kebab-eval = { path = "../kebab-eval" } anyhow = { workspace = true } serde_json = { workspace = true } clap = { version = "4", features = ["derive"] } diff --git a/crates/kb-cli/src/main.rs b/crates/kebab-cli/src/main.rs similarity index 82% rename from crates/kb-cli/src/main.rs rename to crates/kebab-cli/src/main.rs index b71203c..25b9624 100644 --- a/crates/kb-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -6,7 +6,7 @@ use std::process::ExitCode; use clap::{Parser, Subcommand}; -use kb_app::doctor_signal::{DoctorUnhealthy, NoHitSignal, RefusalSignal}; +use kebab_app::doctor_signal::{DoctorUnhealthy, NoHitSignal, RefusalSignal}; mod wire; @@ -170,12 +170,12 @@ enum ModeFlag { Hybrid, } -impl From for kb_core::SearchMode { +impl From for kebab_core::SearchMode { fn from(m: ModeFlag) -> Self { match m { - ModeFlag::Lexical => kb_core::SearchMode::Lexical, - ModeFlag::Vector => kb_core::SearchMode::Vector, - ModeFlag::Hybrid => kb_core::SearchMode::Hybrid, + ModeFlag::Lexical => kebab_core::SearchMode::Lexical, + ModeFlag::Vector => kebab_core::SearchMode::Vector, + ModeFlag::Hybrid => kebab_core::SearchMode::Hybrid, } } } @@ -183,15 +183,15 @@ impl From for kb_core::SearchMode { fn main() -> ExitCode { let cli = Cli::parse(); let level = if cli.debug { - kb_app::logging::LogLevel::Debug + kebab_app::logging::LogLevel::Debug } else if cli.verbose { - kb_app::logging::LogLevel::Verbose + kebab_app::logging::LogLevel::Verbose } else { - kb_app::logging::LogLevel::Default + kebab_app::logging::LogLevel::Default }; // Fail-soft: if logging init errors (e.g. XDG state dir is read-only), // proceed without a guard rather than crashing — `kb` is still usable. - let _log_guard = kb_app::logging::init(level).ok(); + let _log_guard = kebab_app::logging::init(level).ok(); match run(&cli) { Ok(()) => ExitCode::from(0), Err(e) => { @@ -227,14 +227,14 @@ fn exit_code(err: &anyhow::Error) -> u8 { fn run(cli: &Cli) -> anyhow::Result<()> { match &cli.command { Cmd::Init { force } => { - kb_app::init_workspace(*force)?; + kebab_app::init_workspace(*force)?; if !cli.json { println!( "created {}", - kb_config::Config::xdg_config_path().display() + kebab_config::Config::xdg_config_path().display() ); - println!("created {}", kb_config::Config::xdg_data_dir().display()); - println!("created {}", kb_config::Config::xdg_state_dir().display()); + println!("created {}", kebab_config::Config::xdg_data_dir().display()); + println!("created {}", kebab_config::Config::xdg_state_dir().display()); println!("hint edit the config above, then `kb ingest`"); } Ok(()) @@ -244,13 +244,13 @@ fn run(cli: &Cli) -> anyhow::Result<()> { root, summary_only, } => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let scope = kb_core::SourceScope { + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let scope = kebab_core::SourceScope { root: root.clone().unwrap_or_else(|| PathBuf::from(&cfg.workspace.root)), include: cfg.workspace.include.clone(), exclude: cfg.workspace.exclude.clone(), }; - let report = kb_app::ingest_with_config(cfg, scope, *summary_only)?; + let report = kebab_app::ingest_with_config(cfg, scope, *summary_only)?; if cli.json { println!("{}", serde_json::to_string(&wire::wire_ingest(&report))?); } else { @@ -269,8 +269,8 @@ fn run(cli: &Cli) -> anyhow::Result<()> { Cmd::List { what } => match what { ListWhat::Docs => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let docs = kb_app::list_docs_with_config(cfg, kb_core::DocFilter::default())?; + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let docs = kebab_app::list_docs_with_config(cfg, kebab_core::DocFilter::default())?; if cli.json { println!("{}", serde_json::to_string(&wire::wire_doc_summaries(&docs))?); } else { @@ -284,9 +284,9 @@ fn run(cli: &Cli) -> anyhow::Result<()> { Cmd::Inspect { what } => match what { InspectWhat::Doc { id } => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let doc_id: kb_core::DocumentId = id.parse()?; - let doc = kb_app::inspect_doc_with_config(cfg, &doc_id)?; + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let doc_id: kebab_core::DocumentId = id.parse()?; + let doc = kebab_app::inspect_doc_with_config(cfg, &doc_id)?; // Inspect doc emits a `CanonicalDocument` — there's no §2 // wire schema for it (P1-5 will decide whether this also // becomes a tagged wrapper or stays as the raw domain @@ -296,9 +296,9 @@ fn run(cli: &Cli) -> anyhow::Result<()> { Ok(()) } InspectWhat::Chunk { id } => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let chunk_id: kb_core::ChunkId = id.parse()?; - let chunk = kb_app::inspect_chunk_with_config(cfg, &chunk_id)?; + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let chunk_id: kebab_core::ChunkId = id.parse()?; + let chunk = kebab_app::inspect_chunk_with_config(cfg, &chunk_id)?; println!("{}", serde_json::to_string(&wire::wire_chunk_inspection(&chunk))?); Ok(()) } @@ -310,14 +310,14 @@ fn run(cli: &Cli) -> anyhow::Result<()> { mode, explain: _, } => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let q = kb_core::SearchQuery { + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let q = kebab_core::SearchQuery { text: query.clone(), mode: (*mode).into(), k: *k, - filters: kb_core::SearchFilters::default(), + filters: kebab_core::SearchFilters::default(), }; - let hits = kb_app::search_with_config(cfg, q)?; + let hits = kebab_app::search_with_config(cfg, q)?; if cli.json { println!("{}", serde_json::to_string(&wire::wire_search_hits(&hits))?); } else { @@ -351,8 +351,8 @@ fn run(cli: &Cli) -> anyhow::Result<()> { temperature, seed, } => { - let cfg = kb_config::Config::load(cli.config.as_deref())?; - let opts = kb_app::AskOpts { + let cfg = kebab_config::Config::load(cli.config.as_deref())?; + let opts = kebab_app::AskOpts { k: *k, explain: *explain, mode: (*mode).into(), @@ -363,7 +363,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> { // wires up a real `mpsc::Sender` here. stream_sink: None, }; - let ans = kb_app::ask_with_config(cfg, query, opts)?; + let ans = kebab_app::ask_with_config(cfg, query, opts)?; if cli.json { println!("{}", serde_json::to_string(&wire::wire_answer(&ans))?); } else { @@ -377,7 +377,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> { } Cmd::Doctor => { - let report = kb_app::doctor_with_config_path(cli.config.as_deref())?; + let report = kebab_app::doctor_with_config_path(cli.config.as_deref())?; if cli.json { println!("{}", serde_json::to_string(&wire::wire_doctor(&report))?); } else { @@ -409,7 +409,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> { temperature, seed, } => { - let opts = kb_eval::EvalRunOpts { + let opts = kebab_eval::EvalRunOpts { suite: suite.clone(), mode: (*mode).into(), with_rag: *with_rag, @@ -417,7 +417,7 @@ fn run(cli: &Cli) -> anyhow::Result<()> { temperature: *temperature, seed: *seed, }; - let run = kb_eval::run_eval(&opts)?; + let run = kebab_eval::run_eval(&opts)?; if cli.json { println!("{}", serde_json::to_string_pretty(&run)?); } else { @@ -430,8 +430,8 @@ fn run(cli: &Cli) -> anyhow::Result<()> { } EvalWhat::Aggregate { run_id } => { - let agg = kb_eval::compute_aggregate(run_id)?; - kb_eval::store_aggregate(run_id, &agg)?; + let agg = kebab_eval::compute_aggregate(run_id)?; + kebab_eval::store_aggregate(run_id, &agg)?; if cli.json { println!("{}", serde_json::to_string_pretty(&agg)?); } else { @@ -450,20 +450,20 @@ fn run(cli: &Cli) -> anyhow::Result<()> { strict_chunker_version, write_report, } => { - let cfg = kb_config::Config::load(None)?; - let opts = kb_eval::CompareOpts { + let cfg = kebab_config::Config::load(None)?; + let opts = kebab_eval::CompareOpts { strict_chunker_version: *strict_chunker_version, }; - let report = kb_eval::compare_runs_with_config(&cfg, run_a, run_b, &opts)?; - let md = kb_eval::render_report_md(&report); + let report = kebab_eval::compare_runs_with_config(&cfg, run_a, run_b, &opts)?; + let md = kebab_eval::render_report_md(&report); if cli.json { println!("{}", serde_json::to_string_pretty(&report)?); } else { print!("{md}"); } if *write_report { - let resolved_data_dir = kb_config::expand_path(&cfg.storage.data_dir, ""); - let runs_dir = kb_config::expand_path( + let resolved_data_dir = kebab_config::expand_path(&cfg.storage.data_dir, ""); + let runs_dir = kebab_config::expand_path( &cfg.storage.runs_dir, &resolved_data_dir.to_string_lossy(), ); diff --git a/crates/kb-cli/src/wire.rs b/crates/kebab-cli/src/wire.rs similarity index 97% rename from crates/kb-cli/src/wire.rs rename to crates/kebab-cli/src/wire.rs index 7dc236d..fd1493a 100644 --- a/crates/kb-cli/src/wire.rs +++ b/crates/kebab-cli/src/wire.rs @@ -17,8 +17,8 @@ use serde_json::Value; -use kb_app::DoctorReport; -use kb_core::{Answer, Chunk, DocSummary, IngestReport, SearchHit}; +use kebab_app::DoctorReport; +use kebab_core::{Answer, Chunk, DocSummary, IngestReport, SearchHit}; /// Insert `schema_version` into an object-shaped `Value`. Helper for the /// "serialize, then tag" pattern used by all the per-type wrappers below. @@ -132,7 +132,7 @@ mod tests { #[test] fn ingest_wrapper_tags_schema_version() { - use kb_core::SourceScope; + use kebab_core::SourceScope; let r = IngestReport { scope: SourceScope { root: std::path::PathBuf::from("/tmp"), diff --git a/crates/kb-config/Cargo.toml b/crates/kebab-config/Cargo.toml similarity index 87% rename from crates/kb-config/Cargo.toml rename to crates/kebab-config/Cargo.toml index f1430f8..4b40b01 100644 --- a/crates/kb-config/Cargo.toml +++ b/crates/kebab-config/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-config" +name = "kebab-config" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -9,7 +9,7 @@ description = "Config schema + XDG path resolution" [dependencies] # kb-core::CoreError reserved for P1-* config errors -kb-core = { path = "../kb-core" } +kebab-core = { path = "../kebab-core" } anyhow = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/kb-config/src/lib.rs b/crates/kebab-config/src/lib.rs similarity index 100% rename from crates/kb-config/src/lib.rs rename to crates/kebab-config/src/lib.rs diff --git a/crates/kb-config/src/paths.rs b/crates/kebab-config/src/paths.rs similarity index 100% rename from crates/kb-config/src/paths.rs rename to crates/kebab-config/src/paths.rs diff --git a/crates/kb-core/Cargo.toml b/crates/kebab-core/Cargo.toml similarity index 96% rename from crates/kb-core/Cargo.toml rename to crates/kebab-core/Cargo.toml index 795aad3..38b50af 100644 --- a/crates/kb-core/Cargo.toml +++ b/crates/kebab-core/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-core" +name = "kebab-core" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } diff --git a/crates/kb-core/src/answer.rs b/crates/kebab-core/src/answer.rs similarity index 100% rename from crates/kb-core/src/answer.rs rename to crates/kebab-core/src/answer.rs diff --git a/crates/kb-core/src/asset.rs b/crates/kebab-core/src/asset.rs similarity index 100% rename from crates/kb-core/src/asset.rs rename to crates/kebab-core/src/asset.rs diff --git a/crates/kb-core/src/chunk.rs b/crates/kebab-core/src/chunk.rs similarity index 100% rename from crates/kb-core/src/chunk.rs rename to crates/kebab-core/src/chunk.rs diff --git a/crates/kb-core/src/citation.rs b/crates/kebab-core/src/citation.rs similarity index 100% rename from crates/kb-core/src/citation.rs rename to crates/kebab-core/src/citation.rs diff --git a/crates/kb-core/src/document.rs b/crates/kebab-core/src/document.rs similarity index 100% rename from crates/kb-core/src/document.rs rename to crates/kebab-core/src/document.rs diff --git a/crates/kb-core/src/errors.rs b/crates/kebab-core/src/errors.rs similarity index 100% rename from crates/kb-core/src/errors.rs rename to crates/kebab-core/src/errors.rs diff --git a/crates/kb-core/src/ids.rs b/crates/kebab-core/src/ids.rs similarity index 100% rename from crates/kb-core/src/ids.rs rename to crates/kebab-core/src/ids.rs diff --git a/crates/kb-core/src/ingest.rs b/crates/kebab-core/src/ingest.rs similarity index 100% rename from crates/kb-core/src/ingest.rs rename to crates/kebab-core/src/ingest.rs diff --git a/crates/kb-core/src/jobs.rs b/crates/kebab-core/src/jobs.rs similarity index 100% rename from crates/kb-core/src/jobs.rs rename to crates/kebab-core/src/jobs.rs diff --git a/crates/kb-core/src/lib.rs b/crates/kebab-core/src/lib.rs similarity index 100% rename from crates/kb-core/src/lib.rs rename to crates/kebab-core/src/lib.rs diff --git a/crates/kb-core/src/media.rs b/crates/kebab-core/src/media.rs similarity index 100% rename from crates/kb-core/src/media.rs rename to crates/kebab-core/src/media.rs diff --git a/crates/kb-core/src/metadata.rs b/crates/kebab-core/src/metadata.rs similarity index 100% rename from crates/kb-core/src/metadata.rs rename to crates/kebab-core/src/metadata.rs diff --git a/crates/kb-core/src/normalize.rs b/crates/kebab-core/src/normalize.rs similarity index 100% rename from crates/kb-core/src/normalize.rs rename to crates/kebab-core/src/normalize.rs diff --git a/crates/kb-core/src/search.rs b/crates/kebab-core/src/search.rs similarity index 100% rename from crates/kb-core/src/search.rs rename to crates/kebab-core/src/search.rs diff --git a/crates/kb-core/src/traits.rs b/crates/kebab-core/src/traits.rs similarity index 100% rename from crates/kb-core/src/traits.rs rename to crates/kebab-core/src/traits.rs diff --git a/crates/kb-core/src/vector.rs b/crates/kebab-core/src/vector.rs similarity index 100% rename from crates/kb-core/src/vector.rs rename to crates/kebab-core/src/vector.rs diff --git a/crates/kb-core/src/versions.rs b/crates/kebab-core/src/versions.rs similarity index 100% rename from crates/kb-core/src/versions.rs rename to crates/kebab-core/src/versions.rs diff --git a/crates/kb-embed-local/Cargo.toml b/crates/kebab-embed-local/Cargo.toml similarity index 85% rename from crates/kb-embed-local/Cargo.toml rename to crates/kebab-embed-local/Cargo.toml index 3a32217..41c30c8 100644 --- a/crates/kb-embed-local/Cargo.toml +++ b/crates/kebab-embed-local/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-embed-local" +name = "kebab-embed-local" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "Local fastembed-rs adapter implementing kb_core::Embedder (multilingual-e5-small default)" [dependencies] -kb-config = { path = "../kb-config" } -kb-embed = { path = "../kb-embed" } +kebab-config = { path = "../kebab-config" } +kebab-embed = { path = "../kebab-embed" } # Default features bring `ort-download-binaries` (bundled ONNX runtime) # and `hf-hub-native-tls` (first-run model download). No extra features # needed for the multilingual-e5-small path. diff --git a/crates/kb-embed-local/src/lib.rs b/crates/kebab-embed-local/src/lib.rs similarity index 97% rename from crates/kb-embed-local/src/lib.rs rename to crates/kebab-embed-local/src/lib.rs index 2d0796c..7267fe3 100644 --- a/crates/kb-embed-local/src/lib.rs +++ b/crates/kebab-embed-local/src/lib.rs @@ -1,5 +1,5 @@ //! `kb-embed-local` — `FastembedEmbedder`, a local ONNX-backed -//! [`Embedder`](kb_embed::Embedder) implementation. +//! [`Embedder`](kebab_embed::Embedder) implementation. //! //! Wraps [`fastembed::TextEmbedding`] for the default `multilingual-e5-small` //! (384-dim) model. Honors `config.models.embedding.batch_size` and applies @@ -26,8 +26,8 @@ use std::sync::Mutex; use anyhow::{Context, Result}; use fastembed::{EmbeddingModel, InitOptions, TextEmbedding}; -use kb_config::expand_path; -use kb_embed::{Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion}; +use kebab_config::expand_path; +use kebab_embed::{Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion}; /// Subdirectory under `config.storage.model_dir` where the fastembed /// adapter writes / reads ONNX + tokenizer files. Hard-coded per task @@ -58,9 +58,9 @@ impl FastembedEmbedder { /// `config.models.embedding.dimensions` matches the model's actual /// dim BEFORE returning, so a mismatch fails at construction (not on /// first `embed`). - pub fn new(config: &kb_config::Config) -> Result { + pub fn new(config: &kebab_config::Config) -> Result { // 1. Resolve `{data_dir}/models/fastembed/` from the config - // templates. Goes through the shared `kb_config::expand_path` + // templates. Goes through the shared `kebab_config::expand_path` // so every crate resolves storage paths identically. let data_dir = expand_path(&config.storage.data_dir, ""); let model_dir = expand_path(&config.storage.model_dir, &data_dir.to_string_lossy()); @@ -224,7 +224,7 @@ pub(crate) fn check_dim(model_dim: usize, cfg_dim: usize) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use kb_embed::EmbeddingInput; + use kebab_embed::EmbeddingInput; // ── check_dim ──────────────────────────────────────────────────── // diff --git a/crates/kb-embed-local/tests/embed_model.rs b/crates/kebab-embed-local/tests/embed_model.rs similarity index 96% rename from crates/kb-embed-local/tests/embed_model.rs rename to crates/kebab-embed-local/tests/embed_model.rs index b2af074..df545ff 100644 --- a/crates/kb-embed-local/tests/embed_model.rs +++ b/crates/kebab-embed-local/tests/embed_model.rs @@ -22,16 +22,16 @@ use std::hash::{Hash, Hasher}; use std::sync::OnceLock; use std::time::Instant; -use kb_embed::{Embedder, EmbeddingInput, EmbeddingKind}; -use kb_embed_local::FastembedEmbedder; +use kebab_embed::{Embedder, EmbeddingInput, EmbeddingKind}; +use kebab_embed_local::FastembedEmbedder; /// Build a `Config` whose `data_dir` lives in a per-process temp dir so /// the test never writes into the developer's real `~/.local/share/kb`. /// Returns the `Config` and the `TempDir` guard (caller keeps the guard /// alive for the test duration). -fn test_config() -> (kb_config::Config, tempfile::TempDir) { +fn test_config() -> (kebab_config::Config, tempfile::TempDir) { let tmp = tempfile::tempdir().expect("create tempdir"); - let mut cfg = kb_config::Config::defaults(); + let mut cfg = kebab_config::Config::defaults(); cfg.storage.data_dir = tmp.path().to_string_lossy().into_owned(); // model_dir keeps its default `{data_dir}/models` template; the // adapter resolves it itself. @@ -141,12 +141,12 @@ fn output_vectors_are_l2_normalized() { }, ]; let out = emb.embed(&inputs).expect("embed"); - // Per `kb_embed::assert_unit_norm` docs: `5e-4` is the safe bound at + // Per `kebab_embed::assert_unit_norm` docs: `5e-4` is the safe bound at // 384 dims (f32::EPSILON × √384 ≈ 2.3e-6, but ONNX kernels add // their own per-component noise; 1e-3 is very generous and matches // the spec's `± 1e-3`). - kb_embed::assert_unit_norm(&out, 1e-3); - kb_embed::assert_vector_shape(&out, 384); + kebab_embed::assert_unit_norm(&out, 1e-3); + kebab_embed::assert_vector_shape(&out, 384); } // ─── determinism ────────────────────────────────────────────────────── diff --git a/crates/kb-embed-local/tests/fixtures/embed/known-sentences.json b/crates/kebab-embed-local/tests/fixtures/embed/known-sentences.json similarity index 100% rename from crates/kb-embed-local/tests/fixtures/embed/known-sentences.json rename to crates/kebab-embed-local/tests/fixtures/embed/known-sentences.json diff --git a/crates/kb-embed/Cargo.toml b/crates/kebab-embed/Cargo.toml similarity index 90% rename from crates/kb-embed/Cargo.toml rename to crates/kebab-embed/Cargo.toml index 2719095..451607b 100644 --- a/crates/kb-embed/Cargo.toml +++ b/crates/kebab-embed/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-embed" +name = "kebab-embed" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "Embedder trait re-exports + opt-in deterministic MockEmbedder for downstream tests" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } serde = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/crates/kb-embed/src/lib.rs b/crates/kebab-embed/src/lib.rs similarity index 96% rename from crates/kb-embed/src/lib.rs rename to crates/kebab-embed/src/lib.rs index a4adba8..e1b5c0c 100644 --- a/crates/kb-embed/src/lib.rs +++ b/crates/kebab-embed/src/lib.rs @@ -1,9 +1,9 @@ //! `kb-embed` — thin re-export crate for the [`Embedder`] trait surface. //! //! This crate exists so downstream code (`kb-store-vector`, `kb-search`, -//! adapters in p3-2) can `use kb_embed::Embedder` and stay stable across +//! adapters in p3-2) can `use kebab_embed::Embedder` and stay stable across //! kb-core reorganizations. It defines **no new types**; everything is a -//! re-export of [`kb_core`]. +//! re-export of [`kebab_core`]. //! //! ## Mock implementation //! @@ -19,7 +19,7 @@ // Per spec §7.2 — these are the only public-surface types this crate offers. // Adding new types is forbidden by the task contract. -pub use kb_core::{ +pub use kebab_core::{ Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, }; diff --git a/crates/kb-embed/src/mock.rs b/crates/kebab-embed/src/mock.rs similarity index 98% rename from crates/kb-embed/src/mock.rs rename to crates/kebab-embed/src/mock.rs index 3ca846a..d538bc3 100644 --- a/crates/kb-embed/src/mock.rs +++ b/crates/kebab-embed/src/mock.rs @@ -38,7 +38,7 @@ //! * Different `text` → different output with overwhelming probability. //! * All output components are finite (`is_finite()`). -use kb_core::{Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion}; +use kebab_core::{Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion}; /// Deterministic test double. See module docs for the hashing recipe. pub struct MockEmbedder { diff --git a/crates/kb-embed/tests/mock.rs b/crates/kebab-embed/tests/mock.rs similarity index 99% rename from crates/kb-embed/tests/mock.rs rename to crates/kebab-embed/tests/mock.rs index 3923f1d..d3ac109 100644 --- a/crates/kb-embed/tests/mock.rs +++ b/crates/kebab-embed/tests/mock.rs @@ -4,7 +4,7 @@ #![cfg(feature = "mock")] -use kb_embed::{ +use kebab_embed::{ Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, MockEmbedder, assert_unit_norm, assert_vector_shape, }; diff --git a/crates/kb-embed/tests/reexports.rs b/crates/kebab-embed/tests/reexports.rs similarity index 99% rename from crates/kb-embed/tests/reexports.rs rename to crates/kebab-embed/tests/reexports.rs index cfa5c14..72203ab 100644 --- a/crates/kb-embed/tests/reexports.rs +++ b/crates/kebab-embed/tests/reexports.rs @@ -5,7 +5,7 @@ //! Runs under both `cargo test -p kb-embed` and //! `cargo test -p kb-embed --features mock`. -use kb_embed::{ +use kebab_embed::{ Embedder, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, assert_vector_shape, }; diff --git a/crates/kb-eval/Cargo.toml b/crates/kebab-eval/Cargo.toml similarity index 83% rename from crates/kb-eval/Cargo.toml rename to crates/kebab-eval/Cargo.toml index 4281ee4..137d058 100644 --- a/crates/kb-eval/Cargo.toml +++ b/crates/kebab-eval/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-eval" +name = "kebab-eval" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -9,10 +9,10 @@ description = "Golden-fixture eval runner: load YAML, drive kb-app search/ask, [dependencies] # Allowed deps per p5-1 spec — domain types + facade only. -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-app = { path = "../kb-app" } -kb-store-sqlite = { path = "../kb-store-sqlite" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-app = { path = "../kebab-app" } +kebab-store-sqlite = { path = "../kebab-store-sqlite" } serde = { workspace = true } serde_json = { workspace = true } serde_yaml = { workspace = true } diff --git a/crates/kb-eval/src/compare.rs b/crates/kebab-eval/src/compare.rs similarity index 98% rename from crates/kb-eval/src/compare.rs rename to crates/kebab-eval/src/compare.rs index 686e2c5..24ed840 100644 --- a/crates/kb-eval/src/compare.rs +++ b/crates/kebab-eval/src/compare.rs @@ -14,9 +14,9 @@ use std::fmt::Write as _; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; -use kb_config::Config; -use kb_core::{ChunkId, DocumentId}; -use kb_store_sqlite::SqliteStore; +use kebab_config::Config; +use kebab_core::{ChunkId, DocumentId}; +use kebab_store_sqlite::SqliteStore; use crate::loader::load_golden_set; use crate::metrics::{ @@ -300,7 +300,7 @@ fn extract_chunker_version(snapshot_json: &str) -> Option { } fn parse_results( - rows: &[kb_store_sqlite::EvalQueryResultRecord], + rows: &[kebab_store_sqlite::EvalQueryResultRecord], ) -> Result> { let mut out = HashMap::with_capacity(rows.len()); for row in rows { @@ -456,9 +456,9 @@ mod tests { let g = GoldenQuery { id: "q1".into(), query: "q".into(), - lang: kb_core::Lang(String::new()), + lang: kebab_core::Lang(String::new()), expected_doc_ids: vec![], - expected_chunk_ids: vec![kb_core::ChunkId("c1".into())], + expected_chunk_ids: vec![kebab_core::ChunkId("c1".into())], must_contain: vec![], forbidden: vec![], difficulty: None, diff --git a/crates/kb-eval/src/lib.rs b/crates/kebab-eval/src/lib.rs similarity index 94% rename from crates/kb-eval/src/lib.rs rename to crates/kebab-eval/src/lib.rs index 14cf89b..c0e0b01 100644 --- a/crates/kb-eval/src/lib.rs +++ b/crates/kebab-eval/src/lib.rs @@ -1,7 +1,7 @@ //! `kb-eval` — golden-fixture eval runner (P5-1). //! //! Loads `fixtures/golden_queries.yaml`, runs each entry through the -//! [`kb_app`] facade (lexical / vector / hybrid + optional RAG), and +//! [`kebab_app`] facade (lexical / vector / hybrid + optional RAG), and //! persists results into `eval_runs` / `eval_query_results` plus //! `runs_dir//per_query.jsonl` (design §5.7, §6.3). //! diff --git a/crates/kb-eval/src/loader.rs b/crates/kebab-eval/src/loader.rs similarity index 96% rename from crates/kb-eval/src/loader.rs rename to crates/kebab-eval/src/loader.rs index 5b24fa5..9112ec8 100644 --- a/crates/kb-eval/src/loader.rs +++ b/crates/kebab-eval/src/loader.rs @@ -6,7 +6,7 @@ //! tests that don't have a SQLite store handy. //! - [`load_golden_set_validated`] — additionally verifies every //! `expected_doc_id` / `expected_chunk_id` exists in the SQLite DB -//! the supplied [`kb_config::Config`] points at. Used by +//! the supplied [`kebab_config::Config`] points at. Used by //! [`crate::run_eval`] in production so a stale golden set fails //! fast at run start. @@ -14,7 +14,7 @@ use std::collections::{BTreeSet, HashSet}; use std::path::Path; use anyhow::{Context, Result, anyhow}; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use crate::types::GoldenQuery; @@ -43,11 +43,11 @@ pub fn load_golden_set(path: &Path) -> Result> { /// Currently used only by the in-module tests below; production code /// inlines `load_golden_set` + `validate_against_db` in /// [`crate::run_eval_with_config`] so the validation can run against -/// an already-opened [`kb_config::Config`] without re-parsing YAML. +/// an already-opened [`kebab_config::Config`] without re-parsing YAML. #[cfg(test)] pub(crate) fn load_golden_set_validated( yaml_path: &Path, - cfg: &kb_config::Config, + cfg: &kebab_config::Config, ) -> Result> { let queries = load_golden_set(yaml_path)?; validate_against_db(&queries, cfg)?; @@ -73,7 +73,7 @@ fn check_unique_ids(queries: &[GoldenQuery]) -> Result<()> { /// Read every doc_id / chunk_id referenced by `queries` and confirm /// SQLite has rows for them. Builds a sorted, deduplicated error /// message listing every missing ID. -pub(crate) fn validate_against_db(queries: &[GoldenQuery], cfg: &kb_config::Config) -> Result<()> { +pub(crate) fn validate_against_db(queries: &[GoldenQuery], cfg: &kebab_config::Config) -> Result<()> { // Short-circuit when there is nothing to validate — saves opening // SQLite for golden sets that omit expected_*_ids entirely. let needs_check = queries @@ -140,8 +140,8 @@ mod tests { //! `tests/loader.rs`; only the validated-variant cases need to sit //! next to the function so they can see the `pub(crate)` symbol. use super::*; - use kb_config::Config; - use kb_store_sqlite::SqliteStore; + use kebab_config::Config; + use kebab_store_sqlite::SqliteStore; use rusqlite::params; use std::fs; use tempfile::tempdir; diff --git a/crates/kb-eval/src/metrics.rs b/crates/kebab-eval/src/metrics.rs similarity index 98% rename from crates/kb-eval/src/metrics.rs rename to crates/kebab-eval/src/metrics.rs index e8056ef..6f25784 100644 --- a/crates/kb-eval/src/metrics.rs +++ b/crates/kebab-eval/src/metrics.rs @@ -13,9 +13,9 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use kb_config::Config; -use kb_core::{ChunkId, Citation, DocumentId}; -use kb_store_sqlite::SqliteStore; +use kebab_config::Config; +use kebab_core::{ChunkId, Citation, DocumentId}; +use kebab_store_sqlite::SqliteStore; use crate::loader::load_golden_set; use crate::types::{GoldenQuery, QueryResult}; @@ -175,7 +175,7 @@ fn load_golden_for_metrics() -> Result> { /// `tasks/p5/p5-2-metrics-compare.md`), this will need to take one. pub(crate) fn aggregate_from_rows( queries: &[GoldenQuery], - rows: &[kb_store_sqlite::EvalQueryResultRecord], + rows: &[kebab_store_sqlite::EvalQueryResultRecord], ) -> Result { let golden_by_id: HashMap<&str, &GoldenQuery> = queries.iter().map(|q| (q.id.as_str(), q)).collect(); @@ -395,14 +395,14 @@ fn ratio_or_zero(num: u32, denom: u32) -> f32 { #[cfg(test)] mod tests { use super::*; - use kb_core::{ + use kebab_core::{ ChunkId, ChunkerVersion, Citation, DocumentId, IndexVersion, RetrievalDetail, SearchHit, SearchMode, }; - use kb_core::asset::WorkspacePath; - use kb_core::media::Lang; - use kb_core::answer::{Answer, AnswerCitation, AnswerRetrievalSummary, ModelRef, TokenUsage, TraceId}; - use kb_core::versions::PromptTemplateVersion; + use kebab_core::asset::WorkspacePath; + use kebab_core::media::Lang; + use kebab_core::answer::{Answer, AnswerCitation, AnswerRetrievalSummary, ModelRef, TokenUsage, TraceId}; + use kebab_core::versions::PromptTemplateVersion; use time::OffsetDateTime; fn gq(id: &str, expected_chunks: &[&str], expected_docs: &[&str]) -> GoldenQuery { @@ -460,9 +460,9 @@ mod tests { } fn record(id: &str, hits: Vec, error: Option, answer: Option) - -> kb_store_sqlite::EvalQueryResultRecord + -> kebab_store_sqlite::EvalQueryResultRecord { - kb_store_sqlite::EvalQueryResultRecord { + kebab_store_sqlite::EvalQueryResultRecord { query_id: id.into(), result_json: serde_json::to_string(&qr(id, hits, error, answer)).unwrap(), } diff --git a/crates/kb-eval/src/runner.rs b/crates/kebab-eval/src/runner.rs similarity index 93% rename from crates/kb-eval/src/runner.rs rename to crates/kebab-eval/src/runner.rs index 6e5f2ad..b76d72e 100644 --- a/crates/kb-eval/src/runner.rs +++ b/crates/kebab-eval/src/runner.rs @@ -6,10 +6,10 @@ use std::path::PathBuf; use std::time::Instant; use anyhow::{Context, Result}; -use kb_app::App; -use kb_config::expand_path; -use kb_core::{SearchFilters, SearchQuery}; -use kb_store_sqlite::{EvalRunRow, SqliteStore}; +use kebab_app::App; +use kebab_config::expand_path; +use kebab_core::{SearchFilters, SearchQuery}; +use kebab_store_sqlite::{EvalRunRow, SqliteStore}; use time::OffsetDateTime; use crate::loader::{load_golden_set, validate_against_db}; @@ -25,18 +25,18 @@ fn elapsed_ms_u32(start: Instant) -> u32 { } /// Run the golden suite end-to-end against the active XDG-loaded -/// [`kb_config::Config`]. Wraps [`run_eval_with_config`] with +/// [`kebab_config::Config`]. Wraps [`run_eval_with_config`] with /// `Config::load(None)`. pub fn run_eval(opts: &EvalRunOpts) -> Result { - let cfg = kb_config::Config::load(None).context("load Config for run_eval")?; + let cfg = kebab_config::Config::load(None).context("load Config for run_eval")?; run_eval_with_config(&cfg, opts) } /// Run the golden suite end-to-end against an explicit -/// [`kb_config::Config`]. Used by integration tests (TempDir-backed +/// [`kebab_config::Config`]. Used by integration tests (TempDir-backed /// data_dir) and any future caller that wants to drive the runner /// against a non-default config. -pub fn run_eval_with_config(cfg: &kb_config::Config, opts: &EvalRunOpts) -> Result { +pub fn run_eval_with_config(cfg: &kebab_config::Config, opts: &EvalRunOpts) -> Result { let started = Instant::now(); // ── 1. Load golden set ──────────────────────────────────────────────── @@ -167,7 +167,7 @@ fn execute_query(app: &App, gq: &GoldenQuery, opts: &EvalRunOpts) -> QueryResult // call did not already error out (we want one error per query, not // a duplicated one). let answer = if opts.with_rag && error.is_none() { - let ask_opts = kb_app::AskOpts { + let ask_opts = kebab_app::AskOpts { k: opts.k, explain: true, mode: opts.mode, @@ -206,7 +206,7 @@ fn execute_query(app: &App, gq: &GoldenQuery, opts: &EvalRunOpts) -> QueryResult /// stable run-time property of the config alone. P5-2 may compose it /// from `embedding.{model,version,dimensions}` if it needs the field /// for compare reports. -fn build_config_snapshot(cfg: &kb_config::Config) -> Result { +fn build_config_snapshot(cfg: &kebab_config::Config) -> Result { let cfg_value = serde_json::to_value(cfg).context("serialize Config")?; Ok(serde_json::json!({ "config": cfg_value, @@ -234,7 +234,7 @@ fn build_config_snapshot(cfg: &kb_config::Config) -> Result { /// `run_id` collision would already have failed the `eval_runs` /// PRIMARY KEY upstream). fn write_per_query_jsonl( - cfg: &kb_config::Config, + cfg: &kebab_config::Config, run_id: &str, per_query: &[QueryResult], ) -> Result<()> { diff --git a/crates/kb-eval/src/types.rs b/crates/kebab-eval/src/types.rs similarity index 92% rename from crates/kb-eval/src/types.rs rename to crates/kebab-eval/src/types.rs index 6165770..db5e15d 100644 --- a/crates/kb-eval/src/types.rs +++ b/crates/kebab-eval/src/types.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -use kb_core::{Answer, ChunkId, DocumentId, Lang, SearchHit, SearchMode}; +use kebab_core::{Answer, ChunkId, DocumentId, Lang, SearchHit, SearchMode}; /// One golden query loaded from `fixtures/golden_queries.yaml`. /// @@ -41,10 +41,10 @@ pub struct EvalRunOpts { /// Suite label persisted into `eval_runs.suite`. The shipped /// fixture is `"golden"`; other suites can reuse the same runner. pub suite: String, - /// Retrieval mode forwarded to every `kb_app::search` / - /// `kb_app::ask` call inside the run. + /// Retrieval mode forwarded to every `kebab_app::search` / + /// `kebab_app::ask` call inside the run. pub mode: SearchMode, - /// When `true`, also call `kb_app::ask` per query and record the + /// When `true`, also call `kebab_app::ask` per query and record the /// resulting `Answer` on the `QueryResult`. pub with_rag: bool, /// Top-k forwarded to retrieval (and `AskOpts.k` when `with_rag`). diff --git a/crates/kb-eval/tests/fixtures/eval/compare-1.json b/crates/kebab-eval/tests/fixtures/eval/compare-1.json similarity index 100% rename from crates/kb-eval/tests/fixtures/eval/compare-1.json rename to crates/kebab-eval/tests/fixtures/eval/compare-1.json diff --git a/crates/kb-eval/tests/fixtures/eval/run-1.json b/crates/kebab-eval/tests/fixtures/eval/run-1.json similarity index 100% rename from crates/kb-eval/tests/fixtures/eval/run-1.json rename to crates/kebab-eval/tests/fixtures/eval/run-1.json diff --git a/crates/kb-eval/tests/loader.rs b/crates/kebab-eval/tests/loader.rs similarity index 98% rename from crates/kb-eval/tests/loader.rs rename to crates/kebab-eval/tests/loader.rs index 115dacb..62f4033 100644 --- a/crates/kb-eval/tests/loader.rs +++ b/crates/kebab-eval/tests/loader.rs @@ -8,7 +8,7 @@ use std::fs; -use kb_eval::load_golden_set; +use kebab_eval::load_golden_set; use tempfile::tempdir; // ── 1. parser accepts well-formed YAML with optional fields ────────────────── diff --git a/crates/kb-eval/tests/metrics_and_compare.rs b/crates/kebab-eval/tests/metrics_and_compare.rs similarity index 98% rename from crates/kb-eval/tests/metrics_and_compare.rs rename to crates/kebab-eval/tests/metrics_and_compare.rs index 9721df9..be9746f 100644 --- a/crates/kb-eval/tests/metrics_and_compare.rs +++ b/crates/kebab-eval/tests/metrics_and_compare.rs @@ -9,17 +9,17 @@ use std::fs; use std::path::PathBuf; -use kb_config::Config; -use kb_core::{ +use kebab_config::Config; +use kebab_core::{ ChunkId, ChunkerVersion, Citation, DocumentId, IndexVersion, Lang, RetrievalDetail, SearchHit, SearchMode, asset::WorkspacePath, }; -use kb_eval::{ +use kebab_eval::{ AggregateMetrics, CompareOpts, CompareReport, ComparisonKind, GoldenQuery, QueryResult, compare_runs_with_config, compute_aggregate_with_config, store_aggregate_with_config, }; -use kb_store_sqlite::{EvalRunRow, SqliteStore}; +use kebab_store_sqlite::{EvalRunRow, SqliteStore}; use tempfile::TempDir; use time::OffsetDateTime; @@ -259,7 +259,7 @@ fn compare_runs_classifies_win_loss_draw_regression() { drop(store); let report = compare_runs_with_config(&cfg, "run_a", "run_b", &CompareOpts::default()).unwrap(); - let by_id: std::collections::HashMap<&str, &kb_eval::QueryComparison> = + let by_id: std::collections::HashMap<&str, &kebab_eval::QueryComparison> = report.per_query.iter().map(|c| (c.query_id.as_str(), c)).collect(); assert_eq!(by_id["q-001"].kind, ComparisonKind::Loss); assert_eq!(by_id["q-002"].kind, ComparisonKind::Win); @@ -414,7 +414,7 @@ fn render_report_md_is_human_readable() { drop(store); let report = compare_runs_with_config(&cfg, "run_a", "run_b", &CompareOpts::default()).unwrap(); - let md = kb_eval::render_report_md(&report); + let md = kebab_eval::render_report_md(&report); assert!(md.starts_with("# Eval compare:"), "md = {md}"); assert!(md.contains("hit@1")); assert!(md.contains("MRR")); diff --git a/crates/kb-eval/tests/runner.rs b/crates/kebab-eval/tests/runner.rs similarity index 98% rename from crates/kb-eval/tests/runner.rs rename to crates/kebab-eval/tests/runner.rs index 248c1fd..e13e0f9 100644 --- a/crates/kb-eval/tests/runner.rs +++ b/crates/kebab-eval/tests/runner.rs @@ -1,6 +1,6 @@ //! Runner integration tests for `kb-eval` (P5-1). //! -//! Drives [`kb_eval::run_eval_with_config`] end-to-end against a +//! Drives [`kebab_eval::run_eval_with_config`] end-to-end against a //! TempDir-backed config: //! //! - tiny seeded SQLite corpus (3 docs / 3 chunks) used as the @@ -17,10 +17,10 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Mutex; -use kb_config::Config; -use kb_core::SearchMode; -use kb_eval::{EvalRunOpts, QueryResult, run_eval_with_config}; -use kb_store_sqlite::SqliteStore; +use kebab_config::Config; +use kebab_core::SearchMode; +use kebab_eval::{EvalRunOpts, QueryResult, run_eval_with_config}; +use kebab_store_sqlite::SqliteStore; use rusqlite::params; use tempfile::TempDir; @@ -110,7 +110,7 @@ fn seed_corpus(store: &SqliteStore) { // Build the FTS index so lexical search returns hits. Reuses the // same connection guard rather than reopening — the SAVEPOINT // protocol nests correctly under the existing read_conn lock. - kb_store_sqlite::rebuild_chunks_fts(&conn).unwrap(); + kebab_store_sqlite::rebuild_chunks_fts(&conn).unwrap(); drop(conn); } diff --git a/crates/kb-llm-local/Cargo.toml b/crates/kebab-llm-local/Cargo.toml similarity index 91% rename from crates/kb-llm-local/Cargo.toml rename to crates/kebab-llm-local/Cargo.toml index 20fc623..6cc8669 100644 --- a/crates/kb-llm-local/Cargo.toml +++ b/crates/kebab-llm-local/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-llm-local" +name = "kebab-llm-local" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,9 +8,9 @@ repository = { workspace = true } description = "Ollama HTTP adapter implementing kb_core::LanguageModel via reqwest::blocking" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-llm = { path = "../kb-llm" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-llm = { path = "../kebab-llm" } # `default-features = false` drops the `default-tls` (native-tls / openssl) # feature so we don't pull in a system OpenSSL; we explicitly pin rustls. # Note: `default-features = false` does NOT drop tokio — reqwest 0.12's diff --git a/crates/kb-llm-local/src/error.rs b/crates/kebab-llm-local/src/error.rs similarity index 100% rename from crates/kb-llm-local/src/error.rs rename to crates/kebab-llm-local/src/error.rs diff --git a/crates/kb-llm-local/src/lib.rs b/crates/kebab-llm-local/src/lib.rs similarity index 91% rename from crates/kb-llm-local/src/lib.rs rename to crates/kebab-llm-local/src/lib.rs index 64a6233..3e0bbfd 100644 --- a/crates/kb-llm-local/src/lib.rs +++ b/crates/kebab-llm-local/src/lib.rs @@ -1,5 +1,5 @@ //! `kb-llm-local` — Ollama HTTP adapter implementing -//! [`kb_core::LanguageModel`] over the local `POST /api/generate` endpoint. +//! [`kebab_core::LanguageModel`] over the local `POST /api/generate` endpoint. //! //! ## Why a separate crate //! @@ -39,11 +39,11 @@ mod ollama; pub use error::LlmError; pub use ollama::OllamaLanguageModel; -// Re-export the trait surface so adapter consumers can `use kb_llm_local::*` +// Re-export the trait surface so adapter consumers can `use kebab_llm_local::*` // without also depending on `kb-llm` directly. These are the same symbols // `kb-llm` re-exports from `kb-core`; this crate adds **no new types** to // the trait surface (`LlmError` and `OllamaLanguageModel` are // implementation-side only). -pub use kb_llm::{ +pub use kebab_llm::{ FinishReason, GenerateRequest, LanguageModel, ModelRef, TokenChunk, TokenUsage, }; diff --git a/crates/kb-llm-local/src/ollama.rs b/crates/kebab-llm-local/src/ollama.rs similarity index 98% rename from crates/kb-llm-local/src/ollama.rs rename to crates/kebab-llm-local/src/ollama.rs index f4f2f9e..51e2a49 100644 --- a/crates/kb-llm-local/src/ollama.rs +++ b/crates/kebab-llm-local/src/ollama.rs @@ -41,7 +41,7 @@ use std::io::{BufRead, BufReader}; use std::time::Duration; -use kb_core::{ +use kebab_core::{ FinishReason, GenerateRequest, LanguageModel, ModelRef, TokenChunk, TokenUsage, }; use serde::{Deserialize, Serialize}; @@ -68,7 +68,7 @@ pub struct OllamaLanguageModel { } impl OllamaLanguageModel { - /// Build an adapter from a workspace [`kb_config::Config`]. Reads + /// Build an adapter from a workspace [`kebab_config::Config`]. Reads /// `config.models.llm.{provider, model, endpoint, context_tokens, /// temperature, seed}`. /// @@ -76,7 +76,7 @@ impl OllamaLanguageModel { /// expected to have validated `provider == "ollama"`; this constructor /// trusts the config and would happily build for an unknown provider. /// (Provider routing is the App layer's job, not the adapter's.) - pub fn new(config: &kb_config::Config) -> anyhow::Result { + pub fn new(config: &kebab_config::Config) -> anyhow::Result { let llm = &config.models.llm; let client = reqwest::blocking::Client::builder() .timeout(REQUEST_TIMEOUT) @@ -292,7 +292,7 @@ impl Iterator for OllamaStream { // pipelines that expect a terminal frame still terminate. self.done = true; tracing::warn!( - target: "kb_llm_local", + target: "kebab_llm_local", "ollama stream ended without a `done: true` frame; synthesizing Aborted", ); return Some(Ok(TokenChunk::Done { @@ -361,14 +361,14 @@ impl Iterator for OllamaStream { }; let prompt_tokens = line.prompt_eval_count.unwrap_or_else(|| { tracing::warn!( - target: "kb_llm_local", + target: "kebab_llm_local", "ollama done frame missing prompt_eval_count; defaulting to 0", ); 0 }); let completion_tokens = line.eval_count.unwrap_or_else(|| { tracing::warn!( - target: "kb_llm_local", + target: "kebab_llm_local", "ollama done frame missing eval_count; defaulting to 0", ); 0 diff --git a/crates/kb-llm-local/tests/construction.rs b/crates/kebab-llm-local/tests/construction.rs similarity index 94% rename from crates/kb-llm-local/tests/construction.rs rename to crates/kebab-llm-local/tests/construction.rs index 80ed36b..37f842b 100644 --- a/crates/kb-llm-local/tests/construction.rs +++ b/crates/kebab-llm-local/tests/construction.rs @@ -2,8 +2,8 @@ //! relevant config fields and exposes them via the trait surface, all //! without touching the network (per design §7.2 lazy-connect contract). -use kb_config::Config; -use kb_llm_local::{LanguageModel, OllamaLanguageModel}; +use kebab_config::Config; +use kebab_llm_local::{LanguageModel, OllamaLanguageModel}; #[test] fn construction_with_default_config_returns_expected_model_ref() { diff --git a/crates/kb-llm-local/tests/integration.rs b/crates/kebab-llm-local/tests/integration.rs similarity index 92% rename from crates/kb-llm-local/tests/integration.rs rename to crates/kebab-llm-local/tests/integration.rs index 6d33813..16a237c 100644 --- a/crates/kb-llm-local/tests/integration.rs +++ b/crates/kebab-llm-local/tests/integration.rs @@ -11,9 +11,9 @@ //! These hit `http://127.0.0.1:11434` directly and require an actual model //! pulled locally. CI runs default (non-ignored) tests only. -use kb_config::Config; -use kb_core::{GenerateRequest, TokenChunk}; -use kb_llm_local::{LanguageModel, OllamaLanguageModel}; +use kebab_config::Config; +use kebab_core::{GenerateRequest, TokenChunk}; +use kebab_llm_local::{LanguageModel, OllamaLanguageModel}; #[test] #[ignore = "requires a local Ollama daemon + pulled model"] diff --git a/crates/kb-llm-local/tests/streaming.rs b/crates/kebab-llm-local/tests/streaming.rs similarity index 99% rename from crates/kb-llm-local/tests/streaming.rs rename to crates/kebab-llm-local/tests/streaming.rs index f52d04e..cc25940 100644 --- a/crates/kb-llm-local/tests/streaming.rs +++ b/crates/kebab-llm-local/tests/streaming.rs @@ -10,9 +10,9 @@ //! error mapping, finish-reason mapping, missing-counter degradation, and //! determinism semantics. -use kb_config::Config; -use kb_core::{FinishReason, GenerateRequest, TokenChunk}; -use kb_llm_local::{LanguageModel, LlmError, OllamaLanguageModel}; +use kebab_config::Config; +use kebab_core::{FinishReason, GenerateRequest, TokenChunk}; +use kebab_llm_local::{LanguageModel, LlmError, OllamaLanguageModel}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; diff --git a/crates/kb-llm/Cargo.toml b/crates/kebab-llm/Cargo.toml similarity index 90% rename from crates/kb-llm/Cargo.toml rename to crates/kebab-llm/Cargo.toml index f70ab76..15684b5 100644 --- a/crates/kb-llm/Cargo.toml +++ b/crates/kebab-llm/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-llm" +name = "kebab-llm" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,7 +8,7 @@ repository = { workspace = true } description = "LanguageModel trait re-export + feature-gated MockLanguageModel for downstream tests" [dependencies] -kb-core = { path = "../kb-core" } +kebab-core = { path = "../kebab-core" } anyhow = { workspace = true } [features] diff --git a/crates/kb-llm/src/lib.rs b/crates/kebab-llm/src/lib.rs similarity index 94% rename from crates/kb-llm/src/lib.rs rename to crates/kebab-llm/src/lib.rs index d2d7ccd..5001c52 100644 --- a/crates/kb-llm/src/lib.rs +++ b/crates/kebab-llm/src/lib.rs @@ -1,8 +1,8 @@ //! `kb-llm` — thin re-export crate for the [`LanguageModel`] trait surface. //! //! This crate exists so downstream code (`kb-rag`, adapters in p4-2) can -//! `use kb_llm::LanguageModel` and stay stable across kb-core reorganizations. -//! It defines **no new types**; everything is a re-export of [`kb_core`]. +//! `use kebab_llm::LanguageModel` and stay stable across kb-core reorganizations. +//! It defines **no new types**; everything is a re-export of [`kebab_core`]. //! //! ## Mock implementation //! @@ -20,7 +20,7 @@ // Per spec §7.2 — these are the only public-surface types this crate offers. // Adding new types is forbidden by the task contract. -pub use kb_core::{ +pub use kebab_core::{ FinishReason, GenerateRequest, LanguageModel, ModelRef, TokenChunk, TokenUsage, }; diff --git a/crates/kb-llm/src/mock.rs b/crates/kebab-llm/src/mock.rs similarity index 99% rename from crates/kb-llm/src/mock.rs rename to crates/kebab-llm/src/mock.rs index e84c90d..c63faef 100644 --- a/crates/kb-llm/src/mock.rs +++ b/crates/kebab-llm/src/mock.rs @@ -36,7 +36,7 @@ //! - No tokenizer. `usage.prompt_tokens` / `completion_tokens` are whatever //! the constructor was given — the mock does not count. -use kb_core::{ +use kebab_core::{ FinishReason, GenerateRequest, LanguageModel, ModelRef, TokenChunk, TokenUsage, }; diff --git a/crates/kb-llm/tests/mock.rs b/crates/kebab-llm/tests/mock.rs similarity index 99% rename from crates/kb-llm/tests/mock.rs rename to crates/kebab-llm/tests/mock.rs index c086cda..2d5bbbd 100644 --- a/crates/kb-llm/tests/mock.rs +++ b/crates/kebab-llm/tests/mock.rs @@ -4,7 +4,7 @@ #![cfg(feature = "mock")] -use kb_llm::{ +use kebab_llm::{ FinishReason, GenerateRequest, LanguageModel, MockLanguageModel, TokenChunk, TokenUsage, assert_finish_chunk, }; diff --git a/crates/kb-llm/tests/reexports.rs b/crates/kebab-llm/tests/reexports.rs similarity index 99% rename from crates/kb-llm/tests/reexports.rs rename to crates/kebab-llm/tests/reexports.rs index 88f5db2..8df88d5 100644 --- a/crates/kb-llm/tests/reexports.rs +++ b/crates/kebab-llm/tests/reexports.rs @@ -5,7 +5,7 @@ //! Runs under both `cargo test -p kb-llm` and //! `cargo test -p kb-llm --features mock`. -use kb_llm::{ +use kebab_llm::{ FinishReason, GenerateRequest, LanguageModel, ModelRef, TokenChunk, TokenUsage, assert_finish_chunk, }; diff --git a/crates/kb-normalize/Cargo.toml b/crates/kebab-normalize/Cargo.toml similarity index 85% rename from crates/kb-normalize/Cargo.toml rename to crates/kebab-normalize/Cargo.toml index 6d61e35..9138837 100644 --- a/crates/kb-normalize/Cargo.toml +++ b/crates/kebab-normalize/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-normalize" +name = "kebab-normalize" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "Lift parser output (kb-parse-types) into kb-core::CanonicalDocument with deterministic IDs (§3.4, §4.2, §4.3)" [dependencies] -kb-core = { path = "../kb-core" } -kb-parse-types = { path = "../kb-parse-types" } +kebab-core = { path = "../kebab-core" } +kebab-parse-types = { path = "../kebab-parse-types" } serde = { workspace = true } serde_json = { workspace = true } unicode-normalization = "0.1" @@ -23,5 +23,5 @@ tracing = { workspace = true } # Forbidden as a regular dep per design §8 (kb-normalize must not depend # on any specific parser); `cargo tree -p kb-normalize --depth 1` (the # default scope, excluding dev-deps) confirms this. -kb-parse-md = { path = "../kb-parse-md" } +kebab-parse-md = { path = "../kebab-parse-md" } serde_json = { workspace = true } diff --git a/crates/kb-normalize/src/lib.rs b/crates/kebab-normalize/src/lib.rs similarity index 97% rename from crates/kb-normalize/src/lib.rs rename to crates/kebab-normalize/src/lib.rs index 9e08c76..2723d6f 100644 --- a/crates/kb-normalize/src/lib.rs +++ b/crates/kebab-normalize/src/lib.rs @@ -1,5 +1,5 @@ //! `kb-normalize` — lift parser output (`kb-parse-types`) into a -//! [`kb_core::CanonicalDocument`] with deterministic IDs. +//! [`kebab_core::CanonicalDocument`] with deterministic IDs. //! //! Per design §3.4 (CanonicalDocument / Block), §4.2 (ID recipe), §4.3 //! (ordinal rule), §3.6 (Provenance), §8 (module boundaries). @@ -20,16 +20,16 @@ use std::collections::HashMap; use anyhow::Result; -use kb_core::{ +use kebab_core::{ Block, BlockId, CanonicalDocument, CodeBlock, CommonBlock, DocumentId, HeadingBlock, ImageRefBlock, Inline, Lang, ListBlock, Metadata, ParserVersion, Provenance, ProvenanceEvent, ProvenanceKind, RawAsset, TableBlock, TextBlock, }; -use kb_parse_types::{ParsedBlock, ParsedPayload, Warning, WarningKind}; +use kebab_parse_types::{ParsedBlock, ParsedPayload, Warning, WarningKind}; use time::OffsetDateTime; use unicode_normalization::UnicodeNormalization; -pub use kb_core::{id_for_block, id_for_doc}; +pub use kebab_core::{id_for_block, id_for_doc}; /// Build a [`CanonicalDocument`] from the raw asset, frontmatter /// metadata, parser blocks, parser version, and any warnings. @@ -38,7 +38,7 @@ pub use kb_core::{id_for_block, id_for_doc}; /// /// * `doc_id = id_for_doc(workspace_path, asset_id, parser_version)` — /// `workspace_path` is consumed verbatim from `asset` (already NFC + -/// POSIX per `kb_core::normalize::to_posix`). +/// POSIX per `kebab_core::normalize::to_posix`). /// * `block_id = id_for_block(doc_id, kind, heading_path, ordinal, /// source_span)` — `ordinal` is **0-based, scoped to (heading_path, /// block_kind), in document order** per §4.3. @@ -329,7 +329,7 @@ fn flatten_inline(i: &Inline, out: &mut String) { #[cfg(test)] mod tests { use super::*; - use kb_core::{ + use kebab_core::{ AssetId, AssetStorage, Checksum, MediaType, SourceSpan, SourceType, SourceUri, TrustLevel, WorkspacePath, normalize::to_posix, }; @@ -386,7 +386,7 @@ mod tests { let h1_b = vec!["B".to_string()]; vec![ ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Paragraph, + kind: kebab_parse_types::ParsedBlockKind::Paragraph, heading_path: h1_a.clone(), source_span: SourceSpan::Line { start: 1, end: 1 }, payload: ParsedPayload::Paragraph { @@ -395,7 +395,7 @@ mod tests { }, }, ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Paragraph, + kind: kebab_parse_types::ParsedBlockKind::Paragraph, heading_path: h1_a.clone(), source_span: SourceSpan::Line { start: 2, end: 2 }, payload: ParsedPayload::Paragraph { @@ -404,7 +404,7 @@ mod tests { }, }, ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Paragraph, + kind: kebab_parse_types::ParsedBlockKind::Paragraph, heading_path: h1_a.clone(), source_span: SourceSpan::Line { start: 3, end: 3 }, payload: ParsedPayload::Paragraph { @@ -413,7 +413,7 @@ mod tests { }, }, ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Code, + kind: kebab_parse_types::ParsedBlockKind::Code, heading_path: h1_a, source_span: SourceSpan::Line { start: 4, end: 5 }, payload: ParsedPayload::Code { @@ -422,7 +422,7 @@ mod tests { }, }, ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Paragraph, + kind: kebab_parse_types::ParsedBlockKind::Paragraph, heading_path: h1_b, source_span: SourceSpan::Line { start: 6, end: 6 }, payload: ParsedPayload::Paragraph { @@ -715,7 +715,7 @@ mod tests { fn audio_ref_block_skipped_with_warning() { let span = SourceSpan::Line { start: 1, end: 1 }; let blocks = vec![ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::AudioRef, + kind: kebab_parse_types::ParsedBlockKind::AudioRef, heading_path: vec![], source_span: span, payload: ParsedPayload::AudioRef { @@ -759,7 +759,7 @@ mod tests { let nfd_heading = "\u{1100}\u{1161}".to_string(); // 가 (NFD) let nfc_heading = "\u{AC00}".to_string(); // 가 (NFC) let mk_block = |heading: String| ParsedBlock { - kind: kb_parse_types::ParsedBlockKind::Paragraph, + kind: kebab_parse_types::ParsedBlockKind::Paragraph, heading_path: vec![heading], source_span: span.clone(), payload: ParsedPayload::Paragraph { diff --git a/crates/kb-normalize/tests/normalize_snapshot.rs b/crates/kebab-normalize/tests/normalize_snapshot.rs similarity index 97% rename from crates/kb-normalize/tests/normalize_snapshot.rs rename to crates/kebab-normalize/tests/normalize_snapshot.rs index ec10ddf..65b04f6 100644 --- a/crates/kb-normalize/tests/normalize_snapshot.rs +++ b/crates/kebab-normalize/tests/normalize_snapshot.rs @@ -15,12 +15,12 @@ use std::path::PathBuf; -use kb_core::{ +use kebab_core::{ AssetId, AssetStorage, Checksum, MediaType, ParserVersion, RawAsset, SourceUri, WorkspacePath, }; -use kb_normalize::build_canonical_document; -use kb_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; +use kebab_normalize::build_canonical_document; +use kebab_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; use serde_json::Value; use time::OffsetDateTime; diff --git a/crates/kb-parse-md/Cargo.toml b/crates/kebab-parse-md/Cargo.toml similarity index 92% rename from crates/kb-parse-md/Cargo.toml rename to crates/kebab-parse-md/Cargo.toml index 3b39606..912800a 100644 --- a/crates/kb-parse-md/Cargo.toml +++ b/crates/kebab-parse-md/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-parse-md" +name = "kebab-parse-md" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "Markdown frontmatter and block parsing into kb-core::Metadata / kb-parse-types intermediates" [dependencies] -kb-core = { path = "../kb-core" } -kb-parse-types = { path = "../kb-parse-types" } +kebab-core = { path = "../kebab-core" } +kebab-parse-types = { path = "../kebab-parse-types" } anyhow = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/kb-parse-md/src/blocks.rs b/crates/kebab-parse-md/src/blocks.rs similarity index 99% rename from crates/kb-parse-md/src/blocks.rs rename to crates/kebab-parse-md/src/blocks.rs index e2d7daa..19b5001 100644 --- a/crates/kb-parse-md/src/blocks.rs +++ b/crates/kebab-parse-md/src/blocks.rs @@ -1,10 +1,10 @@ -//! Markdown body → flat `Vec` (§3.4 / §3.7b). +//! Markdown body → flat `Vec` (§3.4 / §3.7b). //! //! Uses `pulldown-cmark` (with GFM tables enabled at runtime via //! `Options::ENABLE_TABLES`) to walk the body once and emit a flat list of //! parsed blocks. Heading paths are computed by tracking the most-recent //! heading text at each level. Source spans are reported as -//! [`kb_core::SourceSpan::Line`] in 1-indexed file-line coordinates by +//! [`kebab_core::SourceSpan::Line`] in 1-indexed file-line coordinates by //! converting `pulldown-cmark`'s byte offsets to line numbers and adding the //! caller-supplied `body_offset_lines`. //! @@ -19,10 +19,10 @@ //! //! ## Inline filter //! -//! [`kb_core::Inline`] only models `Text | Code | Link | Strong | Emph`. +//! [`kebab_core::Inline`] only models `Text | Code | Link | Strong | Emph`. //! Inline images, footnotes, hard breaks, etc. are dropped silently per //! design §3.4. Block-level `![alt](src)` (an image as the sole content of a -//! paragraph) is lifted to [`kb_parse_types::ParsedPayload::ImageRef`]. +//! paragraph) is lifted to [`kebab_parse_types::ParsedPayload::ImageRef`]. //! //! ## CRLF //! @@ -33,8 +33,8 @@ use std::ops::Range; -use kb_core::{Inline, SourceSpan}; -use kb_parse_types::{ParsedBlock, ParsedBlockKind, ParsedPayload, Warning, WarningKind}; +use kebab_core::{Inline, SourceSpan}; +use kebab_parse_types::{ParsedBlock, ParsedBlockKind, ParsedPayload, Warning, WarningKind}; use pulldown_cmark::{CodeBlockKind, Event, HeadingLevel, Options, Parser, Tag, TagEnd}; /// Parse a Markdown body into a flat `Vec` plus any warnings. @@ -1595,7 +1595,7 @@ mod tests { let (blocks, _) = parse(body, 1); assert_eq!(blocks.len(), 1, "expected single list block"); match &blocks[0].kind { - kb_parse_types::ParsedBlockKind::List => {} + kebab_parse_types::ParsedBlockKind::List => {} other => panic!("expected list, got {other:?}"), } } diff --git a/crates/kb-parse-md/src/frontmatter.rs b/crates/kebab-parse-md/src/frontmatter.rs similarity index 99% rename from crates/kb-parse-md/src/frontmatter.rs rename to crates/kebab-parse-md/src/frontmatter.rs index 93230da..86d3f80 100644 --- a/crates/kb-parse-md/src/frontmatter.rs +++ b/crates/kebab-parse-md/src/frontmatter.rs @@ -1,4 +1,4 @@ -//! Markdown frontmatter parsing → `kb_core::Metadata`. +//! Markdown frontmatter parsing → `kebab_core::Metadata`. //! //! Implements the contract pinned in design §0 Q9 (frontmatter derive table) //! and §3.6 (Metadata shape). Produces structured warnings via @@ -18,8 +18,8 @@ use std::ops::Range; use std::sync::OnceLock; -use kb_core::{Metadata, SourceType, TrustLevel}; -use kb_parse_types::{Warning, WarningKind}; +use kebab_core::{Metadata, SourceType, TrustLevel}; +use kebab_parse_types::{Warning, WarningKind}; use lingua::{IsoCode639_1, Language, LanguageDetector, LanguageDetectorBuilder}; use serde::Deserialize; use serde_json::{Map, Value}; @@ -59,7 +59,7 @@ pub struct FrontmatterSpan { } /// Parse the frontmatter (if any) from a Markdown byte slice into a -/// `kb_core::Metadata`, applying the §0 Q9 derive table for missing fields. +/// `kebab_core::Metadata`, applying the §0 Q9 derive table for missing fields. /// /// On a malformed frontmatter the function still returns `Ok` — the /// frontmatter contents are discarded and the caller is told via a @@ -589,7 +589,7 @@ fn iso_code(lang: Language) -> &'static str { #[cfg(test)] mod tests { use super::*; - use kb_core::{ + use kebab_core::{ AssetId, WorkspacePath, ids::id_for_doc, versions::ParserVersion, diff --git a/crates/kb-parse-md/src/lib.rs b/crates/kebab-parse-md/src/lib.rs similarity index 100% rename from crates/kb-parse-md/src/lib.rs rename to crates/kebab-parse-md/src/lib.rs diff --git a/crates/kb-parse-md/tests/blocks_snapshots.rs b/crates/kebab-parse-md/tests/blocks_snapshots.rs similarity index 95% rename from crates/kb-parse-md/tests/blocks_snapshots.rs rename to crates/kebab-parse-md/tests/blocks_snapshots.rs index 1c569ee..2de7bee 100644 --- a/crates/kb-parse-md/tests/blocks_snapshots.rs +++ b/crates/kebab-parse-md/tests/blocks_snapshots.rs @@ -10,13 +10,13 @@ //! env-var pattern. Migrating kb-parse-md to the env-var style is out of //! scope; both styles are intentional for now. //! -//! Following the kb_core::Inline schema migration (struct-variant shape), +//! Following the kebab_core::Inline schema migration (struct-variant shape), //! `ParsedBlock` now serializes directly through serde — no projection //! shim is required. Inlines surface as structured objects, e.g. //! `[{"kind":"text","text":"…"},{"kind":"code","code":"…"}]`. -use kb_parse_md::parse_blocks; -use kb_parse_types::{ParsedBlock, Warning}; +use kebab_parse_md::parse_blocks; +use kebab_parse_types::{ParsedBlock, Warning}; use serde::Serialize; use serde_json::Value; use std::fs; diff --git a/crates/kb-parse-md/tests/frontmatter_snapshots.rs b/crates/kebab-parse-md/tests/frontmatter_snapshots.rs similarity index 96% rename from crates/kb-parse-md/tests/frontmatter_snapshots.rs rename to crates/kebab-parse-md/tests/frontmatter_snapshots.rs index 84c6bcc..da22976 100644 --- a/crates/kb-parse-md/tests/frontmatter_snapshots.rs +++ b/crates/kebab-parse-md/tests/frontmatter_snapshots.rs @@ -5,7 +5,7 @@ //! and therefore stable; lingua autodetect over our fixtures is also //! stable for the language set we configured. -use kb_parse_md::{BodyHints, parse_frontmatter}; +use kebab_parse_md::{BodyHints, parse_frontmatter}; use serde::Serialize; use serde_json::Value; use std::fs; @@ -18,9 +18,9 @@ use time::macros::datetime; /// snapshot focuses on the §0 Q9 derive contract. #[derive(Serialize)] struct Snapshot { - metadata: kb_core::Metadata, + metadata: kebab_core::Metadata, span_present: bool, - warnings: Vec, + warnings: Vec, } fn fixtures_dir() -> PathBuf { diff --git a/crates/kb-parse-types/Cargo.toml b/crates/kebab-parse-types/Cargo.toml similarity index 82% rename from crates/kb-parse-types/Cargo.toml rename to crates/kebab-parse-types/Cargo.toml index 6f79453..58bc504 100644 --- a/crates/kb-parse-types/Cargo.toml +++ b/crates/kebab-parse-types/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-parse-types" +name = "kebab-parse-types" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,5 +8,5 @@ repository = { workspace = true } description = "Parser intermediate representations (no parser libs allowed)" [dependencies] -kb-core = { path = "../kb-core" } +kebab-core = { path = "../kebab-core" } serde = { workspace = true } diff --git a/crates/kb-parse-types/src/lib.rs b/crates/kebab-parse-types/src/lib.rs similarity index 92% rename from crates/kb-parse-types/src/lib.rs rename to crates/kebab-parse-types/src/lib.rs index e09016f..1b06c5c 100644 --- a/crates/kb-parse-types/src/lib.rs +++ b/crates/kebab-parse-types/src/lib.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; pub struct ParsedBlock { pub kind: ParsedBlockKind, pub heading_path: Vec, - pub source_span: kb_core::SourceSpan, + pub source_span: kebab_core::SourceSpan, pub payload: ParsedPayload, } @@ -36,11 +36,11 @@ pub enum ParsedPayload { }, Paragraph { text: String, - inlines: Vec, + inlines: Vec, }, List { ordered: bool, - items: Vec>, + items: Vec>, }, Code { lang: Option, @@ -52,7 +52,7 @@ pub enum ParsedPayload { }, Quote { text: String, - inlines: Vec, + inlines: Vec, }, ImageRef { src: String, diff --git a/crates/kb-rag/Cargo.toml b/crates/kebab-rag/Cargo.toml similarity index 70% rename from crates/kb-rag/Cargo.toml rename to crates/kebab-rag/Cargo.toml index c255008..7afcfad 100644 --- a/crates/kb-rag/Cargo.toml +++ b/crates/kebab-rag/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-rag" +name = "kebab-rag" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,11 +8,11 @@ repository = { workspace = true } description = "RAG pipeline: retrieve → gate → pack → generate → cite-validate" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-search = { path = "../kb-search" } -kb-llm = { path = "../kb-llm" } -kb-store-sqlite = { path = "../kb-store-sqlite" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-search = { path = "../kebab-search" } +kebab-llm = { path = "../kebab-llm" } +kebab-store-sqlite = { path = "../kebab-store-sqlite" } serde = { workspace = true } serde_json = { workspace = true } regex = { workspace = true } @@ -23,7 +23,7 @@ anyhow = { workspace = true } blake3 = { workspace = true } [dev-dependencies] -kb-llm = { path = "../kb-llm", features = ["mock"] } +kebab-llm = { path = "../kebab-llm", features = ["mock"] } tempfile = { workspace = true } rusqlite = { workspace = true } serde_json = { workspace = true } diff --git a/crates/kb-rag/src/lib.rs b/crates/kebab-rag/src/lib.rs similarity index 92% rename from crates/kb-rag/src/lib.rs rename to crates/kebab-rag/src/lib.rs index c019862..a883dae 100644 --- a/crates/kb-rag/src/lib.rs +++ b/crates/kebab-rag/src/lib.rs @@ -18,7 +18,7 @@ //! reachable via `Retriever`), `kb-embed*` (only via `Retriever`), //! `kb-llm-local` (only via `LanguageModel`), `kb-tui`, `kb-desktop`. -pub use kb_core::{Answer, AnswerCitation, AnswerRetrievalSummary, RefusalReason}; +pub use kebab_core::{Answer, AnswerCitation, AnswerRetrievalSummary, RefusalReason}; mod pipeline; diff --git a/crates/kb-rag/src/pipeline.rs b/crates/kebab-rag/src/pipeline.rs similarity index 98% rename from crates/kb-rag/src/pipeline.rs rename to crates/kebab-rag/src/pipeline.rs index 015d979..fe9fbe3 100644 --- a/crates/kb-rag/src/pipeline.rs +++ b/crates/kebab-rag/src/pipeline.rs @@ -33,13 +33,13 @@ use std::sync::Arc; use anyhow::{Context, Result}; -use kb_core::{ +use kebab_core::{ Answer, AnswerCitation, AnswerRetrievalSummary, Citation, FinishReason, GenerateRequest, LanguageModel, ModelRef, RefusalReason, Retriever, SearchFilters, SearchHit, SearchMode, SearchQuery, TokenChunk, TokenUsage, TraceId, }; -use kb_core::versions::PromptTemplateVersion; -use kb_store_sqlite::SqliteStore; +use kebab_core::versions::PromptTemplateVersion; +use kebab_store_sqlite::SqliteStore; use regex::Regex; use std::sync::OnceLock; use time::OffsetDateTime; @@ -86,7 +86,7 @@ pub struct AskOpts { /// Single-threaded RAG orchestrator. See module docs for the stage list. pub struct RagPipeline { - config: kb_config::Config, + config: kebab_config::Config, retriever: Arc, llm: Arc, docs: Arc, @@ -98,7 +98,7 @@ impl RagPipeline { /// `Arc`'d trait objects (kb-app builds them from config; tests /// inject mocks). pub fn new( - config: kb_config::Config, + config: kebab_config::Config, retriever: Arc, llm: Arc, docs: Arc, @@ -380,7 +380,7 @@ impl RagPipeline { for hit in hits { let chunk_full = - ::get_chunk(&self.docs, &hit.chunk_id) + ::get_chunk(&self.docs, &hit.chunk_id) .context("kb-rag: docs.get_chunk")?; let chunk_text = match chunk_full { Some(c) => c.text, @@ -542,7 +542,7 @@ impl RagPipeline { /// paths attach the configured embedding model so `kb explain` can /// later identify which embedder shaped the retrieval (even on /// refusals — see `refuse_score_gate`). -fn embedding_ref_for(mode: SearchMode, cfg: &kb_config::Config) -> Option { +fn embedding_ref_for(mode: SearchMode, cfg: &kebab_config::Config) -> Option { match mode { SearchMode::Lexical => None, SearchMode::Vector | SearchMode::Hybrid => Some(ModelRef { diff --git a/crates/kb-rag/tests/common/mod.rs b/crates/kebab-rag/tests/common/mod.rs similarity index 97% rename from crates/kb-rag/tests/common/mod.rs rename to crates/kebab-rag/tests/common/mod.rs index 1e6a3c1..95892c2 100644 --- a/crates/kb-rag/tests/common/mod.rs +++ b/crates/kebab-rag/tests/common/mod.rs @@ -14,12 +14,12 @@ use std::sync::Arc; -use kb_config::Config; -use kb_core::{ +use kebab_config::Config; +use kebab_core::{ ChunkerVersion, ChunkId, Citation, DocumentId, IndexVersion, RetrievalDetail, Retriever, SearchHit, SearchMode, SearchQuery, WorkspacePath, }; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use rusqlite::params; use tempfile::TempDir; @@ -176,7 +176,7 @@ impl Retriever for MockRetriever { } } -/// Pad a short prefix to the 32-hex shape `kb_core` newtypes expect. +/// Pad a short prefix to the 32-hex shape `kebab_core` newtypes expect. pub fn id32(prefix: &str) -> String { let mut s = prefix.to_string(); while s.len() < 32 { diff --git a/crates/kb-rag/tests/pipeline.rs b/crates/kebab-rag/tests/pipeline.rs similarity index 99% rename from crates/kb-rag/tests/pipeline.rs rename to crates/kebab-rag/tests/pipeline.rs index cd49da6..874cbf9 100644 --- a/crates/kb-rag/tests/pipeline.rs +++ b/crates/kebab-rag/tests/pipeline.rs @@ -10,11 +10,11 @@ use std::sync::Arc; use std::sync::atomic::Ordering; use common::{MockRetriever, RagEnv, id32, mk_hit}; -use kb_core::{ +use kebab_core::{ FinishReason, LanguageModel, Retriever, SearchMode, TokenChunk, TokenUsage, }; -use kb_llm::MockLanguageModel; -use kb_rag::{AskOpts, RagPipeline, RefusalReason}; +use kebab_llm::MockLanguageModel; +use kebab_rag::{AskOpts, RagPipeline, RefusalReason}; /// LM ID used everywhere — kept short so snapshots stay stable. const TEST_LM_ID: &str = "mock-lm"; @@ -49,7 +49,7 @@ impl CountingLm { } impl LanguageModel for CountingLm { - fn model_ref(&self) -> kb_core::ModelRef { + fn model_ref(&self) -> kebab_core::ModelRef { self.inner.model_ref() } fn context_tokens(&self) -> usize { @@ -57,7 +57,7 @@ impl LanguageModel for CountingLm { } fn generate_stream( &self, - req: kb_core::GenerateRequest, + req: kebab_core::GenerateRequest, ) -> anyhow::Result> + Send>> { self.calls.fetch_add(1, Ordering::SeqCst); self.inner.generate_stream(req) diff --git a/crates/kb-search/Cargo.toml b/crates/kebab-search/Cargo.toml similarity index 79% rename from crates/kb-search/Cargo.toml rename to crates/kebab-search/Cargo.toml index b5a8295..f195b81 100644 --- a/crates/kb-search/Cargo.toml +++ b/crates/kebab-search/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-search" +name = "kebab-search" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,17 +8,17 @@ repository = { workspace = true } description = "Retriever implementations for kb (P2-2 lexical FTS5; P3 vector / hybrid will follow)" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } -kb-store-sqlite = { path = "../kb-store-sqlite" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } +kebab-store-sqlite = { path = "../kebab-store-sqlite" } # P3-4 hybrid retriever wraps a `dyn VectorStore` (typically backed by # `kb-store-vector::LanceVectorStore`) and a `dyn Embedder` (any P3-2 # adapter). Listed as a runtime dep so callers can construct # `VectorRetriever::new` against the trait objects without a concrete # adapter — the concrete adapter (`kb-embed-local`) stays out of this # crate per the spec's Forbidden deps list. -kb-store-vector = { path = "../kb-store-vector" } -kb-embed = { path = "../kb-embed" } +kebab-store-vector = { path = "../kebab-store-vector" } +kebab-embed = { path = "../kebab-embed" } rusqlite = { workspace = true } globset = { workspace = true } serde_json = { workspace = true } @@ -32,4 +32,4 @@ tempfile = { workspace = true } # feature) and stand up a real `LanceVectorStore` on a tmp directory. # The mock-retriever unit tests (the bulk of the hybrid suite) do not # need either, but the integration / snapshot lane does. -kb-embed = { path = "../kb-embed", features = ["mock"] } +kebab-embed = { path = "../kebab-embed", features = ["mock"] } diff --git a/crates/kb-search/src/citation_helper.rs b/crates/kebab-search/src/citation_helper.rs similarity index 95% rename from crates/kb-search/src/citation_helper.rs rename to crates/kebab-search/src/citation_helper.rs index d0c0068..3001640 100644 --- a/crates/kb-search/src/citation_helper.rs +++ b/crates/kebab-search/src/citation_helper.rs @@ -1,4 +1,4 @@ -//! Shared helpers for building `kb_core::Citation` values from a +//! Shared helpers for building `kebab_core::Citation` values from a //! chunk's first `SourceSpan`. //! //! Both the lexical and vector retrievers join against the same @@ -9,7 +9,7 @@ //! §1.6). Living here means a future PDF / image / audio extractor can //! enrich the mapping in one place rather than two. -use kb_core::{Citation, SourceSpan, WorkspacePath}; +use kebab_core::{Citation, SourceSpan, WorkspacePath}; /// Build a `Citation` from the chunk's first `SourceSpan`. P1 markdown /// only emits `Line`, so the other variants are mostly defensive — we diff --git a/crates/kb-search/src/hybrid.rs b/crates/kebab-search/src/hybrid.rs similarity index 99% rename from crates/kb-search/src/hybrid.rs rename to crates/kebab-search/src/hybrid.rs index 5741566..e1c036b 100644 --- a/crates/kb-search/src/hybrid.rs +++ b/crates/kebab-search/src/hybrid.rs @@ -20,7 +20,7 @@ use std::collections::HashMap; use std::sync::Arc; use anyhow::Result; -use kb_core::{ +use kebab_core::{ IndexVersion, RetrievalDetail, Retriever, SearchHit, SearchMode, SearchQuery, }; @@ -75,7 +75,7 @@ impl HybridRetriever { /// retrievers. Reads `config.search.hybrid_fusion` (only `"rrf"` /// is recognised today) and `config.search.rrf_k`. pub fn new( - config: &kb_config::Config, + config: &kebab_config::Config, lexical: Arc, vector: Arc, ) -> Self { @@ -335,7 +335,7 @@ fn parse_fusion(name: &str, k_rrf: u32) -> FusionPolicy { #[cfg(test)] mod tests { use super::*; - use kb_core::{ + use kebab_core::{ ChunkId, ChunkerVersion, Citation, DocumentId, IndexVersion, SearchFilters, SearchHit, SearchMode, WorkspacePath, }; diff --git a/crates/kb-search/src/lexical.rs b/crates/kebab-search/src/lexical.rs similarity index 98% rename from crates/kb-search/src/lexical.rs rename to crates/kebab-search/src/lexical.rs index 0912348..71d32c6 100644 --- a/crates/kb-search/src/lexical.rs +++ b/crates/kebab-search/src/lexical.rs @@ -1,7 +1,7 @@ //! Lexical (FTS5 + bm25) retriever — design §3.7 / §1.5 / §2.2 / §6.4. //! //! Owns the SQL pattern documented in `tasks/p2/p2-2-lexical-retriever.md` -//! and constructs `kb_core::SearchHit` values directly from the joined +//! and constructs `kebab_core::SearchHit` values directly from the joined //! `chunks_fts` / `chunks` / `documents` rows. Reads only — never mutates //! the underlying SQLite file. @@ -9,12 +9,12 @@ use std::sync::Arc; use anyhow::{Context, Result}; use globset::GlobMatcher; -use kb_core::{ +use kebab_core::{ ChunkId, ChunkerVersion, DocumentId, IndexVersion, RetrievalDetail, Retriever, SearchFilters, SearchHit, SearchMode, SearchQuery, SourceSpan, TrustLevel, WorkspacePath, }; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use rusqlite::{params_from_iter, Connection, Row, ToSql}; use crate::citation_helper::citation_from_first_span; @@ -57,7 +57,7 @@ impl LexicalRetriever { /// Construct with default settings derived from `kb-config`'s defaults. /// Snippet width is computed from `Config::defaults().search.snippet_chars`. pub fn new(store: Arc, index_version: IndexVersion) -> Self { - let cfg = kb_config::Config::defaults(); + let cfg = kebab_config::Config::defaults(); Self::with_settings(store, index_version, cfg.search.snippet_chars) } @@ -297,7 +297,7 @@ fn run_query( params.push(Box::new(lang.0.clone())); } if let Some(trust_min) = &filters.trust_min { - // Mirror `kb_store_sqlite::documents::list_documents` ranking: + // Mirror `kebab_store_sqlite::documents::list_documents` ranking: // Generated < Secondary < Primary. Doing the rank in SQL // (rather than post-filtering) keeps the row stream short // when the workspace contains many low-trust docs. @@ -523,7 +523,7 @@ mod tests { #[test] fn build_citation_line_round_trip() { - use kb_core::Citation; + use kebab_core::Citation; let p = WorkspacePath::new("a/b.md".to_string()).unwrap(); let span = SourceSpan::Line { start: 7, end: 12 }; let c = citation_from_first_span("c1", p.clone(), Some("S1".to_string()), Some(&span)); @@ -545,7 +545,7 @@ mod tests { #[test] fn build_citation_page_forwards_section() { - use kb_core::Citation; + use kebab_core::Citation; let p = WorkspacePath::new("doc.pdf".to_string()).unwrap(); let span = SourceSpan::Page { page: 4, @@ -568,7 +568,7 @@ mod tests { #[test] fn build_citation_none_falls_back_to_line_one() { - use kb_core::Citation; + use kebab_core::Citation; let p = WorkspacePath::new("x.md".to_string()).unwrap(); let c = citation_from_first_span("c1", p, None, None); match c { diff --git a/crates/kb-search/src/lib.rs b/crates/kebab-search/src/lib.rs similarity index 94% rename from crates/kb-search/src/lib.rs rename to crates/kebab-search/src/lib.rs index 63708b6..47f832d 100644 --- a/crates/kb-search/src/lib.rs +++ b/crates/kebab-search/src/lib.rs @@ -1,4 +1,4 @@ -//! `kb-search` — `kb_core::Retriever` implementations. +//! `kb-search` — `kebab_core::Retriever` implementations. //! //! - [`LexicalRetriever`] (P2-2): SQLite-FTS5 + bm25 backed retriever //! for `SearchMode::Lexical`. diff --git a/crates/kb-search/src/vector.rs b/crates/kebab-search/src/vector.rs similarity index 98% rename from crates/kb-search/src/vector.rs rename to crates/kebab-search/src/vector.rs index 8fdd0f9..77ff2a1 100644 --- a/crates/kb-search/src/vector.rs +++ b/crates/kebab-search/src/vector.rs @@ -1,7 +1,7 @@ //! Vector retriever — design §3.7 / §7.2 / §1.6. //! //! Wraps a `dyn VectorStore` + `dyn Embedder` + the SQLite metadata -//! store into a `kb_core::Retriever`. The vector store knows how to +//! store into a `kebab_core::Retriever`. The vector store knows how to //! find the nearest chunks by cosine on the embedding column; SQLite //! owns the human-readable metadata (heading_path / section_label / //! source_spans / chunker_version / workspace_path) needed for @@ -19,12 +19,12 @@ use std::collections::HashMap; use std::sync::Arc; use anyhow::{Context, Result}; -use kb_core::{ +use kebab_core::{ ChunkId, ChunkerVersion, DocumentId, Embedder, EmbeddingInput, EmbeddingKind, IndexVersion, RetrievalDetail, Retriever, SearchHit, SearchMode, SearchQuery, SourceSpan, VectorHit, VectorStore, WorkspacePath, }; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use rusqlite::params_from_iter; use crate::citation_helper::citation_from_first_span; @@ -67,7 +67,7 @@ impl VectorRetriever { sqlite: Arc, index_version: IndexVersion, ) -> Self { - let cfg = kb_config::Config::defaults(); + let cfg = kebab_config::Config::defaults(); Self::with_settings(store, embed, sqlite, index_version, cfg.search.snippet_chars) } @@ -268,7 +268,7 @@ fn build_hit( meta: &ChunkMeta, rank: u32, index_version: &IndexVersion, - model_id: &kb_core::EmbeddingModelId, + model_id: &kebab_core::EmbeddingModelId, snippet_chars: usize, ) -> Result { let heading_path: Vec = serde_json::from_str(&meta.heading_path_json) diff --git a/crates/kb-search/tests/common/mod.rs b/crates/kebab-search/tests/common/mod.rs similarity index 96% rename from crates/kb-search/tests/common/mod.rs rename to crates/kebab-search/tests/common/mod.rs index 5ff9db8..69b87bd 100644 --- a/crates/kb-search/tests/common/mod.rs +++ b/crates/kebab-search/tests/common/mod.rs @@ -16,15 +16,15 @@ use std::sync::Arc; -use kb_config::Config; -use kb_core::{ +use kebab_config::Config; +use kebab_core::{ ChunkId, DocumentId, EmbeddingId, EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, IndexVersion, VectorRecord, VectorStore, }; -use kb_embed::{Embedder, MockEmbedder}; -use kb_search::{LexicalRetriever, VectorRetriever}; -use kb_store_sqlite::SqliteStore; -use kb_store_vector::LanceVectorStore; +use kebab_embed::{Embedder, MockEmbedder}; +use kebab_search::{LexicalRetriever, VectorRetriever}; +use kebab_store_sqlite::SqliteStore; +use kebab_store_vector::LanceVectorStore; use rusqlite::params; use tempfile::TempDir; @@ -205,7 +205,7 @@ impl HybridEnv { } } -/// Pad a short prefix to the 32-hex shape `kb_core` newtypes expect. +/// Pad a short prefix to the 32-hex shape `kebab_core` newtypes expect. pub fn id32(prefix: &str) -> String { let mut s = prefix.to_string(); while s.len() < 32 { diff --git a/crates/kb-search/tests/fixtures/search/hybrid/run-1.json b/crates/kebab-search/tests/fixtures/search/hybrid/run-1.json similarity index 100% rename from crates/kb-search/tests/fixtures/search/hybrid/run-1.json rename to crates/kebab-search/tests/fixtures/search/hybrid/run-1.json diff --git a/crates/kb-search/tests/fixtures/search/lexical/run-1.json b/crates/kebab-search/tests/fixtures/search/lexical/run-1.json similarity index 100% rename from crates/kb-search/tests/fixtures/search/lexical/run-1.json rename to crates/kebab-search/tests/fixtures/search/lexical/run-1.json diff --git a/crates/kb-search/tests/hybrid.rs b/crates/kebab-search/tests/hybrid.rs similarity index 99% rename from crates/kb-search/tests/hybrid.rs rename to crates/kebab-search/tests/hybrid.rs index b9279a6..af62bfc 100644 --- a/crates/kb-search/tests/hybrid.rs +++ b/crates/kebab-search/tests/hybrid.rs @@ -14,10 +14,10 @@ use std::sync::Arc; use common::{ HybridEnv, id32, require_avx_or_panic, TEST_LEX_INDEX_VERSION, TEST_VEC_INDEX_VERSION, }; -use kb_core::{ +use kebab_core::{ Retriever, SearchFilters, SearchHit, SearchMode, SearchQuery, }; -use kb_search::{FusionPolicy, HybridRetriever}; +use kebab_search::{FusionPolicy, HybridRetriever}; use serde_json::json; fn build_hybrid(env: &HybridEnv) -> HybridRetriever { diff --git a/crates/kb-search/tests/lexical.rs b/crates/kebab-search/tests/lexical.rs similarity index 98% rename from crates/kb-search/tests/lexical.rs rename to crates/kebab-search/tests/lexical.rs index 91a2b71..4cb98be 100644 --- a/crates/kb-search/tests/lexical.rs +++ b/crates/kebab-search/tests/lexical.rs @@ -7,10 +7,10 @@ use std::sync::Arc; -use kb_config::Config; -use kb_core::{IndexVersion, Lang, Retriever, SearchFilters, SearchMode, SearchQuery, TrustLevel}; -use kb_search::LexicalRetriever; -use kb_store_sqlite::SqliteStore; +use kebab_config::Config; +use kebab_core::{IndexVersion, Lang, Retriever, SearchFilters, SearchMode, SearchQuery, TrustLevel}; +use kebab_search::LexicalRetriever; +use kebab_store_sqlite::SqliteStore; use rusqlite::Connection; use tempfile::TempDir; @@ -143,7 +143,7 @@ fn insert_chunk( .expect("insert chunk"); } -/// Pad a short ID to the 32-hex shape kb_core newtypes expect. +/// Pad a short ID to the 32-hex shape kebab_core newtypes expect. fn id32(prefix: &str) -> String { let mut s = prefix.to_string(); while s.len() < 32 { @@ -237,7 +237,7 @@ fn lexical_single_doc_match_returns_one_hit_with_citation_round_trip() { // Citation round-trips through `to_uri`/`parse` (line variant). let uri = h.citation.to_uri(); - let parsed = kb_core::Citation::parse(&uri).expect("parse uri"); + let parsed = kebab_core::Citation::parse(&uri).expect("parse uri"); // Reparsed citation has section=None (URI fragment doesn't carry it), // so compare by `to_uri` equivalence rather than struct equality. assert_eq!(parsed.to_uri(), uri); @@ -457,7 +457,7 @@ fn lexical_citation_round_trip_against_first_source_span() { assert_eq!(hits.len(), 1); let uri = hits[0].citation.to_uri(); assert_eq!(uri, "notes/m.md#L12-L34"); - let parsed = kb_core::Citation::parse(&uri).unwrap(); + let parsed = kebab_core::Citation::parse(&uri).unwrap(); assert_eq!(parsed.to_uri(), uri); } diff --git a/crates/kb-source-fs/Cargo.toml b/crates/kebab-source-fs/Cargo.toml similarity index 84% rename from crates/kb-source-fs/Cargo.toml rename to crates/kebab-source-fs/Cargo.toml index 9274c3c..7429315 100644 --- a/crates/kb-source-fs/Cargo.toml +++ b/crates/kebab-source-fs/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-source-fs" +name = "kebab-source-fs" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "Local filesystem SourceConnector — walks workspace.root + applies gitignore filters" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } anyhow = { workspace = true } serde = { workspace = true } time = { workspace = true } diff --git a/crates/kb-source-fs/src/connector.rs b/crates/kebab-source-fs/src/connector.rs similarity index 97% rename from crates/kb-source-fs/src/connector.rs rename to crates/kebab-source-fs/src/connector.rs index ed94bc9..f904395 100644 --- a/crates/kb-source-fs/src/connector.rs +++ b/crates/kebab-source-fs/src/connector.rs @@ -3,10 +3,10 @@ //! ```ignore //! pub struct FsSourceConnector { /* internal */ } //! impl FsSourceConnector { -//! pub fn new(config: &kb_config::Config) -> anyhow::Result; +//! pub fn new(config: &kebab_config::Config) -> anyhow::Result; //! } -//! impl kb_core::SourceConnector for FsSourceConnector { -//! fn scan(&self, scope: &kb_core::SourceScope) -> anyhow::Result>; +//! impl kebab_core::SourceConnector for FsSourceConnector { +//! fn scan(&self, scope: &kebab_core::SourceScope) -> anyhow::Result>; //! } //! ``` @@ -15,8 +15,8 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use time::OffsetDateTime; -use kb_config::Config; -use kb_core::{ +use kebab_config::Config; +use kebab_core::{ AssetStorage, Checksum, RawAsset, SourceConnector, SourceScope, SourceUri, id_for_asset, to_posix, }; @@ -194,7 +194,7 @@ fn dirs_home() -> Option { #[cfg(test)] mod tests { use super::*; - use kb_config::Config; + use kebab_config::Config; fn cfg_with_root(root: &str) -> Config { let mut c = Config::defaults(); @@ -353,7 +353,7 @@ mod tests { #[test] fn scan_emits_posix_normalized_paths() { // End-to-end: the connector must produce POSIX-normalized - // workspace paths via `kb_core::to_posix`. We can't construct an + // workspace paths via `kebab_core::to_posix`. We can't construct an // input with literal `./` / `//` segments via the filesystem (the // OS won't let us), so instead we assert the resulting strings // are already POSIX-clean (no leading `./`, no `//`, forward diff --git a/crates/kb-source-fs/src/hash.rs b/crates/kebab-source-fs/src/hash.rs similarity index 98% rename from crates/kb-source-fs/src/hash.rs rename to crates/kebab-source-fs/src/hash.rs index b2b40a1..6ab61d0 100644 --- a/crates/kb-source-fs/src/hash.rs +++ b/crates/kebab-source-fs/src/hash.rs @@ -7,7 +7,7 @@ //! - `full_hex` is the canonical lowercase hex (64 chars) of the full //! blake3 digest. The `kb-core::Checksum` invariant is "full hex"; the //! 32-char prefix is reserved for `AssetId` derivation via -//! `kb_core::id_for_asset`. +//! `kebab_core::id_for_asset`. use std::fs::File; use std::io::{self, Read}; diff --git a/crates/kb-source-fs/src/lib.rs b/crates/kebab-source-fs/src/lib.rs similarity index 100% rename from crates/kb-source-fs/src/lib.rs rename to crates/kebab-source-fs/src/lib.rs diff --git a/crates/kb-source-fs/src/media.rs b/crates/kebab-source-fs/src/media.rs similarity index 98% rename from crates/kb-source-fs/src/media.rs rename to crates/kebab-source-fs/src/media.rs index ecaf6e3..b2d8663 100644 --- a/crates/kb-source-fs/src/media.rs +++ b/crates/kebab-source-fs/src/media.rs @@ -5,7 +5,7 @@ use std::path::Path; -use kb_core::{AudioType, ImageType, MediaType}; +use kebab_core::{AudioType, ImageType, MediaType}; /// Return `MediaType` for `path` based purely on its lowercased extension. /// `.md` → Markdown, `.pdf` → Pdf, image and audio extensions map onto diff --git a/crates/kb-source-fs/src/walker.rs b/crates/kebab-source-fs/src/walker.rs similarity index 100% rename from crates/kb-source-fs/src/walker.rs rename to crates/kebab-source-fs/src/walker.rs diff --git a/crates/kb-source-fs/tests/snapshot_tree1.rs b/crates/kebab-source-fs/tests/snapshot_tree1.rs similarity index 97% rename from crates/kb-source-fs/tests/snapshot_tree1.rs rename to crates/kebab-source-fs/tests/snapshot_tree1.rs index 6eb0ed9..0431faf 100644 --- a/crates/kb-source-fs/tests/snapshot_tree1.rs +++ b/crates/kebab-source-fs/tests/snapshot_tree1.rs @@ -25,9 +25,9 @@ use std::path::PathBuf; -use kb_config::Config; -use kb_core::{SourceConnector, SourceScope}; -use kb_source_fs::FsSourceConnector; +use kebab_config::Config; +use kebab_core::{SourceConnector, SourceScope}; +use kebab_source_fs::FsSourceConnector; use serde_json::Value; /// Repo root, derived from `CARGO_MANIFEST_DIR` (= `crates/kb-source-fs`). diff --git a/crates/kb-source-fs/tests/symlink_cycle.rs b/crates/kebab-source-fs/tests/symlink_cycle.rs similarity index 98% rename from crates/kb-source-fs/tests/symlink_cycle.rs rename to crates/kebab-source-fs/tests/symlink_cycle.rs index bcd00c4..1bd8d50 100644 --- a/crates/kb-source-fs/tests/symlink_cycle.rs +++ b/crates/kebab-source-fs/tests/symlink_cycle.rs @@ -17,9 +17,9 @@ use std::os::unix::fs::symlink; -use kb_config::Config; -use kb_core::{SourceConnector, SourceScope}; -use kb_source_fs::FsSourceConnector; +use kebab_config::Config; +use kebab_core::{SourceConnector, SourceScope}; +use kebab_source_fs::FsSourceConnector; fn cfg_with_root(root: &str) -> Config { let mut c = Config::defaults(); diff --git a/crates/kb-store-sqlite/Cargo.toml b/crates/kebab-store-sqlite/Cargo.toml similarity index 85% rename from crates/kb-store-sqlite/Cargo.toml rename to crates/kebab-store-sqlite/Cargo.toml index 7569405..2cc17ed 100644 --- a/crates/kb-store-sqlite/Cargo.toml +++ b/crates/kebab-store-sqlite/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-store-sqlite" +name = "kebab-store-sqlite" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,8 +8,8 @@ repository = { workspace = true } description = "SQLite-backed DocumentStore + JobRepo for kb (§5 DDL, §7.2 traits)" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } # `bundled` ships SQLite source + builds in-tree (no system libsqlite3). # Explicitly NOT `bundled-sqlcipher` per task allowed-deps list. rusqlite = { version = "0.32", features = ["bundled"] } @@ -34,6 +34,6 @@ serde_json = { workspace = true } # test. Forbidden as regular deps per design §8 (store consumes domain # types from kb-core only); `cargo tree -p kb-store-sqlite --depth 1` # (default scope, excludes dev-deps) confirms this. -kb-parse-md = { path = "../kb-parse-md" } -kb-normalize = { path = "../kb-normalize" } -kb-chunk = { path = "../kb-chunk" } +kebab-parse-md = { path = "../kebab-parse-md" } +kebab-normalize = { path = "../kebab-normalize" } +kebab-chunk = { path = "../kebab-chunk" } diff --git a/crates/kb-store-sqlite/snapshots/ingest_report.snapshot.json b/crates/kebab-store-sqlite/snapshots/ingest_report.snapshot.json similarity index 100% rename from crates/kb-store-sqlite/snapshots/ingest_report.snapshot.json rename to crates/kebab-store-sqlite/snapshots/ingest_report.snapshot.json diff --git a/crates/kb-store-sqlite/src/answers.rs b/crates/kebab-store-sqlite/src/answers.rs similarity index 97% rename from crates/kb-store-sqlite/src/answers.rs rename to crates/kebab-store-sqlite/src/answers.rs index e559980..e4ef25d 100644 --- a/crates/kb-store-sqlite/src/answers.rs +++ b/crates/kebab-store-sqlite/src/answers.rs @@ -2,13 +2,13 @@ //! //! `kb-rag` always persists an `answers` row at the end of every //! `RagPipeline::ask` — including refusal paths (`NoChunks`, -//! `ScoreGate`, `LlmSelfJudge`). The trait `kb_core::DocumentStore` +//! `ScoreGate`, `LlmSelfJudge`). The trait `kebab_core::DocumentStore` //! does not surface this method (answers aren't documents); we add it //! as an inherent method on `SqliteStore` so kb-rag can call //! `self.docs.put_answer(...)` directly. use anyhow::{Context, Result}; -use kb_core::{Answer, RefusalReason, SearchMode}; +use kebab_core::{Answer, RefusalReason, SearchMode}; use rusqlite::params; use crate::error::StoreError; diff --git a/crates/kb-store-sqlite/src/documents.rs b/crates/kebab-store-sqlite/src/documents.rs similarity index 86% rename from crates/kb-store-sqlite/src/documents.rs rename to crates/kebab-store-sqlite/src/documents.rs index 59db371..b321e12 100644 --- a/crates/kb-store-sqlite/src/documents.rs +++ b/crates/kebab-store-sqlite/src/documents.rs @@ -18,10 +18,10 @@ use time::OffsetDateTime; use crate::error::StoreError; use crate::store::{SqliteStore, upsert_asset_row, validate_asset_id}; -impl kb_core::DocumentStore for SqliteStore { - fn put_asset(&self, asset: &kb_core::RawAsset) -> Result<()> { +impl kebab_core::DocumentStore for SqliteStore { + fn put_asset(&self, asset: &kebab_core::RawAsset) -> Result<()> { // Validate the AssetId shape before any row work — defense in - // depth against hand-constructed `kb_core::AssetId` values that + // depth against hand-constructed `kebab_core::AssetId` values that // bypass `FromStr`. See `validate_asset_id` for rationale. validate_asset_id(&asset.asset_id)?; // No bytes here — read storage_kind/storage_path from the @@ -30,10 +30,10 @@ impl kb_core::DocumentStore for SqliteStore { // this branch is for the case where bytes were already persisted // (or referenced) and we just want to record the row. let (storage_kind, storage_path) = match &asset.stored { - kb_core::AssetStorage::Copied { path } => { + kebab_core::AssetStorage::Copied { path } => { ("copied", path.to_string_lossy().into_owned()) } - kb_core::AssetStorage::Reference { path, .. } => { + kebab_core::AssetStorage::Reference { path, .. } => { ("reference", path.to_string_lossy().into_owned()) } }; @@ -41,7 +41,7 @@ impl kb_core::DocumentStore for SqliteStore { upsert_asset_row(&conn, asset, storage_kind, &storage_path) } - fn put_document(&self, doc: &kb_core::CanonicalDocument) -> Result<()> { + fn put_document(&self, doc: &kebab_core::CanonicalDocument) -> Result<()> { let mut conn = self.lock_conn(); let tx = conn.transaction().map_err(StoreError::from)?; upsert_document(&tx, doc)?; @@ -52,8 +52,8 @@ impl kb_core::DocumentStore for SqliteStore { fn put_blocks( &self, - doc: &kb_core::DocumentId, - blocks: &[kb_core::Block], + doc: &kebab_core::DocumentId, + blocks: &[kebab_core::Block], ) -> Result<()> { let mut conn = self.lock_conn(); let tx = conn.transaction().map_err(StoreError::from)?; @@ -95,8 +95,8 @@ impl kb_core::DocumentStore for SqliteStore { fn put_chunks( &self, - doc: &kb_core::DocumentId, - chunks: &[kb_core::Chunk], + doc: &kebab_core::DocumentId, + chunks: &[kebab_core::Chunk], ) -> Result<()> { let now = OffsetDateTime::now_utc() .format(&time::format_description::well_known::Rfc3339) @@ -149,8 +149,8 @@ impl kb_core::DocumentStore for SqliteStore { fn get_document( &self, - id: &kb_core::DocumentId, - ) -> Result> { + id: &kebab_core::DocumentId, + ) -> Result> { let conn = self.lock_conn(); let row: Option = conn .query_row( @@ -182,30 +182,30 @@ impl kb_core::DocumentStore for SqliteStore { Ok(payload_json) }) .map_err(StoreError::from)?; - let mut blocks: Vec = Vec::new(); + let mut blocks: Vec = Vec::new(); for row in block_rows { let payload_json = row.map_err(StoreError::from)?; - let block: kb_core::Block = serde_json::from_str(&payload_json) + let block: kebab_core::Block = serde_json::from_str(&payload_json) .context("deserialize block payload_json")?; blocks.push(block); } - let metadata: kb_core::Metadata = serde_json::from_str(&row.metadata_json) + let metadata: kebab_core::Metadata = serde_json::from_str(&row.metadata_json) .context("deserialize metadata_json")?; - let provenance: kb_core::Provenance = + let provenance: kebab_core::Provenance = serde_json::from_str(&row.provenance_json) .context("deserialize provenance_json")?; - Ok(Some(kb_core::CanonicalDocument { - doc_id: kb_core::DocumentId(row.doc_id), - source_asset_id: kb_core::AssetId(row.asset_id), - workspace_path: kb_core::WorkspacePath(row.workspace_path), + Ok(Some(kebab_core::CanonicalDocument { + doc_id: kebab_core::DocumentId(row.doc_id), + source_asset_id: kebab_core::AssetId(row.asset_id), + workspace_path: kebab_core::WorkspacePath(row.workspace_path), title: row.title.unwrap_or_default(), - lang: kb_core::Lang(row.lang.unwrap_or_default()), + lang: kebab_core::Lang(row.lang.unwrap_or_default()), blocks, metadata, provenance, - parser_version: kb_core::ParserVersion(row.parser_version), + parser_version: kebab_core::ParserVersion(row.parser_version), // INVARIANT: `doc_version` is bumped by 1 on every re-ingest // (see `upsert_document`). The column is INTEGER (i64) but // CanonicalDocument carries u32; an overflow would require @@ -217,7 +217,7 @@ impl kb_core::DocumentStore for SqliteStore { })) } - fn get_chunk(&self, id: &kb_core::ChunkId) -> Result> { + fn get_chunk(&self, id: &kebab_core::ChunkId) -> Result> { let conn = self.lock_conn(); let row = conn .query_row( @@ -235,29 +235,29 @@ impl kb_core::DocumentStore for SqliteStore { let Some(row) = row else { return Ok(None) }; let heading_path: Vec = serde_json::from_str(&row.heading_path_json) .context("deserialize chunk.heading_path_json")?; - let source_spans: Vec = + let source_spans: Vec = serde_json::from_str(&row.source_spans_json) .context("deserialize chunk.source_spans_json")?; - let block_ids: Vec = + let block_ids: Vec = serde_json::from_str(&row.block_ids_json) .context("deserialize chunk.block_ids_json")?; - Ok(Some(kb_core::Chunk { - chunk_id: kb_core::ChunkId(row.chunk_id), - doc_id: kb_core::DocumentId(row.doc_id), + Ok(Some(kebab_core::Chunk { + chunk_id: kebab_core::ChunkId(row.chunk_id), + doc_id: kebab_core::DocumentId(row.doc_id), block_ids, text: row.text, heading_path, source_spans, token_estimate: row.token_estimate as usize, - chunker_version: kb_core::ChunkerVersion(row.chunker_version), + chunker_version: kebab_core::ChunkerVersion(row.chunker_version), policy_hash: row.policy_hash, })) } fn list_documents( &self, - filter: &kb_core::DocFilter, - ) -> Result> { + filter: &kebab_core::DocFilter, + ) -> Result> { // Build a dynamic WHERE clause from the filter. Each condition // appends one positional `?` placeholder and one `Box` to `params` so order stays in sync. @@ -292,9 +292,9 @@ impl kb_core::DocumentStore for SqliteStore { ELSE 0 END >= ?"); let rank: i64 = match trust_min { - kb_core::TrustLevel::Primary => 3, - kb_core::TrustLevel::Secondary => 2, - kb_core::TrustLevel::Generated => 1, + kebab_core::TrustLevel::Primary => 3, + kebab_core::TrustLevel::Secondary => 2, + kebab_core::TrustLevel::Generated => 1, }; params_dyn.push(Box::new(rank)); } @@ -337,7 +337,7 @@ impl kb_core::DocumentStore for SqliteStore { let tags: Vec = tag_iter .collect::>>() .map_err(StoreError::from)?; - out.push(kb_core::DocSummary { tags, ..summary }); + out.push(kebab_core::DocSummary { tags, ..summary }); } Ok(out) } @@ -405,7 +405,7 @@ fn chunk_row_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result { }) } -fn doc_summary_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result { +fn doc_summary_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result { let doc_id: String = row.get(0)?; let workspace_path: String = row.get(1)?; let title: Option = row.get(2)?; @@ -421,10 +421,10 @@ fn doc_summary_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result) -> rusqlite::Result) -> rusqlite::Result(err: rusqlite::Error) -> rusqlite::Result> { /// UPSERT the documents row and bump `doc_version` on conflict. fn upsert_document( tx: &rusqlite::Transaction<'_>, - doc: &kb_core::CanonicalDocument, + doc: &kebab_core::CanonicalDocument, ) -> Result<()> { let metadata_json = serde_json::to_string(&doc.metadata) .context("serialize metadata")?; @@ -535,27 +535,27 @@ fn upsert_document( Ok(()) } -fn source_type_label(s: &kb_core::SourceType) -> &'static str { +fn source_type_label(s: &kebab_core::SourceType) -> &'static str { match s { - kb_core::SourceType::Markdown => "markdown", - kb_core::SourceType::Note => "note", - kb_core::SourceType::Paper => "paper", - kb_core::SourceType::Reference => "reference", - kb_core::SourceType::Inbox => "inbox", + kebab_core::SourceType::Markdown => "markdown", + kebab_core::SourceType::Note => "note", + kebab_core::SourceType::Paper => "paper", + kebab_core::SourceType::Reference => "reference", + kebab_core::SourceType::Inbox => "inbox", } } -fn trust_level_label(s: &kb_core::TrustLevel) -> &'static str { +fn trust_level_label(s: &kebab_core::TrustLevel) -> &'static str { match s { - kb_core::TrustLevel::Primary => "primary", - kb_core::TrustLevel::Secondary => "secondary", - kb_core::TrustLevel::Generated => "generated", + kebab_core::TrustLevel::Primary => "primary", + kebab_core::TrustLevel::Secondary => "secondary", + kebab_core::TrustLevel::Generated => "generated", } } fn replace_document_tags( tx: &rusqlite::Transaction<'_>, - doc_id: &kb_core::DocumentId, + doc_id: &kebab_core::DocumentId, tags: &[String], ) -> Result<()> { tx.execute("DELETE FROM document_tags WHERE doc_id = ?", params![doc_id.0]) @@ -586,23 +586,23 @@ struct BlockRow { } fn block_to_row( - doc: &kb_core::DocumentId, - block: &kb_core::Block, + doc: &kebab_core::DocumentId, + block: &kebab_core::Block, stream_ordinal: i64, ) -> Result { let (block_id, kind, heading_path_json, source_span_json) = match block { - kb_core::Block::Heading(b) => ( + kebab_core::Block::Heading(b) => ( b.common.block_id.0.clone(), "heading", serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::Paragraph(b) | kb_core::Block::Quote(b) => ( + kebab_core::Block::Paragraph(b) | kebab_core::Block::Quote(b) => ( b.common.block_id.0.clone(), // Discriminate Paragraph vs Quote on the enum tag: payload // round-trip carries the variant, but the column needs a // stable label for filtering. - if matches!(block, kb_core::Block::Paragraph(_)) { + if matches!(block, kebab_core::Block::Paragraph(_)) { "paragraph" } else { "quote" @@ -610,31 +610,31 @@ fn block_to_row( serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::List(b) => ( + kebab_core::Block::List(b) => ( b.common.block_id.0.clone(), "list", serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::Code(b) => ( + kebab_core::Block::Code(b) => ( b.common.block_id.0.clone(), "code", serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::Table(b) => ( + kebab_core::Block::Table(b) => ( b.common.block_id.0.clone(), "table", serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::ImageRef(b) => ( + kebab_core::Block::ImageRef(b) => ( b.common.block_id.0.clone(), "imageref", serde_json::to_string(&b.common.heading_path)?, serde_json::to_string(&b.common.source_span)?, ), - kb_core::Block::AudioRef(b) => ( + kebab_core::Block::AudioRef(b) => ( b.common.block_id.0.clone(), "audioref", serde_json::to_string(&b.common.heading_path)?, diff --git a/crates/kb-store-sqlite/src/embeddings.rs b/crates/kebab-store-sqlite/src/embeddings.rs similarity index 99% rename from crates/kb-store-sqlite/src/embeddings.rs rename to crates/kebab-store-sqlite/src/embeddings.rs index 3fd51f1..0ef76a2 100644 --- a/crates/kb-store-sqlite/src/embeddings.rs +++ b/crates/kebab-store-sqlite/src/embeddings.rs @@ -137,7 +137,7 @@ impl SqliteStore { #[cfg(test)] mod tests { use super::*; - use kb_config::Config; + use kebab_config::Config; use tempfile::TempDir; use time::OffsetDateTime; diff --git a/crates/kb-store-sqlite/src/error.rs b/crates/kebab-store-sqlite/src/error.rs similarity index 100% rename from crates/kb-store-sqlite/src/error.rs rename to crates/kebab-store-sqlite/src/error.rs diff --git a/crates/kb-store-sqlite/src/eval.rs b/crates/kebab-store-sqlite/src/eval.rs similarity index 99% rename from crates/kb-store-sqlite/src/eval.rs rename to crates/kebab-store-sqlite/src/eval.rs index 34a29b0..007e01f 100644 --- a/crates/kb-store-sqlite/src/eval.rs +++ b/crates/kebab-store-sqlite/src/eval.rs @@ -2,7 +2,7 @@ //! //! `kb-eval` calls these directly via the inherent methods on //! [`SqliteStore`]. The pattern mirrors [`crate::answers`]: the trait -//! `kb_core::DocumentStore` is the document surface, and run-level +//! `kebab_core::DocumentStore` is the document surface, and run-level //! audit rows (jobs, ingest_runs, answers, eval_runs) are inherent //! methods so the trait surface stays small. diff --git a/crates/kb-store-sqlite/src/filters.rs b/crates/kebab-store-sqlite/src/filters.rs similarity index 97% rename from crates/kb-store-sqlite/src/filters.rs rename to crates/kebab-store-sqlite/src/filters.rs index be56201..2b1ff00 100644 --- a/crates/kb-store-sqlite/src/filters.rs +++ b/crates/kebab-store-sqlite/src/filters.rs @@ -34,7 +34,7 @@ impl SqliteStore { /// The result preserves the input order so the caller can feed it /// back to a Lance distance-asc result list and `take(k)` directly. /// - /// `filters` semantics mirror `kb_core::SearchFilters`: + /// `filters` semantics mirror `kebab_core::SearchFilters`: /// /// - `tags_any`: doc must own at least one of the listed tags /// (empty vec ⇒ no tag constraint). @@ -52,9 +52,9 @@ impl SqliteStore { /// search callers (spec §5.6). pub fn filter_chunks( &self, - chunk_ids: &[kb_core::ChunkId], - filters: &kb_core::SearchFilters, - ) -> Result> { + chunk_ids: &[kebab_core::ChunkId], + filters: &kebab_core::SearchFilters, + ) -> Result> { if chunk_ids.is_empty() { return Ok(Vec::new()); } @@ -110,9 +110,9 @@ impl SqliteStore { ELSE 0 END >= ?", ); let rank: i64 = match min { - kb_core::TrustLevel::Primary => 3, - kb_core::TrustLevel::Secondary => 2, - kb_core::TrustLevel::Generated => 1, + kebab_core::TrustLevel::Primary => 3, + kebab_core::TrustLevel::Secondary => 2, + kebab_core::TrustLevel::Generated => 1, }; bind.push(Box::new(rank)); } @@ -187,8 +187,8 @@ impl SqliteStore { #[cfg(test)] mod tests { use super::*; - use kb_config::Config; - use kb_core::{ChunkId, Lang, SearchFilters, TrustLevel}; + use kebab_config::Config; + use kebab_core::{ChunkId, Lang, SearchFilters, TrustLevel}; use rusqlite::params; use tempfile::TempDir; use time::OffsetDateTime; diff --git a/crates/kb-store-sqlite/src/fts.rs b/crates/kebab-store-sqlite/src/fts.rs similarity index 100% rename from crates/kb-store-sqlite/src/fts.rs rename to crates/kebab-store-sqlite/src/fts.rs diff --git a/crates/kb-store-sqlite/src/jobs.rs b/crates/kebab-store-sqlite/src/jobs.rs similarity index 88% rename from crates/kb-store-sqlite/src/jobs.rs rename to crates/kebab-store-sqlite/src/jobs.rs index 745ffb7..dbeb4be 100644 --- a/crates/kb-store-sqlite/src/jobs.rs +++ b/crates/kebab-store-sqlite/src/jobs.rs @@ -85,12 +85,12 @@ impl SqliteStore { } } -impl kb_core::JobRepo for SqliteStore { +impl kebab_core::JobRepo for SqliteStore { fn create( &self, - kind: kb_core::JobKind, + kind: kebab_core::JobKind, payload: Value, - ) -> Result { + ) -> Result { let now_dt = OffsetDateTime::now_utc(); let now = now_dt .format(&time::format_description::well_known::Rfc3339) @@ -116,7 +116,7 @@ impl kb_core::JobRepo for SqliteStore { fn update_progress( &self, - id: &kb_core::JobId, + id: &kebab_core::JobId, progress: Value, ) -> Result<()> { let progress_json = serde_json::to_string(&progress) @@ -141,8 +141,8 @@ impl kb_core::JobRepo for SqliteStore { fn finish( &self, - id: &kb_core::JobId, - status: kb_core::JobStatus, + id: &kebab_core::JobId, + status: kebab_core::JobStatus, error: Option<&str>, ) -> Result<()> { let now = OffsetDateTime::now_utc() @@ -169,8 +169,8 @@ impl kb_core::JobRepo for SqliteStore { fn list( &self, - filter: &kb_core::JobFilter, - ) -> Result> { + filter: &kebab_core::JobFilter, + ) -> Result> { let conn = self.lock_conn(); let mut sql = String::from( "SELECT job_id, kind, status, payload_json, progress_json, @@ -204,13 +204,13 @@ impl kb_core::JobRepo for SqliteStore { } /// Mint a JobId over (kind, canonical(payload), nanos). The 32-hex -/// invariant on `kb_core::JobId` is honored by taking the first 32 chars +/// invariant on `kebab_core::JobId` is honored by taking the first 32 chars /// of the blake3 hex. fn mint_job_id( - kind: &kb_core::JobKind, + kind: &kebab_core::JobKind, payload: &Value, at: OffsetDateTime, -) -> kb_core::JobId { +) -> kebab_core::JobId { // Plain serde_json::to_vec is enough — JobIds are not part of the // §4.2 ID family and don't need canonical-JSON parity with other IDs. // The nanosecond suffix is what guarantees uniqueness, not stable @@ -222,32 +222,32 @@ fn mint_job_id( } hasher.update(&at.unix_timestamp_nanos().to_be_bytes()); let hex = hasher.finalize().to_hex().to_string(); - kb_core::JobId(hex[..32].to_string()) + kebab_core::JobId(hex[..32].to_string()) } -fn job_kind_label(k: &kb_core::JobKind) -> &'static str { +fn job_kind_label(k: &kebab_core::JobKind) -> &'static str { match k { - kb_core::JobKind::Ingest => "ingest", - kb_core::JobKind::Chunk => "chunk", - kb_core::JobKind::Embed => "embed", - kb_core::JobKind::Ocr => "ocr", - kb_core::JobKind::Transcribe => "transcribe", - kb_core::JobKind::Reindex => "reindex", - kb_core::JobKind::Doctor => "doctor", + kebab_core::JobKind::Ingest => "ingest", + kebab_core::JobKind::Chunk => "chunk", + kebab_core::JobKind::Embed => "embed", + kebab_core::JobKind::Ocr => "ocr", + kebab_core::JobKind::Transcribe => "transcribe", + kebab_core::JobKind::Reindex => "reindex", + kebab_core::JobKind::Doctor => "doctor", } } -fn job_status_label(s: &kb_core::JobStatus) -> &'static str { +fn job_status_label(s: &kebab_core::JobStatus) -> &'static str { match s { - kb_core::JobStatus::Pending => "pending", - kb_core::JobStatus::Running => "running", - kb_core::JobStatus::Succeeded => "succeeded", - kb_core::JobStatus::Failed => "failed", - kb_core::JobStatus::Canceled => "canceled", + kebab_core::JobStatus::Pending => "pending", + kebab_core::JobStatus::Running => "running", + kebab_core::JobStatus::Succeeded => "succeeded", + kebab_core::JobStatus::Failed => "failed", + kebab_core::JobStatus::Canceled => "canceled", } } -fn job_row_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result { +fn job_row_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result { let job_id: String = row.get(0)?; let kind_raw: String = row.get(1)?; let status_raw: String = row.get(2)?; @@ -258,10 +258,10 @@ fn job_row_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result = row.get(8)?; - let kind: kb_core::JobKind = + let kind: kebab_core::JobKind = serde_json::from_value(serde_json::Value::String(kind_raw)) .map_err(conv_err(1))?; - let status: kb_core::JobStatus = + let status: kebab_core::JobStatus = serde_json::from_value(serde_json::Value::String(status_raw)) .map_err(conv_err(2))?; let payload: Value = serde_json::from_str(&payload_json).map_err(conv_err(3))?; @@ -303,8 +303,8 @@ fn job_row_from_sql(row: &rusqlite::Row<'_>) -> rusqlite::Result None, }; - Ok(kb_core::JobRow { - job_id: kb_core::JobId(job_id), + Ok(kebab_core::JobRow { + job_id: kebab_core::JobId(job_id), kind, status, payload, diff --git a/crates/kb-store-sqlite/src/lib.rs b/crates/kebab-store-sqlite/src/lib.rs similarity index 94% rename from crates/kb-store-sqlite/src/lib.rs rename to crates/kebab-store-sqlite/src/lib.rs index 640e5b9..23290c1 100644 --- a/crates/kb-store-sqlite/src/lib.rs +++ b/crates/kebab-store-sqlite/src/lib.rs @@ -1,5 +1,5 @@ //! `kb-store-sqlite` — SQLite-backed implementations of -//! [`kb_core::DocumentStore`] and [`kb_core::JobRepo`] (§7.2), plus the +//! [`kebab_core::DocumentStore`] and [`kebab_core::JobRepo`] (§7.2), plus the //! asset writer that copies (or references) raw bytes per design §5.2. //! //! Schema is owned by `migrations/V001__init.sql` (workspace root), which diff --git a/crates/kb-store-sqlite/src/schema.rs b/crates/kebab-store-sqlite/src/schema.rs similarity index 100% rename from crates/kb-store-sqlite/src/schema.rs rename to crates/kebab-store-sqlite/src/schema.rs diff --git a/crates/kb-store-sqlite/src/store.rs b/crates/kebab-store-sqlite/src/store.rs similarity index 94% rename from crates/kb-store-sqlite/src/store.rs rename to crates/kebab-store-sqlite/src/store.rs index f0c1560..2ee3636 100644 --- a/crates/kb-store-sqlite/src/store.rs +++ b/crates/kebab-store-sqlite/src/store.rs @@ -21,7 +21,7 @@ use crate::schema; /// collide on `.tmp..`. static TEMP_SUFFIX_COUNTER: AtomicU64 = AtomicU64::new(0); -/// Length, in hex chars, of a valid `kb_core::AssetId`. blake3 first-half +/// Length, in hex chars, of a valid `kebab_core::AssetId`. blake3 first-half /// truncated, mirrored from `kb-core`'s newtype invariant. const ASSET_ID_HEX_LEN: usize = 32; @@ -63,8 +63,8 @@ impl SqliteStore { /// apply pragmas (foreign_keys / WAL / synchronous=NORMAL / /// temp_store=MEMORY), and create parent directories as needed. /// **Does not run migrations** — call [`Self::run_migrations`] next. - pub fn open(config: &kb_config::Config) -> Result { - let data_dir = kb_config::expand_path(&config.storage.data_dir, ""); + pub fn open(config: &kebab_config::Config) -> Result { + let data_dir = kebab_config::expand_path(&config.storage.data_dir, ""); std::fs::create_dir_all(&data_dir) .with_context(|| format!("create data_dir {}", data_dir.display()))?; let db_path = data_dir.join(SQLITE_FILE); @@ -135,10 +135,10 @@ impl SqliteStore { /// `StoreError::Conflict` wrapped in `anyhow::Error`. pub fn put_asset_with_bytes( &self, - asset: &kb_core::RawAsset, + asset: &kebab_core::RawAsset, bytes: &[u8], ) -> Result<()> { - // 0. Validate the AssetId shape before any I/O. `kb_core::AssetId` + // 0. Validate the AssetId shape before any I/O. `kebab_core::AssetId` // is a `pub String` newtype: `FromStr` enforces the 32-hex-char // invariant, but a hand-constructed `AssetId("../etc/passwd…")` // can bypass that and reach `assets_path_for`. Refuse such IDs at @@ -239,8 +239,8 @@ impl SqliteStore { // latter stores the raw `kb://...` string. No file I/O ⇒ no // orphan risk; just UPSERT the row. let storage_path = match &asset.source_uri { - kb_core::SourceUri::File(p) => p.to_string_lossy().into_owned(), - kb_core::SourceUri::Kb(u) => u.clone(), + kebab_core::SourceUri::File(p) => p.to_string_lossy().into_owned(), + kebab_core::SourceUri::Kb(u) => u.clone(), }; let conn = self.lock_conn(); upsert_asset_row(&conn, asset, "reference", &storage_path)?; @@ -255,7 +255,7 @@ impl SqliteStore { /// invoke [`validate_asset_id`] (already enforced at every store /// entry that takes a `RawAsset`). The `id.len() >= ASSET_SHARD_LEN` /// guard below is a defense-in-depth fallback only. - pub(crate) fn assets_path_for(&self, asset_id: &kb_core::AssetId) -> PathBuf { + pub(crate) fn assets_path_for(&self, asset_id: &kebab_core::AssetId) -> PathBuf { let id = &asset_id.0; let shard = if id.len() >= ASSET_SHARD_LEN { &id[..ASSET_SHARD_LEN] @@ -267,10 +267,10 @@ impl SqliteStore { } /// Reject an `AssetId` whose shape would let a malicious caller escape -/// the `data_dir/assets//` shard tree. `kb_core::AssetId(pub String)` +/// the `data_dir/assets//` shard tree. `kebab_core::AssetId(pub String)` /// permits hand-construction, so any function that turns an `AssetId` /// into a filesystem path must call this first. -pub(crate) fn validate_asset_id(asset_id: &kb_core::AssetId) -> Result<()> { +pub(crate) fn validate_asset_id(asset_id: &kebab_core::AssetId) -> Result<()> { if asset_id.0.len() != ASSET_ID_HEX_LEN || !asset_id.0.bytes().all(|b| b.is_ascii_hexdigit()) { @@ -304,13 +304,13 @@ fn temp_path_for(dest: &Path) -> PathBuf { /// reads `storage_kind/path` from `asset.stored`). pub(crate) fn upsert_asset_row( conn: &Connection, - asset: &kb_core::RawAsset, + asset: &kebab_core::RawAsset, storage_kind: &str, storage_path: &str, ) -> Result<()> { let source_uri = match &asset.source_uri { - kb_core::SourceUri::File(p) => format!("file://{}", p.to_string_lossy()), - kb_core::SourceUri::Kb(u) => u.clone(), + kebab_core::SourceUri::File(p) => format!("file://{}", p.to_string_lossy()), + kebab_core::SourceUri::Kb(u) => u.clone(), }; let media_type = serde_json::to_string(&asset.media_type) .context("serialize media_type")?; diff --git a/crates/kb-store-sqlite/tests/asset_writer.rs b/crates/kebab-store-sqlite/tests/asset_writer.rs similarity index 97% rename from crates/kb-store-sqlite/tests/asset_writer.rs rename to crates/kebab-store-sqlite/tests/asset_writer.rs index 43112a7..54f6268 100644 --- a/crates/kb-store-sqlite/tests/asset_writer.rs +++ b/crates/kebab-store-sqlite/tests/asset_writer.rs @@ -3,8 +3,8 @@ use std::path::PathBuf; -use kb_core::{AssetId, AssetStorage, Checksum, MediaType, RawAsset, SourceUri, WorkspacePath}; -use kb_store_sqlite::SqliteStore; +use kebab_core::{AssetId, AssetStorage, Checksum, MediaType, RawAsset, SourceUri, WorkspacePath}; +use kebab_store_sqlite::SqliteStore; use time::OffsetDateTime; mod common; @@ -174,7 +174,7 @@ fn put_asset_with_bytes_orphan_cleanup_on_upsert_failure() { #[test] fn put_asset_with_bytes_rejects_invalid_asset_id() { - // `kb_core::AssetId(pub String)` lets a hand-construction bypass the + // `kebab_core::AssetId(pub String)` lets a hand-construction bypass the // 32-hex `FromStr` invariant. The store boundary must reject any ID // whose shape would let path construction escape `data_dir/assets/`. let env = common::TestEnv::with_threshold(100); diff --git a/crates/kb-store-sqlite/tests/common/mod.rs b/crates/kebab-store-sqlite/tests/common/mod.rs similarity index 98% rename from crates/kb-store-sqlite/tests/common/mod.rs rename to crates/kebab-store-sqlite/tests/common/mod.rs index 742ce0b..e65c18f 100644 --- a/crates/kb-store-sqlite/tests/common/mod.rs +++ b/crates/kebab-store-sqlite/tests/common/mod.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; -use kb_config::Config; +use kebab_config::Config; use rusqlite::Connection; use tempfile::TempDir; diff --git a/crates/kb-store-sqlite/tests/contract_roundtrip.rs b/crates/kebab-store-sqlite/tests/contract_roundtrip.rs similarity index 96% rename from crates/kb-store-sqlite/tests/contract_roundtrip.rs rename to crates/kebab-store-sqlite/tests/contract_roundtrip.rs index 2ea6080..ba56601 100644 --- a/crates/kb-store-sqlite/tests/contract_roundtrip.rs +++ b/crates/kebab-store-sqlite/tests/contract_roundtrip.rs @@ -8,14 +8,14 @@ use std::path::PathBuf; -use kb_chunk::MdHeadingV1Chunker; -use kb_core::{ +use kebab_chunk::MdHeadingV1Chunker; +use kebab_core::{ AssetId, AssetStorage, Checksum, ChunkPolicy, ChunkerVersion, Chunker, DocumentStore, MediaType, ParserVersion, RawAsset, SourceUri, WorkspacePath, }; -use kb_normalize::build_canonical_document; -use kb_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; -use kb_store_sqlite::SqliteStore; +use kebab_normalize::build_canonical_document; +use kebab_parse_md::{BodyHints, parse_blocks, parse_frontmatter}; +use kebab_store_sqlite::SqliteStore; use time::OffsetDateTime; mod common; diff --git a/crates/kb-store-sqlite/tests/fts.rs b/crates/kebab-store-sqlite/tests/fts.rs similarity index 99% rename from crates/kb-store-sqlite/tests/fts.rs rename to crates/kebab-store-sqlite/tests/fts.rs index 2b7faa7..350c16d 100644 --- a/crates/kb-store-sqlite/tests/fts.rs +++ b/crates/kebab-store-sqlite/tests/fts.rs @@ -13,7 +13,7 @@ //! that bypasses the `SqliteStore` mutex; that's fine because each test //! gets its own tempdir and no concurrent mutator is in flight. -use kb_store_sqlite::{SqliteStore, rebuild_chunks_fts}; +use kebab_store_sqlite::{SqliteStore, rebuild_chunks_fts}; use rusqlite::Connection; mod common; diff --git a/crates/kb-store-sqlite/tests/idempotency.rs b/crates/kebab-store-sqlite/tests/idempotency.rs similarity index 95% rename from crates/kb-store-sqlite/tests/idempotency.rs rename to crates/kebab-store-sqlite/tests/idempotency.rs index 56f45bd..2484852 100644 --- a/crates/kb-store-sqlite/tests/idempotency.rs +++ b/crates/kebab-store-sqlite/tests/idempotency.rs @@ -4,13 +4,13 @@ use std::path::PathBuf; -use kb_core::{ +use kebab_core::{ AssetId, AssetStorage, Block, CanonicalDocument, Checksum, Chunk, ChunkerVersion, CommonBlock, DocumentId, DocumentStore, HeadingBlock, Lang, MediaType, Metadata, ParserVersion, Provenance, RawAsset, SourceSpan, SourceType, SourceUri, TextBlock, TrustLevel, WorkspacePath, }; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use time::OffsetDateTime; mod common; @@ -50,7 +50,7 @@ fn make_doc() -> CanonicalDocument { let span = SourceSpan::Line { start: 1, end: 1 }; let block = Block::Heading(HeadingBlock { common: CommonBlock { - block_id: kb_core::BlockId("b".repeat(32)), + block_id: kebab_core::BlockId("b".repeat(32)), heading_path: vec![], source_span: span.clone(), }, @@ -59,7 +59,7 @@ fn make_doc() -> CanonicalDocument { }); let para = Block::Paragraph(TextBlock { common: CommonBlock { - block_id: kb_core::BlockId("c".repeat(32)), + block_id: kebab_core::BlockId("c".repeat(32)), heading_path: vec!["Title".into()], source_span: span, }, @@ -83,9 +83,9 @@ fn make_doc() -> CanonicalDocument { fn make_chunks(doc_id: &DocumentId) -> Vec { vec![Chunk { - chunk_id: kb_core::ChunkId("e".repeat(32)), + chunk_id: kebab_core::ChunkId("e".repeat(32)), doc_id: doc_id.clone(), - block_ids: vec![kb_core::BlockId("b".repeat(32))], + block_ids: vec![kebab_core::BlockId("b".repeat(32))], text: "Title\n\nbody".into(), heading_path: vec!["Title".into()], source_spans: vec![SourceSpan::Line { start: 1, end: 1 }], @@ -221,7 +221,7 @@ fn put_blocks_transactional_rollback_on_fk_violation() { let phantom = DocumentId("0".repeat(32)); let phantom_blocks = vec![Block::Heading(HeadingBlock { common: CommonBlock { - block_id: kb_core::BlockId("9".repeat(32)), + block_id: kebab_core::BlockId("9".repeat(32)), heading_path: vec![], source_span: SourceSpan::Line { start: 1, end: 1 }, }, diff --git a/crates/kb-store-sqlite/tests/ingest_report_snapshot.rs b/crates/kebab-store-sqlite/tests/ingest_report_snapshot.rs similarity index 97% rename from crates/kb-store-sqlite/tests/ingest_report_snapshot.rs rename to crates/kebab-store-sqlite/tests/ingest_report_snapshot.rs index 2e6a563..acfd72d 100644 --- a/crates/kb-store-sqlite/tests/ingest_report_snapshot.rs +++ b/crates/kebab-store-sqlite/tests/ingest_report_snapshot.rs @@ -1,4 +1,4 @@ -//! Snapshot test pinning the JSON wire form of `kb_core::IngestReport` +//! Snapshot test pinning the JSON wire form of `kebab_core::IngestReport` //! for an inline fixture run. The store crate doesn't (yet) write //! IngestReports — that's `kb-app`'s job — but the wire schema lives in //! `kb-core`, and we want a determinism pin that fails loudly if the @@ -8,7 +8,7 @@ use std::path::PathBuf; -use kb_core::{ +use kebab_core::{ AssetId, ChunkerVersion, DocumentId, IngestItem, IngestItemKind, IngestReport, ParserVersion, SourceScope, WorkspacePath, }; diff --git a/crates/kb-store-sqlite/tests/jobs.rs b/crates/kebab-store-sqlite/tests/jobs.rs similarity index 96% rename from crates/kb-store-sqlite/tests/jobs.rs rename to crates/kebab-store-sqlite/tests/jobs.rs index a5c5c32..7aa9381 100644 --- a/crates/kb-store-sqlite/tests/jobs.rs +++ b/crates/kebab-store-sqlite/tests/jobs.rs @@ -1,7 +1,7 @@ //! `JobRepo` smoke tests: create → progress → finish, list filters. -use kb_core::{JobFilter, JobKind, JobRepo, JobStatus}; -use kb_store_sqlite::SqliteStore; +use kebab_core::{JobFilter, JobKind, JobRepo, JobStatus}; +use kebab_store_sqlite::SqliteStore; use serde_json::json; mod common; diff --git a/crates/kb-store-sqlite/tests/list_docs.rs b/crates/kebab-store-sqlite/tests/list_docs.rs similarity index 97% rename from crates/kb-store-sqlite/tests/list_docs.rs rename to crates/kebab-store-sqlite/tests/list_docs.rs index f690f6b..53df3ac 100644 --- a/crates/kb-store-sqlite/tests/list_docs.rs +++ b/crates/kebab-store-sqlite/tests/list_docs.rs @@ -2,12 +2,12 @@ use std::path::PathBuf; -use kb_core::{ +use kebab_core::{ AssetId, AssetStorage, Block, CanonicalDocument, Checksum, CommonBlock, DocFilter, DocumentId, DocumentStore, HeadingBlock, Lang, MediaType, Metadata, ParserVersion, Provenance, RawAsset, SourceSpan, SourceType, SourceUri, TrustLevel, WorkspacePath, }; -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; use time::OffsetDateTime; mod common; @@ -38,7 +38,7 @@ fn make_doc( let doc_id = DocumentId(format!("d{suffix}").repeat(16)); let block = Block::Heading(HeadingBlock { common: CommonBlock { - block_id: kb_core::BlockId(format!("b{suffix}").repeat(16)), + block_id: kebab_core::BlockId(format!("b{suffix}").repeat(16)), heading_path: vec![], source_span: SourceSpan::Line { start: 1, end: 1 }, }, diff --git a/crates/kb-store-sqlite/tests/migration.rs b/crates/kebab-store-sqlite/tests/migration.rs similarity index 98% rename from crates/kb-store-sqlite/tests/migration.rs rename to crates/kebab-store-sqlite/tests/migration.rs index ad6ab54..c16c876 100644 --- a/crates/kb-store-sqlite/tests/migration.rs +++ b/crates/kebab-store-sqlite/tests/migration.rs @@ -1,7 +1,7 @@ //! Migration test: a fresh DB, after `run_migrations`, exposes every //! table and index P1 needs (per §5.1–§5.7). -use kb_store_sqlite::SqliteStore; +use kebab_store_sqlite::SqliteStore; mod common; diff --git a/crates/kb-store-vector/Cargo.toml b/crates/kebab-store-vector/Cargo.toml similarity index 93% rename from crates/kb-store-vector/Cargo.toml rename to crates/kebab-store-vector/Cargo.toml index 3bccdbc..d40ac0e 100644 --- a/crates/kb-store-vector/Cargo.toml +++ b/crates/kebab-store-vector/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "kb-store-vector" +name = "kebab-store-vector" version = { workspace = true } edition = { workspace = true } rust-version = { workspace = true } @@ -8,13 +8,13 @@ repository = { workspace = true } description = "LanceDB-backed VectorStore for kb (§5.6 embedding_records, §6.3 lancedb tables, §7.2 VectorStore)" [dependencies] -kb-core = { path = "../kb-core" } -kb-config = { path = "../kb-config" } +kebab-core = { path = "../kebab-core" } +kebab-config = { path = "../kebab-config" } # kb-store-sqlite is allowed for the embedding_records writers only # (P3-3 spec: "Allowed dep `kb-store-sqlite` for writing/reading rows in # embedding_records"). The Two-phase upsert flow uses # `put_embedding_records_pending` + `mark_embedding_records_committed`. -kb-store-sqlite = { path = "../kb-store-sqlite" } +kebab-store-sqlite = { path = "../kebab-store-sqlite" } # LanceDB embedded vector store. `default-features=false` opts out of # the cloud object-store integrations (aws / gcs / azure / dynamodb / diff --git a/crates/kb-store-vector/src/arrow_batch.rs b/crates/kebab-store-vector/src/arrow_batch.rs similarity index 98% rename from crates/kb-store-vector/src/arrow_batch.rs rename to crates/kebab-store-vector/src/arrow_batch.rs index f17587b..1e1910e 100644 --- a/crates/kb-store-vector/src/arrow_batch.rs +++ b/crates/kebab-store-vector/src/arrow_batch.rs @@ -27,7 +27,7 @@ use arrow_array::{ TimestampMicrosecondArray, }; use arrow_schema::{DataType, Field, Schema, SchemaRef, TimeUnit}; -use kb_core::VectorRecord; +use kebab_core::VectorRecord; use time::OffsetDateTime; /// Arrow schema for a Lance table whose vector column is FixedSizeList @@ -184,7 +184,7 @@ fn descriptor_bytes(v: &serde_json::Value) -> Vec { #[cfg(test)] mod tests { use super::*; - use kb_core::{ChunkId, DocumentId, EmbeddingId, EmbeddingModelId, EmbeddingVersion}; + use kebab_core::{ChunkId, DocumentId, EmbeddingId, EmbeddingModelId, EmbeddingVersion}; use time::OffsetDateTime; fn make_rec(chunk_idx: u8, dim: usize) -> VectorRecord { diff --git a/crates/kb-store-vector/src/lib.rs b/crates/kebab-store-vector/src/lib.rs similarity index 94% rename from crates/kb-store-vector/src/lib.rs rename to crates/kebab-store-vector/src/lib.rs index 94c5f70..53b30a9 100644 --- a/crates/kb-store-vector/src/lib.rs +++ b/crates/kebab-store-vector/src/lib.rs @@ -1,4 +1,4 @@ -//! `kb-store-vector` — LanceDB-backed [`kb_core::VectorStore`] for kb. +//! `kb-store-vector` — LanceDB-backed [`kebab_core::VectorStore`] for kb. //! //! Stores per-model Lance tables under `config.storage.vector_dir/` //! (`chunk_embeddings__.lance/`). `upsert` runs the diff --git a/crates/kb-store-vector/src/paths.rs b/crates/kebab-store-vector/src/paths.rs similarity index 98% rename from crates/kb-store-vector/src/paths.rs rename to crates/kebab-store-vector/src/paths.rs index 40508f2..a236595 100644 --- a/crates/kb-store-vector/src/paths.rs +++ b/crates/kebab-store-vector/src/paths.rs @@ -4,7 +4,7 @@ //! `kb-store-sqlite`, `kb-embed-local`, and `kb-eval` all resolve //! `${XDG_DATA_HOME:-…}` / leading `~` / `{data_dir}` identically. This //! module re-exports nothing; consumers within the crate `use -//! kb_config::expand_path` directly. +//! kebab_config::expand_path` directly. /// Build the per-model Lance table name. Per design §6.3: /// `chunk_embeddings__.lance`. Model IDs may contain diff --git a/crates/kb-store-vector/src/store.rs b/crates/kebab-store-vector/src/store.rs similarity index 98% rename from crates/kb-store-vector/src/store.rs rename to crates/kebab-store-vector/src/store.rs index 99e3c6f..dbb857c 100644 --- a/crates/kb-store-vector/src/store.rs +++ b/crates/kebab-store-vector/src/store.rs @@ -1,4 +1,4 @@ -//! `LanceVectorStore` — `kb_core::VectorStore` impl over LanceDB. +//! `LanceVectorStore` — `kebab_core::VectorStore` impl over LanceDB. //! //! See module-level docs in `lib.rs` for the high-level shape (two-phase //! upsert, sync/async bridge, table layout). @@ -11,18 +11,18 @@ use anyhow::{Context, Result}; use arrow_array::{Array, Float32Array, RecordBatch, StringArray}; use arrow_schema::SchemaRef; use futures::TryStreamExt; -use kb_core::{ +use kebab_core::{ ChunkId, DocumentId, EmbeddingModelId, IndexId, SearchFilters, VectorHit, VectorRecord, VectorStore, }; -use kb_store_sqlite::{EmbeddingRecordRow, SqliteStore}; +use kebab_store_sqlite::{EmbeddingRecordRow, SqliteStore}; use lancedb::Connection; use lancedb::query::{ExecutableQuery, QueryBase}; use serde_json::json; use time::OffsetDateTime; use tokio::runtime::{Builder as RuntimeBuilder, Runtime}; -use kb_config::expand_path; +use kebab_config::expand_path; use crate::arrow_batch::{build_batch, schema_for, schema_params_hash}; use crate::paths::lance_table_name; @@ -87,7 +87,7 @@ impl LanceVectorStore { /// runtime context will panic with `"Cannot start a runtime from /// within a runtime"`. See the struct-level `# Async context` /// section. - pub fn new(config: &kb_config::Config, sqlite: Arc) -> Result { + pub fn new(config: &kebab_config::Config, sqlite: Arc) -> Result { let data_dir = expand_path(&config.storage.data_dir, ""); let vector_dir = expand_path(&config.storage.vector_dir, &data_dir.to_string_lossy()); @@ -194,11 +194,11 @@ impl VectorStore for LanceVectorStore { })?; let params_hash = schema_params_hash(dim); - let id = kb_core::id_for_index( + let id = kebab_core::id_for_index( INDEX_COLLECTION, model, dim, - &kb_core::IndexVersion(INDEX_VERSION.to_string()), + &kebab_core::IndexVersion(INDEX_VERSION.to_string()), INDEX_KIND, ¶ms_hash, ); diff --git a/crates/kb-store-vector/tests/common/mod.rs b/crates/kebab-store-vector/tests/common/mod.rs similarity index 98% rename from crates/kb-store-vector/tests/common/mod.rs rename to crates/kebab-store-vector/tests/common/mod.rs index 00031b1..a05b68c 100644 --- a/crates/kb-store-vector/tests/common/mod.rs +++ b/crates/kebab-store-vector/tests/common/mod.rs @@ -59,12 +59,12 @@ pub fn require_avx_or_panic() { } } -use kb_config::Config; -use kb_core::{ +use kebab_config::Config; +use kebab_core::{ ChunkId, DocumentId, EmbeddingId, EmbeddingModelId, EmbeddingVersion, VectorRecord, }; -use kb_store_sqlite::SqliteStore; -use kb_store_vector::LanceVectorStore; +use kebab_store_sqlite::SqliteStore; +use kebab_store_vector::LanceVectorStore; use rusqlite::params; use tempfile::TempDir; diff --git a/crates/kb-store-vector/tests/fixtures/vector/run-1.json b/crates/kebab-store-vector/tests/fixtures/vector/run-1.json similarity index 100% rename from crates/kb-store-vector/tests/fixtures/vector/run-1.json rename to crates/kebab-store-vector/tests/fixtures/vector/run-1.json diff --git a/crates/kb-store-vector/tests/snapshot.rs b/crates/kebab-store-vector/tests/snapshot.rs similarity index 98% rename from crates/kb-store-vector/tests/snapshot.rs rename to crates/kebab-store-vector/tests/snapshot.rs index 5ac7e0d..1d3d9e1 100644 --- a/crates/kb-store-vector/tests/snapshot.rs +++ b/crates/kebab-store-vector/tests/snapshot.rs @@ -13,7 +13,7 @@ use std::path::PathBuf; -use kb_core::{SearchFilters, VectorStore}; +use kebab_core::{SearchFilters, VectorStore}; use serde_json::json; mod common; diff --git a/crates/kb-store-vector/tests/upsert_search.rs b/crates/kebab-store-vector/tests/upsert_search.rs similarity index 98% rename from crates/kb-store-vector/tests/upsert_search.rs rename to crates/kebab-store-vector/tests/upsert_search.rs index 4e16f4c..2dab53b 100644 --- a/crates/kb-store-vector/tests/upsert_search.rs +++ b/crates/kebab-store-vector/tests/upsert_search.rs @@ -11,8 +11,8 @@ //! //! See `tests/common/mod.rs` for the full rationale. -use kb_core::{EmbeddingModelId, SearchFilters, VectorStore}; -use kb_store_sqlite::EmbeddingRecordRow; +use kebab_core::{EmbeddingModelId, SearchFilters, VectorStore}; +use kebab_store_sqlite::EmbeddingRecordRow; use rusqlite::params; use time::OffsetDateTime; @@ -221,7 +221,7 @@ fn model_isolation_two_models_two_directories() { // Same chunk_id, different model — should land in a separate table. let mut r2 = make_record(0xaa, 0xaa, dir(0), "alpha", &[], "model-B"); - r2.embedding_id = kb_core::EmbeddingId( + r2.embedding_id = kebab_core::EmbeddingId( "ee01ee01ee01ee01ee01ee01ee01ee01".to_string(), ); env.vector.upsert(&[r2]).unwrap(); diff --git a/tasks/phase-5-evaluation.md b/tasks/phase-5-evaluation.md index bf57c39..4688132 100644 --- a/tasks/phase-5-evaluation.md +++ b/tasks/phase-5-evaluation.md @@ -1,7 +1,7 @@ --- phase: P5 title: "Golden query / regression eval" -status: planned +status: completed depends_on: [P4] source: kb_local_rust_report.md §17 Phase 5, §18 --- From f1a448d6dcd261d14318669c0b3be9ed58d3d97a Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 2 May 2026 04:01:35 +0000 Subject: [PATCH 2/3] =?UTF-8?q?refactor(rename):=20kb=20=E2=86=92=20kebab?= =?UTF-8?q?=20=E2=80=94=20binary,=20env=20vars,=20XDG=20paths,=20file=20re?= =?UTF-8?q?names?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 두 번째 commit. 사용자 facing surface (CLI binary, env vars, XDG paths) + 코드 안 single-letter token (`KB_`, `kb.sqlite`, `/kb/`, tracing target) 일괄 rename. 그리고 3 개 file rename: - 디자인 doc `2026-04-27-kb-final-form-design.md` → `2026-04-27-kebab-final-form-design.md` - 최초 보고서 `kb_local_rust_report.md` → `kebab_local_rust_report.md` - workspace ignore `.kbignore` → `.kebabignore` ## 변경 - `crates/kebab-cli/Cargo.toml`: `[[bin]] name = "kb"` → `"kebab"`. - `crates/kebab-cli/src/main.rs`: `#[command(name = "kb", …)]` → `name = "kebab"`. - 모든 `KB_*` env var (코드 + doc + 테스트) → `KEBAB_*`. apply_env prefix 매칭 + 30+ 개 setting 키 모두. - XDG paths: `~/.config/kb` / `~/.local/share/kb` / `~/.cache/kb` / `~/.local/state/kb` → `~/.config/kebab` 등. config defaults + expand_path tests + paths.rs 의 hardcode 모두. - SQLite filename: `kb.sqlite` → `kebab.sqlite` (`SQLITE_FILE` const + 테스트 hardcode 모두). - tracing target: `target: "kb-*"` → `"kebab-*"` (10+ 곳). - snapshot fixture: `.kbignore` → `.kebabignore` (`fixtures/source-fs/ tree-1.snapshot.json` 갱신). ## 검증 - `cargo test --workspace -j 1` clean (linker OOM 회피 위해 직렬). - `cargo clippy --workspace --all-targets -- -D warnings` clean. 다음 commit 에서 docs sweep. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/kebab-app/src/lib.rs | 22 +-- crates/kebab-app/src/logging.rs | 2 +- crates/kebab-app/tests/ingest_lexical.rs | 4 +- crates/kebab-chunk/src/md_heading_v1.rs | 2 +- crates/kebab-cli/Cargo.toml | 2 +- crates/kebab-cli/src/main.rs | 2 +- crates/kebab-config/src/lib.rs | 136 +++++++++--------- crates/kebab-config/src/paths.rs | 14 +- crates/kebab-core/src/lib.rs | 2 +- crates/kebab-embed-local/src/lib.rs | 8 +- crates/kebab-embed-local/tests/embed_model.rs | 2 +- crates/kebab-embed/src/lib.rs | 2 +- crates/kebab-eval/src/metrics.rs | 8 +- crates/kebab-eval/src/runner.rs | 12 +- .../kebab-eval/tests/metrics_and_compare.rs | 6 +- crates/kebab-eval/tests/runner.rs | 12 +- crates/kebab-llm-local/src/lib.rs | 2 +- crates/kebab-llm-local/tests/integration.rs | 4 +- crates/kebab-llm/src/lib.rs | 2 +- crates/kebab-normalize/src/lib.rs | 2 +- crates/kebab-rag/src/pipeline.rs | 14 +- crates/kebab-search/src/hybrid.rs | 4 +- crates/kebab-search/tests/hybrid.rs | 12 +- crates/kebab-search/tests/lexical.rs | 8 +- crates/kebab-source-fs/src/connector.rs | 18 +-- crates/kebab-source-fs/src/lib.rs | 4 +- crates/kebab-source-fs/src/walker.rs | 14 +- .../kebab-source-fs/tests/snapshot_tree1.rs | 14 +- crates/kebab-store-sqlite/src/store.rs | 6 +- crates/kebab-store-sqlite/tests/common/mod.rs | 2 +- crates/kebab-store-sqlite/tests/fts.rs | 4 +- crates/kebab-store-vector/src/lib.rs | 2 +- crates/kebab-store-vector/src/store.rs | 12 +- crates/kebab-store-vector/tests/snapshot.rs | 8 +- ... => 2026-04-27-kebab-final-form-design.md} | 0 fixtures/source-fs/tree-1.snapshot.json | 2 +- .../tree-1/{.kbignore => .kebabignore} | 0 ...st_report.md => kebab_local_rust_report.md | 0 38 files changed, 185 insertions(+), 185 deletions(-) rename docs/superpowers/specs/{2026-04-27-kb-final-form-design.md => 2026-04-27-kebab-final-form-design.md} (100%) rename fixtures/source-fs/tree-1/{.kbignore => .kebabignore} (100%) rename kb_local_rust_report.md => kebab_local_rust_report.md (100%) diff --git a/crates/kebab-app/src/lib.rs b/crates/kebab-app/src/lib.rs index f5e6727..15d6fc9 100644 --- a/crates/kebab-app/src/lib.rs +++ b/crates/kebab-app/src/lib.rs @@ -61,7 +61,7 @@ pub use app::App; /// Kept in lock-step with the literal used in the `kb-store-sqlite` /// idempotency / round-trip tests so the version label written by the /// app and the one used in cross-crate fixtures match. -const KB_PARSE_MD_VERSION: &str = "pulldown-cmark-0.x"; +const KEBAB_PARSE_MD_VERSION: &str = "pulldown-cmark-0.x"; /// Caller-supplied knobs for one [`ask`] invocation. /// @@ -187,7 +187,7 @@ pub fn ingest_with_config( .context("kb-app::ingest: ensure Lance table")?; } - let parser_version = ParserVersion(KB_PARSE_MD_VERSION.to_string()); + let parser_version = ParserVersion(KEBAB_PARSE_MD_VERSION.to_string()); let chunk_policy = chunk_policy_from_config(&app.config); // Pre-load every existing doc_id so we can label `IngestItem.kind` @@ -236,7 +236,7 @@ pub fn ingest_with_config( Ok(i) => i, Err(e) => { tracing::error!( - target: "kb-app", + target: "kebab-app", path = %asset.workspace_path.0, error = %e, "kb-app::ingest: per-file fatal" @@ -318,7 +318,7 @@ pub fn ingest_with_config( progress, ) { tracing::warn!( - target: "kb-app", + target: "kebab-app", error = %e, "kb-app::ingest: JobRepo::update_progress failed" ); @@ -330,7 +330,7 @@ pub fn ingest_with_config( None, ) { tracing::warn!( - target: "kb-app", + target: "kebab-app", error = %e, "kb-app::ingest: JobRepo::finish failed" ); @@ -338,7 +338,7 @@ pub fn ingest_with_config( } Err(e) => { tracing::warn!( - target: "kb-app", + target: "kebab-app", error = %e, "kb-app::ingest: JobRepo::create failed; run not recorded in `jobs`" ); @@ -361,7 +361,7 @@ pub fn ingest_with_config( Ok(s) => Some(s), Err(e) => { tracing::warn!( - target: "kb-app", + target: "kebab-app", error = %e, "kb-app::ingest: failed to serialize items_json; storing NULL" ); @@ -385,14 +385,14 @@ pub fn ingest_with_config( }; if let Err(e) = app.sqlite.record_ingest_run(&row) { tracing::warn!( - target: "kb-app", + target: "kebab-app", error = %e, "kb-app::ingest: record_ingest_run failed" ); } tracing::info!( - target: "kb-app", + target: "kebab-app", scanned = scanned_count, new = new_count, updated = updated_count, @@ -448,7 +448,7 @@ fn ingest_one_asset( existing_doc_ids: &std::collections::HashSet, ) -> anyhow::Result { tracing::debug!( - target: "kb-app::ingest", + target: "kebab-app::ingest", path = %asset.workspace_path.0, "processing asset" ); @@ -795,7 +795,7 @@ pub fn doctor_with_config_path(config_path: Option<&std::path::Path>) -> anyhow: // data_dir_writable — probe the resolved storage.data_dir from the // loaded config when present, else the XDG default. Apply env - // overrides so KB_STORAGE_DATA_DIR is respected too. + // overrides so KEBAB_STORAGE_DATA_DIR is respected too. let data_dir = match loaded_cfg.as_ref() { Some(c) => { // Re-apply env overrides on top so the same precedence as diff --git a/crates/kebab-app/src/logging.rs b/crates/kebab-app/src/logging.rs index a78c4c9..1b4baeb 100644 --- a/crates/kebab-app/src/logging.rs +++ b/crates/kebab-app/src/logging.rs @@ -1,6 +1,6 @@ //! Tracing initialization helper for `kb-cli`. //! -//! Daily-rolling file appender at `~/.local/state/kb/logs/` per task spec. +//! Daily-rolling file appender at `~/.local/state/kebab/logs/` per task spec. //! Returns a `WorkerGuard` that the caller must keep alive until program //! exit (so buffered log lines flush). diff --git a/crates/kebab-app/tests/ingest_lexical.rs b/crates/kebab-app/tests/ingest_lexical.rs index f3ae13f..9999552 100644 --- a/crates/kebab-app/tests/ingest_lexical.rs +++ b/crates/kebab-app/tests/ingest_lexical.rs @@ -87,8 +87,8 @@ fn ingest_records_ingest_runs_row_with_aggregate_counts() { assert_eq!(report.scanned, 3); let db_path = std::path::PathBuf::from(&env.config.storage.data_dir) - .join("kb.sqlite"); - let conn = rusqlite::Connection::open(&db_path).expect("open kb.sqlite"); + .join("kebab.sqlite"); + let conn = rusqlite::Connection::open(&db_path).expect("open kebab.sqlite"); let (scanned, new_c, updated, skipped, errors, items_json): ( i64, i64, diff --git a/crates/kebab-chunk/src/md_heading_v1.rs b/crates/kebab-chunk/src/md_heading_v1.rs index f9054b7..f29a4d4 100644 --- a/crates/kebab-chunk/src/md_heading_v1.rs +++ b/crates/kebab-chunk/src/md_heading_v1.rs @@ -186,7 +186,7 @@ impl Chunker for MdHeadingV1Chunker { flush(&mut acc, doc, &chunker_version, &policy_hash, &mut out); tracing::debug!( - target: "kb-chunk", + target: "kebab-chunk", doc_id = %doc.doc_id, chunks = out.len(), "md-heading-v1 chunked", diff --git a/crates/kebab-cli/Cargo.toml b/crates/kebab-cli/Cargo.toml index 591b60f..abd9da6 100644 --- a/crates/kebab-cli/Cargo.toml +++ b/crates/kebab-cli/Cargo.toml @@ -8,7 +8,7 @@ repository = { workspace = true } description = "kb command-line interface" [[bin]] -name = "kb" +name = "kebab" path = "src/main.rs" [dependencies] diff --git a/crates/kebab-cli/src/main.rs b/crates/kebab-cli/src/main.rs index 25b9624..e2df18a 100644 --- a/crates/kebab-cli/src/main.rs +++ b/crates/kebab-cli/src/main.rs @@ -11,7 +11,7 @@ use kebab_app::doctor_signal::{DoctorUnhealthy, NoHitSignal, RefusalSignal}; mod wire; #[derive(Parser, Debug)] -#[command(name = "kb", version, about = "personal local knowledge base")] +#[command(name = "kebab", version, about = "personal local knowledge base")] struct Cli { /// Path to a non-default `config.toml`. #[arg(long, global = true)] diff --git a/crates/kebab-config/src/lib.rs b/crates/kebab-config/src/lib.rs index 4620ff1..9452efb 100644 --- a/crates/kebab-config/src/lib.rs +++ b/crates/kebab-config/src/lib.rs @@ -1,6 +1,6 @@ //! `kb-config` — `Config` schema and XDG path resolution (§6). //! -//! Layer order (`Config::load`): defaults → file → env (`KB_
_`). +//! Layer order (`Config::load`): defaults → file → env (`KEBAB_
_`). //! CLI overrides land later, applied by `kb-cli` after `Config::load`. use std::collections::HashMap; @@ -113,8 +113,8 @@ impl Config { ], }, storage: StorageCfg { - data_dir: "${XDG_DATA_HOME:-~/.local/share}/kb".to_string(), - sqlite: "{data_dir}/kb.sqlite".to_string(), + data_dir: "${XDG_DATA_HOME:-~/.local/share}/kebab".to_string(), + sqlite: "{data_dir}/kebab.sqlite".to_string(), vector_dir: "{data_dir}/lancedb".to_string(), asset_dir: "{data_dir}/assets".to_string(), artifact_dir: "{data_dir}/artifacts".to_string(), @@ -191,139 +191,139 @@ impl Config { Ok(cfg) } - /// Apply `KB_
_` env overrides. Unknown keys are ignored. + /// Apply `KEBAB_
_` env overrides. Unknown keys are ignored. /// /// The mapping is an explicit grep-friendly whitelist — one match arm /// per leaf key in `Config`. Booleans accept `1` / `true` / `yes` /// (case-insensitive) for true and anything else for false. Numeric /// keys silently keep their prior value if the env value fails to - /// parse, so a malformed `KB_*` cannot crash startup. + /// parse, so a malformed `KEBAB_*` cannot crash startup. pub fn apply_env(mut self, env: &HashMap) -> Self { for (k, v) in env { - if !k.starts_with("KB_") { + if !k.starts_with("KEBAB_") { continue; } match k.as_str() { // workspace - "KB_WORKSPACE_ROOT" => self.workspace.root = v.clone(), + "KEBAB_WORKSPACE_ROOT" => self.workspace.root = v.clone(), // storage - "KB_STORAGE_DATA_DIR" => self.storage.data_dir = v.clone(), - "KB_STORAGE_SQLITE" => self.storage.sqlite = v.clone(), - "KB_STORAGE_VECTOR_DIR" => self.storage.vector_dir = v.clone(), - "KB_STORAGE_ASSET_DIR" => self.storage.asset_dir = v.clone(), - "KB_STORAGE_ARTIFACT_DIR" => self.storage.artifact_dir = v.clone(), - "KB_STORAGE_MODEL_DIR" => self.storage.model_dir = v.clone(), - "KB_STORAGE_RUNS_DIR" => self.storage.runs_dir = v.clone(), - "KB_STORAGE_COPY_THRESHOLD_MB" => { + "KEBAB_STORAGE_DATA_DIR" => self.storage.data_dir = v.clone(), + "KEBAB_STORAGE_SQLITE" => self.storage.sqlite = v.clone(), + "KEBAB_STORAGE_VECTOR_DIR" => self.storage.vector_dir = v.clone(), + "KEBAB_STORAGE_ASSET_DIR" => self.storage.asset_dir = v.clone(), + "KEBAB_STORAGE_ARTIFACT_DIR" => self.storage.artifact_dir = v.clone(), + "KEBAB_STORAGE_MODEL_DIR" => self.storage.model_dir = v.clone(), + "KEBAB_STORAGE_RUNS_DIR" => self.storage.runs_dir = v.clone(), + "KEBAB_STORAGE_COPY_THRESHOLD_MB" => { if let Ok(n) = v.parse::() { self.storage.copy_threshold_mb = n; } } // indexing - "KB_INDEXING_MAX_PARALLEL_EXTRACTORS" => { + "KEBAB_INDEXING_MAX_PARALLEL_EXTRACTORS" => { if let Ok(n) = v.parse::() { self.indexing.max_parallel_extractors = n; } } - "KB_INDEXING_MAX_PARALLEL_EMBEDDINGS" => { + "KEBAB_INDEXING_MAX_PARALLEL_EMBEDDINGS" => { if let Ok(n) = v.parse::() { self.indexing.max_parallel_embeddings = n; } } - "KB_INDEXING_WATCH_FILESYSTEM" => { + "KEBAB_INDEXING_WATCH_FILESYSTEM" => { self.indexing.watch_filesystem = parse_bool(v); } // chunking - "KB_CHUNKING_TARGET_TOKENS" => { + "KEBAB_CHUNKING_TARGET_TOKENS" => { if let Ok(n) = v.parse::() { self.chunking.target_tokens = n; } } - "KB_CHUNKING_OVERLAP_TOKENS" => { + "KEBAB_CHUNKING_OVERLAP_TOKENS" => { if let Ok(n) = v.parse::() { self.chunking.overlap_tokens = n; } } - "KB_CHUNKING_RESPECT_MARKDOWN_HEADINGS" => { + "KEBAB_CHUNKING_RESPECT_MARKDOWN_HEADINGS" => { self.chunking.respect_markdown_headings = parse_bool(v); } - "KB_CHUNKING_CHUNKER_VERSION" => self.chunking.chunker_version = v.clone(), + "KEBAB_CHUNKING_CHUNKER_VERSION" => self.chunking.chunker_version = v.clone(), // models.embedding - "KB_MODELS_EMBEDDING_PROVIDER" => self.models.embedding.provider = v.clone(), - "KB_MODELS_EMBEDDING_MODEL" => self.models.embedding.model = v.clone(), - "KB_MODELS_EMBEDDING_VERSION" => self.models.embedding.version = v.clone(), - "KB_MODELS_EMBEDDING_DIMENSIONS" => { + "KEBAB_MODELS_EMBEDDING_PROVIDER" => self.models.embedding.provider = v.clone(), + "KEBAB_MODELS_EMBEDDING_MODEL" => self.models.embedding.model = v.clone(), + "KEBAB_MODELS_EMBEDDING_VERSION" => self.models.embedding.version = v.clone(), + "KEBAB_MODELS_EMBEDDING_DIMENSIONS" => { if let Ok(n) = v.parse::() { self.models.embedding.dimensions = n; } } - "KB_MODELS_EMBEDDING_BATCH_SIZE" => { + "KEBAB_MODELS_EMBEDDING_BATCH_SIZE" => { if let Ok(n) = v.parse::() { self.models.embedding.batch_size = n; } } // models.llm - "KB_MODELS_LLM_PROVIDER" => self.models.llm.provider = v.clone(), - "KB_MODELS_LLM_MODEL" => self.models.llm.model = v.clone(), - "KB_MODELS_LLM_CONTEXT_TOKENS" => { + "KEBAB_MODELS_LLM_PROVIDER" => self.models.llm.provider = v.clone(), + "KEBAB_MODELS_LLM_MODEL" => self.models.llm.model = v.clone(), + "KEBAB_MODELS_LLM_CONTEXT_TOKENS" => { if let Ok(n) = v.parse::() { self.models.llm.context_tokens = n; } } - "KB_MODELS_LLM_ENDPOINT" => self.models.llm.endpoint = v.clone(), - "KB_MODELS_LLM_TEMPERATURE" => { + "KEBAB_MODELS_LLM_ENDPOINT" => self.models.llm.endpoint = v.clone(), + "KEBAB_MODELS_LLM_TEMPERATURE" => { if let Ok(f) = v.parse::() { self.models.llm.temperature = f; } } - "KB_MODELS_LLM_SEED" => { + "KEBAB_MODELS_LLM_SEED" => { if let Ok(n) = v.parse::() { self.models.llm.seed = n; } } // search - "KB_SEARCH_DEFAULT_K" => { + "KEBAB_SEARCH_DEFAULT_K" => { if let Ok(n) = v.parse::() { self.search.default_k = n; } } - "KB_SEARCH_HYBRID_FUSION" => self.search.hybrid_fusion = v.clone(), - "KB_SEARCH_RRF_K" => { + "KEBAB_SEARCH_HYBRID_FUSION" => self.search.hybrid_fusion = v.clone(), + "KEBAB_SEARCH_RRF_K" => { if let Ok(n) = v.parse::() { self.search.rrf_k = n; } } - "KB_SEARCH_SNIPPET_CHARS" => { + "KEBAB_SEARCH_SNIPPET_CHARS" => { if let Ok(n) = v.parse::() { self.search.snippet_chars = n; } } // rag - "KB_RAG_PROMPT_TEMPLATE_VERSION" => { + "KEBAB_RAG_PROMPT_TEMPLATE_VERSION" => { self.rag.prompt_template_version = v.clone(); } - "KB_RAG_SCORE_GATE" => { + "KEBAB_RAG_SCORE_GATE" => { if let Ok(f) = v.parse::() { self.rag.score_gate = f; } } - "KB_RAG_EXPLAIN_DEFAULT" => { + "KEBAB_RAG_EXPLAIN_DEFAULT" => { self.rag.explain_default = parse_bool(v); } - "KB_RAG_MAX_CONTEXT_TOKENS" => { + "KEBAB_RAG_MAX_CONTEXT_TOKENS" => { if let Ok(n) = v.parse::() { self.rag.max_context_tokens = n; } } - // Unknown KB_* keys are silently ignored — see + // Unknown KEBAB_* keys are silently ignored — see // `env_unknown_key_is_ignored` test. _ => {} } @@ -331,58 +331,58 @@ impl Config { self } - /// `~/.config/kb/config.toml` (honors `XDG_CONFIG_HOME`). + /// `~/.config/kebab/config.toml` (honors `XDG_CONFIG_HOME`). pub fn xdg_config_path() -> PathBuf { if let Ok(custom) = std::env::var("XDG_CONFIG_HOME") { if !custom.is_empty() { - return PathBuf::from(custom).join("kb").join("config.toml"); + return PathBuf::from(custom).join("kebab").join("config.toml"); } } match dirs::config_dir() { - Some(d) => d.join("kb").join("config.toml"), - None => PathBuf::from("./kb/config.toml"), + Some(d) => d.join("kebab").join("config.toml"), + None => PathBuf::from("./kebab/config.toml"), } } - /// `~/.local/share/kb` (honors `XDG_DATA_HOME`). + /// `~/.local/share/kebab` (honors `XDG_DATA_HOME`). pub fn xdg_data_dir() -> PathBuf { if let Ok(custom) = std::env::var("XDG_DATA_HOME") { if !custom.is_empty() { - return PathBuf::from(custom).join("kb"); + return PathBuf::from(custom).join("kebab"); } } match dirs::data_dir() { - Some(d) => d.join("kb"), - None => PathBuf::from("./kb-data"), + Some(d) => d.join("kebab"), + None => PathBuf::from("./kebab-data"), } } - /// `~/.cache/kb` (honors `XDG_CACHE_HOME`). + /// `~/.cache/kebab` (honors `XDG_CACHE_HOME`). pub fn xdg_cache_dir() -> PathBuf { if let Ok(custom) = std::env::var("XDG_CACHE_HOME") { if !custom.is_empty() { - return PathBuf::from(custom).join("kb"); + return PathBuf::from(custom).join("kebab"); } } match dirs::cache_dir() { - Some(d) => d.join("kb"), - None => PathBuf::from("./kb-cache"), + Some(d) => d.join("kebab"), + None => PathBuf::from("./kebab-cache"), } } - /// `~/.local/state/kb` (honors `XDG_STATE_HOME`). + /// `~/.local/state/kebab` (honors `XDG_STATE_HOME`). pub fn xdg_state_dir() -> PathBuf { if let Ok(custom) = std::env::var("XDG_STATE_HOME") { if !custom.is_empty() { - return PathBuf::from(custom).join("kb"); + return PathBuf::from(custom).join("kebab"); } } // `dirs` doesn't expose state_dir on all platforms; fall back to - // `$HOME/.local/state/kb` if XDG_STATE_HOME is unset. + // `$HOME/.local/state/kebab` if XDG_STATE_HOME is unset. if let Some(home) = dirs::home_dir() { - return home.join(".local").join("state").join("kb"); + return home.join(".local").join("state").join("kebab"); } - PathBuf::from("./kb-state") + PathBuf::from("./kebab-state") } } @@ -417,7 +417,7 @@ mod tests { #[test] fn env_override_score_gate() { let mut env = HashMap::new(); - env.insert("KB_RAG_SCORE_GATE".to_string(), "0.5".to_string()); + env.insert("KEBAB_RAG_SCORE_GATE".to_string(), "0.5".to_string()); let c = Config::defaults().apply_env(&env); assert!((c.rag.score_gate - 0.5).abs() < 1e-6); } @@ -425,7 +425,7 @@ mod tests { #[test] fn env_override_search_k() { let mut env = HashMap::new(); - env.insert("KB_SEARCH_DEFAULT_K".to_string(), "25".to_string()); + env.insert("KEBAB_SEARCH_DEFAULT_K".to_string(), "25".to_string()); let c = Config::defaults().apply_env(&env); assert_eq!(c.search.default_k, 25); } @@ -434,7 +434,7 @@ mod tests { fn env_unknown_key_is_ignored() { let baseline = Config::defaults(); let mut env = HashMap::new(); - env.insert("KB_NOPE_FOO".to_string(), "garbage".to_string()); + env.insert("KEBAB_NOPE_FOO".to_string(), "garbage".to_string()); let c = Config::defaults().apply_env(&env); assert_eq!(c, baseline); } @@ -442,7 +442,7 @@ mod tests { #[test] fn env_overrides_chunking_target_tokens() { let mut env = HashMap::new(); - env.insert("KB_CHUNKING_TARGET_TOKENS".to_string(), "777".to_string()); + env.insert("KEBAB_CHUNKING_TARGET_TOKENS".to_string(), "777".to_string()); let c = Config::defaults().apply_env(&env); assert_eq!(c.chunking.target_tokens, 777); } @@ -451,10 +451,10 @@ mod tests { fn env_overrides_models_llm_endpoint_and_temperature() { let mut env = HashMap::new(); env.insert( - "KB_MODELS_LLM_ENDPOINT".to_string(), + "KEBAB_MODELS_LLM_ENDPOINT".to_string(), "http://10.0.0.1:11434".to_string(), ); - env.insert("KB_MODELS_LLM_TEMPERATURE".to_string(), "0.7".to_string()); + env.insert("KEBAB_MODELS_LLM_TEMPERATURE".to_string(), "0.7".to_string()); let c = Config::defaults().apply_env(&env); assert_eq!(c.models.llm.endpoint, "http://10.0.0.1:11434"); assert!((c.models.llm.temperature - 0.7).abs() < 1e-6); @@ -464,7 +464,7 @@ mod tests { fn env_overrides_indexing_watch_filesystem_bool() { let mut env = HashMap::new(); env.insert( - "KB_INDEXING_WATCH_FILESYSTEM".to_string(), + "KEBAB_INDEXING_WATCH_FILESYSTEM".to_string(), "true".to_string(), ); let c = Config::defaults().apply_env(&env); @@ -477,10 +477,10 @@ mod tests { let prev = std::env::var("XDG_CONFIG_HOME").ok(); // SAFETY: tests in this module run sequentially; we restore below. unsafe { - std::env::set_var("XDG_CONFIG_HOME", "/tmp/kbtest-xdg-config"); + std::env::set_var("XDG_CONFIG_HOME", "/tmp/kebabtest-xdg-config"); } let p = Config::xdg_config_path(); - assert_eq!(p, PathBuf::from("/tmp/kbtest-xdg-config/kb/config.toml")); + assert_eq!(p, PathBuf::from("/tmp/kebabtest-xdg-config/kebab/config.toml")); // SAFETY: scope-local restore. unsafe { match prev { diff --git a/crates/kebab-config/src/paths.rs b/crates/kebab-config/src/paths.rs index f36c6dc..bc39ca6 100644 --- a/crates/kebab-config/src/paths.rs +++ b/crates/kebab-config/src/paths.rs @@ -1,7 +1,7 @@ //! Shared path expansion helper. //! //! `Config::storage.*` fields are stored as raw template strings (e.g. -//! `${XDG_DATA_HOME:-~/.local/share}/kb`, `{data_dir}/runs`). Every +//! `${XDG_DATA_HOME:-~/.local/share}/kebab`, `{data_dir}/runs`). Every //! crate that turns one of those strings into a real filesystem path //! needs to apply the same set of substitutions; this module is the //! single source of truth so the behavior cannot drift. @@ -133,8 +133,8 @@ mod tests { // SAFETY: lock held for the duration of this test. unsafe { std::env::set_var("XDG_DATA_HOME", "/custom/path") }; - let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kb", ""); - assert_eq!(p, PathBuf::from("/custom/path/kb")); + let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kebab", ""); + assert_eq!(p, PathBuf::from("/custom/path/kebab")); } #[test] @@ -145,8 +145,8 @@ mod tests { unsafe { std::env::remove_var("XDG_DATA_HOME") }; let home = std::env::var("HOME").expect("HOME must be set in tests"); - let expected = PathBuf::from(home).join(".local/share/kb"); - let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kb", ""); + let expected = PathBuf::from(home).join(".local/share/kebab"); + let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kebab", ""); assert_eq!(p, expected); } @@ -180,7 +180,7 @@ mod tests { // SAFETY: lock held for the duration of this test. unsafe { std::env::set_var("XDG_DATA_HOME", "/xdg/data") }; - let p = expand_path("{data_dir}/runs", "/xdg/data/kb"); - assert_eq!(p, PathBuf::from("/xdg/data/kb/runs")); + let p = expand_path("{data_dir}/runs", "/xdg/data/kebab"); + assert_eq!(p, PathBuf::from("/xdg/data/kebab/runs")); } } diff --git a/crates/kebab-core/src/lib.rs b/crates/kebab-core/src/lib.rs index 629d739..3abedb2 100644 --- a/crates/kebab-core/src/lib.rs +++ b/crates/kebab-core/src/lib.rs @@ -4,7 +4,7 @@ //! `kb-*` crate, so every other crate in the workspace can depend on it //! freely. //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` for +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` for //! the canonical type bodies — this crate is the byte-for-byte mirror. pub mod ids; diff --git a/crates/kebab-embed-local/src/lib.rs b/crates/kebab-embed-local/src/lib.rs index 7267fe3..ca442fd 100644 --- a/crates/kebab-embed-local/src/lib.rs +++ b/crates/kebab-embed-local/src/lib.rs @@ -19,7 +19,7 @@ //! rules `kb-store-sqlite` applies to `data_dir` (`${XDG_DATA_HOME:-…}`, //! leading `~`, `{data_dir}` substitution). //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` //! §7.2 (Embedder), §6.4 ([models.embedding]), §9 (versioning). use std::sync::Mutex; @@ -82,7 +82,7 @@ impl FastembedEmbedder { check_dim(model_info.dim, config.models.embedding.dimensions)?; tracing::info!( - target: "kb-embed-local", + target: "kebab-embed-local", cache_dir = %cache_dir.display(), model = %config.models.embedding.model, dims = model_info.dim, @@ -97,7 +97,7 @@ impl FastembedEmbedder { .with_cache_dir(cache_dir.clone()) .with_show_download_progress(false); tracing::info!( - target: "kb-embed-local", + target: "kebab-embed-local", model = %config.models.embedding.model, cache_dir = %cache_dir.display(), "loading embedding model (first run will download ~470MB)" @@ -106,7 +106,7 @@ impl FastembedEmbedder { .context("fastembed: TextEmbedding::try_new")?; let dimensions = model_info.dim; tracing::info!( - target: "kb-embed-local", + target: "kebab-embed-local", model = %config.models.embedding.model, dimensions, "embedding model loaded" diff --git a/crates/kebab-embed-local/tests/embed_model.rs b/crates/kebab-embed-local/tests/embed_model.rs index df545ff..fee9c5f 100644 --- a/crates/kebab-embed-local/tests/embed_model.rs +++ b/crates/kebab-embed-local/tests/embed_model.rs @@ -26,7 +26,7 @@ use kebab_embed::{Embedder, EmbeddingInput, EmbeddingKind}; use kebab_embed_local::FastembedEmbedder; /// Build a `Config` whose `data_dir` lives in a per-process temp dir so -/// the test never writes into the developer's real `~/.local/share/kb`. +/// the test never writes into the developer's real `~/.local/share/kebab`. /// Returns the `Config` and the `TempDir` guard (caller keeps the guard /// alive for the test duration). fn test_config() -> (kebab_config::Config, tempfile::TempDir) { diff --git a/crates/kebab-embed/src/lib.rs b/crates/kebab-embed/src/lib.rs index e1b5c0c..6eaea68 100644 --- a/crates/kebab-embed/src/lib.rs +++ b/crates/kebab-embed/src/lib.rs @@ -11,7 +11,7 @@ //! deterministic test double. Real adapters (fastembed, candle, ollama-embed) //! live in p3-2 and MUST NOT be implemented here. //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` §7.1, §7.2, +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` §7.1, §7.2, //! §11 for the contract. // ── Trait re-exports ────────────────────────────────────────────────────── diff --git a/crates/kebab-eval/src/metrics.rs b/crates/kebab-eval/src/metrics.rs index 6f25784..4f3eee8 100644 --- a/crates/kebab-eval/src/metrics.rs +++ b/crates/kebab-eval/src/metrics.rs @@ -40,10 +40,10 @@ const STORAGE_DECIMALS: u32 = 4; /// (P5-1) used — otherwise `expected_*` / `must_contain` won't line up /// with the stored `query_id`s. `pub(crate)` so the runner shares the /// exact same name + default rather than duplicating constants. -pub(crate) const KB_EVAL_GOLDEN: &str = "KB_EVAL_GOLDEN"; +pub(crate) const KEBAB_EVAL_GOLDEN: &str = "KEBAB_EVAL_GOLDEN"; /// Default golden YAML path (relative to CWD when set). Same -/// rationale as [`KB_EVAL_GOLDEN`] — single source of truth. +/// rationale as [`KEBAB_EVAL_GOLDEN`] — single source of truth. pub(crate) const DEFAULT_GOLDEN_PATH: &str = "fixtures/golden_queries.yaml"; /// Aggregate metrics for one stored eval run. @@ -151,7 +151,7 @@ pub fn store_aggregate_with_config( /// the runner uses, same default path. Pulled into its own helper so /// `compare_runs` can share it. pub(crate) fn resolve_golden_path() -> PathBuf { - match std::env::var(KB_EVAL_GOLDEN) { + match std::env::var(KEBAB_EVAL_GOLDEN) { Ok(s) if !s.is_empty() => PathBuf::from(s), _ => PathBuf::from(DEFAULT_GOLDEN_PATH), } @@ -161,7 +161,7 @@ fn load_golden_for_metrics() -> Result> { let path = resolve_golden_path(); load_golden_set(&path).with_context(|| { format!( - "load golden set from {} (override via KB_EVAL_GOLDEN)", + "load golden set from {} (override via KEBAB_EVAL_GOLDEN)", path.display() ) }) diff --git a/crates/kebab-eval/src/runner.rs b/crates/kebab-eval/src/runner.rs index b76d72e..c70ded0 100644 --- a/crates/kebab-eval/src/runner.rs +++ b/crates/kebab-eval/src/runner.rs @@ -13,7 +13,7 @@ use kebab_store_sqlite::{EvalRunRow, SqliteStore}; use time::OffsetDateTime; use crate::loader::{load_golden_set, validate_against_db}; -use crate::metrics::{DEFAULT_GOLDEN_PATH, KB_EVAL_GOLDEN}; +use crate::metrics::{DEFAULT_GOLDEN_PATH, KEBAB_EVAL_GOLDEN}; use crate::types::{EvalRun, EvalRunOpts, GoldenQuery, QueryResult}; /// Convert a wall-clock duration since `start` into milliseconds clamped @@ -46,7 +46,7 @@ pub fn run_eval_with_config(cfg: &kebab_config::Config, opts: &EvalRunOpts) -> R let golden_path = resolve_golden_path(); let queries = load_golden_set(&golden_path).with_context(|| { format!( - "load golden set from {} (override via KB_EVAL_GOLDEN)", + "load golden set from {} (override via KEBAB_EVAL_GOLDEN)", golden_path.display() ) })?; @@ -55,7 +55,7 @@ pub fn run_eval_with_config(cfg: &kebab_config::Config, opts: &EvalRunOpts) -> R // ── 2. Mint identifiers + open store ────────────────────────────────── let run_id = mint_run_id(); let created_at = OffsetDateTime::now_utc(); - let commit_hash = std::env::var("KB_COMMIT_HASH") + let commit_hash = std::env::var("KEBAB_COMMIT_HASH") .ok() .filter(|s| !s.is_empty()); @@ -110,7 +110,7 @@ pub fn run_eval_with_config(cfg: &kebab_config::Config, opts: &EvalRunOpts) -> R let duration_ms = elapsed_ms_u32(started); tracing::info!( - target: "kb-eval", + target: "kebab-eval", run_id = %run_id, suite = %opts.suite, queries = per_query.len(), @@ -136,11 +136,11 @@ fn mint_run_id() -> String { format!("run_{id}") } -/// Resolve the golden YAML path. Honors the `KB_EVAL_GOLDEN` env +/// Resolve the golden YAML path. Honors the `KEBAB_EVAL_GOLDEN` env /// override; otherwise relative to CWD. The path is NOT expanded for /// `~` / `${...}` placeholders — direct file paths only. fn resolve_golden_path() -> PathBuf { - match std::env::var(KB_EVAL_GOLDEN) { + match std::env::var(KEBAB_EVAL_GOLDEN) { Ok(s) if !s.is_empty() => PathBuf::from(s), _ => PathBuf::from(DEFAULT_GOLDEN_PATH), } diff --git a/crates/kebab-eval/tests/metrics_and_compare.rs b/crates/kebab-eval/tests/metrics_and_compare.rs index be9746f..3cf992b 100644 --- a/crates/kebab-eval/tests/metrics_and_compare.rs +++ b/crates/kebab-eval/tests/metrics_and_compare.rs @@ -34,7 +34,7 @@ fn cfg_with_data_dir(tmp: &TempDir, golden_yaml: &str) -> Config { // SAFELY scoped — `set_var` is process-global so callers serialise // tests via the `serial_test`-style guard below. unsafe { - std::env::set_var("KB_EVAL_GOLDEN", &golden_path); + std::env::set_var("KEBAB_EVAL_GOLDEN", &golden_path); } cfg } @@ -127,9 +127,9 @@ fn write_run( store.record_eval_run_with_results(&row, &results).unwrap(); } -/// Each test mutates a process-global env var (`KB_EVAL_GOLDEN`) and +/// Each test mutates a process-global env var (`KEBAB_EVAL_GOLDEN`) and /// expects to see its own write. Take this mutex around the body of -/// every test that touches `KB_EVAL_GOLDEN` so two concurrent test +/// every test that touches `KEBAB_EVAL_GOLDEN` so two concurrent test /// threads don't trip over each other's golden YAML. fn env_guard() -> std::sync::MutexGuard<'static, ()> { use std::sync::{Mutex, OnceLock}; diff --git a/crates/kebab-eval/tests/runner.rs b/crates/kebab-eval/tests/runner.rs index e13e0f9..6a34c39 100644 --- a/crates/kebab-eval/tests/runner.rs +++ b/crates/kebab-eval/tests/runner.rs @@ -7,7 +7,7 @@ //! workspace's source-of-truth, //! - lexical-only retrieval (`SearchMode::Lexical`) so no embedder is //! required (`models.embedding.provider = "none"`), -//! - golden YAML pointed at via `KB_EVAL_GOLDEN`. +//! - golden YAML pointed at via `KEBAB_EVAL_GOLDEN`. //! //! Determinism: lexical-only with a fixed seed corpus produces //! byte-identical `per_query.jsonl` content (modulo `run_id` / @@ -24,7 +24,7 @@ use kebab_store_sqlite::SqliteStore; use rusqlite::params; use tempfile::TempDir; -/// `KB_EVAL_GOLDEN` is process-global state. Tests touching it must +/// `KEBAB_EVAL_GOLDEN` is process-global state. Tests touching it must /// serialize so they don't trample each other when `cargo test` /// runs them in parallel. static GOLDEN_ENV_LOCK: Mutex<()> = Mutex::new(()); @@ -143,19 +143,19 @@ fn lexical_opts() -> EvalRunOpts { } } -/// Run the eval after pointing `KB_EVAL_GOLDEN` at `yaml`. The env +/// Run the eval after pointing `KEBAB_EVAL_GOLDEN` at `yaml`. The env /// guard must outlive the call so concurrent tests don't reset the /// var mid-run. fn run_with_golden R, R>(yaml: &Path, f: F) -> R { let _g = GOLDEN_ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner()); - // SAFETY: `KB_EVAL_GOLDEN` is a benign env var; the GOLDEN_ENV_LOCK + // SAFETY: `KEBAB_EVAL_GOLDEN` is a benign env var; the GOLDEN_ENV_LOCK // serializes mutations so concurrent tests don't race. unsafe { - std::env::set_var("KB_EVAL_GOLDEN", yaml); + std::env::set_var("KEBAB_EVAL_GOLDEN", yaml); } let out = f(); unsafe { - std::env::remove_var("KB_EVAL_GOLDEN"); + std::env::remove_var("KEBAB_EVAL_GOLDEN"); } out } diff --git a/crates/kebab-llm-local/src/lib.rs b/crates/kebab-llm-local/src/lib.rs index 3e0bbfd..d97e521 100644 --- a/crates/kebab-llm-local/src/lib.rs +++ b/crates/kebab-llm-local/src/lib.rs @@ -29,7 +29,7 @@ //! - **Lazy connect.** [`OllamaLanguageModel::new`] does not hit the network; //! the first error surfaces on [`LanguageModel::generate_stream`]. //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` §7.2, +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` §7.2, //! §6.4 (`[models.llm]`), §0 Q5 (streaming), §10 (errors), and report §11.2 //! (Ollama protocol notes). diff --git a/crates/kebab-llm-local/tests/integration.rs b/crates/kebab-llm-local/tests/integration.rs index 16a237c..a0836e2 100644 --- a/crates/kebab-llm-local/tests/integration.rs +++ b/crates/kebab-llm-local/tests/integration.rs @@ -19,8 +19,8 @@ use kebab_llm_local::{LanguageModel, OllamaLanguageModel}; #[ignore = "requires a local Ollama daemon + pulled model"] fn real_ollama_streams_non_empty_response() { // Use whatever model the workspace defaults select. Override via the - // KB_MODELS_LLM_MODEL env var if you want a different one for this run - // (e.g. `KB_MODELS_LLM_MODEL=qwen2.5:7b-instruct cargo test ... -- --ignored`). + // KEBAB_MODELS_LLM_MODEL env var if you want a different one for this run + // (e.g. `KEBAB_MODELS_LLM_MODEL=qwen2.5:7b-instruct cargo test ... -- --ignored`). let cfg = Config::load(None).expect("config should load"); let llm = OllamaLanguageModel::new(&cfg).unwrap(); diff --git a/crates/kebab-llm/src/lib.rs b/crates/kebab-llm/src/lib.rs index 5001c52..c536ae8 100644 --- a/crates/kebab-llm/src/lib.rs +++ b/crates/kebab-llm/src/lib.rs @@ -12,7 +12,7 @@ //! from `generate_stream` itself (e.g., connection refused) before any chunk //! is yielded; the mock never does. //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` §7.1, §7.2, +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` §7.1, §7.2, //! §0 Q5 (streaming), §3.8 (`ModelRef`) for the contract. // ── Trait re-exports ────────────────────────────────────────────────────── diff --git a/crates/kebab-normalize/src/lib.rs b/crates/kebab-normalize/src/lib.rs index 2723d6f..b65cb18 100644 --- a/crates/kebab-normalize/src/lib.rs +++ b/crates/kebab-normalize/src/lib.rs @@ -96,7 +96,7 @@ pub fn build_canonical_document( .collect(); tracing::debug!( - target: "kb-normalize", + target: "kebab-normalize", "built canonical document doc_id={} blocks={}", doc_id.0, lifted_blocks.len() diff --git a/crates/kebab-rag/src/pipeline.rs b/crates/kebab-rag/src/pipeline.rs index fe9fbe3..3271f63 100644 --- a/crates/kebab-rag/src/pipeline.rs +++ b/crates/kebab-rag/src/pipeline.rs @@ -135,7 +135,7 @@ impl RagPipeline { let top_score = hits.first().map(|h| h.retrieval.fusion_score).unwrap_or(0.0); tracing::debug!( - target: "kb-rag", + target: "kebab-rag", chunks_returned, top_score, mode = ?opts.mode, @@ -161,7 +161,7 @@ impl RagPipeline { // collapse to the more accurate `NoChunks` refusal here. if packed_entries.is_empty() { tracing::warn!( - target: "kb-rag", + target: "kebab-rag", chunks_returned = hits.len(), "kb-rag: all retrieved chunks were unfetchable from the store; \ falling back to NoChunks refusal" @@ -324,7 +324,7 @@ impl RagPipeline { // Drop the moved `finish_reason` early into a tracing breadcrumb; the // wire schema does not surface it (per design §3.8). tracing::debug!( - target: "kb-rag", + target: "kebab-rag", grounded = answer.grounded, refusal = ?answer.refusal_reason, refusal_phrase_detected = matched_refusal_phrase, @@ -354,7 +354,7 @@ impl RagPipeline { self.docs.put_answer(&answer, query, packed_chunks_json.as_deref()) { tracing::warn!( - target: "kb-rag", + target: "kebab-rag", error = %e, "kb-rag: put_answer failed; in-memory Answer still returned" ); @@ -386,7 +386,7 @@ impl RagPipeline { Some(c) => c.text, None => { tracing::warn!( - target: "kb-rag", + target: "kebab-rag", chunk_id = %hit.chunk_id.0, "kb-rag: chunk not found in store; skipping" ); @@ -454,7 +454,7 @@ impl RagPipeline { created_at: OffsetDateTime::now_utc(), }; if let Err(e) = self.docs.put_answer(&answer, query, None) { - tracing::warn!(target: "kb-rag", error = %e, "kb-rag: put_answer (NoChunks) failed"); + tracing::warn!(target: "kebab-rag", error = %e, "kb-rag: put_answer (NoChunks) failed"); } Ok(answer) } @@ -529,7 +529,7 @@ impl RagPipeline { created_at: OffsetDateTime::now_utc(), }; if let Err(e) = self.docs.put_answer(&answer, query, None) { - tracing::warn!(target: "kb-rag", error = %e, "kb-rag: put_answer (ScoreGate) failed"); + tracing::warn!(target: "kebab-rag", error = %e, "kb-rag: put_answer (ScoreGate) failed"); } Ok(answer) } diff --git a/crates/kebab-search/src/hybrid.rs b/crates/kebab-search/src/hybrid.rs index e1c036b..9ebd7de 100644 --- a/crates/kebab-search/src/hybrid.rs +++ b/crates/kebab-search/src/hybrid.rs @@ -93,7 +93,7 @@ impl HybridRetriever { let vec_iv = vector.index_version(); if lex_iv.0 != vec_iv.0 { tracing::warn!( - target: "kb-search", + target: "kebab-search", lexical_index = %lex_iv.0, vector_index = %vec_iv.0, "kb-search hybrid: lexical and vector index_version differ; consider re-indexing" @@ -323,7 +323,7 @@ fn parse_fusion(name: &str, k_rrf: u32) -> FusionPolicy { "rrf" => FusionPolicy::Rrf { k_rrf: k }, other => { tracing::warn!( - target: "kb-search", + target: "kebab-search", policy = other, "kb-search hybrid: unknown fusion policy; falling back to RRF" ); diff --git a/crates/kebab-search/tests/hybrid.rs b/crates/kebab-search/tests/hybrid.rs index af62bfc..fcda0c5 100644 --- a/crates/kebab-search/tests/hybrid.rs +++ b/crates/kebab-search/tests/hybrid.rs @@ -157,17 +157,17 @@ fn hybrid_snapshot_run_1() { .join("hybrid") .join("run-1.json"); - if std::env::var_os("KB_UPDATE_SNAPSHOTS").is_some() { + if std::env::var_os("KEBAB_UPDATE_SNAPSHOTS").is_some() { std::fs::create_dir_all(fixture.parent().unwrap()).unwrap(); std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap()).unwrap(); eprintln!("[snapshot] regenerated {}", fixture.display()); - // Fail loudly so that accidentally setting KB_UPDATE_SNAPSHOTS + // Fail loudly so that accidentally setting KEBAB_UPDATE_SNAPSHOTS // in CI surfaces as a test failure rather than a silent // overwrite + green run. Same fail-loud-instead-of-silent-pass // philosophy as P3-2's `SNAPSHOT_HASH_BASELINE = 0` and P3-3's // placeholder fixture guards. panic!( - "[snapshot] regenerated {}, re-run without KB_UPDATE_SNAPSHOTS to verify pin", + "[snapshot] regenerated {}, re-run without KEBAB_UPDATE_SNAPSHOTS to verify pin", fixture.display() ); } @@ -176,7 +176,7 @@ fn hybrid_snapshot_run_1() { serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else(|_| { panic!( "missing snapshot fixture at {}; run with \ - KB_UPDATE_SNAPSHOTS=1 to create", + KEBAB_UPDATE_SNAPSHOTS=1 to create", fixture.display() ) })) @@ -189,14 +189,14 @@ fn hybrid_snapshot_run_1() { panic!( "snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \ Path: {}. To regenerate: \ - `KB_UPDATE_SNAPSHOTS=1 cargo test -p kb-search -- --ignored hybrid_snapshot`.", + `KEBAB_UPDATE_SNAPSHOTS=1 cargo test -p kb-search -- --ignored hybrid_snapshot`.", fixture.display() ); } assert_eq!( actual, expected, - "hybrid snapshot drift; rerun with KB_UPDATE_SNAPSHOTS=1 to regenerate" + "hybrid snapshot drift; rerun with KEBAB_UPDATE_SNAPSHOTS=1 to regenerate" ); // Independent guard: fusion scores must be non-increasing across diff --git a/crates/kebab-search/tests/lexical.rs b/crates/kebab-search/tests/lexical.rs index 4cb98be..2939aef 100644 --- a/crates/kebab-search/tests/lexical.rs +++ b/crates/kebab-search/tests/lexical.rs @@ -29,7 +29,7 @@ impl Env { config.storage.data_dir = temp.path().to_string_lossy().into_owned(); let store = SqliteStore::open(&config).expect("open store"); store.run_migrations().expect("run migrations"); - let db_path = temp.path().join("kb.sqlite"); + let db_path = temp.path().join("kebab.sqlite"); Self { _temp: temp, store: Arc::new(store), @@ -618,7 +618,7 @@ fn lexical_snapshot_run_1() { // `Vec` for a fixed query is checked verbatim against // `tests/fixtures/search/lexical/run-1.json`. Update both sides in // the same commit when intentional changes ship. - // Stable because rusqlite ships bundled SQLite — a tokenizer/bm25 algorithm change in a future SQLite bump will require regenerating run-1.json via `KB_UPDATE_SNAPSHOTS=1`. + // Stable because rusqlite ships bundled SQLite — a tokenizer/bm25 algorithm change in a future SQLite bump will require regenerating run-1.json via `KEBAB_UPDATE_SNAPSHOTS=1`. let env = Env::new(); let conn = env.raw_conn(); insert_document(&conn, &id32("d"), "notes/snap.md", "Snap", "en", "primary", &[]); @@ -656,11 +656,11 @@ fn lexical_snapshot_run_1() { let baseline_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/search/lexical/run-1.json"); - if std::env::var_os("KB_UPDATE_SNAPSHOTS").is_some() { + if std::env::var_os("KEBAB_UPDATE_SNAPSHOTS").is_some() { std::fs::write(&baseline_path, serde_json::to_string_pretty(&actual).unwrap()).unwrap(); } let baseline_text = std::fs::read_to_string(&baseline_path) - .expect("baseline snapshot must exist; run with KB_UPDATE_SNAPSHOTS=1 to seed"); + .expect("baseline snapshot must exist; run with KEBAB_UPDATE_SNAPSHOTS=1 to seed"); let expected: serde_json::Value = serde_json::from_str(&baseline_text).unwrap(); assert_eq!(actual, expected, "lexical run-1 snapshot drift"); } diff --git a/crates/kebab-source-fs/src/connector.rs b/crates/kebab-source-fs/src/connector.rs index f904395..0cf5e3e 100644 --- a/crates/kebab-source-fs/src/connector.rs +++ b/crates/kebab-source-fs/src/connector.rs @@ -74,13 +74,13 @@ impl SourceConnector for FsSourceConnector { scope.root.clone() }; - // Union: config.workspace.exclude ∪ scope.exclude ∪ .kbignore. - // Per §6.2 the union of `.kbignore` and `config.workspace.exclude` + // Union: config.workspace.exclude ∪ scope.exclude ∪ .kebabignore. + // Per §6.2 the union of `.kebabignore` and `config.workspace.exclude` // is the filter set. `scope.exclude` is added on top so a caller // can layer a per-call narrowing. let mut excludes = self.default_exclude.clone(); excludes.extend(scope.exclude.iter().cloned()); - // .kbignore is re-read on every scan() so users can edit it without + // .kebabignore is re-read on every scan() so users can edit it without // restarting any long-running process. let kbignore = read_kbignore(&root)?; @@ -243,7 +243,7 @@ mod tests { fn scan_filters_by_kbignore() { let dir = tempfile::tempdir().unwrap(); let root = dir.path(); - std::fs::write(root.join(".kbignore"), "*.tmp\n").unwrap(); + std::fs::write(root.join(".kebabignore"), "*.tmp\n").unwrap(); std::fs::write(root.join("a.md"), b"x").unwrap(); std::fs::write(root.join("b.tmp"), b"x").unwrap(); @@ -252,14 +252,14 @@ mod tests { .unwrap(); let v = conn.scan(&SourceScope::default()).unwrap(); let names: Vec<_> = v.iter().map(|a| a.workspace_path.0.clone()).collect(); - // Decision: `.kbignore` itself IS emitted as a RawAsset (MediaType::Other("")). + // Decision: `.kebabignore` itself IS emitted as a RawAsset (MediaType::Other("")). // Rationale: a config file that affects ingest is itself part of the // workspace contents; the markdown extractor (P1-2) will reject Other("") - // on its own. If we ever decide to omit `.kbignore` from the asset list, + // on its own. If we ever decide to omit `.kebabignore` from the asset list, // this test will catch it. assert!( - names.contains(&".kbignore".to_string()), - ".kbignore must be emitted as an asset; got: {names:?}" + names.contains(&".kebabignore".to_string()), + ".kebabignore must be emitted as an asset; got: {names:?}" ); assert!(names.contains(&"a.md".to_string())); assert!(!names.contains(&"b.tmp".to_string())); @@ -285,7 +285,7 @@ mod tests { fn scan_unions_config_exclude_and_kbignore() { let dir = tempfile::tempdir().unwrap(); let root = dir.path(); - std::fs::write(root.join(".kbignore"), "*.tmp\n").unwrap(); + std::fs::write(root.join(".kebabignore"), "*.tmp\n").unwrap(); std::fs::write(root.join("a.md"), b"x").unwrap(); std::fs::write(root.join("b.tmp"), b"x").unwrap(); std::fs::write(root.join("c.log"), b"x").unwrap(); diff --git a/crates/kebab-source-fs/src/lib.rs b/crates/kebab-source-fs/src/lib.rs index ae6b128..ea7f294 100644 --- a/crates/kebab-source-fs/src/lib.rs +++ b/crates/kebab-source-fs/src/lib.rs @@ -1,10 +1,10 @@ //! `kb-source-fs` — local filesystem `SourceConnector`. //! //! Walks `config.workspace.root`, applies gitignore-style filters from -//! `config.workspace.exclude` ∪ `.kbignore`, computes BLAKE3 of every file, +//! `config.workspace.exclude` ∪ `.kebabignore`, computes BLAKE3 of every file, //! and emits `Vec` sorted by `workspace_path` for determinism. //! -//! Per design §3.3 (RawAsset), §6.2 (workspace + .kbignore), §6.6 (POSIX +//! Per design §3.3 (RawAsset), §6.2 (workspace + .kebabignore), §6.6 (POSIX //! normalization), §7.1 (SourceScope), §7.2 (SourceConnector), §8 (module //! boundaries). diff --git a/crates/kebab-source-fs/src/walker.rs b/crates/kebab-source-fs/src/walker.rs index 6f399cc..1ba14fa 100644 --- a/crates/kebab-source-fs/src/walker.rs +++ b/crates/kebab-source-fs/src/walker.rs @@ -3,7 +3,7 @@ //! //! Filter set (per task spec, design §6.2): //! - `config.workspace.exclude` (passed in by `FsSourceConnector`) -//! - `/.kbignore` (optional file at workspace root) +//! - `/.kebabignore` (optional file at workspace root) //! - default-excludes for `.DS_Store` and macOS resource forks (`._*`) //! //! All three are merged via `ignore::overrides::OverrideBuilder`, which @@ -36,7 +36,7 @@ use walkdir::{DirEntry, WalkDir}; /// Default-excludes baked into the connector. These are NOT configurable; /// they cover noise that is never useful to ingest and would otherwise need -/// to appear in every user's `.kbignore`. +/// to appear in every user's `.kebabignore`. const DEFAULT_EXCLUDES: &[&str] = &[ // Finder metadata ".DS_Store", @@ -46,7 +46,7 @@ const DEFAULT_EXCLUDES: &[&str] = &[ "**/._*", ]; -/// Build the merged `Override` from `config.workspace.exclude` ∪ `.kbignore` +/// Build the merged `Override` from `config.workspace.exclude` ∪ `.kebabignore` /// ∪ baked-in default excludes. /// /// Each input pattern is registered as an *exclude* (gitignore-style: a @@ -73,16 +73,16 @@ pub(crate) fn build_overrides( for pat in kbignore_patterns { builder .add(&format!("!{pat}")) - .with_context(|| format!("invalid .kbignore pattern: {pat}"))?; + .with_context(|| format!("invalid .kebabignore pattern: {pat}"))?; } builder.build().context("failed to compile override set") } -/// Read `/.kbignore` if it exists. Each non-blank, non-comment line is +/// Read `/.kebabignore` if it exists. Each non-blank, non-comment line is /// a gitignore pattern. Missing file → empty Vec (not an error). pub(crate) fn read_kbignore(root: &Path) -> Result> { - let path = root.join(".kbignore"); + let path = root.join(".kebabignore"); if !path.exists() { return Ok(Vec::new()); } @@ -250,7 +250,7 @@ mod tests { fn read_kbignore_strips_blanks_and_comments() { let dir = tempfile::tempdir().unwrap(); std::fs::write( - dir.path().join(".kbignore"), + dir.path().join(".kebabignore"), "# comment\n*.tmp\n\nignored/**\n", ) .unwrap(); diff --git a/crates/kebab-source-fs/tests/snapshot_tree1.rs b/crates/kebab-source-fs/tests/snapshot_tree1.rs index 0431faf..3578710 100644 --- a/crates/kebab-source-fs/tests/snapshot_tree1.rs +++ b/crates/kebab-source-fs/tests/snapshot_tree1.rs @@ -9,8 +9,8 @@ //! │ ├── alpha.md //! │ └── beta.md //! ├── ignored/ -//! │ └── skip.tmp # excluded by .kbignore -//! ├── .kbignore # contains: *.tmp +//! │ └── skip.tmp # excluded by .kebabignore +//! ├── .kebabignore # contains: *.tmp //! └── .DS_Store # implicitly excluded //! ``` //! @@ -52,7 +52,7 @@ fn cfg_for_fixture(root: &str) -> Config { let mut c = Config::defaults(); c.workspace.root = root.to_string(); // Clear default excludes (`.git/**`, `node_modules/**`, `.obsidian/**`) - // so the snapshot is purely a function of the fixture + .kbignore + + // so the snapshot is purely a function of the fixture + .kebabignore + // baked-in default-excludes. c.workspace.exclude.clear(); c @@ -101,18 +101,18 @@ fn scan_and_strip() -> Value { fn tree_1_snapshot_matches_baseline() { let actual = scan_and_strip(); - // If KB_REGEN_SNAPSHOT is set, (re)write the baseline and exit + // If KEBAB_REGEN_SNAPSHOT is set, (re)write the baseline and exit // *before* attempting to read it. This is the only path that may // create the file from scratch. - if std::env::var_os("KB_REGEN_SNAPSHOT").is_some() { + if std::env::var_os("KEBAB_REGEN_SNAPSHOT").is_some() { let pretty = serde_json::to_string_pretty(&actual).unwrap() + "\n"; std::fs::write(baseline_path(), pretty).expect("write baseline"); - panic!("regenerated baseline; rerun without KB_REGEN_SNAPSHOT to verify"); + panic!("regenerated baseline; rerun without KEBAB_REGEN_SNAPSHOT to verify"); } let baseline_text = std::fs::read_to_string(baseline_path()).unwrap_or_else(|_| { panic!( - "missing baseline at {} — regenerate via `KB_REGEN_SNAPSHOT=1 cargo test \ + "missing baseline at {} — regenerate via `KEBAB_REGEN_SNAPSHOT=1 cargo test \ -p kb-source-fs --test snapshot_tree1 -- tree_1_snapshot_matches_baseline`", baseline_path().display() ) diff --git a/crates/kebab-store-sqlite/src/store.rs b/crates/kebab-store-sqlite/src/store.rs index 2ee3636..0ac1408 100644 --- a/crates/kebab-store-sqlite/src/store.rs +++ b/crates/kebab-store-sqlite/src/store.rs @@ -28,7 +28,7 @@ const ASSET_ID_HEX_LEN: usize = 32; /// Default file name under `config.storage.data_dir`. Kept private — the /// path layout is a §6.3 design decision, not part of the store's public /// surface. -const SQLITE_FILE: &str = "kb.sqlite"; +const SQLITE_FILE: &str = "kebab.sqlite"; /// Subdirectory under `data_dir` holding shard-prefixed asset bytes /// (`/`). Mirrors design §6.3. @@ -74,7 +74,7 @@ impl SqliteStore { apply_pragmas(&conn)?; tracing::debug!( - target: "kb-store-sqlite", + target: "kebab-store-sqlite", data_dir = %data_dir.display(), db = %db_path.display(), "opened sqlite store" @@ -94,7 +94,7 @@ impl SqliteStore { schema::runner() .run(&mut *conn) .map_err(|e| StoreError::Migration(e.to_string()))?; - tracing::debug!(target: "kb-store-sqlite", "migrations applied"); + tracing::debug!(target: "kebab-store-sqlite", "migrations applied"); Ok(()) } diff --git a/crates/kebab-store-sqlite/tests/common/mod.rs b/crates/kebab-store-sqlite/tests/common/mod.rs index e65c18f..b9f8b25 100644 --- a/crates/kebab-store-sqlite/tests/common/mod.rs +++ b/crates/kebab-store-sqlite/tests/common/mod.rs @@ -37,7 +37,7 @@ impl TestEnv { } pub fn db_path(&self) -> PathBuf { - self.temp.path().join("kb.sqlite") + self.temp.path().join("kebab.sqlite") } /// Open a side-channel rusqlite connection for direct SQL inspection. diff --git a/crates/kebab-store-sqlite/tests/fts.rs b/crates/kebab-store-sqlite/tests/fts.rs index 350c16d..2d66204 100644 --- a/crates/kebab-store-sqlite/tests/fts.rs +++ b/crates/kebab-store-sqlite/tests/fts.rs @@ -335,7 +335,7 @@ fn normalize_ws(s: &str) -> String { /// - no `END;` after the virtual-table line fn extract_design_5_5_fts_block() -> String { let doc = include_str!( - "../../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md" + "../../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md" ); let heading_idx = doc .find("### 5.5 Chunks + FTS5") @@ -437,7 +437,7 @@ fn fts_v002_matches_design_section_5_5_verbatim() { // ── 6. WAL cleanup: drop store before tempdir reaps WAL/SHM ────────── /// Mirror the P1-6 pattern: opening + migrating + dropping the store -/// must not strand `kb.sqlite-wal`/`-shm` files such that the tempdir +/// must not strand `kebab.sqlite-wal`/`-shm` files such that the tempdir /// can't be cleaned up. After dropping the store + side-channel conn, /// the WAL/SHM siblings must either not exist or be removable — if a /// stray handle were holding them open, on Windows the remove would diff --git a/crates/kebab-store-vector/src/lib.rs b/crates/kebab-store-vector/src/lib.rs index 53b30a9..5543982 100644 --- a/crates/kebab-store-vector/src/lib.rs +++ b/crates/kebab-store-vector/src/lib.rs @@ -20,7 +20,7 @@ //! serializes vector ops, and current-thread saves the two worker //! threads a multi-thread runtime spawns by default. //! -//! See `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` +//! See `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` //! §5.6 (embedding_records DDL), §6.3 (lancedb table naming), //! §7.2 (VectorStore), §9 (versioning). diff --git a/crates/kebab-store-vector/src/store.rs b/crates/kebab-store-vector/src/store.rs index dbb857c..2fbbc0e 100644 --- a/crates/kebab-store-vector/src/store.rs +++ b/crates/kebab-store-vector/src/store.rs @@ -111,7 +111,7 @@ impl LanceVectorStore { })?; tracing::debug!( - target: "kb-store-vector", + target: "kebab-store-vector", vector_dir = %vector_dir.display(), "opened LanceVectorStore" ); @@ -141,7 +141,7 @@ impl LanceVectorStore { .await .context("create_empty_table")?; tracing::info!( - target: "kb-store-vector", + target: "kebab-store-vector", table = table_name, dim, "created Lance table" @@ -275,7 +275,7 @@ impl VectorStore for LanceVectorStore { .context("phase 3: mark embedding_records committed")?; tracing::info!( - target: "kb-store-vector", + target: "kebab-store-vector", table = %table_name, rows = recs.len(), "upsert committed" @@ -306,7 +306,7 @@ impl VectorStore for LanceVectorStore { Some(name) => name, None => { tracing::debug!( - target: "kb-store-vector", + target: "kebab-store-vector", dim, "search: no Lance table matches query dim — returning empty" ); @@ -477,7 +477,7 @@ fn decode_lance_hits(batches: &[RecordBatch]) -> Result> { fn score_from_distance(distance: f32) -> f32 { if distance.is_nan() { tracing::warn!( - target: "kb-store-vector", + target: "kebab-store-vector", "NaN cosine distance from Lance — coercing to score 0" ); return 0.0; @@ -515,7 +515,7 @@ async fn find_matching_table( } Err(e) => { tracing::warn!( - target: "kb-store-vector", + target: "kebab-store-vector", table = %name, error = %e, "search: skipped unopenable table" diff --git a/crates/kebab-store-vector/tests/snapshot.rs b/crates/kebab-store-vector/tests/snapshot.rs index 1d3d9e1..8f33702 100644 --- a/crates/kebab-store-vector/tests/snapshot.rs +++ b/crates/kebab-store-vector/tests/snapshot.rs @@ -73,7 +73,7 @@ fn vector_hits_snapshot_run_1() { .join("vector") .join("run-1.json"); - if std::env::var_os("KB_UPDATE_SNAPSHOTS").is_some() { + if std::env::var_os("KEBAB_UPDATE_SNAPSHOTS").is_some() { std::fs::create_dir_all(fixture.parent().unwrap()).unwrap(); std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap()) .unwrap(); @@ -83,7 +83,7 @@ fn vector_hits_snapshot_run_1() { let expected: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else( |_| panic!( - "missing snapshot fixture at {}; run with KB_UPDATE_SNAPSHOTS=1 to create", + "missing snapshot fixture at {}; run with KEBAB_UPDATE_SNAPSHOTS=1 to create", fixture.display() ), )) @@ -97,14 +97,14 @@ fn vector_hits_snapshot_run_1() { panic!( "snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \ Path: {}. To regenerate: \ - `KB_UPDATE_SNAPSHOTS=1 cargo test -p kb-store-vector -- --ignored snapshot`.", + `KEBAB_UPDATE_SNAPSHOTS=1 cargo test -p kb-store-vector -- --ignored snapshot`.", fixture.display() ); } assert_eq!( actual, expected, - "snapshot drift; rerun with KB_UPDATE_SNAPSHOTS=1 to regenerate" + "snapshot drift; rerun with KEBAB_UPDATE_SNAPSHOTS=1 to regenerate" ); // Independent guard: scores must be non-increasing. diff --git a/docs/superpowers/specs/2026-04-27-kb-final-form-design.md b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md similarity index 100% rename from docs/superpowers/specs/2026-04-27-kb-final-form-design.md rename to docs/superpowers/specs/2026-04-27-kebab-final-form-design.md diff --git a/fixtures/source-fs/tree-1.snapshot.json b/fixtures/source-fs/tree-1.snapshot.json index 350d53c..7059f6a 100644 --- a/fixtures/source-fs/tree-1.snapshot.json +++ b/fixtures/source-fs/tree-1.snapshot.json @@ -15,7 +15,7 @@ "kind": "copied", "path": "" }, - "workspace_path": ".kbignore" + "workspace_path": ".kebabignore" }, { "asset_id": "ba6cd31cab86eff7a86638ee76494bcf", diff --git a/fixtures/source-fs/tree-1/.kbignore b/fixtures/source-fs/tree-1/.kebabignore similarity index 100% rename from fixtures/source-fs/tree-1/.kbignore rename to fixtures/source-fs/tree-1/.kebabignore diff --git a/kb_local_rust_report.md b/kebab_local_rust_report.md similarity index 100% rename from kb_local_rust_report.md rename to kebab_local_rust_report.md From f9714aa5cbf06ce2166d9301b646f706d5deea3e Mon Sep 17 00:00:00 2001 From: altair823 Date: Sat, 2 May 2026 04:01:55 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs(rename):=20kb=20=E2=86=92=20kebab=20?= =?UTF-8?q?=E2=80=94=20README,=20tasks/,=20docs/,=20design=20doc,=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 마지막 commit. 모든 .md 안의 `kb` 단어 일괄 갱신. - 19 개 crate 이름 (`kb-core`, `kb-app`, …) → `kebab-*` (Rust 모듈 path 표기 `kb_*` → `kebab_*` 포함). - 미래 component (`kb-tui`, `kb-desktop`, `kb-asr-whisper`, `kb-ocr`, `kb-mcp`, `kb-vlm`, `kb-rerank`, `kb-vision-ocr`, `kb-index`, `kb-smoke`, `kb-architecture`) → `kebab-*` (P6+ 가 시작될 때 같은 prefix 사용). - CLI 명령 예제: `kb ingest` / `kb search` / `kb ask` / `kb init` / `kb doctor` / `kb inspect` / `kb list` / `kb eval` → `kebab `. fenced code block + 인라인 backtick 모두. - XDG paths + env vars + binary 경로 (`target/release/kb` → `target/release/kebab`) 동기화. - design doc / 최초 보고서 / SMOKE / HOTFIXES / phase epic / task spec 모든 reference 통일. - task-decomposition.md 의 `git -c user.name=kb` 는 과거 git history 기록용 author 정보라 그대로 유지 (실제 git history 의 author 는 변경 불가). - `tasks/phase-5-evaluation.md` 의 `status: planned` → `completed` 도 같이 (P5-1 + P5-2 PR 머지 후 미반영분). ## 검증 - `grep -rEn "\bkb-[a-z]|\bkb_[a-z]|\.config/kb\b|kb\.sqlite|\bKB_[A-Z]" --include="*.md"` 0 hits (task-decomposition.md 의 git author 제외). - 모든 file path reference 살아있음 (renamed file 들 모두 새 path 로 update). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 140 ++++----- docs/SMOKE.md | 52 ++-- docs/spec/ai-generation-guidelines.md | 4 +- docs/spec/canonical-document.md | 2 +- docs/spec/chunk-policy.md | 2 +- docs/spec/citation-policy.md | 2 +- docs/spec/domain-model.md | 4 +- docs/spec/ids.md | 4 +- docs/spec/module-boundaries.md | 8 +- .../plans/2026-04-27-task-decomposition.md | 270 +++++++++--------- .../2026-04-27-kebab-final-form-design.md | 210 +++++++------- kebab_local_rust_report.md | 268 ++++++++--------- tasks/HOTFIXES.md | 28 +- tasks/INDEX.md | 24 +- tasks/_template.md | 6 +- tasks/p0/p0-1-skeleton.md | 118 ++++---- tasks/p1/p1-1-source-fs.md | 38 +-- tasks/p1/p1-2-parse-md-frontmatter.md | 30 +- tasks/p1/p1-3-parse-md-blocks.md | 36 +-- tasks/p1/p1-4-normalize.md | 52 ++-- tasks/p1/p1-5-chunk.md | 30 +- tasks/p1/p1-6-store-sqlite.md | 54 ++-- tasks/p2/p2-1-fts-schema.md | 24 +- tasks/p2/p2-2-lexical-retriever.md | 44 +-- tasks/p3/p3-1-embedder-trait.md | 36 +-- tasks/p3/p3-2-fastembed-adapter.md | 34 +-- tasks/p3/p3-3-lancedb-store.md | 42 +-- tasks/p3/p3-4-hybrid-fusion.md | 54 ++-- tasks/p3/p3-5-app-wiring.md | 102 +++---- tasks/p4/p4-1-llm-trait.md | 32 +-- tasks/p4/p4-2-ollama-adapter.md | 40 +-- tasks/p4/p4-3-rag-pipeline.md | 54 ++-- tasks/p5/p5-1-golden-fixture-runner.md | 46 +-- tasks/p5/p5-2-metrics-compare.md | 42 +-- tasks/p6/p6-1-image-extractor-exif.md | 34 +-- tasks/p6/p6-2-ocr-adapter.md | 36 +-- tasks/p6/p6-3-caption-adapter.md | 38 +-- tasks/p7/p7-1-pdf-text-extractor.md | 28 +- tasks/p7/p7-2-pdf-page-chunker.md | 32 +-- tasks/p8/p8-1-whisper-adapter.md | 36 +-- tasks/p8/p8-2-segment-chunker.md | 30 +- tasks/p9/p9-1-tui-library.md | 38 +-- tasks/p9/p9-2-tui-search.md | 40 +-- tasks/p9/p9-3-tui-ask.md | 40 +-- tasks/p9/p9-4-tui-inspect.md | 36 +-- tasks/p9/p9-5-desktop-tauri.md | 48 ++-- tasks/phase-0-skeleton.md | 32 +-- tasks/phase-1-markdown-ingestion.md | 46 +-- tasks/phase-2-lexical-search.md | 28 +- tasks/phase-3-vector-hybrid.md | 42 +-- tasks/phase-4-local-llm-rag.md | 32 +-- tasks/phase-5-evaluation.md | 22 +- tasks/phase-6-image.md | 18 +- tasks/phase-7-pdf.md | 16 +- tasks/phase-8-audio.md | 20 +- tasks/phase-9-ui.md | 24 +- 56 files changed, 1324 insertions(+), 1324 deletions(-) diff --git a/README.md b/README.md index c75c85d..9af3f77 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# kb — Local-first Knowledge Base +# kebab — Local-first Knowledge Base -> **상태:** P0–P4 구현 완료 (31 component task 중 17 완료) + 3건 post-merge hotfix 적용. `kb index` / `kb search --mode {lexical,vector,hybrid}` / `kb ask` 모두 실 동작. 다음 단계 = P5 (eval suite). 자세한 진행 상황은 [tasks/INDEX.md](tasks/INDEX.md), 머지 후 발견된 버그와 fix는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md). +> **상태:** P0–P4 구현 완료 (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). -`kb` 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지 / 음성을 한 곳에 색인하고, 의미 검색 + citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed / whisper.cpp) 에서 돌아간다. +`kebab` 는 개인용 로컬 knowledge base + RAG 도구다. Markdown / PDF / 이미지 / 음성을 한 곳에 색인하고, 의미 검색 + citation 포함 LLM 답변을 단일 binary 로 제공한다. 모든 추론은 로컬 (Ollama / fastembed / whisper.cpp) 에서 돌아간다. 대상 하드웨어: M4 48GB MacBook 1대, 사용자 1명. @@ -12,14 +12,14 @@ | 명령 | 동작 | 상태 | |------|------|------| -| `kb init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 | ✅ P0 | -| `kb ingest []` | Markdown 색인 (idempotent). PDF/이미지/음성은 P6+. | ✅ P3-5 | -| `kb search --mode {lexical,vector,hybrid} ""` | 검색 — citation 포함, hybrid는 RRF fusion | ✅ P3-5 | -| `kb list docs` | 색인된 문서 목록 | ✅ P3-5 | -| `kb inspect doc ` / `kb inspect chunk ` | raw record 보기 | ✅ P3-5 | -| `kb ask ""` | RAG 답변 + 근거 인용. 근거 부족 시 거절. Ollama 필요. | ✅ P4-3 | -| `kb doctor` | 설정/모델/DB 헬스 체크 | ✅ P0 | -| `kb eval run / compare` | golden query 회귀 측정 | ⏳ P5 | +| `kebab init` | XDG 경로에 데이터 디렉토리 + config.toml 생성 | ✅ P0 | +| `kebab ingest []` | Markdown 색인 (idempotent). PDF/이미지/음성은 P6+. | ✅ P3-5 | +| `kebab search --mode {lexical,vector,hybrid} ""` | 검색 — citation 포함, hybrid는 RRF fusion | ✅ P3-5 | +| `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 eval run / compare` | golden query 회귀 측정 | ⏳ P5 | 기계 친화 모드: 모든 명령에 `--json` 플래그. 출력은 frozen wire schema v1 (`schema_version` 필드 항상 포함, 예: `ingest_report.v1`, `search_hit.v1`, `answer.v1`, `doctor.v1`). @@ -44,35 +44,35 @@ | 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/kb/`, `~/.config/kb/`, …) | +| layout | XDG (`~/.local/share/kebab/`, `~/.config/kebab/`, …) | -전체는 [docs/superpowers/specs/2026-04-27-kb-final-form-design.md](docs/superpowers/specs/2026-04-27-kb-final-form-design.md) 참조. +전체는 [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](docs/superpowers/specs/2026-04-27-kebab-final-form-design.md) 참조. --- ## 의존성 그래프 ```text -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-embed trait crate) - ├─> kb-search - ├─> kb-llm-local (kb-llm trait crate) - ├─> kb-rag - ├─> kb-eval - └─> kb-config - └─> kb-core (모두 의존) +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 진입은 `kb-app` facade 만 통한다 (design §8). `kb-cli` 가 `--config ` flag 를 honor 하려면 `kb_app::*_with_config(cfg, …)` companion 을 통해 Config 을 명시적으로 thread 하는 패턴 — 자세한 이유는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md) 의 `--config` 항목. +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` 항목. --- @@ -80,16 +80,16 @@ UI → store/llm/parse 직접 의존 금지. 모든 user-facing 진입은 `kb-ap | 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-app wiring | `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 | ⏳ | +| **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 이후 병렬 가능. @@ -100,13 +100,13 @@ P0~P5 직렬. P6~P9 P5 이후 병렬 가능. ## 디렉토리 구조 ```text -kb/ +kebab/ ├── README.md # 이 파일 -├── kb_local_rust_report.md # 최초 설계 보고서 (방향성 + 근거) +├── kebab_local_rust_report.md # 최초 설계 보고서 (방향성 + 근거) ├── docs/ │ ├── superpowers/ │ │ ├── specs/ -│ │ │ └── 2026-04-27-kb-final-form-design.md # frozen design (12 sections) +│ │ │ └── 2026-04-27-kebab-final-form-design.md # frozen design (12 sections) │ │ └── plans/ │ │ └── 2026-04-27-task-decomposition.md # task 분해 implementation plan │ ├── SMOKE.md # 로컬 워크스페이스에 직접 돌려보는 절차 @@ -127,19 +127,19 @@ kb/ │ ├── p8/p8-1, p8-2 # (2) │ └── p9/p9-1 … p9-5 # (5) ├── crates/ -│ ├── kb-core/ kb-parse-types/ kb-config/ # 도메인 + 설정 (P0) -│ ├── kb-source-fs/ # 워크스페이스 walk + checksum (P1-1) -│ ├── kb-parse-md/ # Markdown frontmatter + blocks (P1-2/3) -│ ├── kb-normalize/ # ParsedBlock → CanonicalDocument (P1-4) -│ ├── kb-chunk/ # heading-aware chunker (P1-5) -│ ├── kb-store-sqlite/ # SQLite + FTS5 (V001/V002/V003) (P1-6, P2-1, P3-3) -│ ├── kb-search/ # Lexical + Vector + Hybrid retriever (P2-2, P3-4) -│ ├── kb-embed/ kb-embed-local/ # Embedder trait + fastembed adapter (P3-1, P3-2) -│ ├── kb-store-vector/ # LanceDB VectorStore (P3-3) -│ ├── kb-llm/ kb-llm-local/ # LanguageModel trait + Ollama adapter (P4-1, P4-2) -│ ├── kb-rag/ # RAG pipeline (P4-3) -│ ├── kb-app/ # facade (P0 시그니처 + P3-5 본체) -│ └── kb-cli/ # binary (P0 → 핫픽스로 --config flag wiring 강화) +│ ├── 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 트리 ``` @@ -153,19 +153,19 @@ kb/ cargo build --release # 첫 실행 — XDG 경로에 config.toml 생성 -./target/release/kb init +./target/release/kebab init # config 손보고 -${EDITOR:-vi} ~/.config/kb/config.toml +${EDITOR:-vi} ~/.config/kebab/config.toml # 색인 -./target/release/kb ingest +./target/release/kebab ingest # 검색 -./target/release/kb search "Markdown chunking 규칙" --mode hybrid +./target/release/kebab search "Markdown chunking 규칙" --mode hybrid # 질문 (Ollama 필요) -./target/release/kb ask "내 KB 설계에서 저장소 전략은?" +./target/release/kebab ask "내 KB 설계에서 저장소 전략은?" ``` 워크스페이스를 격리해서 직접 돌려보는 패턴은 [docs/SMOKE.md](docs/SMOKE.md) 참조 — `--config ` 로 임시 디렉토리에 격리된 KB 를 만들 수 있다. @@ -181,17 +181,17 @@ ${EDITOR:-vi} ~/.config/kb/config.toml - multi-workspace (P+ 후순위) - LLM-as-judge eval (rule-based `must_contain` 만) - visual embedding (CLIP) — P+ -- desktop app `kb://` protocol handler — P+ +- desktop app `kebab://` protocol handler — P+ --- ## 외부 AI 통합 -`kb` 의 `--json` 모드 + frozen wire schema v1 은 외부 자동화의 stable contract. 가능한 통합: +`kebab` 의 `--json` 모드 + frozen wire schema v1 은 외부 자동화의 stable contract. 가능한 통합: -1. **Claude Code / Codex skill** — 얇은 wrapper (`kb search --json` / `kb ask --json` 호출). ~50 lines. -2. **MCP server** — `kb-mcp` binary (stdio JSON-RPC) 가 `kb-app` facade 를 1:1 노출. Claude Desktop / Cursor / Zed 등 공유. -3. **HTTP wrapper** — `kb serve --bind 127.0.0.1:7711` (P+, local-only 가치 깨므로 신중). +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 가치 깨므로 신중). --- @@ -199,7 +199,7 @@ ${EDITOR:-vi} ~/.config/kb/config.toml 이 repo 는 단일 사용자 프로젝트지만 spec 변경 절차는 명문화되어 있다. -1. **frozen design 변경** — `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` 가 단일 contract. 변경 시 영향 받는 component task 모두 동시 갱신 필요. PR 1개로 묶기. +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 는 재처리 필요. @@ -215,8 +215,8 @@ ${EDITOR:-vi} ~/.config/kb/config.toml ## 참고 -- 최초 설계 보고서: [kb_local_rust_report.md](kb_local_rust_report.md) -- Frozen design: [docs/superpowers/specs/2026-04-27-kb-final-form-design.md](docs/superpowers/specs/2026-04-27-kb-final-form-design.md) +- 최초 설계 보고서: [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) - Task 인덱스: [tasks/INDEX.md](tasks/INDEX.md) - Post-merge 핫픽스 로그: [tasks/HOTFIXES.md](tasks/HOTFIXES.md) diff --git a/docs/SMOKE.md b/docs/SMOKE.md index ddc9aca..e3cdf79 100644 --- a/docs/SMOKE.md +++ b/docs/SMOKE.md @@ -1,21 +1,21 @@ --- -title: "kb 스모크 실행 가이드" +title: "kebab 스모크 실행 가이드" date: 2026-05-01 --- -# kb 스모크 실행 가이드 +# kebab 스모크 실행 가이드 -P3-5 머지 후 (`kb-app::ingest` / `search` / `list` / `inspect` 와이어링) 부터, 그리고 P4-3 머지 후 (`kb ask` 와이어링) 부터 사용자가 자기 설치본을 직접 검증할 수 있다. 이 문서는 사용자 환경 (`~/.config/kb/`, `~/.local/share/kb/`) 을 건드리지 않고 임시 디렉토리에 격리된 KB 를 띄워 전체 파이프라인을 1세션 안에 한 번 돌리는 절차다. +P3-5 머지 후 (`kebab-app::ingest` / `search` / `list` / `inspect` 와이어링) 부터, 그리고 P4-3 머지 후 (`kebab ask` 와이어링) 부터 사용자가 자기 설치본을 직접 검증할 수 있다. 이 문서는 사용자 환경 (`~/.config/kebab/`, `~/.local/share/kebab/`) 을 건드리지 않고 임시 디렉토리에 격리된 KB 를 띄워 전체 파이프라인을 1세션 안에 한 번 돌리는 절차다. ## 준비 빌드: ```bash -cargo build --release -p kb-cli # debug 도 무방. 디버그가 더 빠르게 빌드됨. +cargo build --release -p kebab-cli # debug 도 무방. 디버그가 더 빠르게 빌드됨. ``` -원격 Ollama (선택, `kb ask` 만 필요): +원격 Ollama (선택, `kebab ask` 만 필요): ```bash # Mac 등 별도 호스트에서 @@ -34,8 +34,8 @@ curl http://:11434/api/tags ## 격리된 워크스페이스 생성 ```bash -mkdir -p /tmp/kb-smoke/{workspace,data} -cat > /tmp/kb-smoke/workspace/intro.md <<'EOF' +mkdir -p /tmp/kebab-smoke/{workspace,data} +cat > /tmp/kebab-smoke/workspace/intro.md <<'EOF' --- title: 인사말 tags: [demo] @@ -51,19 +51,19 @@ EOF ## 격리된 config -`/tmp/kb-smoke/config.toml`: +`/tmp/kebab-smoke/config.toml`: ```toml schema_version = 1 [workspace] -root = "/tmp/kb-smoke/workspace" +root = "/tmp/kebab-smoke/workspace" include = ["**/*.md"] exclude = [".git/**", "node_modules/**", ".obsidian/**"] [storage] -data_dir = "/tmp/kb-smoke/data" -sqlite = "{data_dir}/kb.sqlite" +data_dir = "/tmp/kebab-smoke/data" +sqlite = "{data_dir}/kebab.sqlite" vector_dir = "{data_dir}/lancedb" asset_dir = "{data_dir}/assets" artifact_dir = "{data_dir}/artifacts" @@ -110,12 +110,12 @@ explain_default = false max_context_tokens = 6000 ``` -`KB_*` 환경변수로 override 가능 (`KB_MODELS_LLM_MODEL=qwen2.5:32b kb …` 등). 자세한 키 목록은 `crates/kb-config/src/lib.rs` 의 `apply_env` 매치 암. +`KEBAB_*` 환경변수로 override 가능 (`KEBAB_MODELS_LLM_MODEL=qwen2.5:32b kebab …` 등). 자세한 키 목록은 `crates/kebab-config/src/lib.rs` 의 `apply_env` 매치 암. ## 명령 시퀀스 ```bash -KB() { ./target/debug/kb --config /tmp/kb-smoke/config.toml "$@"; } +KEBAB() { ./target/debug/kebab --config /tmp/kebab-smoke/config.toml "$@"; } KB doctor # 1. health check KB ingest # 2. 워크스페이스 색인 @@ -128,31 +128,31 @@ KB ask "이 KB 안에서 ..." --mode hybrid --k 5 # 8. RAG 답변 (Ollama KB --json ask "..." --mode hybrid # 9. 기계 친화 출력 검증 ``` -각 명령은 0 종료 코드면 정상. `kb ask` 는 거절 시 종료 코드 1 (`RefusalSignal`) — 의도된 동작. +각 명령은 0 종료 코드면 정상. `kebab ask` 는 거절 시 종료 코드 1 (`RefusalSignal`) — 의도된 동작. ## 검증 체크리스트 -- `kb doctor` 가 `--config` path 를 honor 하고 그 안의 `storage.data_dir` 를 출력 (XDG default 가 아님). -- `kb ingest` idempotent — 두 번째 실행이 `new=0 updated=N`. -- `kb list docs` 출력에 frontmatter 의 `title` 이 아닌 deterministic `doc_id` (32-hex) + `workspace_path` 가 보임. -- `kb search --mode hybrid` 의 `fusion_score` 가 `[0, 1]` 범위 (top-1 종종 1.0 — 두 retriever 모두 rank 1 일 때). -- `kb ask` JSON 응답에 `model.id` 가 config 의 모델 (`gemma4:26b` 등) 과 일치, `embedding.id = multilingual-e5-small`, `citations[].marker` 가 `[1]` / `[2]` 형식 (square-bracketed bare index). -- 코퍼스에 없는 주제로 `kb ask` → `refusal_reason: "llm_self_judge"` (또는 `no_chunks` / `score_gate`) + `grounded: false`. +- `kebab doctor` 가 `--config` path 를 honor 하고 그 안의 `storage.data_dir` 를 출력 (XDG default 가 아님). +- `kebab ingest` idempotent — 두 번째 실행이 `new=0 updated=N`. +- `kebab list docs` 출력에 frontmatter 의 `title` 이 아닌 deterministic `doc_id` (32-hex) + `workspace_path` 가 보임. +- `kebab search --mode hybrid` 의 `fusion_score` 가 `[0, 1]` 범위 (top-1 종종 1.0 — 두 retriever 모두 rank 1 일 때). +- `kebab ask` JSON 응답에 `model.id` 가 config 의 모델 (`gemma4:26b` 등) 과 일치, `embedding.id = multilingual-e5-small`, `citations[].marker` 가 `[1]` / `[2]` 형식 (square-bracketed bare index). +- 코퍼스에 없는 주제로 `kebab ask` → `refusal_reason: "llm_self_judge"` (또는 `no_chunks` / `score_gate`) + `grounded: false`. ## 정리 ```bash -rm -rf /tmp/kb-smoke/data # 데이터만 날리고 다시 ingest 가능 -rm -rf /tmp/kb-smoke # 통째로 정리 +rm -rf /tmp/kebab-smoke/data # 데이터만 날리고 다시 ingest 가능 +rm -rf /tmp/kebab-smoke # 통째로 정리 ``` -`~/.config/kb/` 와 `~/.local/share/kb/` 는 한 번도 터치되지 않는다 (`--config` flag 가 정확히 honor 되는 경우 — P3-5 hotfix 이후 보장). +`~/.config/kebab/` 와 `~/.local/share/kebab/` 는 한 번도 터치되지 않는다 (`--config` flag 가 정확히 honor 되는 경우 — P3-5 hotfix 이후 보장). ## 알려진 동작 -- 첫 `kb ingest` 시 fastembed 모델 다운로드 (~470MB) — `data_dir/models/fastembed/` 에 캐시. -- `kb ask` 응답 시간 = LLM 토큰 throughput 에 종속. M4 Pro 48GB + gemma4:26b 기준 답변 50–100 토큰에 20–55초. -- `--config` path 가 존재하지 않거나 malformed 면 `kb doctor` 가 hard fail (defaults 가 silently mask 하지 않게 하는 hotfix 동작). +- 첫 `kebab ingest` 시 fastembed 모델 다운로드 (~470MB) — `data_dir/models/fastembed/` 에 캐시. +- `kebab ask` 응답 시간 = LLM 토큰 throughput 에 종속. M4 Pro 48GB + gemma4:26b 기준 답변 50–100 토큰에 20–55초. +- `--config` path 가 존재하지 않거나 malformed 면 `kebab doctor` 가 hard fail (defaults 가 silently mask 하지 않게 하는 hotfix 동작). - 매 CLI invocation 마다 fastembed 모델 init 비용 (~4초) — process-level 캐시 부재 때문. P9 TUI 진입 시 `App` 의 `OnceLock` 으로 세션 동안 한 번만 init. 자세한 history 와 발견된 버그는 [tasks/HOTFIXES.md](../tasks/HOTFIXES.md) 참조. diff --git a/docs/spec/ai-generation-guidelines.md b/docs/spec/ai-generation-guidelines.md index b0040fd..58827ad 100644 --- a/docs/spec/ai-generation-guidelines.md +++ b/docs/spec/ai-generation-guidelines.md @@ -5,8 +5,8 @@ When implementing tasks against this codebase: - Treat the frozen design doc as the single source of truth. Do not invent new fields, traits, or enum variants. - Prefer editing existing files to creating new ones; reuse types from - `kb-core` instead of duplicating shapes. + `kebab-core` instead of duplicating shapes. - For each task, follow the task spec under `tasks/p/p-.md`. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §11 + §12. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §11 + §12. diff --git a/docs/spec/canonical-document.md b/docs/spec/canonical-document.md index 0926fc2..afa67ee 100644 --- a/docs/spec/canonical-document.md +++ b/docs/spec/canonical-document.md @@ -4,4 +4,4 @@ Medium-agnostic representation of a document with `Block`s, `SourceSpan`s, and provenance. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §3.4 + §3.7a. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §3.4 + §3.7a. diff --git a/docs/spec/chunk-policy.md b/docs/spec/chunk-policy.md index fe204fc..9a2fae0 100644 --- a/docs/spec/chunk-policy.md +++ b/docs/spec/chunk-policy.md @@ -5,4 +5,4 @@ `policy_hash` so chunk IDs include the policy. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §3.5 + §7.1 + §7.2. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §3.5 + §7.1 + §7.2. diff --git a/docs/spec/citation-policy.md b/docs/spec/citation-policy.md index 3ef0f82..6273aab 100644 --- a/docs/spec/citation-policy.md +++ b/docs/spec/citation-policy.md @@ -4,4 +4,4 @@ Citations use W3C Media Fragments URIs to locate evidence inside a document. Five variants: `Line`, `Page`, `Region`, `Caption`, `Time`. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §3.5 + §0 Q3. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §3.5 + §0 Q3. diff --git a/docs/spec/domain-model.md b/docs/spec/domain-model.md index 98ef0ec..ae342d5 100644 --- a/docs/spec/domain-model.md +++ b/docs/spec/domain-model.md @@ -1,6 +1,6 @@ # Domain model -The domain types live in `kb-core` and mirror the frozen design exactly. +The domain types live in `kebab-core` and mirror the frozen design exactly. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §3. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §3. diff --git a/docs/spec/ids.md b/docs/spec/ids.md index 19bbc15..607f934 100644 --- a/docs/spec/ids.md +++ b/docs/spec/ids.md @@ -1,6 +1,6 @@ # ID recipe -All `kb-*` IDs are 32 hex chars: the first 32 of `blake3(canonical_json(tuple))`. +All `kebab-*` IDs are 32 hex chars: the first 32 of `blake3(canonical_json(tuple))`. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §4. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §4. diff --git a/docs/spec/module-boundaries.md b/docs/spec/module-boundaries.md index 7225608..dbcc7f4 100644 --- a/docs/spec/module-boundaries.md +++ b/docs/spec/module-boundaries.md @@ -1,8 +1,8 @@ # Module boundaries -`kb-core` is leaf — every other crate depends on it. Parsers depend on -`kb-parse-types` (not on `kb-normalize`); `kb-normalize` depends on -`kb-parse-types` (not on parsers). UI crates depend only on `kb-app`. +`kebab-core` is leaf — every other crate depends on it. Parsers depend on +`kebab-parse-types` (not on `kebab-normalize`); `kebab-normalize` depends on +`kebab-parse-types` (not on parsers). UI crates depend only on `kebab-app`. Canonical source: -[docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../superpowers/specs/2026-04-27-kb-final-form-design.md), §8. +[docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../superpowers/specs/2026-04-27-kebab-final-form-design.md), §8. diff --git a/docs/superpowers/plans/2026-04-27-task-decomposition.md b/docs/superpowers/plans/2026-04-27-task-decomposition.md index 58e6499..3d417ba 100644 --- a/docs/superpowers/plans/2026-04-27-task-decomposition.md +++ b/docs/superpowers/plans/2026-04-27-task-decomposition.md @@ -8,11 +8,11 @@ - Phase A — Author the canonical task spec template (`tasks/_template.md`). - Phase B — Decompose P1 (Markdown ingestion) into 6 component task specs to validate the template. - Phase C — After Phase B passes review, decompose P0 + P2..P9 into the remaining ~24 task specs in one pass. -- Each task spec cites only the frozen design doc (`docs/superpowers/specs/2026-04-27-kb-final-form-design.md`) for types, traits, schema, layout. No new domain types or traits are introduced inside task specs. +- Each task spec cites only the frozen design doc (`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md`) for types, traits, schema, layout. No new domain types or traits are introduced inside task specs. **Tech Stack:** Plain Markdown documents under `tasks/`. No code changes in this plan — produces specs that AI sub-agents will later implement against. -**Frozen contract source:** [docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../specs/2026-04-27-kb-final-form-design.md). All task specs reference this. Modifications to the contract require updating that file first, then re-checking dependent task specs. +**Frozen contract source:** [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../specs/2026-04-27-kebab-final-form-design.md). All task specs reference this. Modifications to the contract require updating that file first, then re-checking dependent task specs. **Phase task index (target file layout):** @@ -83,7 +83,7 @@ Existing per-phase epic files (`tasks/phase-0-skeleton.md` … `phase-9-ui.md`) - [ ] **Step 1: Verify the design doc path resolves** ```bash -test -f docs/superpowers/specs/2026-04-27-kb-final-form-design.md && echo OK +test -f docs/superpowers/specs/2026-04-27-kebab-final-form-design.md && echo OK ``` Expected: `OK` @@ -101,7 +101,7 @@ title: "" status: planned depends_on: [] # other task_ids unblocks: [] # other task_ids -contract_source: ../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [] # e.g. [§3.5, §5.5, §7.2] --- @@ -117,7 +117,7 @@ contract_sections: [] # e.g. [§3.5, §5.5, §7.2] ## Allowed dependencies -- `kb-core` +- `kebab-core` - - @@ -166,7 +166,7 @@ If any item here is needed during implementation, STOP and update the frozen des | unit | ... | ... | | snapshot | ... (JSON freeze) | `fixtures/...` | | contract | trait round-trip | mock impls | -| integration | end-to-end via `kb-app` facade | tmp workspace | +| integration | end-to-end via `kebab-app` facade | tmp workspace | All tests must run under `cargo test -p ` and not require external network or Ollama unless explicitly stated. @@ -247,7 +247,7 @@ git add tasks/p1 tasks/INDEX.md git commit -m "tasks: prepare P1 component decomposition skeleton" ``` -### Task B1: `p1-1-source-fs.md` (kb-source-fs) +### Task B1: `p1-1-source-fs.md` (kebab-source-fs) **Files:** - Create: `tasks/p1/p1-1-source-fs.md` @@ -259,13 +259,13 @@ Write the following content to `tasks/p1/p1-1-source-fs.md`: ````markdown --- phase: P1 -component: kb-source-fs +component: kebab-source-fs task_id: p1-1 title: "Local filesystem source connector" status: planned depends_on: [p0-1] unblocks: [p1-2, p1-3, p1-4, p1-5, p1-6] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.3, §6.2, §6.6, §7.1, §7.2 SourceConnector, §8] --- @@ -281,8 +281,8 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `ignore` (gitignore semantics) - `blake3` - `walkdir` @@ -293,21 +293,21 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums ## Forbidden dependencies -- `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `SourceScope` | `kb_core::SourceScope` | `kb-app` from config | +| `SourceScope` | `kebab_core::SourceScope` | `kebab-app` from config | | filesystem | `&Path` | OS | -| `.kbignore` | text file | workspace root, optional | +| `.kebabignore` | text file | workspace root, optional | ## Outputs | output | type | downstream consumer | |--------|------|---------------------| -| `Vec` | `kb_core::RawAsset` | `kb-parse-md`, asset writer in `kb-store-sqlite` (via `kb-app`) | +| `Vec` | `kebab_core::RawAsset` | `kebab-parse-md`, asset writer in `kebab-store-sqlite` (via `kebab-app`) | ## Public surface (signatures only — no new types) @@ -315,11 +315,11 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums pub struct FsSourceConnector { /* internal */ } impl FsSourceConnector { - pub fn new(config: &kb_config::Config) -> anyhow::Result; + pub fn new(config: &kebab_config::Config) -> anyhow::Result; } -impl kb_core::SourceConnector for FsSourceConnector { - fn scan(&self, scope: &kb_core::SourceScope) -> anyhow::Result>; +impl kebab_core::SourceConnector for FsSourceConnector { + fn scan(&self, scope: &kebab_core::SourceScope) -> anyhow::Result>; } ``` @@ -329,7 +329,7 @@ impl kb_core::SourceConnector for FsSourceConnector { - `asset_id` derived per design §4.2 from `blake3(raw bytes)` full hex. - `media_type` selected from extension + libmagic-like sniff fallback (`.md` → Markdown, others fall through to `MediaType::Other`). - `discovered_at` = current `OffsetDateTime::now_utc()` at scan time. -- Combine `config.workspace.exclude` ∪ `.kbignore` for filter (union; ordering does not matter). +- Combine `config.workspace.exclude` ∪ `.kebabignore` for filter (union; ordering does not matter). - Symbolic links: follow once, detect cycles via `canonicalize` + visited set. - Files larger than `storage.copy_threshold_mb` MB → emit `AssetStorage::Reference { path, sha }` (do not copy bytes here; copying is done by the asset writer task). - Idempotent: same input → same `Vec` (sort by `workspace_path`). @@ -337,7 +337,7 @@ impl kb_core::SourceConnector for FsSourceConnector { ## Storage / wire effects - Reads: filesystem under `config.workspace.root`. -- Writes: nothing. (Asset copy is handled by the asset writer in `kb-store-sqlite`.) +- Writes: nothing. (Asset copy is handled by the asset writer in `kebab-store-sqlite`.) ## Test plan @@ -346,25 +346,25 @@ impl kb_core::SourceConnector for FsSourceConnector { | unit | POSIX path normalization | inline cases incl. `./a/b.md`, `a//b.md`, `a/b.md` → identical | | unit | blake3 of known bytes matches expected hex | inline | | unit | gitignore filter (`*.tmp`, `node_modules/**`) excludes correctly | tmp tree built in test | -| unit | `.kbignore` ∪ config exclude works | tmp tree | +| unit | `.kebabignore` ∪ config exclude works | tmp tree | | unit | symlink cycle does not loop | tmp tree with `a -> b -> a` | | snapshot | `Vec` serialized JSON for fixture tree is stable | `fixtures/source-fs/tree-1` | | determinism | re-running scan twice produces byte-identical JSON | `fixtures/source-fs/tree-1` | -All tests run under `cargo test -p kb-source-fs` with no network and no model. +All tests run under `cargo test -p kebab-source-fs` with no network and no model. ## Definition of Done -- [ ] `cargo check -p kb-source-fs` passes -- [ ] `cargo test -p kb-source-fs` passes +- [ ] `cargo check -p kebab-source-fs` passes +- [ ] `cargo test -p kebab-source-fs` passes - [ ] Snapshot test `fixtures/source-fs/tree-1` round-trips deterministically -- [ ] No imports outside Allowed dependencies (verified via `cargo tree -p kb-source-fs`) +- [ ] No imports outside Allowed dependencies (verified via `cargo tree -p kebab-source-fs`) - [ ] PR description links to design §3.3, §6.2, §7.2 ## Out of scope - File watching (P+). -- Asset copy/reference storage on disk (`kb-store-sqlite` task p1-6). +- Asset copy/reference storage on disk (`kebab-store-sqlite` task p1-6). - Non-fs source connectors (HTTP, S3 — P+). ## Risks / notes @@ -400,13 +400,13 @@ Write to `tasks/p1/p1-2-parse-md-frontmatter.md`: ````markdown --- phase: P1 -component: kb-parse-md (frontmatter submodule) +component: kebab-parse-md (frontmatter submodule) task_id: p1-2 title: "Markdown frontmatter parsing → Metadata" status: planned depends_on: [p0-1] unblocks: [p1-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.6 Metadata, §0 Q9 frontmatter, §10 errors] --- @@ -414,15 +414,15 @@ contract_sections: [§3.6 Metadata, §0 Q9 frontmatter, §10 errors] ## Goal -Parse YAML/TOML frontmatter from Markdown bytes into `kb_core::Metadata`, with auto-derive defaults and unknown-key preservation in `metadata.user`. +Parse YAML/TOML frontmatter from Markdown bytes into `kebab_core::Metadata`, with auto-derive defaults and unknown-key preservation in `metadata.user`. ## Why now / why this size -Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from block parsing keeps both halves of `kb-parse-md` simple and lets us reach 100% test coverage on the rules in design §0 Q9. +Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from block parsing keeps both halves of `kebab-parse-md` simple and lets us reach 100% test coverage on the rules in design §0 Q9. ## Allowed dependencies -- `kb-core` +- `kebab-core` - `serde` - `serde_yaml` (or `yaml-rust2`) for YAML - `toml` for TOML @@ -432,7 +432,7 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from ## Forbidden dependencies -- `kb-store-*`, `kb-llm*`, `kb-rag`, `kb-embed*`, `kb-search`, `kb-tui`, `kb-desktop`, `kb-source-fs`, `kb-chunk`, `kb-normalize`, `pulldown-cmark` (block parser is a sibling task) +- `kebab-store-*`, `kebab-llm*`, `kebab-rag`, `kebab-embed*`, `kebab-search`, `kebab-tui`, `kebab-desktop`, `kebab-source-fs`, `kebab-chunk`, `kebab-normalize`, `pulldown-cmark` (block parser is a sibling task) ## Inputs @@ -445,7 +445,7 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from | output | type | downstream | |--------|------|------------| -| `(Metadata, Option, Vec)` | tuple | `kb-normalize` → CanonicalDocument | +| `(Metadata, Option, Vec)` | tuple | `kebab-normalize` → CanonicalDocument | ## Public surface (signatures only — no new types) @@ -453,7 +453,7 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from pub fn parse_frontmatter( bytes: &[u8], hints: &BodyHints, -) -> anyhow::Result<(kb_core::Metadata, Option, Vec)>; +) -> anyhow::Result<(kebab_core::Metadata, Option, Vec)>; ``` `FrontmatterSpan` and `Warning` are crate-internal helpers; if any new public type is needed, STOP and update the frozen design doc first. @@ -490,12 +490,12 @@ pub fn parse_frontmatter( | snapshot | `fixtures/markdown/frontmatter-only.md` produces stable JSON | fixture | | snapshot | mixed-language body with no `lang:` detects `ko` or `en` | `fixtures/markdown/mixed-lang.md` | -All tests under `cargo test -p kb-parse-md --lib frontmatter`. +All tests under `cargo test -p kebab-parse-md --lib frontmatter`. ## Definition of Done -- [ ] `cargo check -p kb-parse-md` passes -- [ ] `cargo test -p kb-parse-md frontmatter` passes +- [ ] `cargo check -p kebab-parse-md` passes +- [ ] `cargo test -p kebab-parse-md frontmatter` passes - [ ] No `pulldown-cmark` import in this submodule - [ ] Snapshot tests stable across two consecutive runs - [ ] PR links design §0 Q9, §3.6 @@ -534,13 +534,13 @@ Write to `tasks/p1/p1-3-parse-md-blocks.md`: ````markdown --- phase: P1 -component: kb-parse-md (blocks submodule) +component: kebab-parse-md (blocks submodule) task_id: p1-3 title: "Markdown body → Block tree with line spans" status: planned depends_on: [p0-1] unblocks: [p1-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 Block, §3.4 SourceSpan, §0 Q3 citation] --- @@ -548,7 +548,7 @@ contract_sections: [§3.4 Block, §3.4 SourceSpan, §0 Q3 citation] ## Goal -Parse Markdown body bytes into a flat `Vec` (intermediate, crate-private) with heading paths and line ranges preserved, ready for `kb-normalize` to lift into `CanonicalDocument`. +Parse Markdown body bytes into a flat `Vec` (intermediate, crate-private) with heading paths and line ranges preserved, ready for `kebab-normalize` to lift into `CanonicalDocument`. ## Why now / why this size @@ -556,14 +556,14 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from ## Allowed dependencies -- `kb-core` +- `kebab-core` - `pulldown-cmark` (CommonMark with source-map; GFM tables enabled via feature) - `serde` - `thiserror` ## Forbidden dependencies -- `kb-store-*`, `kb-llm*`, `kb-rag`, `kb-embed*`, `kb-search`, `kb-source-fs`, `kb-chunk`, `kb-normalize`, `kb-tui`, `kb-desktop`, `comrak` (alternative parser; pick one) +- `kebab-store-*`, `kebab-llm*`, `kebab-rag`, `kebab-embed*`, `kebab-search`, `kebab-source-fs`, `kebab-chunk`, `kebab-normalize`, `kebab-tui`, `kebab-desktop`, `comrak` (alternative parser; pick one) ## Inputs @@ -576,7 +576,7 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from | output | type | downstream | |--------|------|------------| -| `Vec` (intermediate type, crate-private) | – | `kb-normalize` | +| `Vec` (intermediate type, crate-private) | – | `kebab-normalize` | | `Vec` | – | propagated into Provenance | ## Public surface (signatures only — no new types) @@ -585,7 +585,7 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from pub fn parse_blocks(body: &[u8], body_offset_lines: u32) -> anyhow::Result<(Vec, Vec)>; ``` -`ParsedBlock` is a crate-internal mirror that maps 1:1 to `kb_core::Block` variants once `kb-normalize` assigns `BlockId`s. +`ParsedBlock` is a crate-internal mirror that maps 1:1 to `kebab_core::Block` variants once `kebab-normalize` assigns `BlockId`s. ## Behavior contract @@ -593,7 +593,7 @@ pub fn parse_blocks(body: &[u8], body_offset_lines: u32) -> anyhow::Result<(Vec< - Heading tree: every block records its ancestor heading texts in order (e.g., `["아키텍처", "Chunking 정책"]`). - Code blocks: language tag preserved (` ```rust ` → `Some("rust")`), fenced content not split. - Tables: GFM tables produce `TableBlock` with header row + body rows; if a table cell is malformed, fall back to a `Paragraph` block + warning. -- Image references: `![alt](src)` produces `ImageRefBlock` with `asset_id = None`, `src = "..."`, `alt = "..."`. Resolution to `AssetId` happens later in `kb-normalize`. +- Image references: `![alt](src)` produces `ImageRefBlock` with `asset_id = None`, `src = "..."`, `alt = "..."`. Resolution to `AssetId` happens later in `kebab-normalize`. - Lists: ordered/unordered preserved; nested list items flattened into one `ListBlock` with each top-level item's text. - Inline elements: only `Text`, `Code`, `Link`, `Strong`, `Emph` (per design §3.4). Drop other inlines silently. - Malformed input never panics. Worst case: empty `Vec` + warning. @@ -616,12 +616,12 @@ pub fn parse_blocks(body: &[u8], body_offset_lines: u32) -> anyhow::Result<(Vec< | snapshot | `fixtures/markdown/nested-headings.md` → ParsedBlock JSON stable | fixture | | snapshot | `fixtures/markdown/code-and-table.md` → JSON stable | fixture | -All tests under `cargo test -p kb-parse-md --lib blocks`. +All tests under `cargo test -p kebab-parse-md --lib blocks`. ## Definition of Done -- [ ] `cargo check -p kb-parse-md` passes -- [ ] `cargo test -p kb-parse-md blocks` passes +- [ ] `cargo check -p kebab-parse-md` passes +- [ ] `cargo test -p kebab-parse-md blocks` passes - [ ] Snapshot tests stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.4 @@ -629,7 +629,7 @@ All tests under `cargo test -p kb-parse-md --lib blocks`. ## Out of scope - Frontmatter (p1-2). -- Lifting `ParsedBlock` → `kb_core::Block` with `BlockId` (p1-4). +- Lifting `ParsedBlock` → `kebab_core::Block` with `BlockId` (p1-4). - Chunking (p1-5). ## Risks / notes @@ -658,13 +658,13 @@ Write to `tasks/p1/p1-4-normalize.md`: ````markdown --- phase: P1 -component: kb-normalize +component: kebab-normalize task_id: p1-4 title: "Lift parser output → CanonicalDocument with deterministic IDs" status: planned depends_on: [p1-2, p1-3] unblocks: [p1-5, p1-6] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4, §4 ID recipe, §3.6 Provenance] --- @@ -676,12 +676,12 @@ Combine `Metadata` (p1-2) + `Vec` (p1-3) + `RawAsset` (p1-1) into a ## Why now / why this size -Single responsibility: ID generation + struct assembly. Keeps `kb-parse-md` purely a parser and isolates the (security-critical) deterministic ID logic in one crate. +Single responsibility: ID generation + struct assembly. Keeps `kebab-parse-md` purely a parser and isolates the (security-critical) deterministic ID logic in one crate. ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde` - `serde-json-canonicalizer` (canonical JSON for ID hashing) - `blake3` @@ -691,38 +691,38 @@ Single responsibility: ID generation + struct assembly. Keeps `kb-parse-md` pure ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md` (consumed via plain types only — must not couple back), `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md` (consumed via plain types only — must not couple back), `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` -Note: this crate accepts `ParsedBlock` from `kb-parse-md` either by (a) exposing `ParsedBlock` as a `kb-core` type, or (b) `kb-parse-md` re-exporting via a public DTO. Pick (a): move `ParsedBlock` into `kb-core` so this task does not import `kb-parse-md`. +Note: this crate accepts `ParsedBlock` from `kebab-parse-md` either by (a) exposing `ParsedBlock` as a `kebab-core` type, or (b) `kebab-parse-md` re-exporting via a public DTO. Pick (a): move `ParsedBlock` into `kebab-core` so this task does not import `kebab-parse-md`. ## Inputs | input | type | source | |-------|------|--------| -| `RawAsset` | `kb_core::RawAsset` | p1-1 | +| `RawAsset` | `kebab_core::RawAsset` | p1-1 | | `Metadata` + frontmatter span + warnings | from p1-2 | parser caller | | `Vec` + warnings | from p1-3 | parser caller | -| `parser_version` | `kb_core::ParserVersion` | constant in `kb-parse-md` | +| `parser_version` | `kebab_core::ParserVersion` | constant in `kebab-parse-md` | ## Outputs | output | type | downstream | |--------|------|------------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | `kb-chunk`, `kb-store-sqlite` | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | `kebab-chunk`, `kebab-store-sqlite` | ## Public surface (signatures only — no new types) ```rust pub fn build_canonical_document( - asset: &kb_core::RawAsset, - metadata: kb_core::Metadata, - blocks: Vec, - parser_version: &kb_core::ParserVersion, + asset: &kebab_core::RawAsset, + metadata: kebab_core::Metadata, + blocks: Vec, + parser_version: &kebab_core::ParserVersion, warnings: Vec, -) -> anyhow::Result; +) -> anyhow::Result; -pub fn id_for_doc(workspace_path: &kb_core::WorkspacePath, asset: &kb_core::AssetId, parser_version: &kb_core::ParserVersion) -> kb_core::DocumentId; -pub fn id_for_block(doc: &kb_core::DocumentId, kind: &str, heading_path: &[String], ordinal: u32, span: &kb_core::SourceSpan) -> kb_core::BlockId; +pub fn id_for_doc(workspace_path: &kebab_core::WorkspacePath, asset: &kebab_core::AssetId, parser_version: &kebab_core::ParserVersion) -> kebab_core::DocumentId; +pub fn id_for_block(doc: &kebab_core::DocumentId, kind: &str, heading_path: &[String], ordinal: u32, span: &kebab_core::SourceSpan) -> kebab_core::BlockId; ``` ## Behavior contract @@ -750,14 +750,14 @@ pub fn id_for_block(doc: &kb_core::DocumentId, kind: &str, heading_path: &[Strin | unit | provenance contains Discovered/Parsed/Normalized in order | inline | | snapshot | `fixtures/markdown/code-and-table.md` → CanonicalDocument JSON stable (incl. all IDs) | fixture | -All tests under `cargo test -p kb-normalize`. +All tests under `cargo test -p kebab-normalize`. ## Definition of Done -- [ ] `cargo check -p kb-normalize` passes -- [ ] `cargo test -p kb-normalize` passes +- [ ] `cargo check -p kebab-normalize` passes +- [ ] `cargo test -p kebab-normalize` passes - [ ] Determinism test runs ≥ 1000 iterations under 1 second -- [ ] No `kb-parse-md` import (consumed via `kb-core::ParsedBlock`) +- [ ] No `kebab-parse-md` import (consumed via `kebab-core::ParsedBlock`) - [ ] PR links design §4.2, §4.3 ## Out of scope @@ -791,13 +791,13 @@ Write to `tasks/p1/p1-5-chunk.md`: ````markdown --- phase: P1 -component: kb-chunk +component: kebab-chunk task_id: p1-5 title: "Markdown heading-aware chunker (md-heading-v1)" status: planned depends_on: [p1-4] unblocks: [p1-6, p2-2, p3-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.5 Chunk, §4.2 chunk_id recipe, §7.2 Chunker, §0 Q3 citation] --- @@ -813,8 +813,8 @@ The first concrete `Chunker`. Establishes how subsequent chunkers (PDF page chun ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde` - `blake3` (policy_hash) - `serde-json-canonicalizer` @@ -822,30 +822,30 @@ The first concrete `Chunker`. Establishes how subsequent chunkers (PDF page chun ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize` (consumes `CanonicalDocument` only via `kb-core`), `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize` (consumes `CanonicalDocument` only via `kebab-core`), `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | p1-4 | -| `ChunkPolicy` | `kb_core::ChunkPolicy` | `kb-app` from config | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | p1-4 | +| `ChunkPolicy` | `kebab_core::ChunkPolicy` | `kebab-app` from config | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` | `kb_core::Chunk` | `kb-store-sqlite` (p1-6), `kb-embed*` (P3) | +| `Vec` | `kebab_core::Chunk` | `kebab-store-sqlite` (p1-6), `kebab-embed*` (P3) | ## Public surface (signatures only — no new types) ```rust pub struct MdHeadingV1Chunker; -impl kb_core::Chunker for MdHeadingV1Chunker { - fn chunker_version(&self) -> kb_core::ChunkerVersion; - fn policy_hash(&self, policy: &kb_core::ChunkPolicy) -> String; - fn chunk(&self, doc: &kb_core::CanonicalDocument, policy: &kb_core::ChunkPolicy) -> anyhow::Result>; +impl kebab_core::Chunker for MdHeadingV1Chunker { + fn chunker_version(&self) -> kebab_core::ChunkerVersion; + fn policy_hash(&self, policy: &kebab_core::ChunkPolicy) -> String; + fn chunk(&self, doc: &kebab_core::CanonicalDocument, policy: &kebab_core::ChunkPolicy) -> anyhow::Result>; } ``` @@ -882,12 +882,12 @@ impl kb_core::Chunker for MdHeadingV1Chunker { | determinism | identical input + identical policy → identical chunk_ids | inline | | snapshot | `fixtures/markdown/long-section.md` → Vec JSON stable | fixture | -All tests under `cargo test -p kb-chunk`. +All tests under `cargo test -p kebab-chunk`. ## Definition of Done -- [ ] `cargo check -p kb-chunk` passes -- [ ] `cargo test -p kb-chunk` passes +- [ ] `cargo check -p kebab-chunk` passes +- [ ] `cargo test -p kebab-chunk` passes - [ ] Snapshot stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.5, §4.2 @@ -924,13 +924,13 @@ Write to `tasks/p1/p1-6-store-sqlite.md`: ````markdown --- phase: P1 -component: kb-store-sqlite (P1 subset) +component: kebab-store-sqlite (P1 subset) task_id: p1-6 title: "SQLite store: assets/documents/blocks/chunks + asset writer + migrations" status: planned depends_on: [p1-1, p1-4, p1-5] unblocks: [p2-1, p3-3, p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5 DDL (5.1, 5.2, 5.3, 5.4, 5.5 chunks only — FTS handled in p2-1), §5.7 jobs/ingest_runs, §5.8 transactions, §6.3 data_dir layout] --- @@ -946,8 +946,8 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `rusqlite` (with `bundled-sqlcipher` disabled; use `bundled` feature) - `refinery` for migrations - `serde_json` @@ -958,7 +958,7 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT ## Forbidden dependencies -- `kb-source-fs` (only types via `kb-core`), `kb-parse-md`, `kb-normalize`, `kb-chunk` (only types via `kb-core`), `kb-store-vector`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs` (only types via `kebab-core`), `kebab-parse-md`, `kebab-normalize`, `kebab-chunk` (only types via `kebab-core`), `kebab-store-vector`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs @@ -966,17 +966,17 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT |-------|------|--------| | migrations | `migrations/V001__init.sql` | repo | | `RawAsset` + bytes | `(RawAsset, Vec)` | p1-1 + reader | -| `CanonicalDocument` | `kb_core::CanonicalDocument` | p1-4 | -| `Vec` | `kb_core::Chunk` | p1-5 | -| `IngestRun` aggregates | `(scope, counts, duration)` | `kb-app` | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | p1-4 | +| `Vec` | `kebab_core::Chunk` | p1-5 | +| `IngestRun` aggregates | `(scope, counts, duration)` | `kebab-app` | ## Outputs | output | type | downstream | |--------|------|------------| -| `data_dir/kb.sqlite` rows in `assets`, `documents`, `blocks`, `chunks`, `document_tags`, `ingest_runs`, `jobs`, `schema_meta`, `migrations` | – | every later phase | +| `data_dir/kebab.sqlite` rows in `assets`, `documents`, `blocks`, `chunks`, `document_tags`, `ingest_runs`, `jobs`, `schema_meta`, `migrations` | – | every later phase | | `data_dir/assets//` bytes (when copied) | – | future re-extraction, integrity verification | -| `IngestReport` (wire schema v1) | `kb_core::IngestReport` | `kb-cli`, eval | +| `IngestReport` (wire schema v1) | `kebab_core::IngestReport` | `kebab-cli`, eval | ## Public surface (signatures only — no new types) @@ -984,23 +984,23 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT pub struct SqliteStore { /* internal */ } impl SqliteStore { - pub fn open(config: &kb_config::Config) -> anyhow::Result; + pub fn open(config: &kebab_config::Config) -> anyhow::Result; pub fn run_migrations(&self) -> anyhow::Result<()>; - pub fn put_asset_with_bytes(&self, asset: &kb_core::RawAsset, bytes: &[u8]) -> anyhow::Result<()>; + pub fn put_asset_with_bytes(&self, asset: &kebab_core::RawAsset, bytes: &[u8]) -> anyhow::Result<()>; } -impl kb_core::DocumentStore for SqliteStore { - fn put_asset(&self, a: &kb_core::RawAsset) -> anyhow::Result<()>; - fn put_document(&self, d: &kb_core::CanonicalDocument) -> anyhow::Result<()>; - fn put_blocks(&self, doc: &kb_core::DocumentId, blocks: &[kb_core::Block]) -> anyhow::Result<()>; - fn put_chunks(&self, doc: &kb_core::DocumentId, chunks: &[kb_core::Chunk]) -> anyhow::Result<()>; - fn get_document(&self, id: &kb_core::DocumentId) -> anyhow::Result>; - fn get_chunk(&self, id: &kb_core::ChunkId) -> anyhow::Result>; - fn list_documents(&self, filter: &kb_core::DocFilter) -> anyhow::Result>; +impl kebab_core::DocumentStore for SqliteStore { + fn put_asset(&self, a: &kebab_core::RawAsset) -> anyhow::Result<()>; + fn put_document(&self, d: &kebab_core::CanonicalDocument) -> anyhow::Result<()>; + fn put_blocks(&self, doc: &kebab_core::DocumentId, blocks: &[kebab_core::Block]) -> anyhow::Result<()>; + fn put_chunks(&self, doc: &kebab_core::DocumentId, chunks: &[kebab_core::Chunk]) -> anyhow::Result<()>; + fn get_document(&self, id: &kebab_core::DocumentId) -> anyhow::Result>; + fn get_chunk(&self, id: &kebab_core::ChunkId) -> anyhow::Result>; + fn list_documents(&self, filter: &kebab_core::DocFilter) -> anyhow::Result>; } -impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } +impl kebab_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } ``` ## Behavior contract @@ -1020,7 +1020,7 @@ impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } ## Storage / wire effects -- Writes: `kb.sqlite` (multiple tables), `data_dir/assets//` (copied case). +- Writes: `kebab.sqlite` (multiple tables), `data_dir/assets//` (copied case). - Reads on subsequent calls: same DB. ## Test plan @@ -1036,14 +1036,14 @@ impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } | contract | DocumentStore trait round-trip for fixture document | `fixtures/markdown/code-and-table.md` | | snapshot | IngestReport JSON for fixture run | fixture | -All tests under `cargo test -p kb-store-sqlite` with no network. +All tests under `cargo test -p kebab-store-sqlite` with no network. ## Definition of Done -- [ ] `cargo check -p kb-store-sqlite` passes -- [ ] `cargo test -p kb-store-sqlite` passes +- [ ] `cargo check -p kebab-store-sqlite` passes +- [ ] `cargo test -p kebab-store-sqlite` passes - [ ] migration `V001__init.sql` matches design §5 verbatim (diff-checked in CI) -- [ ] Writes to `~/.local/share/kb/` are gated by `kb-config`'s `data_dir` and never escape it +- [ ] Writes to `~/.local/share/kebab/` are gated by `kebab-config`'s `data_dir` and never escape it - [ ] No imports outside Allowed dependencies - [ ] PR links design §5 @@ -1056,7 +1056,7 @@ All tests under `cargo test -p kb-store-sqlite` with no network. ## Risks / notes -- WAL mode requires careful test cleanup: tests must drop the connection before removing `kb.sqlite-wal` / `-shm`. +- WAL mode requires careful test cleanup: tests must drop the connection before removing `kebab.sqlite-wal` / `-shm`. - Asset directory shard prefix uses `asset_id[..2]`; using `asset_id[..1]` would create at most 16 dirs (insufficient). ```` @@ -1074,7 +1074,7 @@ git commit -m "tasks: add p1-6 store-sqlite component spec" ```bash ls tasks/p1/ | sort -for f in tasks/p1/p1-*.md; do grep -q '2026-04-27-kb-final-form-design.md' "$f" || echo "MISSING REF in $f"; done +for f in tasks/p1/p1-*.md; do grep -q '2026-04-27-kebab-final-form-design.md' "$f" || echo "MISSING REF in $f"; done echo done ``` @@ -1106,9 +1106,9 @@ For each component task below, the steps are: (1) write file, (2) verify, (3) co **Files:** Create: `tasks/p0/p0-1-skeleton.md`. Also `mkdir -p tasks/p0`. -`contract_sections`: §3 (all subsections), §4, §5 (migrations meta only), §6, §7, §8, §10. `Allowed`: workspace + `kb-core` + `kb-config` + `kb-app` + `kb-cli` only. +`contract_sections`: §3 (all subsections), §4, §5 (migrations meta only), §6, §7, §8, §10. `Allowed`: workspace + `kebab-core` + `kebab-config` + `kebab-app` + `kebab-cli` only. -Body covers: workspace `Cargo.toml` resolver=3, edition 2024, member list (`kb-core`, `kb-config`, `kb-app`, `kb-cli`), workspace dependencies, `kb-core` types and traits per design §3 / §7, deterministic ID functions per §4 (with full unit tests), `kb-config` loader (TOML + env + CLI override per §6.4), `kb-app` facade signatures (`ingest`, `search`, `ask`, `inspect_doc`, `inspect_chunk`, `doctor`, `init`), `kb-cli` skeleton with clap + `--help`. DoD: `cargo check --workspace`, `cargo test --workspace` (Newtype+ID+canonical-json tests only), `kb --help` works, `docs/spec/*` stubs created (link to frozen design doc), `docs/wire-schema/v1/*.schema.json` stubs (one file per object in §2). +Body covers: workspace `Cargo.toml` resolver=3, edition 2024, member list (`kebab-core`, `kebab-config`, `kebab-app`, `kebab-cli`), workspace dependencies, `kebab-core` types and traits per design §3 / §7, deterministic ID functions per §4 (with full unit tests), `kebab-config` loader (TOML + env + CLI override per §6.4), `kebab-app` facade signatures (`ingest`, `search`, `ask`, `inspect_doc`, `inspect_chunk`, `doctor`, `init`), `kebab-cli` skeleton with clap + `--help`. DoD: `cargo check --workspace`, `cargo test --workspace` (Newtype+ID+canonical-json tests only), `kebab --help` works, `docs/spec/*` stubs created (link to frozen design doc), `docs/wire-schema/v1/*.schema.json` stubs (one file per object in §2). - [ ] **Step 1: Create directory and file** @@ -1134,11 +1134,11 @@ git -c user.name=kb -c user.email=kb@local commit -m "tasks: add p0-1 skeleton c #### `p2-1-fts-schema.md` -`contract_sections`: §5.5 FTS5 + triggers, §9 versioning. `Allowed`: `kb-core`, `kb-config`, `kb-store-sqlite` (extends migrations). `depends_on: [p1-6]`. Migration `V002__fts.sql` adds `chunks_fts` virtual table and three triggers verbatim from §5.5. Tests: backfill from existing chunks via `INSERT INTO chunks_fts SELECT ... FROM chunks`, then assert FTS row count == chunks row count; insert/update/delete in `chunks` reflects in `chunks_fts`. +`contract_sections`: §5.5 FTS5 + triggers, §9 versioning. `Allowed`: `kebab-core`, `kebab-config`, `kebab-store-sqlite` (extends migrations). `depends_on: [p1-6]`. Migration `V002__fts.sql` adds `chunks_fts` virtual table and three triggers verbatim from §5.5. Tests: backfill from existing chunks via `INSERT INTO chunks_fts SELECT ... FROM chunks`, then assert FTS row count == chunks row count; insert/update/delete in `chunks` reflects in `chunks_fts`. #### `p2-2-lexical-retriever.md` -`contract_sections`: §3.7 SearchQuery/Hit, §0 Q3 citation (URI fragment), §1.5 search output (for snippet length defaults), §2.2 wire schema. `Allowed`: `kb-core`, `kb-config`, `kb-store-sqlite`. `depends_on: [p2-1]`. Implements `Retriever` trait with `bm25(chunks_fts)` ranking, snippet via SQLite `snippet()` (≤ `snippet_chars` chars), citation built per §0 Q3 from `source_spans`. Tests: top-k correctness on fixture corpus, citation line range round-trip against original Markdown, deterministic across two runs. +`contract_sections`: §3.7 SearchQuery/Hit, §0 Q3 citation (URI fragment), §1.5 search output (for snippet length defaults), §2.2 wire schema. `Allowed`: `kebab-core`, `kebab-config`, `kebab-store-sqlite`. `depends_on: [p2-1]`. Implements `Retriever` trait with `bm25(chunks_fts)` ranking, snippet via SQLite `snippet()` (≤ `snippet_chars` chars), citation built per §0 Q3 from `source_spans`. Tests: top-k correctness on fixture corpus, citation line range round-trip against original Markdown, deterministic across two runs. - [ ] **Step 1: Create directory and both spec files** (template per Phase B; bodies as described above). - [ ] **Step 2: Verify with `for f in tasks/p2/p2-*.md; do test -s "$f" || echo MISSING $f; done; echo done` (expect only `done`).** @@ -1152,10 +1152,10 @@ git add tasks/p2 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: **Files:** `mkdir -p tasks/p3` and create `p3-1-embedder-trait.md`, `p3-2-fastembed-adapter.md`, `p3-3-lancedb-store.md`, `p3-4-hybrid-fusion.md`. -- `p3-1-embedder-trait.md`: §3.7, §7.2 Embedder, §11. Allowed: `kb-core`, `kb-config`. No external embedding dep. Public surface: `Embedder` trait + `EmbeddingInput`/`EmbeddingKind` already in core (validate they exist; if not, this task is also a `kb-core` patch). `depends_on: [p0-1]`. Tests: trait dyn dispatch, mock embedder. -- `p3-2-fastembed-adapter.md`: §11.3, §6.4 `[models.embedding]`. Allowed: `kb-core`, `kb-config`, `fastembed`, `tokenizers`, `ort`. `depends_on: [p3-1]`. Provides `FastembedEmbedder` implementing `Embedder` for `multilingual-e5-small` (default), with required Document/Query prefix per §11.3. Tests: dimension check, deterministic vector for fixed input (hash compare on first 8 floats with epsilon), batch size respected. -- `p3-3-lancedb-store.md`: §3.5, §5.6 embedding_records, §6.3 lancedb table naming. Allowed: `kb-core`, `kb-config`, `lancedb`, `arrow`, `kb-store-sqlite` (write `embedding_records` row only — no other table). `depends_on: [p3-2, p1-6]`. Implements `VectorStore` trait. Table naming `chunk_embeddings__.lance`. `ensure_table` creates if missing. `upsert` inserts vectors and writes a matching `embedding_records` row in same logical operation (best-effort 2PC: lance commit, then SQLite insert; on SQLite failure, log warning + leave lance row — re-upsert is idempotent because of the `UNIQUE(chunk_id, model_id, model_version, dimensions)` constraint and lance upsert semantics). `search` filters via SearchFilters and returns top-k. Tests: smoke (insert+search), dimension mismatch error, model isolation (two models stay in two tables). -- `p3-4-hybrid-fusion.md`: §3.7 RetrievalDetail, §0 Q3, §1.6 search --explain, §6.4 `[search]` rrf settings. Allowed: `kb-core`, `kb-config`, `kb-store-sqlite` (lexical Retriever from p2-2), `kb-store-vector` (vector Retriever wrapper around `VectorStore::search`). `depends_on: [p2-2, p3-3]`. Implements `HybridRetriever` that dispatches by `SearchMode`, fuses with RRF (k from config, default 60), populates `lexical_score`, `vector_score`, `lexical_rank`, `vector_rank`, `fusion_score`. Tests: pure lexical mode == p2-2 output; pure vector mode == p3-3 output; hybrid produces strictly larger or equal coverage of expected hits than either single mode on a small fixture; deterministic. +- `p3-1-embedder-trait.md`: §3.7, §7.2 Embedder, §11. Allowed: `kebab-core`, `kebab-config`. No external embedding dep. Public surface: `Embedder` trait + `EmbeddingInput`/`EmbeddingKind` already in core (validate they exist; if not, this task is also a `kebab-core` patch). `depends_on: [p0-1]`. Tests: trait dyn dispatch, mock embedder. +- `p3-2-fastembed-adapter.md`: §11.3, §6.4 `[models.embedding]`. Allowed: `kebab-core`, `kebab-config`, `fastembed`, `tokenizers`, `ort`. `depends_on: [p3-1]`. Provides `FastembedEmbedder` implementing `Embedder` for `multilingual-e5-small` (default), with required Document/Query prefix per §11.3. Tests: dimension check, deterministic vector for fixed input (hash compare on first 8 floats with epsilon), batch size respected. +- `p3-3-lancedb-store.md`: §3.5, §5.6 embedding_records, §6.3 lancedb table naming. Allowed: `kebab-core`, `kebab-config`, `lancedb`, `arrow`, `kebab-store-sqlite` (write `embedding_records` row only — no other table). `depends_on: [p3-2, p1-6]`. Implements `VectorStore` trait. Table naming `chunk_embeddings__.lance`. `ensure_table` creates if missing. `upsert` inserts vectors and writes a matching `embedding_records` row in same logical operation (best-effort 2PC: lance commit, then SQLite insert; on SQLite failure, log warning + leave lance row — re-upsert is idempotent because of the `UNIQUE(chunk_id, model_id, model_version, dimensions)` constraint and lance upsert semantics). `search` filters via SearchFilters and returns top-k. Tests: smoke (insert+search), dimension mismatch error, model isolation (two models stay in two tables). +- `p3-4-hybrid-fusion.md`: §3.7 RetrievalDetail, §0 Q3, §1.6 search --explain, §6.4 `[search]` rrf settings. Allowed: `kebab-core`, `kebab-config`, `kebab-store-sqlite` (lexical Retriever from p2-2), `kebab-store-vector` (vector Retriever wrapper around `VectorStore::search`). `depends_on: [p2-2, p3-3]`. Implements `HybridRetriever` that dispatches by `SearchMode`, fuses with RRF (k from config, default 60), populates `lexical_score`, `vector_score`, `lexical_rank`, `vector_rank`, `fusion_score`. Tests: pure lexical mode == p2-2 output; pure vector mode == p3-3 output; hybrid produces strictly larger or equal coverage of expected hits than either single mode on a small fixture; deterministic. - [ ] **Step 1: Create directory and 4 files** (template per Phase B). - [ ] **Step 2: Verify** @@ -1176,9 +1176,9 @@ git add tasks/p3 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: **Files:** `mkdir -p tasks/p4` and create `p4-1-llm-trait.md`, `p4-2-ollama-adapter.md`, `p4-3-rag-pipeline.md`. -- `p4-1-llm-trait.md`: §7.2 LanguageModel + TokenChunk, §0 Q5 streaming, §3.8 Answer types referenced. Allowed: `kb-core`, `kb-config`. `depends_on: [p0-1]`. Defines (or validates) `LanguageModel` trait, `GenerateRequest`, `TokenChunk`, `FinishReason`, `TokenUsage` per design. Tests: trait dyn dispatch, mock LM streams 3 tokens. -- `p4-2-ollama-adapter.md`: §11.2 Ollama, §6.4 `[models.llm]`, §0 Q5 streaming. Allowed: `kb-core`, `kb-config`, `reqwest` (blocking + json + stream feature) or `ureq` + manual SSE; `serde_json`, `tokio`/runtime if needed. `depends_on: [p4-1]`. Implements `OllamaLanguageModel` with streaming `/api/generate`. `temperature=0.0` default, `seed` honored for determinism. Reachability/missing-model errors map to `LlmError` per design §10. Tests: against a mock HTTP server (`wiremock` or hand-rolled `tiny_http`); deterministic stream collect equals buffered concatenation; missing model returns `LlmError::ModelNotPulled` with proper hint. -- `p4-3-rag-pipeline.md`: §0 Q4 refusal (two-layer), §0 Q7 footer, §1.1–1.4 ask scenes, §2.3 Answer wire, §3.8 internal Answer, §6.4 `[rag]`. Allowed: `kb-core`, `kb-config`, `kb-search` (Retriever), `kb-llm` (LanguageModel). `depends_on: [p3-4, p4-2]`. Pipeline: retrieve top-k → score gate (`refusal_reason: ScoreGate` if top1 < gate) → context packer (token budget + heading_path header `[#n doc=… heading=… span=…]`) → render `rag-v1` prompt → stream → collect → citation extraction (regex `\[(\d+)\]`) → citation validation (each `[n]` must map to a packed chunk; otherwise `grounded=false`, `refusal_reason: LlmSelfJudge`) → write `answers` row. Tests: happy path produces grounded Answer with citations; query with all chunks below gate produces ScoreGate refusal; query whose LLM emits a citation pointing to non-existent `[7]` becomes LlmSelfJudge refusal; identical query under temperature=0 produces byte-identical Answer (snapshot). +- `p4-1-llm-trait.md`: §7.2 LanguageModel + TokenChunk, §0 Q5 streaming, §3.8 Answer types referenced. Allowed: `kebab-core`, `kebab-config`. `depends_on: [p0-1]`. Defines (or validates) `LanguageModel` trait, `GenerateRequest`, `TokenChunk`, `FinishReason`, `TokenUsage` per design. Tests: trait dyn dispatch, mock LM streams 3 tokens. +- `p4-2-ollama-adapter.md`: §11.2 Ollama, §6.4 `[models.llm]`, §0 Q5 streaming. Allowed: `kebab-core`, `kebab-config`, `reqwest` (blocking + json + stream feature) or `ureq` + manual SSE; `serde_json`, `tokio`/runtime if needed. `depends_on: [p4-1]`. Implements `OllamaLanguageModel` with streaming `/api/generate`. `temperature=0.0` default, `seed` honored for determinism. Reachability/missing-model errors map to `LlmError` per design §10. Tests: against a mock HTTP server (`wiremock` or hand-rolled `tiny_http`); deterministic stream collect equals buffered concatenation; missing model returns `LlmError::ModelNotPulled` with proper hint. +- `p4-3-rag-pipeline.md`: §0 Q4 refusal (two-layer), §0 Q7 footer, §1.1–1.4 ask scenes, §2.3 Answer wire, §3.8 internal Answer, §6.4 `[rag]`. Allowed: `kebab-core`, `kebab-config`, `kebab-search` (Retriever), `kebab-llm` (LanguageModel). `depends_on: [p3-4, p4-2]`. Pipeline: retrieve top-k → score gate (`refusal_reason: ScoreGate` if top1 < gate) → context packer (token budget + heading_path header `[#n doc=… heading=… span=…]`) → render `rag-v1` prompt → stream → collect → citation extraction (regex `\[(\d+)\]`) → citation validation (each `[n]` must map to a packed chunk; otherwise `grounded=false`, `refusal_reason: LlmSelfJudge`) → write `answers` row. Tests: happy path produces grounded Answer with citations; query with all chunks below gate produces ScoreGate refusal; query whose LLM emits a citation pointing to non-existent `[7]` becomes LlmSelfJudge refusal; identical query under temperature=0 produces byte-identical Answer (snapshot). - [ ] **Step 1, 2, 3** as in C3. @@ -1193,8 +1193,8 @@ git add tasks/p4 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: **Files:** `mkdir -p tasks/p5` and create `p5-1-golden-fixture-runner.md`, `p5-2-metrics-compare.md`. -- `p5-1-golden-fixture-runner.md`: phase epic + §5.7 eval_runs/eval_query_results, §6.3 runs_dir. Allowed: `kb-core`, `kb-config`, `kb-app` (calls facade for search/ask), `serde_yaml`. `depends_on: [p4-3]`. Loads `fixtures/golden_queries.yaml`, runs each query in selected mode (lexical/vector/hybrid/rag), captures per-query results to `eval_query_results` and to `runs_dir//per_query.jsonl`. Tests: fixture with 3 queries runs end-to-end on a tiny corpus, all rows recorded. -- `p5-2-metrics-compare.md`: phase epic, §0 Q6 wire schema. Allowed: `kb-core`, `kb-config`, `kb-store-sqlite` (read eval rows). `depends_on: [p5-1]`. Computes hit@k, MRR, recall@k_doc, citation_coverage, groundedness (rule-based via `must_contain`), empty_result_rate, refusal_correctness. `kb eval compare a b` produces wins/losses/draws + delta. Tests: fixed input rows produce expected metric values; compare produces stable sorted output. +- `p5-1-golden-fixture-runner.md`: phase epic + §5.7 eval_runs/eval_query_results, §6.3 runs_dir. Allowed: `kebab-core`, `kebab-config`, `kebab-app` (calls facade for search/ask), `serde_yaml`. `depends_on: [p4-3]`. Loads `fixtures/golden_queries.yaml`, runs each query in selected mode (lexical/vector/hybrid/rag), captures per-query results to `eval_query_results` and to `runs_dir//per_query.jsonl`. Tests: fixture with 3 queries runs end-to-end on a tiny corpus, all rows recorded. +- `p5-2-metrics-compare.md`: phase epic, §0 Q6 wire schema. Allowed: `kebab-core`, `kebab-config`, `kebab-store-sqlite` (read eval rows). `depends_on: [p5-1]`. Computes hit@k, MRR, recall@k_doc, citation_coverage, groundedness (rule-based via `must_contain`), empty_result_rate, refusal_correctness. `kebab eval compare a b` produces wins/losses/draws + delta. Tests: fixed input rows produce expected metric values; compare produces stable sorted output. - [ ] **Step 1, 2, 3** as in C3. @@ -1209,9 +1209,9 @@ git add tasks/p5 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: `mkdir -p tasks/p6` and create: -- `p6-1-image-extractor-exif.md`: phase epic §9.1, §3.4 ImageRefBlock, §3.7a ImageType. Allowed: `kb-core`, `kb-config`, `image`, `kamadak-exif`. Implements `Extractor` for `MediaType::Image(_)` producing a `CanonicalDocument` whose body is exactly one `ImageRefBlock`. EXIF goes to `metadata.user`. `depends_on: [p0-1, p1-6]`. Tests: PNG/JPEG decode metadata; EXIF extraction; deterministic doc_id. -- `p6-2-ocr-adapter.md`: phase epic §9.1. Allowed: `kb-core`, `kb-config`, `image`, OS-specific OCR (feature `apple-vision` for macOS via sidecar binary; feature `tesseract` for cross-platform; default tesseract). Defines `OcrEngine` trait + adapter. Populates `ImageRefBlock.ocr` `OcrText` (`joined`, regions, engine, engine_version). `depends_on: [p6-1]`. Tests: deterministic text on a fixed fixture image with high-confidence text. -- `p6-3-caption-adapter.md`: phase epic §9.1 caption section, §3.7a ModelCaption. Allowed: `kb-core`, `kb-config`, `kb-llm` (reuse LanguageModel for VLM). Optional/feature-gated. `depends_on: [p6-1, p4-2]`. Populates `ImageRefBlock.caption`. Tests: with mock LM, caption recorded with model id; absence of feature flag leaves caption=None. +- `p6-1-image-extractor-exif.md`: phase epic §9.1, §3.4 ImageRefBlock, §3.7a ImageType. Allowed: `kebab-core`, `kebab-config`, `image`, `kamadak-exif`. Implements `Extractor` for `MediaType::Image(_)` producing a `CanonicalDocument` whose body is exactly one `ImageRefBlock`. EXIF goes to `metadata.user`. `depends_on: [p0-1, p1-6]`. Tests: PNG/JPEG decode metadata; EXIF extraction; deterministic doc_id. +- `p6-2-ocr-adapter.md`: phase epic §9.1. Allowed: `kebab-core`, `kebab-config`, `image`, OS-specific OCR (feature `apple-vision` for macOS via sidecar binary; feature `tesseract` for cross-platform; default tesseract). Defines `OcrEngine` trait + adapter. Populates `ImageRefBlock.ocr` `OcrText` (`joined`, regions, engine, engine_version). `depends_on: [p6-1]`. Tests: deterministic text on a fixed fixture image with high-confidence text. +- `p6-3-caption-adapter.md`: phase epic §9.1 caption section, §3.7a ModelCaption. Allowed: `kebab-core`, `kebab-config`, `kebab-llm` (reuse LanguageModel for VLM). Optional/feature-gated. `depends_on: [p6-1, p4-2]`. Populates `ImageRefBlock.caption`. Tests: with mock LM, caption recorded with model id; absence of feature flag leaves caption=None. - [ ] **Step 1, 2, 3** as in C3. @@ -1226,8 +1226,8 @@ git add tasks/p6 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: `mkdir -p tasks/p7` and create: -- `p7-1-pdf-text-extractor.md`: phase epic §9.2, §3.4 SourceSpan::Page. Allowed: `kb-core`, `kb-config`, `pdf-extract`, `lopdf` (page metadata). `depends_on: [p0-1, p1-6]`. Extractor for `MediaType::Pdf` produces a `CanonicalDocument` with one `Paragraph` per page, `SourceSpan::Page`. Failed-text pages are emitted as paragraphs with empty text and a `Provenance` warning marking them as scanned candidates. Tests: page count, span correctness, failure handling. -- `p7-2-pdf-page-chunker.md`: phase epic §9.2, §3.5, §0 Q3 citation. Allowed: `kb-core`, `kb-config`. New chunker version `pdf-page-v1` that respects page boundaries. `depends_on: [p7-1]`. Tests: chunk does not cross page boundary; very long page subdivides per `target_tokens`. +- `p7-1-pdf-text-extractor.md`: phase epic §9.2, §3.4 SourceSpan::Page. Allowed: `kebab-core`, `kebab-config`, `pdf-extract`, `lopdf` (page metadata). `depends_on: [p0-1, p1-6]`. Extractor for `MediaType::Pdf` produces a `CanonicalDocument` with one `Paragraph` per page, `SourceSpan::Page`. Failed-text pages are emitted as paragraphs with empty text and a `Provenance` warning marking them as scanned candidates. Tests: page count, span correctness, failure handling. +- `p7-2-pdf-page-chunker.md`: phase epic §9.2, §3.5, §0 Q3 citation. Allowed: `kebab-core`, `kebab-config`. New chunker version `pdf-page-v1` that respects page boundaries. `depends_on: [p7-1]`. Tests: chunk does not cross page boundary; very long page subdivides per `target_tokens`. - [ ] **Step 1, 2, 3** as in C3. @@ -1242,7 +1242,7 @@ git add tasks/p7 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: `mkdir -p tasks/p8` and create: -- `p8-1-whisper-adapter.md`: phase epic §9.3, §3.4 AudioRefBlock + `Transcript`. Allowed: `kb-core`, `kb-config`, whisper.cpp Rust binding (`whisper-rs`) or sidecar binary. `depends_on: [p0-1, p1-6]`. Implements `Transcriber` trait. Default model `large-v3` via config; tests use a tiny model (e.g., `base.en`) for speed. Tests: monotone segment timestamps, language detection populated, deterministic transcript on fixed audio. +- `p8-1-whisper-adapter.md`: phase epic §9.3, §3.4 AudioRefBlock + `Transcript`. Allowed: `kebab-core`, `kebab-config`, whisper.cpp Rust binding (`whisper-rs`) or sidecar binary. `depends_on: [p0-1, p1-6]`. Implements `Transcriber` trait. Default model `large-v3` via config; tests use a tiny model (e.g., `base.en`) for speed. Tests: monotone segment timestamps, language detection populated, deterministic transcript on fixed audio. - `p8-2-segment-chunker.md`: phase epic §9.3, §3.5. New `audio-segment-v1` chunker that groups segments up to `target_tokens` with priority on speaker turn boundaries (when present). `depends_on: [p8-1]`. Tests: chunk timestamp == first/last segment timestamp; speaker change forces split. - [ ] **Step 1, 2, 3** as in C3. @@ -1258,11 +1258,11 @@ git add tasks/p8 && git -c user.name=kb -c user.email=kb@local commit -m "tasks: `mkdir -p tasks/p9` and create: -- `p9-1-tui-library.md`: phase epic §16.2, §3.7. Allowed: `kb-core`, `kb-app` only (UI law). `ratatui`, `crossterm`. `depends_on: [p1-6]`. Library list view + tag filter. Tests: snapshot of rendered frame against fixture corpus list. +- `p9-1-tui-library.md`: phase epic §16.2, §3.7. Allowed: `kebab-core`, `kebab-app` only (UI law). `ratatui`, `crossterm`. `depends_on: [p1-6]`. Library list view + tag filter. Tests: snapshot of rendered frame against fixture corpus list. - `p9-2-tui-search.md`: phase epic §16.2, §1.5. Allowed: same as p9-1. `depends_on: [p2-2, p3-4]`. Search input + result list + preview pane; `Enter` triggers external editor jump (`$EDITOR + `). Tests: search results render; `g` keybinding constructs the correct editor command. - `p9-3-tui-ask.md`: phase epic §16.2, §1.1, §1.2. Allowed: same. `depends_on: [p4-3]`. Ask pane shows streaming tokens; `--explain` toggle. Tests: streaming render, refusal render. - `p9-4-tui-inspect.md`: §1.6 inspect, §3.5. Allowed: same. `depends_on: [p1-6, p3-3]`. Renders Document and Chunk inspection per wire schemas 2.5/2.6. -- `p9-5-desktop-tauri.md`: phase epic §16.3, §1 all scenes. Allowed: `kb-core`, `kb-app`, Tauri backend; frontend stack TBD by user (vanilla TS by default). `depends_on: [p9-1, p9-2, p9-3, p9-4]`. Backend exposes Tauri commands that wrap `kb-app` 1:1. Source viewer per medium (Markdown render, PDF page, image with region overlay, audio with seek). Tests: backend command unit tests (no frontend e2e in this task). +- `p9-5-desktop-tauri.md`: phase epic §16.3, §1 all scenes. Allowed: `kebab-core`, `kebab-app`, Tauri backend; frontend stack TBD by user (vanilla TS by default). `depends_on: [p9-1, p9-2, p9-3, p9-4]`. Backend exposes Tauri commands that wrap `kebab-app` 1:1. Source viewer per medium (Markdown render, PDF page, image with region overlay, audio with seek). Tests: backend command unit tests (no frontend e2e in this task). - [ ] **Step 1, 2, 3** as in C3. @@ -1351,5 +1351,5 @@ git -c user.name=kb -c user.email=kb@local commit -m "tasks: update INDEX with f - [ ] `tasks/p0/`, `tasks/p1/` … `tasks/p9/` exist with the component spec files listed above. - [ ] Every component spec contains: frontmatter (with `contract_sections`), Allowed/Forbidden, Inputs, Outputs, Public surface, Behavior contract, Storage/wire effects, Test plan, Definition of Done, Out of scope, Risks/notes. - [ ] `tasks/INDEX.md` lists every component task. -- [ ] No new domain types introduced inside any component spec — every type referenced is defined in [docs/superpowers/specs/2026-04-27-kb-final-form-design.md](../specs/2026-04-27-kb-final-form-design.md). +- [ ] No new domain types introduced inside any component spec — every type referenced is defined in [docs/superpowers/specs/2026-04-27-kebab-final-form-design.md](../specs/2026-04-27-kebab-final-form-design.md). - [ ] All commits authored sequentially per task; rollback is per-task. diff --git a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md index 5f14b11..212a699 100644 --- a/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md +++ b/docs/superpowers/specs/2026-04-27-kebab-final-form-design.md @@ -3,7 +3,7 @@ title: "KB v1 최종 결과물 형태 — Frozen Design" date: 2026-04-27 status: frozen purpose: 작은 단위 분해 작업 시 spec 변경을 막기 위한 단단한 contract 동결 -source_report: ../../../kb_local_rust_report.md +source_report: ../../../kebab_local_rust_report.md related_tasks: ../../../tasks/INDEX.md --- @@ -11,7 +11,7 @@ related_tasks: ../../../tasks/INDEX.md 이 문서는 사용자가 만족할 **최종 결과물의 매우 구체적 형태**를 동결한다. 각 phase 의 task 분해는 이 contract 위에서 수행되며, 이 문서가 바뀌지 않는 한 task 들의 인터페이스는 변하지 않는다. -전제 보고서는 [`kb_local_rust_report.md`](../../../kb_local_rust_report.md). 그 보고서가 *방향*과 *근거*를 제공하며, 이 문서가 *형태*를 못박는다. +전제 보고서는 [`kebab_local_rust_report.md`](../../../kebab_local_rust_report.md). 그 보고서가 *방향*과 *근거*를 제공하며, 이 문서가 *형태*를 못박는다. --- @@ -20,7 +20,7 @@ related_tasks: ../../../tasks/INDEX.md | # | 결정 | 값 | 근거 | |---|------|-----|------| | Q1 | scope 우선순위 | UX → Data 역도출 | 사용자 만족이 spec 안정성 lever | -| Q2 | headline UX | `kb ask` 답변 화면 | 검색/citation/RAG/refusal/모델메타 모두 노출 | +| Q2 | headline UX | `kebab ask` 답변 화면 | 검색/citation/RAG/refusal/모델메타 모두 노출 | | – | ask 기본 형식 | inline numeric refs `[1]…[n]` + footer | 일상 가독성 | | – | ask `--explain` | per-claim 분해 + verbose footer + retrieval trace | 디버그 단일 플래그 | | Q3 | citation 문자열 | URI fragment (`path#k=v…`, W3C Media Fragments) | 표준 정합 + Windows path 안전 + 브라우저 자동 스크롤 | @@ -34,7 +34,7 @@ related_tasks: ../../../tasks/INDEX.md | Q10 | workspace | single root + XDG layout | personal v1 적정 | | – | asset 보존 | content-addressable copy, `copy_threshold_mb=100` 초과 시 reference + checksum | reproducibility + 디스크 절감 | | – | wire 버전 | additive within `vN`, breaking → `vN+1` | 외부 깨짐 방지 | -| – | ignore | gitignore 문법 + `.kbignore` | 익숙함 | +| – | ignore | gitignore 문법 + `.kebabignore` | 익숙함 | | – | 에러 | thiserror per crate, anyhow at boundary | 추적성 + UX | | – | sync | watch=false default | v1 명시 ingest | @@ -42,43 +42,43 @@ related_tasks: ../../../tasks/INDEX.md ## 1. Headline UX scenes -### 1.1 `kb ask` (default) +### 1.1 `kebab ask` (default) ```text -$ kb ask "Markdown chunking 규칙은?" +$ kebab ask "Markdown chunking 규칙은?" heading boundary 우선 [1]. code block 중간 분할 금지 [2]. table 가능한 한 단일 chunk 유지 [2]. 긴 section 은 paragraph 단위로 분할 [1]. chunk 마다 heading_path 와 source_span 보존 [1]. ───────────────────────────────────────────────────────── -[1] notes/rust/kb-architecture.md#L661-L672 +[1] notes/rust/kebab-architecture.md#L661-L672 §14 Chunking 정책 -[2] notes/rust/kb-architecture.md#L665-L668 +[2] notes/rust/kebab-architecture.md#L665-L668 §14 Chunking 정책 grounded ✓ qwen2.5:14b-instruct rag-v1 3 chunks ``` -### 1.2 `kb ask --explain` +### 1.2 `kebab ask --explain` ```text -$ kb ask --explain "Markdown chunking 규칙은?" +$ kebab ask --explain "Markdown chunking 규칙은?" ▎ heading boundary 우선 - └ notes/rust/kb-architecture.md#L662 + └ notes/rust/kebab-architecture.md#L662 「heading boundary를 우선한다」 ▎ code block 중간 분할 금지 - └ notes/rust/kb-architecture.md#L663 + └ notes/rust/kebab-architecture.md#L663 「code block은 중간에서 자르지 않는다」 ▎ table 단일 chunk 유지 - └ notes/rust/kb-architecture.md#L664 + └ notes/rust/kebab-architecture.md#L664 「table은 가능한 한 하나의 chunk로」 ▎ heading_path / source_span 보존 - └ notes/rust/kb-architecture.md#L668-L670 + └ notes/rust/kebab-architecture.md#L668-L670 retrieval trace query "Markdown chunking 규칙은?" @@ -87,8 +87,8 @@ retrieval trace threshold (gate) 0.30 → top-1 0.82 pass fusion rrf (k=60) chunks (used) 3 / 8 returned - #1 0.82 notes/rust/kb-architecture.md#L661-L672 bm25=12.4 vec=0.78 - #2 0.78 notes/rust/kb-architecture.md#L692-L713 bm25=10.1 vec=0.74 + #1 0.82 notes/rust/kebab-architecture.md#L661-L672 bm25=12.4 vec=0.78 + #2 0.78 notes/rust/kebab-architecture.md#L692-L713 bm25=10.1 vec=0.74 #3 0.55 guides/markdown-style.md#L4-L18 bm25=8.2 vec=0.61 grounded ✓ qwen2.5:14b-instruct rag-v1 3 chunks @@ -96,10 +96,10 @@ prompt 1184 tokens completion 312 tokens latency 1842 ms embedding multilingual-e5-small index v1.0 ``` -### 1.3 `kb ask` (refusal — score gate) +### 1.3 `kebab ask` (refusal — score gate) ```text -$ kb ask "당신의 회사 매출은?" +$ kebab ask "당신의 회사 매출은?" 근거 부족. KB 에 해당 내용 없음. 가까운 후보 (모두 임계 0.30 미만): @@ -108,10 +108,10 @@ $ kb ask "당신의 회사 매출은?" grounded ✗ qwen2.5:14b-instruct rag-v1 0 chunks used ``` -### 1.4 `kb ask` (refusal — LLM self-judge) +### 1.4 `kebab ask` (refusal — LLM self-judge) ```text -$ kb ask "이 책의 23쪽 결론은?" +$ kebab ask "이 책의 23쪽 결론은?" 근거 부족. 제공된 chunk 중 결론 내용 없음. 검색은 됨, LLM 이 결론 부재 판단: @@ -121,17 +121,17 @@ $ kb ask "이 책의 23쪽 결론은?" grounded ✗ qwen2.5:14b-instruct rag-v1 3 chunks searched, 0 grounded ``` -### 1.5 `kb search` (dense) +### 1.5 `kebab search` (dense) ```text -$ kb search "Markdown chunking 규칙" +$ kebab search "Markdown chunking 규칙" -1. 0.82 notes/rust/kb-architecture.md#L661-L672 +1. 0.82 notes/rust/kebab-architecture.md#L661-L672 §14 Chunking 정책 heading boundary 우선. code block 중간 분할 금지. table 가능한 한 단일 chunk… -2. 0.71 notes/rust/kb-architecture.md#L692-L713 +2. 0.71 notes/rust/kebab-architecture.md#L692-L713 §15 검색과 RAG 정책 검색은 처음부터 hybrid 로 설계하되 구현은 단계적… @@ -142,7 +142,7 @@ $ kb search "Markdown chunking 규칙" 3 hits hybrid index v1.0 bm25+e5-small/RRF ``` -### 1.6 `kb search --explain` +### 1.6 `kebab search --explain` 각 hit 아래 추가: @@ -174,8 +174,8 @@ $ kb search "Markdown chunking 규칙" { "schema_version": "citation.v1", "kind": "line|page|region|caption|time", - "path": "notes/rust/kb.md", - "uri": "notes/rust/kb.md#L12-L34", + "path": "notes/rust/kebab.md", + "uri": "notes/rust/kebab.md#L12-L34", "line": { "start": 12, "end": 34, "section": "§14 Chunking 정책" }, "page": { "page": 13, "section": "Experiment Setup" }, @@ -196,7 +196,7 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 "score": 0.82, "chunk_id": "9b4a8c1e7d3f2a05", "doc_id": "3f9a2c10ee4d6b78", - "doc_path": "notes/rust/kb-architecture.md", + "doc_path": "notes/rust/kebab-architecture.md", "heading_path": ["아키텍처", "Chunking 정책"], "section_label": "§14 Chunking 정책", "snippet": "heading boundary 우선. code block 중간 분할 금지…", @@ -261,7 +261,7 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 { "kind": "new|updated|skipped|error", "doc_id": "3f9a2c10ee4d6b78", - "doc_path": "notes/rust/kb-architecture.md", + "doc_path": "notes/rust/kebab-architecture.md", "asset_id": "8c1e7d3f2a05", "byte_len": 41822, "block_count": 184, @@ -277,13 +277,13 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 `--summary-only` 시 `items: null`. -### 2.5 DocSummary (`kb list docs`) +### 2.5 DocSummary (`kebab list docs`) ```json { "schema_version": "doc_summary.v1", "doc_id": "3f9a2c10ee4d6b78", - "doc_path": "notes/rust/kb-architecture.md", + "doc_path": "notes/rust/kebab-architecture.md", "title": "Rust 로컬 Knowledge Base 설계", "lang": "ko", "tags": ["knowledge-base", "rust", "rag"], @@ -305,7 +305,7 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 "schema_version": "chunk_inspection.v1", "chunk_id": "9b4a8c1e7d3f2a05", "doc_id": "3f9a2c10ee4d6b78", - "doc_path": "notes/rust/kb-architecture.md", + "doc_path": "notes/rust/kebab-architecture.md", "heading_path": ["아키텍처", "Chunking 정책"], "text": "heading boundary 우선…", "source_spans": [{ "kind": "line", "start": 661, "end": 672 }], @@ -325,9 +325,9 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 "schema_version": "doctor.v1", "ok": true, "checks": [ - { "name": "config_loaded", "ok": true, "detail": "~/.config/kb/config.toml" }, - { "name": "data_dir_writable", "ok": true, "detail": "~/.local/share/kb" }, - { "name": "sqlite_open", "ok": true, "detail": "kb.sqlite (schema v1)" }, + { "name": "config_loaded", "ok": true, "detail": "~/.config/kebab/config.toml" }, + { "name": "data_dir_writable", "ok": true, "detail": "~/.local/share/kebab" }, + { "name": "sqlite_open", "ok": true, "detail": "kebab.sqlite (schema v1)" }, { "name": "lancedb_open", "ok": true, "detail": "lancedb/" }, { "name": "embedding_model", "ok": true, "detail": "multilingual-e5-small (384d)" }, { "name": "ollama_reachable", "ok": true, "detail": "http://127.0.0.1:11434" }, @@ -346,7 +346,7 @@ variant 별 해당 키만 채움. `path` 와 `uri` 는 항상 채움 (`uri` 는 --- -## 3. 도메인 모델 (kb-core) +## 3. 도메인 모델 (kebab-core) ### 3.1 Newtype IDs @@ -581,7 +581,7 @@ pub struct RetrievalDetail { ### 3.7a Forward-declared types -`Block::ImageRef` / `AudioRef` variant 은 v1 부터 존재하나, 그 안의 `ocr` / `caption` / `transcript` 필드는 P1 에선 항상 `None`. 다음 타입은 `kb-core` 에 stub 으로 둠 (최종 도메인 모델 슬롯): +`Block::ImageRef` / `AudioRef` variant 은 v1 부터 존재하나, 그 안의 `ocr` / `caption` / `transcript` 필드는 P1 에선 항상 `None`. 다음 타입은 `kebab-core` 에 stub 으로 둠 (최종 도메인 모델 슬롯): ```rust pub struct OcrText { pub joined: String, pub regions: Vec, pub engine: String, pub engine_version: String } @@ -596,41 +596,41 @@ pub enum ImageType { Png, Jpeg, Webp, Gif, Tiff, Other(String) } pub enum AudioType { M4a, Mp3, Wav, Flac, Ogg, Other(String) } ``` -`ExtractConfig`, `DocFilter`, `JobKind`, `JobStatus`, `JobFilter`, `JobRow`, `JobId`, `VectorRecord`, `VectorHit`, `RefusalSignal`, `NoHitSignal`, `DoctorUnhealthy` 도 `kb-core` 에 정의 (자세한 필드는 사용 시 결정, 이 spec 에서 forward-ref 만 보장). +`ExtractConfig`, `DocFilter`, `JobKind`, `JobStatus`, `JobFilter`, `JobRow`, `JobId`, `VectorRecord`, `VectorHit`, `RefusalSignal`, `NoHitSignal`, `DoctorUnhealthy` 도 `kebab-core` 에 정의 (자세한 필드는 사용 시 결정, 이 spec 에서 forward-ref 만 보장). `OffsetDateTime` 는 `time::OffsetDateTime`, `Result` 는 crate-local alias. -### 3.7b Parser intermediate types — `kb-parse-types` +### 3.7b Parser intermediate types — `kebab-parse-types` -Parser 의 *중간* 표현 (`ParsedBlock` 류) 은 `kb-core` 가 아니라 별도의 thin crate **`kb-parse-types`** 에 둔다. 이유: `kb-normalize` 는 medium-agnostic 한 ID/Provenance lift 를 책임지고 어떤 parser 도 직접 import 하면 안 된다. 그러나 normalize 에 들어오는 입력 타입이 어딘가에 정의되어야 하는데, 그것을 `kb-core` 에 박으면 (a) parser-별 ParsedBlock 변종 (`ParsedImageRegion`, `ParsedPdfPage`, `ParsedAudioSegment`) 이 향후 합류할 때 core 의 namespace 가 폭발하고, (b) parser 의 의미 변경이 core 변경이 되어 모든 의존자가 영향을 받는다. +Parser 의 *중간* 표현 (`ParsedBlock` 류) 은 `kebab-core` 가 아니라 별도의 thin crate **`kebab-parse-types`** 에 둔다. 이유: `kebab-normalize` 는 medium-agnostic 한 ID/Provenance lift 를 책임지고 어떤 parser 도 직접 import 하면 안 된다. 그러나 normalize 에 들어오는 입력 타입이 어딘가에 정의되어야 하는데, 그것을 `kebab-core` 에 박으면 (a) parser-별 ParsedBlock 변종 (`ParsedImageRegion`, `ParsedPdfPage`, `ParsedAudioSegment`) 이 향후 합류할 때 core 의 namespace 가 폭발하고, (b) parser 의 의미 변경이 core 변경이 되어 모든 의존자가 영향을 받는다. -`kb-parse-types` 는 이 둘 사이의 **유일한 layer** 다. 의존 그래프: +`kebab-parse-types` 는 이 둘 사이의 **유일한 layer** 다. 의존 그래프: ```text -kb-core (도메인 모델 — Block, Chunk, SourceSpan, IDs, …) +kebab-core (도메인 모델 — Block, Chunk, SourceSpan, IDs, …) ▲ │ -kb-parse-types (parser 중간 표현 — ParsedBlock, ParsedImageRegion[P+], ParsedPdfPage[P+], ParsedAudioSegment[P+], Inline) +kebab-parse-types (parser 중간 표현 — ParsedBlock, ParsedImageRegion[P+], ParsedPdfPage[P+], ParsedAudioSegment[P+], Inline) ▲ ▲ │ │ -kb-parse-md, kb-parse-pdf, kb-normalize -kb-parse-image, kb-parse-audio +kebab-parse-md, kebab-parse-pdf, kebab-normalize +kebab-parse-image, kebab-parse-audio ``` -`kb-parse-types` 는: -- `kb-core` 에만 의존 (`Block`, `SourceSpan`, `Lang` 등 도메인 타입 사용). -- 다른 어떤 `kb-*` 에도 의존하지 않는다. +`kebab-parse-types` 는: +- `kebab-core` 에만 의존 (`Block`, `SourceSpan`, `Lang` 등 도메인 타입 사용). +- 다른 어떤 `kebab-*` 에도 의존하지 않는다. - 어떤 parser 의 구체 라이브러리 (`pulldown-cmark`, `pdf-extract`, `image`, `whisper-rs`) 에도 의존하지 않는다. - serde + thiserror 정도의 외부 의존만 가진다. P1 에서 정의되는 타입: ```rust -// kb-parse-types — depends on kb-core only. +// kebab-parse-types — depends on kebab-core only. pub struct ParsedBlock { pub kind: ParsedBlockKind, pub heading_path: Vec, - pub source_span: kb_core::SourceSpan, + pub source_span: kebab_core::SourceSpan, pub payload: ParsedPayload, } @@ -638,11 +638,11 @@ pub enum ParsedBlockKind { Heading, Paragraph, List, Code, Table, Quote, ImageRe pub enum ParsedPayload { Heading { level: u8, text: String }, - Paragraph { text: String, inlines: Vec }, - List { ordered: bool, items: Vec> }, + Paragraph { text: String, inlines: Vec }, + List { ordered: bool, items: Vec> }, Code { lang: Option, code: String }, Table { headers: Vec, rows: Vec> }, - Quote { text: String, inlines: Vec }, + Quote { text: String, inlines: Vec }, ImageRef { src: String, alt: String }, AudioRef { src: String }, // duration_ms filled by extractor before chunking } @@ -651,7 +651,7 @@ pub struct Warning { pub kind: WarningKind, pub note: String } pub enum WarningKind { MalformedFrontmatter, MalformedTable, EncodingFallback, ExtractFailed } ``` -`Inline` 은 `kb-core` (§3.4) 에 있는 도메인 타입. `kb-parse-types` 는 그것을 *참조* 만 한다 — 같은 의미를 두 crate 에 중복 정의하지 않는다 (그러면 normalize 가 identity-conversion 을 해야 해서 무의미). +`Inline` 은 `kebab-core` (§3.4) 에 있는 도메인 타입. `kebab-parse-types` 는 그것을 *참조* 만 한다 — 같은 의미를 두 crate 에 중복 정의하지 않는다 (그러면 normalize 가 identity-conversion 을 해야 해서 무의미). P6/P7/P8 에서 추가될 타입 (forward-ref): @@ -661,7 +661,7 @@ pub struct ParsedPdfPage { pub page: u32, pub text: String } pub struct ParsedAudioSegment { pub start_ms: u64, pub end_ms: u64, pub text: String } ``` -→ 새 medium 추가 시 `kb-core::Block` 변종은 변하지 않고, `kb-parse-types` 만 확장된다. +→ 새 medium 추가 시 `kebab-core::Block` 변종은 변하지 않고, `kebab-parse-types` 만 확장된다. ### 3.8 Answer / RAG types @@ -995,28 +995,28 @@ CREATE TABLE eval_query_results ( | 종류 | 기본 위치 | |------|-----------| | 워크스페이스 | `~/KnowledgeBase/` | -| config | `~/.config/kb/config.toml` | -| data | `~/.local/share/kb/` | -| cache | `~/.cache/kb/` | -| state (logs) | `~/.local/state/kb/` | +| config | `~/.config/kebab/config.toml` | +| data | `~/.local/share/kebab/` | +| cache | `~/.cache/kebab/` | +| state (logs) | `~/.local/state/kebab/` | -`~`, `$HOME`, `${KB_*}` expand. 절대 path 정규화 후 사용. +`~`, `$HOME`, `${KEBAB_*}` expand. 절대 path 정규화 후 사용. ### 6.2 Workspace 구조 ``` ~/KnowledgeBase/ ├── inbox/ notes/ papers/ photos/ recordings/ -└── .kbignore +└── .kebabignore ``` -`.kbignore` 와 `config.workspace.exclude` 합집합. +`.kebabignore` 와 `config.workspace.exclude` 합집합. ### 6.3 Data dir 구조 ``` -~/.local/share/kb/ -├── kb.sqlite (+ -wal, -shm) +~/.local/share/kebab/ +├── kebab.sqlite (+ -wal, -shm) ├── lancedb/ │ └── chunk_embeddings__.lance/ ├── assets// # shard @@ -1025,7 +1025,7 @@ CREATE TABLE eval_query_results ( └── runs// # eval per_query.jsonl + report.md ``` -### 6.4 Config (`~/.config/kb/config.toml`) — frozen schema +### 6.4 Config (`~/.config/kebab/config.toml`) — frozen schema ```toml schema_version = 1 @@ -1036,8 +1036,8 @@ include = ["**/*.md"] exclude = [".git/**", "node_modules/**", ".obsidian/**"] [storage] -data_dir = "${XDG_DATA_HOME:-~/.local/share}/kb" -sqlite = "{data_dir}/kb.sqlite" +data_dir = "${XDG_DATA_HOME:-~/.local/share}/kebab" +sqlite = "{data_dir}/kebab.sqlite" vector_dir = "{data_dir}/lancedb" asset_dir = "{data_dir}/assets" artifact_dir = "{data_dir}/artifacts" @@ -1086,15 +1086,15 @@ max_context_tokens = 8000 config 우선순위: default → file → env (`KB_
_`) → CLI flag. -### 6.5 `kb init` 출력 +### 6.5 `kebab init` 출력 ```text -$ kb init -created ~/.config/kb/config.toml -created ~/.local/share/kb/ +$ kebab init +created ~/.config/kebab/config.toml +created ~/.local/share/kebab/ created ~/KnowledgeBase/ -opened ~/.local/share/kb/kb.sqlite (schema v1) -hint edit ~/.config/kb/config.toml then `kb ingest ~/KnowledgeBase` +opened ~/.local/share/kebab/kebab.sqlite (schema v1) +hint edit ~/.config/kebab/config.toml then `kebab ingest ~/KnowledgeBase` ``` 기존 파일 보존, `--force` 명시 필요. @@ -1107,7 +1107,7 @@ hint edit ~/.config/kb/config.toml then `kb ingest ~/KnowledgeBase` --- -## 7. Trait contracts (kb-core) +## 7. Trait contracts (kebab-core) ### 7.1 입출력 보조 @@ -1210,34 +1210,34 @@ pub trait JobRepo { ## 8. 모듈 경계 (Allowed / Forbidden) ```text -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 (parser intermediate) - ├─> kb-normalize - │ └─> kb-parse-types - ├─> kb-chunk - ├─> kb-store-sqlite (DocumentStore, JobRepo, Retriever[lexical]) - ├─> kb-store-vector (VectorStore) - ├─> kb-embed-local - ├─> kb-search (Retriever[hybrid]) - ├─> kb-llm-local - ├─> kb-rag - ├─> kb-eval - └─> kb-config - └─> kb-core (모두 의존) +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 (parser intermediate) + ├─> kebab-normalize + │ └─> kebab-parse-types + ├─> kebab-chunk + ├─> kebab-store-sqlite (DocumentStore, JobRepo, Retriever[lexical]) + ├─> kebab-store-vector (VectorStore) + ├─> kebab-embed-local + ├─> kebab-search (Retriever[hybrid]) + ├─> kebab-llm-local + ├─> kebab-rag + ├─> kebab-eval + └─> kebab-config + └─> kebab-core (모두 의존) ``` -`kb-parse-types` 는 `kb-core` 와 parsers/normalize 사이의 thin layer (§3.7b 참조). parser-별 중간 표현 (`ParsedBlock`, `ParsedImageRegion`, `ParsedPdfPage`, `ParsedAudioSegment`, `Inline`) 을 한 곳에 모아 (a) `kb-core` 의 namespace 폭발을 막고 (b) `kb-normalize` 가 parser 를 직접 import 하지 않게 한다. +`kebab-parse-types` 는 `kebab-core` 와 parsers/normalize 사이의 thin layer (§3.7b 참조). parser-별 중간 표현 (`ParsedBlock`, `ParsedImageRegion`, `ParsedPdfPage`, `ParsedAudioSegment`, `Inline`) 을 한 곳에 모아 (a) `kebab-core` 의 namespace 폭발을 막고 (b) `kebab-normalize` 가 parser 를 직접 import 하지 않게 한다. 핵심 금지: - UI → store/llm/parse 직접 의존 ✗ - parse-* → store/llm/embed ✗ -- parse-* → kb-normalize ✗ (단방향: parsers → kb-parse-types ← normalize) +- parse-* → kebab-normalize ✗ (단방향: parsers → kebab-parse-types ← normalize) - chunk → llm/embed ✗ - normalize → store / parse-* ✗ -- kb-parse-types → 어떤 parser/normalize/store/llm/embed/search/rag/ui ✗ (`kb-core` 만 의존) +- kebab-parse-types → 어떤 parser/normalize/store/llm/embed/search/rag/ui ✗ (`kebab-core` 만 의존) - 다른 store 와 cross-write ✗ `cargo deny` + workspace deny.toml + CI 체크로 강제. @@ -1270,7 +1270,7 @@ CI: ## 10. 에러 모델 + exit codes ```rust -// kb-core +// kebab-core pub enum CoreError { InvalidId, InvalidCitation, InvalidSpan, Malformed } // crate-local examples pub enum ParseMdError { Yaml(String), Encoding, Pulldown(String), Span } @@ -1278,7 +1278,7 @@ pub enum StoreError { Sqlx(rusqlite::Error), Migration(String), Conflict(String) pub enum LlmError { Unreachable, ModelNotPulled(String), Timeout, Stream(String) } ``` -Boundary (`kb-app`, `kb-cli`) 에서 `anyhow::Error` 합침. exit code 매핑: +Boundary (`kebab-app`, `kebab-cli`) 에서 `anyhow::Error` 합침. exit code 매핑: ```rust fn exit_code(err: &anyhow::Error) -> i32 { @@ -1295,17 +1295,17 @@ fn exit_code(err: &anyhow::Error) -> i32 { | `--verbose` | + anyhow chain | | `--debug` 또는 `RUST_LOG=debug` | + tracing target/level/span | -Refusal 은 에러 아님. `kb ask` 거절은 정상 stdout (Answer with grounded=false) + exit 1. +Refusal 은 에러 아님. `kebab ask` 거절은 정상 stdout (Answer with grounded=false) + exit 1. -Logging: `tracing` + `tracing-subscriber` + `tracing-appender` daily roll, `~/.local/state/kb/logs/`. structured (`trace_id`, `doc_id`, `chunk_id`). +Logging: `tracing` + `tracing-subscriber` + `tracing-appender` daily roll, `~/.local/state/kebab/logs/`. structured (`trace_id`, `doc_id`, `chunk_id`). -`kb doctor` 출력 (사람): +`kebab doctor` 출력 (사람): ```text -$ kb doctor -✓ config_loaded ~/.config/kb/config.toml -✓ data_dir_writable ~/.local/share/kb -✓ sqlite_open kb.sqlite (schema v1) +$ kebab doctor +✓ config_loaded ~/.config/kebab/config.toml +✓ data_dir_writable ~/.local/share/kebab +✓ sqlite_open kebab.sqlite (schema v1) ✓ lancedb_open lancedb/ ✓ embedding_model multilingual-e5-small (384d) ✓ ollama_reachable http://127.0.0.1:11434 @@ -1322,7 +1322,7 @@ $ kb doctor 이 문서가 동결 ↔ 다음 컴포넌트 분해 작업이 안전: - 모든 wire schema (`docs/wire-schema/v1/*.schema.json`) -- 모든 trait 시그니처 (kb-core) +- 모든 trait 시그니처 (kebab-core) - 모든 ID recipe (4.2) - SQLite DDL (5장) - Filesystem + config schema (6장) @@ -1334,7 +1334,7 @@ $ kb doctor **의도적으로 빠진 것 (out of scope, P+)**: - multi-workspace - watch mode -- desktop app `kb://` protocol handler +- desktop app `kebab://` protocol handler - LLM-as-judge eval - visual embedding (CLIP) - real-time collab diff --git a/kebab_local_rust_report.md b/kebab_local_rust_report.md index 3810dac..a53fb8c 100644 --- a/kebab_local_rust_report.md +++ b/kebab_local_rust_report.md @@ -17,11 +17,11 @@ urlcolor: blue 최종 방향은 다음 한 문장으로 요약할 수 있다. -> Markdown을 1등급 지식 소스로 삼고, 이미지, PDF, 음성은 각각 extractor adapter를 통해 동일한 `CanonicalDocument -> Chunk -> Embed -> Index -> Search -> RAG` 파이프라인으로 흘려보낸다. CLI, TUI, desktop app은 모두 같은 `kb-app` facade를 함수 호출로 사용한다. +> Markdown을 1등급 지식 소스로 삼고, 이미지, PDF, 음성은 각각 extractor adapter를 통해 동일한 `CanonicalDocument -> Chunk -> Embed -> Index -> Search -> RAG` 파이프라인으로 흘려보낸다. CLI, TUI, desktop app은 모두 같은 `kebab-app` facade를 함수 호출로 사용한다. 가장 먼저 만들 것은 채팅 UI가 아니다. 먼저 만들어야 할 것은 다음 7가지다. -1. `kb-core`의 도메인 모델과 trait 계약 +1. `kebab-core`의 도메인 모델과 trait 계약 2. deterministic ID 규칙 3. Markdown canonicalization 4. chunking policy @@ -66,16 +66,16 @@ urlcolor: blue Markdown files | v -kb-source-fs +kebab-source-fs | v -kb-parse-md +kebab-parse-md | v CanonicalDocument | v -kb-chunk +kebab-chunk | v Chunks @@ -88,15 +88,15 @@ SQLite metadata/FTS LanceDB vectors Raw asset store +--------------------+--------------------+ | v - kb-search + kebab-search | v - kb-rag + kebab-rag | +---------------+---------------+ | | | v v v - kb-cli kb-tui kb-desktop + kebab-cli kebab-tui kebab-desktop ``` 추후 확장 후 구조는 다음과 같다. @@ -133,23 +133,23 @@ Cargo workspace는 여러 package를 함께 관리하는 구조이며, 공통 `C [workspace] resolver = "3" members = [ - "crates/kb-core", - "crates/kb-config", - "crates/kb-source-fs", - "crates/kb-parse-md", - "crates/kb-normalize", - "crates/kb-chunk", - "crates/kb-store-sqlite", - "crates/kb-store-vector", - "crates/kb-embed", - "crates/kb-embed-local", - "crates/kb-search", - "crates/kb-llm", - "crates/kb-llm-local", - "crates/kb-rag", - "crates/kb-eval", - "crates/kb-app", - "crates/kb-cli" + "crates/kebab-core", + "crates/kebab-config", + "crates/kebab-source-fs", + "crates/kebab-parse-md", + "crates/kebab-normalize", + "crates/kebab-chunk", + "crates/kebab-store-sqlite", + "crates/kebab-store-vector", + "crates/kebab-embed", + "crates/kebab-embed-local", + "crates/kebab-search", + "crates/kebab-llm", + "crates/kebab-llm-local", + "crates/kebab-rag", + "crates/kebab-eval", + "crates/kebab-app", + "crates/kebab-cli" ] [workspace.package] @@ -173,7 +173,7 @@ tracing = "0.1" 초기 repo는 이렇게 잡는다. ```text -kb/ +kebab/ Cargo.toml README.md docs/ @@ -191,70 +191,70 @@ kb/ nested-headings.md code-and-table.md crates/ - kb-core/ - kb-config/ - kb-source-fs/ - kb-parse-md/ - kb-normalize/ - kb-chunk/ - kb-store-sqlite/ - kb-store-vector/ - kb-embed/ - kb-embed-local/ - kb-search/ - kb-llm/ - kb-llm-local/ - kb-rag/ - kb-eval/ - kb-app/ - kb-cli/ + kebab-core/ + kebab-config/ + kebab-source-fs/ + kebab-parse-md/ + kebab-normalize/ + kebab-chunk/ + kebab-store-sqlite/ + kebab-store-vector/ + kebab-embed/ + kebab-embed-local/ + kebab-search/ + kebab-llm/ + kebab-llm-local/ + kebab-rag/ + kebab-eval/ + kebab-app/ + kebab-cli/ ``` 나중에 추가할 crate는 다음과 같다. ```text -crates/kb-parse-image/ -crates/kb-parse-pdf/ -crates/kb-parse-audio/ -crates/kb-rerank/ -crates/kb-tui/ -crates/kb-desktop/ +crates/kebab-parse-image/ +crates/kebab-parse-pdf/ +crates/kebab-parse-audio/ +crates/kebab-rerank/ +crates/kebab-tui/ +crates/kebab-desktop/ ``` 중요한 의존성 규칙은 다음과 같다. ```text -kb-cli, kb-tui, kb-desktop - -> kb-app - -> kb-index / kb-search / kb-rag - -> kb-core traits +kebab-cli, kebab-tui, kebab-desktop + -> kebab-app + -> kebab-index / kebab-search / kebab-rag + -> kebab-core traits -> concrete adapters ``` -UI crate는 절대로 parser, DB, LLM adapter를 직접 호출하지 않는다. 모든 user-facing command는 `kb-app` facade를 통해 호출한다. +UI crate는 절대로 parser, DB, LLM adapter를 직접 호출하지 않는다. 모든 user-facing command는 `kebab-app` facade를 통해 호출한다. # 5. 컴포넌트 목록과 책임 | 컴포넌트 | 책임 | 초기 구현 | |---|---|---| -| `kb-core` | domain type, trait, error, ID 규칙 | 필수 | -| `kb-config` | config 파일 로딩, 기본값, 경로 확장 | 필수 | -| `kb-source-fs` | 로컬 폴더 scan, checksum, 변경 감지 | 필수 | -| `kb-parse-md` | Markdown -> structured document | 필수 | -| `kb-normalize` | parser output -> `CanonicalDocument` | 필수 | -| `kb-chunk` | block-aware chunking | 필수 | -| `kb-store-sqlite` | metadata, document, chunk, job, FTS | 필수 | -| `kb-store-vector` | vector upsert/search | P1 | -| `kb-embed` | embedding trait | P1 | -| `kb-embed-local` | local embedding adapter | P1 | -| `kb-search` | lexical, vector, hybrid retrieval | P1 | -| `kb-llm` | language model trait | P1 | -| `kb-llm-local` | Ollama 또는 llama.cpp adapter | P1 | -| `kb-rag` | context packing, answer, citation | P1 | -| `kb-eval` | golden query, regression test | P1 | -| `kb-cli` | command line interface | 필수 | -| `kb-tui` | terminal UI | P2 | -| `kb-desktop` | desktop app | P3 | +| `kebab-core` | domain type, trait, error, ID 규칙 | 필수 | +| `kebab-config` | config 파일 로딩, 기본값, 경로 확장 | 필수 | +| `kebab-source-fs` | 로컬 폴더 scan, checksum, 변경 감지 | 필수 | +| `kebab-parse-md` | Markdown -> structured document | 필수 | +| `kebab-normalize` | parser output -> `CanonicalDocument` | 필수 | +| `kebab-chunk` | block-aware chunking | 필수 | +| `kebab-store-sqlite` | metadata, document, chunk, job, FTS | 필수 | +| `kebab-store-vector` | vector upsert/search | P1 | +| `kebab-embed` | embedding trait | P1 | +| `kebab-embed-local` | local embedding adapter | P1 | +| `kebab-search` | lexical, vector, hybrid retrieval | P1 | +| `kebab-llm` | language model trait | P1 | +| `kebab-llm-local` | Ollama 또는 llama.cpp adapter | P1 | +| `kebab-rag` | context packing, answer, citation | P1 | +| `kebab-eval` | golden query, regression test | P1 | +| `kebab-cli` | command line interface | 필수 | +| `kebab-tui` | terminal UI | P2 | +| `kebab-desktop` | desktop app | P3 | # 6. 핵심 도메인 모델 @@ -398,10 +398,10 @@ Markdown frontmatter 기본 규약은 다음 정도로 시작한다. ```yaml --- -id: rust-kb-architecture +id: rust-kebab-architecture title: Rust 로컬 Knowledge Base 설계 aliases: - - local kb + - local kebab - rust rag tags: - knowledge-base @@ -418,7 +418,7 @@ lang: ko Markdown citation은 line range를 기본으로 한다. ```text -notes/rust/kb.md:L12-L34 +notes/rust/kebab.md:L12-L34 ``` # 9. 이미지, PDF, 음성 확장 전략 @@ -534,13 +534,13 @@ Ollama 문서는 macOS Sonoma 이상에서 Apple M series CPU/GPU support를 언 초기 adapter는 다음처럼 둔다. ```text -kb-llm-local +kebab-llm-local - OllamaLanguageModel - later: LlamaCppLanguageModel - later: CandleLanguageModel ``` -Ollama가 내부적으로 local server를 쓰더라도, 프로젝트 아키텍처 관점에서는 HTTP MSA가 아니다. `kb-llm-local` 안에 캡슐화된 model adapter일 뿐이다. +Ollama가 내부적으로 local server를 쓰더라도, 프로젝트 아키텍처 관점에서는 HTTP MSA가 아니다. `kebab-llm-local` 안에 캡슐화된 model adapter일 뿐이다. ## 11.3 Local embedding @@ -583,10 +583,10 @@ M4 48GB MacBook은 개인용 local KB에 충분한 타겟이지만, indexing과 root = "~/KnowledgeBase" [storage] -sqlite_path = "~/.local/share/kb/kb.sqlite" -vector_path = "~/.local/share/kb/lancedb" -raw_asset_path = "~/.local/share/kb/assets" -artifact_path = "~/.local/share/kb/artifacts" +sqlite_path = "~/.local/share/kebab/kebab.sqlite" +vector_path = "~/.local/share/kebab/lancedb" +raw_asset_path = "~/.local/share/kebab/assets" +artifact_path = "~/.local/share/kebab/artifacts" [indexing] max_parallel_extractors = 2 @@ -609,7 +609,7 @@ context_tokens = 32768 # 13. Trait 계약 -컴포넌트는 trait으로 연결한다. 아래 계약을 `kb-core`에 둔다. +컴포넌트는 trait으로 연결한다. 아래 계약을 `kebab-core`에 둔다. ```rust pub trait SourceConnector { @@ -741,14 +741,14 @@ pub struct Answer { CLI는 가장 먼저 만든다. ```text -kb init -kb ingest -kb index -kb search -kb ask -kb inspect doc -kb inspect chunk -kb doctor +kebab init +kebab ingest +kebab index +kebab search +kebab ask +kebab inspect doc +kebab inspect chunk +kebab doctor ``` CLI는 개발과 테스트의 기준점이다. TUI와 desktop app은 CLI 기능이 안정된 뒤 붙인다. @@ -791,10 +791,10 @@ Desktop app은 가장 나중에 만든다. 이유는 UI보다 먼저 domain mode 산출물: ```text -kb-core -kb-config -kb-app -kb-cli +kebab-core +kebab-config +kebab-app +kebab-cli docs/spec/* fixtures/markdown/* ``` @@ -804,7 +804,7 @@ fixtures/markdown/* ```text cargo check --workspace cargo test --workspace -kb --help +kebab --help ``` ## Phase 1 - Markdown ingestion @@ -814,19 +814,19 @@ kb --help 구현 crate: ```text -kb-source-fs -kb-parse-md -kb-normalize -kb-chunk -kb-store-sqlite +kebab-source-fs +kebab-parse-md +kebab-normalize +kebab-chunk +kebab-store-sqlite ``` 완료 조건: ```text -kb ingest ~/KnowledgeBase -kb list docs -kb inspect doc +kebab ingest ~/KnowledgeBase +kebab list docs +kebab inspect doc ``` ## Phase 2 - Lexical search @@ -836,14 +836,14 @@ kb inspect doc 완료 조건: ```text -kb search "Rust workspace 설계" +kebab search "Rust workspace 설계" ``` 결과는 citation을 포함해야 한다. ```text 1. Rust workspace는 여러 package를 하나로 관리한다... - source: notes/rust/kb.md:L12-L34 + source: notes/rust/kebab.md:L12-L34 ``` ## Phase 3 - Vector search와 embedding @@ -853,18 +853,18 @@ kb search "Rust workspace 설계" 구현 crate: ```text -kb-embed -kb-embed-local -kb-store-vector -kb-search +kebab-embed +kebab-embed-local +kebab-store-vector +kebab-search ``` 완료 조건: ```text -kb index --embeddings -kb search --mode vector "비슷한 설계 원칙" -kb search --mode hybrid "Markdown chunking 규칙" +kebab index --embeddings +kebab search --mode vector "비슷한 설계 원칙" +kebab search --mode hybrid "Markdown chunking 규칙" ``` ## Phase 4 - Local LLM RAG @@ -874,15 +874,15 @@ kb search --mode hybrid "Markdown chunking 규칙" 구현 crate: ```text -kb-llm -kb-llm-local -kb-rag +kebab-llm +kebab-llm-local +kebab-rag ``` 완료 조건: ```text -kb ask "내 KB 설계에서 저장소 전략은?" +kebab ask "내 KB 설계에서 저장소 전략은?" ``` 답변은 citation을 포함해야 하며, 근거가 없으면 거절해야 한다. @@ -895,7 +895,7 @@ kb ask "내 KB 설계에서 저장소 전략은?" ```text fixtures/golden_queries.yaml -kb-eval +kebab-eval ``` 측정값: @@ -915,8 +915,8 @@ answer groundedness 완료 조건: ```text -kb ingest ./assets/diagram.png -kb search "이미지 안의 OCR 텍스트" +kebab ingest ./assets/diagram.png +kebab search "이미지 안의 OCR 텍스트" ``` ## Phase 7 - PDF support @@ -926,8 +926,8 @@ kb search "이미지 안의 OCR 텍스트" 완료 조건: ```text -kb ingest ./paper.pdf -kb search "PDF 안의 특정 개념" +kebab ingest ./paper.pdf +kebab search "PDF 안의 특정 개념" ``` ## Phase 8 - 음성 support @@ -937,8 +937,8 @@ kb search "PDF 안의 특정 개념" 완료 조건: ```text -kb ingest ./meeting.m4a -kb search "회의에서 언급한 결정사항" +kebab ingest ./meeting.m4a +kebab search "회의에서 언급한 결정사항" ``` ## Phase 9 - TUI와 desktop app @@ -948,7 +948,7 @@ kb search "회의에서 언급한 결정사항" 순서: ```text -kb-tui -> kb-desktop +kebab-tui -> kebab-desktop ``` # 18. 테스트 전략 @@ -986,7 +986,7 @@ AI에게 “전체 repo를 만들어줘”라고 시키지 말고, component spe 템플릿은 다음과 같다. ```text -Component: kb-parse-md +Component: kebab-parse-md Responsibility: - Markdown bytes를 CanonicalDocument로 변환한다. @@ -994,17 +994,17 @@ Responsibility: - line range 또는 byte range를 최대한 보존한다. Allowed dependencies: -- kb-core +- kebab-core - pulldown-cmark 또는 comrak - serde - thiserror Forbidden dependencies: -- kb-store -- kb-llm -- kb-rag -- kb-tui -- kb-desktop +- kebab-store +- kebab-llm +- kebab-rag +- kebab-tui +- kebab-desktop Inputs: - RawAsset @@ -1052,7 +1052,7 @@ Non-goals: ## 1-2일차 - workspace 생성 -- `kb-core` 도메인 타입 초안 +- `kebab-core` 도메인 타입 초안 - ID 규칙 문서화 - CLI skeleton @@ -1073,7 +1073,7 @@ Non-goals: - SQLite FTS5 검색 - citation 출력 -- `kb inspect` 구현 +- `kebab inspect` 구현 ## 12-14일차 @@ -1100,7 +1100,7 @@ MVP 완료 조건은 다음과 같다. - [ ] parser/chunker version을 바꾸면 재처리 대상이 식별된다. - [ ] local embedding을 붙일 수 있는 trait이 있다. - [ ] local LLM을 붙일 수 있는 trait이 있다. -- [ ] `kb-app` facade를 통해 CLI가 동작한다. +- [ ] `kebab-app` facade를 통해 CLI가 동작한다. P1 완료 조건은 다음과 같다. diff --git a/tasks/HOTFIXES.md b/tasks/HOTFIXES.md index ca853e4..9e63261 100644 --- a/tasks/HOTFIXES.md +++ b/tasks/HOTFIXES.md @@ -14,30 +14,30 @@ historical contract that was implemented; this file accumulates the deltas so phase 5+ readers can find the live behavior without diffing git history. -## 2026-05-01 — `--config` flag silently ignored across all kb-cli subcommands +## 2026-05-01 — `--config` flag silently ignored across all kebab-cli subcommands -**Discovered**: post-P3-5 manual smoke at `/tmp/kb-smoke/`. +**Discovered**: post-P3-5 manual smoke at `/tmp/kebab-smoke/`. -**Symptom**: `kb --config /path/to/config.toml ingest|search|list|inspect|doctor` ignored the flag and fell back to `~/.config/kb/config.toml` (XDG default). Users had to use `KB_*` env vars to point at a non-default config. +**Symptom**: `kebab --config /path/to/config.toml ingest|search|list|inspect|doctor` ignored the flag and fell back to `~/.config/kebab/config.toml` (XDG default). Users had to use `KEBAB_*` env vars to point at a non-default config. -**Root cause**: `kb-cli` read `cli.config` only inside `Cmd::Ingest` to build `SourceScope`, then called bare `kb_app::ingest(scope, summary_only)` which internally re-loaded `Config::load(None)` (XDG path). Same pattern in `Cmd::Search` / `List` / `Inspect` / `Doctor`. P3-5 introduced `*_with_config` test seams via `#[doc(hidden)] pub fn` but kb-cli never used them. +**Root cause**: `kebab-cli` read `cli.config` only inside `Cmd::Ingest` to build `SourceScope`, then called bare `kebab_app::ingest(scope, summary_only)` which internally re-loaded `Config::load(None)` (XDG path). Same pattern in `Cmd::Search` / `List` / `Inspect` / `Doctor`. P3-5 introduced `*_with_config` test seams via `#[doc(hidden)] pub fn` but kebab-cli never used them. **Fix** (PR #20, fix/cli-config-flag-and-search-output): -- `kb-cli` now builds the Config once via `Config::load(cli.config.as_deref())` at the top of every subcommand and threads it into `kb_app::*_with_config(cfg, ...)` instead of `kb_app::*(...)`. -- `kb_app::doctor()` rewritten as `doctor_with_config_path(Option<&Path>)` that reports the actual path probed and hard-fails when `--config ` doesn't exist (defaults would otherwise mask user intent). -- `kb-app` module doc-comment updated: `#[doc(hidden)] pub fn *_with_config` is no longer "test-only seam" — it's the official "config-explicit" API consumed by CLI `--config`, integration tests, and TUI sessions. -- Same PR also improved `kb search` printer: `{:.4}` score formatting (RRF range collapses on `{:.2}`) and `> heading_path` suffix so chunks from the same document are visually distinct. +- `kebab-cli` now builds the Config once via `Config::load(cli.config.as_deref())` at the top of every subcommand and threads it into `kebab_app::*_with_config(cfg, ...)` instead of `kebab_app::*(...)`. +- `kebab_app::doctor()` rewritten as `doctor_with_config_path(Option<&Path>)` that reports the actual path probed and hard-fails when `--config ` doesn't exist (defaults would otherwise mask user intent). +- `kebab-app` module doc-comment updated: `#[doc(hidden)] pub fn *_with_config` is no longer "test-only seam" — it's the official "config-explicit" API consumed by CLI `--config`, integration tests, and TUI sessions. +- Same PR also improved `kebab search` printer: `{:.4}` score formatting (RRF range collapses on `{:.2}`) and `> heading_path` suffix so chunks from the same document are visually distinct. **Amends**: tasks/p3/p3-5-app-wiring.md (the test seam was always meant to be the config-explicit API; only the doc-comment lied). -### 2026-05-01 — `--config` regression in `kb ask` (P4-3 follow-up) +### 2026-05-01 — `--config` regression in `kebab ask` (P4-3 follow-up) **Discovered**: post-P4-3 manual smoke against 192.168.0.47 Ollama with `gemma4:26b`. -**Symptom**: `kb --config ask` returned `model.id = qwen2.5:14b-instruct` (XDG default model) and `score_gate = 0.30` (XDG default), instead of `gemma4:26b` / `0.05` from the explicit config. P4-3 added the ask body but kb-cli's `Cmd::Ask` arm still called bare `kb_app::ask(query, opts)` — same regression class as the P3-5 fix above, just missed when ask was wired. +**Symptom**: `kebab --config ask` returned `model.id = qwen2.5:14b-instruct` (XDG default model) and `score_gate = 0.30` (XDG default), instead of `gemma4:26b` / `0.05` from the explicit config. P4-3 added the ask body but kebab-cli's `Cmd::Ask` arm still called bare `kebab_app::ask(query, opts)` — same regression class as the P3-5 fix above, just missed when ask was wired. **Fix** (PR #24, fix/cli-ask-honor-config-flag): -- `kb-cli` builds `Config::load(cli.config.as_deref())` once at the top of `Cmd::Ask` and calls `kb_app::ask_with_config(cfg, query, opts)`. +- `kebab-cli` builds `Config::load(cli.config.as_deref())` once at the top of `Cmd::Ask` and calls `kebab_app::ask_with_config(cfg, query, opts)`. **Amends**: tasks/p4/p4-3-rag-pipeline.md. @@ -48,15 +48,15 @@ git history. **Root cause**: RRF formula `score(c) = Σ 1/(k_rrf + rank_m(c))` produces values bounded by `num_retrievers / (k_rrf + 1)`. With `num_retrievers = 2` and the default `k_rrf = 60`, the upper bound is `2/61 ≈ 0.0328`. The default `config.rag.score_gate = 0.05` was calibrated for vector / lexical scores already in `[0, 1]` and silently refused every hybrid query. `fusion_score` was also incomparable across modes — Lexical / Vector lived in `[0, 1]`, Hybrid lived in `(0, 0.033]`. **Fix** (PR #25, fix/rrf-fusion-score-normalize-and-docs): -- `crates/kb-search/src/hybrid.rs` divides every raw RRF score by `2 / (k_rrf + 1)` so `fusion_score` always lives in `[0, 1]` regardless of mode. Both retrievers contributing rank 1 normalises to `1.0`; chunks present in only one retriever cap around `0.5`. RRF's rank-ordering invariants are preserved (same constant divides every score), so sort + tiebreak behaviour is identical. +- `crates/kebab-search/src/hybrid.rs` divides every raw RRF score by `2 / (k_rrf + 1)` so `fusion_score` always lives in `[0, 1]` regardless of mode. Both retrievers contributing rank 1 normalises to `1.0`; chunks present in only one retriever cap around `0.5`. RRF's rank-ordering invariants are preserved (same constant divides every score), so sort + tiebreak behaviour is identical. - One unit test (`rrf_formula_matches_known_value`) updated to expect the normalised value `(1/61 + 1/62) / (2/61) ≈ 0.9919`. -- The integration snapshot `crates/kb-search/tests/fixtures/search/hybrid/run-1.json` already used presence checks (`fusion_score_positive: true`) rather than absolute values, so it didn't need regeneration. +- The integration snapshot `crates/kebab-search/tests/fixtures/search/hybrid/run-1.json` already used presence checks (`fusion_score_positive: true`) rather than absolute values, so it didn't need regeneration. **Why not a per-mode `score_gate` config**: separate `lexical_score_gate / vector_score_gate / hybrid_score_gate` would force every downstream consumer (CLI, eval, TUI) to know which mode picks which threshold. Normalising the score itself is a one-line change at the source and makes `Answer.retrieval.score_gate` semantically meaningful without per-mode bookkeeping. **Amends**: tasks/p3/p3-4-hybrid-fusion.md (RRF formula now divides by `2/(k_rrf+1)` after summation), tasks/phase-3-vector-hybrid.md (RRF section). -**Verification**: post-fix smoke at `/tmp/kb-smoke/` with default `score_gate = 0.05` succeeded across four scenarios — Korean→Korean, English→English, cross-language, and out-of-corpus refusal. +**Verification**: post-fix smoke at `/tmp/kebab-smoke/` with default `score_gate = 0.05` succeeded across four scenarios — Korean→Korean, English→English, cross-language, and out-of-corpus refusal. ## How to add an entry diff --git a/tasks/INDEX.md b/tasks/INDEX.md index 998ba36..723f065 100644 --- a/tasks/INDEX.md +++ b/tasks/INDEX.md @@ -1,12 +1,12 @@ --- title: "KB 작업 단위 인덱스" -source: kb_local_rust_report.md +source: kebab_local_rust_report.md date: 2026-04-27 --- # KB 작업 단위 인덱스 -[`kb_local_rust_report.md`](../kb_local_rust_report.md) 의 Phase 로드맵을 아키텍처 수준 작업 단위로 분해. 각 task 문서는 독립적으로 착수/검수 가능한 단위. +[`kebab_local_rust_report.md`](../kebab_local_rust_report.md) 의 Phase 로드맵을 아키텍처 수준 작업 단위로 분해. 각 task 문서는 독립적으로 착수/검수 가능한 단위. ## 의존 그래프 @@ -25,16 +25,16 @@ P0~P5 는 직렬. P6~P9 는 P5 이후 병렬 가능. | # | 코드 | 제목 | 핵심 산출 crate | 선행 | |---|------|------|----------------|------| -| P0 | [phase-0-skeleton.md](phase-0-skeleton.md) | Workspace 뼈대 + 도메인 계약 | kb-core, kb-parse-types, kb-config, kb-app, kb-cli | – | -| P1 | [phase-1-markdown-ingestion.md](phase-1-markdown-ingestion.md) | Markdown ingestion 파이프라인 | kb-source-fs, kb-parse-md, kb-normalize, kb-chunk, kb-store-sqlite | P0 | -| P2 | [phase-2-lexical-search.md](phase-2-lexical-search.md) | SQLite FTS5 lexical 검색 + citation | kb-search (lexical) | P1 | -| P3 | [phase-3-vector-hybrid.md](phase-3-vector-hybrid.md) | Local embedding + LanceDB + hybrid | kb-embed, kb-embed-local, kb-store-vector, kb-search | P2 | -| P4 | [phase-4-local-llm-rag.md](phase-4-local-llm-rag.md) | Local LLM + RAG + grounded answer | kb-llm, kb-llm-local, kb-rag | P3 | -| P5 | [phase-5-evaluation.md](phase-5-evaluation.md) | Golden query / regression eval | kb-eval | P4 | -| P6 | [phase-6-image.md](phase-6-image.md) | 이미지 ingestion (OCR + caption) | kb-parse-image | P5 | -| P7 | [phase-7-pdf.md](phase-7-pdf.md) | PDF text + page citation | kb-parse-pdf | P5 | -| P8 | [phase-8-audio.md](phase-8-audio.md) | 음성 transcription + timestamp citation | kb-parse-audio | P5 | -| P9 | [phase-9-ui.md](phase-9-ui.md) | TUI + desktop app | kb-tui, kb-desktop | P5 | +| P0 | [phase-0-skeleton.md](phase-0-skeleton.md) | Workspace 뼈대 + 도메인 계약 | kebab-core, kebab-parse-types, kebab-config, kebab-app, kebab-cli | – | +| P1 | [phase-1-markdown-ingestion.md](phase-1-markdown-ingestion.md) | Markdown ingestion 파이프라인 | kebab-source-fs, kebab-parse-md, kebab-normalize, kebab-chunk, kebab-store-sqlite | P0 | +| P2 | [phase-2-lexical-search.md](phase-2-lexical-search.md) | SQLite FTS5 lexical 검색 + citation | kebab-search (lexical) | P1 | +| P3 | [phase-3-vector-hybrid.md](phase-3-vector-hybrid.md) | Local embedding + LanceDB + hybrid | kebab-embed, kebab-embed-local, kebab-store-vector, kebab-search | P2 | +| P4 | [phase-4-local-llm-rag.md](phase-4-local-llm-rag.md) | Local LLM + RAG + grounded answer | kebab-llm, kebab-llm-local, kebab-rag | P3 | +| P5 | [phase-5-evaluation.md](phase-5-evaluation.md) | Golden query / regression eval | kebab-eval | P4 | +| P6 | [phase-6-image.md](phase-6-image.md) | 이미지 ingestion (OCR + caption) | kebab-parse-image | P5 | +| P7 | [phase-7-pdf.md](phase-7-pdf.md) | PDF text + page citation | kebab-parse-pdf | P5 | +| P8 | [phase-8-audio.md](phase-8-audio.md) | 음성 transcription + timestamp citation | kebab-parse-audio | P5 | +| P9 | [phase-9-ui.md](phase-9-ui.md) | TUI + desktop app | kebab-tui, kebab-desktop | P5 | ## Component task decomposition (per phase) diff --git a/tasks/_template.md b/tasks/_template.md index 6b1c863..d08ff76 100644 --- a/tasks/_template.md +++ b/tasks/_template.md @@ -6,7 +6,7 @@ title: "" status: planned depends_on: [] # other task_ids unblocks: [] # other task_ids -contract_source: ../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [] # e.g. [§3.5, §5.5, §7.2] --- @@ -22,7 +22,7 @@ contract_sections: [] # e.g. [§3.5, §5.5, §7.2] ## Allowed dependencies -- `kb-core` +- `kebab-core` - - @@ -71,7 +71,7 @@ If any item here is needed during implementation, STOP and update the frozen des | unit | ... | ... | | snapshot | ... (JSON freeze) | `fixtures/...` | | contract | trait round-trip | mock impls | -| integration | end-to-end via `kb-app` facade | tmp workspace | +| integration | end-to-end via `kebab-app` facade | tmp workspace | All tests must run under `cargo test -p ` and not require external network or Ollama unless explicitly stated. diff --git a/tasks/p0/p0-1-skeleton.md b/tasks/p0/p0-1-skeleton.md index 15b5665..9726aa4 100644 --- a/tasks/p0/p0-1-skeleton.md +++ b/tasks/p0/p0-1-skeleton.md @@ -1,12 +1,12 @@ --- phase: P0 -component: workspace + kb-core + kb-config + kb-app + kb-cli +component: workspace + kebab-core + kebab-config + kebab-app + kebab-cli task_id: p0-1 title: "Workspace skeleton + frozen domain types/traits + ID recipe + facade" status: completed depends_on: [] unblocks: [p1-1, p1-2, p1-3, p1-4, p1-5, p1-6, p2-1, p2-2, p3-1, p3-2, p3-3, p3-4, p4-1, p4-2, p4-3, p5-1, p5-2, p6-1, p6-2, p6-3, p7-1, p7-2, p8-1, p8-2, p9-1, p9-2, p9-3, p9-4, p9-5] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3 (all), §4, §5.1 schema_meta+migrations, §6 (config + XDG), §7 (all traits), §8 module boundaries, §9 versioning, §10 errors+exit codes, §2.8 wire schema_version] --- @@ -14,56 +14,56 @@ contract_sections: [§3 (all), §4, §5.1 schema_meta+migrations, §6 (config + ## Goal -Stand up the Cargo workspace (Rust 2024, resolver=3) with `kb-core`, `kb-parse-types`, `kb-config`, `kb-app`, `kb-cli` crates. Freeze every domain type, trait, ID recipe, error type, and CLI entry shape per the frozen design doc so that all subsequent component tasks compile against stable contracts. +Stand up the Cargo workspace (Rust 2024, resolver=3) with `kebab-core`, `kebab-parse-types`, `kebab-config`, `kebab-app`, `kebab-cli` crates. Freeze every domain type, trait, ID recipe, error type, and CLI entry shape per the frozen design doc so that all subsequent component tasks compile against stable contracts. ## Why now / why this size -Every other task imports `kb-core`. If types or trait signatures wobble after this point, every downstream task spec drifts. This task is large but indivisible: types + traits + ID recipe + facade + CLI skeleton + wire schema stubs must land together so the rest of the workspace can compile against them. +Every other task imports `kebab-core`. If types or trait signatures wobble after this point, every downstream task spec drifts. This task is large but indivisible: types + traits + ID recipe + facade + CLI skeleton + wire schema stubs must land together so the rest of the workspace can compile against them. ## Allowed dependencies - workspace `[workspace.dependencies]`: `anyhow = "1"`, `thiserror = "2"`, `serde = { version = "1", features = ["derive"] }`, `serde_json = "1"`, `time = { version = "0.3", features = ["serde", "macros"] }`, `uuid = { version = "1", features = ["v7", "serde"] }`, `blake3 = "1"`, `tracing = "0.1"` - per crate: - - `kb-core`: workspace deps + `serde_json::Map`, `serde-json-canonicalizer`, `unicode-normalization` - - `kb-parse-types`: workspace deps + `kb-core` ONLY (no parsers, no stores, no normalize). Defines parser intermediate representations per design §3.7b. - - `kb-config`: workspace deps + `toml = "0.8"`, `dirs = "5"` (XDG paths) - - `kb-app`: workspace deps + `kb-core`, `kb-config`, `tracing-subscriber`, `tracing-appender` - - `kb-cli`: workspace deps + `kb-core`, `kb-config`, `kb-app`, `clap = { version = "4", features = ["derive"] }` + - `kebab-core`: workspace deps + `serde_json::Map`, `serde-json-canonicalizer`, `unicode-normalization` + - `kebab-parse-types`: workspace deps + `kebab-core` ONLY (no parsers, no stores, no normalize). Defines parser intermediate representations per design §3.7b. + - `kebab-config`: workspace deps + `toml = "0.8"`, `dirs = "5"` (XDG paths) + - `kebab-app`: workspace deps + `kebab-core`, `kebab-config`, `tracing-subscriber`, `tracing-appender` + - `kebab-cli`: workspace deps + `kebab-core`, `kebab-config`, `kebab-app`, `clap = { version = "4", features = ["derive"] }` ## Forbidden dependencies -- `kb-core` MUST NOT depend on any other `kb-*` crate. -- `kb-parse-types` MUST depend ONLY on `kb-core`. No parser libraries (`pulldown-cmark`, `pdf-extract`, `image`, `whisper-rs`, …), no other `kb-*` crate. -- `kb-config` MUST NOT depend on `kb-app`, `kb-cli`, parsers, stores, embedders, search, llm, rag, tui, desktop. -- `kb-app` MUST NOT yet depend on parsers/stores/embedders/search/llm/rag (those crates do not exist yet — facade methods stub out and return `unimplemented!()` or `anyhow::bail!("not yet wired (Pn-i)")`). -- `kb-cli` MUST NOT call any non-`kb-app` crate directly. +- `kebab-core` MUST NOT depend on any other `kebab-*` crate. +- `kebab-parse-types` MUST depend ONLY on `kebab-core`. No parser libraries (`pulldown-cmark`, `pdf-extract`, `image`, `whisper-rs`, …), no other `kebab-*` crate. +- `kebab-config` MUST NOT depend on `kebab-app`, `kebab-cli`, parsers, stores, embedders, search, llm, rag, tui, desktop. +- `kebab-app` MUST NOT yet depend on parsers/stores/embedders/search/llm/rag (those crates do not exist yet — facade methods stub out and return `unimplemented!()` or `anyhow::bail!("not yet wired (Pn-i)")`). +- `kebab-cli` MUST NOT call any non-`kebab-app` crate directly. ## Inputs | input | type | source | |-------|------|--------| -| frozen design doc | Markdown | `docs/superpowers/specs/2026-04-27-kb-final-form-design.md` | -| user `kb` invocation | command-line args | end user | +| frozen design doc | Markdown | `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` | +| user `kebab` invocation | command-line args | end user | ## Outputs | output | type | downstream consumer | |--------|------|---------------------| | compiling workspace | Rust crates | every later task | -| `kb-core` types/traits | Rust API | every other crate | -| `kb-core` ID functions | Rust API | parsers, normalize, chunkers, embedders, search, rag | -| `kb-config::Config` | Rust struct | every other crate | -| `kb-app` facade methods (stubs) | Rust API | `kb-cli`, future TUI/desktop | -| `kb` binary | executable | end user | +| `kebab-core` types/traits | Rust API | every other crate | +| `kebab-core` ID functions | Rust API | parsers, normalize, chunkers, embedders, search, rag | +| `kebab-config::Config` | Rust struct | every other crate | +| `kebab-app` facade methods (stubs) | Rust API | `kebab-cli`, future TUI/desktop | +| `kebab` binary | executable | end user | | `docs/wire-schema/v1/*.schema.json` stubs | JSON Schema files | future wire emitters and consumers | | `docs/spec/*.md` stubs (link to frozen design) | Markdown | future contributors | ## Public surface (signatures only — no new types) -All types/traits below are defined in `kb-core` exactly per design §3 and §7 (no additions, no renames). Subagent must copy field-for-field. +All types/traits below are defined in `kebab-core` exactly per design §3 and §7 (no additions, no renames). Subagent must copy field-for-field. ```rust -// ── kb-core ───────────────────────────────────────────────────────────────── +// ── kebab-core ───────────────────────────────────────────────────────────────── // Newtype IDs (design §3.1) — Display + FromStr implemented. pub struct AssetId(pub String); @@ -114,7 +114,7 @@ pub struct AudioRefBlock { /* per §3.4 */ } pub enum Inline { /* per §3.4 */ } pub enum SourceSpan { /* per §3.4 */ } -// (ParsedBlock + parser intermediates live in kb-parse-types per design §3.7b — NOT in kb-core.) +// (ParsedBlock + parser intermediates live in kebab-parse-types per design §3.7b — NOT in kebab-core.) // Chunk + Citation (§3.5) pub struct Chunk { /* per §3.5 */ } @@ -232,14 +232,14 @@ pub fn nfc(input: &str) -> String; // §4.1 ``` ```rust -// ── kb-parse-types ────────────────────────────────────────────────────────── +// ── kebab-parse-types ────────────────────────────────────────────────────────── // Per design §3.7b. Defines parser intermediate representations consumed by -// kb-normalize. Depends on kb-core only — never on parser libraries. +// kebab-normalize. Depends on kebab-core only — never on parser libraries. pub struct ParsedBlock { pub kind: ParsedBlockKind, pub heading_path: Vec, - pub source_span: kb_core::SourceSpan, + pub source_span: kebab_core::SourceSpan, pub payload: ParsedPayload, } @@ -247,16 +247,16 @@ pub enum ParsedBlockKind { Heading, Paragraph, List, Code, Table, Quote, ImageRe pub enum ParsedPayload { Heading { level: u8, text: String }, - Paragraph { text: String, inlines: Vec }, - List { ordered: bool, items: Vec> }, + Paragraph { text: String, inlines: Vec }, + List { ordered: bool, items: Vec> }, Code { lang: Option, code: String }, Table { headers: Vec, rows: Vec> }, - Quote { text: String, inlines: Vec }, + Quote { text: String, inlines: Vec }, ImageRef { src: String, alt: String }, AudioRef { src: String }, } -// `Inline` itself lives in kb-core (§3.4) — parse-types references it, never duplicates it. +// `Inline` itself lives in kebab-core (§3.4) — parse-types references it, never duplicates it. pub struct Warning { pub kind: WarningKind, pub note: String } pub enum WarningKind { MalformedFrontmatter, MalformedTable, EncodingFallback, ExtractFailed } @@ -268,31 +268,31 @@ pub struct ParsedAudioSegment; ``` ```rust -// ── kb-config ─────────────────────────────────────────────────────────────── +// ── kebab-config ─────────────────────────────────────────────────────────────── pub struct Config { /* full schema per §6.4 */ } impl Config { pub fn load(path: Option<&std::path::Path>) -> anyhow::Result; pub fn from_file(path: &std::path::Path) -> anyhow::Result; pub fn defaults() -> Self; pub fn apply_env(self, env: &std::collections::HashMap) -> Self; - pub fn xdg_config_path() -> std::path::PathBuf; // ~/.config/kb/config.toml - pub fn xdg_data_dir() -> std::path::PathBuf; // ~/.local/share/kb + pub fn xdg_config_path() -> std::path::PathBuf; // ~/.config/kebab/config.toml + pub fn xdg_data_dir() -> std::path::PathBuf; // ~/.local/share/kebab pub fn xdg_cache_dir() -> std::path::PathBuf; pub fn xdg_state_dir() -> std::path::PathBuf; } ``` ```rust -// ── kb-app ────────────────────────────────────────────────────────────────── +// ── kebab-app ────────────────────────────────────────────────────────────────── pub fn init_workspace(force: bool) -> anyhow::Result<()>; -pub fn ingest(scope: kb_core::SourceScope, summary_only: bool) -> anyhow::Result; -pub fn list_docs(filter: kb_core::DocFilter) -> anyhow::Result>; -pub fn inspect_doc(id: &kb_core::DocumentId) -> anyhow::Result; -pub fn inspect_chunk(id: &kb_core::ChunkId) -> anyhow::Result; -pub fn search(query: kb_core::SearchQuery) -> anyhow::Result>; -pub fn ask(query: &str, opts: AskOpts) -> anyhow::Result; +pub fn ingest(scope: kebab_core::SourceScope, summary_only: bool) -> anyhow::Result; +pub fn list_docs(filter: kebab_core::DocFilter) -> anyhow::Result>; +pub fn inspect_doc(id: &kebab_core::DocumentId) -> anyhow::Result; +pub fn inspect_chunk(id: &kebab_core::ChunkId) -> anyhow::Result; +pub fn search(query: kebab_core::SearchQuery) -> anyhow::Result>; +pub fn ask(query: &str, opts: AskOpts) -> anyhow::Result; pub fn doctor() -> anyhow::Result; -pub struct AskOpts { pub k: usize, pub explain: bool, pub mode: kb_core::SearchMode, pub temperature: Option, pub seed: Option } +pub struct AskOpts { pub k: usize, pub explain: bool, pub mode: kebab_core::SearchMode, pub temperature: Option, pub seed: Option } pub struct DoctorReport { pub ok: bool, pub checks: Vec } pub struct DoctorCheck { pub name: String, pub ok: bool, pub detail: String, pub hint: Option } ``` @@ -300,9 +300,9 @@ pub struct DoctorCheck { pub name: String, pub ok: bool, pub detail: String, pub P0 facade implementations call `anyhow::bail!("not yet wired (P-)")`; later phases replace bodies but never change signatures. ```rust -// ── kb-cli ────────────────────────────────────────────────────────────────── +// ── kebab-cli ────────────────────────────────────────────────────────────────── // clap subcommands: init | ingest | list (docs) | inspect (doc|chunk) | search | ask | doctor | eval (subcommand placeholder) -// Each maps 1:1 to a kb_app function. Exit code mapping per §10. +// Each maps 1:1 to a kebab_app function. Exit code mapping per §10. ``` ## Behavior contract @@ -312,18 +312,18 @@ P0 facade implementations call `anyhow::bail!("not yet wired (P-)")`; late - `id_from` uses `serde-json-canonicalizer` exactly as design §4.2 specifies and truncates blake3 to 32 hex chars. - `Citation::to_uri` emits W3C Media Fragments URIs per §0 Q3 (`#L-L`, `#p=`, `#xywh=…`, `#caption`, `#t=hh:mm:ss,hh:mm:ss[&speaker=…]`). - `Citation::parse` is the strict inverse (round-trip property). -- `kb-config` resolves XDG paths via `dirs` crate; respects `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, `XDG_STATE_HOME` if set. -- Config layer order: defaults → file → env (`KB_
_`) → CLI flag (CLI override is applied by `kb-cli` after `Config::load`). -- `kb-cli` global flags: `--config `, `--verbose`, `--debug`, `--json`, `--explain` (where applicable). On `--json`, output conforms to wire schema v1. -- `kb-cli` exit codes: 0 success, 1 no-hit/refusal, 2 error, 3 doctor unhealthy (per §10). +- `kebab-config` resolves XDG paths via `dirs` crate; respects `XDG_CONFIG_HOME`, `XDG_DATA_HOME`, `XDG_CACHE_HOME`, `XDG_STATE_HOME` if set. +- Config layer order: defaults → file → env (`KB_
_`) → CLI flag (CLI override is applied by `kebab-cli` after `Config::load`). +- `kebab-cli` global flags: `--config `, `--verbose`, `--debug`, `--json`, `--explain` (where applicable). On `--json`, output conforms to wire schema v1. +- `kebab-cli` exit codes: 0 success, 1 no-hit/refusal, 2 error, 3 doctor unhealthy (per §10). - All facade-returned wire objects emit `schema_version` per §2 (e.g., `"answer.v1"`, `"search_hit.v1"`). ## Storage / wire effects -- Filesystem: creates `~/.config/kb/`, `~/.local/share/kb/`, `~/KnowledgeBase/` only when `kb init` runs; never on `Config::load`. +- Filesystem: creates `~/.config/kebab/`, `~/.local/share/kebab/`, `~/KnowledgeBase/` only when `kebab init` runs; never on `Config::load`. - Wire schemas: ships `docs/wire-schema/v1/{citation,search_hit,answer,ingest_report,doc_summary,chunk_inspection,doctor}.schema.json` as **stubs** declaring the top-level `schema_version` and required fields per §2. Full property validation can land later. -- DB: workspace ships `migrations/V001__init.sql` containing **only** §5.1 `schema_meta` + `migrations` tables (the full schema lands in p1-6's migration file or p0-1 may pre-stage the empty migrations directory; choose the former to keep this task within `kb-core`/`kb-config`/`kb-app`/`kb-cli` scope). -- Logging: `tracing` initialized in `kb-cli`; daily-rolling file in `~/.local/state/kb/logs/`. +- DB: workspace ships `migrations/V001__init.sql` containing **only** §5.1 `schema_meta` + `migrations` tables (the full schema lands in p1-6's migration file or p0-1 may pre-stage the empty migrations directory; choose the former to keep this task within `kebab-core`/`kebab-config`/`kebab-app`/`kebab-cli` scope). +- Logging: `tracing` initialized in `kebab-cli`; daily-rolling file in `~/.local/state/kebab/logs/`. ## Test plan @@ -336,20 +336,20 @@ P0 facade implementations call `anyhow::bail!("not yet wired (P-)")`; late | unit | newtype `Display`/`FromStr` rejects invalid lengths/chars | inline | | unit | `Config::defaults` + env override + CLI override produces expected merged config | inline | | snapshot | `Config::defaults` JSON serde stable | inline (round-trip) | -| smoke | `kb --help`, `kb init`, `kb doctor` run; doctor reports config_loaded ✓ data_dir_writable ✓ even with no DB present (downstream checks may fail with hint) | tmp `XDG_*` env | +| smoke | `kebab --help`, `kebab init`, `kebab doctor` run; doctor reports config_loaded ✓ data_dir_writable ✓ even with no DB present (downstream checks may fail with hint) | tmp `XDG_*` env | | build | `cargo check --workspace` and `cargo test --workspace` pass | repo | All tests must run with no network, no Ollama, no models. ## Definition of Done -- [ ] `Cargo.toml` workspace lists `kb-core`, `kb-parse-types`, `kb-config`, `kb-app`, `kb-cli` and resolver=3, edition 2024 +- [ ] `Cargo.toml` workspace lists `kebab-core`, `kebab-parse-types`, `kebab-config`, `kebab-app`, `kebab-cli` and resolver=3, edition 2024 - [ ] `cargo check --workspace` passes - [ ] `cargo test --workspace` passes -- [ ] `kb-parse-types` `cargo tree` shows ONLY `kb-core` + `serde`/`thiserror` style deps (no parser libs, no other `kb-*`) -- [ ] `kb --help` prints subcommands -- [ ] `kb init` creates XDG dirs idempotently and writes `config.toml` -- [ ] `kb doctor` returns wire JSON conforming to `doctor.v1` (in `--json` mode) +- [ ] `kebab-parse-types` `cargo tree` shows ONLY `kebab-core` + `serde`/`thiserror` style deps (no parser libs, no other `kebab-*`) +- [ ] `kebab --help` prints subcommands +- [ ] `kebab init` creates XDG dirs idempotently and writes `config.toml` +- [ ] `kebab doctor` returns wire JSON conforming to `doctor.v1` (in `--json` mode) - [ ] `docs/wire-schema/v1/*.schema.json` stubs exist (7 files: citation, search_hit, answer, ingest_report, doc_summary, chunk_inspection, doctor) - [ ] `docs/spec/` stubs exist linking to the frozen design (one file per: domain-model, ids, canonical-document, chunk-policy, citation-policy, module-boundaries, ai-generation-guidelines) - [ ] `fixtures/` root directory created with all subdirectories that downstream tasks reference: `fixtures/markdown/`, `fixtures/source-fs/`, `fixtures/search/lexical/`, `fixtures/search/hybrid/`, `fixtures/embed/`, `fixtures/vector/`, `fixtures/rag/`, `fixtures/eval/`, `fixtures/image/`, `fixtures/pdf/`, `fixtures/audio/`. Each subdir gets a `.gitkeep` so it tracks. P1 ships at minimum `fixtures/markdown/{simple-note,nested-headings,code-and-table}.md` (per epic phase-0); other dirs stay empty until their phase lands. @@ -361,12 +361,12 @@ All tests must run with no network, no Ollama, no models. - Any parser / store / embedder / search / llm / rag / tui / desktop logic (downstream phases). - Full schema migrations (most DDL lands in p1-6 / p2-1 / p3-3). - Wire schema deep validation (only required fields + `schema_version` checked here). -- Real `kb-app` business logic (functions stub with `unimplemented!()` or explicit `bail!`). +- Real `kebab-app` business logic (functions stub with `unimplemented!()` or explicit `bail!`). ## Risks / notes - ID recipe is the contract that every later record depends on. Any change after this task lands forces a `parser_version` / `chunker_version` / `embedding_version` cascade per §9. Treat changes as schema migrations and update the design doc first. - Newtype IDs use `String` (not `[u8; 16]`) to keep serde simple; tests must still enforce 32-char hex constraint on `FromStr`. -- `kb-app` stubs must use `bail!` not `panic!` so the CLI exits with code 2 cleanly per §10. +- `kebab-app` stubs must use `bail!` not `panic!` so the CLI exits with code 2 cleanly per §10. - `clap` v4 derive: subcommand `inspect` has nested `doc` / `chunk` variants; ensure exit code 0/1/2 mapping wraps the facade call uniformly. - XDG path discovery on macOS: spec uses XDG (not `Application Support`) per §6.1 — `dirs` crate honors XDG env vars; tests must set them explicitly. diff --git a/tasks/p1/p1-1-source-fs.md b/tasks/p1/p1-1-source-fs.md index f849e12..4695a1a 100644 --- a/tasks/p1/p1-1-source-fs.md +++ b/tasks/p1/p1-1-source-fs.md @@ -1,12 +1,12 @@ --- phase: P1 -component: kb-source-fs +component: kebab-source-fs task_id: p1-1 title: "Local filesystem source connector" status: completed depends_on: [p0-1] unblocks: [p1-2, p1-3, p1-4, p1-5, p1-6] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.3, §6.2, §6.6, §7.1, §7.2 SourceConnector, §8] --- @@ -22,8 +22,8 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `ignore` (gitignore semantics) - `blake3` - `walkdir` @@ -34,21 +34,21 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums ## Forbidden dependencies -- `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `SourceScope` | `kb_core::SourceScope` | `kb-app` from config | +| `SourceScope` | `kebab_core::SourceScope` | `kebab-app` from config | | filesystem | `&Path` | OS | -| `.kbignore` | text file | workspace root, optional | +| `.kebabignore` | text file | workspace root, optional | ## Outputs | output | type | downstream consumer | |--------|------|---------------------| -| `Vec` | `kb_core::RawAsset` | `kb-parse-md`, asset writer in `kb-store-sqlite` (via `kb-app`) | +| `Vec` | `kebab_core::RawAsset` | `kebab-parse-md`, asset writer in `kebab-store-sqlite` (via `kebab-app`) | ## Public surface (signatures only — no new types) @@ -56,11 +56,11 @@ Walk the workspace root, apply gitignore-style filters, compute BLAKE3 checksums pub struct FsSourceConnector { /* internal */ } impl FsSourceConnector { - pub fn new(config: &kb_config::Config) -> anyhow::Result; + pub fn new(config: &kebab_config::Config) -> anyhow::Result; } -impl kb_core::SourceConnector for FsSourceConnector { - fn scan(&self, scope: &kb_core::SourceScope) -> anyhow::Result>; +impl kebab_core::SourceConnector for FsSourceConnector { + fn scan(&self, scope: &kebab_core::SourceScope) -> anyhow::Result>; } ``` @@ -70,7 +70,7 @@ impl kb_core::SourceConnector for FsSourceConnector { - `asset_id` derived per design §4.2 from `blake3(raw bytes)` full hex. - `media_type` selected from extension + libmagic-like sniff fallback (`.md` → Markdown, others fall through to `MediaType::Other`). - `discovered_at` = current `OffsetDateTime::now_utc()` at scan time. -- Combine `config.workspace.exclude` ∪ `.kbignore` for filter (union; ordering does not matter). +- Combine `config.workspace.exclude` ∪ `.kebabignore` for filter (union; ordering does not matter). - Symbolic links: follow once, detect cycles via `canonicalize` + visited set. - Files larger than `storage.copy_threshold_mb` MB → emit `AssetStorage::Reference { path, sha }` (do not copy bytes here; copying is done by the asset writer task). - Idempotent: same input → same `Vec` (sort by `workspace_path`). @@ -78,7 +78,7 @@ impl kb_core::SourceConnector for FsSourceConnector { ## Storage / wire effects - Reads: filesystem under `config.workspace.root`. -- Writes: nothing. (Asset copy is handled by the asset writer in `kb-store-sqlite`.) +- Writes: nothing. (Asset copy is handled by the asset writer in `kebab-store-sqlite`.) ## Test plan @@ -87,25 +87,25 @@ impl kb_core::SourceConnector for FsSourceConnector { | unit | POSIX path normalization | inline cases incl. `./a/b.md`, `a//b.md`, `a/b.md` → identical | | unit | blake3 of known bytes matches expected hex | inline | | unit | gitignore filter (`*.tmp`, `node_modules/**`) excludes correctly | tmp tree built in test | -| unit | `.kbignore` ∪ config exclude works | tmp tree | +| unit | `.kebabignore` ∪ config exclude works | tmp tree | | unit | symlink cycle does not loop | tmp tree with `a -> b -> a` | | snapshot | `Vec` serialized JSON for fixture tree is stable | `fixtures/source-fs/tree-1` | | determinism | re-running scan twice produces byte-identical JSON | `fixtures/source-fs/tree-1` | -All tests run under `cargo test -p kb-source-fs` with no network and no model. +All tests run under `cargo test -p kebab-source-fs` with no network and no model. ## Definition of Done -- [ ] `cargo check -p kb-source-fs` passes -- [ ] `cargo test -p kb-source-fs` passes +- [ ] `cargo check -p kebab-source-fs` passes +- [ ] `cargo test -p kebab-source-fs` passes - [ ] Snapshot test `fixtures/source-fs/tree-1` round-trips deterministically -- [ ] No imports outside Allowed dependencies (verified via `cargo tree -p kb-source-fs`) +- [ ] No imports outside Allowed dependencies (verified via `cargo tree -p kebab-source-fs`) - [ ] PR description links to design §3.3, §6.2, §7.2 ## Out of scope - File watching (P+). -- Asset copy/reference storage on disk (`kb-store-sqlite` task p1-6). +- Asset copy/reference storage on disk (`kebab-store-sqlite` task p1-6). - Non-fs source connectors (HTTP, S3 — P+). ## Risks / notes diff --git a/tasks/p1/p1-2-parse-md-frontmatter.md b/tasks/p1/p1-2-parse-md-frontmatter.md index 07f2e02..7910ba8 100644 --- a/tasks/p1/p1-2-parse-md-frontmatter.md +++ b/tasks/p1/p1-2-parse-md-frontmatter.md @@ -1,29 +1,29 @@ --- phase: P1 -component: kb-parse-md (frontmatter submodule) +component: kebab-parse-md (frontmatter submodule) task_id: p1-2 title: "Markdown frontmatter parsing → Metadata" status: completed depends_on: [p0-1] unblocks: [p1-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md -contract_sections: [design §3.6 Metadata, design §3.7b kb-parse-types (Warning), design §0 Q9 frontmatter, design §10 errors] +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md +contract_sections: [design §3.6 Metadata, design §3.7b kebab-parse-types (Warning), design §0 Q9 frontmatter, design §10 errors] --- # p1-2 — Markdown frontmatter parsing ## Goal -Parse YAML/TOML frontmatter from Markdown bytes into `kb_core::Metadata`, with auto-derive defaults and unknown-key preservation in `metadata.user`. +Parse YAML/TOML frontmatter from Markdown bytes into `kebab_core::Metadata`, with auto-derive defaults and unknown-key preservation in `metadata.user`. ## Why now / why this size -Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from block parsing keeps both halves of `kb-parse-md` simple and lets us reach 100% test coverage on the rules in design §0 Q9. +Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from block parsing keeps both halves of `kebab-parse-md` simple and lets us reach 100% test coverage on the rules in design §0 Q9. ## Allowed dependencies -- `kb-core` -- `kb-parse-types` (provides shared `Warning` + `WarningKind` per design §3.7b) +- `kebab-core` +- `kebab-parse-types` (provides shared `Warning` + `WarningKind` per design §3.7b) - `serde` - `serde_yaml` (or `yaml-rust2`) for YAML - `toml` for TOML @@ -33,7 +33,7 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from ## Forbidden dependencies -- `kb-store-*`, `kb-llm*`, `kb-rag`, `kb-embed*`, `kb-search`, `kb-tui`, `kb-desktop`, `kb-source-fs`, `kb-chunk`, `kb-normalize`, `pulldown-cmark` (block parser is a sibling task) +- `kebab-store-*`, `kebab-llm*`, `kebab-rag`, `kebab-embed*`, `kebab-search`, `kebab-tui`, `kebab-desktop`, `kebab-source-fs`, `kebab-chunk`, `kebab-normalize`, `pulldown-cmark` (block parser is a sibling task) ## Inputs @@ -46,7 +46,7 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from | output | type | downstream | |--------|------|------------| -| `(Metadata, Option, Vec)` | tuple | `kb-normalize` → CanonicalDocument | +| `(Metadata, Option, Vec)` | tuple | `kebab-normalize` → CanonicalDocument | ## Public surface (signatures only — no new types) @@ -54,10 +54,10 @@ Frontmatter is small but contractually load-bearing (Q9 spec). Isolating it from pub fn parse_frontmatter( bytes: &[u8], hints: &BodyHints, -) -> anyhow::Result<(kb_core::Metadata, Option, Vec)>; +) -> anyhow::Result<(kebab_core::Metadata, Option, Vec)>; ``` -`Warning` / `WarningKind` come from `kb-parse-types` (shared with `p1-3` blocks parser and downstream `kb-normalize`). `FrontmatterSpan` is crate-internal; if any new public type is needed, STOP and update the frozen design doc first. +`Warning` / `WarningKind` come from `kebab-parse-types` (shared with `p1-3` blocks parser and downstream `kebab-normalize`). `FrontmatterSpan` is crate-internal; if any new public type is needed, STOP and update the frozen design doc first. ## Behavior contract @@ -68,7 +68,7 @@ pub fn parse_frontmatter( - `source_type` default `markdown`; `trust_level` default `primary`. - `aliases`, `tags` default empty. - Unknown keys → `metadata.user` (`serde_json::Map`), preserved verbatim, no warning. -- Unknown enum value (e.g. `trust_level: weird`) → emit `kb_parse_types::Warning { kind: WarningKind::MalformedFrontmatter, note: "unknown trust_level=weird, defaulted to primary" }` + ingest continues with default. +- Unknown enum value (e.g. `trust_level: weird`) → emit `kebab_parse_types::Warning { kind: WarningKind::MalformedFrontmatter, note: "unknown trust_level=weird, defaulted to primary" }` + ingest continues with default. - Malformed YAML → frontmatter discarded, body still parsed, `Warning { kind: WarningKind::MalformedFrontmatter, note: "" }` emitted. - No frontmatter at all → defaults applied silently. - `id:` field captured into `metadata.user_id_alias` (alias only — does NOT influence `doc_id` per design §4.2). @@ -91,12 +91,12 @@ pub fn parse_frontmatter( | snapshot | `fixtures/markdown/frontmatter-only.md` produces stable JSON | fixture | | snapshot | mixed-language body with no `lang:` detects `ko` or `en` | `fixtures/markdown/mixed-lang.md` | -All tests under `cargo test -p kb-parse-md --lib frontmatter`. +All tests under `cargo test -p kebab-parse-md --lib frontmatter`. ## Definition of Done -- [ ] `cargo check -p kb-parse-md` passes -- [ ] `cargo test -p kb-parse-md frontmatter` passes +- [ ] `cargo check -p kebab-parse-md` passes +- [ ] `cargo test -p kebab-parse-md frontmatter` passes - [ ] No `pulldown-cmark` import in this submodule - [ ] Snapshot tests stable across two consecutive runs - [ ] PR links design §0 Q9, §3.6 diff --git a/tasks/p1/p1-3-parse-md-blocks.md b/tasks/p1/p1-3-parse-md-blocks.md index dcd4813..8a41e16 100644 --- a/tasks/p1/p1-3-parse-md-blocks.md +++ b/tasks/p1/p1-3-parse-md-blocks.md @@ -1,20 +1,20 @@ --- phase: P1 -component: kb-parse-md (blocks submodule) +component: kebab-parse-md (blocks submodule) task_id: p1-3 title: "Markdown body → Block tree with line spans" status: completed depends_on: [p0-1] unblocks: [p1-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md -contract_sections: [§3.4 Block, §3.4 SourceSpan, §3.7b kb-parse-types, §0 Q3 citation] +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md +contract_sections: [§3.4 Block, §3.4 SourceSpan, §3.7b kebab-parse-types, §0 Q3 citation] --- # p1-3 — Markdown body → Block tree ## Goal -Parse Markdown body bytes into a flat `Vec` with heading paths and line ranges preserved, ready for `kb-normalize` to lift into `CanonicalDocument`. +Parse Markdown body bytes into a flat `Vec` with heading paths and line ranges preserved, ready for `kebab-normalize` to lift into `CanonicalDocument`. ## Why now / why this size @@ -22,15 +22,15 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from ## Allowed dependencies -- `kb-core` -- `kb-parse-types` (defines `ParsedBlock`, `ParsedPayload`, `Warning`) +- `kebab-core` +- `kebab-parse-types` (defines `ParsedBlock`, `ParsedPayload`, `Warning`) - `pulldown-cmark` (CommonMark with source-map; GFM tables enabled via feature) - `serde` - `thiserror` ## Forbidden dependencies -- `kb-store-*`, `kb-llm*`, `kb-rag`, `kb-embed*`, `kb-search`, `kb-source-fs`, `kb-chunk`, `kb-normalize`, `kb-tui`, `kb-desktop`, `comrak` (alternative parser; pick one) +- `kebab-store-*`, `kebab-llm*`, `kebab-rag`, `kebab-embed*`, `kebab-search`, `kebab-source-fs`, `kebab-chunk`, `kebab-normalize`, `kebab-tui`, `kebab-desktop`, `comrak` (alternative parser; pick one) ## Inputs @@ -43,8 +43,8 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from | output | type | downstream | |--------|------|------------| -| `Vec` | shared type from `kb-parse-types` | `kb-normalize` | -| `Vec` | shared type | propagated into Provenance | +| `Vec` | shared type from `kebab-parse-types` | `kebab-normalize` | +| `Vec` | shared type | propagated into Provenance | ## Public surface (signatures only — no new types) @@ -52,10 +52,10 @@ This is the heaviest part of P1 parser. Separating it from frontmatter and from pub fn parse_blocks( body: &[u8], body_offset_lines: u32, -) -> anyhow::Result<(Vec, Vec)>; +) -> anyhow::Result<(Vec, Vec)>; ``` -`ParsedBlock` is defined in `kb-parse-types` (design §3.7b). `kb-parse-md` does NOT define its own; it consumes the shared type. Lift to `kb_core::Block` (with `BlockId` assignment) is `kb-normalize`'s job (p1-4). +`ParsedBlock` is defined in `kebab-parse-types` (design §3.7b). `kebab-parse-md` does NOT define its own; it consumes the shared type. Lift to `kebab_core::Block` (with `BlockId` assignment) is `kebab-normalize`'s job (p1-4). ## Behavior contract @@ -63,9 +63,9 @@ pub fn parse_blocks( - Heading tree: every block records its ancestor heading texts in order (e.g., `["아키텍처", "Chunking 정책"]`). - Code blocks: `ParsedPayload::Code { lang: Some("rust"), code }` — fenced content not split. - Tables: GFM tables produce `ParsedPayload::Table { headers, rows }`; if a table cell is malformed, fall back to `ParsedPayload::Paragraph` + `Warning::MalformedTable`. -- Image references: `![alt](src)` produces `ParsedPayload::ImageRef { src, alt }`. `AssetId` resolution happens later in `kb-normalize` (when image src can be matched to a workspace asset). -- Lists: ordered/unordered preserved via `ParsedPayload::List { ordered, items }`; nested list items flattened so each `items[i]` is a `Vec` for one top-level item. -- Inline elements: only `Text`, `Code`, `Link`, `Strong`, `Emph` (per `kb_core::Inline` per design §3.4). Drop other inlines silently. +- Image references: `![alt](src)` produces `ParsedPayload::ImageRef { src, alt }`. `AssetId` resolution happens later in `kebab-normalize` (when image src can be matched to a workspace asset). +- Lists: ordered/unordered preserved via `ParsedPayload::List { ordered, items }`; nested list items flattened so each `items[i]` is a `Vec` for one top-level item. +- Inline elements: only `Text`, `Code`, `Link`, `Strong`, `Emph` (per `kebab_core::Inline` per design §3.4). Drop other inlines silently. - Malformed input never panics. Worst case: empty `Vec` + `Warning::ExtractFailed`. ## Storage / wire effects @@ -86,12 +86,12 @@ pub fn parse_blocks( | snapshot | `fixtures/markdown/nested-headings.md` → ParsedBlock JSON stable | fixture | | snapshot | `fixtures/markdown/code-and-table.md` → JSON stable | fixture | -All tests under `cargo test -p kb-parse-md --lib blocks`. +All tests under `cargo test -p kebab-parse-md --lib blocks`. ## Definition of Done -- [ ] `cargo check -p kb-parse-md` passes -- [ ] `cargo test -p kb-parse-md blocks` passes +- [ ] `cargo check -p kebab-parse-md` passes +- [ ] `cargo test -p kebab-parse-md blocks` passes - [ ] Snapshot tests stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.4 @@ -99,7 +99,7 @@ All tests under `cargo test -p kb-parse-md --lib blocks`. ## Out of scope - Frontmatter (p1-2). -- Lifting `kb_parse_types::ParsedBlock` → `kb_core::Block` with `BlockId` (p1-4 normalize). +- Lifting `kebab_parse_types::ParsedBlock` → `kebab_core::Block` with `BlockId` (p1-4 normalize). - Chunking (p1-5). ## Risks / notes diff --git a/tasks/p1/p1-4-normalize.md b/tasks/p1/p1-4-normalize.md index dd8bddf..ff550f7 100644 --- a/tasks/p1/p1-4-normalize.md +++ b/tasks/p1/p1-4-normalize.md @@ -1,30 +1,30 @@ --- phase: P1 -component: kb-normalize +component: kebab-normalize task_id: p1-4 title: "Lift parser output → CanonicalDocument with deterministic IDs" status: completed depends_on: [p1-2, p1-3] unblocks: [p1-5, p1-6] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md -contract_sections: [§3.4, §3.7b kb-parse-types, §4 ID recipe, §3.6 Provenance, §8 module boundaries] +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md +contract_sections: [§3.4, §3.7b kebab-parse-types, §4 ID recipe, §3.6 Provenance, §8 module boundaries] --- # p1-4 — Lift to CanonicalDocument ## Goal -Combine `Metadata` (p1-2) + `Vec` (p1-3) + `RawAsset` (p1-1) into a `kb_core::CanonicalDocument` with deterministic `doc_id` and `block_id`s per design §4 recipe. +Combine `Metadata` (p1-2) + `Vec` (p1-3) + `RawAsset` (p1-1) into a `kebab_core::CanonicalDocument` with deterministic `doc_id` and `block_id`s per design §4 recipe. ## Why now / why this size -Single responsibility: ID generation + struct assembly. Keeps `kb-parse-md` purely a parser and isolates the (security-critical) deterministic ID logic in one crate. +Single responsibility: ID generation + struct assembly. Keeps `kebab-parse-md` purely a parser and isolates the (security-critical) deterministic ID logic in one crate. ## Allowed dependencies -- `kb-core` -- `kb-parse-types` (input shapes — `ParsedBlock`, `ParsedPayload`, `Warning`) -- `kb-config` +- `kebab-core` +- `kebab-parse-types` (input shapes — `ParsedBlock`, `ParsedPayload`, `Warning`) +- `kebab-config` - `serde` - `serde-json-canonicalizer` (canonical JSON for ID hashing) - `blake3` @@ -34,36 +34,36 @@ Single responsibility: ID generation + struct assembly. Keeps `kb-parse-md` pure ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md` (consumed via shared `kb-parse-types` only — `kb-parse-md` must NOT appear in this crate's `cargo tree`), `kb-parse-pdf`, `kb-parse-image`, `kb-parse-audio`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md` (consumed via shared `kebab-parse-types` only — `kebab-parse-md` must NOT appear in this crate's `cargo tree`), `kebab-parse-pdf`, `kebab-parse-image`, `kebab-parse-audio`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `RawAsset` | `kb_core::RawAsset` | p1-1 | -| `Metadata` + frontmatter span + warnings | `(kb_core::Metadata, Option, Vec)` | p1-2 | -| `Vec` + warnings | `(Vec, Vec)` | p1-3 | -| `parser_version` | `kb_core::ParserVersion` | constant in `kb-parse-md` | +| `RawAsset` | `kebab_core::RawAsset` | p1-1 | +| `Metadata` + frontmatter span + warnings | `(kebab_core::Metadata, Option, Vec)` | p1-2 | +| `Vec` + warnings | `(Vec, Vec)` | p1-3 | +| `parser_version` | `kebab_core::ParserVersion` | constant in `kebab-parse-md` | ## Outputs | output | type | downstream | |--------|------|------------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | `kb-chunk`, `kb-store-sqlite` | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | `kebab-chunk`, `kebab-store-sqlite` | ## Public surface (signatures only — no new types) ```rust pub fn build_canonical_document( - asset: &kb_core::RawAsset, - metadata: kb_core::Metadata, - blocks: Vec, - parser_version: &kb_core::ParserVersion, - warnings: Vec, -) -> anyhow::Result; + asset: &kebab_core::RawAsset, + metadata: kebab_core::Metadata, + blocks: Vec, + parser_version: &kebab_core::ParserVersion, + warnings: Vec, +) -> anyhow::Result; -pub fn id_for_doc(workspace_path: &kb_core::WorkspacePath, asset: &kb_core::AssetId, parser_version: &kb_core::ParserVersion) -> kb_core::DocumentId; -pub fn id_for_block(doc: &kb_core::DocumentId, kind: &str, heading_path: &[String], ordinal: u32, span: &kb_core::SourceSpan) -> kb_core::BlockId; +pub fn id_for_doc(workspace_path: &kebab_core::WorkspacePath, asset: &kebab_core::AssetId, parser_version: &kebab_core::ParserVersion) -> kebab_core::DocumentId; +pub fn id_for_block(doc: &kebab_core::DocumentId, kind: &str, heading_path: &[String], ordinal: u32, span: &kebab_core::SourceSpan) -> kebab_core::BlockId; ``` ## Behavior contract @@ -91,14 +91,14 @@ pub fn id_for_block(doc: &kb_core::DocumentId, kind: &str, heading_path: &[Strin | unit | provenance contains Discovered/Parsed/Normalized in order | inline | | snapshot | `fixtures/markdown/code-and-table.md` → CanonicalDocument JSON stable (incl. all IDs) | fixture | -All tests under `cargo test -p kb-normalize`. +All tests under `cargo test -p kebab-normalize`. ## Definition of Done -- [ ] `cargo check -p kb-normalize` passes -- [ ] `cargo test -p kb-normalize` passes +- [ ] `cargo check -p kebab-normalize` passes +- [ ] `cargo test -p kebab-normalize` passes - [ ] Determinism test runs ≥ 1000 iterations under 1 second -- [ ] No `kb-parse-md` (or any other parser crate) appears in `cargo tree -p kb-normalize` — input types come from `kb-parse-types` only +- [ ] No `kebab-parse-md` (or any other parser crate) appears in `cargo tree -p kebab-normalize` — input types come from `kebab-parse-types` only - [ ] PR links design §3.7b, §4.2, §4.3, §8 ## Out of scope diff --git a/tasks/p1/p1-5-chunk.md b/tasks/p1/p1-5-chunk.md index 3eb7c8c..a205afb 100644 --- a/tasks/p1/p1-5-chunk.md +++ b/tasks/p1/p1-5-chunk.md @@ -1,12 +1,12 @@ --- phase: P1 -component: kb-chunk +component: kebab-chunk task_id: p1-5 title: "Markdown heading-aware chunker (md-heading-v1)" status: completed depends_on: [p1-4] unblocks: [p1-6, p2-2, p3-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.5 Chunk, §4.2 chunk_id recipe, §7.2 Chunker, §0 Q3 citation] --- @@ -22,8 +22,8 @@ The first concrete `Chunker`. Establishes how subsequent chunkers (PDF page chun ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde` - `blake3` (policy_hash) - `serde-json-canonicalizer` @@ -31,30 +31,30 @@ The first concrete `Chunker`. Establishes how subsequent chunkers (PDF page chun ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize` (consumes `CanonicalDocument` only via `kb-core`), `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize` (consumes `CanonicalDocument` only via `kebab-core`), `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | p1-4 | -| `ChunkPolicy` | `kb_core::ChunkPolicy` | `kb-app` from config | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | p1-4 | +| `ChunkPolicy` | `kebab_core::ChunkPolicy` | `kebab-app` from config | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` | `kb_core::Chunk` | `kb-store-sqlite` (p1-6), `kb-embed*` (P3) | +| `Vec` | `kebab_core::Chunk` | `kebab-store-sqlite` (p1-6), `kebab-embed*` (P3) | ## Public surface (signatures only — no new types) ```rust pub struct MdHeadingV1Chunker; -impl kb_core::Chunker for MdHeadingV1Chunker { - fn chunker_version(&self) -> kb_core::ChunkerVersion; - fn policy_hash(&self, policy: &kb_core::ChunkPolicy) -> String; - fn chunk(&self, doc: &kb_core::CanonicalDocument, policy: &kb_core::ChunkPolicy) -> anyhow::Result>; +impl kebab_core::Chunker for MdHeadingV1Chunker { + fn chunker_version(&self) -> kebab_core::ChunkerVersion; + fn policy_hash(&self, policy: &kebab_core::ChunkPolicy) -> String; + fn chunk(&self, doc: &kebab_core::CanonicalDocument, policy: &kebab_core::ChunkPolicy) -> anyhow::Result>; } ``` @@ -91,12 +91,12 @@ impl kb_core::Chunker for MdHeadingV1Chunker { | determinism | identical input + identical policy → identical chunk_ids | inline | | snapshot | `fixtures/markdown/long-section.md` → Vec JSON stable | fixture | -All tests under `cargo test -p kb-chunk`. +All tests under `cargo test -p kebab-chunk`. ## Definition of Done -- [ ] `cargo check -p kb-chunk` passes -- [ ] `cargo test -p kb-chunk` passes +- [ ] `cargo check -p kebab-chunk` passes +- [ ] `cargo test -p kebab-chunk` passes - [ ] Snapshot stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.5, §4.2 diff --git a/tasks/p1/p1-6-store-sqlite.md b/tasks/p1/p1-6-store-sqlite.md index 93b7e7c..0b5ed7c 100644 --- a/tasks/p1/p1-6-store-sqlite.md +++ b/tasks/p1/p1-6-store-sqlite.md @@ -1,12 +1,12 @@ --- phase: P1 -component: kb-store-sqlite (P1 subset) +component: kebab-store-sqlite (P1 subset) task_id: p1-6 title: "SQLite store: assets/documents/blocks/chunks + asset writer + migrations" status: completed depends_on: [p1-1, p1-4, p1-5] unblocks: [p2-1, p3-3, p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5 DDL (5.1, 5.2, 5.3, 5.4, 5.5 chunks only — FTS handled in p2-1), §5.7 jobs/ingest_runs, §5.8 transactions, §6.3 data_dir layout] --- @@ -22,8 +22,8 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `rusqlite` (with `bundled-sqlcipher` disabled; use `bundled` feature) - `refinery` for migrations - `serde_json` @@ -34,7 +34,7 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT ## Forbidden dependencies -- `kb-source-fs` (only types via `kb-core`), `kb-parse-md`, `kb-normalize`, `kb-chunk` (only types via `kb-core`), `kb-store-vector`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs` (only types via `kebab-core`), `kebab-parse-md`, `kebab-normalize`, `kebab-chunk` (only types via `kebab-core`), `kebab-store-vector`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs @@ -42,17 +42,17 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT |-------|------|--------| | migrations | `migrations/V001__init.sql` | repo | | `RawAsset` + bytes | `(RawAsset, Vec)` | p1-1 + reader | -| `CanonicalDocument` | `kb_core::CanonicalDocument` | p1-4 | -| `Vec` | `kb_core::Chunk` | p1-5 | -| `IngestRun` aggregates | `(scope, counts, duration)` | `kb-app` | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | p1-4 | +| `Vec` | `kebab_core::Chunk` | p1-5 | +| `IngestRun` aggregates | `(scope, counts, duration)` | `kebab-app` | ## Outputs | output | type | downstream | |--------|------|------------| -| `data_dir/kb.sqlite` rows in `assets`, `documents`, `blocks`, `chunks`, `document_tags`, `ingest_runs`, `jobs`, `schema_meta`, `migrations` | – | every later phase | +| `data_dir/kebab.sqlite` rows in `assets`, `documents`, `blocks`, `chunks`, `document_tags`, `ingest_runs`, `jobs`, `schema_meta`, `migrations` | – | every later phase | | `data_dir/assets//` bytes (when copied) | – | future re-extraction, integrity verification | -| `IngestReport` (wire schema v1) | `kb_core::IngestReport` | `kb-cli`, eval | +| `IngestReport` (wire schema v1) | `kebab_core::IngestReport` | `kebab-cli`, eval | ## Public surface (signatures only — no new types) @@ -60,23 +60,23 @@ P1's terminal task. Closes the loop `walk → parse → chunk → store`. The FT pub struct SqliteStore { /* internal */ } impl SqliteStore { - pub fn open(config: &kb_config::Config) -> anyhow::Result; + pub fn open(config: &kebab_config::Config) -> anyhow::Result; pub fn run_migrations(&self) -> anyhow::Result<()>; - pub fn put_asset_with_bytes(&self, asset: &kb_core::RawAsset, bytes: &[u8]) -> anyhow::Result<()>; + pub fn put_asset_with_bytes(&self, asset: &kebab_core::RawAsset, bytes: &[u8]) -> anyhow::Result<()>; } -impl kb_core::DocumentStore for SqliteStore { - fn put_asset(&self, a: &kb_core::RawAsset) -> anyhow::Result<()>; - fn put_document(&self, d: &kb_core::CanonicalDocument) -> anyhow::Result<()>; - fn put_blocks(&self, doc: &kb_core::DocumentId, blocks: &[kb_core::Block]) -> anyhow::Result<()>; - fn put_chunks(&self, doc: &kb_core::DocumentId, chunks: &[kb_core::Chunk]) -> anyhow::Result<()>; - fn get_document(&self, id: &kb_core::DocumentId) -> anyhow::Result>; - fn get_chunk(&self, id: &kb_core::ChunkId) -> anyhow::Result>; - fn list_documents(&self, filter: &kb_core::DocFilter) -> anyhow::Result>; +impl kebab_core::DocumentStore for SqliteStore { + fn put_asset(&self, a: &kebab_core::RawAsset) -> anyhow::Result<()>; + fn put_document(&self, d: &kebab_core::CanonicalDocument) -> anyhow::Result<()>; + fn put_blocks(&self, doc: &kebab_core::DocumentId, blocks: &[kebab_core::Block]) -> anyhow::Result<()>; + fn put_chunks(&self, doc: &kebab_core::DocumentId, chunks: &[kebab_core::Chunk]) -> anyhow::Result<()>; + fn get_document(&self, id: &kebab_core::DocumentId) -> anyhow::Result>; + fn get_chunk(&self, id: &kebab_core::ChunkId) -> anyhow::Result>; + fn list_documents(&self, filter: &kebab_core::DocFilter) -> anyhow::Result>; } -impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } +impl kebab_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } ``` ## Behavior contract @@ -96,7 +96,7 @@ impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } ## Storage / wire effects -- Writes: `kb.sqlite` (multiple tables), `data_dir/assets//` (copied case). +- Writes: `kebab.sqlite` (multiple tables), `data_dir/assets//` (copied case). - Reads on subsequent calls: same DB. ## Test plan @@ -112,14 +112,14 @@ impl kb_core::JobRepo for SqliteStore { /* per design §7.2 signatures */ } | contract | DocumentStore trait round-trip for fixture document | `fixtures/markdown/code-and-table.md` | | snapshot | IngestReport JSON for fixture run | fixture | -All tests under `cargo test -p kb-store-sqlite` with no network. +All tests under `cargo test -p kebab-store-sqlite` with no network. ## Definition of Done -- [ ] `cargo check -p kb-store-sqlite` passes -- [ ] `cargo test -p kb-store-sqlite` passes +- [ ] `cargo check -p kebab-store-sqlite` passes +- [ ] `cargo test -p kebab-store-sqlite` passes - [ ] migration `V001__init.sql` matches design §5 verbatim (diff-checked in CI) -- [ ] Writes to `~/.local/share/kb/` are gated by `kb-config`'s `data_dir` and never escape it +- [ ] Writes to `~/.local/share/kebab/` are gated by `kebab-config`'s `data_dir` and never escape it - [ ] No imports outside Allowed dependencies - [ ] PR links design §5 @@ -132,5 +132,5 @@ All tests under `cargo test -p kb-store-sqlite` with no network. ## Risks / notes -- WAL mode requires careful test cleanup: tests must drop the connection before removing `kb.sqlite-wal` / `-shm`. +- WAL mode requires careful test cleanup: tests must drop the connection before removing `kebab.sqlite-wal` / `-shm`. - Asset directory shard prefix uses `asset_id[..2]`; using `asset_id[..1]` would create at most 16 dirs (insufficient). diff --git a/tasks/p2/p2-1-fts-schema.md b/tasks/p2/p2-1-fts-schema.md index 4e46623..4c61688 100644 --- a/tasks/p2/p2-1-fts-schema.md +++ b/tasks/p2/p2-1-fts-schema.md @@ -1,12 +1,12 @@ --- phase: P2 -component: kb-store-sqlite (FTS5 migration) +component: kebab-store-sqlite (FTS5 migration) task_id: p2-1 title: "FTS5 virtual table + triggers (V002 migration)" status: completed depends_on: [p1-6] unblocks: [p2-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5.5 chunks_fts + triggers, §9 versioning] --- @@ -18,19 +18,19 @@ Add `chunks_fts` virtual table and three sync triggers via migration `V002__fts. ## Why now / why this size -`chunks_fts` is the lexical index for `kb-search`. Splitting it from p1-6 keeps P1 focused on relational data; bringing it as `V002` lets users upgrade an existing P1 DB without re-ingesting. +`chunks_fts` is the lexical index for `kebab-search`. Splitting it from p1-6 keeps P1 focused on relational data; bringing it as `V002` lets users upgrade an existing P1 DB without re-ingesting. ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-store-sqlite` (extends migrations) +- `kebab-core` +- `kebab-config` +- `kebab-store-sqlite` (extends migrations) - `rusqlite` - `refinery` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-vector`, `kb-embed*`, `kb-search` (consumer is p2-2), `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-vector`, `kebab-embed*`, `kebab-search` (consumer is p2-2), `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs @@ -52,7 +52,7 @@ Add `chunks_fts` virtual table and three sync triggers via migration `V002__fts. pub fn rebuild_chunks_fts(conn: &rusqlite::Connection) -> anyhow::Result<()>; ``` -(Used by `kb index --rebuild-fts`. Re-runs `INSERT INTO chunks_fts SELECT ... FROM chunks` after `DELETE FROM chunks_fts;`.) +(Used by `kebab index --rebuild-fts`. Re-runs `INSERT INTO chunks_fts SELECT ... FROM chunks` after `DELETE FROM chunks_fts;`.) ## Behavior contract @@ -64,7 +64,7 @@ pub fn rebuild_chunks_fts(conn: &rusqlite::Connection) -> anyhow::Result<()>; ## Storage / wire effects -- Writes: `chunks_fts` virtual table inside `kb.sqlite`. +- Writes: `chunks_fts` virtual table inside `kebab.sqlite`. - Reads: existing `chunks` rows for backfill. ## Test plan @@ -78,12 +78,12 @@ pub fn rebuild_chunks_fts(conn: &rusqlite::Connection) -> anyhow::Result<()>; | function | `rebuild_chunks_fts` produces deterministic content equal to fresh backfill | tmp DB | | migration | running `V002` twice is a no-op (refinery handles idempotency) | tmp DB | -All tests under `cargo test -p kb-store-sqlite fts`. +All tests under `cargo test -p kebab-store-sqlite fts`. ## Definition of Done -- [ ] `cargo check -p kb-store-sqlite` passes -- [ ] `cargo test -p kb-store-sqlite fts` passes +- [ ] `cargo check -p kebab-store-sqlite` passes +- [ ] `cargo test -p kebab-store-sqlite fts` passes - [ ] `migrations/V002__fts.sql` matches design §5.5 verbatim (CI diff check) - [ ] No imports outside Allowed dependencies - [ ] PR links design §5.5 diff --git a/tasks/p2/p2-2-lexical-retriever.md b/tasks/p2/p2-2-lexical-retriever.md index 75524a5..1dd020c 100644 --- a/tasks/p2/p2-2-lexical-retriever.md +++ b/tasks/p2/p2-2-lexical-retriever.md @@ -1,12 +1,12 @@ --- phase: P2 -component: kb-search (lexical mode) +component: kebab-search (lexical mode) task_id: p2-2 title: "Lexical Retriever via SQLite FTS5 + bm25 + citation" status: completed depends_on: [p2-1] unblocks: [p3-4, p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.7 SearchQuery/Hit, §0 Q3 citation (URI fragment), §1.5/1.6 search output, §2.2 wire schema, §6.4 search settings] --- @@ -14,38 +14,38 @@ contract_sections: [§3.7 SearchQuery/Hit, §0 Q3 citation (URI fragment), §1.5 ## Goal -Implement `kb_core::Retriever` for `SearchMode::Lexical` using SQLite FTS5. Returns `SearchHit` with `bm25` ranking, `snippet()`-derived preview, and proper W3C-fragment citation. +Implement `kebab_core::Retriever` for `SearchMode::Lexical` using SQLite FTS5. Returns `SearchHit` with `bm25` ranking, `snippet()`-derived preview, and proper W3C-fragment citation. ## Why now / why this size -First concrete `Retriever`. Lets `kb search --mode lexical` work without any embedding/LLM infrastructure. Establishes the SearchHit construction contract that hybrid (p3-4) reuses. +First concrete `Retriever`. Lets `kebab search --mode lexical` work without any embedding/LLM infrastructure. Establishes the SearchHit construction contract that hybrid (p3-4) reuses. ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-store-sqlite` (read access to `chunks_fts` + `chunks` + `documents`) +- `kebab-core` +- `kebab-config` +- `kebab-store-sqlite` (read access to `chunks_fts` + `chunks` + `documents`) - `rusqlite` - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-vector`, `kb-embed*`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-vector`, `kebab-embed*`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `SearchQuery` (mode=Lexical) | `kb_core::SearchQuery` | `kb-app::search` | -| `kb-config::search` settings (`default_k`, `snippet_chars`) | `kb_config::Config` | runtime | -| SQLite connection (read) | `rusqlite::Connection` | `kb-store-sqlite` | +| `SearchQuery` (mode=Lexical) | `kebab_core::SearchQuery` | `kebab-app::search` | +| `kebab-config::search` settings (`default_k`, `snippet_chars`) | `kebab_config::Config` | runtime | +| SQLite connection (read) | `rusqlite::Connection` | `kebab-store-sqlite` | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` | `kb_core::SearchHit` | `kb-cli` printer, `kb-rag` packer (P4), hybrid (p3-4) | +| `Vec` | `kebab_core::SearchHit` | `kebab-cli` printer, `kebab-rag` packer (P4), hybrid (p3-4) | ## Public surface (signatures only — no new types) @@ -53,12 +53,12 @@ First concrete `Retriever`. Lets `kb search --mode lexical` work without any emb pub struct LexicalRetriever { /* internal: holds an Arc + IndexVersion */ } impl LexicalRetriever { - pub fn new(store: std::sync::Arc, index_version: kb_core::IndexVersion) -> Self; + pub fn new(store: std::sync::Arc, index_version: kebab_core::IndexVersion) -> Self; } -impl kb_core::Retriever for LexicalRetriever { - fn search(&self, query: &kb_core::SearchQuery) -> anyhow::Result>; - fn index_version(&self) -> kb_core::IndexVersion; +impl kebab_core::Retriever for LexicalRetriever { + fn search(&self, query: &kebab_core::SearchQuery) -> anyhow::Result>; + fn index_version(&self) -> kebab_core::IndexVersion; } ``` @@ -98,8 +98,8 @@ impl kb_core::Retriever for LexicalRetriever { ## Storage / wire effects -- Reads only. Never mutates `kb.sqlite`. -- Wire: `Vec` serialized via wire schema `search_hit.v1` when `kb-cli --json` is used. +- Reads only. Never mutates `kebab.sqlite`. +- Wire: `Vec` serialized via wire schema `search_hit.v1` when `kebab-cli --json` is used. ## Test plan @@ -114,13 +114,13 @@ impl kb_core::Retriever for LexicalRetriever { | determinism | identical query twice produces identical hit order and scores | tmp DB | | snapshot | `Vec` JSON for fixed corpus stable | `fixtures/search/lexical/run-1.json` | -All tests under `cargo test -p kb-search lexical`. +All tests under `cargo test -p kebab-search lexical`. ## Definition of Done -- [ ] `cargo check -p kb-search` passes -- [ ] `cargo test -p kb-search lexical` passes -- [ ] No imports outside Allowed dependencies (`cargo tree -p kb-search` audit) +- [ ] `cargo check -p kebab-search` passes +- [ ] `cargo test -p kebab-search lexical` passes +- [ ] No imports outside Allowed dependencies (`cargo tree -p kebab-search` audit) - [ ] Output JSON conforms to `docs/wire-schema/v1/search_hit.schema.json` - [ ] PR links design §3.7, §0 Q3, §2.2 diff --git a/tasks/p3/p3-1-embedder-trait.md b/tasks/p3/p3-1-embedder-trait.md index f84e2a1..b6a80f0 100644 --- a/tasks/p3/p3-1-embedder-trait.md +++ b/tasks/p3/p3-1-embedder-trait.md @@ -1,12 +1,12 @@ --- phase: P3 -component: kb-embed (trait crate) +component: kebab-embed (trait crate) task_id: p3-1 title: "Embedder trait + EmbeddingInput/Kind validation" status: completed depends_on: [p0-1] unblocks: [p3-2, p3-3, p3-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [design §3.7 SearchHit.embedding_model, design §7.1 EmbeddingInput/Kind, design §7.2 Embedder, report §11 LLM/embedding split] --- @@ -14,16 +14,16 @@ contract_sections: [design §3.7 SearchHit.embedding_model, design §7.1 Embeddi ## Goal -Provide the `kb-embed` crate that re-exports `Embedder` trait, `EmbeddingInput`/`EmbeddingKind`, and offers a mock implementation for downstream tests. This task is **trait-only**; concrete adapters live in p3-2. +Provide the `kebab-embed` crate that re-exports `Embedder` trait, `EmbeddingInput`/`EmbeddingKind`, and offers a mock implementation for downstream tests. This task is **trait-only**; concrete adapters live in p3-2. ## Why now / why this size -Concrete adapters (fastembed, ollama-embed, candle) need a stable trait surface. Owning the trait + a mock implementation in a tiny crate keeps `kb-store-vector` and `kb-search` testable without touching real models. +Concrete adapters (fastembed, ollama-embed, candle) need a stable trait surface. Owning the trait + a mock implementation in a tiny crate keeps `kebab-store-vector` and `kebab-search` testable without touching real models. ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde` - `thiserror` - `tracing` @@ -31,35 +31,35 @@ Concrete adapters (fastembed, ollama-embed, candle) need a stable trait surface. ## Forbidden dependencies -- `fastembed`, `ort`, `tokenizers`, `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `fastembed`, `ort`, `tokenizers`, `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `EmbeddingInput` | `kb_core::EmbeddingInput<'_>` | callers (parser-side or query-side) | +| `EmbeddingInput` | `kebab_core::EmbeddingInput<'_>` | callers (parser-side or query-side) | | model identity | `(EmbeddingModelId, EmbeddingVersion, dimensions)` | adapter at construction | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec>` | row-aligned with input | `kb-store-vector`, `kb-search` (vector mode) | +| `Vec>` | row-aligned with input | `kebab-store-vector`, `kebab-search` (vector mode) | ## Public surface (signatures only — no new types) ```rust -pub use kb_core::{EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, Embedder}; +pub use kebab_core::{EmbeddingInput, EmbeddingKind, EmbeddingModelId, EmbeddingVersion, Embedder}; /// Test-only mock that produces deterministic vectors. Compiled only when `mock` feature is on. #[cfg(feature = "mock")] pub struct MockEmbedder { /* internal: model_id, dims, seed */ } #[cfg(feature = "mock")] impl MockEmbedder { - pub fn new(model_id: kb_core::EmbeddingModelId, version: kb_core::EmbeddingVersion, dimensions: usize) -> Self; + pub fn new(model_id: kebab_core::EmbeddingModelId, version: kebab_core::EmbeddingVersion, dimensions: usize) -> Self; } #[cfg(feature = "mock")] -impl kb_core::Embedder for MockEmbedder { /* per §7.2 */ } +impl kebab_core::Embedder for MockEmbedder { /* per §7.2 */ } ``` ## Behavior contract @@ -84,21 +84,21 @@ impl kb_core::Embedder for MockEmbedder { /* per §7.2 */ } | unit | dimensions match construction-time value | inline | | contract | property test: 100 random inputs, each vector has length == dimensions, all finite floats | inline (proptest) | -All tests under `cargo test -p kb-embed`. +All tests under `cargo test -p kebab-embed`. ## Definition of Done -- [ ] `cargo check -p kb-embed` passes -- [ ] `cargo test -p kb-embed` passes +- [ ] `cargo check -p kebab-embed` passes +- [ ] `cargo test -p kebab-embed` passes - [ ] No external embedding dep present - [ ] PR links design §7.2 Embedder, §11 ## Out of scope -- Real adapter (`kb-embed-local` is p3-2). +- Real adapter (`kebab-embed-local` is p3-2). - Reranker traits (P+). ## Risks / notes -- `MockEmbedder` is gated by `mock` feature (default OFF). Downstream tests opt in via `[dev-dependencies] kb-embed = { path = "...", features = ["mock"] }`. CI build of release binary (`cargo build --release` without `--features mock`) MUST NOT include `MockEmbedder` symbol — verifiable via `cargo bloat` or `nm` symbol scan. -- Trait re-exports keep the call site stable even if `kb-core` reorganizes; downstream crates should `use kb_embed::Embedder` rather than `use kb_core::Embedder`. +- `MockEmbedder` is gated by `mock` feature (default OFF). Downstream tests opt in via `[dev-dependencies] kebab-embed = { path = "...", features = ["mock"] }`. CI build of release binary (`cargo build --release` without `--features mock`) MUST NOT include `MockEmbedder` symbol — verifiable via `cargo bloat` or `nm` symbol scan. +- Trait re-exports keep the call site stable even if `kebab-core` reorganizes; downstream crates should `use kebab_embed::Embedder` rather than `use kebab_core::Embedder`. diff --git a/tasks/p3/p3-2-fastembed-adapter.md b/tasks/p3/p3-2-fastembed-adapter.md index 1b4510e..1ad957a 100644 --- a/tasks/p3/p3-2-fastembed-adapter.md +++ b/tasks/p3/p3-2-fastembed-adapter.md @@ -1,12 +1,12 @@ --- phase: P3 -component: kb-embed-local (fastembed adapter) +component: kebab-embed-local (fastembed adapter) task_id: p3-2 title: "fastembed-rs Embedder for multilingual-e5-small" status: completed depends_on: [p3-1] unblocks: [p3-3, p3-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [design §7.2 Embedder, report §11.3 local embedding, design §6.4 [models.embedding], design §9 versioning] --- @@ -22,9 +22,9 @@ First real `Embedder`. Drives `EmbeddingId` recipe (model_id + model_version + d ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-embed` +- `kebab-core` +- `kebab-config` +- `kebab-embed` - `fastembed = "4"` (or current stable) - `tokenizers` - `ort` (transitive via fastembed) @@ -33,21 +33,21 @@ First real `Embedder`. Drives `EmbeddingId` recipe (model_id + model_version + d ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop`, network HTTP libs (model download is fastembed's responsibility) +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop`, network HTTP libs (model download is fastembed's responsibility) ## Inputs | input | type | source | |-------|------|--------| -| `kb-config::Config.models.embedding` | settings | runtime | -| `EmbeddingInput[..]` | `kb_core::EmbeddingInput<'_>[]` | callers | +| `kebab-config::Config.models.embedding` | settings | runtime | +| `EmbeddingInput[..]` | `kebab_core::EmbeddingInput<'_>[]` | callers | | model cache | `data_dir/models/fastembed/` | filesystem | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec>` | row-aligned, `dimensions = 384` | `kb-store-vector`, query vectors for hybrid search | +| `Vec>` | row-aligned, `dimensions = 384` | `kebab-store-vector`, query vectors for hybrid search | | model identity | `(EmbeddingModelId, EmbeddingVersion, usize)` | record fields, `embedding_id` recipe | ## Public surface (signatures only — no new types) @@ -56,14 +56,14 @@ First real `Embedder`. Drives `EmbeddingId` recipe (model_id + model_version + d pub struct FastembedEmbedder { /* internal: TextEmbedding instance + model meta */ } impl FastembedEmbedder { - pub fn new(config: &kb_config::Config) -> anyhow::Result; + pub fn new(config: &kebab_config::Config) -> anyhow::Result; } -impl kb_core::Embedder for FastembedEmbedder { - fn model_id(&self) -> kb_core::EmbeddingModelId; - fn model_version(&self) -> kb_core::EmbeddingVersion; +impl kebab_core::Embedder for FastembedEmbedder { + fn model_id(&self) -> kebab_core::EmbeddingModelId; + fn model_version(&self) -> kebab_core::EmbeddingVersion; fn dimensions(&self) -> usize; - fn embed(&self, inputs: &[kb_core::EmbeddingInput<'_>]) -> anyhow::Result>>; + fn embed(&self, inputs: &[kebab_core::EmbeddingInput<'_>]) -> anyhow::Result>>; } ``` @@ -96,12 +96,12 @@ impl kb_core::Embedder for FastembedEmbedder { | performance | batch of 64 short inputs completes in < 5s on CI host | tmp config (skip on slow CI via `#[ignore]`) | | snapshot | aggregate hash of vectors for 5 known sentences stable across runs | `fixtures/embed/known-sentences.json` | -All tests under `cargo test -p kb-embed-local`. Mark slow tests `#[ignore]` and run via `cargo test -- --ignored` in dedicated CI lane. +All tests under `cargo test -p kebab-embed-local`. Mark slow tests `#[ignore]` and run via `cargo test -- --ignored` in dedicated CI lane. ## Definition of Done -- [ ] `cargo check -p kb-embed-local` passes -- [ ] `cargo test -p kb-embed-local` passes (excluding `#[ignore]`) +- [ ] `cargo check -p kebab-embed-local` passes +- [ ] `cargo test -p kebab-embed-local` passes (excluding `#[ignore]`) - [ ] First-run model download works under `data_dir/models/fastembed/` - [ ] No imports outside Allowed dependencies - [ ] PR links design §11.3, §6.4, §9 diff --git a/tasks/p3/p3-3-lancedb-store.md b/tasks/p3/p3-3-lancedb-store.md index 3a80a3f..86a71f9 100644 --- a/tasks/p3/p3-3-lancedb-store.md +++ b/tasks/p3/p3-3-lancedb-store.md @@ -1,12 +1,12 @@ --- phase: P3 -component: kb-store-vector (LanceDB) +component: kebab-store-vector (LanceDB) task_id: p3-3 title: "LanceDB VectorStore + embedding_records writer" status: completed depends_on: [p3-2, p1-6] unblocks: [p3-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5.6 embedding_records, §6.3 lancedb table naming, §7.2 VectorStore, §9 versioning] --- @@ -18,13 +18,13 @@ Implement `VectorStore` over LanceDB (embedded). Stores per-model tables (`chunk ## Why now / why this size -Closes the loop chunk → vector. Splits cleanly from `kb-search` so hybrid (p3-4) can compose lexical + vector retrievers without leaking storage details. +Closes the loop chunk → vector. Splits cleanly from `kebab-search` so hybrid (p3-4) can compose lexical + vector retrievers without leaking storage details. ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-store-sqlite` (only for writing/reading rows in `embedding_records`) +- `kebab-core` +- `kebab-config` +- `kebab-store-sqlite` (only for writing/reading rows in `embedding_records`) - `lancedb` - `arrow` (and `arrow-array`, `arrow-schema`) - `serde`, `serde_json` @@ -33,16 +33,16 @@ Closes the loop chunk → vector. Splits cleanly from `kb-search` so hybrid (p3- ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-embed*` (consumes `Vec` via input only — no embedding logic here), `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-embed*` (consumes `Vec` via input only — no embedding logic here), `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `VectorRecord[..]` | `kb_core::VectorRecord` | `kb-app::embed_index` (P3 facade) | -| query vector | `&[f32]` | `kb-embed-local` (`Embedder::embed` for query) | -| filters | `kb_core::SearchFilters` | `SearchQuery` | -| `kb-config::Config.storage.vector_dir` | path | runtime | +| `VectorRecord[..]` | `kebab_core::VectorRecord` | `kebab-app::embed_index` (P3 facade) | +| query vector | `&[f32]` | `kebab-embed-local` (`Embedder::embed` for query) | +| filters | `kebab_core::SearchFilters` | `SearchQuery` | +| `kebab-config::Config.storage.vector_dir` | path | runtime | ## Outputs @@ -50,7 +50,7 @@ Closes the loop chunk → vector. Splits cleanly from `kb-search` so hybrid (p3- |--------|------|------------| | Lance tables under `vector_dir/chunk_embeddings__.lance/` | filesystem | future searches | | `embedding_records` rows | SQLite | reverse lookup, reindex bookkeeping | -| `Vec` | `kb_core::VectorHit` | hybrid retriever (p3-4) | +| `Vec` | `kebab_core::VectorHit` | hybrid retriever (p3-4) | ## Public surface (signatures only — no new types) @@ -58,13 +58,13 @@ Closes the loop chunk → vector. Splits cleanly from `kb-search` so hybrid (p3- pub struct LanceVectorStore { /* internal: connection + sqlite handle */ } impl LanceVectorStore { - pub fn new(config: &kb_config::Config, sqlite: std::sync::Arc) -> anyhow::Result; + pub fn new(config: &kebab_config::Config, sqlite: std::sync::Arc) -> anyhow::Result; } -impl kb_core::VectorStore for LanceVectorStore { - fn ensure_table(&self, model: &kb_core::EmbeddingModelId, dim: usize) -> anyhow::Result; - fn upsert(&self, recs: &[kb_core::VectorRecord]) -> anyhow::Result<()>; - fn search(&self, query_vec: &[f32], k: usize, filters: &kb_core::SearchFilters) -> anyhow::Result>; +impl kebab_core::VectorStore for LanceVectorStore { + fn ensure_table(&self, model: &kebab_core::EmbeddingModelId, dim: usize) -> anyhow::Result; + fn upsert(&self, recs: &[kebab_core::VectorRecord]) -> anyhow::Result<()>; + fn search(&self, query_vec: &[f32], k: usize, filters: &kebab_core::SearchFilters) -> anyhow::Result>; } ``` @@ -116,12 +116,12 @@ impl kb_core::VectorStore for LanceVectorStore { | determinism | same query vector + same data → same top-k order | tmp data_dir | | snapshot | `Vec` JSON for fixed corpus stable | `fixtures/vector/run-1.json` | -All tests under `cargo test -p kb-store-vector`. +All tests under `cargo test -p kebab-store-vector`. ## Definition of Done -- [ ] `cargo check -p kb-store-vector` passes -- [ ] `cargo test -p kb-store-vector` passes +- [ ] `cargo check -p kebab-store-vector` passes +- [ ] `cargo test -p kebab-store-vector` passes - [ ] No imports outside Allowed dependencies - [ ] `embedding_records` rows align 1:1 with Lance rows after a successful upsert batch - [ ] PR links design §5.6, §6.3, §7.2 @@ -130,7 +130,7 @@ All tests under `cargo test -p kb-store-vector`. - IVF / PQ index tuning (P+). - Image / multimodal vector tables (P6). -- `kb-app` orchestration of indexing jobs (`embed_index` facade method body). +- `kebab-app` orchestration of indexing jobs (`embed_index` facade method body). ## Risks / notes diff --git a/tasks/p3/p3-4-hybrid-fusion.md b/tasks/p3/p3-4-hybrid-fusion.md index 0d34a6d..53446a0 100644 --- a/tasks/p3/p3-4-hybrid-fusion.md +++ b/tasks/p3/p3-4-hybrid-fusion.md @@ -1,12 +1,12 @@ --- phase: P3 -component: kb-search (hybrid) +component: kebab-search (hybrid) task_id: p3-4 title: "Hybrid Retriever (RRF) over lexical + vector" status: completed depends_on: [p2-2, p3-3] unblocks: [p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.7 RetrievalDetail, §0 Q3, §1.6 search --explain, §6.4 [search] rrf settings] --- @@ -22,17 +22,17 @@ Single mediator. Keeps the lexical and vector retrievers focused; only this task ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-store-sqlite` (for `LexicalRetriever`) -- `kb-store-vector` (for `LanceVectorStore`) -- `kb-embed` (trait only — for query embedding via `Embedder`) +- `kebab-core` +- `kebab-config` +- `kebab-store-sqlite` (for `LexicalRetriever`) +- `kebab-store-vector` (for `LanceVectorStore`) +- `kebab-embed` (trait only — for query embedding via `Embedder`) - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop`. (`kb-embed-local` is a runtime-injected `dyn Embedder`; this crate must not depend on the concrete adapter directly.) +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop`. (`kebab-embed-local` is a runtime-injected `dyn Embedder`; this crate must not depend on the concrete adapter directly.) ## Inputs @@ -41,21 +41,21 @@ Single mediator. Keeps the lexical and vector retrievers focused; only this task | `LexicalRetriever` | trait object | constructed elsewhere | | `LanceVectorStore` | trait object | constructed elsewhere | | `Box` | for query embedding | runtime-injected | -| `kb-config::Config.search` | `default_k`, `hybrid_fusion`, `rrf_k` | runtime | -| `SearchQuery` | `kb_core::SearchQuery` | `kb-app::search` | +| `kebab-config::Config.search` | `default_k`, `hybrid_fusion`, `rrf_k` | runtime | +| `SearchQuery` | `kebab_core::SearchQuery` | `kebab-app::search` | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` (with full `RetrievalDetail`) | `kb_core::SearchHit` | `kb-cli` printer, `kb-rag` packer | +| `Vec` (with full `RetrievalDetail`) | `kebab_core::SearchHit` | `kebab-cli` printer, `kebab-rag` packer | ## Public surface (signatures only — no new types) ```rust pub struct HybridRetriever { - lexical: std::sync::Arc, - vector: std::sync::Arc, // wrapper over LanceVectorStore + Embedder + lexical: std::sync::Arc, + vector: std::sync::Arc, // wrapper over LanceVectorStore + Embedder fusion: FusionPolicy, k: usize, } @@ -64,27 +64,27 @@ pub enum FusionPolicy { Rrf { k_rrf: u32 } } impl HybridRetriever { pub fn new( - config: &kb_config::Config, - lexical: std::sync::Arc, - vector: std::sync::Arc, + config: &kebab_config::Config, + lexical: std::sync::Arc, + vector: std::sync::Arc, ) -> Self; } -impl kb_core::Retriever for HybridRetriever { - fn search(&self, query: &kb_core::SearchQuery) -> anyhow::Result>; - fn index_version(&self) -> kb_core::IndexVersion; +impl kebab_core::Retriever for HybridRetriever { + fn search(&self, query: &kebab_core::SearchQuery) -> anyhow::Result>; + fn index_version(&self) -> kebab_core::IndexVersion; } /// Wrapper that turns a VectorStore + Embedder into a Retriever. pub struct VectorRetriever { - store: std::sync::Arc, - embed: std::sync::Arc, - /* heading_path/snippet enrichment hits SQLite via kb-store-sqlite read accessor */ + store: std::sync::Arc, + embed: std::sync::Arc, + /* heading_path/snippet enrichment hits SQLite via kebab-store-sqlite read accessor */ } impl VectorRetriever { - pub fn new(store: std::sync::Arc, embed: std::sync::Arc, sqlite: std::sync::Arc) -> Self; + pub fn new(store: std::sync::Arc, embed: std::sync::Arc, sqlite: std::sync::Arc) -> Self; } -impl kb_core::Retriever for VectorRetriever { /* per §7.2 */ } +impl kebab_core::Retriever for VectorRetriever { /* per §7.2 */ } ``` ## Behavior contract @@ -124,12 +124,12 @@ impl kb_core::Retriever for VectorRetriever { /* per §7.2 */ } | determinism | identical query twice → byte-identical `Vec` | tmp DB | | snapshot | hybrid output JSON stable | `fixtures/search/hybrid/run-1.json` | -All tests under `cargo test -p kb-search hybrid`. +All tests under `cargo test -p kebab-search hybrid`. ## Definition of Done -- [ ] `cargo check -p kb-search` passes -- [ ] `cargo test -p kb-search hybrid` passes +- [ ] `cargo check -p kebab-search` passes +- [ ] `cargo test -p kebab-search hybrid` passes - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.7, §6.4 search, §0 Q3 diff --git a/tasks/p3/p3-5-app-wiring.md b/tasks/p3/p3-5-app-wiring.md index 206f855..069e563 100644 --- a/tasks/p3/p3-5-app-wiring.md +++ b/tasks/p3/p3-5-app-wiring.md @@ -1,64 +1,64 @@ --- phase: P3 -component: kb-app (facade wiring) +component: kebab-app (facade wiring) task_id: p3-5 -title: "Wire kb-app facade — ingest / search / list / inspect end-to-end" +title: "Wire kebab-app facade — ingest / search / list / inspect end-to-end" status: completed depends_on: [p1-6, p2-2, p3-2, p3-3, p3-4] unblocks: [p4-3, p9-1, p9-2, p9-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§7 components, §7.2 traits, §1 UX scenes (ingest, search, list, inspect), §6.4 config, §10 errors] --- -# p3-5 — Wire kb-app facade +# p3-5 — Wire kebab-app facade ## Goal -Replace the `bail!("not yet wired")` stubs in `kb-app` (currently `ingest`, `search`, `list_docs`, `inspect_doc`, `inspect_chunk`) with real bodies that compose the libraries shipped in P1–P3. After this task, `kb index` actually walks a workspace and persists chunks, and `kb search --mode {lexical,vector,hybrid}` returns real `SearchHit`s. `kb-app::ask` stays stubbed (P4-3 owns it). +Replace the `bail!("not yet wired")` stubs in `kebab-app` (currently `ingest`, `search`, `list_docs`, `inspect_doc`, `inspect_chunk`) with real bodies that compose the libraries shipped in P1–P3. After this task, `kebab index` actually walks a workspace and persists chunks, and `kebab search --mode {lexical,vector,hybrid}` returns real `SearchHit`s. `kebab-app::ask` stays stubbed (P4-3 owns it). ## Why now / why this size -P1–P3 shipped libraries but the CLI is unusable: every facade method bails. Inserting this glue task between P3-4 (last library that completes the retrieval stack) and P4-1 (LLM trait, where `kb-app::ask` will need this same wiring anyway) lets the user run the tool end-to-end for the first time and validates that the library boundaries actually compose. Limiting it to non-LLM commands keeps it a single-session task; `ask` wiring lives in P4-3. +P1–P3 shipped libraries but the CLI is unusable: every facade method bails. Inserting this glue task between P3-4 (last library that completes the retrieval stack) and P4-1 (LLM trait, where `kebab-app::ask` will need this same wiring anyway) lets the user run the tool end-to-end for the first time and validates that the library boundaries actually compose. Limiting it to non-LLM commands keeps it a single-session task; `ask` wiring lives in P4-3. ## Allowed dependencies -`kb-app` may depend on: +`kebab-app` may depend on: -- `kb-core`, `kb-config` (already) -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-sqlite` (P1) -- `kb-search` (P2-2, P3-4) -- `kb-store-vector`, `kb-embed`, `kb-embed-local` (P3-2, P3-3, P3-4) +- `kebab-core`, `kebab-config` (already) +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-sqlite` (P1) +- `kebab-search` (P2-2, P3-4) +- `kebab-store-vector`, `kebab-embed`, `kebab-embed-local` (P3-2, P3-3, P3-4) - `tracing`, `anyhow`, `serde`, `serde_json`, `time`, `dirs`, `toml` (existing) ## Forbidden dependencies -- `kb-llm*`, `kb-rag` (P4 ownership — `ask` stays stubbed) -- `kb-tui`, `kb-desktop` (P9 — those *consume* `kb-app`, not the other way) -- `kb-parse-pdf`, `kb-parse-image`, `kb-parse-audio` (P6/P7/P8) -- network HTTP libs (model download is `kb-embed-local`'s responsibility via fastembed) +- `kebab-llm*`, `kebab-rag` (P4 ownership — `ask` stays stubbed) +- `kebab-tui`, `kebab-desktop` (P9 — those *consume* `kebab-app`, not the other way) +- `kebab-parse-pdf`, `kebab-parse-image`, `kebab-parse-audio` (P6/P7/P8) +- network HTTP libs (model download is `kebab-embed-local`'s responsibility via fastembed) ## Inputs | input | type | source | |-------|------|--------| -| `kb-config::Config` | `kb_config::Config` | loaded once at process start; threaded through every facade call | -| `SourceScope` | `kb_core::SourceScope` | CLI | -| `SearchQuery` | `kb_core::SearchQuery` | CLI | -| `DocFilter` | `kb_core::DocFilter` | CLI | -| `DocumentId` / `ChunkId` | `kb_core::ids::*` | CLI | +| `kebab-config::Config` | `kebab_config::Config` | loaded once at process start; threaded through every facade call | +| `SourceScope` | `kebab_core::SourceScope` | CLI | +| `SearchQuery` | `kebab_core::SearchQuery` | CLI | +| `DocFilter` | `kebab_core::DocFilter` | CLI | +| `DocumentId` / `ChunkId` | `kebab_core::ids::*` | CLI | ## Outputs | output | type | downstream | |--------|------|------------| -| `IngestReport` | `kb_core::IngestReport` | `kb-cli` (`ingest_report.v1`), `kb-eval` (P5) | -| `Vec` | `kb_core::SearchHit` | `kb-cli` (`search_hit.v1`), `kb-rag` (P4-3) | -| `Vec` | `kb_core::DocSummary` | `kb-cli` (`doc_summary.v1`) | -| `CanonicalDocument` / `Chunk` | `kb_core::*` | `kb-cli` (`chunk_inspection.v1`) | +| `IngestReport` | `kebab_core::IngestReport` | `kebab-cli` (`ingest_report.v1`), `kebab-eval` (P5) | +| `Vec` | `kebab_core::SearchHit` | `kebab-cli` (`search_hit.v1`), `kebab-rag` (P4-3) | +| `Vec` | `kebab_core::DocSummary` | `kebab-cli` (`doc_summary.v1`) | +| `CanonicalDocument` / `Chunk` | `kebab_core::*` | `kebab-cli` (`chunk_inspection.v1`) | ## Public surface (signatures only — no new types) -The signatures already live on `kb-app`. This task only swaps the bodies. Frozen surface: +The signatures already live on `kebab-app`. This task only swaps the bodies. Frozen surface: ```rust pub fn ingest(scope: SourceScope, summary_only: bool) -> anyhow::Result; @@ -69,7 +69,7 @@ pub fn search(query: SearchQuery) -> anyhow::Result`, `Arc`, `Arc` so cold-start cost is paid once per process. Document the lifetime model: a CLI invocation paths through `App::open(&Config)` once, runs the requested op, then drops everything on exit. The TUI (P9) holds the `App` for the session. @@ -100,17 +100,17 @@ Each call constructs (or reuses) the retrievers from a single `App` context that ### `list_docs(filter)` / `inspect_doc(id)` / `inspect_chunk(id)` -Direct delegation to `kb_core::DocumentStore` trait methods on `SqliteStore`. No vector / embedding involvement. +Direct delegation to `kebab_core::DocumentStore` trait methods on `SqliteStore`. No vector / embedding involvement. ### Lifecycle -Define an internal `pub(crate) struct App { config: Arc, sqlite: Arc, vector: Option>, embedder: Option>, /* retrievers built per call */ }` with a `pub(crate) fn open(config: &Config) -> Result`. Each public `kb-app::*` function builds an `App` (or accepts one in tests) and uses it. The free functions stay the public API so `kb-cli` and `kb-tui` don't need to refactor. +Define an internal `pub(crate) struct App { config: Arc, sqlite: Arc, vector: Option>, embedder: Option>, /* retrievers built per call */ }` with a `pub(crate) fn open(config: &Config) -> Result`. Each public `kebab-app::*` function builds an `App` (or accepts one in tests) and uses it. The free functions stay the public API so `kebab-cli` and `kebab-tui` don't need to refactor. `vector` and `embedder` are `Option` because the spec allows running KB without embeddings (lexical-only mode for resource-constrained boxes — `provider == "none"`). When absent, `search` with `Vector` / `Hybrid` modes returns `anyhow::Error` with a hint to enable embeddings; `ingest` skips the vector step. ### Versioning -- `parser_version` from `kb-parse-md` constant. +- `parser_version` from `kebab-parse-md` constant. - `chunker_version` from `MdHeadingV1Chunker::chunker_version()`. - `embedding_model` / `embedding_version` from the embedder. - `index_version` from the retriever (or `Lexical`/`Vector` IndexId). @@ -119,15 +119,15 @@ All wired through to the persisted records and the wire output. ## Storage / wire effects -- Writes: `data_dir/kb.sqlite` (assets, documents, blocks, chunks, embedding_records, jobs/ingest_runs), `data_dir/assets//` (when copied), `data_dir/lancedb/chunk_embeddings__.lance/` (when embeddings on), `data_dir/models/fastembed/` (model cache, first run only). +- Writes: `data_dir/kebab.sqlite` (assets, documents, blocks, chunks, embedding_records, jobs/ingest_runs), `data_dir/assets//` (when copied), `data_dir/lancedb/chunk_embeddings__.lance/` (when embeddings on), `data_dir/models/fastembed/` (model cache, first run only). - Reads on subsequent calls: same DB. -- Wire JSON conforms to `ingest_report.v1`, `search_hit.v1`, `doc_summary.v1`, `chunk_inspection.v1` — `kb-cli` already has the wrappers. +- Wire JSON conforms to `ingest_report.v1`, `search_hit.v1`, `doc_summary.v1`, `chunk_inspection.v1` — `kebab-cli` already has the wrappers. ## Test plan | kind | description | fixture / data | |------|-------------|----------------| -| integration | `ingest` over a 3-file fixture workspace produces non-empty `IngestReport` and writes the expected SQLite rows + Lance rows | `crates/kb-app/tests/fixtures/workspace/`, tmp `data_dir` | +| integration | `ingest` over a 3-file fixture workspace produces non-empty `IngestReport` and writes the expected SQLite rows + Lance rows | `crates/kebab-app/tests/fixtures/workspace/`, tmp `data_dir` | | integration | `ingest` with `provider="none"` skips Lance and produces an `IngestReport` with `embeddings_indexed=0` | tmp config | | integration | `ingest` is idempotent: re-running on the same workspace updates `documents.updated_at` and bumps `doc_version` without duplicating rows | tmp `data_dir` | | integration | `search` with `mode=Lexical` returns hits whose `embedding_model` is `None` | tmp `data_dir` | @@ -136,36 +136,36 @@ All wired through to the persisted records and the wire output. | integration | `search` with `mode=Vector` and `provider="none"` returns a clear error | tmp config | | integration | `list_docs` with `tags_any=["rust"]` filters correctly | tmp `data_dir` | | integration | `inspect_doc` round-trips a document; `inspect_chunk` round-trips a chunk | tmp `data_dir` | -| smoke | end-to-end CLI smoke: `kb index` + `kb search --mode hybrid "..."` against the fixture workspace using `cargo run` (manual / `assert_cmd`) | fixture | +| smoke | end-to-end CLI smoke: `kebab index` + `kebab search --mode hybrid "..."` against the fixture workspace using `cargo run` (manual / `assert_cmd`) | fixture | -`#[ignore]` AVX-gated tests follow the P3-3/P3-4 pattern: `require_avx_or_panic()` at the top of each LanceDB-touching body. Default `cargo test -p kb-app` runs the lexical-only and SQLite-only paths. +`#[ignore]` AVX-gated tests follow the P3-3/P3-4 pattern: `require_avx_or_panic()` at the top of each LanceDB-touching body. Default `cargo test -p kebab-app` runs the lexical-only and SQLite-only paths. -All tests under `cargo test -p kb-app`. CLI smoke optional via `assert_cmd` if it doesn't add a heavyweight dep tree. +All tests under `cargo test -p kebab-app`. CLI smoke optional via `assert_cmd` if it doesn't add a heavyweight dep tree. ## Definition of Done -- [ ] `cargo check -p kb-app` passes. -- [ ] `cargo test -p kb-app` passes (default lane). -- [ ] `cargo test -p kb-app -- --ignored` passes on AVX hardware. -- [ ] `cargo run -p kb-cli -- index` succeeds against a non-empty fixture workspace. -- [ ] `cargo run -p kb-cli -- search --mode hybrid ""` returns real hits + citations. -- [ ] `cargo run -p kb-cli -- list` returns the indexed documents. +- [ ] `cargo check -p kebab-app` passes. +- [ ] `cargo test -p kebab-app` passes (default lane). +- [ ] `cargo test -p kebab-app -- --ignored` passes on AVX hardware. +- [ ] `cargo run -p kebab-cli -- index` succeeds against a non-empty fixture workspace. +- [ ] `cargo run -p kebab-cli -- search --mode hybrid ""` returns real hits + citations. +- [ ] `cargo run -p kebab-cli -- list` returns the indexed documents. - [ ] `cargo clippy --workspace --all-targets -- -D warnings` clean. - [ ] No imports outside Allowed dependencies. - [ ] PR links design §1.2, §1.5, §1.6, §7. ## Out of scope -- `kb-app::ask` body — P4-3 (RAG pipeline). -- `kb index --rebuild-fts` CLI option — wire `kb_store_sqlite::rebuild_chunks_fts` only when needed by a downstream task. -- `kb index --resume` checkpointing — design §1.2 mentions it but spec leaves to a P+ refinement; document but defer. +- `kebab-app::ask` body — P4-3 (RAG pipeline). +- `kebab index --rebuild-fts` CLI option — wire `kebab_store_sqlite::rebuild_chunks_fts` only when needed by a downstream task. +- `kebab index --resume` checkpointing — design §1.2 mentions it but spec leaves to a P+ refinement; document but defer. - `--watch` mode — P+. - TUI / desktop integration — P9 consumes the wired facade. ## Risks / notes -- Cold-start cost: first `kb index` run downloads the fastembed model (~470MB) and warms the ONNX session. Surface via `tracing::info!` (already wired in P3-2). -- **Post-merge fix (2026-05)**: original P3-5 implementation left every `kb-cli` subcommand calling the bare `kb_app::*` free functions, which silently re-loaded `Config::load(None)` (XDG default) and ignored the user's `--config ` flag. CLI now goes through `kb_app::*_with_config(cfg, ...)` everywhere. The `#[doc(hidden)] pub fn *_with_config` companions originally framed as "test seam" are now the official config-explicit API for CLI / TUI / integration tests. See [HOTFIXES.md](../HOTFIXES.md) for details. +- Cold-start cost: first `kebab index` run downloads the fastembed model (~470MB) and warms the ONNX session. Surface via `tracing::info!` (already wired in P3-2). +- **Post-merge fix (2026-05)**: original P3-5 implementation left every `kebab-cli` subcommand calling the bare `kebab_app::*` free functions, which silently re-loaded `Config::load(None)` (XDG default) and ignored the user's `--config ` flag. CLI now goes through `kebab_app::*_with_config(cfg, ...)` everywhere. The `#[doc(hidden)] pub fn *_with_config` companions originally framed as "test seam" are now the official config-explicit API for CLI / TUI / integration tests. See [HOTFIXES.md](../HOTFIXES.md) for details. - The `App` lifecycle struct is internal but its construction is the natural seam for adding caching / connection pooling later. Keep it `pub(crate)` so future refactors don't break the CLI. - Mismatched `index_version` across stored records and the live retriever should fail loud at `App::open` (not at first search). Reuse the `tracing::warn!` from `HybridRetriever::new` (P3-4). - The fastembed adapter holds a `tokio::runtime` (P3-3); `App` must be constructed from a synchronous context. Document on `App::open`. diff --git a/tasks/p4/p4-1-llm-trait.md b/tasks/p4/p4-1-llm-trait.md index 2363f72..78e68a5 100644 --- a/tasks/p4/p4-1-llm-trait.md +++ b/tasks/p4/p4-1-llm-trait.md @@ -1,12 +1,12 @@ --- phase: P4 -component: kb-llm (trait crate) +component: kebab-llm (trait crate) task_id: p4-1 title: "LanguageModel trait + GenerateRequest/TokenChunk" status: completed depends_on: [p0-1] unblocks: [p4-2, p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§7.1 GenerateRequest/TokenChunk, §7.2 LanguageModel, §0 Q5 streaming, §3.8 ModelRef] --- @@ -14,16 +14,16 @@ contract_sections: [§7.1 GenerateRequest/TokenChunk, §7.2 LanguageModel, §0 Q ## Goal -Provide the `kb-llm` crate that re-exports the `LanguageModel` trait and helper types (`GenerateRequest`, `TokenChunk`, `FinishReason`, `TokenUsage`, `ModelRef`), plus a `MockLanguageModel` for downstream tests. +Provide the `kebab-llm` crate that re-exports the `LanguageModel` trait and helper types (`GenerateRequest`, `TokenChunk`, `FinishReason`, `TokenUsage`, `ModelRef`), plus a `MockLanguageModel` for downstream tests. ## Why now / why this size -`kb-rag` (p4-3) consumes a `LanguageModel` trait object. Owning the trait + a deterministic mock here lets RAG tests run with no Ollama dependency. Real adapters (Ollama, llama.cpp, candle) live in p4-2 and beyond. +`kebab-rag` (p4-3) consumes a `LanguageModel` trait object. Owning the trait + a deterministic mock here lets RAG tests run with no Ollama dependency. Real adapters (Ollama, llama.cpp, candle) live in p4-2 and beyond. ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde` - `thiserror` - `tracing` @@ -31,13 +31,13 @@ Provide the `kb-llm` crate that re-exports the `LanguageModel` trait and helper ## Forbidden dependencies -- `reqwest`, `ureq`, `tokio`, `whisper-rs`, `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-rag`, `kb-tui`, `kb-desktop` +- `reqwest`, `ureq`, `tokio`, `whisper-rs`, `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `GenerateRequest` | `kb_core::GenerateRequest` | RAG pipeline | +| `GenerateRequest` | `kebab_core::GenerateRequest` | RAG pipeline | | concrete adapter at runtime | `dyn LanguageModel` | p4-2+ | ## Outputs @@ -45,12 +45,12 @@ Provide the `kb-llm` crate that re-exports the `LanguageModel` trait and helper | output | type | downstream | |--------|------|------------| | streaming `TokenChunk` iterator | `Box> + Send>` | RAG pipeline | -| `ModelRef` identity | `kb_core::ModelRef` | Answer.model | +| `ModelRef` identity | `kebab_core::ModelRef` | Answer.model | ## Public surface (signatures only — no new types) ```rust -pub use kb_core::{LanguageModel, GenerateRequest, TokenChunk, FinishReason, TokenUsage, ModelRef}; +pub use kebab_core::{LanguageModel, GenerateRequest, TokenChunk, FinishReason, TokenUsage, ModelRef}; /// Test-only deterministic mock. Compiled only when `mock` feature is on. #[cfg(feature = "mock")] @@ -59,12 +59,12 @@ pub struct MockLanguageModel { pub provider: String, pub context_tokens: usize, pub canned_response: String, // emitted token-by-token - pub canned_finish: kb_core::FinishReason, - pub canned_usage: kb_core::TokenUsage, + pub canned_finish: kebab_core::FinishReason, + pub canned_usage: kebab_core::TokenUsage, } #[cfg(feature = "mock")] -impl kb_core::LanguageModel for MockLanguageModel { /* per §7.2 */ } +impl kebab_core::LanguageModel for MockLanguageModel { /* per §7.2 */ } ``` ## Behavior contract @@ -89,12 +89,12 @@ impl kb_core::LanguageModel for MockLanguageModel { /* per §7.2 */ } | unit | concatenation of streamed `TokenChunk::Token` equals canned text (truncated by stop strings) | inline | | contract | `model_ref()` populates `provider` and leaves `dimensions = None` | inline | -All tests under `cargo test -p kb-llm`. +All tests under `cargo test -p kebab-llm`. ## Definition of Done -- [ ] `cargo check -p kb-llm` passes -- [ ] `cargo test -p kb-llm` passes +- [ ] `cargo check -p kebab-llm` passes +- [ ] `cargo test -p kebab-llm` passes - [ ] No HTTP / async runtime deps present - [ ] PR links design §7.2 LanguageModel, §0 Q5 diff --git a/tasks/p4/p4-2-ollama-adapter.md b/tasks/p4/p4-2-ollama-adapter.md index 6dae6c0..cdd9538 100644 --- a/tasks/p4/p4-2-ollama-adapter.md +++ b/tasks/p4/p4-2-ollama-adapter.md @@ -1,12 +1,12 @@ --- phase: P4 -component: kb-llm-local (Ollama adapter) +component: kebab-llm-local (Ollama adapter) task_id: p4-2 title: "OllamaLanguageModel — streaming /api/generate" status: completed depends_on: [p4-1] unblocks: [p4-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [design §7.2 LanguageModel, report §11.2 Ollama, design §6.4 [models.llm], design §0 Q5 streaming, design §10 errors] --- @@ -18,13 +18,13 @@ Implement `OllamaLanguageModel` against Ollama's local HTTP API (`POST /api/gene ## Why now / why this size -First real LM. Required for `kb ask` to function. Isolated from RAG pipeline so swapping providers stays config-only. +First real LM. Required for `kebab ask` to function. Isolated from RAG pipeline so swapping providers stays config-only. ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-llm` +- `kebab-core` +- `kebab-config` +- `kebab-llm` - `reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }` - `serde`, `serde_json` - `tracing` @@ -32,21 +32,21 @@ First real LM. Required for `kb ask` to function. Isolated from RAG pipeline so ## Forbidden dependencies -- `tokio`, `async-std`, `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-rag`, `kb-tui`, `kb-desktop`. (Streaming uses `reqwest::blocking::Response::bytes_stream` via line-delimited JSON; no async runtime needed.) +- `tokio`, `async-std`, `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-rag`, `kebab-tui`, `kebab-desktop`. (Streaming uses `reqwest::blocking::Response::bytes_stream` via line-delimited JSON; no async runtime needed.) ## Inputs | input | type | source | |-------|------|--------| -| `kb-config::Config.models.llm` | endpoint, model, context, temperature, seed | runtime | -| `GenerateRequest` | `kb_core::GenerateRequest` | RAG pipeline | +| `kebab-config::Config.models.llm` | endpoint, model, context, temperature, seed | runtime | +| `GenerateRequest` | `kebab_core::GenerateRequest` | RAG pipeline | | Ollama HTTP server (local) | `http://127.0.0.1:11434` | external process | ## Outputs | output | type | downstream | |--------|------|------------| -| streaming `TokenChunk` iterator | per §7.2 | `kb-rag` | +| streaming `TokenChunk` iterator | per §7.2 | `kebab-rag` | | `ModelRef` | `{ id, provider="ollama", dimensions=None }` | `Answer.model` | ## Public surface (signatures only — no new types) @@ -55,14 +55,14 @@ First real LM. Required for `kb ask` to function. Isolated from RAG pipeline so pub struct OllamaLanguageModel { /* internal: reqwest::blocking::Client + config */ } impl OllamaLanguageModel { - pub fn new(config: &kb_config::Config) -> anyhow::Result; + pub fn new(config: &kebab_config::Config) -> anyhow::Result; } -impl kb_core::LanguageModel for OllamaLanguageModel { - fn model_ref(&self) -> kb_core::ModelRef; +impl kebab_core::LanguageModel for OllamaLanguageModel { + fn model_ref(&self) -> kebab_core::ModelRef; fn context_tokens(&self) -> usize; - fn generate_stream(&self, req: kb_core::GenerateRequest) - -> anyhow::Result> + Send>>; + fn generate_stream(&self, req: kebab_core::GenerateRequest) + -> anyhow::Result> + Send>>; } ``` @@ -93,7 +93,7 @@ impl kb_core::LanguageModel for OllamaLanguageModel { - UTF-8 boundary: buffer incomplete byte sequences across stream lines before emitting `TokenChunk::Token`. - Determinism: with `temperature=0` and fixed `seed`, Ollama's output is reproducible (modulo nondeterminism in the model itself); tests that verify determinism use a fixed seed and may rely on aggregate hash with tolerance, NOT byte equality. - `model_ref().provider = "ollama"`, `dimensions = None`. -- Reachability check: `OllamaLanguageModel::new` does NOT eagerly hit the network; first failure surfaces on `generate_stream`. Use `kb doctor` (separate task) to probe. +- Reachability check: `OllamaLanguageModel::new` does NOT eagerly hit the network; first failure surfaces on `generate_stream`. Use `kebab doctor` (separate task) to probe. ## Storage / wire effects @@ -112,12 +112,12 @@ impl kb_core::LanguageModel for OllamaLanguageModel { | determinism | identical request + temperature=0 + seed=0 produces identical token stream against mock | mocked HTTP | | `#[ignore]` integration | real Ollama on `localhost:11434` with `qwen2.5:14b-instruct` produces non-empty output | requires user opt-in | -All non-ignored tests under `cargo test -p kb-llm-local`. Real-LM integration runs via `cargo test -p kb-llm-local -- --ignored`. +All non-ignored tests under `cargo test -p kebab-llm-local`. Real-LM integration runs via `cargo test -p kebab-llm-local -- --ignored`. ## Definition of Done -- [ ] `cargo check -p kb-llm-local` passes -- [ ] `cargo test -p kb-llm-local` passes (mocked tests; real LM behind `#[ignore]`) +- [ ] `cargo check -p kebab-llm-local` passes +- [ ] `cargo test -p kebab-llm-local` passes (mocked tests; real LM behind `#[ignore]`) - [ ] No async runtime present (uses `reqwest::blocking`) - [ ] No imports outside Allowed dependencies - [ ] PR links design §11.2, §0 Q5, §10 @@ -125,7 +125,7 @@ All non-ignored tests under `cargo test -p kb-llm-local`. Real-LM integration ru ## Out of scope - llama.cpp / candle adapters (P+). -- Embedding via Ollama's `/api/embed` endpoint (alternate adapter inside `kb-embed-local` if requested later). +- Embedding via Ollama's `/api/embed` endpoint (alternate adapter inside `kebab-embed-local` if requested later). - Cancellation / abort tokens (P+). - Connection pooling tuning (default `reqwest::blocking` is sufficient for single-user CLI). diff --git a/tasks/p4/p4-3-rag-pipeline.md b/tasks/p4/p4-3-rag-pipeline.md index 4eae6b8..aaaf77c 100644 --- a/tasks/p4/p4-3-rag-pipeline.md +++ b/tasks/p4/p4-3-rag-pipeline.md @@ -1,12 +1,12 @@ --- phase: P4 -component: kb-rag +component: kebab-rag task_id: p4-3 title: "RAG pipeline: retrieve → gate → pack → generate → cite-validate" status: completed depends_on: [p3-4, p4-2] unblocks: [p5-1] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§0 Q4 refusal (two-layer), §0 Q7 footer, §1.1–1.4 ask scenes, §2.3 Answer wire, §3.8 internal Answer, §6.4 [rag], §10 errors] --- @@ -22,11 +22,11 @@ This is the user-facing payoff. Splitting it further would couple too many inter ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-search` (Retriever trait object) -- `kb-llm` (LanguageModel trait object) -- `kb-store-sqlite` (read chunk full text/section + write `answers` row) +- `kebab-core` +- `kebab-config` +- `kebab-search` (Retriever trait object) +- `kebab-llm` (LanguageModel trait object) +- `kebab-store-sqlite` (read chunk full text/section + write `answers` row) - `serde`, `serde_json` - `regex` (for citation marker extraction) - `time` @@ -35,51 +35,51 @@ This is the user-facing payoff. Splitting it further would couple too many inter ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-vector` (only via Retriever trait), `kb-embed*` (only via Retriever), `kb-llm-local` (only via LanguageModel trait), `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-vector` (only via Retriever trait), `kebab-embed*` (only via Retriever), `kebab-llm-local` (only via LanguageModel trait), `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `query: &str` | text | `kb-app::ask` | +| `query: &str` | text | `kebab-app::ask` | | `AskOpts` | k, explain, mode, temperature, seed | CLI | | `dyn Retriever` | hybrid retriever from p3-4 | runtime injection | | `dyn LanguageModel` | from p4-2 (or mock) | runtime injection | | `dyn DocumentStore` | for chunk full-text fetch | from p1-6 | -| `kb-config::Config.rag` | `prompt_template_version`, `score_gate`, `max_context_tokens` | runtime | +| `kebab-config::Config.rag` | `prompt_template_version`, `score_gate`, `max_context_tokens` | runtime | ## Outputs | output | type | downstream | |--------|------|------------| -| `Answer` | `kb_core::Answer` | `kb-cli` printer, `answers` table | +| `Answer` | `kebab_core::Answer` | `kebab-cli` printer, `answers` table | | `answers` table row | SQLite | history, eval | ## Public surface (signatures only — no new types) ```rust pub struct RagPipeline { - retriever: std::sync::Arc, - llm: std::sync::Arc, - docs: std::sync::Arc, - config: kb_config::Config, + retriever: std::sync::Arc, + llm: std::sync::Arc, + docs: std::sync::Arc, + config: kebab_config::Config, } impl RagPipeline { pub fn new( - config: kb_config::Config, - retriever: std::sync::Arc, - llm: std::sync::Arc, - docs: std::sync::Arc, + config: kebab_config::Config, + retriever: std::sync::Arc, + llm: std::sync::Arc, + docs: std::sync::Arc, ) -> Self; - pub fn ask(&self, query: &str, opts: AskOpts) -> anyhow::Result; + pub fn ask(&self, query: &str, opts: AskOpts) -> anyhow::Result; } pub struct AskOpts { pub k: usize, pub explain: bool, - pub mode: kb_core::SearchMode, + pub mode: kebab_core::SearchMode, pub temperature: Option, pub seed: Option, pub stream_sink: Option>, // tty/UI token streaming @@ -158,12 +158,12 @@ pub struct AskOpts { | determinism | identical inputs + temperature=0 + seed=0 → identical Answer (snapshot) | mock | | snapshot | `Answer` JSON for fixed query stable | `fixtures/rag/run-1.json` | -All tests under `cargo test -p kb-rag` with no real Ollama (mock LM only). +All tests under `cargo test -p kebab-rag` with no real Ollama (mock LM only). ## Definition of Done -- [ ] `cargo check -p kb-rag` passes -- [ ] `cargo test -p kb-rag` passes +- [ ] `cargo check -p kebab-rag` passes +- [ ] `cargo test -p kebab-rag` passes - [ ] No imports outside Allowed dependencies - [ ] All paths write an `answers` row - [ ] Output JSON conforms to `answer.v1` @@ -178,9 +178,9 @@ All tests under `cargo test -p kb-rag` with no real Ollama (mock LM only). ## Risks / notes -- Citation regex is STRICT `\[#(\d{1,3})\]` only. Models that emit `[1]`/`[ #1 ]`/`[foo]` are treated as no-marker → refusal. This is intentional: a noisy citation grammar lets prose `[1]` or `vec![1]` slip through as false positives, which corrupts both `grounded` and `kb eval` `citation_coverage`. The prompt template (`rag-v1`) explicitly instructs `[#번호]`. -- **Post-merge fix (2026-05)**: kb-cli's `Cmd::Ask` arm originally called bare `kb_app::ask(query, opts)`, ignoring `--config ` and silently using XDG defaults (manifested as wrong model id / score_gate / data_dir surfacing in `Answer.retrieval`). Fixed by routing through `kb_app::ask_with_config(cfg, query, opts)`. See [HOTFIXES.md](../HOTFIXES.md). -- **Post-merge fix (2026-05)**: `config.rag.score_gate` default `0.05` was incompatible with hybrid RRF `fusion_score` (raw range `(0, 2/k_rrf]` ≈ `≤ 0.033` at default k_rrf=60), refusing every hybrid `kb ask`. Closed by normalizing RRF in p3-4 to `[0, 1]`. See p3-4 spec + HOTFIXES.md. +- Citation regex is STRICT `\[#(\d{1,3})\]` only. Models that emit `[1]`/`[ #1 ]`/`[foo]` are treated as no-marker → refusal. This is intentional: a noisy citation grammar lets prose `[1]` or `vec![1]` slip through as false positives, which corrupts both `grounded` and `kebab eval` `citation_coverage`. The prompt template (`rag-v1`) explicitly instructs `[#번호]`. +- **Post-merge fix (2026-05)**: kebab-cli's `Cmd::Ask` arm originally called bare `kebab_app::ask(query, opts)`, ignoring `--config ` and silently using XDG defaults (manifested as wrong model id / score_gate / data_dir surfacing in `Answer.retrieval`). Fixed by routing through `kebab_app::ask_with_config(cfg, query, opts)`. See [HOTFIXES.md](../HOTFIXES.md). +- **Post-merge fix (2026-05)**: `config.rag.score_gate` default `0.05` was incompatible with hybrid RRF `fusion_score` (raw range `(0, 2/k_rrf]` ≈ `≤ 0.033` at default k_rrf=60), refusing every hybrid `kebab ask`. Closed by normalizing RRF in p3-4 to `[0, 1]`. See p3-4 spec + HOTFIXES.md. - `stream_sink` channel: pipeline `send`s tokens; if the receiver is dropped (caller cancelled), `SendError` is silently swallowed and generation continues to completion (so the `Answer` row still gets persisted). Pipeline does NOT panic on a dead sink. - `temperature=0` does not fully eliminate stochasticity in some quantized Ollama models; document this and rely on `must_contain` rule-based metrics in P5 instead of exact match. - Prompt-injection defense lives entirely in the system prompt; do NOT mutate `[근거]` text. If chunk text contains `<|system|>` or similar tokens, do not strip them — they are inert when wrapped. diff --git a/tasks/p5/p5-1-golden-fixture-runner.md b/tasks/p5/p5-1-golden-fixture-runner.md index 5cc7e47..66b196b 100644 --- a/tasks/p5/p5-1-golden-fixture-runner.md +++ b/tasks/p5/p5-1-golden-fixture-runner.md @@ -1,12 +1,12 @@ --- phase: P5 -component: kb-eval (runner) +component: kebab-eval (runner) task_id: p5-1 title: "Golden query fixture loader + per-query runner" status: completed depends_on: [p4-3] unblocks: [p5-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5.7 eval_runs/eval_query_results, §6.3 runs_dir, phase epic tasks/phase-5-evaluation.md] --- @@ -14,7 +14,7 @@ contract_sections: [§5.7 eval_runs/eval_query_results, §6.3 runs_dir, phase ep ## Goal -Load `fixtures/golden_queries.yaml`, run each query through `kb-app` (lexical / vector / hybrid / rag), and persist results into `eval_query_results` + `runs_dir//per_query.jsonl`. +Load `fixtures/golden_queries.yaml`, run each query through `kebab-app` (lexical / vector / hybrid / rag), and persist results into `eval_query_results` + `runs_dir//per_query.jsonl`. ## Why now / why this size @@ -22,10 +22,10 @@ The runner is the data collector; metrics computation is p5-2's job. Splitting t ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-app` (calls facade for search / ask) -- `kb-store-sqlite` (writes eval rows) +- `kebab-core` +- `kebab-config` +- `kebab-app` (calls facade for search / ask) +- `kebab-store-sqlite` (writes eval rows) - `serde`, `serde_yaml`, `serde_json` - `time` - `tracing` @@ -33,7 +33,7 @@ The runner is the data collector; metrics computation is p5-2's job. Splitting t ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-vector`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag` (all reached via `kb-app` facade only), `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-vector`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag` (all reached via `kebab-app` facade only), `kebab-tui`, `kebab-desktop` ## Inputs @@ -41,7 +41,7 @@ The runner is the data collector; metrics computation is p5-2's job. Splitting t |-------|------|--------| | `fixtures/golden_queries.yaml` | YAML | repo-shipped | | `EvalRunOpts` | suite, mode, with_rag, k, temperature, seed | CLI | -| `kb-app` facade | search/ask | runtime | +| `kebab-app` facade | search/ask | runtime | ## Outputs @@ -50,7 +50,7 @@ The runner is the data collector; metrics computation is p5-2's job. Splitting t | `eval_runs` row | SQLite | p5-2, history | | `eval_query_results` rows | SQLite | p5-2 | | `runs_dir//per_query.jsonl` | filesystem | external tools, audits | -| `EvalRun` struct | `kb_eval::EvalRun` | caller | +| `EvalRun` struct | `kebab_eval::EvalRun` | caller | ## Public surface (signatures only — no new types) @@ -58,9 +58,9 @@ The runner is the data collector; metrics computation is p5-2's job. Splitting t pub struct GoldenQuery { pub id: String, pub query: String, - pub lang: kb_core::Lang, - pub expected_doc_ids: Vec, - pub expected_chunk_ids: Vec, + pub lang: kebab_core::Lang, + pub expected_doc_ids: Vec, + pub expected_chunk_ids: Vec, pub must_contain: Vec, pub forbidden: Vec, pub difficulty: Option, @@ -68,7 +68,7 @@ pub struct GoldenQuery { pub struct EvalRunOpts { pub suite: String, // "golden" default - pub mode: kb_core::SearchMode, + pub mode: kebab_core::SearchMode, pub with_rag: bool, pub k: usize, pub temperature: Option, @@ -86,9 +86,9 @@ pub struct EvalRun { pub struct QueryResult { pub query_id: String, pub query: String, - pub mode: kb_core::SearchMode, - pub hits_top_k: Vec, - pub answer: Option, + pub mode: kebab_core::SearchMode, + pub hits_top_k: Vec, + pub answer: Option, pub elapsed_ms: u32, pub error: Option, } @@ -103,10 +103,10 @@ pub fn run_eval(opts: &EvalRunOpts) -> anyhow::Result; - Parses YAML; required fields: `id`, `query`. Optional: everything else (defaults to empty / `None`). - Validates uniqueness of `id` and that `expected_doc_ids` / `expected_chunk_ids` exist in DB; missing → return error listing the offenders. - `run_eval`: - - Loads `fixtures/golden_queries.yaml` (path overridable via env `KB_EVAL_GOLDEN`). + - Loads `fixtures/golden_queries.yaml` (path overridable via env `KEBAB_EVAL_GOLDEN`). - Generates `run_id = "run_" + ulid_lower()`. - - Captures `config_snapshot_json`: serialized `kb_config::Config` plus `chunker_version`, `embedding_model+version+dims`, `llm.model_id`, `prompt_template_version`, `score_gate`, `rrf_k`, `index_version`. - - For each query: call `kb_app::search(SearchQuery { mode: opts.mode, k: opts.k, .. })`. If `opts.with_rag`, also call `kb_app::ask(query, AskOpts { mode: opts.mode, k: opts.k, explain: true, temperature: opts.temperature, seed: opts.seed, .. })`. + - Captures `config_snapshot_json`: serialized `kebab_config::Config` plus `chunker_version`, `embedding_model+version+dims`, `llm.model_id`, `prompt_template_version`, `score_gate`, `rrf_k`, `index_version`. + - For each query: call `kebab_app::search(SearchQuery { mode: opts.mode, k: opts.k, .. })`. If `opts.with_rag`, also call `kebab_app::ask(query, AskOpts { mode: opts.mode, k: opts.k, explain: true, temperature: opts.temperature, seed: opts.seed, .. })`. - Each `QueryResult` measured by elapsed wall-clock (ms). - Errors are caught per-query (do not abort the run). Failed queries record `error: Some(msg)` and `hits_top_k = vec![]`. - Determinism: with `temperature=0` and fixed `seed`, two consecutive runs produce byte-identical `per_query.jsonl` for non-RAG queries; RAG queries may differ in negligible token budget telemetry. @@ -130,12 +130,12 @@ pub fn run_eval(opts: &EvalRunOpts) -> anyhow::Result; | determinism | re-running same suite + fixed seed → identical `per_query.jsonl` (lexical only) | tmp DB, fixed corpus | | snapshot | `EvalRun` (with mock LM for `with_rag`) JSON stable | `fixtures/eval/run-1.json` | -All tests under `cargo test -p kb-eval runner`. +All tests under `cargo test -p kebab-eval runner`. ## Definition of Done -- [ ] `cargo check -p kb-eval` passes -- [ ] `cargo test -p kb-eval runner` passes +- [ ] `cargo check -p kebab-eval` passes +- [ ] `cargo test -p kebab-eval runner` passes - [ ] `fixtures/golden_queries.yaml` template shipped (≥ 5 example entries) - [ ] No imports outside Allowed dependencies - [ ] PR links design §5.7 diff --git a/tasks/p5/p5-2-metrics-compare.md b/tasks/p5/p5-2-metrics-compare.md index cc6e6e2..d525ac7 100644 --- a/tasks/p5/p5-2-metrics-compare.md +++ b/tasks/p5/p5-2-metrics-compare.md @@ -1,12 +1,12 @@ --- phase: P5 -component: kb-eval (metrics + compare) +component: kebab-eval (metrics + compare) task_id: p5-2 title: "Metrics computation + compare report" status: completed depends_on: [p5-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§5.7 eval_runs.aggregate_json, phase epic tasks/phase-5-evaluation.md] --- @@ -14,7 +14,7 @@ contract_sections: [§5.7 eval_runs.aggregate_json, phase epic tasks/phase-5-eva ## Goal -Compute hit@k, MRR, recall@k_doc, citation_coverage, groundedness, empty_result_rate, refusal_correctness from stored `eval_query_results`. Write `aggregate_json` back into `eval_runs`. Provide `kb eval compare a b` that diffs two runs. +Compute hit@k, MRR, recall@k_doc, citation_coverage, groundedness, empty_result_rate, refusal_correctness from stored `eval_query_results`. Write `aggregate_json` back into `eval_runs`. Provide `kebab eval compare a b` that diffs two runs. ## Why now / why this size @@ -22,16 +22,16 @@ Metric formulas + comparison logic are pure computation. Splitting them from p5- ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-store-sqlite` (read eval rows, write `aggregate_json`) +- `kebab-core` +- `kebab-config` +- `kebab-store-sqlite` (read eval rows, write `aggregate_json`) - `serde`, `serde_json` - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-app`, `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-vector`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-app`, `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-vector`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs @@ -46,7 +46,7 @@ Metric formulas + comparison logic are pure computation. Splitting them from p5- | output | type | downstream | |--------|------|------------| | `eval_runs.aggregate_json` updated | SQLite | history, CI checks | -| `CompareReport` | `kb_eval::CompareReport` | `kb-cli` printer | +| `CompareReport` | `kebab_eval::CompareReport` | `kebab-cli` printer | | optional `runs_dir//report.md` | filesystem | human-readable summary | ## Public surface (signatures only — no new types) @@ -127,15 +127,15 @@ pub fn render_report_md(report: &CompareReport) -> String; | determinism | running `compute_aggregate` twice produces identical `AggregateMetrics` | inline | | snapshot | `CompareReport` JSON for a fixed pair of runs stable | `fixtures/eval/compare-1.json` | -All tests under `cargo test -p kb-eval metrics`. +All tests under `cargo test -p kebab-eval metrics`. ## Definition of Done -- [ ] `cargo check -p kb-eval` passes -- [ ] `cargo test -p kb-eval metrics` passes +- [ ] `cargo check -p kebab-eval` passes +- [ ] `cargo test -p kebab-eval metrics` passes - [ ] No imports outside Allowed dependencies - [ ] `eval_runs.aggregate_json` always populated after `store_aggregate` -- [ ] `kb eval compare` CLI surface integrated via `kb-app` (call `compare_runs` + `render_report_md`) +- [ ] `kebab eval compare` CLI surface integrated via `kebab-app` (call `compare_runs` + `render_report_md`) - [ ] PR links phase epic tasks/phase-5-evaluation.md ## Out of scope @@ -172,11 +172,11 @@ same one this spec defines, the names / wiring just differ. / `compare_runs(a, b)` so integration tests can drive the pipeline against a TempDir-backed `Config`. The no-arg forms wrap them with `Config::load(None)`. -- **CLI surface lives on `kb-cli` directly, not via `kb-app`.** DoD - asks for `kb eval compare` to be reached "via kb-app", but `kb-app` - already depends on `kb-eval` (the P5-1 runner uses the App facade), - so routing the CLI through `kb-app` would form a cycle. `kb-cli` → - `kb-eval` is wired directly; `kb-app` is unchanged. +- **CLI surface lives on `kebab-cli` directly, not via `kebab-app`.** DoD + asks for `kebab eval compare` to be reached "via kebab-app", but `kebab-app` + already depends on `kebab-eval` (the P5-1 runner uses the App facade), + so routing the CLI through `kebab-app` would form a cycle. `kebab-cli` → + `kebab-eval` is wired directly; `kebab-app` is unchanged. - **`AggregateMetrics` is `Serialize + Deserialize`.** The spec defines only the field shape; we add `Deserialize` so the stored `aggregate_json` can round-trip back into the type for follow-up @@ -184,12 +184,12 @@ same one this spec defines, the names / wiring just differ. - **`anyhow`** is used in `Result` returns since the rest of the workspace already speaks anyhow; not in the spec's Allowed list but matches every other crate. -- **`kb-eval` crate-level `kb-app` dep stays.** The crate already - depends on `kb-app` from P5-1 (the runner uses the `App` facade), so +- **`kebab-eval` crate-level `kebab-app` dep stays.** The crate already + depends on `kebab-app` from P5-1 (the runner uses the `App` facade), so the Cargo.toml entry remains. The new modules (`metrics.rs`, - `compare.rs`) do not import `kb-app` themselves — they're behind the + `compare.rs`) do not import `kebab-app` themselves — they're behind the same crate boundary as the runner, but the metric/compare *surface* - is `kb-app`-clean. Splitting the crate to avoid a transitive Cargo + is `kebab-app`-clean. Splitting the crate to avoid a transitive Cargo edge would be churn for no behavior gain. - **`citation_coverage` is intentionally weaker than the spec literal.** Spec calls for "every citation resolves to a real chunk in the DB". diff --git a/tasks/p6/p6-1-image-extractor-exif.md b/tasks/p6/p6-1-image-extractor-exif.md index 390f3aa..ff6449a 100644 --- a/tasks/p6/p6-1-image-extractor-exif.md +++ b/tasks/p6/p6-1-image-extractor-exif.md @@ -1,12 +1,12 @@ --- phase: P6 -component: kb-parse-image (image extractor + EXIF) +component: kebab-parse-image (image extractor + EXIF) task_id: p6-1 title: "Image Extractor producing single-block CanonicalDocument + EXIF metadata" status: planned depends_on: [p0-1, p1-6] unblocks: [p6-2, p6-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 Block::ImageRef + ImageRefBlock, §3.7a OcrText/ModelCaption stubs, §9.1 image extraction policy, §9 versioning] --- @@ -22,8 +22,8 @@ Establishes the image-as-document contract and decouples extraction (asset → I ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `image = "0.25"` (decoding for size + format detect) - `kamadak-exif` for EXIF - `serde`, `serde_json` @@ -33,31 +33,31 @@ Establishes the image-as-document contract and decouples extraction (asset → I ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop`, OCR libs, LLM libs +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop`, OCR libs, LLM libs ## Inputs | input | type | source | |-------|------|--------| -| `RawAsset` | `kb_core::RawAsset` | from `kb-source-fs` | +| `RawAsset` | `kebab_core::RawAsset` | from `kebab-source-fs` | | image bytes | `&[u8]` | filesystem | -| `parser_version` | `kb_core::ParserVersion` | constant in this crate (`"image-meta-v1"`) | +| `parser_version` | `kebab_core::ParserVersion` | constant in this crate (`"image-meta-v1"`) | ## Outputs | output | type | downstream | |--------|------|------------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | `kb-chunk` (image-region chunker) → `kb-store-sqlite` | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | `kebab-chunk` (image-region chunker) → `kebab-store-sqlite` | ## Public surface (signatures only — no new types) ```rust pub struct ImageExtractor; -impl kb_core::Extractor for ImageExtractor { - fn supports(&self, m: &kb_core::MediaType) -> bool { matches!(m, kb_core::MediaType::Image(_)) } - fn parser_version(&self) -> kb_core::ParserVersion { kb_core::ParserVersion("image-meta-v1".into()) } - fn extract(&self, ctx: &kb_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; +impl kebab_core::Extractor for ImageExtractor { + fn supports(&self, m: &kebab_core::MediaType) -> bool { matches!(m, kebab_core::MediaType::Image(_)) } + fn parser_version(&self) -> kebab_core::ParserVersion { kebab_core::ParserVersion("image-meta-v1".into()) } + fn extract(&self, ctx: &kebab_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; } ``` @@ -69,7 +69,7 @@ impl kb_core::Extractor for ImageExtractor { - `metadata.source_type = SourceType::Reference` (per design enum); `trust_level = TrustLevel::Primary`; `tags`/`aliases` empty. - `metadata.user["exif"]` = JSON object with whitelisted EXIF tags (DateTimeOriginal, GPS lat/lon, Make, Model, Orientation, Software). Missing tags omitted. - `metadata.user["dimensions"] = { "w": , "h": , "format": "" }`. -- `provenance` includes `Discovered`, `Parsed` events (no Normalized — ID assignment happens here directly per §3.4 stub from p1-4 logic, OR pipe through `kb-normalize` if available; this task's choice: emit a fully formed CanonicalDocument with deterministic IDs by calling `kb_core::id_for_doc` and `kb_core::id_for_block` directly). +- `provenance` includes `Discovered`, `Parsed` events (no Normalized — ID assignment happens here directly per §3.4 stub from p1-4 logic, OR pipe through `kebab-normalize` if available; this task's choice: emit a fully formed CanonicalDocument with deterministic IDs by calling `kebab_core::id_for_doc` and `kebab_core::id_for_block` directly). - Failure modes: - Truncated/corrupt image → still emits a CanonicalDocument with `dimensions = null`, EXIF empty, `Provenance` warning event with the decoder error message. - Unsupported format → `anyhow::Error` (caller skips). @@ -77,7 +77,7 @@ impl kb_core::Extractor for ImageExtractor { ## Storage / wire effects -- None directly (the caller persists via `kb-store-sqlite`). +- None directly (the caller persists via `kebab-store-sqlite`). ## Test plan @@ -90,12 +90,12 @@ impl kb_core::Extractor for ImageExtractor { | determinism | identical bytes → identical `doc_id`, `block_id` across two runs | inline | | snapshot | `CanonicalDocument` JSON stable for fixture | `fixtures/image/red-100x50.png` | -All tests under `cargo test -p kb-parse-image`. +All tests under `cargo test -p kebab-parse-image`. ## Definition of Done -- [ ] `cargo check -p kb-parse-image` passes -- [ ] `cargo test -p kb-parse-image` passes +- [ ] `cargo check -p kebab-parse-image` passes +- [ ] `cargo test -p kebab-parse-image` passes - [ ] No OCR/caption/embedding code present - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.4, §9.1 diff --git a/tasks/p6/p6-2-ocr-adapter.md b/tasks/p6/p6-2-ocr-adapter.md index 809f32d..f349773 100644 --- a/tasks/p6/p6-2-ocr-adapter.md +++ b/tasks/p6/p6-2-ocr-adapter.md @@ -1,12 +1,12 @@ --- phase: P6 -component: kb-parse-image (OCR adapter) +component: kebab-parse-image (OCR adapter) task_id: p6-2 title: "OcrEngine trait + Tesseract adapter (Apple Vision feature-gated)" status: planned depends_on: [p6-1] unblocks: [p6-3] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 ImageRefBlock.ocr, §3.7a OcrText/OcrRegion, §9.1 OCR vs caption provenance] --- @@ -22,9 +22,9 @@ Strict separation of OCR (observed text) from caption (model-generated). Confini ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-parse-image` (consumes its types) +- `kebab-core` +- `kebab-config` +- `kebab-parse-image` (consumes its types) - `tesseract = "0.13"` (feature `tesseract`, default ON) - For feature `apple-vision`: `std::process::Command` only (sidecar binary, not a Rust dep) - `serde`, `serde_json` @@ -34,21 +34,21 @@ Strict separation of OCR (observed text) from caption (model-generated). Confini ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| | image bytes | `&[u8]` | from extractor | -| optional language hint | `kb_core::Lang` | metadata | -| `kb-config` OCR settings | engine name, languages | runtime | +| optional language hint | `kebab_core::Lang` | metadata | +| `kebab-config` OCR settings | engine name, languages | runtime | ## Outputs | output | type | downstream | |--------|------|------------| -| `OcrText` | `kb_core::OcrText` | merged into `ImageRefBlock.ocr` | +| `OcrText` | `kebab_core::OcrText` | merged into `ImageRefBlock.ocr` | ## Public surface (signatures only — no new types) @@ -56,11 +56,11 @@ Strict separation of OCR (observed text) from caption (model-generated). Confini pub trait OcrEngine: Send + Sync { fn engine_name(&self) -> &'static str; fn engine_version(&self) -> String; - fn recognize(&self, image_bytes: &[u8], lang_hint: Option<&kb_core::Lang>) -> anyhow::Result; + fn recognize(&self, image_bytes: &[u8], lang_hint: Option<&kebab_core::Lang>) -> anyhow::Result; } pub struct TesseractOcr { /* internal: lazy api handle */ } -impl TesseractOcr { pub fn new(config: &kb_config::Config) -> anyhow::Result; } +impl TesseractOcr { pub fn new(config: &kebab_config::Config) -> anyhow::Result; } impl OcrEngine for TesseractOcr { /* per trait */ } #[cfg(feature = "apple-vision")] @@ -71,8 +71,8 @@ impl OcrEngine for AppleVisionOcr { /* per trait */ } pub fn apply_ocr( engine: &dyn OcrEngine, image_bytes: &[u8], - block: &mut kb_core::ImageRefBlock, - lang_hint: Option<&kb_core::Lang>, + block: &mut kebab_core::ImageRefBlock, + lang_hint: Option<&kebab_core::Lang>, ) -> anyhow::Result<()>; ``` @@ -85,7 +85,7 @@ pub fn apply_ocr( - `joined` = `regions.iter().map(|r| r.text).join(" ")` (no smart layout reconstruction in v1). - `engine = "tesseract"`, `engine_version = `. The `tesseract` crate (0.13+) does NOT expose a stable Rust `version()` accessor. Use one of: (a) call libtesseract's `TessVersion()` via the bundled FFI surface, OR (b) at adapter construction, shell-out `tesseract --version` once and cache the parsed `"5.3.4"`-style string. Both are deterministic for a fixed install. Pin the chosen approach in the implementation PR. - Apple Vision sidecar (feature `apple-vision`): - - Spawn a small Swift binary `kb-vision-ocr` (path from `config.ocr.apple_vision_binary`) feeding the image via stdin and reading JSON `{ regions: [{x,y,w,h,text,confidence}, ...] }` from stdout. + - Spawn a small Swift binary `kebab-vision-ocr` (path from `config.ocr.apple_vision_binary`) feeding the image via stdin and reading JSON `{ regions: [{x,y,w,h,text,confidence}, ...] }` from stdout. - Same threshold and `joined` rules as Tesseract. `engine = "apple-vision"`, `engine_version = sidecar's --version`. - This subagent task does NOT write the Swift sidecar; it only wires the Rust side. Document the expected sidecar interface in `docs/spec/sidecar-vision.md` (separate doc spec stub, optional). - `apply_ocr` calls `engine.recognize`, sets `block.ocr = Some(text)`, and appends a `Provenance::OcrApplied` event in the caller's CanonicalDocument (caller responsibility — this task exposes a helper). @@ -109,12 +109,12 @@ pub fn apply_ocr( | determinism | two runs of recognize on same input → identical OcrText | fixture | | `#[cfg(feature = "apple-vision")]` smoke | sidecar invocation captured (mock binary echoes fixed JSON) | inline mock | -All tests under `cargo test -p kb-parse-image ocr`. Tesseract install required on CI host. +All tests under `cargo test -p kebab-parse-image ocr`. Tesseract install required on CI host. ## Definition of Done -- [ ] `cargo check -p kb-parse-image --features tesseract` passes -- [ ] `cargo test -p kb-parse-image ocr` passes +- [ ] `cargo check -p kebab-parse-image --features tesseract` passes +- [ ] `cargo test -p kebab-parse-image ocr` passes - [ ] `apple-vision` feature compiles on macOS and gracefully no-ops on Linux - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.4, §3.7a, §9.1 @@ -129,5 +129,5 @@ All tests under `cargo test -p kb-parse-image ocr`. Tesseract install required o ## Risks / notes - Tesseract performance varies wildly with image quality; document `min_confidence` and default page-segmentation mode. -- Apple Vision sidecar requires code signing for distribution; for v1 dev builds, accept unsigned binary from `~/.local/bin/kb-vision-ocr`. +- Apple Vision sidecar requires code signing for distribution; for v1 dev builds, accept unsigned binary from `~/.local/bin/kebab-vision-ocr`. - Large image downscale loses small-text recognition; expose `config.ocr.max_pixels` so power users can tune. diff --git a/tasks/p6/p6-3-caption-adapter.md b/tasks/p6/p6-3-caption-adapter.md index 5616307..c4a99fd 100644 --- a/tasks/p6/p6-3-caption-adapter.md +++ b/tasks/p6/p6-3-caption-adapter.md @@ -1,12 +1,12 @@ --- phase: P6 -component: kb-parse-image (caption adapter) +component: kebab-parse-image (caption adapter) task_id: p6-3 title: "ModelCaption adapter (LanguageModel-driven, feature-gated)" status: planned depends_on: [p6-1, p4-2] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 ImageRefBlock.caption, §3.7a ModelCaption, §9.1 caption (model-generated, low trust)] --- @@ -22,10 +22,10 @@ Captioning closes the multimodal loop. Strict separation from OCR keeps trust le ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-parse-image` -- `kb-llm` (LanguageModel trait) +- `kebab-core` +- `kebab-config` +- `kebab-parse-image` +- `kebab-llm` (LanguageModel trait) - `base64` - `serde`, `serde_json` - `image` (resize for prompt cost control) @@ -34,7 +34,7 @@ Captioning closes the multimodal loop. Strict separation from OCR keeps trust le ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-rag`, `kb-llm-local` (only via trait), `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-rag`, `kebab-llm-local` (only via trait), `kebab-tui`, `kebab-desktop` ## Inputs @@ -42,28 +42,28 @@ Captioning closes the multimodal loop. Strict separation from OCR keeps trust le |-------|------|--------| | image bytes | `&[u8]` | extractor | | `dyn LanguageModel` (vision-capable) | runtime | injected | -| `kb-config.image.caption` | `{ enabled, max_pixels, prompt_template_version }` | runtime | +| `kebab-config.image.caption` | `{ enabled, max_pixels, prompt_template_version }` | runtime | ## Outputs | output | type | downstream | |--------|------|------------| -| `ModelCaption` | `kb_core::ModelCaption` | merged into `ImageRefBlock.caption` | +| `ModelCaption` | `kebab_core::ModelCaption` | merged into `ImageRefBlock.caption` | ## Public surface (signatures only — no new types) ```rust pub fn caption_image( - llm: &dyn kb_core::LanguageModel, + llm: &dyn kebab_core::LanguageModel, image_bytes: &[u8], - cfg: &kb_config::Config, -) -> anyhow::Result; + cfg: &kebab_config::Config, +) -> anyhow::Result; pub fn apply_caption( - llm: &dyn kb_core::LanguageModel, + llm: &dyn kebab_core::LanguageModel, image_bytes: &[u8], - block: &mut kb_core::ImageRefBlock, - cfg: &kb_config::Config, + block: &mut kebab_core::ImageRefBlock, + cfg: &kebab_config::Config, ) -> anyhow::Result<()>; ``` @@ -86,7 +86,7 @@ pub fn apply_caption( ## Storage / wire effects -- None directly. Caller persists via `kb-store-sqlite`. +- None directly. Caller persists via `kebab-store-sqlite`. ## Test plan @@ -99,12 +99,12 @@ pub fn apply_caption( | unit | downscale honors `max_pixels` (resulting bytes < some threshold) | fixture large image | | determinism | identical input + temperature=0 + seed=0 → identical caption (mock) | inline | -All tests under `cargo test -p kb-parse-image caption` with mock LM only. +All tests under `cargo test -p kebab-parse-image caption` with mock LM only. ## Definition of Done -- [ ] `cargo check -p kb-parse-image --features caption` passes -- [ ] `cargo test -p kb-parse-image caption` passes +- [ ] `cargo check -p kebab-parse-image --features caption` passes +- [ ] `cargo test -p kebab-parse-image caption` passes - [ ] No imports outside Allowed dependencies - [ ] Feature default OFF; only on when user opts in via config - [ ] PR links design §3.4 ImageRefBlock.caption, §9.1 diff --git a/tasks/p7/p7-1-pdf-text-extractor.md b/tasks/p7/p7-1-pdf-text-extractor.md index df6c13f..518c2d0 100644 --- a/tasks/p7/p7-1-pdf-text-extractor.md +++ b/tasks/p7/p7-1-pdf-text-extractor.md @@ -1,12 +1,12 @@ --- phase: P7 -component: kb-parse-pdf (text extractor) +component: kebab-parse-pdf (text extractor) task_id: p7-1 title: "Text PDF extractor → CanonicalDocument with page-level blocks" status: planned depends_on: [p0-1, p1-6] unblocks: [p7-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 SourceSpan::Page, §3.4 Block::Paragraph, §9.2 PDF text extraction, §9 versioning] --- @@ -22,8 +22,8 @@ Strict scope: page text + page numbers. Layout reconstruction (multi-column merg ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `pdf-extract = "0.7"` (or current stable) - `lopdf = "0.32"` for page metadata (count, optional title from /Info) - `serde`, `serde_json` @@ -33,30 +33,30 @@ Strict scope: page text + page numbers. Layout reconstruction (multi-column merg ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop`, OCR libraries (OCR fallback is a separate task, not this one) +- `kebab-source-fs`, `kebab-parse-md`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop`, OCR libraries (OCR fallback is a separate task, not this one) ## Inputs | input | type | source | |-------|------|--------| -| `RawAsset` | `kb_core::RawAsset` | `kb-source-fs` | +| `RawAsset` | `kebab_core::RawAsset` | `kebab-source-fs` | | PDF bytes | `&[u8]` | filesystem | ## Outputs | output | type | downstream | |--------|------|------------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | `kb-chunk` (`pdf-page-v1` chunker in p7-2) | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | `kebab-chunk` (`pdf-page-v1` chunker in p7-2) | ## Public surface (signatures only — no new types) ```rust pub struct PdfTextExtractor; -impl kb_core::Extractor for PdfTextExtractor { - fn supports(&self, m: &kb_core::MediaType) -> bool { matches!(m, kb_core::MediaType::Pdf) } - fn parser_version(&self) -> kb_core::ParserVersion { kb_core::ParserVersion("pdf-text-v1".into()) } - fn extract(&self, ctx: &kb_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; +impl kebab_core::Extractor for PdfTextExtractor { + fn supports(&self, m: &kebab_core::MediaType) -> bool { matches!(m, kebab_core::MediaType::Pdf) } + fn parser_version(&self) -> kebab_core::ParserVersion { kebab_core::ParserVersion("pdf-text-v1".into()) } + fn extract(&self, ctx: &kebab_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; } ``` @@ -100,12 +100,12 @@ impl kb_core::Extractor for PdfTextExtractor { | determinism | identical bytes → identical CanonicalDocument JSON across two runs | inline | | snapshot | CanonicalDocument JSON for fixture stable | `fixtures/pdf/three-page-en.pdf` | -All tests under `cargo test -p kb-parse-pdf`. +All tests under `cargo test -p kebab-parse-pdf`. ## Definition of Done -- [ ] `cargo check -p kb-parse-pdf` passes -- [ ] `cargo test -p kb-parse-pdf` passes +- [ ] `cargo check -p kebab-parse-pdf` passes +- [ ] `cargo test -p kebab-parse-pdf` passes - [ ] No OCR / LLM code present - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.4 SourceSpan::Page, §9.2 diff --git a/tasks/p7/p7-2-pdf-page-chunker.md b/tasks/p7/p7-2-pdf-page-chunker.md index ff6c6e7..d15c9fc 100644 --- a/tasks/p7/p7-2-pdf-page-chunker.md +++ b/tasks/p7/p7-2-pdf-page-chunker.md @@ -1,12 +1,12 @@ --- phase: P7 -component: kb-chunk (pdf-page-v1) +component: kebab-chunk (pdf-page-v1) task_id: p7-2 title: "PDF page-aware chunker (pdf-page-v1)" status: planned depends_on: [p7-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.5 Chunk, §4.2 chunk_id recipe, §0 Q3 citation, §9 versioning] --- @@ -22,8 +22,8 @@ Per-medium chunkers must stay tiny and obvious. Page-aware logic is small but it ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde`, `serde_json` - `blake3` (policy_hash) - `serde-json-canonicalizer` @@ -31,30 +31,30 @@ Per-medium chunkers must stay tiny and obvious. Page-aware logic is small but it ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-parse-pdf` (consumes `CanonicalDocument` via `kb-core` only), `kb-normalize`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-parse-pdf` (consumes `CanonicalDocument` via `kebab-core` only), `kebab-normalize`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `CanonicalDocument` (produced by `pdf-text-v1`) | `kb_core::CanonicalDocument` | p7-1 | -| `ChunkPolicy` | `kb_core::ChunkPolicy` | `kb-app` | +| `CanonicalDocument` (produced by `pdf-text-v1`) | `kebab_core::CanonicalDocument` | p7-1 | +| `ChunkPolicy` | `kebab_core::ChunkPolicy` | `kebab-app` | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` | `kb_core::Chunk` | `kb-store-sqlite`, `kb-embed*` | +| `Vec` | `kebab_core::Chunk` | `kebab-store-sqlite`, `kebab-embed*` | ## Public surface (signatures only — no new types) ```rust pub struct PdfPageV1Chunker; -impl kb_core::Chunker for PdfPageV1Chunker { - fn chunker_version(&self) -> kb_core::ChunkerVersion { kb_core::ChunkerVersion("pdf-page-v1".into()) } - fn policy_hash(&self, policy: &kb_core::ChunkPolicy) -> String; - fn chunk(&self, doc: &kb_core::CanonicalDocument, policy: &kb_core::ChunkPolicy) -> anyhow::Result>; +impl kebab_core::Chunker for PdfPageV1Chunker { + fn chunker_version(&self) -> kebab_core::ChunkerVersion { kebab_core::ChunkerVersion("pdf-page-v1".into()) } + fn policy_hash(&self, policy: &kebab_core::ChunkPolicy) -> String; + fn chunk(&self, doc: &kebab_core::CanonicalDocument, policy: &kebab_core::ChunkPolicy) -> anyhow::Result>; } ``` @@ -62,7 +62,7 @@ impl kb_core::Chunker for PdfPageV1Chunker { ## Behavior contract -- Only operates on documents whose blocks all carry `SourceSpan::Page` (i.e., from `kb-parse-pdf`). Other documents → return `anyhow::Error("PdfPageV1Chunker only handles PDF docs")`. +- Only operates on documents whose blocks all carry `SourceSpan::Page` (i.e., from `kebab-parse-pdf`). Other documents → return `anyhow::Error("PdfPageV1Chunker only handles PDF docs")`. - For each page block (1 block per page after p7-1): - If `text.len()` (byte estimate) ≤ `policy.target_tokens * 4` (proxy for tokens) → emit one chunk for the entire page. - Else → split by paragraphs (split text on `\n\n` or sentence-ending punctuation followed by whitespace) and group adjacent paragraphs until the running byte total approaches `policy.target_tokens * 4`. Apply `policy.overlap_tokens * 4` bytes of trailing overlap into the next chunk's prefix. @@ -91,12 +91,12 @@ impl kb_core::Chunker for PdfPageV1Chunker { | determinism | same input → same chunk_ids twice | inline | | snapshot | `Vec` JSON for fixture stable | `fixtures/pdf/three-page-en.pdf` (chunked) | -All tests under `cargo test -p kb-chunk pdf`. +All tests under `cargo test -p kebab-chunk pdf`. ## Definition of Done -- [ ] `cargo check -p kb-chunk` passes (existing `md-heading-v1` continues to pass) -- [ ] `cargo test -p kb-chunk pdf` passes +- [ ] `cargo check -p kebab-chunk` passes (existing `md-heading-v1` continues to pass) +- [ ] `cargo test -p kebab-chunk pdf` passes - [ ] Snapshot stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.5, §0 Q3, §9 diff --git a/tasks/p8/p8-1-whisper-adapter.md b/tasks/p8/p8-1-whisper-adapter.md index 286cd77..95c6841 100644 --- a/tasks/p8/p8-1-whisper-adapter.md +++ b/tasks/p8/p8-1-whisper-adapter.md @@ -1,12 +1,12 @@ --- phase: P8 -component: kb-parse-audio (whisper adapter) +component: kebab-parse-audio (whisper adapter) task_id: p8-1 title: "Audio Extractor + Transcriber trait + whisper.cpp adapter" status: planned depends_on: [p0-1, p1-6] unblocks: [p8-2] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.4 Block::AudioRef + AudioRefBlock, §3.7a Transcript + TranscriptSegment, §9.3 audio policy, §9 versioning] --- @@ -22,8 +22,8 @@ Audio stays a single, replaceable engine boundary (Transcriber trait). Extractor ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `whisper-rs = "0.13"` (or current stable) - `symphonia = { version = "0.5", features = ["all"] }` — decode `.m4a/.mp3/.wav/.flac/.ogg` to interleaved f32 PCM at the source's native sample rate / channel layout. Symphonia does NOT resample; that is rubato's job. - `rubato = "0.15"` — sample-rate conversion to 16 kHz mono f32 (the input shape whisper.cpp expects). Use `rubato::FftFixedIn::new(input_sample_rate, 16_000, frames_per_chunk, sub_chunks, 1 /* channels after downmix */)` for fixed-input streaming; pre-mix multi-channel to mono via simple averaging before the resampler. @@ -34,21 +34,21 @@ Audio stays a single, replaceable engine boundary (Transcriber trait). Extractor ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-parse-pdf`, `kb-parse-image`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-parse-pdf`, `kebab-parse-image`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `RawAsset` | `kb_core::RawAsset` | `kb-source-fs` | +| `RawAsset` | `kebab_core::RawAsset` | `kebab-source-fs` | | audio bytes | `&[u8]` | filesystem | -| `kb-config.audio` | `{ model_path, language, chunk_seconds, n_threads, gpu }` | runtime | +| `kebab-config.audio` | `{ model_path, language, chunk_seconds, n_threads, gpu }` | runtime | ## Outputs | output | type | downstream | |--------|------|------------| -| `CanonicalDocument` | `kb_core::CanonicalDocument` | `kb-chunk` (`audio-segment-v1` chunker in p8-2) | +| `CanonicalDocument` | `kebab_core::CanonicalDocument` | `kebab-chunk` (`audio-segment-v1` chunker in p8-2) | ## Public surface (signatures only — no new types) @@ -56,19 +56,19 @@ Audio stays a single, replaceable engine boundary (Transcriber trait). Extractor pub trait Transcriber: Send + Sync { fn engine(&self) -> &'static str; fn engine_version(&self) -> String; - fn transcribe(&self, pcm_f32_16khz: &[f32], language_hint: Option<&kb_core::Lang>) -> anyhow::Result; + fn transcribe(&self, pcm_f32_16khz: &[f32], language_hint: Option<&kebab_core::Lang>) -> anyhow::Result; } pub struct WhisperCppTranscriber { /* internal: whisper_rs::WhisperContext */ } -impl WhisperCppTranscriber { pub fn new(config: &kb_config::Config) -> anyhow::Result; } +impl WhisperCppTranscriber { pub fn new(config: &kebab_config::Config) -> anyhow::Result; } impl Transcriber for WhisperCppTranscriber { /* per trait */ } pub struct AudioExtractor { transcriber: std::sync::Arc } impl AudioExtractor { pub fn new(transcriber: std::sync::Arc) -> Self; } -impl kb_core::Extractor for AudioExtractor { - fn supports(&self, m: &kb_core::MediaType) -> bool { matches!(m, kb_core::MediaType::Audio(_)) } - fn parser_version(&self) -> kb_core::ParserVersion { kb_core::ParserVersion("audio-whisper-v1".into()) } - fn extract(&self, ctx: &kb_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; +impl kebab_core::Extractor for AudioExtractor { + fn supports(&self, m: &kebab_core::MediaType) -> bool { matches!(m, kebab_core::MediaType::Audio(_)) } + fn parser_version(&self) -> kebab_core::ParserVersion { kebab_core::ParserVersion("audio-whisper-v1".into()) } + fn extract(&self, ctx: &kebab_core::ExtractContext, bytes: &[u8]) -> anyhow::Result; } ``` @@ -87,7 +87,7 @@ impl kb_core::Extractor for AudioExtractor { - `provenance` events: `Discovered`, `Parsed`, `Transcribed`. - `block_id` per design §4.2 with `block_kind = "audio_ref"`, `heading_path = []`, `ordinal = 0`, `source_span = SourceSpan::Time { start_ms: 0, end_ms: duration_ms }`. - `WhisperCppTranscriber`: - - Loads model from `config.audio.model_path` (e.g., `~/.local/share/kb/models/whisper/ggml-large-v3.bin`). + - Loads model from `config.audio.model_path` (e.g., `~/.local/share/kebab/models/whisper/ggml-large-v3.bin`). - Runs with `WhisperFullParams::new(SamplingStrategy::Greedy { best_of: 1 })` — deterministic. - Streams in chunks of `config.audio.chunk_seconds` (default 30) to bound memory; aggregates segments. - `Transcript.segments` populated with `start_ms`, `end_ms`, `text`, `confidence: Some(p)` from whisper's per-token probabilities (averaged), `speaker: None` (diarization is P+). @@ -115,12 +115,12 @@ impl kb_core::Extractor for AudioExtractor { | `#[ignore]` integration | 30-second Korean audio → segments_count > 1, language = "ko" | requires `large-v3` model | | snapshot | CanonicalDocument JSON stable for short fixture | `fixtures/audio/hello.wav` | -All tests under `cargo test -p kb-parse-audio`. Mark slow/large-model tests `#[ignore]`. +All tests under `cargo test -p kebab-parse-audio`. Mark slow/large-model tests `#[ignore]`. ## Definition of Done -- [ ] `cargo check -p kb-parse-audio` passes -- [ ] `cargo test -p kb-parse-audio` passes (excluding `#[ignore]`) +- [ ] `cargo check -p kebab-parse-audio` passes +- [ ] `cargo test -p kebab-parse-audio` passes (excluding `#[ignore]`) - [ ] No imports outside Allowed dependencies (resampler crate may be added — record in PR) - [ ] First-run model download path documented (NOT performed by code; user responsibility) - [ ] PR links design §3.4, §3.7a, §9.3 diff --git a/tasks/p8/p8-2-segment-chunker.md b/tasks/p8/p8-2-segment-chunker.md index 724788d..6ec5241 100644 --- a/tasks/p8/p8-2-segment-chunker.md +++ b/tasks/p8/p8-2-segment-chunker.md @@ -1,12 +1,12 @@ --- phase: P8 -component: kb-chunk (audio-segment-v1) +component: kebab-chunk (audio-segment-v1) task_id: p8-2 title: "Audio segment chunker (audio-segment-v1)" status: planned depends_on: [p8-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§3.5 Chunk, §3.4 SourceSpan::Time, §4.2 chunk_id recipe, §0 Q3 citation, §9 versioning] --- @@ -22,8 +22,8 @@ Per-medium chunker. Tiny but versioned — `chunk_id` depends on `chunker_versio ## Allowed dependencies -- `kb-core` -- `kb-config` +- `kebab-core` +- `kebab-config` - `serde`, `serde_json` - `blake3` (policy_hash) - `serde-json-canonicalizer` @@ -31,30 +31,30 @@ Per-medium chunker. Tiny but versioned — `chunk_id` depends on `chunker_versio ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-md`, `kb-parse-pdf`, `kb-parse-image`, `kb-parse-audio` (consumes via `kb-core` only), `kb-normalize`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-tui`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-md`, `kebab-parse-pdf`, `kebab-parse-image`, `kebab-parse-audio` (consumes via `kebab-core` only), `kebab-normalize`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-tui`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `CanonicalDocument` containing one `AudioRefBlock` with `Transcript` | `kb_core::CanonicalDocument` | p8-1 | -| `ChunkPolicy` | `kb_core::ChunkPolicy` | `kb-app` | +| `CanonicalDocument` containing one `AudioRefBlock` with `Transcript` | `kebab_core::CanonicalDocument` | p8-1 | +| `ChunkPolicy` | `kebab_core::ChunkPolicy` | `kebab-app` | ## Outputs | output | type | downstream | |--------|------|------------| -| `Vec` | `kb_core::Chunk` | `kb-store-sqlite`, embedders | +| `Vec` | `kebab_core::Chunk` | `kebab-store-sqlite`, embedders | ## Public surface (signatures only — no new types) ```rust pub struct AudioSegmentV1Chunker; -impl kb_core::Chunker for AudioSegmentV1Chunker { - fn chunker_version(&self) -> kb_core::ChunkerVersion { kb_core::ChunkerVersion("audio-segment-v1".into()) } - fn policy_hash(&self, policy: &kb_core::ChunkPolicy) -> String; - fn chunk(&self, doc: &kb_core::CanonicalDocument, policy: &kb_core::ChunkPolicy) -> anyhow::Result>; +impl kebab_core::Chunker for AudioSegmentV1Chunker { + fn chunker_version(&self) -> kebab_core::ChunkerVersion { kebab_core::ChunkerVersion("audio-segment-v1".into()) } + fn policy_hash(&self, policy: &kebab_core::ChunkPolicy) -> String; + fn chunk(&self, doc: &kebab_core::CanonicalDocument, policy: &kebab_core::ChunkPolicy) -> anyhow::Result>; } ``` @@ -92,12 +92,12 @@ impl kb_core::Chunker for AudioSegmentV1Chunker { | determinism | same input → same chunk_ids twice | inline | | snapshot | `Vec` JSON for fixture transcript stable | `fixtures/audio/transcript-1.json` (constructed) | -All tests under `cargo test -p kb-chunk audio`. +All tests under `cargo test -p kebab-chunk audio`. ## Definition of Done -- [ ] `cargo check -p kb-chunk` passes (md-heading-v1 + pdf-page-v1 + audio-segment-v1 all coexist) -- [ ] `cargo test -p kb-chunk audio` passes +- [ ] `cargo check -p kebab-chunk` passes (md-heading-v1 + pdf-page-v1 + audio-segment-v1 all coexist) +- [ ] `cargo test -p kebab-chunk audio` passes - [ ] Snapshot stable across two runs - [ ] No imports outside Allowed dependencies - [ ] PR links design §3.5, §3.4 SourceSpan::Time, §4.2 diff --git a/tasks/p9/p9-1-tui-library.md b/tasks/p9/p9-1-tui-library.md index 610a9d8..4ff3ade 100644 --- a/tasks/p9/p9-1-tui-library.md +++ b/tasks/p9/p9-1-tui-library.md @@ -1,12 +1,12 @@ --- phase: P9 -component: kb-tui (library view) +component: kebab-tui (library view) task_id: p9-1 title: "Ratatui library list view + tag filter" status: planned depends_on: [p1-6] unblocks: [p9-2, p9-3, p9-4] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [report §16.2 TUI (also tasks/phase-9-ui.md epic), design §3.7 SearchHit, design §1 UX scenes for shared key bindings] --- @@ -14,7 +14,7 @@ contract_sections: [report §16.2 TUI (also tasks/phase-9-ui.md epic), design § ## Goal -Stand up a Ratatui app skeleton with a "Library" pane: list documents, filter by tag/lang, navigate. Establishes the global app loop, key dispatch, and `kb-app` integration point that the search/ask/inspect panes (p9-2..p9-4) extend. +Stand up a Ratatui app skeleton with a "Library" pane: list documents, filter by tag/lang, navigate. Establishes the global app loop, key dispatch, and `kebab-app` integration point that the search/ask/inspect panes (p9-2..p9-4) extend. ## Why now / why this size @@ -22,9 +22,9 @@ Library is the cheapest screen and the natural anchor for the TUI shell. Subsequ ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-app` (facade — the only crate this binary touches besides `kb-core`/`kb-config`) +- `kebab-core` +- `kebab-config` +- `kebab-app` (facade — the only crate this binary touches besides `kebab-core`/`kebab-config`) - `ratatui = "0.28"` - `crossterm` - `tracing` @@ -32,15 +32,15 @@ Library is the cheapest screen and the natural anchor for the TUI shell. Subsequ ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag` (UI must go through `kb-app` only — this is the design §8 boundary) +- `kebab-source-fs`, `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag` (UI must go through `kebab-app` only — this is the design §8 boundary) ## Inputs | input | type | source | |-------|------|--------| -| `kb-app::list_docs(filter)` | facade call | runtime | +| `kebab-app::list_docs(filter)` | facade call | runtime | | keyboard events | `crossterm` | terminal | -| `kb-config::Config` | runtime | env / file | +| `kebab-config::Config` | runtime | env / file | ## Outputs @@ -57,7 +57,7 @@ Library is the cheapest screen and the natural anchor for the TUI shell. Subsequ // state in WITHOUT modifying the App struct definition. This avoids merge // conflicts when p9-2/3/4 land in parallel; only p9-1 ever changes `App`. pub struct App { - pub config: kb_config::Config, + pub config: kebab_config::Config, pub focus: Pane, pub library: LibraryState, // owned by p9-1 pub search: Option, // populated by p9-2 (None until that crate links in) @@ -73,7 +73,7 @@ pub struct AskState; // body filled by p9-3 pub struct InspectState; // body filled by p9-4 impl App { - pub fn new(config: kb_config::Config) -> anyhow::Result; + pub fn new(config: kebab_config::Config) -> anyhow::Result; pub fn run(&mut self) -> anyhow::Result<()>; // blocking loop until quit } @@ -102,12 +102,12 @@ pub enum KeyOutcome { Continue, Quit, SwitchPane(Pane), Refresh } - `Enter` → switch to Inspect pane (p9-4) on selected doc - `q` or `Esc` → quit - All facade calls run on the main thread (no async). For long calls, render a "loading…" state and call from a worker thread; bridge via `mpsc::channel` (this task may keep things synchronous and accept brief UI hangs for v1). -- Logging: `tracing` initialized to a file under `~/.local/state/kb/logs/`; never to stdout/stderr (so the TUI is not corrupted). +- Logging: `tracing` initialized to a file under `~/.local/state/kebab/logs/`; never to stdout/stderr (so the TUI is not corrupted). - Error rendering: a popup overlay shows `error: {msg}\nhint: {hint}` from `anyhow::Error` chain; press any key to dismiss. ## Storage / wire effects -- Reads: `kb-app::list_docs` only. +- Reads: `kebab-app::list_docs` only. - Writes: none. ## Test plan @@ -118,16 +118,16 @@ pub enum KeyOutcome { Continue, Quit, SwitchPane(Pane), Refresh } | unit | filter `f` opens edit overlay; `Enter` triggers refresh | inline | | snapshot | rendered library with 3 docs + filter open produces stable frame buffer (use `ratatui::backend::TestBackend`) | inline | | unit | error popup renders without panic on injected `anyhow::Error` | inline | -| integration | mocked `kb-app::list_docs` returning N docs renders all rows | inline | +| integration | mocked `kebab-app::list_docs` returning N docs renders all rows | inline | -All tests under `cargo test -p kb-tui library`. +All tests under `cargo test -p kebab-tui library`. ## Definition of Done -- [ ] `cargo check -p kb-tui` passes -- [ ] `cargo test -p kb-tui library` passes -- [ ] No imports outside `kb-core`, `kb-config`, `kb-app` -- [ ] `kb tui` (or `kb` if TUI is the default) launches and shows Library on a real terminal (manual smoke) +- [ ] `cargo check -p kebab-tui` passes +- [ ] `cargo test -p kebab-tui library` passes +- [ ] No imports outside `kebab-core`, `kebab-config`, `kebab-app` +- [ ] `kebab tui` (or `kebab` if TUI is the default) launches and shows Library on a real terminal (manual smoke) - [ ] PR links design §8 module boundary, report §16.2 (TUI epic) ## Out of scope diff --git a/tasks/p9/p9-2-tui-search.md b/tasks/p9/p9-2-tui-search.md index 454f0c8..dfb26c6 100644 --- a/tasks/p9/p9-2-tui-search.md +++ b/tasks/p9/p9-2-tui-search.md @@ -1,12 +1,12 @@ --- phase: P9 -component: kb-tui (search pane) +component: kebab-tui (search pane) task_id: p9-2 title: "TUI Search pane: input + result list + preview + editor jump" status: planned depends_on: [p2-2, p3-4, p9-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§1.5/1.6 search output, §3.7 SearchHit, §0 Q3 citation] --- @@ -14,7 +14,7 @@ contract_sections: [§1.5/1.6 search output, §3.7 SearchHit, §0 Q3 citation] ## Goal -Add a Search pane to the TUI that drives `kb-app::search`, renders dense results (rank+score / path#frag / heading / snippet), and supports `g` (editor jump to citation) for the selected hit. +Add a Search pane to the TUI that drives `kebab-app::search`, renders dense results (rank+score / path#frag / heading / snippet), and supports `g` (editor jump to citation) for the selected hit. ## Why now / why this size @@ -22,25 +22,25 @@ Search is the most-used surface. Confining it to one pane leverages the App skel ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-app` -- `kb-tui` (extends p9-1) +- `kebab-core` +- `kebab-config` +- `kebab-app` +- `kebab-tui` (extends p9-1) - `ratatui`, `crossterm` - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag`, `kb-desktop` +- `kebab-source-fs`, `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag`, `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `kb-app::search(query)` | facade | runtime | +| `kebab-app::search(query)` | facade | runtime | | keyboard events | `crossterm` | terminal | -| selected hit's citation | `kb_core::Citation` | App state | +| selected hit's citation | `kebab_core::Citation` | App state | ## Outputs @@ -54,16 +54,16 @@ Search is the most-used surface. Confining it to one pane leverages the App skel ```rust pub fn render_search(f: &mut ratatui::Frame, area: ratatui::layout::Rect, state: &App); pub fn handle_key_search(state: &mut App, key: crossterm::event::KeyEvent) -> KeyOutcome; -pub fn jump_to_citation(citation: &kb_core::Citation, editor_env: &str /* $EDITOR */) -> anyhow::Result<()>; +pub fn jump_to_citation(citation: &kebab_core::Citation, editor_env: &str /* $EDITOR */) -> anyhow::Result<()>; ``` -This task fills the body of `kb_tui::SearchState` (forward-declared in p9-1). The `App` struct itself is NOT edited — only `SearchState` gets fields: +This task fills the body of `kebab_tui::SearchState` (forward-declared in p9-1). The `App` struct itself is NOT edited — only `SearchState` gets fields: ```rust pub struct SearchState { pub input: String, - pub mode: kb_core::SearchMode, - pub hits: Vec, + pub mode: kebab_core::SearchMode, + pub hits: Vec, pub selected_hit: usize, pub last_query_at: Option, // debounce timer } @@ -73,7 +73,7 @@ The Library pane's keypress handler (in p9-1) sets `app.search = Some(SearchStat ## Behavior contract -- Layout: top input bar (search query + mode badge `[hybrid|lexical|vector]`), middle result list (one hit per 4 lines per design §1.5 dense format), bottom preview pane (full chunk text fetched lazily via `kb-app::inspect_chunk`). +- Layout: top input bar (search query + mode badge `[hybrid|lexical|vector]`), middle result list (one hit per 4 lines per design §1.5 dense format), bottom preview pane (full chunk text fetched lazily via `kebab-app::inspect_chunk`). - Key bindings (Search pane): - typing → updates `search_input`; debounced (200 ms) re-search - `Tab` → cycles `search_mode` Lexical → Vector → Hybrid → Lexical @@ -104,14 +104,14 @@ The Library pane's keypress handler (in p9-1) sets `app.search = Some(SearchStat | unit | `j` / `k` move selection within bounds | inline | | unit | `jump_to_citation` for `Line` builds `+ ` command (assert via mocked Command runner) | inline | | snapshot | rendered Search pane with 3 hits + preview stable | TestBackend | -| integration | mocked `kb-app::search` returning fixture hits drives render | inline | +| integration | mocked `kebab-app::search` returning fixture hits drives render | inline | -All tests under `cargo test -p kb-tui search`. +All tests under `cargo test -p kebab-tui search`. ## Definition of Done -- [ ] `cargo check -p kb-tui` passes -- [ ] `cargo test -p kb-tui search` passes +- [ ] `cargo check -p kebab-tui` passes +- [ ] `cargo test -p kebab-tui search` passes - [ ] `g` keybinding launches `$EDITOR` with correct `+` argument (manual smoke against vim) - [ ] No imports outside Allowed dependencies - [ ] PR links design §1.5/1.6, §3.7 @@ -125,5 +125,5 @@ All tests under `cargo test -p kb-tui search`. ## Risks / notes - Suspending and restoring crossterm raw mode around the editor spawn is finicky; code defensively (RAII guard). -- Different editors take different jump syntaxes. Provide an env override `KB_EDITOR_JUMP_FORMAT="vim"` for users on exotic editors. +- Different editors take different jump syntaxes. Provide an env override `KEBAB_EDITOR_JUMP_FORMAT="vim"` for users on exotic editors. - Long snippet text wrap: clamp to viewport width and ellipsize per design §1.5 (`…` already in dense template). diff --git a/tasks/p9/p9-3-tui-ask.md b/tasks/p9/p9-3-tui-ask.md index 1c10b8e..bea3e29 100644 --- a/tasks/p9/p9-3-tui-ask.md +++ b/tasks/p9/p9-3-tui-ask.md @@ -1,12 +1,12 @@ --- phase: P9 -component: kb-tui (ask pane) +component: kebab-tui (ask pane) task_id: p9-3 title: "TUI Ask pane: streaming answer + citation links + --explain toggle" status: planned depends_on: [p4-3, p9-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§1.1–1.4 ask scenes, §2.3 Answer wire, §3.8 Answer] --- @@ -14,7 +14,7 @@ contract_sections: [§1.1–1.4 ask scenes, §2.3 Answer wire, §3.8 Answer] ## Goal -Add an Ask pane that calls `kb-app::ask`, streams tokens into the answer area in real time, renders citation footnotes (default mode A), and toggles to `--explain` (mode B + retrieval trace) with a key. +Add an Ask pane that calls `kebab-app::ask`, streams tokens into the answer area in real time, renders citation footnotes (default mode A), and toggles to `--explain` (mode B + retrieval trace) with a key. ## Why now / why this size @@ -22,23 +22,23 @@ Streaming UI is the only TUI piece that meaningfully differs from search/inspect ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-app` -- `kb-tui` (extends p9-1) +- `kebab-core` +- `kebab-config` +- `kebab-app` +- `kebab-tui` (extends p9-1) - `ratatui`, `crossterm` - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag` (only via `kb-app`), `kb-desktop` +- `kebab-source-fs`, `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag` (only via `kebab-app`), `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `kb-app::ask(query, AskOpts)` | facade | runtime | +| `kebab-app::ask(query, AskOpts)` | facade | runtime | | keyboard events | `crossterm` | terminal | ## Outputs @@ -46,7 +46,7 @@ Streaming UI is the only TUI piece that meaningfully differs from search/inspect | output | type | downstream | |--------|------|------------| | Ratatui Ask pane render | terminal | user | -| `kb-app::ask` invocation with streaming closure | facade | RAG pipeline | +| `kebab-app::ask` invocation with streaming closure | facade | RAG pipeline | ## Public surface (signatures only — no new types) @@ -55,7 +55,7 @@ pub fn render_ask(f: &mut ratatui::Frame, area: ra pub fn handle_key_ask(state: &mut App, key: crossterm::event::KeyEvent) -> KeyOutcome; ``` -This task fills the body of `kb_tui::AskState` (forward-declared in p9-1). `App` is NOT edited — only `AskState` gets fields: +This task fills the body of `kebab_tui::AskState` (forward-declared in p9-1). `App` is NOT edited — only `AskState` gets fields: ```rust pub struct AskState { @@ -63,8 +63,8 @@ pub struct AskState { pub explain: bool, pub streaming: bool, pub partial: String, - pub answer: Option, - pub thread: Option>>, + pub answer: Option, + pub thread: Option>>, pub rx: Option>, } ``` @@ -74,7 +74,7 @@ pub struct AskState { ## Behavior contract - Layout: top input bar (`?` prompt, query text), middle answer area (rendered Markdown-light: paragraphs + inline `[N]` markers), bottom-right citations panel (numbered list of citations with `path#fragment` and section label), bottom-left status (`grounded ✓/✗ model prompt_v k chunks`). -- Submission: `Enter` triggers a worker thread that calls `kb-app::ask` with `AskOpts.stream_sink: Some(tx)` (`tx: mpsc::Sender`). The thread holds the `tx`, the TUI holds the matching `rx` (set on `AskState.rx`). On each render frame the TUI drains `rx.try_iter()` into `state.partial`, no blocking. +- Submission: `Enter` triggers a worker thread that calls `kebab-app::ask` with `AskOpts.stream_sink: Some(tx)` (`tx: mpsc::Sender`). The thread holds the `tx`, the TUI holds the matching `rx` (set on `AskState.rx`). On each render frame the TUI drains `rx.try_iter()` into `state.partial`, no blocking. - Streaming: while `ask_streaming = true`, the Answer area shows `ask_partial` and a small "▍" cursor. When the worker finishes, `ask_answer` is populated and the citations panel switches to the final list. - Refusal rendering: - `grounded = false` and `refusal_reason = ScoreGate` → render the answer (which is the human-friendly "근거 부족…" message), citations show "가까운 후보". @@ -85,12 +85,12 @@ pub struct AskState { - `e` → toggle `ask_explain`; resubmit on next `Enter`. While explain ON, citations panel is replaced by the per-claim breakdown (mode B in design §1.2) and a footer shows the retrieval trace summary. - `Esc` → switch back to Library pane (cancellation of an in-flight ask is best-effort: the worker thread continues but its final answer is dropped). - `j` / `k` → scroll the answer area when oversized. -- All facade calls stay within `kb-app::ask` — never reach into `kb-rag` directly. +- All facade calls stay within `kebab-app::ask` — never reach into `kebab-rag` directly. - Errors render as a popup overlay; do not crash the pane. ## Storage / wire effects -- Reads/writes via `kb-app::ask` which itself writes the `answers` row in `kb.sqlite`. The pane has no direct DB access. +- Reads/writes via `kebab-app::ask` which itself writes the `answers` row in `kebab.sqlite`. The pane has no direct DB access. ## Test plan @@ -102,14 +102,14 @@ pub struct AskState { | unit | refusal answer renders without citations panel index errors | inline | | snapshot | rendered Ask pane mid-stream is stable | TestBackend | | snapshot | rendered Ask pane after finished grounded answer is stable | TestBackend | -| integration | mocked `kb-app::ask` returning a canned `Answer` populates final state correctly | inline | +| integration | mocked `kebab-app::ask` returning a canned `Answer` populates final state correctly | inline | -All tests under `cargo test -p kb-tui ask`. +All tests under `cargo test -p kebab-tui ask`. ## Definition of Done -- [ ] `cargo check -p kb-tui` passes -- [ ] `cargo test -p kb-tui ask` passes +- [ ] `cargo check -p kebab-tui` passes +- [ ] `cargo test -p kebab-tui ask` passes - [ ] No imports outside Allowed dependencies - [ ] Manual smoke: stream tokens visible character-by-character against a real Ollama (or `MockLanguageModel`) - [ ] PR links design §1.1–1.4, §2.3 diff --git a/tasks/p9/p9-4-tui-inspect.md b/tasks/p9/p9-4-tui-inspect.md index 5289cc6..e92dad8 100644 --- a/tasks/p9/p9-4-tui-inspect.md +++ b/tasks/p9/p9-4-tui-inspect.md @@ -1,12 +1,12 @@ --- phase: P9 -component: kb-tui (inspect pane) +component: kebab-tui (inspect pane) task_id: p9-4 title: "TUI Inspect pane: document & chunk detail render" status: planned depends_on: [p1-6, p9-1] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [§1 inspect output, §3.5 Chunk, §2.5 DocSummary, §2.6 ChunkInspection] --- @@ -22,24 +22,24 @@ Inspect is read-only and has no external interactions; smallest possible pane. U ## Allowed dependencies -- `kb-core` -- `kb-config` -- `kb-app` -- `kb-tui` (extends p9-1) +- `kebab-core` +- `kebab-config` +- `kebab-app` +- `kebab-tui` (extends p9-1) - `ratatui`, `crossterm` - `tracing` - `thiserror` ## Forbidden dependencies -- `kb-source-fs`, `kb-parse-*`, `kb-normalize`, `kb-chunk`, `kb-store-*`, `kb-embed*`, `kb-search`, `kb-llm*`, `kb-rag` (only via `kb-app`), `kb-desktop` +- `kebab-source-fs`, `kebab-parse-*`, `kebab-normalize`, `kebab-chunk`, `kebab-store-*`, `kebab-embed*`, `kebab-search`, `kebab-llm*`, `kebab-rag` (only via `kebab-app`), `kebab-desktop` ## Inputs | input | type | source | |-------|------|--------| -| `kb-app::inspect_doc(id)` | facade | runtime | -| `kb-app::inspect_chunk(id)` | facade | runtime | +| `kebab-app::inspect_doc(id)` | facade | runtime | +| `kebab-app::inspect_chunk(id)` | facade | runtime | | keyboard events | `crossterm` | terminal | ## Outputs @@ -51,19 +51,19 @@ Inspect is read-only and has no external interactions; smallest possible pane. U ## Public surface (signatures only — no new types) ```rust -pub enum InspectTarget { Doc(kb_core::DocumentId), Chunk(kb_core::ChunkId) } +pub enum InspectTarget { Doc(kebab_core::DocumentId), Chunk(kebab_core::ChunkId) } pub fn render_inspect(f: &mut ratatui::Frame, area: ratatui::layout::Rect, state: &App); pub fn handle_key_inspect(state: &mut App, key: crossterm::event::KeyEvent) -> KeyOutcome; ``` -This task fills the body of `kb_tui::InspectState` (forward-declared in p9-1). `App` is NOT edited. +This task fills the body of `kebab_tui::InspectState` (forward-declared in p9-1). `App` is NOT edited. ```rust pub struct InspectState { pub target: Option, - pub doc: Option, - pub chunk: Option, + pub doc: Option, + pub chunk: Option, pub collapsed: std::collections::HashSet<&'static str>, pub scroll: u16, } @@ -89,7 +89,7 @@ pub struct InspectState { - `c` → collapse / expand currently focused section (focus is implicit by current scroll position; v1 may simplify by toggling all sections) - `Esc` → return to previous pane (Library or Search) - `Enter` → no-op (Inspect is terminal — no editor jump here; users use Search pane for jump) -- Loading: while `kb-app::inspect_doc` or `inspect_chunk` runs, show "loading…". On error, popup with hint. +- Loading: while `kebab-app::inspect_doc` or `inspect_chunk` runs, show "loading…". On error, popup with hint. - Renders must conform to wire schemas `doc_summary.v1` (subset for header) and `chunk_inspection.v1`. ## Storage / wire effects @@ -100,18 +100,18 @@ pub struct InspectState { | kind | description | fixture / data | |------|-------------|----------------| -| unit | switching to InspectTarget::Doc triggers `kb-app::inspect_doc` once | inline mock | +| unit | switching to InspectTarget::Doc triggers `kebab-app::inspect_doc` once | inline mock | | unit | scroll bounded by content height | inline | | unit | collapse toggle via `c` flips state | inline | | snapshot | doc-view rendered for fixture stable | TestBackend + fixture | | snapshot | chunk-view rendered for fixture stable | TestBackend + fixture | -All tests under `cargo test -p kb-tui inspect`. +All tests under `cargo test -p kebab-tui inspect`. ## Definition of Done -- [ ] `cargo check -p kb-tui` passes -- [ ] `cargo test -p kb-tui inspect` passes +- [ ] `cargo check -p kebab-tui` passes +- [ ] `cargo test -p kebab-tui inspect` passes - [ ] No imports outside Allowed dependencies - [ ] Manual smoke: inspect a doc with multiple chunks, scroll, return to library - [ ] PR links design §3.5, §2.5, §2.6 diff --git a/tasks/p9/p9-5-desktop-tauri.md b/tasks/p9/p9-5-desktop-tauri.md index f1d2ec4..90f0f0a 100644 --- a/tasks/p9/p9-5-desktop-tauri.md +++ b/tasks/p9/p9-5-desktop-tauri.md @@ -1,12 +1,12 @@ --- phase: P9 -component: kb-desktop (Tauri) +component: kebab-desktop (Tauri) task_id: p9-5 -title: "Tauri desktop app: backend commands wrapping kb-app + multimodal source viewer" +title: "Tauri desktop app: backend commands wrapping kebab-app + multimodal source viewer" status: planned depends_on: [p9-1, p9-2, p9-3, p9-4] unblocks: [] -contract_source: ../../docs/superpowers/specs/2026-04-27-kb-final-form-design.md +contract_source: ../../docs/superpowers/specs/2026-04-27-kebab-final-form-design.md contract_sections: [report §16.3 desktop (also tasks/phase-9-ui.md epic), design §1 ask/search scenes, design §2 wire schemas v1, design §8 module boundaries] --- @@ -14,23 +14,23 @@ contract_sections: [report §16.3 desktop (also tasks/phase-9-ui.md epic), desig ## Goal -Stand up a Tauri 2.x app (`kb-desktop` crate as backend, `kb-desktop-frontend/` as web assets) whose Tauri commands wrap `kb-app` 1:1. The frontend renders multimodal source viewers (Markdown render, PDF page viewer, image viewer with region overlay, audio player with seek). Citation clicks route to the appropriate viewer. +Stand up a Tauri 2.x app (`kebab-desktop` crate as backend, `kebab-desktop-frontend/` as web assets) whose Tauri commands wrap `kebab-app` 1:1. The frontend renders multimodal source viewers (Markdown render, PDF page viewer, image viewer with region overlay, audio player with seek). Citation clicks route to the appropriate viewer. ## Why now / why this size -Last task. Combines all backend phases into a single user-facing surface. Strict policy: backend commands are thin wrappers over `kb-app`; no new business logic. +Last task. Combines all backend phases into a single user-facing surface. Strict policy: backend commands are thin wrappers over `kebab-app`; no new business logic. ## Allowed dependencies -- backend (`kb-desktop`): - - `kb-core` - - `kb-config` - - `kb-app` +- backend (`kebab-desktop`): + - `kebab-core` + - `kebab-config` + - `kebab-app` - `tauri = "2"` + `tauri-build` - `serde`, `serde_json` - `tracing` - `thiserror` -- frontend (`kb-desktop-frontend/`): vanilla TypeScript + Vite (default; user may swap to Svelte/Solid in a follow-up). +- frontend (`kebab-desktop-frontend/`): vanilla TypeScript + Vite (default; user may swap to Svelte/Solid in a follow-up). - PDF rendering: `pdfjs-dist` - Markdown rendering: `marked` + `dompurify` - Audio: HTML `