--- title: v0.20.2 — full dogfood findings (8 todo patch release) created: 2026-05-28 status: accepted contract_sections: [§6 filesystem+config layout, §9 versioning rules and version cascade, wire schema v1 additive] parent_handoff: docs/superpowers/handoffs/2026-05-28-v0.20.1-fulldogfood-findings-handoff.md --- # v0.20.2 — full dogfood findings (8 todo patch release) ## 1. Summary v0.20.1 full dogfood run (`/build/dogfood/corpus` 6293 file → 3940 docs / 34896 chunks) 에서 발견된 8개 finding 을 단일 patch release (0.20.2) 로 수렴. **P0: 3개 (behavior + docs)**, **P1: 3개 (docs drift)**, **P2: 2개 (setup hint)**. 중심은 RAG response language auto-matching (Todo #1), bulk search input schema 명확화 (Todo #2), 그리고 wire schema + config 문서화 정확화. --- ## 2. Background ### 2.1 v0.20.1 Release 및 dogfood 환경 - v0.20.1: `PR#191` merge (V009 한국어 morphological tokenizer + N-gram supplement + eager backfill). 2026-05-28 fresh full ingest. - dogfood data: `/build/dogfood/corpus/` (6293 source file, format/category 별 분류) → 3940 docs, 34896 chunks. - dogfood KB: `/build/dogfood/kb/` (SQLite + LanceDB + assets). - test coverage: `docs/DOGFOOD.md` §1~§11 시나리오 + handoff §3 regression 시나리오 전체 실행. ### 2.2 발견된 finding 8개 각 finding 은 user-visible behavior 또는 docs/wire schema drift. brainstorming 은 이미 completed (handoff §1 + user confirmation). - **Finding O** (Todo #1): Ask 영어 query → 한국어 response (RAG prompt template 한계) - **Finding V** (Todo #2): bulk search input format 불명확 (`{"text":...}` vs `{"query":...}`) - **Finding Q** (Todo #3): list docs title 중복 (heading-based title 추출 unique 부족) - **Finding U** (Todo #4): doc.lang = "und" 53% (code file 의 자연 언어 감지 설계 의도 미문서화) - **Finding H/L** (Todo #5): fusion_score 위치 오표기 (top-level 같이 표기되나 실제 `.retrieval.fusion_score`) - **Finding X** (Todo #6): score_kind 의 mode 별 의미 미명시 (lexical mode 에서 `fusion_score == lexical_score`) - **Finding G** (Todo #7): schema --json 의 `index_version` 의미 혼동 (lexical index ≠ vector store index) - **Todo #8**: Ollama endpoint default localhost (init hint 부재) --- ## 3. Goals + Non-Goals ### Goals - User-visible behavior 및 wire schema 정확성 검증 완료. - RAG response language auto-matching (영어 query → 영어 response, 한국어 → 한국어). - bulk search input shape 명확화 + error message hint. - list docs 출력에서 title 중복 제거 (doc_path 보조 표시). - docs/wire schema 에서 fusion_score, score_kind, index_version 의미 정리. - config.toml init hint 에 Ollama remote endpoint 안내 추가. - 모든 finding 을 patch release 한 건으로 번들링. ### Non-Goals - wire schema major bump (v1 → v2) — all changes additive or docs-only. - RAG `--response-lang` flag 도입 (Non-goal per brief). - code 주석 자연 언어 감지 (Non-goal, V010 migration 비용 高). - Search ranking algorithm 변경. - Embedding model 또는 vector search 변경. --- ## 4. Design Decisions (8 todo) ### Todo #1 — Ask 응답 언어 (RAG prompt template) [P0] **Problem**: `crates/kebab-rag/src/pipeline.rs` line 1874~1878 의 `SYSTEM_PROMPT_RAG_V1`/`SYSTEM_PROMPT_RAG_V2` 가 통째로 한국어 prose. `MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT` (line 1872 부근) 도 한국어. 응답 언어를 지정하는 규칙이 없어 gemma4 가 영어 query 에도 한국어로 답함. **Decision**: **Query 언어 자동 매칭** (user confirmation 완료). **Implementation**: 1. **New system prompt: `SYSTEM_PROMPT_RAG_V3`** — rag-v2 의 7규칙 + 언어 매칭 규칙 1개. - 위치: `crates/kebab-rag/src/pipeline.rs` line 1880 부근. - 규칙 문구 (예시, 다듬음 권장): ``` 답변은 [원본 질문]과 같은 언어로 작성한다. 단 [근거]에서 큰따옴표로 직접 인용하는 부분은 원문 언어 그대로 둔다. ``` - rag-v2 는 그대로 보존 (legacy backwards-compat). 2. **system_prompt_for() 함수 갱신** (line 1883 부근): - match arm 에 `"rag-v3"` 추가. - unknown 에러 메시지의 expected 목록도 v3 포함 갱신. 3. **`MULTI_HOP_SYNTHESIZE_SYSTEM_PROMPT` 갱신** (line 1872): - 동일 언어 매칭 규칙 추가. - decompose/decide 는 JSON array 출력이라 응답 언어와 무관 → 제외. 4. **config default 갱신** (`crates/kebab-config/src/lib.rs`): - `prompt_template_version` default `"rag-v2"` → `"rag-v3"`. - user TOML 에서 `prompt_template_version = "rag-v2"` 명시 시 legacy 유지. 5. **frozen contract 갱신** (`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md`): - line 287: wire 예시의 `prompt_template_version: "rag-v1"` → `"rag-v3"` (또는 다양한 버전 예시). - line 899: rag-v2 는 legacy backwards-compat 으로 보존 표기. - line 1349: config 예시의 `prompt_template_version = "rag-v2"` → `"rag-v3"`. - line 1533: §9 cascade table 의 `prompt_template_version` 행에 v3 mention. **Trade-off**: 한국어 corpus 를 영어로 물으면 LLM 이 근거를 영어로 번역해 답함. citation `[#번호]` 로 원문 추적은 유지. **Out-of-scope — 다른 prompt 의 언어 동작 (M1)**: 이미지 caption (`crates/kebab-parse-image/src/caption.rs:199-216` `build_prompt(lang_hint)`) 은 `lang_hint = ko/kor` 시 한국어, 그 외 영어로 강제하는 **별도 메커니즘**을 이미 가짐 — lang_hint 기반 + ingest 시점 + 자체 `prompt_template_version` cascade (`caption.rs:132`) 라 query-언어 매칭(본 Todo)과 메커니즘이 다르며 본 release scope 밖. OCR 텍스트 추출은 prompt 없음 (엔진 직접). 이 결정을 명시해 향후 "다른 prompt 에 언어 강제 없음" 암묵 가정으로 인한 drift 추적 불가를 방지. **Unit test**: `system_prompt_for("rag-v3")` 반환값 + v3 언어 규칙 포함 검증. `system_prompt_for_unknown_version_returns_err_with_hint` (`pipeline.rs:2314-2318`) 의 `bail!` 메시지를 `"expected rag-v1, rag-v2 or rag-v3"` 로 갱신 + assert 보강 (contains 검사라 기존도 통과하나 명시). config default 테스트는 §6 (M2) 참조. --- ### Todo #4 — doc.lang semantic (documentation only) [P0] **Problem**: "감지 실패"가 아니라 **설계상 의도**. 모든 code parser (`crates/kebab-parse-code/src/{rust,python,java,kotlin,cpp,c,go,javascript,typescript}.rs`) 가 `lang: Lang("und")` 하드코딩. code 는 자연어 감지를 안 함. 대신 `code_lang` (`crates/kebab-core/src/metadata.rs:38`) 가 소스 언어 보유. 자연어 감지 (lingua) 는 Markdown 등 prose parser (`crates/kebab-parse-md/src/frontmatter.rs:557 detect_lang`) 에서만 수행. und 53% = code 비중. **Decision**: **자연어로 명확화 + 문서화**. 코드 변경 없음 (user confirmation 완료). **Implementation**: 1. **wire schema 명확화** (`docs/wire-schema/v1/schema.schema.json` 또는 README): - `lang` (자연 언어 prose, lingua 감지) vs `code_lang` (소스 코드 언어, ast parser) 의미 분리 명시. - "und" 가 code file 에서 정상 (code parser 가 자연 언어 감지 안 함) 임을 명시. - `lang_breakdown` 의 und 가 대부분 code 비중임을 추가. 2. **README 갱신** (해당 섹션): - 같은 명확화 추가. **Non-goal**: code 주석 자연 언어 감지 (Option B 기각 — V010 migration + 전체 재처리 비용). --- ### Todo #2 — bulk search input format (docs + 소량 code) [P0] **Problem**: 사용자가 여러 input shape (`{"text":...}`, `{"query":{"text":...}}`) 를 시도했으나 모두 실패. 정확한 shape 가 docs 에 부재. **Actual shape** (leader 가 `crates/kebab-app/src/bulk.rs:124-184 parse_one` 직접 검증): ```json {"query": "<텍스트>", "mode": "lexical|vector|hybrid", "k": 3, "trust_min": "primary|secondary|generated", "ingested_after": "", "media": [""], "tag": [""], "lang": ""} ``` - `query` (**required, string** — nested object 아님). - `mode` (optional, default `hybrid`; lexical/vector/hybrid). - `k` (optional, int; **생략 또는 0 → app 이 config `search.default_k`(현재 10, `kebab-config/src/lib.rs:697`)로 해석**. wire default 는 0, `bulk.rs:140-143`). (m1) - `trust_min` (optional, enum primary/secondary/generated). - `ingested_after` (optional, RFC3339 date-time). - `media` (optional, array of string — alias 정규화). - `tag` (optional, array of string). - `lang` (optional, string ISO code). **Implementation**: 1. **wire schema 추가** (`docs/wire-schema/v1/bulk_search_input.schema.json` 신규): - 위 8개 필드 명시 (required: `query` 만; 나머지 optional + default 설명). - **(m4)** 기존 `bulk_search_item.schema.json` 의 `query` 필드(= "Input echo, verbatim JSON object") description 을 신규 input schema 로 `$ref` / cross-ref 연결해 일관성 유지. 2. **CLI help 갱신** (`kebab search --bulk --help`): ```bash echo '{"query":"한국","mode":"lexical","k":3}' | kebab search --bulk --json ``` 3. **error message 갱신** (`bulk.rs:129`): - 현재: `missing required field: query` - 개선: expected shape hint 추가 (예: `missing required field: query (expected {"query":"","mode":"lexical|vector|hybrid",...})`). 4. **docs/DOGFOOD.md 갱신**: bulk scenario 예시 정정. --- ### Todo #3 — list docs title 중복 (code) [P0] **Problem**: heading 기반 title 추출이 unique 하지 않아 여러 file 이 `title: "Registry"`, `title: "dispatch"` 등 동일 title 로 반환. **Decision (self-review)**: **Option A** — human-readable 출력에 `doc_path` 보조 표시. title 자체·wire schema 는 불변 (안전). **Implementation**: 1. **human-readable output** (`crates/kebab-cli/src/main.rs` 또는 등가, list docs 서브커맨드): - 각 doc row 에 `doc_path` 컬럼 추가 (현재 `--json` 에는 이미 노출). - 예: `title: "Registry" doc_path: "src/Registry.java"` 2. **README `kebab list docs` 동작 명세 갱신**: path 표시 추가 명시. **Non-goal**: title unique 화 (filename 추가 suffix 등) — title 자체는 wire schema 로 exposed 되므로 변경 최소화. --- ### Todo #5 / #6 — fusion_score / score_kind docs (docs) [P1] **Actual wire schema** (`docs/wire-schema/v1/search_hit.schema.json`): - required: `score` (top-level), `score_kind`, `retrieval` (object), `index_version`. - `fusion_score`, `lexical_score`, `vector_score`, `lexical_rank`, `vector_rank` 는 모두 `retrieval` object 내부. **top-level `score` 의 의미 (B1 — 코드 검증 완료, `kebab-core/src/search.rs:95-99`)**: - top-level `score` 는 canonical ranking score 이며, 그 의미를 `score_kind` 가 선언한다: - `Rrf` (hybrid) — RRF normalized `[0,1]` - `Bm25` (lexical-only) — raw BM25 - `Cosine` (vector-only) — raw cosine - single-mode 검색에서는 fusion 미실행 → `score == retrieval.fusion_score`. hybrid 에서만 `retrieval.fusion_score` 가 RRF normalized 값. **Finding X (score_kind semantics)**: lexical mode 의 `score == fusion_score == lexical_score` 는 위 single-mode 동작의 정상 귀결. **Note (m2)**: `score_kind` 의 rrf/bm25/cosine 의미는 이미 `search_hit.schema.json` 의 `score_kind` description (p9-fb-38) 에 문서화돼 있음 → 실제 gap 은 README + (score ↔ fusion_score 관계). 범위를 "README 보강 + schema 의 기존 score_kind 설명 cross-ref" 로 한정 (schema 자체 변경 최소). **Implementation**: 1. **README search response 예시 정정**: `.retrieval.{fusion_score, lexical_score, vector_score, lexical_rank, vector_rank}` 구조 + top-level `score` vs `retrieval.fusion_score` 관계 (위 B1) 명시. 2. **schema.json description**: README 에서 기존 `score_kind` description cross-ref. (schema 자체는 충분 — 필요 시 description 보강만.) --- ### Todo #7 — schema --json index_version (docs) [P1] **Problem**: `crates/kebab-app/src/schema.rs:213` 의 `index_version` 은 **vector store (lance) index version**. search hit 의 `index_version` 은 lexical (`fts5-v009-...`). 두 의미 다름. **Decision (self-review)**: rename 안 함 (wire v1 호환성 보존). **문서화만**. **Implementation**: 1. **README + `docs/wire-schema/v1/schema.schema.json` description**: - **schema.json 의 `index_version`** = vector store (lance) index version. 예: `"v1"`. - **search_hit.json 의 `index_version`** = lexical (FTS5) index version. 예: `"fts5-v009-korean-morphological"`. - 두 field 의 의미 다르며, version cascade 에서 별도 추적됨 명시. --- ### Todo #8 — Ollama endpoint default (docs) [P2] **Problem**: localhost default 유지하나, remote Ollama 사용 시 setup 단계에서 endpoint 갱신 필요성 안내 부재. **Decision (self-review)**: localhost default 유지 + init hint. **Implementation**: 1. **`kebab init` 의 hint 메시지** (에 따라 코드 수정): - "Remote Ollama 사용 시 [config 파일] 의 `[models.llm] endpoint` 갱신 필요" 안내. --- ## 5. Contract / Version / Release 영향 ### 5.1 Frozen Contract Bump `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 갱신 (same PR). **load-bearing default 선언은 line 1349 + 1533** (★): - ★ line 1349 (config 예시): `prompt_template_version = "rag-v2" # default` → `"rag-v3"`. - ★ line 1533 (§9 cascade table): `prompt_template_version` 행의 "코드 상수 (rag-v2)" → rag-v3. - line 899 (m3): 기존 텍스트는 "V1 은 legacy backwards-compat 으로 보존" (**rag-v1** 에 관한 것). 변경 후 v1·v2 **둘 다** legacy (v3 default) 이므로 이 노트에 **rag-v2 도 legacy 임을 추가**. - line 287 (n1): `answer.v1` JSON **예시** 블록 안 stale 값 (같은 블록 model 이 `qwen2.5:14b-instruct` 로 현행 gemma4 와 이미 불일치). historical 예시로 두거나 블록 전체 refresh — 단독 교체는 비일관 예시 유발. **default 선언 대상 아님** (1349/1533 과 동급으로 묶지 말 것). **(contract 규칙)** CLAUDE.md "design doc 변경 → 모든 referencing task spec 같은 PR": `prompt_template_version` / §9 를 참조하는 `tasks/p/` task spec 이 있으면 같은 PR 에서 동반 갱신 (구현 단계에서 grep 확인). ### 5.2 Version Cascade - **workspace `Cargo.toml` version bump**: 0.20.1 → 0.20.2 (patch, release commit). - **wire schema**: additive only (bulk_search_input schema 추가, description 갱신). breaking 아님 → v1 유지. - **config schema**: no breaking changes. `prompt_template_version` default 변경은 user config 에서 명시 시 legacy 유지. - **eval cascade (M4)**: `prompt_template_version` 은 §9 cascade 의 snapshot 대상 — eval runner 가 `eval_runs.config_snapshot_json` 에 박제. rag-v3 default flip 후 신규 eval run 의 `config_snapshot_json.prompt_template_version` 이 `rag-v3` 로 기록됨 → rag-v2 baseline 과 `compare` 시 prompt 차원 변경을 감안해야 함. **코드 변경 / migration 불필요 (additive)** — eval 비교 해석에만 영향. - **dogfood trigger 해당**: #1 (RAG prompt template 변경 = Search/RAG behavior trigger) → dogfood verification 필수. ### 5.3 Release Notes `docs/release-notes/v0.20.2-draft.md` (또는 gitea release body): - **변경 사실**: RAG response language auto-matching (query 언어 → response 언어). - **Trade-off**: 한국어 corpus 를 영어로 물으면 LLM 이 영어로 답함 (한국어 근거 번역). citation 은 유지. - **Dogfood evidence**: `/build/dogfood/kb/` 에서 영어/한국어/혼합 query 응답 언어 검증 결과 (hyperlink to HOTFIXES). - **Documentation 정확화**: wire schema (fusion_score, score_kind, index_version), bulk search input, list docs path, doc.lang semantic, Ollama endpoint hint. --- ## 6. Testing / Dogfood 검증 정책 **Per CLAUDE.md § 도그푸딩 검증 정책**: 1. **Full dogfood 재실행** (각 finding 구현 후): - re-ingest: prompt/docs 변경은 chunk 불변 → ingest skip. #1/#4/docs todo 는 재-ingest 불필요. - `docs/DOGFOOD.md` §1~§11 query 시나리오 + handoff §3 regression 시나리오 전체 실행. - dogfood KB: `/build/dogfood/kb/`, config: `/build/dogfood/config.toml`, binary = 새로 빌드한 release. 2. **각 finding 별 검증 체크리스트**: - **#1**: 영어/한국어/혼합 query 의 응답 언어 일치 확인. - **#2**: bulk search `{"query":"테스트","mode":"lexical","k":3}` 정상 동작. - **#3**: `kebab list docs` 출력에 title + path 함께 표시. - **#4**: schema 및 README 문서화 검토 (code 동작 확인 불필요). - **#5/#6**: search response 예시 정정 및 schema description 정확성 검토. - **#7**: schema --json 및 search hit 의 `index_version` 의미 documentation 검토. - **#8**: `kebab init` 출력에 Ollama endpoint hint 노출 확인. 3. **Unit test**: - `system_prompt_for("rag-v3")` 반환값 + v3 언어 규칙 포함. - unknown 에러 메시지 갱신 ("expected rag-v1, rag-v2 or rag-v3") — `pipeline.rs:2314-2318`. - **(M2)** config default 테스트 갱신: `defaults_rag_prompt_template_version_is_rag_v2` (`kebab-config/src/lib.rs:1316`) → `..._is_rag_v3`, assert `"rag-v3"`. default flip 시 이 테스트가 **FAIL 하므로 반드시 동반 수정**. (`pipeline.rs:2463` fixture 는 명시값 `"rag-v2"` 라 영향 없으나 확인.) 4. **도그푸딩 결과 기록** (`tasks/HOTFIXES.md` + release notes): - dated entry: 각 finding 검증 scenario + evidence snippet. - release notes: user-facing surface 변경 (RAG response language, docs 정확화). --- ## 7. Non-goals (Handoff §2.4 별도 spec) - `--response-lang` flag (Todo #1 Option B 기각). - code 주석 자연 언어 감지 (Todo #4 Option B 기각). - BS-A HTML 지원 / BS-B Tier visibility / BS-C `kebab dogfood` subcommand / BS-D code chunk N-gram / BS-E builtin_blacklist 명세. --- ## 8. References - Parent handoff: `docs/superpowers/handoffs/2026-05-28-v0.20.1-fulldogfood-findings-handoff.md` - Frozen contract: `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` - Dogfood run: `/build/dogfood/kb/`, logs at `/build/dogfood/logs/` - Wire schema: `docs/wire-schema/v1/` - Code locations: - RAG: `crates/kebab-rag/src/pipeline.rs:1872~1883` - Metadata: `crates/kebab-core/src/metadata.rs:38` - Frontmatter: `crates/kebab-parse-md/src/frontmatter.rs:557` - Bulk search: `crates/kebab-app/src/bulk.rs:127, 293` - Schema: `crates/kebab-app/src/schema.rs:213` - Config: `crates/kebab-config/src/lib.rs` --- ## Spec Self-Review > **round-1 critic 리뷰 (opus, `.omc/spec-review-r1.md`) 반영 완료**: B1(top-level score placeholder→확정), M1(caption out-of-scope), M2(config default 테스트), M3(bulk 전체 필드 + leader 가 tag/lang 추가 발견), M4(eval cascade), m1~m5 / n1~n2. ✅ **Placeholder check**: round-1 에서 발견된 line 189 placeholder ("top-level score 확인 후 명시") 를 확정 답 (score_kind 가 의미 선언, `search.rs:95-99`) 으로 교체. 잔여 "TBD"/"TODO" 없음. ✅ **Internal contradiction**: 각 todo 간 순서 + 의존성 명확. frozen contract 갱신은 모든 구현 후 한 커밋으로. ✅ **Scope**: patch release (v0.20.2) 로 수렴하는 8개 finding 만 포함. 새 기능 / 범위 확장 없음. 이미지 caption 언어 동작은 명시적 out-of-scope (M1). ✅ **Ambiguity**: 각 todo 의 contract_section 명시, code path 검증, 예시 포함. wire schema 는 v1 유지 (additive). ✅ **Contract section mapping**: §6 (config + prompt_template_version default), §9 (version cascade). ✅ **Code path accuracy**: 인용 line 번호를 opus critic 이 grep/read 로 round-1 재검증. bulk `k` default·bulk 추가 필드(tag/lang)·contract line 899/287 표현 정정 반영 (line 번호 정확성 ≠ 사실 검증 완결성 — round-1 에서 보강). ✅ **Dogfood verification**: CLAUDE.md § 도그푸딩 검증 정책 따라 각 finding 구현 후 full dogfood 재실행 계획 명시.