fix(cli): honor --config flag + improve search output legibility #20
Reference in New Issue
Block a user
Delete Branch "fix/cli-config-flag-and-search-output"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
변경 요약
P3-5 머지 후 직접 워크스페이스 (
/tmp/kb-smoke/, 6개 markdown — Rust/Python/Architecture/SQLite 주제, 한국어+영어 혼합)에 대해 CLI smoke를 돌리다 발견한 두 가지 이슈 핫픽스입니다.발견된 문제
1.
--configflag CLI에서 무시됨kb-cli/src/main.rs:216의Ingestarm이cli.config로 SourceScope만 만들고, 그 후kb_app::ingest(scope, summary_only)호출은 내부적으로Config::load(None)을 부릅니다 —~/.config/kb/config.toml만 봄.search,list,inspect,doctor모두 동일.스모크 중 다음 명령:
--config패스를 완전히 건너뛰고 XDG 기본을 출력. 사용자는KB_*환경 변수로만 우회 가능했습니다.2. Search 출력에서 RRF 점수가 "0.02"로 뭉개짐 + heading_path 누락
{:.2}포맷이 RRF 합산 점수의 (0, ~0.033] 범위 (k_rrf=60 default 기준)를 모두 "0.02"로 잘라버려서 ranking signal 사라짐. 게다가 같은 문서에서 나온 다른 청크가 시각적으로 구분 안 됨.수정
kb-app
*_with_config(Config, ...)헬퍼 (P3-5에서 통합 테스트 seam으로 도���한#[doc(hidden)] pub)를 정식 "config-explicit" API로 재정의. 모듈 doc-comment를 "test-only seam" 표현에서 "three callers: CLI--config, integration tests, TUI session"으로 업데이트.#[doc(hidden)]는 유지 — rustdoc은 깔끔하게 두고 cross-crate 호출은 허용.doctor()를doctor_with_config_path(Option<&Path>)으로 재작성. config_loaded 프로브가 실제 검사한 경로를 보고하고,--config가 존재하지 않거나 malformed 파일을 가리키면 hard error로 fail (defaults가 silently mask하지 않게). data_dir_writable은 로드된 config의storage.data_dir을 (env override 적용 후) 풀어서 probe하므로 사용자 정의 경로가 출력에 그대로 반영됩니다. 기존doctor()시그니처는None-passing wrapper로 남김.kb-cli
ingest/search/list/inspect/doctor모두 subcommand 시작에서Config::load(cli.config.as_deref())으로 Config을 한 번 빌드한 뒤*_with_config변형에 직접 thread.{:.4}로 변경 (RRF 점수 범위 ≤ ~0.033이라 4자리는 가장 보수적 정확도 충족).> heading1 / heading2접미사를 doc_path 뒤에 추가 — 같은 문서 내 다른 chunk를 시각적으로 분간.수정 후 검증
워크스페이스 269 passed / 24 ignored / 0 failed.
cargo clippy --workspace --all-targets -- -D warningsclean.변경 파일
crates/kb-app/src/lib.rs(도큐먼트 +doctor_with_config_path추가)crates/kb-cli/src/main.rs(5개 subcommand에서*_with_config*호출 + 출력 포맷)Out of scope
kb askbody는 P4-3 owner.Two issues surfaced during the post-P3-5 manual smoke test against a six-document workspace: 1. --config flag was silently ignored. kb-cli read cli.config only while building SourceScope inside the Ingest arm, then called kb_app::ingest(scope, summary_only) which internally re-loads Config::load(None) — falling back to ~/.config/kb/config.toml regardless of what the user passed. Same pattern in search, list, inspect, doctor. Users had to rely on KB_* env vars to point at a non-default config. 2. Search output collapsed RRF hybrid scores to "0.02" because `{:.2}` truncated the (0, 0.033]-bounded fused score, and chunks from the same document showed up as identical lines ("3. 0.02 arch/rag-architecture.md") since heading_path was never printed. Fix: - kb-app: doctor/ingest/search/list/inspect already had *_with_config(Config, ...) seams introduced for integration tests (#[doc(hidden)] pub). Repurpose them as the official "config-explicit" API — kb-cli now builds the Config once via Config::load(cli.config.as_deref()) at the top of every subcommand and threads it into the *_with_config variant. Module doc-comment updated to reflect three callers (CLI --config, integration tests, TUI session) instead of "test-only seam". - kb-app: doctor() rewritten as doctor_with_config_path(Option<&Path>) that respects an explicit path. config_loaded probe now reports the actual path checked, returning a clear hard error if --config points at a non-existent or malformed file (defaults would silently mask user intent). data_dir_writable resolves storage.data_dir from the loaded config (with env overrides applied via Config::apply_env) so --config users see their custom paths reflected. Original doctor() signature kept as a None-passing wrapper. - kb-cli: ingest/search/list/inspect/doctor each call the *_with_config* companion. Search printer switches to {:.4} score formatting (RRF hybrid range bounded by ~2/k_rrf ≈ 0.033 at k_rrf=60 default) and appends `> head1 / head2` when heading_path is non- empty so chunks from the same document are visually distinguishable. Verified manually: - `kb --config /tmp/kb-smoke/config.toml doctor` reports the custom config path + custom data_dir, not the XDG defaults. - `kb --config /tmp/kb-smoke/config.toml search "..." --mode hybrid` returns hits with distinct 4-digit scores and heading paths ("rust/ownership.md > Rust 소유권 모델 / Borrow checker"). Workspace 269 passed / 24 ignored / 0 failed; cargo clippy --workspace --all-targets -- -D warnings clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>P3-5 핫픽스 코드 리뷰 — 셀프 머지 게이트로 인해 COMMENT only.
P3-5 직후 직접 워크스페이스 (
/tmp/kb-smoke/, 6개 markdown — 한국어 + 영어 mixed corpus) 대상으로 CLI smoke를 돌리다 두 이슈를 발견하고 같은 PR에서 닫았습니다:--configflag가 모든 subcommand (ingest/search/list/inspect/doctor)에서 silent하게 무시되고 XDG default로 fallback. 사용자 우회는KB_*env vars만 가능했음.{:.2}포맷이라 모든 hit이 "0.02"로 뭉개졌고, heading_path 미표시로 같은 문서 내 다른 chunk 분간 불가능.해결:
#[doc(hidden)] pub fn *_with_config(Config, ...)을 정식 "config-explicit" API로 의미 확장. kb-cli가 매 subcommand에서Config::load(cli.config.as_deref())으로 Config을 한 번 빌드한 뒤 *_with_config 변형에 직접 threading.doctor()를doctor_with_config_path(Option<&Path>)으로 재작성.--config가 없거나 malformed면 hard fail해서 "config_loaded ✓ defaults"의 거짓 통과 방지.{:.4}+> heading1 / heading2suffix.실제 출력 변화:
워크스페이스 269 passed / 24 ignored / 0 failed. clippy clean. inline 코멘트는 모두 잘 만든 결정에 대한 노트입니다. 머지 진행해도 됩니다.
@@ -149,3 +157,2 @@/// Test-only seam — kb-cli must call the public free function/// ([`ingest`]), not this. See module docs./// Config-explicit variant — bypasses [`load_config`] when theP3-5의
#[doc(hidden)] pub fn *_with_config을 test seam에서 "config-explicit API"로 의미 확장한 결정이 정확합니다 —--config우회를 위해 새 함수를 추가하기보다 이미 존재하는 seam을 정식 cross-crate API로 promote.#[doc(hidden)]은 유지해 rustdoc은 깔끔하지만 kb-cli 같은 workspace 내 caller는 호출 가능. 의미 변화를 module doc-comment에 명시한 점도 좋습니다.@@ -840,2 +877,4 @@None => kb_config::Config::xdg_data_dir(),};let writable = (|| -> anyhow::Result<()> {std::fs::create_dir_all(&data_dir)?;doctor_with_config_path이--config <path>가 존재하지 않을 때 silent default fallback이 아니라 hard error로 실패. defaults가 사용자 의도를 가리는 시나리오를 차단 —kb doctor --config wrong.toml이 "config_loaded ✓ ~/.config/kb/config.toml (defaults)"로 거짓 통과하지 않습니다.data_dir_writable이 로드된 config의
storage.data_dir을 풀어서 probe + env override까지 적용 후 expand_tilde.--config사용자가 "내가 지정한 data_dir이 정말 검증되는가"를 즉시 확인 가능. P3-5의 search/ingest path와 동일한 precedence를 doctor에서도 재현.@@ -289,2 +293,3 @@for h in &hits {println!("{:>2}. {:.2} {}", h.rank, h.retrieval.fusion_score, h.doc_path.0);// Show 4-digit score so RRF fused scores (bounded// ~0–0.033 for k_rrf=60) don't all collapse to "0.02".search printer 변경 두 가지 모두 사용자 입장에서 즉시 체감되는 개선:
{:.4}— RRF 합산 점수가 (0, ~2/k_rrf] 즉 k_rrf=60 default에서 ≤ 0.033이라{:.2}는 모든 hit을 "0.02"로 뭉개버렸습니다. 4자리로 ordering signal 보존.> head1 / head2접미사 — 같은 문서에서 나온 다른 chunk가 " arch/rag-architecture.md"로만 표시되어 분간 불가능했던 문제 해결. heading_path가 비어있으면 접미사 생략해 lexical short-doc 케이스에서 noise 안 만듦.