refactor(rename): kb → kebab — binary, env vars, XDG paths, file renames

두 번째 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) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 04:01:35 +00:00
parent 911fb49550
commit f1a448d6dc
38 changed files with 185 additions and 185 deletions

View File

@@ -61,7 +61,7 @@ pub use app::App;
/// Kept in lock-step with the literal used in the `kb-store-sqlite` /// Kept in lock-step with the literal used in the `kb-store-sqlite`
/// idempotency / round-trip tests so the version label written by the /// idempotency / round-trip tests so the version label written by the
/// app and the one used in cross-crate fixtures match. /// 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. /// Caller-supplied knobs for one [`ask`] invocation.
/// ///
@@ -187,7 +187,7 @@ pub fn ingest_with_config(
.context("kb-app::ingest: ensure Lance table")?; .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); let chunk_policy = chunk_policy_from_config(&app.config);
// Pre-load every existing doc_id so we can label `IngestItem.kind` // Pre-load every existing doc_id so we can label `IngestItem.kind`
@@ -236,7 +236,7 @@ pub fn ingest_with_config(
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
tracing::error!( tracing::error!(
target: "kb-app", target: "kebab-app",
path = %asset.workspace_path.0, path = %asset.workspace_path.0,
error = %e, error = %e,
"kb-app::ingest: per-file fatal" "kb-app::ingest: per-file fatal"
@@ -318,7 +318,7 @@ pub fn ingest_with_config(
progress, progress,
) { ) {
tracing::warn!( tracing::warn!(
target: "kb-app", target: "kebab-app",
error = %e, error = %e,
"kb-app::ingest: JobRepo::update_progress failed" "kb-app::ingest: JobRepo::update_progress failed"
); );
@@ -330,7 +330,7 @@ pub fn ingest_with_config(
None, None,
) { ) {
tracing::warn!( tracing::warn!(
target: "kb-app", target: "kebab-app",
error = %e, error = %e,
"kb-app::ingest: JobRepo::finish failed" "kb-app::ingest: JobRepo::finish failed"
); );
@@ -338,7 +338,7 @@ pub fn ingest_with_config(
} }
Err(e) => { Err(e) => {
tracing::warn!( tracing::warn!(
target: "kb-app", target: "kebab-app",
error = %e, error = %e,
"kb-app::ingest: JobRepo::create failed; run not recorded in `jobs`" "kb-app::ingest: JobRepo::create failed; run not recorded in `jobs`"
); );
@@ -361,7 +361,7 @@ pub fn ingest_with_config(
Ok(s) => Some(s), Ok(s) => Some(s),
Err(e) => { Err(e) => {
tracing::warn!( tracing::warn!(
target: "kb-app", target: "kebab-app",
error = %e, error = %e,
"kb-app::ingest: failed to serialize items_json; storing NULL" "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) { if let Err(e) = app.sqlite.record_ingest_run(&row) {
tracing::warn!( tracing::warn!(
target: "kb-app", target: "kebab-app",
error = %e, error = %e,
"kb-app::ingest: record_ingest_run failed" "kb-app::ingest: record_ingest_run failed"
); );
} }
tracing::info!( tracing::info!(
target: "kb-app", target: "kebab-app",
scanned = scanned_count, scanned = scanned_count,
new = new_count, new = new_count,
updated = updated_count, updated = updated_count,
@@ -448,7 +448,7 @@ fn ingest_one_asset(
existing_doc_ids: &std::collections::HashSet<String>, existing_doc_ids: &std::collections::HashSet<String>,
) -> anyhow::Result<kebab_core::IngestItem> { ) -> anyhow::Result<kebab_core::IngestItem> {
tracing::debug!( tracing::debug!(
target: "kb-app::ingest", target: "kebab-app::ingest",
path = %asset.workspace_path.0, path = %asset.workspace_path.0,
"processing asset" "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 // data_dir_writable — probe the resolved storage.data_dir from the
// loaded config when present, else the XDG default. Apply env // 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() { let data_dir = match loaded_cfg.as_ref() {
Some(c) => { Some(c) => {
// Re-apply env overrides on top so the same precedence as // Re-apply env overrides on top so the same precedence as

View File

@@ -1,6 +1,6 @@
//! Tracing initialization helper for `kb-cli`. //! 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 //! Returns a `WorkerGuard` that the caller must keep alive until program
//! exit (so buffered log lines flush). //! exit (so buffered log lines flush).

View File

@@ -87,8 +87,8 @@ fn ingest_records_ingest_runs_row_with_aggregate_counts() {
assert_eq!(report.scanned, 3); assert_eq!(report.scanned, 3);
let db_path = std::path::PathBuf::from(&env.config.storage.data_dir) let db_path = std::path::PathBuf::from(&env.config.storage.data_dir)
.join("kb.sqlite"); .join("kebab.sqlite");
let conn = rusqlite::Connection::open(&db_path).expect("open kb.sqlite"); let conn = rusqlite::Connection::open(&db_path).expect("open kebab.sqlite");
let (scanned, new_c, updated, skipped, errors, items_json): ( let (scanned, new_c, updated, skipped, errors, items_json): (
i64, i64,
i64, i64,

View File

@@ -186,7 +186,7 @@ impl Chunker for MdHeadingV1Chunker {
flush(&mut acc, doc, &chunker_version, &policy_hash, &mut out); flush(&mut acc, doc, &chunker_version, &policy_hash, &mut out);
tracing::debug!( tracing::debug!(
target: "kb-chunk", target: "kebab-chunk",
doc_id = %doc.doc_id, doc_id = %doc.doc_id,
chunks = out.len(), chunks = out.len(),
"md-heading-v1 chunked", "md-heading-v1 chunked",

View File

@@ -8,7 +8,7 @@ repository = { workspace = true }
description = "kb command-line interface" description = "kb command-line interface"
[[bin]] [[bin]]
name = "kb" name = "kebab"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]

View File

@@ -11,7 +11,7 @@ use kebab_app::doctor_signal::{DoctorUnhealthy, NoHitSignal, RefusalSignal};
mod wire; mod wire;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "kb", version, about = "personal local knowledge base")] #[command(name = "kebab", version, about = "personal local knowledge base")]
struct Cli { struct Cli {
/// Path to a non-default `config.toml`. /// Path to a non-default `config.toml`.
#[arg(long, global = true)] #[arg(long, global = true)]

View File

@@ -1,6 +1,6 @@
//! `kb-config` — `Config` schema and XDG path resolution (§6). //! `kb-config` — `Config` schema and XDG path resolution (§6).
//! //!
//! Layer order (`Config::load`): defaults → file → env (`KB_<SECTION>_<KEY>`). //! Layer order (`Config::load`): defaults → file → env (`KEBAB_<SECTION>_<KEY>`).
//! CLI overrides land later, applied by `kb-cli` after `Config::load`. //! CLI overrides land later, applied by `kb-cli` after `Config::load`.
use std::collections::HashMap; use std::collections::HashMap;
@@ -113,8 +113,8 @@ impl Config {
], ],
}, },
storage: StorageCfg { storage: StorageCfg {
data_dir: "${XDG_DATA_HOME:-~/.local/share}/kb".to_string(), data_dir: "${XDG_DATA_HOME:-~/.local/share}/kebab".to_string(),
sqlite: "{data_dir}/kb.sqlite".to_string(), sqlite: "{data_dir}/kebab.sqlite".to_string(),
vector_dir: "{data_dir}/lancedb".to_string(), vector_dir: "{data_dir}/lancedb".to_string(),
asset_dir: "{data_dir}/assets".to_string(), asset_dir: "{data_dir}/assets".to_string(),
artifact_dir: "{data_dir}/artifacts".to_string(), artifact_dir: "{data_dir}/artifacts".to_string(),
@@ -191,139 +191,139 @@ impl Config {
Ok(cfg) Ok(cfg)
} }
/// Apply `KB_<SECTION>_<KEY>` env overrides. Unknown keys are ignored. /// Apply `KEBAB_<SECTION>_<KEY>` env overrides. Unknown keys are ignored.
/// ///
/// The mapping is an explicit grep-friendly whitelist — one match arm /// The mapping is an explicit grep-friendly whitelist — one match arm
/// per leaf key in `Config`. Booleans accept `1` / `true` / `yes` /// per leaf key in `Config`. Booleans accept `1` / `true` / `yes`
/// (case-insensitive) for true and anything else for false. Numeric /// (case-insensitive) for true and anything else for false. Numeric
/// keys silently keep their prior value if the env value fails to /// 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<String, String>) -> Self { pub fn apply_env(mut self, env: &HashMap<String, String>) -> Self {
for (k, v) in env { for (k, v) in env {
if !k.starts_with("KB_") { if !k.starts_with("KEBAB_") {
continue; continue;
} }
match k.as_str() { match k.as_str() {
// workspace // workspace
"KB_WORKSPACE_ROOT" => self.workspace.root = v.clone(), "KEBAB_WORKSPACE_ROOT" => self.workspace.root = v.clone(),
// storage // storage
"KB_STORAGE_DATA_DIR" => self.storage.data_dir = v.clone(), "KEBAB_STORAGE_DATA_DIR" => self.storage.data_dir = v.clone(),
"KB_STORAGE_SQLITE" => self.storage.sqlite = v.clone(), "KEBAB_STORAGE_SQLITE" => self.storage.sqlite = v.clone(),
"KB_STORAGE_VECTOR_DIR" => self.storage.vector_dir = v.clone(), "KEBAB_STORAGE_VECTOR_DIR" => self.storage.vector_dir = v.clone(),
"KB_STORAGE_ASSET_DIR" => self.storage.asset_dir = v.clone(), "KEBAB_STORAGE_ASSET_DIR" => self.storage.asset_dir = v.clone(),
"KB_STORAGE_ARTIFACT_DIR" => self.storage.artifact_dir = v.clone(), "KEBAB_STORAGE_ARTIFACT_DIR" => self.storage.artifact_dir = v.clone(),
"KB_STORAGE_MODEL_DIR" => self.storage.model_dir = v.clone(), "KEBAB_STORAGE_MODEL_DIR" => self.storage.model_dir = v.clone(),
"KB_STORAGE_RUNS_DIR" => self.storage.runs_dir = v.clone(), "KEBAB_STORAGE_RUNS_DIR" => self.storage.runs_dir = v.clone(),
"KB_STORAGE_COPY_THRESHOLD_MB" => { "KEBAB_STORAGE_COPY_THRESHOLD_MB" => {
if let Ok(n) = v.parse::<u64>() { if let Ok(n) = v.parse::<u64>() {
self.storage.copy_threshold_mb = n; self.storage.copy_threshold_mb = n;
} }
} }
// indexing // indexing
"KB_INDEXING_MAX_PARALLEL_EXTRACTORS" => { "KEBAB_INDEXING_MAX_PARALLEL_EXTRACTORS" => {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
self.indexing.max_parallel_extractors = n; self.indexing.max_parallel_extractors = n;
} }
} }
"KB_INDEXING_MAX_PARALLEL_EMBEDDINGS" => { "KEBAB_INDEXING_MAX_PARALLEL_EMBEDDINGS" => {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
self.indexing.max_parallel_embeddings = n; self.indexing.max_parallel_embeddings = n;
} }
} }
"KB_INDEXING_WATCH_FILESYSTEM" => { "KEBAB_INDEXING_WATCH_FILESYSTEM" => {
self.indexing.watch_filesystem = parse_bool(v); self.indexing.watch_filesystem = parse_bool(v);
} }
// chunking // chunking
"KB_CHUNKING_TARGET_TOKENS" => { "KEBAB_CHUNKING_TARGET_TOKENS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.chunking.target_tokens = n; self.chunking.target_tokens = n;
} }
} }
"KB_CHUNKING_OVERLAP_TOKENS" => { "KEBAB_CHUNKING_OVERLAP_TOKENS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.chunking.overlap_tokens = n; self.chunking.overlap_tokens = n;
} }
} }
"KB_CHUNKING_RESPECT_MARKDOWN_HEADINGS" => { "KEBAB_CHUNKING_RESPECT_MARKDOWN_HEADINGS" => {
self.chunking.respect_markdown_headings = parse_bool(v); 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 // models.embedding
"KB_MODELS_EMBEDDING_PROVIDER" => self.models.embedding.provider = v.clone(), "KEBAB_MODELS_EMBEDDING_PROVIDER" => self.models.embedding.provider = v.clone(),
"KB_MODELS_EMBEDDING_MODEL" => self.models.embedding.model = v.clone(), "KEBAB_MODELS_EMBEDDING_MODEL" => self.models.embedding.model = v.clone(),
"KB_MODELS_EMBEDDING_VERSION" => self.models.embedding.version = v.clone(), "KEBAB_MODELS_EMBEDDING_VERSION" => self.models.embedding.version = v.clone(),
"KB_MODELS_EMBEDDING_DIMENSIONS" => { "KEBAB_MODELS_EMBEDDING_DIMENSIONS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.models.embedding.dimensions = n; self.models.embedding.dimensions = n;
} }
} }
"KB_MODELS_EMBEDDING_BATCH_SIZE" => { "KEBAB_MODELS_EMBEDDING_BATCH_SIZE" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.models.embedding.batch_size = n; self.models.embedding.batch_size = n;
} }
} }
// models.llm // models.llm
"KB_MODELS_LLM_PROVIDER" => self.models.llm.provider = v.clone(), "KEBAB_MODELS_LLM_PROVIDER" => self.models.llm.provider = v.clone(),
"KB_MODELS_LLM_MODEL" => self.models.llm.model = v.clone(), "KEBAB_MODELS_LLM_MODEL" => self.models.llm.model = v.clone(),
"KB_MODELS_LLM_CONTEXT_TOKENS" => { "KEBAB_MODELS_LLM_CONTEXT_TOKENS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.models.llm.context_tokens = n; self.models.llm.context_tokens = n;
} }
} }
"KB_MODELS_LLM_ENDPOINT" => self.models.llm.endpoint = v.clone(), "KEBAB_MODELS_LLM_ENDPOINT" => self.models.llm.endpoint = v.clone(),
"KB_MODELS_LLM_TEMPERATURE" => { "KEBAB_MODELS_LLM_TEMPERATURE" => {
if let Ok(f) = v.parse::<f32>() { if let Ok(f) = v.parse::<f32>() {
self.models.llm.temperature = f; self.models.llm.temperature = f;
} }
} }
"KB_MODELS_LLM_SEED" => { "KEBAB_MODELS_LLM_SEED" => {
if let Ok(n) = v.parse::<u64>() { if let Ok(n) = v.parse::<u64>() {
self.models.llm.seed = n; self.models.llm.seed = n;
} }
} }
// search // search
"KB_SEARCH_DEFAULT_K" => { "KEBAB_SEARCH_DEFAULT_K" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.search.default_k = n; self.search.default_k = n;
} }
} }
"KB_SEARCH_HYBRID_FUSION" => self.search.hybrid_fusion = v.clone(), "KEBAB_SEARCH_HYBRID_FUSION" => self.search.hybrid_fusion = v.clone(),
"KB_SEARCH_RRF_K" => { "KEBAB_SEARCH_RRF_K" => {
if let Ok(n) = v.parse::<u32>() { if let Ok(n) = v.parse::<u32>() {
self.search.rrf_k = n; self.search.rrf_k = n;
} }
} }
"KB_SEARCH_SNIPPET_CHARS" => { "KEBAB_SEARCH_SNIPPET_CHARS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.search.snippet_chars = n; self.search.snippet_chars = n;
} }
} }
// rag // rag
"KB_RAG_PROMPT_TEMPLATE_VERSION" => { "KEBAB_RAG_PROMPT_TEMPLATE_VERSION" => {
self.rag.prompt_template_version = v.clone(); self.rag.prompt_template_version = v.clone();
} }
"KB_RAG_SCORE_GATE" => { "KEBAB_RAG_SCORE_GATE" => {
if let Ok(f) = v.parse::<f32>() { if let Ok(f) = v.parse::<f32>() {
self.rag.score_gate = f; self.rag.score_gate = f;
} }
} }
"KB_RAG_EXPLAIN_DEFAULT" => { "KEBAB_RAG_EXPLAIN_DEFAULT" => {
self.rag.explain_default = parse_bool(v); self.rag.explain_default = parse_bool(v);
} }
"KB_RAG_MAX_CONTEXT_TOKENS" => { "KEBAB_RAG_MAX_CONTEXT_TOKENS" => {
if let Ok(n) = v.parse::<usize>() { if let Ok(n) = v.parse::<usize>() {
self.rag.max_context_tokens = n; 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. // `env_unknown_key_is_ignored` test.
_ => {} _ => {}
} }
@@ -331,58 +331,58 @@ impl Config {
self self
} }
/// `~/.config/kb/config.toml` (honors `XDG_CONFIG_HOME`). /// `~/.config/kebab/config.toml` (honors `XDG_CONFIG_HOME`).
pub fn xdg_config_path() -> PathBuf { pub fn xdg_config_path() -> PathBuf {
if let Ok(custom) = std::env::var("XDG_CONFIG_HOME") { if let Ok(custom) = std::env::var("XDG_CONFIG_HOME") {
if !custom.is_empty() { 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() { match dirs::config_dir() {
Some(d) => d.join("kb").join("config.toml"), Some(d) => d.join("kebab").join("config.toml"),
None => PathBuf::from("./kb/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 { pub fn xdg_data_dir() -> PathBuf {
if let Ok(custom) = std::env::var("XDG_DATA_HOME") { if let Ok(custom) = std::env::var("XDG_DATA_HOME") {
if !custom.is_empty() { if !custom.is_empty() {
return PathBuf::from(custom).join("kb"); return PathBuf::from(custom).join("kebab");
} }
} }
match dirs::data_dir() { match dirs::data_dir() {
Some(d) => d.join("kb"), Some(d) => d.join("kebab"),
None => PathBuf::from("./kb-data"), None => PathBuf::from("./kebab-data"),
} }
} }
/// `~/.cache/kb` (honors `XDG_CACHE_HOME`). /// `~/.cache/kebab` (honors `XDG_CACHE_HOME`).
pub fn xdg_cache_dir() -> PathBuf { pub fn xdg_cache_dir() -> PathBuf {
if let Ok(custom) = std::env::var("XDG_CACHE_HOME") { if let Ok(custom) = std::env::var("XDG_CACHE_HOME") {
if !custom.is_empty() { if !custom.is_empty() {
return PathBuf::from(custom).join("kb"); return PathBuf::from(custom).join("kebab");
} }
} }
match dirs::cache_dir() { match dirs::cache_dir() {
Some(d) => d.join("kb"), Some(d) => d.join("kebab"),
None => PathBuf::from("./kb-cache"), 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 { pub fn xdg_state_dir() -> PathBuf {
if let Ok(custom) = std::env::var("XDG_STATE_HOME") { if let Ok(custom) = std::env::var("XDG_STATE_HOME") {
if !custom.is_empty() { 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 // `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() { 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] #[test]
fn env_override_score_gate() { fn env_override_score_gate() {
let mut env = HashMap::new(); 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); let c = Config::defaults().apply_env(&env);
assert!((c.rag.score_gate - 0.5).abs() < 1e-6); assert!((c.rag.score_gate - 0.5).abs() < 1e-6);
} }
@@ -425,7 +425,7 @@ mod tests {
#[test] #[test]
fn env_override_search_k() { fn env_override_search_k() {
let mut env = HashMap::new(); 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); let c = Config::defaults().apply_env(&env);
assert_eq!(c.search.default_k, 25); assert_eq!(c.search.default_k, 25);
} }
@@ -434,7 +434,7 @@ mod tests {
fn env_unknown_key_is_ignored() { fn env_unknown_key_is_ignored() {
let baseline = Config::defaults(); let baseline = Config::defaults();
let mut env = HashMap::new(); 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); let c = Config::defaults().apply_env(&env);
assert_eq!(c, baseline); assert_eq!(c, baseline);
} }
@@ -442,7 +442,7 @@ mod tests {
#[test] #[test]
fn env_overrides_chunking_target_tokens() { fn env_overrides_chunking_target_tokens() {
let mut env = HashMap::new(); 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); let c = Config::defaults().apply_env(&env);
assert_eq!(c.chunking.target_tokens, 777); assert_eq!(c.chunking.target_tokens, 777);
} }
@@ -451,10 +451,10 @@ mod tests {
fn env_overrides_models_llm_endpoint_and_temperature() { fn env_overrides_models_llm_endpoint_and_temperature() {
let mut env = HashMap::new(); let mut env = HashMap::new();
env.insert( env.insert(
"KB_MODELS_LLM_ENDPOINT".to_string(), "KEBAB_MODELS_LLM_ENDPOINT".to_string(),
"http://10.0.0.1:11434".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); let c = Config::defaults().apply_env(&env);
assert_eq!(c.models.llm.endpoint, "http://10.0.0.1:11434"); assert_eq!(c.models.llm.endpoint, "http://10.0.0.1:11434");
assert!((c.models.llm.temperature - 0.7).abs() < 1e-6); assert!((c.models.llm.temperature - 0.7).abs() < 1e-6);
@@ -464,7 +464,7 @@ mod tests {
fn env_overrides_indexing_watch_filesystem_bool() { fn env_overrides_indexing_watch_filesystem_bool() {
let mut env = HashMap::new(); let mut env = HashMap::new();
env.insert( env.insert(
"KB_INDEXING_WATCH_FILESYSTEM".to_string(), "KEBAB_INDEXING_WATCH_FILESYSTEM".to_string(),
"true".to_string(), "true".to_string(),
); );
let c = Config::defaults().apply_env(&env); let c = Config::defaults().apply_env(&env);
@@ -477,10 +477,10 @@ mod tests {
let prev = std::env::var("XDG_CONFIG_HOME").ok(); let prev = std::env::var("XDG_CONFIG_HOME").ok();
// SAFETY: tests in this module run sequentially; we restore below. // SAFETY: tests in this module run sequentially; we restore below.
unsafe { 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(); 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. // SAFETY: scope-local restore.
unsafe { unsafe {
match prev { match prev {

View File

@@ -1,7 +1,7 @@
//! Shared path expansion helper. //! Shared path expansion helper.
//! //!
//! `Config::storage.*` fields are stored as raw template strings (e.g. //! `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 //! crate that turns one of those strings into a real filesystem path
//! needs to apply the same set of substitutions; this module is the //! needs to apply the same set of substitutions; this module is the
//! single source of truth so the behavior cannot drift. //! single source of truth so the behavior cannot drift.
@@ -133,8 +133,8 @@ mod tests {
// SAFETY: lock held for the duration of this test. // SAFETY: lock held for the duration of this test.
unsafe { std::env::set_var("XDG_DATA_HOME", "/custom/path") }; unsafe { std::env::set_var("XDG_DATA_HOME", "/custom/path") };
let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kb", ""); let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kebab", "");
assert_eq!(p, PathBuf::from("/custom/path/kb")); assert_eq!(p, PathBuf::from("/custom/path/kebab"));
} }
#[test] #[test]
@@ -145,8 +145,8 @@ mod tests {
unsafe { std::env::remove_var("XDG_DATA_HOME") }; unsafe { std::env::remove_var("XDG_DATA_HOME") };
let home = std::env::var("HOME").expect("HOME must be set in tests"); let home = std::env::var("HOME").expect("HOME must be set in tests");
let expected = PathBuf::from(home).join(".local/share/kb"); let expected = PathBuf::from(home).join(".local/share/kebab");
let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kb", ""); let p = expand_path("${XDG_DATA_HOME:-~/.local/share}/kebab", "");
assert_eq!(p, expected); assert_eq!(p, expected);
} }
@@ -180,7 +180,7 @@ mod tests {
// SAFETY: lock held for the duration of this test. // SAFETY: lock held for the duration of this test.
unsafe { std::env::set_var("XDG_DATA_HOME", "/xdg/data") }; unsafe { std::env::set_var("XDG_DATA_HOME", "/xdg/data") };
let p = expand_path("{data_dir}/runs", "/xdg/data/kb"); let p = expand_path("{data_dir}/runs", "/xdg/data/kebab");
assert_eq!(p, PathBuf::from("/xdg/data/kb/runs")); assert_eq!(p, PathBuf::from("/xdg/data/kebab/runs"));
} }
} }

View File

@@ -4,7 +4,7 @@
//! `kb-*` crate, so every other crate in the workspace can depend on it //! `kb-*` crate, so every other crate in the workspace can depend on it
//! freely. //! 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. //! the canonical type bodies — this crate is the byte-for-byte mirror.
pub mod ids; pub mod ids;

View File

@@ -19,7 +19,7 @@
//! rules `kb-store-sqlite` applies to `data_dir` (`${XDG_DATA_HOME:-…}`, //! rules `kb-store-sqlite` applies to `data_dir` (`${XDG_DATA_HOME:-…}`,
//! leading `~`, `{data_dir}` substitution). //! 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). //! §7.2 (Embedder), §6.4 ([models.embedding]), §9 (versioning).
use std::sync::Mutex; use std::sync::Mutex;
@@ -82,7 +82,7 @@ impl FastembedEmbedder {
check_dim(model_info.dim, config.models.embedding.dimensions)?; check_dim(model_info.dim, config.models.embedding.dimensions)?;
tracing::info!( tracing::info!(
target: "kb-embed-local", target: "kebab-embed-local",
cache_dir = %cache_dir.display(), cache_dir = %cache_dir.display(),
model = %config.models.embedding.model, model = %config.models.embedding.model,
dims = model_info.dim, dims = model_info.dim,
@@ -97,7 +97,7 @@ impl FastembedEmbedder {
.with_cache_dir(cache_dir.clone()) .with_cache_dir(cache_dir.clone())
.with_show_download_progress(false); .with_show_download_progress(false);
tracing::info!( tracing::info!(
target: "kb-embed-local", target: "kebab-embed-local",
model = %config.models.embedding.model, model = %config.models.embedding.model,
cache_dir = %cache_dir.display(), cache_dir = %cache_dir.display(),
"loading embedding model (first run will download ~470MB)" "loading embedding model (first run will download ~470MB)"
@@ -106,7 +106,7 @@ impl FastembedEmbedder {
.context("fastembed: TextEmbedding::try_new")?; .context("fastembed: TextEmbedding::try_new")?;
let dimensions = model_info.dim; let dimensions = model_info.dim;
tracing::info!( tracing::info!(
target: "kb-embed-local", target: "kebab-embed-local",
model = %config.models.embedding.model, model = %config.models.embedding.model,
dimensions, dimensions,
"embedding model loaded" "embedding model loaded"

View File

@@ -26,7 +26,7 @@ use kebab_embed::{Embedder, EmbeddingInput, EmbeddingKind};
use kebab_embed_local::FastembedEmbedder; use kebab_embed_local::FastembedEmbedder;
/// Build a `Config` whose `data_dir` lives in a per-process temp dir so /// 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 /// Returns the `Config` and the `TempDir` guard (caller keeps the guard
/// alive for the test duration). /// alive for the test duration).
fn test_config() -> (kebab_config::Config, tempfile::TempDir) { fn test_config() -> (kebab_config::Config, tempfile::TempDir) {

View File

@@ -11,7 +11,7 @@
//! deterministic test double. Real adapters (fastembed, candle, ollama-embed) //! deterministic test double. Real adapters (fastembed, candle, ollama-embed)
//! live in p3-2 and MUST NOT be implemented here. //! 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. //! §11 for the contract.
// ── Trait re-exports ────────────────────────────────────────────────────── // ── Trait re-exports ──────────────────────────────────────────────────────

View File

@@ -40,10 +40,10 @@ const STORAGE_DECIMALS: u32 = 4;
/// (P5-1) used — otherwise `expected_*` / `must_contain` won't line up /// (P5-1) used — otherwise `expected_*` / `must_contain` won't line up
/// with the stored `query_id`s. `pub(crate)` so the runner shares the /// with the stored `query_id`s. `pub(crate)` so the runner shares the
/// exact same name + default rather than duplicating constants. /// 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 /// 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"; pub(crate) const DEFAULT_GOLDEN_PATH: &str = "fixtures/golden_queries.yaml";
/// Aggregate metrics for one stored eval run. /// 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 /// the runner uses, same default path. Pulled into its own helper so
/// `compare_runs` can share it. /// `compare_runs` can share it.
pub(crate) fn resolve_golden_path() -> PathBuf { 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), Ok(s) if !s.is_empty() => PathBuf::from(s),
_ => PathBuf::from(DEFAULT_GOLDEN_PATH), _ => PathBuf::from(DEFAULT_GOLDEN_PATH),
} }
@@ -161,7 +161,7 @@ fn load_golden_for_metrics() -> Result<Vec<GoldenQuery>> {
let path = resolve_golden_path(); let path = resolve_golden_path();
load_golden_set(&path).with_context(|| { load_golden_set(&path).with_context(|| {
format!( format!(
"load golden set from {} (override via KB_EVAL_GOLDEN)", "load golden set from {} (override via KEBAB_EVAL_GOLDEN)",
path.display() path.display()
) )
}) })

View File

@@ -13,7 +13,7 @@ use kebab_store_sqlite::{EvalRunRow, SqliteStore};
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::loader::{load_golden_set, validate_against_db}; 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}; use crate::types::{EvalRun, EvalRunOpts, GoldenQuery, QueryResult};
/// Convert a wall-clock duration since `start` into milliseconds clamped /// 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 golden_path = resolve_golden_path();
let queries = load_golden_set(&golden_path).with_context(|| { let queries = load_golden_set(&golden_path).with_context(|| {
format!( format!(
"load golden set from {} (override via KB_EVAL_GOLDEN)", "load golden set from {} (override via KEBAB_EVAL_GOLDEN)",
golden_path.display() 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 ────────────────────────────────── // ── 2. Mint identifiers + open store ──────────────────────────────────
let run_id = mint_run_id(); let run_id = mint_run_id();
let created_at = OffsetDateTime::now_utc(); 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() .ok()
.filter(|s| !s.is_empty()); .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); let duration_ms = elapsed_ms_u32(started);
tracing::info!( tracing::info!(
target: "kb-eval", target: "kebab-eval",
run_id = %run_id, run_id = %run_id,
suite = %opts.suite, suite = %opts.suite,
queries = per_query.len(), queries = per_query.len(),
@@ -136,11 +136,11 @@ fn mint_run_id() -> String {
format!("run_{id}") 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 /// override; otherwise relative to CWD. The path is NOT expanded for
/// `~` / `${...}` placeholders — direct file paths only. /// `~` / `${...}` placeholders — direct file paths only.
fn resolve_golden_path() -> PathBuf { 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), Ok(s) if !s.is_empty() => PathBuf::from(s),
_ => PathBuf::from(DEFAULT_GOLDEN_PATH), _ => PathBuf::from(DEFAULT_GOLDEN_PATH),
} }

View File

@@ -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 // SAFELY scoped — `set_var` is process-global so callers serialise
// tests via the `serial_test`-style guard below. // tests via the `serial_test`-style guard below.
unsafe { unsafe {
std::env::set_var("KB_EVAL_GOLDEN", &golden_path); std::env::set_var("KEBAB_EVAL_GOLDEN", &golden_path);
} }
cfg cfg
} }
@@ -127,9 +127,9 @@ fn write_run(
store.record_eval_run_with_results(&row, &results).unwrap(); 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 /// 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. /// threads don't trip over each other's golden YAML.
fn env_guard() -> std::sync::MutexGuard<'static, ()> { fn env_guard() -> std::sync::MutexGuard<'static, ()> {
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};

View File

@@ -7,7 +7,7 @@
//! workspace's source-of-truth, //! workspace's source-of-truth,
//! - lexical-only retrieval (`SearchMode::Lexical`) so no embedder is //! - lexical-only retrieval (`SearchMode::Lexical`) so no embedder is
//! required (`models.embedding.provider = "none"`), //! 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 //! Determinism: lexical-only with a fixed seed corpus produces
//! byte-identical `per_query.jsonl` content (modulo `run_id` / //! byte-identical `per_query.jsonl` content (modulo `run_id` /
@@ -24,7 +24,7 @@ use kebab_store_sqlite::SqliteStore;
use rusqlite::params; use rusqlite::params;
use tempfile::TempDir; 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` /// serialize so they don't trample each other when `cargo test`
/// runs them in parallel. /// runs them in parallel.
static GOLDEN_ENV_LOCK: Mutex<()> = Mutex::new(()); 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 /// guard must outlive the call so concurrent tests don't reset the
/// var mid-run. /// var mid-run.
fn run_with_golden<F: FnOnce() -> R, R>(yaml: &Path, f: F) -> R { fn run_with_golden<F: FnOnce() -> R, R>(yaml: &Path, f: F) -> R {
let _g = GOLDEN_ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner()); 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. // serializes mutations so concurrent tests don't race.
unsafe { unsafe {
std::env::set_var("KB_EVAL_GOLDEN", yaml); std::env::set_var("KEBAB_EVAL_GOLDEN", yaml);
} }
let out = f(); let out = f();
unsafe { unsafe {
std::env::remove_var("KB_EVAL_GOLDEN"); std::env::remove_var("KEBAB_EVAL_GOLDEN");
} }
out out
} }

View File

@@ -29,7 +29,7 @@
//! - **Lazy connect.** [`OllamaLanguageModel::new`] does not hit the network; //! - **Lazy connect.** [`OllamaLanguageModel::new`] does not hit the network;
//! the first error surfaces on [`LanguageModel::generate_stream`]. //! 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 //! §6.4 (`[models.llm]`), §0 Q5 (streaming), §10 (errors), and report §11.2
//! (Ollama protocol notes). //! (Ollama protocol notes).

View File

@@ -19,8 +19,8 @@ use kebab_llm_local::{LanguageModel, OllamaLanguageModel};
#[ignore = "requires a local Ollama daemon + pulled model"] #[ignore = "requires a local Ollama daemon + pulled model"]
fn real_ollama_streams_non_empty_response() { fn real_ollama_streams_non_empty_response() {
// Use whatever model the workspace defaults select. Override via the // 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 // KEBAB_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`). // (e.g. `KEBAB_MODELS_LLM_MODEL=qwen2.5:7b-instruct cargo test ... -- --ignored`).
let cfg = Config::load(None).expect("config should load"); let cfg = Config::load(None).expect("config should load");
let llm = OllamaLanguageModel::new(&cfg).unwrap(); let llm = OllamaLanguageModel::new(&cfg).unwrap();

View File

@@ -12,7 +12,7 @@
//! from `generate_stream` itself (e.g., connection refused) before any chunk //! from `generate_stream` itself (e.g., connection refused) before any chunk
//! is yielded; the mock never does. //! 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. //! §0 Q5 (streaming), §3.8 (`ModelRef`) for the contract.
// ── Trait re-exports ────────────────────────────────────────────────────── // ── Trait re-exports ──────────────────────────────────────────────────────

View File

@@ -96,7 +96,7 @@ pub fn build_canonical_document(
.collect(); .collect();
tracing::debug!( tracing::debug!(
target: "kb-normalize", target: "kebab-normalize",
"built canonical document doc_id={} blocks={}", "built canonical document doc_id={} blocks={}",
doc_id.0, doc_id.0,
lifted_blocks.len() lifted_blocks.len()

View File

@@ -135,7 +135,7 @@ impl RagPipeline {
let top_score = hits.first().map(|h| h.retrieval.fusion_score).unwrap_or(0.0); let top_score = hits.first().map(|h| h.retrieval.fusion_score).unwrap_or(0.0);
tracing::debug!( tracing::debug!(
target: "kb-rag", target: "kebab-rag",
chunks_returned, chunks_returned,
top_score, top_score,
mode = ?opts.mode, mode = ?opts.mode,
@@ -161,7 +161,7 @@ impl RagPipeline {
// collapse to the more accurate `NoChunks` refusal here. // collapse to the more accurate `NoChunks` refusal here.
if packed_entries.is_empty() { if packed_entries.is_empty() {
tracing::warn!( tracing::warn!(
target: "kb-rag", target: "kebab-rag",
chunks_returned = hits.len(), chunks_returned = hits.len(),
"kb-rag: all retrieved chunks were unfetchable from the store; \ "kb-rag: all retrieved chunks were unfetchable from the store; \
falling back to NoChunks refusal" falling back to NoChunks refusal"
@@ -324,7 +324,7 @@ impl RagPipeline {
// Drop the moved `finish_reason` early into a tracing breadcrumb; the // Drop the moved `finish_reason` early into a tracing breadcrumb; the
// wire schema does not surface it (per design §3.8). // wire schema does not surface it (per design §3.8).
tracing::debug!( tracing::debug!(
target: "kb-rag", target: "kebab-rag",
grounded = answer.grounded, grounded = answer.grounded,
refusal = ?answer.refusal_reason, refusal = ?answer.refusal_reason,
refusal_phrase_detected = matched_refusal_phrase, refusal_phrase_detected = matched_refusal_phrase,
@@ -354,7 +354,7 @@ impl RagPipeline {
self.docs.put_answer(&answer, query, packed_chunks_json.as_deref()) self.docs.put_answer(&answer, query, packed_chunks_json.as_deref())
{ {
tracing::warn!( tracing::warn!(
target: "kb-rag", target: "kebab-rag",
error = %e, error = %e,
"kb-rag: put_answer failed; in-memory Answer still returned" "kb-rag: put_answer failed; in-memory Answer still returned"
); );
@@ -386,7 +386,7 @@ impl RagPipeline {
Some(c) => c.text, Some(c) => c.text,
None => { None => {
tracing::warn!( tracing::warn!(
target: "kb-rag", target: "kebab-rag",
chunk_id = %hit.chunk_id.0, chunk_id = %hit.chunk_id.0,
"kb-rag: chunk not found in store; skipping" "kb-rag: chunk not found in store; skipping"
); );
@@ -454,7 +454,7 @@ impl RagPipeline {
created_at: OffsetDateTime::now_utc(), created_at: OffsetDateTime::now_utc(),
}; };
if let Err(e) = self.docs.put_answer(&answer, query, None) { 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) Ok(answer)
} }
@@ -529,7 +529,7 @@ impl RagPipeline {
created_at: OffsetDateTime::now_utc(), created_at: OffsetDateTime::now_utc(),
}; };
if let Err(e) = self.docs.put_answer(&answer, query, None) { 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) Ok(answer)
} }

View File

@@ -93,7 +93,7 @@ impl HybridRetriever {
let vec_iv = vector.index_version(); let vec_iv = vector.index_version();
if lex_iv.0 != vec_iv.0 { if lex_iv.0 != vec_iv.0 {
tracing::warn!( tracing::warn!(
target: "kb-search", target: "kebab-search",
lexical_index = %lex_iv.0, lexical_index = %lex_iv.0,
vector_index = %vec_iv.0, vector_index = %vec_iv.0,
"kb-search hybrid: lexical and vector index_version differ; consider re-indexing" "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 }, "rrf" => FusionPolicy::Rrf { k_rrf: k },
other => { other => {
tracing::warn!( tracing::warn!(
target: "kb-search", target: "kebab-search",
policy = other, policy = other,
"kb-search hybrid: unknown fusion policy; falling back to RRF" "kb-search hybrid: unknown fusion policy; falling back to RRF"
); );

View File

@@ -157,17 +157,17 @@ fn hybrid_snapshot_run_1() {
.join("hybrid") .join("hybrid")
.join("run-1.json"); .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::create_dir_all(fixture.parent().unwrap()).unwrap();
std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap()).unwrap(); std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap()).unwrap();
eprintln!("[snapshot] regenerated {}", fixture.display()); 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 // in CI surfaces as a test failure rather than a silent
// overwrite + green run. Same fail-loud-instead-of-silent-pass // overwrite + green run. Same fail-loud-instead-of-silent-pass
// philosophy as P3-2's `SNAPSHOT_HASH_BASELINE = 0` and P3-3's // philosophy as P3-2's `SNAPSHOT_HASH_BASELINE = 0` and P3-3's
// placeholder fixture guards. // placeholder fixture guards.
panic!( 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() fixture.display()
); );
} }
@@ -176,7 +176,7 @@ fn hybrid_snapshot_run_1() {
serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else(|_| { serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else(|_| {
panic!( panic!(
"missing snapshot fixture at {}; run with \ "missing snapshot fixture at {}; run with \
KB_UPDATE_SNAPSHOTS=1 to create", KEBAB_UPDATE_SNAPSHOTS=1 to create",
fixture.display() fixture.display()
) )
})) }))
@@ -189,14 +189,14 @@ fn hybrid_snapshot_run_1() {
panic!( panic!(
"snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \ "snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \
Path: {}. To regenerate: \ 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() fixture.display()
); );
} }
assert_eq!( assert_eq!(
actual, expected, 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 // Independent guard: fusion scores must be non-increasing across

View File

@@ -29,7 +29,7 @@ impl Env {
config.storage.data_dir = temp.path().to_string_lossy().into_owned(); config.storage.data_dir = temp.path().to_string_lossy().into_owned();
let store = SqliteStore::open(&config).expect("open store"); let store = SqliteStore::open(&config).expect("open store");
store.run_migrations().expect("run migrations"); store.run_migrations().expect("run migrations");
let db_path = temp.path().join("kb.sqlite"); let db_path = temp.path().join("kebab.sqlite");
Self { Self {
_temp: temp, _temp: temp,
store: Arc::new(store), store: Arc::new(store),
@@ -618,7 +618,7 @@ fn lexical_snapshot_run_1() {
// `Vec<SearchHit>` for a fixed query is checked verbatim against // `Vec<SearchHit>` for a fixed query is checked verbatim against
// `tests/fixtures/search/lexical/run-1.json`. Update both sides in // `tests/fixtures/search/lexical/run-1.json`. Update both sides in
// the same commit when intentional changes ship. // 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 env = Env::new();
let conn = env.raw_conn(); let conn = env.raw_conn();
insert_document(&conn, &id32("d"), "notes/snap.md", "Snap", "en", "primary", &[]); insert_document(&conn, &id32("d"), "notes/snap.md", "Snap", "en", "primary", &[]);
@@ -656,11 +656,11 @@ fn lexical_snapshot_run_1() {
let baseline_path = let baseline_path =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/search/lexical/run-1.json"); 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(); std::fs::write(&baseline_path, serde_json::to_string_pretty(&actual).unwrap()).unwrap();
} }
let baseline_text = std::fs::read_to_string(&baseline_path) 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(); let expected: serde_json::Value = serde_json::from_str(&baseline_text).unwrap();
assert_eq!(actual, expected, "lexical run-1 snapshot drift"); assert_eq!(actual, expected, "lexical run-1 snapshot drift");
} }

View File

@@ -74,13 +74,13 @@ impl SourceConnector for FsSourceConnector {
scope.root.clone() scope.root.clone()
}; };
// Union: config.workspace.exclude scope.exclude .kbignore. // Union: config.workspace.exclude scope.exclude .kebabignore.
// Per §6.2 the union of `.kbignore` and `config.workspace.exclude` // Per §6.2 the union of `.kebabignore` and `config.workspace.exclude`
// is the filter set. `scope.exclude` is added on top so a caller // is the filter set. `scope.exclude` is added on top so a caller
// can layer a per-call narrowing. // can layer a per-call narrowing.
let mut excludes = self.default_exclude.clone(); let mut excludes = self.default_exclude.clone();
excludes.extend(scope.exclude.iter().cloned()); 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. // restarting any long-running process.
let kbignore = read_kbignore(&root)?; let kbignore = read_kbignore(&root)?;
@@ -243,7 +243,7 @@ mod tests {
fn scan_filters_by_kbignore() { fn scan_filters_by_kbignore() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let root = dir.path(); 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("a.md"), b"x").unwrap();
std::fs::write(root.join("b.tmp"), b"x").unwrap(); std::fs::write(root.join("b.tmp"), b"x").unwrap();
@@ -252,14 +252,14 @@ mod tests {
.unwrap(); .unwrap();
let v = conn.scan(&SourceScope::default()).unwrap(); let v = conn.scan(&SourceScope::default()).unwrap();
let names: Vec<_> = v.iter().map(|a| a.workspace_path.0.clone()).collect(); 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 // Rationale: a config file that affects ingest is itself part of the
// workspace contents; the markdown extractor (P1-2) will reject Other("") // 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. // this test will catch it.
assert!( assert!(
names.contains(&".kbignore".to_string()), names.contains(&".kebabignore".to_string()),
".kbignore must be emitted as an asset; got: {names:?}" ".kebabignore must be emitted as an asset; got: {names:?}"
); );
assert!(names.contains(&"a.md".to_string())); assert!(names.contains(&"a.md".to_string()));
assert!(!names.contains(&"b.tmp".to_string())); assert!(!names.contains(&"b.tmp".to_string()));
@@ -285,7 +285,7 @@ mod tests {
fn scan_unions_config_exclude_and_kbignore() { fn scan_unions_config_exclude_and_kbignore() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let root = dir.path(); 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("a.md"), b"x").unwrap();
std::fs::write(root.join("b.tmp"), b"x").unwrap(); std::fs::write(root.join("b.tmp"), b"x").unwrap();
std::fs::write(root.join("c.log"), b"x").unwrap(); std::fs::write(root.join("c.log"), b"x").unwrap();

View File

@@ -1,10 +1,10 @@
//! `kb-source-fs` — local filesystem `SourceConnector`. //! `kb-source-fs` — local filesystem `SourceConnector`.
//! //!
//! Walks `config.workspace.root`, applies gitignore-style filters from //! 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<RawAsset>` sorted by `workspace_path` for determinism. //! and emits `Vec<RawAsset>` 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 //! normalization), §7.1 (SourceScope), §7.2 (SourceConnector), §8 (module
//! boundaries). //! boundaries).

View File

@@ -3,7 +3,7 @@
//! //!
//! Filter set (per task spec, design §6.2): //! Filter set (per task spec, design §6.2):
//! - `config.workspace.exclude` (passed in by `FsSourceConnector`) //! - `config.workspace.exclude` (passed in by `FsSourceConnector`)
//! - `<root>/.kbignore` (optional file at workspace root) //! - `<root>/.kebabignore` (optional file at workspace root)
//! - default-excludes for `.DS_Store` and macOS resource forks (`._*`) //! - default-excludes for `.DS_Store` and macOS resource forks (`._*`)
//! //!
//! All three are merged via `ignore::overrides::OverrideBuilder`, which //! 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; /// Default-excludes baked into the connector. These are NOT configurable;
/// they cover noise that is never useful to ingest and would otherwise need /// 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] = &[ const DEFAULT_EXCLUDES: &[&str] = &[
// Finder metadata // Finder metadata
".DS_Store", ".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. /// baked-in default excludes.
/// ///
/// Each input pattern is registered as an *exclude* (gitignore-style: a /// 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 { for pat in kbignore_patterns {
builder builder
.add(&format!("!{pat}")) .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") builder.build().context("failed to compile override set")
} }
/// Read `<root>/.kbignore` if it exists. Each non-blank, non-comment line is /// Read `<root>/.kebabignore` if it exists. Each non-blank, non-comment line is
/// a gitignore pattern. Missing file → empty Vec (not an error). /// a gitignore pattern. Missing file → empty Vec (not an error).
pub(crate) fn read_kbignore(root: &Path) -> Result<Vec<String>> { pub(crate) fn read_kbignore(root: &Path) -> Result<Vec<String>> {
let path = root.join(".kbignore"); let path = root.join(".kebabignore");
if !path.exists() { if !path.exists() {
return Ok(Vec::new()); return Ok(Vec::new());
} }
@@ -250,7 +250,7 @@ mod tests {
fn read_kbignore_strips_blanks_and_comments() { fn read_kbignore_strips_blanks_and_comments() {
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
std::fs::write( std::fs::write(
dir.path().join(".kbignore"), dir.path().join(".kebabignore"),
"# comment\n*.tmp\n\nignored/**\n", "# comment\n*.tmp\n\nignored/**\n",
) )
.unwrap(); .unwrap();

View File

@@ -9,8 +9,8 @@
//! │ ├── alpha.md //! │ ├── alpha.md
//! │ └── beta.md //! │ └── beta.md
//! ├── ignored/ //! ├── ignored/
//! │ └── skip.tmp # excluded by .kbignore //! │ └── skip.tmp # excluded by .kebabignore
//! ├── .kbignore # contains: *.tmp //! ├── .kebabignore # contains: *.tmp
//! └── .DS_Store # implicitly excluded //! └── .DS_Store # implicitly excluded
//! ``` //! ```
//! //!
@@ -52,7 +52,7 @@ fn cfg_for_fixture(root: &str) -> Config {
let mut c = Config::defaults(); let mut c = Config::defaults();
c.workspace.root = root.to_string(); c.workspace.root = root.to_string();
// Clear default excludes (`.git/**`, `node_modules/**`, `.obsidian/**`) // 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. // baked-in default-excludes.
c.workspace.exclude.clear(); c.workspace.exclude.clear();
c c
@@ -101,18 +101,18 @@ fn scan_and_strip() -> Value {
fn tree_1_snapshot_matches_baseline() { fn tree_1_snapshot_matches_baseline() {
let actual = scan_and_strip(); 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 // *before* attempting to read it. This is the only path that may
// create the file from scratch. // 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"; let pretty = serde_json::to_string_pretty(&actual).unwrap() + "\n";
std::fs::write(baseline_path(), pretty).expect("write baseline"); 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(|_| { let baseline_text = std::fs::read_to_string(baseline_path()).unwrap_or_else(|_| {
panic!( 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`", -p kb-source-fs --test snapshot_tree1 -- tree_1_snapshot_matches_baseline`",
baseline_path().display() baseline_path().display()
) )

View File

@@ -28,7 +28,7 @@ const ASSET_ID_HEX_LEN: usize = 32;
/// Default file name under `config.storage.data_dir`. Kept private — the /// 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 /// path layout is a §6.3 design decision, not part of the store's public
/// surface. /// surface.
const SQLITE_FILE: &str = "kb.sqlite"; const SQLITE_FILE: &str = "kebab.sqlite";
/// Subdirectory under `data_dir` holding shard-prefixed asset bytes /// Subdirectory under `data_dir` holding shard-prefixed asset bytes
/// (`<aa>/<asset_id>`). Mirrors design §6.3. /// (`<aa>/<asset_id>`). Mirrors design §6.3.
@@ -74,7 +74,7 @@ impl SqliteStore {
apply_pragmas(&conn)?; apply_pragmas(&conn)?;
tracing::debug!( tracing::debug!(
target: "kb-store-sqlite", target: "kebab-store-sqlite",
data_dir = %data_dir.display(), data_dir = %data_dir.display(),
db = %db_path.display(), db = %db_path.display(),
"opened sqlite store" "opened sqlite store"
@@ -94,7 +94,7 @@ impl SqliteStore {
schema::runner() schema::runner()
.run(&mut *conn) .run(&mut *conn)
.map_err(|e| StoreError::Migration(e.to_string()))?; .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(()) Ok(())
} }

View File

@@ -37,7 +37,7 @@ impl TestEnv {
} }
pub fn db_path(&self) -> PathBuf { 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. /// Open a side-channel rusqlite connection for direct SQL inspection.

View File

@@ -335,7 +335,7 @@ fn normalize_ws(s: &str) -> String {
/// - no `END;` after the virtual-table line /// - no `END;` after the virtual-table line
fn extract_design_5_5_fts_block() -> String { fn extract_design_5_5_fts_block() -> String {
let doc = include_str!( 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 let heading_idx = doc
.find("### 5.5 Chunks + FTS5") .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 ────────── // ── 6. WAL cleanup: drop store before tempdir reaps WAL/SHM ──────────
/// Mirror the P1-6 pattern: opening + migrating + dropping the store /// 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, /// 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 /// the WAL/SHM siblings must either not exist or be removable — if a
/// stray handle were holding them open, on Windows the remove would /// stray handle were holding them open, on Windows the remove would

View File

@@ -20,7 +20,7 @@
//! serializes vector ops, and current-thread saves the two worker //! serializes vector ops, and current-thread saves the two worker
//! threads a multi-thread runtime spawns by default. //! 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), //! §5.6 (embedding_records DDL), §6.3 (lancedb table naming),
//! §7.2 (VectorStore), §9 (versioning). //! §7.2 (VectorStore), §9 (versioning).

View File

@@ -111,7 +111,7 @@ impl LanceVectorStore {
})?; })?;
tracing::debug!( tracing::debug!(
target: "kb-store-vector", target: "kebab-store-vector",
vector_dir = %vector_dir.display(), vector_dir = %vector_dir.display(),
"opened LanceVectorStore" "opened LanceVectorStore"
); );
@@ -141,7 +141,7 @@ impl LanceVectorStore {
.await .await
.context("create_empty_table")?; .context("create_empty_table")?;
tracing::info!( tracing::info!(
target: "kb-store-vector", target: "kebab-store-vector",
table = table_name, table = table_name,
dim, dim,
"created Lance table" "created Lance table"
@@ -275,7 +275,7 @@ impl VectorStore for LanceVectorStore {
.context("phase 3: mark embedding_records committed")?; .context("phase 3: mark embedding_records committed")?;
tracing::info!( tracing::info!(
target: "kb-store-vector", target: "kebab-store-vector",
table = %table_name, table = %table_name,
rows = recs.len(), rows = recs.len(),
"upsert committed" "upsert committed"
@@ -306,7 +306,7 @@ impl VectorStore for LanceVectorStore {
Some(name) => name, Some(name) => name,
None => { None => {
tracing::debug!( tracing::debug!(
target: "kb-store-vector", target: "kebab-store-vector",
dim, dim,
"search: no Lance table matches query dim — returning empty" "search: no Lance table matches query dim — returning empty"
); );
@@ -477,7 +477,7 @@ fn decode_lance_hits(batches: &[RecordBatch]) -> Result<Vec<LanceCandidate>> {
fn score_from_distance(distance: f32) -> f32 { fn score_from_distance(distance: f32) -> f32 {
if distance.is_nan() { if distance.is_nan() {
tracing::warn!( tracing::warn!(
target: "kb-store-vector", target: "kebab-store-vector",
"NaN cosine distance from Lance — coercing to score 0" "NaN cosine distance from Lance — coercing to score 0"
); );
return 0.0; return 0.0;
@@ -515,7 +515,7 @@ async fn find_matching_table(
} }
Err(e) => { Err(e) => {
tracing::warn!( tracing::warn!(
target: "kb-store-vector", target: "kebab-store-vector",
table = %name, table = %name,
error = %e, error = %e,
"search: skipped unopenable table" "search: skipped unopenable table"

View File

@@ -73,7 +73,7 @@ fn vector_hits_snapshot_run_1() {
.join("vector") .join("vector")
.join("run-1.json"); .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::create_dir_all(fixture.parent().unwrap()).unwrap();
std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap()) std::fs::write(&fixture, serde_json::to_string_pretty(&actual).unwrap())
.unwrap(); .unwrap();
@@ -83,7 +83,7 @@ fn vector_hits_snapshot_run_1() {
let expected: serde_json::Value = let expected: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else( serde_json::from_str(&std::fs::read_to_string(&fixture).unwrap_or_else(
|_| panic!( |_| 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() fixture.display()
), ),
)) ))
@@ -97,14 +97,14 @@ fn vector_hits_snapshot_run_1() {
panic!( panic!(
"snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \ "snapshot fixture is a placeholder — regenerate on AVX hardware then commit. \
Path: {}. To regenerate: \ 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() fixture.display()
); );
} }
assert_eq!( assert_eq!(
actual, expected, 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. // Independent guard: scores must be non-increasing.

View File

@@ -15,7 +15,7 @@
"kind": "copied", "kind": "copied",
"path": "<stripped>" "path": "<stripped>"
}, },
"workspace_path": ".kbignore" "workspace_path": ".kebabignore"
}, },
{ {
"asset_id": "ba6cd31cab86eff7a86638ee76494bcf", "asset_id": "ba6cd31cab86eff7a86638ee76494bcf",