* Graph-connectivity relevance via Random-Walk-with-Restart (personalized * PageRank) from the query's matched SEED nodes over the call/reference graph. * * This is the ranking signal text search (FTS/bm25) CANNOT provide, and it's * codegraph's home turf: relevance by STRUCTURE, not wor
(
nodeIds: string[],
edges: Edge[],
seedIds: Set<string>,
)
| 2392 | * it's a few hundred nodes × ~25 iterations — negligible cost. |
| 2393 | */ |
| 2394 | private computeGraphRelevance( |
| 2395 | nodeIds: string[], |
| 2396 | edges: Edge[], |
| 2397 | seedIds: Set<string>, |
| 2398 | ): Map<string, number> { |
| 2399 | const out = new Map<string, number>(); |
| 2400 | const n = nodeIds.length; |
| 2401 | if (n === 0) return out; |
| 2402 | const idx = new Map<string, number>(); |
| 2403 | for (let i = 0; i < n; i++) idx.set(nodeIds[i]!, i); |
| 2404 | |
| 2405 | const RANK_EDGES = new Set<string>([ |
| 2406 | 'calls', 'references', 'extends', 'implements', 'overrides', |
| 2407 | 'instantiates', 'returns', 'type_of', 'imports', |
| 2408 | ]); |
| 2409 | const adj: number[][] = Array.from({ length: n }, () => []); |
| 2410 | for (const e of edges) { |
| 2411 | if (!RANK_EDGES.has(e.kind)) continue; |
| 2412 | const i = idx.get(e.source); |
| 2413 | const j = idx.get(e.target); |
| 2414 | if (i === undefined || j === undefined || i === j) continue; |
| 2415 | adj[i]!.push(j); |
| 2416 | adj[j]!.push(i); // undirected — reachable either direction |
| 2417 | } |
| 2418 | |
| 2419 | // Restart vector: uniform over seeds present in the candidate set. (Falls |
| 2420 | // back to uniform-over-all if no seed landed in the set, so we never return |
| 2421 | // all-zero.) |
| 2422 | const r = new Array<number>(n).fill(0); |
| 2423 | let rsum = 0; |
| 2424 | for (const id of seedIds) { |
| 2425 | const i = idx.get(id); |
| 2426 | if (i !== undefined) { r[i] = 1; rsum += 1; } |
| 2427 | } |
| 2428 | if (rsum === 0) { for (let i = 0; i < n; i++) r[i] = 1; rsum = n; } |
| 2429 | for (let i = 0; i < n; i++) r[i]! /= rsum; |
| 2430 | |
| 2431 | const alpha = 0.25; |
| 2432 | let s = r.slice(); |
| 2433 | for (let iter = 0; iter < 25; iter++) { |
| 2434 | const next = new Array<number>(n).fill(0); |
| 2435 | for (let i = 0; i < n; i++) { |
| 2436 | const si = s[i]!; |
| 2437 | if (si === 0) continue; |
| 2438 | const d = adj[i]!.length; |
| 2439 | if (d === 0) { next[i]! += si; continue; } // dangling: keep its mass |
| 2440 | const share = si / d; |
| 2441 | for (const j of adj[i]!) next[j]! += share; |
| 2442 | } |
| 2443 | for (let i = 0; i < n; i++) s[i] = (1 - alpha) * next[i]! + alpha * r[i]!; |
| 2444 | } |
| 2445 | for (let i = 0; i < n; i++) out.set(nodeIds[i]!, s[i]!); |
| 2446 | return out; |
| 2447 | } |
| 2448 | |
| 2449 | /** |
| 2450 | * Handle codegraph_explore — deep exploration in a single call |
no test coverage detected