Files
kebab/tasks/HOTFIXES.md
altair823 f9714aa5cb docs(rename): kb → kebab — README, tasks/, docs/, design doc, report
마지막 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 <verb>`. 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) <noreply@anthropic.com>
2026-05-02 04:01:55 +00:00

5.8 KiB

title, date
title date
Post-merge hotfixes log 2026-05-01

Post-merge hotfixes log

Bugs discovered AFTER a phase task was merged, and the small follow-up PRs that close them. Each entry: what broke, how it surfaced, what the fix touched, and which task spec it amends.

The original task specs in tasks/p<N>/p<N>-<M>-*.md stay frozen as the 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 kebab-cli subcommands

Discovered: post-P3-5 manual smoke at /tmp/kebab-smoke/.

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: 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):

  • 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 <path> 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 kebab ask (P4-3 follow-up)

Discovered: post-P4-3 manual smoke against 192.168.0.47 Ollama with gemma4:26b.

Symptom: kebab --config <path> 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):

  • 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.

2026-05-01 — RRF fusion_score incompatible with config.rag.score_gate default

Discovered: post-P4-3 manual smoke. Top hybrid result returned fusion_score = 0.0164 against score_gate = 0.05 → ScoreGate refusal on every hybrid query.

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/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/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/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

Each fix gets a dated subsection with five fields:

  • Discovered: when / how the bug surfaced (smoke, integration test, user report).
  • Symptom: what the user saw / what was wrong.
  • Root cause: the actual code or design issue.
  • Fix: PR number / branch + a one-paragraph summary of the change.
  • Amends: which tasks/p<N>/... spec docs the fix retroactively contradicts. Spec text stays frozen; this log is the live source of truth for post-merge deltas.

If a fix is large enough that the original spec is no longer a useful reference, promote the entry into a new task spec (e.g., p<N>-<M+1>-<topic>.md) and link from here.