Files
kebab/docs/superpowers/plans/2026-05-28-v0.20.x-korean-morphological-tokenizer-plan.md
altair823 028d9ad4ea docs(release): v0.20.1 release notes draft + spec/plan dogfood cross-link
#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
2026-05-28 13:34:33 +00:00

60 KiB
Raw Blame History

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.tomllindera + lindera-dict-ko-dic per-crate dep 추가 (또는 feature gate). chunk crate 가 한국어 tokenizer 의 owner.
  • crates/kebab-chunk/src/lib.rstokenize_korean_morphological(text: &str) -> Option<String> helper 신규 + chunk builder pipeline 에 호출 추가.
  • crates/kebab-store-sqlite/src/store.rs — chunk INSERT path 에 tokenized_korean_text column 추가, backfill API backfill_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.rsbuild_match_string() 의 trigram-specific 분기 단순화 (보존 정책 결정).
  • crates/kebab-app/src/app.rsshort_query_hint() 함수 + 2 호출 site 제거, lexical_index_version() 의 source 갱신.
  • crates/kebab-app/src/lib.rsshort_query_hint re-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.rsshort_query_hint 필드 + 호출 제거 (TUI 측 cascade).
  • crates/kebab-cli/tests/wire_search_response.rssearch_plain_emits_short_query_hint_to_stderr test 삭제 또는 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 Cascadelexical_index_version bump.
  • §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 renamecrates/kebab-store-sqlite/tests/fts.rs:407fts_v007_matches_design_section_5_5_verbatimfts_v009_matches_design_section_5_5_verbatim 로 rename. 함수 body 의 marker 검색 대상도 migrations/V007__fts_trigram.sqlmigrations/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_verbatimv009_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.rs0 (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.rs proactive 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.0 for lindera, Apache-2.0 for 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 -l0.
  • 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.rstokenize_korean_morphological() 함수 신규 + chunk builder pipeline 에 호출.
  • (선택) crates/kebab-chunk/tests/tokenize_korean.rs — lindera segmentation 의 단위 검증.

Implementation outline:

  • 3.1 helper 함수 signaturekebab-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 의 text field 가 채워진 직후 시점) 에서 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_text column 추가. 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_text field 존재 (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.rsbackfill_tokenized_korean_text(progress_cb) API 신규.
  • crates/kebab-app/src/lib.rsApp::open_with_config 에 first-boot hook 추가 + idempotency guard.

Implementation outline:

  • 4.1 backfill API signaturestore.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.rsApp::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_rows test 의 두 번째 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.rsshort_query_hint() 함수 정의 + 2 호출 site (line 532, 616) 제거.
  • crates/kebab-app/src/lib.rs — re-export pub 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_hint field + 호출 cascade 제거.
  • crates/kebab-cli/tests/wire_search_response.rs:215search_plain_emits_short_query_hint_to_stderr test 삭제.
  • crates/kebab-search/src/lexical.rs::build_match_string() — trigram-specific 분기 단순화 검토.

Implementation outline:

  • 5.1 short_query_hint 정의 + 호출 제거crates/kebab-app/src/app.rs:98pub 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 영향 확인SearchResponse struct 의 short_query_hint 또는 hint field 가 search_hit.v1 / search_response.v1 JSON Schema 에 포함되어 있는지 docs/wire-schema/v1/*.schema.json grep. (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:75pub use app::{App, SearchResponse, short_query_hint}; 에서 short_query_hint 부분 제거.
  • 5.4 TUI cascade 제거crates/kebab-tui/src/app.rs:161,188pub 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:215search_plain_emits_short_query_hint_to_stderr test 전체를 삭제. 또는 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 4 success.

Acceptance criteria:

  • grep -rn "short_query_hint" crates/ tests/ 2>/dev/null | wc -l0 (또는 doc-comment 의 1 줄만 남음).
  • cargo build --workspace -j 4 2>&1 | grep -E "^error" | wc -l0 (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-993lexical_index_version() 함수의 반환 문자열 갱신.

Implementation outline:

  • 6.1 현재 source-of-truth 확인 — line 991-993 의 fn lexical_index_versionIndexVersion(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:650lexical_index_version_is_returned_unchanged test 가 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.rsfts_v009_korean_morphological_2char_query_hits + fts_v009_english_whole_token_only 추가.
  • Create crates/kebab-app/tests/search_korean.rskorean_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 testcrates/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≥ 1
    • cargo 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 -l0.
  • 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 — 명령 table kebab 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-dic edge 추가. (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 bump rule 따라 본 변경은 "user 가 새 바이너리로 도그푸딩 또는 실사용을 할 필요" + "frozen design contract 변경 (design §5.5 갱신)" 두 트리거 모두 해당 → bump 필수. Cargo.toml workspace version"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.toml1. cargo build --workspace -j 4 → success + binary --version 출력이 0.20.1.

Acceptance criteria:

  • grep "^version" Cargo.toml | head -1version = "0.20.1".
  • cargo build --release -p kebab-cli -j 4 && ./target/release/kebab --version 2>&1kebab 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 완료 후 → ≥ 1
    

    V007 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.rs proactive download 또는 vendored crate 로 후퇴 (별 follow-up).
  • Risk: lindera version pin 의 transitive dep 가 기존 workspace 와 충돌 (예: unicode-normalization, serde major version 의 lock 갈등).
  • Mitigation: cargo updatecargo tree --duplicates 로 회귀 확인.

S3 (tokenize_korean_morphological)

  • Risk: lindera ko-dic 의 '한국어' segmentation 이 단일 token (['한국어']) 으로 나오면 '한국' query 0-hit (spec critic R1 finding #3). 본 plan 의 S3.5 test tokenize_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 의 SearchResponseshort_query_hint field 를 포함 → 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_korean test 의 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 에게 위임)

  1. lindera version pin0.32 또는 0.34 (cargo search 시점 latest stable) 중 선택. major breaking change 부재 가정.
  2. lindera-dict-ko-dic feature flag nameembedded-dict, ko-dic, compress 중 정확한 이름은 crate metadata 확인 후 결정.
  3. SearchResponseshort_query_hint field 의 wire schema 포함 여부 — S5.2 에서 grep 확인 후 결정.
  4. lexical.rs::build_match_string() 의 trigram-specific 분기 보존 여부 — 본 plan 은 보존 선택 (S5.6). executor 가 코드 가독성 측면에서 단순화 판단 시 별 commit 으로 분리.
  5. 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 tree SPDX 수동 검증으로 대체. 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.toml workspace version = "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

  1. No branch change. 10 commit 모두 feat/korean-morphological-tokenizer branch.
  2. Spec frozen. ACCEPT spec 의 본문 X edit. deviation → tasks/HOTFIXES.md.
  3. Wire schema shape 불변. spec §11.3 따라 hit ordering / snippet content 만 변화. 신규 wire schema 또는 field add 없음.
  4. Regression budget 0. baseline workspace test pass 수 (≥ 1370) 유지 + 신규 +4 test 추가.
  5. Worker protocol — subagent skip. executor 는 nested worker spawn 없이 sequential N step.
  6. Length budget. 500-800 line plan (본 file ≈ 740 line).
  7. Build path. export CARGO_TARGET_DIR=/build/out/cargo-target/target; -j 4 default, -j 1 for workspace sanity.
  8. Commit cadence. 10 commit (S1~S10). S11 verify-only.
  9. Doc sync. S9 의 5 file cascade 명시. README narrow scope 보존.
  10. Release trigger. CLAUDE.md Release / binary version bump rule 의 두 트리거 모두 hit (도그푸딩 필요 + design §5.5 변경). v0.20.1 patch release 별 task.
  11. cargo-deny deferred. Appendix D 의 cargo deny check 절차는 본 PR scope 외 (P9 follow-up). license 검증은 cargo tree SPDX 수동 + 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_CHARS 3→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).