---
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": "<RFC3339>",
 "media": ["<media>"], "tag": ["<tag>"], "lang": "<iso>"}
```
- `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":"<text>","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<N>/` 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 재실행 계획 명시.
