# v0.32.0 — ponytail-audit over-engineering 정리 arc "1인용 RAG 치고 너무 복잡해졌다"는 문제의식에서 출발한 over-engineering 감사(ponytail-audit) 결과를 네 PR(#219–#222)로 정리한 릴리스. **능력(검색·청킹·RAG 품질)은 전부 유지하되 조작 표면·코드 구조·crate 수를 단순화.** 이 arc(#219–#222) 누적 약 **−3400줄, crate 22→20, dep −1**(serde_yaml 제거 — `unsafe-libyaml` 은 serde_yaml_ng 도 transitive 로 가져오므로 유지). (참고: 직전 v0.31.0 spine 이 24→22 를 했으므로 v0.31.0 이전 기준 절대 crate 수는 24→20.) `minor` bump 트리거는 #219 의 CLI 플래그/config 키 제거(인터페이스 변경) 하나뿐 — 나머지 셋은 출력·동작 byte-identical 이라 자체로는 patch 급. 실엔진 도그푸딩 evidence 는 `tasks/HOTFIXES.md` 2026-06-27 항목. ## 1. 죽은 search-cache scaffold 제거 (#219) — minor (인터페이스 변경) in-process LRU search cache 는 spine 재작성(#214, v0.31.0)에서 이미 삭제됐는데, 그 비계만 코드·CLI·문서에 남아 있었다. 잔존물을 걷어낸다. - **변경 사실**: `kebab search --no-cache` 와 `kebab search --explain` 플래그 제거(둘 다 이미 no-op/미배선이었음). `[rag] explain_default` config 키 제거. 내부적으로 `App::search`/`search_uncached` 붕괴 + `search_uncached_with_config` facade 삭제. - **trade-off**: `kebab search --no-cache`/`--explain` 를 타이핑하던 muscle memory 가 있으면 "unexpected argument" 에러가 난다. (실제로 둘 다 아무 동작도 안 하던 죽은 플래그였다.) `kebab ask --explain` 은 **그대로 live** — 영향 없음. - **mitigation**: search 의 ranking 디테일이 필요하면 `kebab search --trace`(live)가 pre-fusion lexical/vector/RRF 후보 + per-stage timing 을 보여준다. `[rag] explain_default` 가 든 옛 config.toml 은 그대로 로드된다(serde 가 unknown 키 무시 — 마이그레이션 불필요). - **upgrade**: 자동. 새 바이너리부터 두 플래그가 사라진다. `search_cache: false` wire capability(`schema.v1`)는 정직하게 유지(없는 캐시를 false 로 보고). ## 2. 9개 동일 code AST chunker → CodeAstV1Chunker 통합 (#220) — 출력 byte-identical `code_{c,cpp,go,java,js,kotlin,python,rust,ts}_ast_v1.rs` 9개는 **struct 이름 + `VERSION_LABEL` 문자열만 다른 byte-identical 복붙**이었다(chunker 는 tree-sitter 미사용, `lang` 은 `SourceSpan::Code` 데이터에서 흐름). 단일 `CodeAstV1Chunker { version_label }` + `for_lang(lang)` 로 통합(**−3030줄**). - **변경 사실**: 9개 src 파일 → 1개. `for_lang` 이 9개 언어를 기존 `VERSION_LABEL` 문자열로 **verbatim** 매핑(`rust→code-rust-ast-v1`, `typescript→code-ts-ast-v1` 등). - **trade-off**: 없음 — `chunker_version` 문자열이 언어별로 동일하게 보존돼 `chunk_id`(= `id_for_chunk(doc, chunker_version, …)`)가 byte-identical → **재인덱싱 0, 임베딩 드리프트 0**. - **mitigation**: 9개 골든 스냅샷 테스트가 expected 무수정 통과(단위 증명) + **실코퍼스 A/B 도그푸딩**: pre-arc(9-chunker) vs post-arc(consolidated) 바이너리로 같은 코드 코퍼스 ingest → 120 chunks 동일, chunk_id+chunker_version diff **완전 비어있음**. - **upgrade**: 자동. 재인덱싱 불필요(같은 chunk_id 라 idempotent upsert 가 no-op). ## 3. 빈 re-export shim crate 흡수 (#221) — 동작 불변 (crate 22→20) `kebab-embed`/`kebab-llm` 은 "새 type 없음"을 자처한 순수 re-export 셸이었다(trait 은 이미 `kebab-core` 소유, mock + test helper 만 보유). `kebab-core` 의 default-OFF `mock` feature 로 흡수. - **변경 사실**: 두 crate 삭제, `MockEmbedder`/`MockLanguageModel`/`assert_*` helper 를 `kebab-core` 로 이동(`mock` feature, dev-dep 에서만 활성). workspace crate 22 → 20. - **trade-off**: 없음 — production 빌드는 mock 모듈을 컴파일하지 않음(default-OFF). trait surface·동작 불변. 외부 integration 영향 0(단일 바이너리). - **mitigation**: mock byte-identical(import rewrite 만), 의존 crate 테스트 전부 녹색. - **upgrade**: 자동. 사용자 표면 무변경. ## 4. FusionPolicy inline + dual-YAML 통일 + NLI default 제거 (#222) — 출력 불변 audit 잔여 작은 정리 셋. (1) 변종 하나뿐이던 `FusionPolicy::Rrf` enum 을 `HybridRetriever.k_rrf: u32` 로 inline(RRF 수식 byte-identical). (2) `serde_yaml`(0.9, archived) → `serde_yaml_ng`(0.10, maintained fork) 일원화 — 중복 YAML dep(`serde_yaml`) 제거(workspace pin). (`unsafe-libyaml` 은 serde_yaml_ng 도 transitive 로 의존하므로 트리에 유지 — 제거된 건 archived `serde_yaml` 하나.) (3) `NliVerifier::hypothesis_token_count` 의 `Ok(0)` default 제거(prod 가 이미 override 라 동작 불변, footgun 을 compile-time 보장으로). - **trade-off**: 없음 — 검색 순위·YAML 파싱·prod NLI 동작 전부 불변. - **upgrade**: 자동. ## 버전 `0.31.0 → 0.32.0` (minor). 새 기능·새 CLI 명령은 없으나 #219 의 플래그·config 키 제거가 인터페이스 변경이라 minor 강제. 나머지 셋은 byte-identical 정리. ## 도그푸딩 (실엔진 종단 검증, 2026-06-27) - **chunker A/B (핵심)**: pre-arc(v0.31.0, 9-chunker) vs post-arc(consolidated) 바이너리로 5언어 9파일 코드 코퍼스 ingest. 양쪽 120 chunks, **chunk_id + chunker_version diff 완전 비어있음** → consolidation 이 실코퍼스에서 byte-identical 임을 종단 확증. 라벨: code-c/go/python/rust/ts-ast-v1. - **GPU**: r9700 ollama 스왑(lemonade↔ollama-r9700), `snowflake-arctic-embed2` 임베딩으로 ingest 성공, lexical 검색(`split oversize` → 해당 함수 청크 hit) 정상. **lemonade 무조건 복구 확인.** - **CLI 표면**: `search --no-cache`/`--explain` → "unexpected argument" 에러(제거 확인), `ask --explain`/`search --trace` → help 에 present(live 확인). ## 알려진 한계 / 후속 - RAG 생성(ask) 경로는 이 arc 가 변경하지 않아(PR4 NLI 는 prod override 라 불변) GPU LLM ask 재도그푸딩은 생략(GPU 는 임베딩 ingest 로 행사). 다음 RAG-prompt/모델 변경 시 재개. - `kebab eval` 은 게이팅하지 않고 일급 subcommand 유지(audit 의 "placeholder" 전제는 stale 주석 오독 — 실제 완전 배선됨). stale 주석만 정정.