* 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>,
)
| 2434 | * it's a few hundred nodes × ~25 iterations — negligible cost. |
| 2435 | */ |
| 2436 | private computeGraphRelevance( |
| 2437 | nodeIds: string[], |
| 2438 | edges: Edge[], |
| 2439 | seedIds: Set<string>, |
| 2440 | ): Map<string, number> { |
| 2441 | const out = new Map<string, number>(); |
| 2442 | const n = nodeIds.length; |
| 2443 | if (n === 0) return out; |
| 2444 | const idx = new Map<string, number>(); |
| 2445 | for (let i = 0; i < n; i++) idx.set(nodeIds[i]!, i); |
| 2446 | |
| 2447 | const RANK_EDGES = new Set<string>([ |
| 2448 | 'calls', 'references', 'extends', 'implements', 'overrides', |
| 2449 | 'instantiates', 'returns', 'type_of', 'imports', |
| 2450 | ]); |
| 2451 | const adj: number[][] = Array.from({ length: n }, () => []); |
| 2452 | for (const e of edges) { |
| 2453 | if (!RANK_EDGES.has(e.kind)) continue; |
| 2454 | const i = idx.get(e.source); |
| 2455 | const j = idx.get(e.target); |
| 2456 | if (i === undefined || j === undefined || i === j) continue; |
| 2457 | adj[i]!.push(j); |
| 2458 | adj[j]!.push(i); // undirected — reachable either direction |
| 2459 | } |
| 2460 | |
| 2461 | // Restart vector: uniform over seeds present in the candidate set. (Falls |
| 2462 | // back to uniform-over-all if no seed landed in the set, so we never return |
| 2463 | // all-zero.) |
| 2464 | const r = new Array<number>(n).fill(0); |
| 2465 | let rsum = 0; |
| 2466 | for (const id of seedIds) { |
| 2467 | const i = idx.get(id); |
| 2468 | if (i !== undefined) { r[i] = 1; rsum += 1; } |
| 2469 | } |
| 2470 | if (rsum === 0) { for (let i = 0; i < n; i++) r[i] = 1; rsum = n; } |
| 2471 | for (let i = 0; i < n; i++) r[i]! /= rsum; |
| 2472 | |
| 2473 | const alpha = 0.25; |
| 2474 | let s = r.slice(); |
| 2475 | for (let iter = 0; iter < 25; iter++) { |
| 2476 | const next = new Array<number>(n).fill(0); |
| 2477 | for (let i = 0; i < n; i++) { |
| 2478 | const si = s[i]!; |
| 2479 | if (si === 0) continue; |
| 2480 | const d = adj[i]!.length; |
| 2481 | if (d === 0) { next[i]! += si; continue; } // dangling: keep its mass |
| 2482 | const share = si / d; |
| 2483 | for (const j of adj[i]!) next[j]! += share; |
| 2484 | } |
| 2485 | for (let i = 0; i < n; i++) s[i] = (1 - alpha) * next[i]! + alpha * r[i]!; |
| 2486 | } |
| 2487 | for (let i = 0; i < n; i++) out.set(nodeIds[i]!, s[i]!); |
| 2488 | return out; |
| 2489 | } |
| 2490 | |
| 2491 | /** |
| 2492 | * Handle codegraph_explore — deep exploration in a single call |
no test coverage detected