fix(search): down-weight the project name in ranking — completes #720 (#748)

The per-word path fix (#745) brought the backend to parity but not above:
the project name still gave the lexically-matching stack a residual dir
match + an FTS class-name match, so a backend query that included the
project name still ranked the frontend at/above the backend.

Derive the project name from go.mod module / package.json name / repo dir,
and treat a query word matching it as non-discriminative: drop it from path
relevance and from codegraph_explore's PascalCase type-disambiguation bias
(reporter's suggestions #1/#2) — unless it's the only query word, so a bare
project-name search still scores.

Narrow by construction: the down-weighting fires ONLY when a query word
matches the derived project name (≥5 chars), so every query that doesn't
name the project is byte-identical. On the reporter's repro the backend
controllers now top a backend question that includes the project name;
queries without it, bare project-name queries, and normal symbol queries
are unchanged. Query-time only (no re-index).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-06-08 22:43:54 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent afec1282e1
commit 75ae1e8bd9
6 changed files with 154 additions and 10 deletions
+18 -1
View File
@@ -176,6 +176,12 @@ function rowToFileRecord(row: FileRow): FileRecord {
export class QueryBuilder {
private db: SqliteDatabase;
// Project-name tokens (go.mod / package.json / repo dir), normalized. A query
// word matching one is dropped from path-relevance scoring — it names the
// whole project, not a symbol, so it carries no discriminative signal (#720).
// Set once by the CodeGraph instance; empty by default (no down-weighting).
private projectNameTokens: Set<string> = new Set();
// Node cache for frequently accessed nodes (LRU-style, max 1000 entries)
private nodeCache: Map<string, Node> = new Map();
private readonly maxCacheSize = 1000;
@@ -219,6 +225,17 @@ export class QueryBuilder {
this.db = db;
}
/** Set the normalized project-name tokens used to down-weight non-discriminative
* query words in path scoring (#720). Called once when the project opens. */
setProjectNameTokens(tokens: Set<string>): void {
this.projectNameTokens = tokens;
}
/** The normalized project-name tokens (#720); empty if none were derived. */
getProjectNameTokens(): Set<string> {
return this.projectNameTokens;
}
// ===========================================================================
// Node Operations
// ===========================================================================
@@ -842,7 +859,7 @@ export class QueryBuilder {
...r,
score: r.score
+ kindBonus(r.node.kind)
+ scorePathRelevance(r.node.filePath, scoringQuery)
+ scorePathRelevance(r.node.filePath, scoringQuery, this.projectNameTokens)
+ nameMatchBonus(r.node.name, scoringQuery),
}));
results.sort((a, b) => b.score - a.score);