Enhance search result merging and Svelte component extraction
Changes search result deduplication to use max scores across channels instead of first-seen prioritization, adds template component usage extraction for Svelte files, exempts exact matches from single-term score dampening, prioritizes structural edges in graph traversal, and increases explore tool node budget while including edge source locations in file clustering.
This commit is contained in:
+25
-10
@@ -437,22 +437,30 @@ export class ContextBuilder {
|
||||
logDebug('Text search failed', { query, error: String(error) });
|
||||
}
|
||||
|
||||
// Step 4: Merge results, prioritizing exact matches, then text (path-boosted)
|
||||
const seenIds = new Set<string>();
|
||||
// Step 4: Merge results, taking the max score when duplicates appear
|
||||
// across search channels. Exact matches may have lower scores than FTS
|
||||
// results for the same node — use the best score from any channel.
|
||||
const resultById = new Map<string, SearchResult>();
|
||||
let searchResults: SearchResult[] = [];
|
||||
|
||||
// Add exact matches first (highest priority)
|
||||
// Add exact matches first
|
||||
for (const result of exactMatches) {
|
||||
if (!seenIds.has(result.node.id)) {
|
||||
seenIds.add(result.node.id);
|
||||
const existing = resultById.get(result.node.id);
|
||||
if (existing) {
|
||||
existing.score = Math.max(existing.score, result.score);
|
||||
} else {
|
||||
resultById.set(result.node.id, result);
|
||||
searchResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Add text search results (includes path relevance scoring from searchNodes)
|
||||
// Add text search results, upgrading scores for duplicates
|
||||
for (const result of textResults) {
|
||||
if (!seenIds.has(result.node.id)) {
|
||||
seenIds.add(result.node.id);
|
||||
const existing = resultById.get(result.node.id);
|
||||
if (existing) {
|
||||
existing.score = Math.max(existing.score, result.score);
|
||||
} else {
|
||||
resultById.set(result.node.id, result);
|
||||
searchResults.push(result);
|
||||
}
|
||||
}
|
||||
@@ -498,6 +506,12 @@ export class ContextBuilder {
|
||||
termGroups.push(group);
|
||||
}
|
||||
|
||||
// Build a set of exact-match node IDs so we can exempt them from dampening.
|
||||
// When the query is "LiveEditMode DevServerPreview", these are specific
|
||||
// symbols the user asked for — dampening them because they only match 1
|
||||
// term group is counter-productive.
|
||||
const exactMatchIds = new Set(exactMatches.map(r => r.node.id));
|
||||
|
||||
for (const result of searchResults) {
|
||||
// Check term matches in name (substring) and path DIRECTORIES (exact).
|
||||
// Directory segments must match exactly — "search" matches directory
|
||||
@@ -517,9 +531,10 @@ export class ContextBuilder {
|
||||
if (matchCount >= 2) {
|
||||
// Multiplicative boost — 2 terms → 2x, 3 terms → 2.5x
|
||||
result.score *= 1 + matchCount * 0.5;
|
||||
} else {
|
||||
} else if (!exactMatchIds.has(result.node.id)) {
|
||||
// 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)
|
||||
// but could also be the right result (e.g., "Protocol" class for an IPC query).
|
||||
// Exempt exact name matches: they are specific symbols the user queried for.
|
||||
result.score *= 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user