Implement kebab_core::Retriever for SearchMode::Lexical using SQLite FTS5. Returns SearchHit with bm25 ranking, snippet()-derived preview, and proper W3C-fragment citation.
Why now / why this size
First concrete Retriever. Lets kebab search --mode lexical work without any embedding/LLM infrastructure. Establishes the SearchHit construction contract that hybrid (p3-4) reuses.
Allowed dependencies
kebab-core
kebab-config
kebab-store-sqlite (read access to chunks_fts + chunks + documents)
with score ASC because SQLite FTS5 returns negative bm25 (lower = better). Convert to a positive normalized score for SearchHit.retrieval.fusion_score: score = -bm25_raw / (1 + abs(bm25_raw)) (bounded ~[0,1]).
:match building: tokenize the query string conservatively (split on whitespace, escape FTS5 special chars, default to AND of terms; if the user supplied an explicit FTS5 expression, pass it through when wrapped in single quotes).
:snippet_words derived from config.search.snippet_chars / 4 (~chars-per-token estimate). Snippet length must not exceed snippet_chars characters.
SearchHit.citation constructed from chunks.source_spans_json first span:
Line → Citation::Line { path, start, end, section: section_label }