* Search remembered facts by relevance (FTS5 bm25 rank), scoped like getFactsByScope. * Empty/again-unusable query ⇒ []. Falls back to a LIKE scan when FTS5 isn't available.
(query: string, scope: 'project' | 'user', cwd: string, limit = 20)
| 385 | const factN = (this.db.prepare(`SELECT count(*) AS n FROM session_facts`).get() as { n: number }).n; |
| 386 | if (ftsN === 0 && factN > 0) { |
| 387 | this.db.exec(`INSERT INTO session_facts_fts(session_facts_fts) VALUES('rebuild')`); |
| 388 | } |
| 389 | this.ftsReady = true; |
| 390 | } catch (e: any) { |
| 391 | logger.debug('FTS over facts unavailable; recall search will fall back to LIKE', { err: e?.message }); |
| 392 | this.ftsReady = false; |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | /** |
| 397 | * Search remembered facts by relevance (FTS5 bm25 rank), scoped like getFactsByScope. |
| 398 | * Empty/again-unusable query ⇒ []. Falls back to a LIKE scan when FTS5 isn't available. |
| 399 | */ |
| 400 | searchFacts(query: string, scope: 'project' | 'user', cwd: string, limit = 20): string[] { |
| 401 | const where = scope === 'user' ? `f.scope = 'user'` : `f.cwd = ? AND f.scope = 'project'`; |
| 402 | const scopeArgs = scope === 'user' ? [] : [cwd]; |
| 403 | const match = buildFtsMatch(query); |
| 404 | if (this.ftsReady && match) { |
| 405 | try { |
| 406 | const rows = this.db.prepare( |
| 407 | `SELECT f.fact AS fact FROM session_facts_fts ft JOIN session_facts f ON f.id = ft.rowid |
| 408 | WHERE session_facts_fts MATCH ? AND ${where} ORDER BY rank LIMIT ?`, |
| 409 | ).all(match, ...scopeArgs, limit * 4) as { fact: string }[]; |
| 410 | return dedupe(rows.map(r => r.fact)).slice(0, limit); |
| 411 | } catch (e: any) { |
| 412 | logger.debug('FTS search failed; falling back to LIKE', { err: e?.message }); |
| 413 | } |
| 414 | } |
no test coverage detected