feat: v0.17.0 한국어 trigram FTS tokenizer + lexical builder + hint surface #159

Merged
altair823 merged 10 commits from feat/korean-trigram-tokenizer into main 2026-05-24 20:29:03 +00:00
Owner

요약

v0.17.0 release 의 본체. P10 종합 도그푸딩 round 2 (2026-05-22) 에서 발견된 "한국어 lexical 검색 무용" 문제 (FTS5 unicode61 토크나이저가 한국어 어절 단위로 잘라 부분 매칭 불가) 를 closure 한다.

chunks_fts 의 tokenizer 를 unicode61trigram 으로 V007 migration 으로 교체 (chunks 원본 + embedding + vector 불변, FTS shadow 만 자동 backfill — re-ingest 불필요) + lexical.rs::build_match_string trigram-aware 재설계 (whole-phrase + token-AND OR-combined, 3자 미만 토큰 drop, 후보 0이면 None 으로 빈 MATCH FTS5 syntax error 회피) + CLI/TUI/wire hint surface 안내 (2자 이하 query → "3자 이상 키워드 권장"). 동반 PR-B (C typedef + parser_version cascade + same-workspace_path orphan purge) + PR-C (code_lang_chunk_breakdown additive) 도 같이 v0.17.0 cut 예정.

설계: docs/superpowers/specs/2026-05-22-korean-trigram-tokenizer-design.md
계획: docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md

변경 요약

  • migration: migrations/V007__fts_trigram.sql (앞선 commit 에 머지) — chunks_fts tokenizer 교체 + trigger 재생성 + 자동 backfill INSERT. 컬럼 구성 V002 와 동일.
  • builder: crates/kebab-search/src/lexical.rs::build_match_string — Codex round 2 권장 알고리즘. raw single-quote mode 유지.
  • wire: SearchResponse.hint: Option<String> additive + docs/wire-schema/v1/search_response.schema.json 명세 + kebab_app::short_query_hint helper export.
  • CLI: text mode 에서 [hint] <message> stderr 한 줄. --jsonsearch_response.v1.hint 필드.
  • TUI: SearchState.short_query_hint + poll_worker 의 stale-aware set (generation guard 후 last_query 기반) + fire_search / mark_input_changed 시 reset + dynamic_status 가 Search pane 일 때 idle 보다 높은 우선순위로 표시.
  • design: §5.5 verbatim 블록 갱신 + CI diff-check fts_v007_matches_design_section_5_5_verbatim (앞선 commit).
  • docs: README + HANDOFF + HOTFIXES + SMOKE + integrations/claude-code SKILL.md.

사용자 가시 영향

  • 한국어 --mode lexical / --mode hybrid 정상 동작 (3자 이상 substring 매칭). 도그푸딩 round 2 "한국어 lexical 무용" 문제 closure.
  • 영어 lexical 도 substring 매칭으로 바뀜 — tokentokenizer 도 hit (recall ↑ / 단어 경계 ↓). 의도된 변경, 회귀 핀 fts_trigram_english_substring_hits.
  • 2자 이하 query 는 0-hit + CLI/TUI/wire hint 안내. raw FTS5 mode ('...') 제외.
  • kebab.sqlite 파일 크기 ~2-5배 또는 수백 MB 증가 (trigram index 비대화).
  • BM25 raw score 분포 변경 (token stream 차이); hybrid (RRF) ranking 영향 미미, retrieval.lexical_score 노출값 변동.

검증

  • 신규 unit / integration:
    • lexical::tests::build_match_string_* 9 PASS (신규 4 + 기존 2 expectation 갱신 + 3 기존).
    • fts.rs::fts_trigram_* (한국어/영어 substring + 2자 0-hit 핀, 3 신규).
    • search_korean.rs::lexical_multi_token_korean_query_hits + lexical_mixed_korean_english_multi_token_query_hits (multi-token 통합 회귀, 2 신규).
    • design §5.5 ↔ V007 diff-check (fts_v007_matches_design_section_5_5_verbatim).
  • snapshot regen 후 downstream test fix:
    • wire_search_response::search_json_truncates_with_max_tokens + search_plain_emits_truncated_hint_to_stderr — 다중 doc fixture 로 budget hit-pop 경로.
    • fetch_integration::fetch_chunk_with_context_returns_neighbors — fixture 5-char unique words + query cherry.
    • eval/runner::runner_per_query_snapshot_matches_fixture — BM25 raw score 분포 변경 reflect.
  • cargo test --workspace --no-fail-fast -j 1 + cargo clippy --workspace --all-targets -- -D warnings green.

시험 항목 (Test Plan)

  • 신규 KB 에 한국어 위키 문서 ingest → kebab search --mode lexical "충돌은" hit, "해시 충돌" multi-token hit, "충돌" 0-hit + stderr [hint], "충돌" --json 의 hint 필드 검증.
  • 기존 v0.16.x KB 의 kebab.sqlite 를 v0.17.0 binary 로 open → V007 자동 backfill 후 동일 한국어 query 시퀀스 검증 (re-ingest 없음).
  • 영어 substring 동작: kebab search --mode lexical "token"tokenizer 도 hit. 도그푸딩 KB 에서 false positive 비율 관찰.
  • kebab tui Search 패널에서 2자 query 입력 → status bar 가 hint 표시, 3자 query 로 바꾸면 hint 사라짐 확인.
  • kebab.sqlite 파일 크기 비교 (v0.16.x vs v0.17.0 backfill 후).

Assisted-by: Claude Code

## 요약 v0.17.0 release 의 본체. P10 종합 도그푸딩 round 2 (2026-05-22) 에서 발견된 "한국어 lexical 검색 무용" 문제 (FTS5 `unicode61` 토크나이저가 한국어 어절 단위로 잘라 부분 매칭 불가) 를 closure 한다. `chunks_fts` 의 tokenizer 를 `unicode61` → `trigram` 으로 V007 migration 으로 교체 (chunks 원본 + embedding + vector 불변, FTS shadow 만 자동 backfill — **re-ingest 불필요**) + `lexical.rs::build_match_string` trigram-aware 재설계 (whole-phrase + token-AND OR-combined, 3자 미만 토큰 drop, 후보 0이면 None 으로 빈 MATCH FTS5 syntax error 회피) + CLI/TUI/wire `hint` surface 안내 (2자 이하 query → "3자 이상 키워드 권장"). 동반 PR-B (C typedef + parser_version cascade + same-workspace_path orphan purge) + PR-C (code_lang_chunk_breakdown additive) 도 같이 v0.17.0 cut 예정. 설계: docs/superpowers/specs/2026-05-22-korean-trigram-tokenizer-design.md 계획: docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md ## 변경 요약 - **migration**: `migrations/V007__fts_trigram.sql` (앞선 commit 에 머지) — `chunks_fts` tokenizer 교체 + trigger 재생성 + 자동 backfill INSERT. 컬럼 구성 V002 와 동일. - **builder**: `crates/kebab-search/src/lexical.rs::build_match_string` — Codex round 2 권장 알고리즘. raw single-quote mode 유지. - **wire**: `SearchResponse.hint: Option<String>` additive + `docs/wire-schema/v1/search_response.schema.json` 명세 + `kebab_app::short_query_hint` helper export. - **CLI**: text mode 에서 `[hint] <message>` stderr 한 줄. `--json` 은 `search_response.v1.hint` 필드. - **TUI**: `SearchState.short_query_hint` + `poll_worker` 의 stale-aware set (generation guard 후 last_query 기반) + `fire_search` / `mark_input_changed` 시 reset + `dynamic_status` 가 Search pane 일 때 idle 보다 높은 우선순위로 표시. - **design**: §5.5 verbatim 블록 갱신 + CI diff-check `fts_v007_matches_design_section_5_5_verbatim` (앞선 commit). - **docs**: README + HANDOFF + HOTFIXES + SMOKE + integrations/claude-code SKILL.md. ## 사용자 가시 영향 - 한국어 `--mode lexical` / `--mode hybrid` 정상 동작 (3자 이상 substring 매칭). 도그푸딩 round 2 "한국어 lexical 무용" 문제 closure. - 영어 lexical 도 substring 매칭으로 바뀜 — `token` 이 `tokenizer` 도 hit (recall ↑ / 단어 경계 ↓). 의도된 변경, 회귀 핀 `fts_trigram_english_substring_hits`. - 2자 이하 query 는 0-hit + CLI/TUI/wire `hint` 안내. raw FTS5 mode (`'...'`) 제외. - `kebab.sqlite` 파일 크기 ~2-5배 또는 수백 MB 증가 (trigram index 비대화). - BM25 raw score 분포 변경 (token stream 차이); hybrid (RRF) ranking 영향 미미, `retrieval.lexical_score` 노출값 변동. ## 검증 - 신규 unit / integration: - `lexical::tests::build_match_string_*` 9 PASS (신규 4 + 기존 2 expectation 갱신 + 3 기존). - `fts.rs::fts_trigram_*` (한국어/영어 substring + 2자 0-hit 핀, 3 신규). - `search_korean.rs::lexical_multi_token_korean_query_hits` + `lexical_mixed_korean_english_multi_token_query_hits` (multi-token 통합 회귀, 2 신규). - design §5.5 ↔ V007 diff-check (`fts_v007_matches_design_section_5_5_verbatim`). - snapshot regen 후 downstream test fix: - `wire_search_response::search_json_truncates_with_max_tokens` + `search_plain_emits_truncated_hint_to_stderr` — 다중 doc fixture 로 budget hit-pop 경로. - `fetch_integration::fetch_chunk_with_context_returns_neighbors` — fixture 5-char unique words + query `cherry`. - `eval/runner::runner_per_query_snapshot_matches_fixture` — BM25 raw score 분포 변경 reflect. - `cargo test --workspace --no-fail-fast -j 1` + `cargo clippy --workspace --all-targets -- -D warnings` green. ## 시험 항목 (Test Plan) - [ ] 신규 KB 에 한국어 위키 문서 ingest → `kebab search --mode lexical "충돌은"` hit, `"해시 충돌"` multi-token hit, `"충돌"` 0-hit + stderr `[hint]`, `"충돌" --json` 의 hint 필드 검증. - [ ] 기존 v0.16.x KB 의 `kebab.sqlite` 를 v0.17.0 binary 로 open → V007 자동 backfill 후 동일 한국어 query 시퀀스 검증 (re-ingest 없음). - [ ] 영어 substring 동작: `kebab search --mode lexical "token"` 이 `tokenizer` 도 hit. 도그푸딩 KB 에서 false positive 비율 관찰. - [ ] `kebab tui` Search 패널에서 2자 query 입력 → status bar 가 hint 표시, 3자 query 로 바꾸면 hint 사라짐 확인. - [ ] `kebab.sqlite` 파일 크기 비교 (v0.16.x vs v0.17.0 backfill 후). Assisted-by: Claude Code
altair823 added 9 commits 2026-05-24 12:24:33 +00:00
P10 도그푸딩 round 2 (2026-05-22) follow-up. SQLite FTS5 tokenizer
unicode61 → trigram 으로 교체해 한국어 lexical 검색 지원 + 작은
버그픽스 2 (C typedef-wrapped struct 미노출, code_lang_breakdown
집계 단위).

Codex + Gemini round 1/2/3 리뷰 반영:
- [r1] 2자 한국어 query 0-hit, build_match_string() multi-token 깨짐,
  contentless → shadow, parser_version cascade, BM25/heading_path/디스크
- [r2] same-workspace_path orphan purge (parser bump cascade 실제 동작),
  trigram 테스트 예시 sqlite 3.45.1 검증, builder 권장안 (whole phrase OR)
- [r3] SMOKE 시나리오 정정, TUI stale hint 방지, search_response.v1 hint
  필드, new purge helpers, single quote raw mode 통일, fixture 도입

PR 구성: PR-A (trigram + builder + 안내), PR-B (C typedef + orphan
purge), PR-C (stats + wire). 셋 머지 후 v0.17.0 release cut.

design: docs/superpowers/specs/2026-05-22-korean-trigram-tokenizer-design.md
plan:   docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P10 도그푸딩 round 2 의 follow-up 후보를 HANDOFF "다음 task" /
"P10 백로그" 절에 반영. HOTFIXES 의 round 2 항목 (한국어 lexical
한계 + code_lang_breakdown + ranking deferred) 정합.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task A1 step 1-3 완료. plan A5 의 baseline 노트 슬롯 채움.

핵심 발견:
- build_match_string() (lexical.rs:177-200): trim → strip_single_quotes
  raw FTS verbatim / 그 외 whitespace split + escape_fts5_token (\"...\"
  + inner doubling) + space join (implicit AND).
- raw mode = single quote '...' 가 trimmed 전체 감쌈 (lexical.rs:167).
- SQLite: rusqlite 0.32 + libsqlite3-sys 0.30.1 bundled (in-tree, SQLite
  ~3.46.x) → trigram 사용 가능.
- Snapshot: tests/lexical.rs::lexical_snapshot_run_1 + tests/hybrid.rs::
  hybrid_snapshot_run_1 (KEBAB_UPDATE_SNAPSHOTS=1 로 regenerate).
  inline normalize_bm25_top_score 는 numerical 무관.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task A2 + A3 한 묶음.

migrations/V007__fts_trigram.sql 신규:
- chunks_fts shadow 를 DROP + 재생성 (tokenize = trigram).
- chunks_ai/ad/au trigger 재생성 (V002 와 동일).
- chunks 에서 backfill INSERT — 사용자 re-ingest 불필요, V007 자동.
- V002 는 historical cold-upgrade replay 위해 그대로 유지.

design §5.5 갱신:
- verbatim block 의 tokenize 만 trigram 으로 교체.
- §5.5 본문 상단에 한국어 채택 사유 + trade-off (영어 lexical 변경,
  BM25 분포, 디스크 ~2-10x, contentless 아님) prose 한 단락 추가.

crates/kebab-store-sqlite/tests/fts.rs:
- fts_v002_matches_design_section_5_5_verbatim →
  fts_v007_matches_design_section_5_5_verbatim 으로 rename.
- extract_migration_5_5_verbatim_block() 의 include_str! path 를
  V007__fts_trigram.sql 로 변경. 주석/assertion msg V007 로.
- V002 cold-upgrade test 들 (fts_v002_backfill_*) 은 그대로 유지.

검증: cargo test -p kebab-store-sqlite --test fts → 10/10 PASS
(`fts_v007_matches_design_section_5_5_verbatim` 포함).

Codex round 1/2 의 design §5.5 contentless 정정·trigram tokenizer
채택 사유 명시 발견 반영.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
도그푸딩 실 한국어 위키 문서 (hash-table.md, 4512줄 mediawiki HTML,
CC-BY-SA) 는 크기·라이선스 부담으로 직접 commit 회피. 대신 도그푸딩
query 들 (해시 충돌·충돌은·시 충·해시충·충돌) 을 모두 cover 하는 합성
fixture 작성. trigram tokenizer 의 정확한 매칭 동작 (3자 substring
hit, 2자 0-hit, raw vs quoted phrase) 검증용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3개 신규 unit tests in tests/fts.rs §7:

1. fts_trigram_korean_3char_substring_hits — Codex sqlite 3.45.1 검증
   동작 5개 assert pin: raw 3자 substring hit (충돌은/발생한),
   quoted phrase hit (\"해시 충돌\"/\"시 충\"), raw 해시충 0-hit (원문
   미존재).
2. fts_trigram_korean_short_query_zero_hit_pinned — 2자 한국어 query
   (충돌·키) 0-hit 회귀 감지. trigram 구조 변경 시 먼저 fail.
3. fts_trigram_english_substring_hits — substring recall 동작 변경
   pin (token→tokenizer, to 0-hit).

검증: cargo test -p kebab-store-sqlite --test fts → 13/13 PASS
(신규 3 + 기존 10).

Step 1c (multi-token 한국어 query e.g. \"해시 충돌\") 와 Step 5
(lexical BM25 snapshot 갱신) 는 Task A5 의 build_match_string()
재설계 후 진행.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR-A 본체. plan Task A4 Step 1c + A5.

- lexical.rs::build_match_string 재설계: whole-phrase + token-AND
  OR-combined, 3자 미만 토큰 drop, 후보 없음 시 None (빈 MATCH
  회피). raw single-quote mode 유지.
- SearchResponse.hint additive — empty result + trimmed < 3 chars
  + non-raw 케이스에 short_query_hint helper 가 set.
- CLI 'kebab search' 가 [hint] stderr 한 줄 (text mode).
- TUI SearchState.short_query_hint + poll_worker stale-aware set
  + fire_search/mark_input_changed reset + dynamic_status 표시.
- docs/wire-schema/v1/search_response.schema.json hint additive.
- 신규 unit tests (lexical 9 PASS, 기존 2 expectation 갱신) +
  통합 회귀 (search_korean: multi_token + mixed, 3 PASS) +
  BM25 snapshot regen (trigram token stream).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HOTFIXES: 새 2026-05-24 절 — v0.17.0 closure 영향 (한국어
  lexical 3-gram, 영어 substring 변경, BM25 분포, 디스크 용량,
  heading_path JSON 노이즈 관찰). 기존 2026-05-22 한국어 lexical
  항목의 Status / Next step 을 closure 표현으로 갱신.
- HANDOFF: 머지 후 발견 deviation 절에 2026-05-24 entry +
  기존 2026-05-22 항목을 closure cross-link 로 정리. P10
  백로그 한국어 tokenizer 항목  v0.17.0 + "다음 task 후보"
  follow-up 라인의 상태 갱신.
- README: 검색 명령 행에 trigram 동작 + hint + 디스크 용량 한 줄.
- SMOKE: 새 "한국어 trigram 검색 (v0.17.0)" 절 — 도그푸딩 query
  시퀀스 (충돌은 raw / 해시 충돌 multi-token / Rust 충돌은
  mixed / 충돌 2자 + stderr / --json hint 검증) + 영어 substring
  동작 변경 안내.
- SKILL.md: search 절에 hint 필드 안내 한 줄 — agent 가
  short query 케이스에서 같은 query 재시도 대신 사용자에게
  surface 하도록.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
trigram tokenizer 가 snippet 단위 + 단어 경계 + BM25 raw score 분포를
모두 바꿔서 unicode61 assumption 기반의 3 test 가 regression.

- wire_search_response::search_json_truncates_with_max_tokens +
  search_plain_emits_truncated_hint_to_stderr: 단일 doc + 작은
  max_tokens 로는 snippet 이 짧아서 budget loop 가 trip 안 함.
  다중 doc fixture (5 doc) + budget 30 token 으로 hit-pop 경로
  통해 truncated=true 보장.
- fetch_integration::fetch_chunk_with_context_returns_neighbors:
  fixture body 의 2-char tokens (A1/A3 등) 가 trigram 비호환으로
  0-hit. apples/banana/cherry/durian/elder 5-char unique words
  로 갱신, query 도 cherry 로 deterministic pin.
- eval/runner::runner_per_query_snapshot_matches_fixture: trigram
  token stream 으로 BM25 raw score 변동. UPDATE_SNAPSHOTS=1 로
  regenerate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
altair823 added 1 commit 2026-05-24 12:45:17 +00:00
PR #159 worker-1 review 의 LOW 가독성 nit 반영 — CLI stderr [hint]
line + --json hint shape 통합 test 가 없었음.

- search_plain_emits_short_query_hint_to_stderr — 빈 KB + 2자 query
  → stderr 가 "[hint]" + "3자 이상" 포함 확인.
- search_json_emits_hint_field_for_short_query — 동일 입력 --json
  → search_response.v1.hint 필드 set + 표준 advisory 문자열 정합.
- search_json_omits_hint_field_when_query_is_long_enough — 3자
  query → hint 필드 absent (additive serializer 의 None 제외 동작).

wire_search_response 5 → 8 PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
altair823 merged commit 67559fb3ce into main 2026-05-24 20:29:03 +00:00
altair823 deleted branch feat/korean-trigram-tokenizer 2026-05-24 20:29:05 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#159