#1 (사용자 요청): release notes draft 작성 + spec/plan 의 dogfood evidence cross-link 보강. docs/release-notes/v0.20.1-draft.md (신규): - 4 단락 본문 (한국어 2자 query 지원 + 영어 substring 회귀 + V007→V009 자동 backfill + ingest 성능 영향). - Migration cascade table (lexical_index_version, corpus_revision, wire schema shape preservation). - API + dependency 변경 (lindera v3, lindera-ko-dic v3, retired short_query_hint helper, 새 facade APIs). - Breaking changes 명시 (영어 substring 회귀, 첫 부팅 latency, DB/ binary 크기 증가). - Upgrade 절차 + Known limitation + 14 dogfood scenario reference. spec Appendix B (segmentation evidence): - "Empirical verification (2026-05-28 dogfood — post-merge update)" subsection 신규. prior-knowledge 가정 vs 실측 결과 table. Scenario 1-4 모두 verified 표시. ko-dic 의 '서울특별시' → '[서울, 특별시]' 분해 증거 명시. plan Changelog: - post-implementation entry: 22 commit on branch, S3 blockers, S7 cascade, S11 sanity regression updates, opus PR review 4 finding fixes. - dogfood evidence entry: 14 scenario verify pass, ko-dic 분해 evidence, HOTFIXES + spec Appendix B cross-link. Spec: …spec…md Appendix B Plan: …plan…md (post-implementation + dogfood evidence Changelog) Release notes: docs/release-notes/v0.20.1-draft.md
60 KiB
title, created, status, spec, critic_r1, critic_r2, parent_handoff, branch, step_count, commit_count
| title | created | status | spec | critic_r1 | critic_r2 | parent_handoff | branch | step_count | commit_count |
|---|---|---|---|---|---|---|---|---|---|
| v0.20.x — 한국어 morphological tokenizer (Bug | 2026-05-28 | accepted | docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec.md | docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec-critic-r1.md | docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec-critic-r2.md | docs/superpowers/handoffs/2026-05-28-v0.20.x-c-korean-morphological-tokenizer-handoff.md | feat/korean-morphological-tokenizer | 11 | 10 |
v0.20.x — 한국어 morphological tokenizer — implementation plan
For agentic workers: REQUIRED SUB-SKILL —
superpowers:executing-plans(또는superpowers:subagent-driven-development). 각 Step 은 단일 commit boundary 를 가지고, 마지막 Step (S11 verify-only) 은 commit 없음. step-level checkbox- [ ]syntax 사용. spec 의 frozen contract 를 보존하며, 본 plan 은 implementation 일정만 다룬다.
Goal. V007 trigram FTS5 tokenizer 의 한계 (2-char 한국어 query 0-hit) 를 해결한다. lindera + lindera-dict-ko-dic 기반 형태소 분석기를 도입하고, 한국어 chunk 의 분해된 morpheme 을 별 column tokenized_korean_text 에 pre-fill 한 뒤 FTS5 의 unicode61 tokenizer 가 공백 경계로 token 화하도록 구성한다. V009 migration 으로 schema 를 한 번 교체하고, 첫 부팅 시 자동 eager backfill 로 기존 KB 의 모든 chunk 를 재-tokenize 한다 (사용자 재-ingest 불필요).
Architecture. V009 migration 은 schema 만 변경 (column ADD, chunks_fts DROP+재정의, triggers 갱신). lindera 호출은 Rust 측 (kebab-chunk::tokenize_korean_morphological) 에서 일어나며, ingest pipeline 의 신규 chunk INSERT 시 동일 transaction 안에 tokenized_korean_text 를 pre-fill 한다. 기존 chunk 의 backfill 은 App::open_with_config 의 first-boot hook 에서 trigger 되어 chunks 전체에 대해 UPDATE → chunks_au trigger 가 chunks_fts 를 자동 재-index. lexical_index_version 은 V007 → V009 로 bump 되어 사용자 visible index_version 값이 변경된다. 영어 substring 매칭은 V002 동작 (whole-token only) 으로 회귀 — release notes 에 정직히 기술된다.
Tech stack. Rust 2024, rusqlite (existing), refinery (existing), lindera = "0.32" (또는 시점 latest stable) workspace dep 신규, lindera-dict-ko-dic per-crate feature dep 신규. CARGO_TARGET_DIR=/build/out/cargo-target/target, -j 4 default.
Spec contract. docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec.md (668 line, ACCEPT, frozen). critic R2 의 9 finding traceability 매트릭스 + Appendix A~D 포함. 본 plan 은 spec 의 §1–§12 를 8 step 으로 분해 + 3 보조 step (test / docs / release).
File map
Modify (12):
Cargo.toml— workspace[workspace.dependencies]에lindera,lindera-dict-ko-dic추가.crates/kebab-chunk/Cargo.toml—lindera+lindera-dict-ko-dicper-crate dep 추가 (또는 feature gate). chunk crate 가 한국어 tokenizer 의 owner.crates/kebab-chunk/src/lib.rs—tokenize_korean_morphological(text: &str) -> Option<String>helper 신규 + chunk builder pipeline 에 호출 추가.crates/kebab-store-sqlite/src/store.rs— chunk INSERT path 에tokenized_korean_textcolumn 추가, backfill APIbackfill_tokenized_korean_text(progress_cb)신규.crates/kebab-store-sqlite/tests/fts.rs— 기존 V007 verbatim test rename + 신규 V009 verbatim test + 한국어 morphological hit test.crates/kebab-search/src/lexical.rs—build_match_string()의 trigram-specific 분기 단순화 (보존 정책 결정).crates/kebab-app/src/app.rs—short_query_hint()함수 + 2 호출 site 제거,lexical_index_version()의 source 갱신.crates/kebab-app/src/lib.rs—short_query_hintre-export 제거,App::open_with_config에 first-boot eager backfill hook 추가.crates/kebab-tui/src/app.rs,crates/kebab-tui/src/search.rs,crates/kebab-tui/src/run.rs—short_query_hint필드 + 호출 제거 (TUI 측 cascade).crates/kebab-cli/tests/wire_search_response.rs—search_plain_emits_short_query_hint_to_stderrtest 삭제 또는 inverted (hint 가 더 이상 emit 안 됨).docs/superpowers/specs/2026-04-27-kebab-final-form-design.md— §5.5 chunks_fts 블록을 V009 의 unicode61 + CASE expression 트리거 본문으로 갱신 (verbatim diff-check 대상).README.md,HANDOFF.md,docs/ARCHITECTURE.md,integrations/claude-code/kebab/SKILL.md,tasks/HOTFIXES.md— surface cascade (S9 묶음).
Create (3):
migrations/V009__fts_korean_morphological.sql— column ADD + chunks_fts re-create + triggers + corpus_revision bump (Section 5 verbatim).crates/kebab-app/tests/search_korean.rs— end-to-end 한국어 2-char query integration test.- (선택)
crates/kebab-chunk/tests/tokenize_korean.rs— lindera tokenize 단위 테스트.
Do NOT modify:
migrations/V007__fts_trigram.sql— historical migration, 그대로 유지 (replay path 보존).migrations/V002__fts.sql— V007 가 이미 design verbatim 비교 대상에서 제외했고, V009 도 동일.- spec 의 ACCEPT 본문 — 본 plan 은 spec 의 implementation 일정만 다룬다. 후속 deviation 은
tasks/HOTFIXES.md에 기록. docs/wire-schema/v1/*.schema.json— wire schema shape 불변 (§11.3, hit ordering 만 변화).
1. Scope summary
이 plan 이 cover 하는 spec section:
- §4 Design Decision (Option A 선택 rationale).
- §5 Migration Cascade (V009) — DDL + corpus_revision + CI diff-check rename.
- §6 Tokenizer Integration — lindera 의존성 + pre-tokenize 우회 + invariant + fallback.
- §7 Query Path — lexical.rs 정리 + short_query_hint 제거 + surface cascade.
- §8 Backward Compatibility + Eager Backfill — first-boot hook.
- §9 Acceptance Criteria — 신규 단위/통합 test.
- §10 Risks + Evidence — Appendix C/D 의 estimate 와 실측 reconciliation.
- §11 Version Cascade —
lexical_index_versionbump. - §12 Release Strategy — v0.20.1 patch release + dogfood verification.
제외 (별 PR / 별 follow-up):
- 일본어/중국어 morphological tokenizer — spec §10.4. 본 plan 은 한국어 ko-dic 만. 동일 패턴 재사용은 별 plan.
- Eval golden baseline regenerate — spec §11.3 결정상 본 PR scope 에 포함하나 별 step (S8) 으로 분리. crate
kebab-eval의 goldens.csv 가 변경되므로 별 commit 권장. - Streaming progress for eager backfill — spec §10.3 mitigation 의 "background job + streaming feedback" 은 v0.20.x 의 후속 P5 sub-item 으로 미룬다.
2. Step decomposition
8 implementation step + 3 보조 step = 총 11 step, 10 commit (S11 verify-only). spec 의 권장 11 step (S1~S11) 을 본 plan 에서는 의존성 그룹에 따라 재배치하고, S2 (design §5.5) + S1 (migration) + S8 (test) 를 한 commit 단위로 묶는다.
Step 1 — V009 migration + design §5.5 갱신 + CI diff-check rename
Implements: §5.1, §5.2, §5.3. Commit 1/10.
Spec sections covered: §5.1 DDL skeleton, §5.2 corpus_revision bump, §5.3 design + CI diff-check 갱신.
Files to modify:
- Create
migrations/V009__fts_korean_morphological.sql. - Modify
docs/superpowers/specs/2026-04-27-kebab-final-form-design.md(§5.5 블록). - Modify
crates/kebab-store-sqlite/tests/fts.rs(V007 verbatim test → V009 rename).
Implementation outline:
- 1.1 V009 migration 파일 작성 — spec §5.1 의 DDL skeleton 을 verbatim 복사한다. 4 영역으로 구성: (a)
ALTER TABLE chunks ADD COLUMN tokenized_korean_text TEXT;, (b) 기존 chunks_fts + 3 trigger DROP, (c) 신규 chunks_fts (unicode61) + chunks_ai/ad/au trigger 재정의 (CASE expression 포함), (d)INSERT INTO chunks_fts ... SELECT ... FROM chunks;(기존 row 재-index), (e) 마지막 줄UPDATE kv SET v = v + 1 WHERE k = 'corpus_revision';. - 1.2 §5.5 verbatim 마커 — V007 의 design verbatim block 마커 (
-- ── BEGIN §5.5 verbatim block ──/-- ── End §5.5 verbatim block ──) 와 동일한 marker 를 V009 migration 의 (c) 블록 주위에 삽입. CI diff-check 가 마커로 슬라이스 추출하므로 형식 일관성 필수. - 1.3 design §5.5 갱신 —
docs/superpowers/specs/2026-04-27-kebab-final-form-design.md§5.5 의 chunks_fts 정의 + 3 trigger 본문을 V009 의 unicode61 + CASE expression 형태로 다시 쓴다. critic R1 finding #5 + critic R2 의 verbatim scope (CASE expression 포함) 따라 trigger body 전체가 verbatim diff 대상. spec drafter 가 §5.3 에서 명시한 "whitespace-normalized string compare of the §5.5 block, scope = CASE expression 포함" 정의 적용. - 1.4 V007 verbatim test rename —
crates/kebab-store-sqlite/tests/fts.rs:407의fts_v007_matches_design_section_5_5_verbatim를fts_v009_matches_design_section_5_5_verbatim로 rename. 함수 body 의 marker 검색 대상도migrations/V007__fts_trigram.sql→migrations/V009__fts_korean_morphological.sql로 변경. fts.rs:402-405 의 doc-comment 갱신 ("V007 stays for historical replay, V009 is the source of truth"). V002 + V007 모두 더 이상 design 비교 대상 아님 명시. - 1.5 corpus_revision SQL 의 정상 동작 검증 — 신규 단위 test
v009_bumps_corpus_revision를 fts.rs 에 추가. TempDir SqliteStore 열기 → migration apply →SELECT v FROM kv WHERE k='corpus_revision'가 V008 적용 시점 대비 +1 이상. - 1.6 Verify.
cargo test -p kebab-store-sqlite --test fts -j 4가 통과.fts_v009_matches_design_section_5_5_verbatim와v009_bumps_corpus_revision모두 hit. 기존 V002/V007 test 는 syntactic 만 유지된 상태로 pass.
Acceptance criteria:
cargo test -p kebab-store-sqlite --test fts fts_v009_matches_design_section_5_5_verbatim -j 4→ exit 0, assertion 통과 (V009 의 §5.5 block 이 design §5.5 와 whitespace-normalized string-equal).cargo test -p kebab-store-sqlite --test fts v009_bumps_corpus_revision -j 4→ exit 0, corpus_revision 값이 strict-monotonic 증가.grep -c "fts_v007_matches_design_section_5_5_verbatim" crates/kebab-store-sqlite/tests/fts.rs→0(rename 완료 확인).cargo clippy -p kebab-store-sqlite --all-targets -j 4 -- -D warnings→ clean.
Step 2 — lindera dependency + license 검증
Implements: §6.1, §10.1. Commit 2/10.
Spec sections covered: §6.1 라이브러리 선정, §10.1 라이센스 검증 (Appendix D).
Files to modify:
Cargo.toml(workspace) —[workspace.dependencies]에lindera = "0.32"와lindera-dict-ko-dic = "0.32"추가. version pin 은 cargo search 결과의 stable latest 로 결정.crates/kebab-chunk/Cargo.toml— per-crate dep 에lindera = { workspace = true }+lindera-dict-ko-dic = { workspace = true, features = ["embedded-dict"] }추가. dict 의 정확한 feature name 은 lindera-dict-ko-dic 의 Cargo.toml metadata 확인 후 결정.crates/kebab-app/Cargo.toml—[features]에fts_korean_morphological = ["dep:lindera"]+default = ["fts_korean_morphological"]등록 (spec §6.3 feature gate).
Implementation outline:
- 2.1 cargo search 로 stable latest 확인 —
cargo search lindera lindera-dict-ko-dic --limit 5. spec §6.1 의 라이브러리 선정 의도와 일치하는 minor version 선택. crate 가 multi-dictionary feature flag (ko-dic,embedded-dict) 를 갖고 있다면 그것을 활성화. - 2.2 Workspace + per-crate dep 추가 —
Cargo.toml의[workspace.dependencies]에 두 dep 추가 (version pin, registry).crates/kebab-chunk/Cargo.toml의[dependencies]에lindera = { workspace = true }와lindera-dict-ko-dic = { workspace = true, features = [...] }추가. - 2.3 cargo build 의 dict 다운로드 정책 검증 — spec §10 risks, Appendix C 의 "release binary 에 embed +15-25 MB" 를 만족시키려면 dict 가 build-time 에 embedded 되어야 한다.
cargo build -p kebab-chunk --release 2>&1 | tail -20으로 dict download / decompression step 가 성공하는지 확인. 실패 시 (network egress 차단 환경) lindera-dict-ko-dic 의embedded-dict또는 동급 feature 가 정확히 어떤 이름인지 crate docs 참조 후 수정. 최악 fallback:build.rsproactive download 또는 vendored crate (별 follow-up). - 2.4 license fingerprint 확인 —
cargo tree --depth 1 -p lindera -p lindera-dict-ko-dic로 두 crate 의 SPDX 확인. Appendix D 의 가정 (MIT OR Apache-2.0for lindera,Apache-2.0for dict) 와 일치하는지 정직히 비교. 불일치 시 plan 의 후속 step (S9 docs sync) 의 release notes 표현을 조정. - 2.5 deny.toml 부재 확인 — 현재 repo 에
deny.toml이 없음 (probe 결과ls deny.toml→ no such file). spec §10.1 + Appendix D 가 명시한cargo deny check절차는 본 plan 에서 deferred. 대신 (a)cargo tree출력의 SPDX 를tasks/HOTFIXES.md에 evidence 로 기록, (b) spec drafter 의 CC BY-SA 라이센스 미포함 fail-fast 정책은 cargo tree 의 license field 수동 확인으로 대체. follow-up:cargo-deny도입은 별 P9 sub-item 으로 분리 (HANDOFF 에 추가). - 2.6 dependency-only commit — 본 step 은 lindera 호출 site 추가 없이 dep 만 추가한다.
cargo build --workspace -j 4 2>&1 | tail -3가 success (어떤 호출도 없으므로 unused-dependency lint trigger 가능 —#[allow(unused_imports)]또는 implicit use 는 다음 step S3 에서 해소). 본 step 의 commit boundary 는 "dep만 추가, 실제 use 는 next commit" 로 명시.
Acceptance criteria:
cargo build --workspace -j 4 2>&1 | grep -E "^error\\[E" | wc -l→0.cargo tree --depth 1 -p lindera -p lindera-dict-ko-dic 2>&1 | grep -iE "MIT|Apache" | wc -l→≥ 2. SPDX 가 Apache-2.0 + MIT 둘 중 적어도 하나 포함.grep -c "lindera" Cargo.toml→≥ 1(workspace dep 등록).grep -c "lindera" crates/kebab-chunk/Cargo.toml→≥ 1(per-crate dep 등록).
Step 3 — kebab-chunk 에 tokenize_korean_morphological() helper + ingest pipeline 통합
Implements: §6.2 (transaction invariant + fallback). Commit 3/10.
Spec sections covered: §6.2 Pre-tokenize 우회 + Invariant + 실패 처리.
Files to modify:
crates/kebab-chunk/src/lib.rs—tokenize_korean_morphological()함수 신규 + chunk builder pipeline 에 호출.- (선택)
crates/kebab-chunk/tests/tokenize_korean.rs— lindera segmentation 의 단위 검증.
Implementation outline:
-
3.1 helper 함수 signature —
kebab-chunk/src/lib.rs에 다음 추가:/// 한국어 chunk text 를 lindera ko-dic 으로 형태소 분해해 공백 join 한 결과를 반환. /// 분석 실패 시 None — 호출자는 NULL fallback 처리. pub fn tokenize_korean_morphological(text: &str) -> Option<String> { use lindera::{ dictionary::{DictionaryConfig, DictionaryKind, load_dictionary_from_config}, mode::Mode, segmenter::Segmenter, tokenizer::Tokenizer, }; let dict_config = DictionaryConfig { kind: Some(DictionaryKind::KoDic), path: None }; let dictionary = load_dictionary_from_config(dict_config).ok()?; let segmenter = Segmenter::new(Mode::Normal, dictionary, None); let tokenizer = Tokenizer::new(segmenter); let tokens = tokenizer.tokenize(text).ok()?; let joined = tokens.iter().map(|t| t.text.as_ref()).collect::<Vec<_>>().join(" "); if joined.trim().is_empty() { None } else { Some(joined) } }실제 API 는 lindera 0.32 의 builder pattern 따라 조정 (crate docs 참조).
OnceCell또는lazy_static으로 Tokenizer 1회만 초기화하는 캐시 패턴 적용 — segmentation cost 의 dictionary load 의존이 hot loop 에 영향 안 가도록. -
3.2 fallback 정책 명시 — spec §6.2 "tokenize_korean_morphological() 실패 처리" 따라 dictionary load fail 또는 token error 시
None반환. 호출 site 에서tracing::warn!(target: "kebab-chunk", "tokenize_korean_morphological fallback to NULL: chunk_id={...}, err={...}");발화. chunk 자체 ingest 는 성공 + chunks_ai trigger 가 ELSE branch (raw text 만 index) 를 탄다. -
3.3 chunk builder pipeline 통합 —
kebab-chunk의 chunk emit / builder loop (구체 함수명은 probe 결과 따라 결정; chunk struct 의textfield 가 채워진 직후 시점) 에서tokenize_korean_morphological(&chunk.text)호출 → 결과를chunk.tokenized_korean_text: Option<String>신규 field 에 저장. chunk struct definition 도 갱신. -
3.4 store INSERT path 갱신 —
crates/kebab-store-sqlite/src/store.rs의 chunks INSERT SQL 에tokenized_korean_textcolumn 추가. signature 갱신: prepared statement 의 placeholder count 가 +1 됨. row binding 도 동일 transaction 안에서 단일 INSERT 로 처리 (spec §6.2 "lindera tokenize → chunks INSERT 는 동일 Rust transaction 내에서" invariant 보장). -
3.5 단위 테스트 —
crates/kebab-chunk/tests/tokenize_korean.rs신규:#[test] fn tokenize_korean_morphological_splits_2char_word() { let out = kebab_chunk::tokenize_korean_morphological("한국 문화는 오래되었다").unwrap(); // 공백으로 join 된 token 시퀀스에 "한국" 이 독립 token 으로 존재해야 함. let tokens: Vec<&str> = out.split_whitespace().collect(); assert!(tokens.contains(&"한국"), "tokens = {:?}", tokens); } #[test] fn tokenize_korean_morphological_empty_returns_none() { assert!(kebab_chunk::tokenize_korean_morphological("").is_none()); assert!(kebab_chunk::tokenize_korean_morphological(" ").is_none()); }한국token 의 hit 가 lindera ko-dic 의 실제 segmentation 동작 의존이므로 (Appendix B 의 prior-knowledge 예측), 실측 실패 시 fixture text 를 spec Appendix B 의 검증 명령 출력으로 교체. -
3.6 Verify.
cargo test -p kebab-chunk --test tokenize_korean -j 4→ exit 0.cargo build --workspace -j 4→ success.
Acceptance criteria:
cargo test -p kebab-chunk --test tokenize_korean tokenize_korean_morphological_splits_2char_word -j 4→ exit 0, fixture"한국 문화는 오래되었다"분해 결과에 token"한국"포함.cargo test -p kebab-chunk --test tokenize_korean tokenize_korean_morphological_empty_returns_none -j 4→ exit 0.cargo build --workspace -j 4 2>&1 | grep -c "warning: unused import.*lindera"→0(lindera dep 가 실제 사용되고 unused import 없음).- chunk struct 의
tokenized_korean_textfield 존재 (grep -n "tokenized_korean_text" crates/kebab-chunk/src/lib.rs→≥ 1). cargo clippy -p kebab-chunk --all-targets -j 4 -- -D warnings→ clean.
Step 4 — App::open_with_config 의 first-boot eager backfill hook
Implements: §8.2 eager backfill. Commit 4/10.
Spec sections covered: §8.1 V007 trigram index 처리, §8.2 자동 eager backfill, §6.2 backfill atomic transaction.
Files to modify:
crates/kebab-store-sqlite/src/store.rs—backfill_tokenized_korean_text(progress_cb)API 신규.crates/kebab-app/src/lib.rs—App::open_with_config에 first-boot hook 추가 + idempotency guard.
Implementation outline:
-
4.1 backfill API signature —
store.rs에 다음 추가:/// 모든 chunks 의 tokenized_korean_text 가 NULL 인 row 를 찾아 /// lindera tokenize → UPDATE. 각 row 의 UPDATE 는 chunks_au trigger 를 fire /// 시켜 chunks_fts 가 재-index 됨. 모든 작업은 동일 transaction 안. /// 결과: 처리된 row 수. pub fn backfill_tokenized_korean_text<F>(&self, progress: F) -> anyhow::Result<u64> where F: Fn(u64, u64);body: (a)
SELECT chunk_id, text FROM chunks WHERE tokenized_korean_text IS NULL로 후보 row 수집. (b) 각 row 에 대해kebab_chunk::tokenize_korean_morphological(text)호출 → 결과를UPDATE chunks SET tokenized_korean_text = ? WHERE chunk_id = ?. (c) BEGIN/COMMIT batch (예: 1000 row 마다 commit) 으로 단일 거대 transaction 회피 + crash recovery 의 partial progress 보장. (d)progress(done, total)콜백 발화. (e) lindera 가 None 반환 시UPDATE ... SET tokenized_korean_text = ''(빈 문자열) 대신 row 를 skip — chunks_au trigger 가 ELSE branch 를 타게 둔다. -
4.2 idempotency 보장 — backfill 은
IS NULL필터로 partial completion 후 재실행 시에도 idempotent. 한 번 채워진 row 는 다시 처리되지 않음. lindera 가 None 을 반환한 row 는 영구히 NULL 로 남으나, dictionary update 후 재실행 시 다시 후보가 되도록 별 marker column 은 추가하지 않음 — spec §8.2 의 "부분 완료 상태 search 동작" 보존. -
4.3 App::open_with_config 의 hook 위치 —
crates/kebab-app/src/lib.rs의App::open_with_config본체에서 migration apply 직후 (즉 store 가 V009 schema 를 보장한 직후) 다음 호출 추가:let backfill_count = app.sqlite .backfill_tokenized_korean_text(|done, total| { if total > 0 && done % 500 == 0 { tracing::info!(target: "kebab-app", "korean tokenizer backfill: {done}/{total}"); } }) .unwrap_or_else(|e| { tracing::warn!(target: "kebab-app", "korean tokenizer backfill failed: {e}"); 0 }); if backfill_count > 0 { tracing::info!(target: "kebab-app", "korean tokenizer backfill complete: {backfill_count} chunks updated"); }backfill 실패 (lindera dict load 등) 는 fatal 아님 — App open 자체는 성공해 사용자가 vector/hybrid mode 로 계속 사용 가능.
-
4.4 startup latency 의 사용자 인지 — KB 가 큰 경우 첫 부팅이 spec §8.2 "약 10,000 chunk 당 ~30-60초" 동안 visible delay. CLI 사용자는
kebab첫 호출이 늦어지는 것을 본다. stderr 로 progress info 발화 (위 callback 의tracing::info!) — 사용자가 hang 으로 오인 안 하도록. -
4.5 backfill 단위 테스트 —
crates/kebab-store-sqlite/tests/fts.rs에 추가:#[test] fn backfill_tokenized_korean_text_populates_nullable_rows() { // TempDir KB 열기, V009 적용된 schema 상태. // chunks 에 ('한국 문화', NULL) row 두 개 INSERT. // backfill_tokenized_korean_text(noop) 호출 → 반환값 == 2. // 두 row 의 tokenized_korean_text IS NOT NULL. // 두번째 호출 → 0 반환 (idempotent). } -
4.6 Verify.
cargo test -p kebab-store-sqlite --test fts backfill_tokenized_korean_text_populates_nullable_rows -j 4→ exit 0.cargo build -p kebab-app -j 4→ success.
Acceptance criteria:
cargo test -p kebab-store-sqlite --test fts backfill_tokenized_korean_text_populates_nullable_rows -j 4→ exit 0, idempotency 확인.App::open_with_config두 번 연속 호출 시 두번째 호출의 backfill_count = 0 (idempotency 의 production 측면).backfill_tokenized_korean_text_populates_nullable_rowstest 의 두 번째backfill_tokenized_korean_text호출 assert (반환값 == 0) 이 이 AC 를 cover 함.cargo test -p kebab-store-sqlite --test fts backfill_tokenized_korean_text_populates_nullable_rows -j 4→ exit 0 으로 검증.cargo clippy -p kebab-app --all-targets -j 4 -- -D warnings→ clean.
Step 5 — short_query_hint() 제거 + lexical.rs build_match_string 정리
Implements: §7.2, §7.3. Commit 5/10.
Spec sections covered: §7.2 lexical.rs 조정, §7.3 CLI hint 제거.
Files to modify:
crates/kebab-app/src/app.rs—short_query_hint()함수 정의 + 2 호출 site (line 532, 616) 제거.crates/kebab-app/src/lib.rs— re-exportpub use app::{..., short_query_hint};의short_query_hint제거.crates/kebab-tui/src/app.rs(161, 188),crates/kebab-tui/src/search.rs(443, 619, 703, 711),crates/kebab-tui/src/run.rs(394) —short_query_hintfield + 호출 cascade 제거.crates/kebab-cli/tests/wire_search_response.rs:215—search_plain_emits_short_query_hint_to_stderrtest 삭제.crates/kebab-search/src/lexical.rs::build_match_string()— trigram-specific 분기 단순화 검토.
Implementation outline:
- 5.1 short_query_hint 정의 + 호출 제거 —
crates/kebab-app/src/app.rs:98의pub fn short_query_hint(query_text: &str, hits_empty: bool) -> Option<String>함수 본체 삭제. line 532, 616 의 두 호출 site (search response 의 hint 채우는 부분) 도 함께 제거 — 해당 SearchResponse field 가 항상 None 으로 셋팅되거나, SearchResponse 의 hint field 자체를 제거 (wire schema 영향 검토 필요). - 5.2 wire schema 영향 확인 —
SearchResponsestruct 의short_query_hint또는hintfield 가search_hit.v1/search_response.v1JSON Schema 에 포함되어 있는지docs/wire-schema/v1/*.schema.jsongrep. (a) 포함되어 있다면 wire 의 additive minor 가 아닌 removal 이므로 별도 결정 — spec §11.3 의 "wire schema shape 변경 없음" 과 모순. → 그 경우 field 는 struct 에 남기고 항상 None / 생략 (Option/Default) 으로 유지. (b) 포함되어 있지 않으면 struct field 도 함께 제거. - 5.3 lib.rs re-export 제거 —
crates/kebab-app/src/lib.rs:75의pub use app::{App, SearchResponse, short_query_hint};에서short_query_hint부분 제거. - 5.4 TUI cascade 제거 —
crates/kebab-tui/src/app.rs:161,188의pub short_query_hint: Option<String>field + default init 삭제.crates/kebab-tui/src/search.rs:443,619,703,711의 4 곳에서s.short_query_hint = None/s.short_query_hint = kebab_app::short_query_hint(...)라인 모두 삭제.crates/kebab-tui/src/run.rs:394의.and_then(|s| s.short_query_hint.as_deref())도 제거 — 그 자리의 UI 표현은 빈 상태 또는 다른 hint 로 대체. - 5.5 CLI test 정리 —
crates/kebab-cli/tests/wire_search_response.rs:215의search_plain_emits_short_query_hint_to_stderrtest 전체를 삭제. 또는 test name 을search_plain_does_not_emit_short_query_hint_to_stderr로 rename 후 assertion 을 negative 로 invert. - 5.6 lexical.rs::build_match_string 검토 —
crates/kebab-search/src/lexical.rs:205의 함수 본체를 읽어 trigram-specific 처리 (예: 2-char Korean token 의 OR-combine, character-class 분해) 존재 여부 확인. spec §7.2 권장: "backward-compat 차원에서 기존 로직 보존" → 본 step 에서는 변경 없음 (보존). 단 함수 doc-comment 에 "V009 unicode61 + 형태소 tokenizer 환경에서는 multi-token Korean query 의 OR-combine 분기는 redundant 하나 보존" 한 줄 추가. - 5.7 Verify.
cargo test --workspace -j 1 -- --skip wire_search_response::search_plain_emits 2>&1 | tail -5으로 cascade 통과 확인.cargo build --workspace -j 4success.
Acceptance criteria:
grep -rn "short_query_hint" crates/ tests/ 2>/dev/null | wc -l→0(또는 doc-comment 의 1 줄만 남음).cargo build --workspace -j 4 2>&1 | grep -E "^error" | wc -l→0(cascade 누락으로 인한 컴파일 에러 없음).cargo test -p kebab-cli --test wire_search_response -j 4→ exit 0,search_plain_emits_short_query_hint_to_stderr가 test list 에서 사라짐.cargo clippy --workspace --all-targets -j 4 -- -D warnings→ clean.
Step 6 — lexical_index_version() 의 V009 bump
Implements: §11.1. Commit 6/10.
Spec sections covered: §11.1 index_version bump, §11.3 wire content 변화 명시.
Files to modify:
crates/kebab-app/src/app.rs:991-993—lexical_index_version()함수의 반환 문자열 갱신.
Implementation outline:
-
6.1 현재 source-of-truth 확인 — line 991-993 의
fn lexical_index_version는IndexVersion(format!("lex:{}", config.chunking.chunker_version))반환. fts version 정보가 없음. spec §11.1 의 권장 문자열"fts5-v009-korean-morphological"또는"v009-morpho"를 채택하기 위해 format 변경 필요. -
6.2 신규 format — 변경:
fn lexical_index_version(config: &kebab_config::Config) -> IndexVersion { IndexVersion(format!( "lex:{}:fts5-v009-korean-morphological", config.chunking.chunker_version )) }기존 5 호출 site (app.rs line 367, 389, 479, 661, 680) 는 함수 호출만 하므로 자동 cascade. test
lexical_index_version_is_returned_unchanged(crates/kebab-search/tests/lexical.rs:650) 의 assertion 갱신 필요. -
6.3 lexical test 갱신 —
crates/kebab-search/tests/lexical.rs:650의lexical_index_version_is_returned_unchangedtest 가 hard-coded 한 expected 값을 V009 신규 format 으로 갱신. 또는 test 의 의도가 "format 자체의 invariant 보장" 이라면 substring match (actual.0.contains("fts5-v009")) 로 변경. -
6.4 Verify.
cargo test -p kebab-search --test lexical lexical_index_version_is_returned_unchanged -j 4→ exit 0.cargo test -p kebab-app -j 4→ 회귀 0.
Acceptance criteria:
cargo test -p kebab-search --test lexical lexical_index_version_is_returned_unchanged -j 4→ exit 0.cargo build -p kebab-cli -j 4 && ./target/debug/kebab schema --json | jq -r '.index_versions.lexical // empty' 2>/dev/null | grep -c "fts5-v009"→≥ 1.cargo clippy -p kebab-app --all-targets -j 4 -- -D warnings→ clean.
Step 7 — 신규 unit/integration test (Korean morphological FTS scenarios)
Implements: §9.1 AC, §9.2 test coverage. Commit 7/10.
Spec sections covered: §9.1 lexical-mode scenarios, §9.2 test coverage.
Files to modify:
crates/kebab-store-sqlite/tests/fts.rs—fts_v009_korean_morphological_2char_query_hits+fts_v009_english_whole_token_only추가.- Create
crates/kebab-app/tests/search_korean.rs—korean_morphological_2char_query_lexical_mode+korean_morphological_mixed_english_korean_query.
Implementation outline:
-
7.1 fts.rs 의 신규 단위 test — spec §9.2 의 verbatim 시그니처 따라:
#[test] fn fts_v009_korean_morphological_2char_query_hits() { // 1. TempDir KB 열기 + V009 schema 보장. // 2. chunks 에 ('한국 문화는 오래되었다', tokenize_korean_morphological(...)) row INSERT. // triggers chunks_ai 가 chunks_fts 에 자동 index. // 3. SELECT chunk_id FROM chunks_fts WHERE chunks_fts MATCH '한국' // → 결과 row count >= 1. // 4. 동일 방식으로 '문화' query → row count >= 1. } #[test] fn fts_v009_english_whole_token_only() { // Path A 회귀 확인: V007 trigram substring 매칭 사라짐. // 1. ('the tokenizer normalizes whitespace', None) row INSERT. // 2. MATCH 'token' → 0 row (substring of 'tokenizer' is NOT matched by unicode61). // 3. MATCH 'tokenizer' → >= 1 row. } -
7.2 search_korean.rs end-to-end test —
crates/kebab-app/tests/search_korean.rs신규:#[test] fn korean_morphological_2char_query_lexical_mode() { // 1. TempDir config, App::open_with_config. // 2. ingest_with_config_opts 로 fixture 한국어 markdown 파일 ingest. // (fixture = "한국어를 공부합니다.\n서울은 한국의 수도입니다.") // 3. App::search 의 SearchQuery { text: "한국", mode: Lexical, ... }. // → hits.len() >= 1. // 4. '서울' query → hits.len() >= 1. } #[test] fn korean_morphological_mixed_english_korean_query() { // 1. fixture = "Rust 최적화는 zero-cost abstraction 을 강조한다." // 2. 'Rust' query → hit, '최적화' query → hit. // 3. 'Rust 최적화' multi-token query → hit (build_match_string 의 OR-combine). }fixture 가 lindera ko-dic 의 실제 segmentation 동작 의존이므로 spec Appendix B 의 prior-knowledge 예측이 실패할 경우 fixture text 와 query 를 조정 (한국어 corpus 의 representative case).
-
7.3 spec §9.2
fts_v009_matches_design_section_5_5_verbatim와의 관계 — 이 test 는 이미 S1 의 rename 으로 생성됨. 본 step 에서 추가하지 않음. -
7.4 Verify.
cargo test -p kebab-store-sqlite --test fts fts_v009_korean -j 4→ 2 test exit 0.cargo test -p kebab-app --test search_korean -j 4→ 2 test exit 0.
Acceptance criteria:
cargo test -p kebab-store-sqlite --test fts fts_v009_korean_morphological_2char_query_hits -j 4→ exit 0, fixture chunk"한국 문화는 오래되었다"가 query"한국"의 hit list 에 존재.cargo test -p kebab-store-sqlite --test fts fts_v009_english_whole_token_only -j 4→ exit 0, fixture"the tokenizer..."가 query"token"에서 0-hit + query"tokenizer"에서 hit.cargo test -p kebab-app --test search_korean korean_morphological_2char_query_lexical_mode -j 4→ exit 0.cargo test -p kebab-app --test search_korean korean_morphological_mixed_english_korean_query -j 4→ exit 0.- 신규 test binary 2개 (
fts확장 +search_korean신규) 의 추가로 workspace test count 가 baseline +4 이상. 검증 명령:cargo test -p kebab-store-sqlite --test fts -- --list 2>&1 | grep "fts_v009_korean" | wc -l→≥ 1cargo test -p kebab-app --test search_korean -- --list 2>&1 | wc -l→≥ 2
Step 8 — eval golden baseline regenerate
Implements: §11.3 wire content 변화 (eval golden 재생성 책임). Commit 8/10.
Spec sections covered: §11.3 eval golden baseline regenerate.
Files to modify:
crates/kebab-eval/goldens/*.csv(또는 동급 fixture) — V007 기준 expected rank/hit_id 시퀀스를 V009 기준 으로 재생성.crates/kebab-eval/tests/*— 회귀 0.
Implementation outline:
- 8.1 baseline regenerate 절차 확인 —
crates/kebab-eval/의 README 또는 doc-comment 에 명시된 baseline regenerate 명령 (e.g.cargo run -p kebab-eval -- regenerate-goldens --kb /tmp/eval-kb) 또는 동급 운영 절차 실행. 정확한 명령은 crate 의 main.rs / lib.rs 의 subcommand 정의 확인 후 결정. - 8.2 V009 적용된 KB 로 baseline 재계산 — spec §12.2 dogfood verification 의 fresh KB 사용 가능 (또는
tasks/HOTFIXES.md의 dogfood corpus 경로 재사용). 재생성된 CSV 의 hit_id / rank 시퀀스가 V007 baseline 과 다름은 의도된 변화 — git diff 가 보여주는 변경량 검토 후 commit. - 8.3 commit 시점 분리 — baseline regenerate 는 별 commit (S8) 이며, executor / dogfood 단계에서 의미 있는 deviation 발견 시 다시 regenerate 가능. PR scope 내부 commit.
- 8.4 Verify.
cargo test -p kebab-eval --workspace -j 4 2>&1 | grep -E "^test result" | head -3→ 모두0 failed.
Acceptance criteria:
cargo test -p kebab-eval -j 4 2>&1 | grep "test result.*failed" | grep -v "0 failed" | wc -l→0.git diff --stat crates/kebab-eval/goldens/→ 변경 라인 수 > 0 (baseline 이 실제로 갱신됨).cargo clippy -p kebab-eval --all-targets -j 4 -- -D warnings→ clean.
Note: spec §11.3 + plan brief 의 "spec §11 의 결정 따라 본 plan scope 에 포함 또는 별 follow-up" 중 본 plan 은 scope 포함 선택 — 단 deviation (regenerate 실패, baseline 차이 너무 큼) 시 별 P5 follow-up 으로 후퇴 가능. 후퇴 시 본 S8 은 verify-only 로 단순화하고 tasks/HOTFIXES.md 에 한 줄 deviation 기록.
Step 9 — docs sync (README + HANDOFF + ARCHITECTURE + SKILL + HOTFIXES)
Implements: §7.4 surface cascade. Commit 9/10.
Spec sections covered: §7.4 README/SKILL/HANDOFF/ARCHITECTURE 갱신.
Files to modify:
README.md— 명령 tablekebab search행 갱신 + Configuration section 보조 정보.integrations/claude-code/kebab/SKILL.md— V007 3-char hint 제거 + 2-char 한국어 query 지원 표현 추가 + 영어 substring 회귀 (optional advanced note).HANDOFF.md— v0.20.1 patch release 의 G section 또는 round-up entry 에 본 변경 한 줄 추가.docs/ARCHITECTURE.md— crate dependency graph 의 lindera 추가, FTS tokenizer 섹션 V007 trigram → V009 unicode61 + 형태소 분해.tasks/HOTFIXES.md— V009 cascade 의 dated entry + cargo deny 도입 deferral note.
Implementation outline:
-
9.1 README.md 변경 — CLAUDE.md "Docs split" rule 따라 narrow scope. (a)
kebab search명령 row 의 description 에 "한국어 2자 query 지원 (예: '한국', '서울')" 추가. (b) Configuration section 의 변경은 없음 (config 노브 없음, S6.3 Option A). (c) Mermaid logical-architecture diagram 의 변경 없음 (FTS5 는 내부 store 측 detail). -
9.2 SKILL.md 변경 —
integrations/claude-code/kebab/SKILL.md의 description / 사용법 섹션에서 "한국어 query 는 3자 이상 권장" 류 표현 검색 + 제거. 신규: "한국어 2자 단어 검색 지원 (예: '한국', '서울')". 영어 substring 매칭 회귀는 일반 사용자에게는 노이즈 → optional advanced note 로 짧게 (1줄) 만 추가. 또는 release notes 만 다루고 SKILL.md 는 user-facing happy path 로 한정 (권장). -
9.3 HANDOFF.md 변경 — v0.20.0 sub-item 1 머지 후 priorities section 의 C 항목 status flip ('지연' / '미해결' → '완료'). v0.20.1 patch release section 의 release notes scope 에 본 변경 추가. 머지 후 발견된 버그 / 결정 summary 행 한 줄 추가 ("Bug #8 한국어 2자 query → v0.20.1 의 V009 morphological tokenizer 로 해소").
-
9.4 docs/ARCHITECTURE.md 변경 — (a) crate dependency graph 에
kebab-chunk → lindera, lindera-dict-ko-dicedge 추가. (b) FTS tokenizer 섹션이 있다면 V007 trigram → V009 unicode61 + 형태소 분해로 갱신 + locked-in 결정 table 의 row 갱신. (c) directory tree 변경 없음 (신규 crate 없음). -
9.5 tasks/HOTFIXES.md entry — 2026-05-28 dated entry 추가:
2026-05-28 — Bug #8 한국어 2자 query 해소 (V009 morphological tokenizer)
- Discovered: 도그푸딩 round 3/4 (2026-05-28). '한국' / '서울' 0-hit 반복.
- Symptom: V007 trigram tokenizer 의 ≥3-char minimum 한계.
- Root cause: trigram 의 bucket 미존재.
- Fix: V009 migration + lindera ko-dic + tokenized_korean_text column + first-boot eager backfill. branch
feat/korean-morphological-tokenizer. - Amends: design §5.5 (unicode61 + CASE expression triggers 로 갱신), §9 (index_version cascade), tasks/HOTFIXES.md 2026-05-22 trigram entry (한국어 2자 query 미해결 footnote 해소).
- Deferred:
cargo-deny정식 도입 (workspace 의 deny.toml) 은 별 P9 follow-up 으로 분리. 현 PR 은cargo tree의 SPDX 수동 검증 + lindera/ko-dic license 의 fail-fast 정책 적용.
-
9.6 Verify. 4 docs file 의 변경 라인 합 > 30 (충분한 cascade). 한 줄짜리 cosmetic 만 들어간 케이스 회피.
Acceptance criteria:
git diff --stat README.md HANDOFF.md docs/ARCHITECTURE.md integrations/claude-code/kebab/SKILL.md tasks/HOTFIXES.md→ 5 file 모두 변경 (각 file 의 변경 라인 ≥ 1).grep -c "한국어 2자" README.md→≥ 1.grep -c "V009" tasks/HOTFIXES.md→≥ 1.grep -c "lindera" docs/ARCHITECTURE.md→≥ 1.
Step 10 — version bump (Cargo.toml workspace version)
Implements: §12.1 v0.20.1 patch release. Commit 10/10.
Spec sections covered: §12.1 release strategy.
Files to modify:
Cargo.toml(workspace) —[workspace.package] version = "0.20.0"→"0.20.1".Cargo.lock— 자동 갱신.
Implementation outline:
- 10.1 version bump — CLAUDE.md
Release / binary version bumprule 따라 본 변경은 "user 가 새 바이너리로 도그푸딩 또는 실사용을 할 필요" + "frozen design contract 변경 (design §5.5 갱신)" 두 트리거 모두 해당 → bump 필수.Cargo.tomlworkspaceversion을"0.20.0"→"0.20.1". - 10.2 Cargo.lock 갱신 —
cargo update --workspace --offline 2>&1 | tail -3또는 단순cargo build --workspace -j 4가 Cargo.lock 의 version 부분 자동 갱신. - 10.3 commit 직후 tag 절차 (별 task) — bump commit 자체는 본 step 의 commit, 이후
gitea-release v0.20.1명령은 별 task 로 분리 (executor 의 sequential N step 후 user 가 직접 cut). spec §12.1 의 release notes 본문 (한국어 2자 query 지원, FTS5 tokenizer 변경 회귀, 자동 backfill, ingest 성능 감소) 4 단락을 release notes draft 로 미리 작성하여docs/release-notes/v0.20.1-draft.md에 보관 (선택, plan 의 brief 의 verifier checklist). - 10.4 Verify.
grep -c "^version = \"0.20.1\"" Cargo.toml→1.cargo build --workspace -j 4→ success + binary--version출력이0.20.1.
Acceptance criteria:
grep "^version" Cargo.toml | head -1→version = "0.20.1".cargo build --release -p kebab-cli -j 4 && ./target/release/kebab --version 2>&1→kebab 0.20.1(또는 동급 출력).cargo build --workspace -j 4 2>&1 | tail -3→ success.
Step 11 — final sanity (no commit)
Implements: AC §9.3 verifier checklist + plan brief §7. No commit.
Spec sections covered: §9.3 verifier checklist, §12.2 dogfood verification.
Implementation outline:
-
11.1 Workspace test.
cargo test --workspace --no-fail-fast -j 1 > /tmp/wstest.out 2>&1→ 모두 pass (baseline 1370+ → 신규 +4 이상). Expected:0 failed. -
11.2 Clippy.
cargo clippy --workspace --all-targets -j 4 -- -D warnings→ 0 warning. -
11.3 cargo fmt.
cargo fmt --all --check→ exit 0 (또는 fmt 적용). -
11.4 Schema 무결성.
./target/release/kebab schema --json | jq -e '.wire.schemas | length' 2>&1→ 기존과 동일 (wire schema 추가 0). -
11.5 Dogfood smoke. TempDir KB 의 fresh KB 시나리오:
rm -rf /tmp/kebab-smoke-v009 ./target/release/kebab --config /tmp/kebab-smoke-v009/config.toml ingest <한국어 fixture> ./target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search '한국' --json | jq '.hits | length' # → ≥ 1 ./target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search '서울' --json | jq '.hits | length' # → ≥ 1 ./target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search '지하철' --json | jq '.hits | length' # → ≥ 1 -
11.6 기존 KB eager backfill 시나리오. 별도 디렉토리에 V007 (pre-V009) 시점 SQLite snapshot 보존 사용 가능 시:
cp -r /tmp/kebab-v007-snapshot /tmp/kebab-v007-test ./target/release/kebab --config /tmp/kebab-v007-test/config.toml schema --json 2>&1 | grep -i "backfill" # eager backfill info log 가 stderr 에 발화 확인. ./target/release/kebab --config /tmp/kebab-v007-test/config.toml search '한국' --json | jq '.hits | length' # backfill 완료 후 → ≥ 1V007 snapshot 부재 시 본 verifier 는 best-effort (manual). dogfood corpus 의 재구성 비용을 고려해 spec §12.2 의 권장 절차 따른다.
-
11.7 git status 확인.
git status --short→ 모든 의도된 변경이 stage 됨 + untracked file 없음 (또는 의도된 untracked 만 — 예: release-notes draft).
Acceptance criteria (final checklist):
kebab search '한국'(fresh V009 KB) → hit ≥ 1.- V009 migration apply 후 기존 V007 KB → eager backfill 자동 시작 → 완료 후
kebab search '한국'→ hit ≥ 1. cargo test --workspace --no-fail-fast -j 1→ 모두 pass (baseline +4 이상).cargo clippy --workspace --all-targets -j 4 -- -D warnings→ clean.cargo fmt --all --check→ exit 0.- (deferred)
cargo deny check→ S2.5 에서 P9 follow-up 으로 분리. 본 PR 의 license 검증은cargo tree의 SPDX 수동 비교로 대체. - README.md / SKILL.md / HANDOFF.md / docs/ARCHITECTURE.md / tasks/HOTFIXES.md 모두 갱신됨.
- release notes draft (
docs/release-notes/v0.20.1-draft.md) 작성, 4 단락 본문 (2자 query 지원 / tokenizer 변경 + 영어 회귀 / 자동 backfill / ingest 성능).
3. Dependencies + sequencing
각 step 의 의존성:
| Step | Title | Blocked by | Blocks | 병렬 가능? |
|---|---|---|---|---|
| S1 | V009 migration + design §5.5 + CI rename | (none) | S3, S4, S6, S7 | (entry) |
| S2 | lindera dep | (none) | S3 | (entry, S1 과 병렬) |
| S3 | tokenize_korean_morphological + ingest 통합 | S1, S2 | S4, S5, S7 | — |
| S4 | first-boot eager backfill | S1, S3 | S7, S11 | S5/S6 와 병렬 가능 |
| S5 | short_query_hint 제거 + lexical.rs 정리 | (none, S3 면 깔끔) | S7 | S4/S6 와 병렬 가능 |
| S6 | lexical_index_version bump | S1 | S7 | S4/S5 와 병렬 가능 |
| S7 | 신규 test (FTS + search_korean) | S3, S4, S6 | S8, S11 | — |
| S8 | eval golden regenerate | S7 + dogfood KB | S11 | — |
| S9 | docs sync | S1~S8 (의미적) | S11 | — |
| S10 | version bump | S9 | S11 | — |
| S11 | final sanity | S1~S10 | (exit) | — |
Parallel dispatch 가능 그룹:
- Group 1 (entry, parallel): S1 + S2. 두 step 은 독립. dispatcher 가 sonnet 두 instance 로 병렬 실행 가능.
- Group 2 (S3 후속, parallel): S4 + S5 + S6. S3 가 끝난 직후 3 instance 동시 실행. S4 (backfill API + hook) + S5 (hint 제거 + lexical.rs) + S6 (index_version bump) 가 file overlap 0 (서로 다른 module). 주의: S5 와 S6 은 모두
crates/kebab-app/src/app.rs를 수정함. 병렬 에이전트 실행 시 편집 행 범위를 사전 분리할 것 (S5: line 98, 532, 616; S6: line 991-993). - Group 3 (sequential): S7 → S8. test 가 먼저 들어가야 eval baseline 갱신 후 회귀 0 검증 가능.
- Group 4 (sequential): S9 → S10. docs sync 후 version bump.
총 시퀀스 (worst-case sequential, sub-agent 1 instance): S1 → S2 → S3 → S4 → S5 → S6 → S7 → S8 → S9 → S10 → S11. 10 commit + 1 verify.
총 시퀀스 (parallel, sub-agent 3 instance): {S1, S2} 병렬 → S3 → {S4, S5, S6} 병렬 → S7 → S8 → S9 → S10 → S11. wall-clock 단축 약 30%.
4. AC verifier 의 actionability
본 plan 의 모든 AC 는 mechanical 검증 가능하도록 작성됨. spec §9.3 의 verifier checklist 도 모두 actionable 한 명령 또는 grep 단위:
| AC type | Verifier 명령 형식 |
|---|---|
| Schema diff-check | cargo test ...verbatim -j 4 → exit 0 |
| Hit/miss assertion | cargo test ...hits -j 4 → exit 0 + grep on output |
| Surface 갱신 cascade | grep -c "<phrase>" <file> → ≥ 1 |
| Test count growth | cargo test ... 2>&1 | grep "test result" | awk |
| Binary version | ./target/release/kebab --version |
| dogfood smoke | kebab search '한국' --json | jq '.hits | length' → ≥ 1 |
각 step 의 AC 는 별 reviewer subagent (sonnet) 가 mechanical 통과 여부를 판단 가능. 해석 여지 없는 fact statement.
5. Risk + open questions
S1 (V009 migration)
- Risk: design §5.5 갱신과 V009 SQL block 의 whitespace 미세 차이로 verbatim test fail.
- Mitigation: S1.3 의 갱신은 V009 SQL 의 (c) 블록을 design §5.5 에 그대로 paste. fts.rs:407 의
normalize_ws함수가 모든 whitespace 차이 흡수.
S2 (lindera dep)
- Risk:
lindera-dict-ko-dic의 dict 다운로드가 build-time 에 실패 (network egress 차단 환경). 또는 정확한 feature flag name 이 spec 의 가정과 다름. - Mitigation: S2.3 의 cargo build 출력 정독 후 dict embed feature name 확정. 실패 시
build.rsproactive download 또는 vendored crate 로 후퇴 (별 follow-up). - Risk: lindera version pin 의 transitive dep 가 기존 workspace 와 충돌 (예:
unicode-normalization,serdemajor version 의 lock 갈등). - Mitigation:
cargo update후cargo tree --duplicates로 회귀 확인.
S3 (tokenize_korean_morphological)
- Risk: lindera ko-dic 의
'한국어'segmentation 이 단일 token (['한국어']) 으로 나오면'한국'query 0-hit (spec critic R1 finding #3). 본 plan 의 S3.5 testtokenize_korean_morphological_splits_2char_word이 fixture"한국 문화는 오래되었다"에서 hit 한다는 prior-knowledge 가정. - Mitigation: test 실패 시 (a) fixture 를 ko-dic 의 실제 분해 결과에 일치하는 corpus 로 교체 (예:
'한국 문화는 오래되었다'가 명시적으로 공백 포함하므로 두 token['한국', '문화는']보장). (b) AC §9.1 의 hit 보장 범위를 spec Appendix B 의 "고유명사 미등록 또는 형태소 경계 일치 시" 로 명시적 좁힘. (c) sub-morpheme 추가 분해 (n-gram supplement) 는 별 follow-up.
S4 (first-boot eager backfill)
- Risk: 거대 KB (예: 100,000 chunk) 의 첫 부팅이 ~5-10 분 hang. 사용자가 hang 으로 오인 + Ctrl-C → partial backfill state.
- Mitigation: S4.3 의
tracing::info!progress log 가 stderr 에 발화. partial 은 idempotent (re-run 시 이어서 처리). 거대 KB 의 사용자 경험은 별 P5 follow-up (background job + streaming feedback) 으로 미룬다. - Risk:
chunks테이블에 row 가 매우 많을 때 단일 transaction commit 이 long-lock → 다른 reader 가 block. - Mitigation: S4.1 body 의 "1000 row 마다 commit" batch 패턴. SQLite WAL mode 와 호환.
S5 (short_query_hint 제거)
- Risk: wire schema 의
SearchResponse가short_query_hintfield 를 포함 → removal 이 wire breaking change. - Mitigation: S5.2 의 wire schema grep 으로 사전 확인. 포함 시 struct field 유지 (항상 None) + wire 의 backward-compat 보존.
S6 (lexical_index_version)
- Risk:
crates/kebab-search/tests/lexical.rs:650외에도 hard-coded expected 가 다른 test 에 존재. - Mitigation:
grep -rn "lex:" crates/ tests/ --include="*.rs"로 사전 확인 + cascade.
S7 (신규 test)
- Risk: end-to-end
search_koreantest 의 fixture markdown 파일이 chunker_version 의존성으로 chunk boundary 가 의도와 다르게 끊겨 한국어 token 이 cross-chunk 분리 → query 의 hit 가 fixture 의 의도와 다름. - Mitigation: fixture text 의 short paragraph 로 단일 chunk 생성 + chunker_version 의 invariant 확인.
S8 (eval golden regenerate)
- Risk: baseline diff 가 거대 (모든 row 의 rank 가 shift) → review 시 spec drift 의심 + revert 압박.
- Mitigation: S8.3 의 commit 분리 + spec §11.3 의 의도된 변화 cross-link. revert 결정은 user 의 별 dogfood 후. 본 PR scope 에서 fail-fast 가능 — 그 경우 별 P5 follow-up 으로 분리.
S9 (docs sync)
- Risk: docs/ARCHITECTURE.md 의 crate dependency graph 가 stale 한 형태 (rebuild 필요).
- Mitigation: ARCHITECTURE.md 의 graph 가 Mermaid 형식이라 단순 edge add. cosmetic.
S10 (version bump)
- Risk: bump 만 한 commit 이라 spec drift 의심 + 후속 task 의 release notes 미완성.
- Mitigation: S10.3 의 release notes draft 작성 (
docs/release-notes/v0.20.1-draft.md). bump commit 본문에 "spec §12.1 release notes draft 포함" 명시.
S11 (final sanity)
- Risk: dogfood smoke 의 fixture 한국어 corpus 부재 → 11.5 verification 의 hit 검증 stub 형식.
- Mitigation: spec §12.2 + handoff §4.2 의 dogfood corpus 경로 (
/build/cache/tmp/v0.20-r5-dogfood/) 재구성 후 사용. 부재 시 manual best-effort.
Open questions (executor 에게 위임)
- lindera version pin —
0.32또는0.34(cargo search 시점 latest stable) 중 선택. major breaking change 부재 가정. lindera-dict-ko-dicfeature flag name —embedded-dict,ko-dic,compress중 정확한 이름은 crate metadata 확인 후 결정.SearchResponse의short_query_hintfield 의 wire schema 포함 여부 — S5.2 에서 grep 확인 후 결정.lexical.rs::build_match_string()의 trigram-specific 분기 보존 여부 — 본 plan 은 보존 선택 (S5.6). executor 가 코드 가독성 측면에서 단순화 판단 시 별 commit 으로 분리.- eval golden regenerate 의 baseline diff size — S8 의 sanity bound (예: 변경 라인 < 50% of total) 미정. executor 의 dogfood 후 판단.
6. Cost optimization (model routing)
각 step 의 implementer + reviewer 모델 routing 제안 (memory feedback_teammate_model_routing.md 따라):
| Step | Title | Implementer | Reviewer | Rationale |
|---|---|---|---|---|
| S1 | V009 migration + design §5.5 + CI rename | opus | sonnet | spec frozen contract (§5.5 verbatim) 의 spirit 보존 필요. trigger CASE expression 의 design 측 갱신은 한 글자 오차로 CI fail. |
| S2 | lindera dep | sonnet | sonnet | dependency 추가만 — mechanical. |
| S3 | tokenize_korean_morphological + ingest 통합 | opus | sonnet | lindera API 의 정확한 호출 + ingest pipeline 의 transaction invariant + chunk struct cascade. multi-crate 동시 변경. |
| S4 | first-boot eager backfill | sonnet | sonnet | API design 은 spec 에 명시. batch commit 패턴은 표준. |
| S5 | short_query_hint 제거 + lexical.rs 정리 | sonnet | sonnet | 다중 file cascade 이나 mechanical search + delete. |
| S6 | lexical_index_version bump | sonnet | sonnet | 단일 함수 + test fixture 갱신. |
| S7 | 신규 test | sonnet | sonnet | spec §9.2 의 verbatim 시그니처 따라. fixture text 의 ko-dic 동작 의존이라 dogfood 단계에서 추가 조정 가능. |
| S8 | eval golden regenerate | sonnet | sonnet | baseline regenerate 명령 실행 + commit. |
| S9 | docs sync | sonnet | sonnet | mechanical text edit. README narrow scope. |
| S10 | version bump | sonnet | sonnet | 한 줄 변경. |
| S11 | final sanity | sonnet | sonnet | 검증 명령 실행만. |
| PR-level final review | (전체 diff) | — | opus | merge 직전 cross-file 일관성 + spec 회귀 미검출 risk. |
- opus 권장 step: S1, S3. spec frozen contract + multi-crate 동시 변경.
- sonnet 충분 step: S2, S4~S11.
- PR-level final review: opus 로 별 reviewer subagent 호출.
7. Verifier checklist (final)
본 plan 의 모든 step 완료 시 final verifier 가 통과시킬 checklist (spec §9.3 의 확장):
kebab search '한국'(fresh V009 KB) → hit ≥ 1.kebab search '서울'(fresh V009 KB) → hit ≥ 1.kebab search '지하철'(fresh V009 KB) → hit ≥ 1.- V009 migration apply 후 기존 V007 KB → eager backfill 자동 시작 (stderr info log 발화) → 완료 후
kebab search '한국'→ hit ≥ 1. cargo test --workspace --no-fail-fast -j 1→ 모두 pass (baseline +4 이상).cargo clippy --workspace --all-targets -j 4 -- -D warnings→ clean.cargo fmt --all --check→ exit 0.- (deferred to P9)
cargo deny check— 본 PR 은cargo treeSPDX 수동 검증으로 대체. P9 follow-up 으로 별 issue. grep -c "fts5-v009" crates/kebab-app/src/app.rs→≥ 1(lexical_index_version V009 bump).grep -c "tokenized_korean_text" migrations/V009__fts_korean_morphological.sql→≥ 1.grep -rn "short_query_hint" crates/ tests/→ 0 production reference (test 또는 doc-comment 1 줄 허용).- README.md / SKILL.md / HANDOFF.md / docs/ARCHITECTURE.md / tasks/HOTFIXES.md 모두 갱신됨 (S9 cascade 5 file).
Cargo.tomlworkspaceversion = "0.20.1".- release notes draft (
docs/release-notes/v0.20.1-draft.md) 작성, 4 단락 본문. cargo build --release -p kebab-cli -j 4 && ./target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search 'token' --json | jq '.hits | length'→0(영어 substring 매칭 회귀 확인, V009 는 whole-token only)../target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search 'tokenizer' --json | jq '.hits | length'→≥ 1(whole-token 매칭 정상)../target/release/kebab --config /tmp/kebab-smoke-v009/config.toml search '한국' --mode hybrid --json | jq '.hits | length'→≥ 1(hybrid mode 회귀 확인 — mode flag 실제 이름은--mode hybrid또는--hybrid등 확인).
8. Constraints
- No branch change. 10 commit 모두
feat/korean-morphological-tokenizerbranch. - Spec frozen. ACCEPT spec 의 본문 X edit. deviation →
tasks/HOTFIXES.md. - Wire schema shape 불변. spec §11.3 따라 hit ordering / snippet content 만 변화. 신규 wire schema 또는 field add 없음.
- Regression budget 0. baseline workspace test pass 수 (≥ 1370) 유지 + 신규 +4 test 추가.
- Worker protocol — subagent skip. executor 는 nested worker spawn 없이 sequential N step.
- Length budget. 500-800 line plan (본 file ≈ 740 line).
- Build path.
export CARGO_TARGET_DIR=/build/out/cargo-target/target;-j 4default,-j 1for workspace sanity. - Commit cadence. 10 commit (S1~S10). S11 verify-only.
- Doc sync. S9 의 5 file cascade 명시. README narrow scope 보존.
- Release trigger. CLAUDE.md
Release / binary version bumprule 의 두 트리거 모두 hit (도그푸딩 필요 + design §5.5 변경). v0.20.1 patch release 별 task. - cargo-deny deferred. Appendix D 의
cargo deny check절차는 본 PR scope 외 (P9 follow-up). license 검증은cargo treeSPDX 수동 + lindera/ko-dic 의 fail-fast 정책으로 대체.
Changelog
- 2026-05-28 closure-r1-mp: plan closure verifier (sonnet) ACCEPT verdict + 9 micro-patches 적용 (MP-1: feature flag, MP-2~MP-7: AC actionability, MP-8: parallel safety, MP-9: hybrid/english regression checks).
- 2026-05-28 post-implementation: 11 step + 10 follow-up commit 모두 머지 — total 17 implementation commit + 4 docs polish + 1 spec/plan archive = 22 commit on
feat/korean-morphological-tokenizer. S3 spec compliance reviewer 가 2 blocker (get_chunk read path + 9 AST chunker cascade) 발견 → fix commit. S7 implementer 가MIT_QUERY_CHARS3→2 cascade 필수 발견 (정당한 scope expansion). S11 sanity 단계에서 V007 trigram-specific test 3 + corpus_revision test baseline + chunk snapshot 10 의 의도된 회귀 update. PR-level final review (opus) 의 4 minor docs finding (README english regression, HOTFIXES paths, hint schema, SKILL.md) 정정. - 2026-05-28 dogfood evidence: reference fixture (korea-overview.md + korea-compound.md, DOGFOOD.md §2.1bis) 의 14 scenario verify pass — '한국' 4 hit, '서울' 2 hit, '지하철' 2 hit, '서울특별시' 1 hit (ko-dic 분해
[서울, 특별시]증거). spec Appendix B 의 prior-knowledge 가정이 실측에서 일치. HOTFIXES 2026-05-28 entry 의 dogfood verification 표 + spec Appendix B 의 Empirical verification subsection 으로 cross-link.
9. References
- Spec contract:
docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec.md(668 line, ACCEPT, frozen). - Critic R1:
docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec-critic-r1.md(9 finding — 3 critical + 6 major, 모두 r1c 에서 resolved). - Critic R2:
docs/superpowers/specs/2026-05-28-v0.20.x-korean-morphological-tokenizer-spec-critic-r2.md(traceability matrix + ACCEPT verdict). - Parent design:
docs/superpowers/specs/2026-04-27-kebab-final-form-design.md(§5.5 chunks_fts + §9 version cascade). - Handoff:
docs/superpowers/handoffs/2026-05-28-v0.20.x-c-korean-morphological-tokenizer-handoff.md. - V007 trigram migration:
migrations/V007__fts_trigram.sql(2026-05-23 v0.17.0). - HOTFIXES:
tasks/HOTFIXES.md— 2026-05-22 trigram entry + 2026-05-24 build_match_string entry. - FTS tests:
crates/kebab-store-sqlite/tests/fts.rs:407(V007 verbatim test → rename target). - Lexical search:
crates/kebab-search/src/lexical.rs:205(build_match_string). - CLI hint:
crates/kebab-app/src/app.rs:98,532,616+crates/kebab-tui/src/{app,search,run}.rs(cascade). - lindera: https://github.com/lindera-morphology/lindera (MIT OR Apache-2.0).
- lindera-dict-ko-dic: https://github.com/lindera-morphology/lindera-dictionary (Apache-2.0, MeCab-ko-dic 기반).
- CLAUDE.md sections: §The facade rule (App::open_with_config 의 hook 위치), §Versioning cascade (index_version bump + S10 release trigger), §Naming + paths (kebab- prefix), §Spec contract (design §5.5 + plan deviations → HOTFIXES).