diff --git a/docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md b/docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md index 8393497..f1072df 100644 --- a/docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md +++ b/docs/superpowers/plans/2026-05-22-korean-trigram-tokenizer.md @@ -48,11 +48,22 @@ Codex 리뷰로 현재 `build_match_string()` (lexical.rs:177) 이 trigram 비 **Files:** - Read: `crates/kebab-search/src/lexical.rs` (`build_match_string()` 본문, MATCH query 빌드 라인 260-290, lexical snapshot 라인 506 부근) -- [ ] **Step 1: builder 동작 기록** — `build_match_string()` 의 정확한 동작 (raw query 입력 처리, mode 분기, escape, prefix `*` 처리, raw FTS mode 진입 조건 — `lexical.rs:167` 기준 **사용자가 single quote `'...'` 로 감싼 경우 raw FTS**) 을 Task A5 의 노트에 baseline 으로 기록 (재설계 시 회귀 방지). +- [x] **Step 1: builder 동작 기록** — `build_match_string()` (lexical.rs:177-200) baseline: + 1. `text.trim()` → trimmed. 빈 → `None` 반환. + 2. `strip_single_quotes(trimmed)` 매치 시 (= `'...'` 전체 감싸기, closing quote 가 trimmed 의 마지막 char) → inner.trim() 빈 아니면 `Some(inner.to_string())` (raw FTS5 verbatim mode). + 3. 그 외 → `trimmed.split_whitespace().map(escape_fts5_token).collect()` → 빈이면 `None`, 아니면 ` ` join (FTS5 default implicit AND). + - `escape_fts5_token` (lexical.rs:218): 토큰을 `"..."` 으로 wrap, inner `"` 은 doubling. + - prefix `*` 별도 처리 없음 — 사용자가 raw mode 로 입력해야. + - raw mode 진입 조건: 사용자가 single quote `'...'` 로 trimmed 전체를 감싼 경우 (`lexical.rs:167` 주석에 명시). + - MATCH 호출: lexical.rs:281 `WHERE chunks_fts MATCH ?` (bound parameter). -- [ ] **Step 2: SQLite 버전 확인** — `sqlite3 --version` 또는 cargo 가 링크하는 `libsqlite3-sys` 번들 버전. trigram 은 SQLite 3.34.0+ 필요 (대부분 충족). `tokenize = 'trigram'` 단독 사용 (case-insensitive 기본). `remove_diacritics` 옵션은 SQLite 3.45.0+ 요구라 호환성 위해 미사용. +- [x] **Step 2: SQLite 버전 확인** — `Cargo.toml`: `rusqlite = { version = "0.32", features = ["bundled"] }` + `Cargo.lock` `libsqlite3-sys = "0.30.1"` (system sqlite 무관, in-tree 빌드). libsqlite3-sys 0.30.1 의 번들 SQLite ~3.46.x — trigram (3.34+) 사용 가능. design 결정대로 `tokenize = 'trigram'` 단독 사용 (case-insensitive 기본). `remove_diacritics` 옵션 미사용. -- [ ] **Step 3: lexical snapshot 위치 확인** — `lexical.rs:506` 근처 BM25 snapshot 테스트가 어느 파일·함수인지 (`crates/kebab-search/tests/` 또는 `insta` 스냅샷 디렉토리) 확인. Task A4 Step 5 에서 갱신 대상. +- [x] **Step 3: lexical snapshot 위치 확인** — Codex round 1 의 "lexical.rs:506" 은 `fn normalize_bm25` (BM25 score → (0,1] mapping) 였음 — numerical transformation 이라 token stream 영향 없음. 진짜 snapshot 은: + - `crates/kebab-search/tests/lexical.rs:1012` `lexical_snapshot_run_1` — fixture 기반, `KEBAB_UPDATE_SNAPSHOTS=1` env 로 regenerate, "baseline snapshot must exist; run with KEBAB_UPDATE_SNAPSHOTS=1 to seed". + - `crates/kebab-search/tests/hybrid.rs:121` `hybrid_snapshot_run_1` — 동일 패턴 (`hybrid_snapshot drift`). 한국어 trigram 영향 받음 (token stream 변경). + - inline `crates/kebab-search/src/lexical.rs:592` `normalize_bm25_top_score_in_unit_interval` — numerical, 영향 없음 (회귀 없음 확인만). + Task A4 Step 5 에서 lexical_snapshot_run_1 + hybrid_snapshot_run_1 둘 다 regenerate. ### Task A2: V007 migration 작성 @@ -161,7 +172,21 @@ Codex 검증: 현재 `build_match_string()` (lexical.rs:177) 은 whitespace spli **사용자 결정** (2자 이하 한국어 query 정책): lexical core 는 정상 0-hit (변경 없음), 안내 메시지는 CLI/TUI 레이어가 출력 ("3자 이상 키워드 권장"). -**A1 baseline 노트:** _(Task A1 Step 1 에서 채움 — 현재 builder 의 raw query 처리, mode 분기, escape, raw FTS 진입 조건)_ +**A1 baseline 노트** (Task A1 Step 1 에서 채움): + +`build_match_string(text: &str) -> Option` (lexical.rs:177-200) baseline: + +1. `text.trim()` → trimmed. 빈 → `None`. +2. `strip_single_quotes(trimmed)` 매치 시 (single quote `'...'` 가 trimmed 전체 감쌈, closing quote 가 마지막 char — `'foo' bar` 는 raw 아님) → inner.trim() 빈 아니면 `Some(inner.to_string())` (raw FTS5 verbatim). +3. 그 외 → `trimmed.split_whitespace().map(escape_fts5_token).collect()` → 빈이면 `None`, 아니면 ` ` join (FTS5 default implicit AND). + +`escape_fts5_token(tok)` (lexical.rs:218): `"..."` wrap + inner `"` doubling. + +재설계 시 회귀 방지 — raw mode (single quote `'...'`) 진입 조건은 그대로 유지. escape_fts5_token 도 그대로 (trigram 도 FTS5 special char escape 필요). 변경은 비-raw 경로의 토큰 합성만. + +SQLite: rusqlite 0.32 + libsqlite3-sys 0.30.1 **bundled** (in-tree). SQLite ~3.46.x → trigram 사용 가능. + +Snapshot: `crates/kebab-search/tests/lexical.rs::lexical_snapshot_run_1` + `crates/kebab-search/tests/hybrid.rs::hybrid_snapshot_run_1` (둘 다 `KEBAB_UPDATE_SNAPSHOTS=1` 로 regenerate). inline `normalize_bm25_top_score_in_unit_interval` 는 numerical 영향 없음. **Files:** - Modify: `crates/kebab-search/src/lexical.rs` (`build_match_string()`)