diff --git a/docs/superpowers/specs/2026-05-28-v0.20.2-dogfood-findings-design.md b/docs/superpowers/specs/2026-05-28-v0.20.2-dogfood-findings-design.md new file mode 100644 index 0000000..7f0cd0e --- /dev/null +++ b/docs/superpowers/specs/2026-05-28-v0.20.2-dogfood-findings-design.md @@ -0,0 +1,336 @@ +--- +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 재실행 계획 명시.