feat: search query utilities and multi-signal scoring

- Add src/search/query-utils.ts with extractSearchTerms, scorePathRelevance,
  kindBonus, detectApiIntent, inferRouteDirectories
- Add multi-signal scoring to searchNodes (kind bonus + path relevance)
- Improve FTS query sanitization (strip :^ chars, filter boolean operators)
- Add comprehensive search tests
This commit is contained in:
Martin Oehlert
2026-02-10 11:40:41 +01:00
parent 4825661e02
commit c80378d73c
3 changed files with 356 additions and 1 deletions
+13 -1
View File
@@ -18,6 +18,7 @@ import {
SearchResult,
} from '../types';
import { safeJsonParse } from '../utils';
import { kindBonus, scorePathRelevance } from '../search/query-utils';
/**
* Database row types (snake_case from SQLite)
@@ -451,6 +452,15 @@ export class QueryBuilder {
results = this.searchNodesLike(query, { kinds, languages, limit, offset });
}
// Apply multi-signal scoring
if (results.length > 0 && query) {
results = results.map(r => ({
...r,
score: r.score + kindBonus(r.node.kind) + scorePathRelevance(r.node.filePath, query),
}));
results.sort((a, b) => b.score - a.score);
}
return results;
}
@@ -463,9 +473,11 @@ export class QueryBuilder {
// Add prefix wildcard for better matching (e.g., "auth" matches "AuthService", "authenticate")
// Escape special FTS5 characters and add prefix wildcard
const ftsQuery = query
.replace(/['"*()]/g, '') // Remove special chars
.replace(/['"*():^]/g, '') // Remove FTS5 special chars
.split(/\s+/)
.filter(term => term.length > 0)
// Strip FTS5 boolean operators to prevent query manipulation
.filter(term => !/^(AND|OR|NOT|NEAR)$/i.test(term))
.map(term => `"${term}"*`) // Prefix match each term
.join(' OR ');