| /** | |
| * Query preprocessing for retrieval. | |
| * | |
| * - normalizeQuery: trim, collapse whitespace, cap length. | |
| * - buildFtsMatch: turn free user text into a SAFE FTS5 MATCH expression. | |
| * Passing raw user input to FTS5 MATCH throws on punctuation/operators | |
| * ("?", "-", quotes, AND/OR/NEAR), so we tokenise, drop noise, quote | |
| * each term, and OR them together. | |
| * | |
| * Kept deterministic and dependency-free. An optional LLM-based rewrite | |
| * can be layered on later behind its own flag; it is intentionally not in | |
| * the hot path here (one fewer model call per turn). | |
| */ | |
| const MAX_LEN = 500; | |
| // Small, language-agnostic-ish stop list. Retrieval is robust to a few | |
| // extra terms, so this only needs to drop the highest-frequency noise. | |
| const STOP = new Set([ | |
| 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'your', 'with', 'have', | |
| 'has', 'had', 'this', 'that', 'what', 'why', 'how', 'when', 'who', 'can', | |
| 'could', 'would', 'should', 'about', 'from', 'into', 'than', 'then', | |
| 'them', 'they', 'there', 'here', 'been', 'was', 'were', 'will', 'just', | |
| 'get', 'got', 'feel', 'feeling', 'please', 'help', 'tell', 'know', | |
| ]); | |
| export function normalizeQuery(raw: string): string { | |
| return (raw || '').replace(/\s+/g, ' ').trim().slice(0, MAX_LEN); | |
| } | |
| /** Extract content tokens (lowercased, >=3 chars, non-stopword). */ | |
| export function queryTerms(query: string): string[] { | |
| const terms = (query.toLowerCase().match(/[a-z0-9]+/g) || []) | |
| .filter((t) => t.length >= 3 && !STOP.has(t)); | |
| return Array.from(new Set(terms)).slice(0, 24); | |
| } | |
| /** | |
| * Build a safe FTS5 MATCH string, e.g. `"chest" OR "pain" OR "breath"`. | |
| * Returns '' when there are no usable terms (caller then skips keyword | |
| * search and relies on vector similarity). | |
| */ | |
| export function buildFtsMatch(query: string): string { | |
| const terms = queryTerms(query); | |
| if (terms.length === 0) return ''; | |
| return terms.map((t) => `"${t}"`).join(' OR '); | |
| } | |