|
@@ -122,9 +122,7 @@ function extractSymbolsFromQuery(query: string): string[] {
|
|
|
'interface', 'interfaces', 'class', 'classes', 'method', 'methods',
|
|
'interface', 'interfaces', 'class', 'classes', 'method', 'methods',
|
|
|
'trigger', 'triggers', 'affected', 'affect', 'affects',
|
|
'trigger', 'triggers', 'affected', 'affect', 'affects',
|
|
|
'else', 'code', 'failing', 'failed', 'silently', 'decide', 'decides',
|
|
'else', 'code', 'failing', 'failed', 'silently', 'decide', 'decides',
|
|
|
- 'connect', 'connection', 'connections',
|
|
|
|
|
'return', 'returns', 'returned', 'take', 'takes', 'taken',
|
|
'return', 'returns', 'returned', 'take', 'takes', 'taken',
|
|
|
- 'send', 'sends', 'receive', 'receives', 'process', 'processes',
|
|
|
|
|
'check', 'checks', 'checked', 'create', 'creates', 'created',
|
|
'check', 'checks', 'checked', 'create', 'creates', 'created',
|
|
|
'read', 'reads', 'write', 'writes', 'written',
|
|
'read', 'reads', 'write', 'writes', 'written',
|
|
|
'start', 'starts', 'stop', 'stops', 'run', 'runs', 'running',
|
|
'start', 'starts', 'stop', 'stops', 'run', 'runs', 'running',
|
|
@@ -520,9 +518,9 @@ export class ContextBuilder {
|
|
|
// Multiplicative boost — 2 terms → 2x, 3 terms → 2.5x
|
|
// Multiplicative boost — 2 terms → 2x, 3 terms → 2.5x
|
|
|
result.score *= 1 + matchCount * 0.5;
|
|
result.score *= 1 + matchCount * 0.5;
|
|
|
} else {
|
|
} else {
|
|
|
- // Dampen single-term matches — they matched a generic word
|
|
|
|
|
- // (e.g., "Execution" or "Shard" alone) not the compound concept
|
|
|
|
|
- result.score *= 0.3;
|
|
|
|
|
|
|
+ // Mild dampen for single-term matches — they might be generic
|
|
|
|
|
+ // but could also be the right result (e.g., "Protocol" class for an IPC query)
|
|
|
|
|
+ result.score *= 0.6;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
searchResults.sort((a, b) => b.score - a.score);
|
|
searchResults.sort((a, b) => b.score - a.score);
|
|
@@ -564,7 +562,9 @@ export class ContextBuilder {
|
|
|
const name = r.node.name;
|
|
const name = r.node.name;
|
|
|
const idx = name.indexOf(titleCased);
|
|
const idx = name.indexOf(titleCased);
|
|
|
if (idx <= 0) continue;
|
|
if (idx <= 0) continue;
|
|
|
- if (!/[a-z]/.test(name.charAt(idx - 1))) continue;
|
|
|
|
|
|
|
+ // Accept CamelCase boundary (lowercase before match) OR
|
|
|
|
|
+ // acronym boundary (uppercase before match, e.g., RPCProtocol)
|
|
|
|
|
+ if (!/[a-zA-Z]/.test(name.charAt(idx - 1))) continue;
|
|
|
if (searchIdSet.has(r.node.id)) continue;
|
|
if (searchIdSet.has(r.node.id)) continue;
|
|
|
if (isTestFile(r.node.filePath) && !isTestQuery) continue;
|
|
if (isTestFile(r.node.filePath) && !isTestQuery) continue;
|
|
|
|
|
|
|
@@ -592,14 +592,20 @@ export class ContextBuilder {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Append CamelCase matches with multi-term boost (guaranteed slots)
|
|
|
|
|
|
|
+ // Append CamelCase matches with multi-term boost.
|
|
|
|
|
+ // These are structurally important (class names containing query terms at
|
|
|
|
|
+ // CamelCase boundaries) but score much lower than FTS results. Scale their
|
|
|
|
|
+ // scores up so multi-term CamelCase matches can compete with FTS results.
|
|
|
const camelResults: SearchResult[] = [];
|
|
const camelResults: SearchResult[] = [];
|
|
|
for (const [, info] of camelNodeTerms) {
|
|
for (const [, info] of camelNodeTerms) {
|
|
|
- info.result.score += (info.termCount - 1) * 15;
|
|
|
|
|
|
|
+ // Multi-term CamelCase matches are extremely relevant — a class matching
|
|
|
|
|
+ // 3+ query terms in its name (e.g., ExtensionHostProcess) is almost
|
|
|
|
|
+ // certainly what the user wants. Scale aggressively.
|
|
|
|
|
+ info.result.score = info.result.score * (1 + info.termCount) + (info.termCount - 1) * 30;
|
|
|
camelResults.push(info.result);
|
|
camelResults.push(info.result);
|
|
|
}
|
|
}
|
|
|
camelResults.sort((a, b) => b.score - a.score);
|
|
camelResults.sort((a, b) => b.score - a.score);
|
|
|
- const maxCamelTotal = Math.ceil(opts.searchLimit / 2);
|
|
|
|
|
|
|
+ const maxCamelTotal = opts.searchLimit;
|
|
|
for (const r of camelResults.slice(0, maxCamelTotal)) {
|
|
for (const r of camelResults.slice(0, maxCamelTotal)) {
|
|
|
searchResults.push(r);
|
|
searchResults.push(r);
|
|
|
searchIdSet.add(r.node.id);
|
|
searchIdSet.add(r.node.id);
|
|
@@ -672,6 +678,13 @@ export class ContextBuilder {
|
|
|
// they want the TerminalPanel class, not the import statement
|
|
// they want the TerminalPanel class, not the import statement
|
|
|
filteredResults = this.resolveImportsToDefinitions(filteredResults);
|
|
filteredResults = this.resolveImportsToDefinitions(filteredResults);
|
|
|
|
|
|
|
|
|
|
+ // Cap entry points so traversal budget isn't spread too thin.
|
|
|
|
|
+ // With 36 entry points and maxNodes=120, each gets only 3 nodes — useless.
|
|
|
|
|
+ // Cap to searchLimit so each entry point gets a meaningful traversal budget.
|
|
|
|
|
+ if (filteredResults.length > opts.searchLimit) {
|
|
|
|
|
+ filteredResults = filteredResults.slice(0, opts.searchLimit);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Add entry points to subgraph
|
|
// Add entry points to subgraph
|
|
|
for (const result of filteredResults) {
|
|
for (const result of filteredResults) {
|
|
|
nodes.set(result.node.id, result.node);
|
|
nodes.set(result.node.id, result.node);
|