feat: Enhance search ranking with name matching and field extraction improvements

Adds nameMatchBonus scoring to prioritize results where node names exactly or partially match query terms. Implements dedicated field extraction for Java/C# to properly categorize class fields vs variables. Optimizes BM25 search with column weights favoring name matches and increased result fetching before post-processing. Refines stop words list to preserve common programming terms like "get", "find", "list".
This commit is contained in:
Colby McHenry
2026-04-06 12:20:44 -05:00
parent b04ee9f9bb
commit e5663c5952
6 changed files with 128 additions and 11 deletions
+18 -4
View File
@@ -18,7 +18,7 @@ import {
SearchResult,
} from '../types';
import { safeJsonParse } from '../utils';
import { kindBonus, scorePathRelevance } from '../search/query-utils';
import { kindBonus, nameMatchBonus, scorePathRelevance } from '../search/query-utils';
/**
* Database row types (snake_case from SQLite)
@@ -492,9 +492,16 @@ export class QueryBuilder {
if (results.length > 0 && query) {
results = results.map(r => ({
...r,
score: r.score + kindBonus(r.node.kind) + scorePathRelevance(r.node.filePath, query),
score: r.score
+ kindBonus(r.node.kind)
+ scorePathRelevance(r.node.filePath, query)
+ nameMatchBonus(r.node.name, query),
}));
results.sort((a, b) => b.score - a.score);
// Trim to requested limit after rescoring
if (results.length > limit) {
results = results.slice(0, limit);
}
}
return results;
@@ -521,8 +528,15 @@ export class QueryBuilder {
return [];
}
// BM25 column weights: id=0, name=20, qualified_name=5, docstring=1, signature=2
// Heavy name weight ensures exact/prefix name matches rank above incidental
// mentions in long docstrings or qualified names of nested symbols.
// Fetch 5x requested limit so post-hoc rescoring (kindBonus, pathRelevance,
// nameMatchBonus) can promote results that BM25 alone undervalues.
const ftsLimit = Math.max(limit * 5, 100);
let sql = `
SELECT nodes.*, bm25(nodes_fts) as score
SELECT nodes.*, bm25(nodes_fts, 0, 20, 5, 1, 2) as score
FROM nodes_fts
JOIN nodes ON nodes_fts.id = nodes.id
WHERE nodes_fts MATCH ?
@@ -541,7 +555,7 @@ export class QueryBuilder {
}
sql += ' ORDER BY score LIMIT ? OFFSET ?';
params.push(limit, offset);
params.push(ftsLimit, offset);
try {
const rows = this.db.prepare(sql).all(...params) as (NodeRow & { score: number })[];