* 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>,
)
| 2551 | * it's a few hundred nodes × ~25 iterations — negligible cost. |
| 2552 | */ |
| 2553 | private computeGraphRelevance( |
| 2554 | nodeIds: string[], |
| 2555 | edges: Edge[], |
| 2556 | seedIds: Set<string>, |
| 2557 | ): Map<string, number> { |
| 2558 | const out = new Map<string, number>(); |
| 2559 | const n = nodeIds.length; |
| 2560 | if (n === 0) return out; |
| 2561 | const idx = new Map<string, number>(); |
| 2562 | for (let i = 0; i < n; i++) idx.set(nodeIds[i]!, i); |
| 2563 | |
| 2564 | const RANK_EDGES = new Set<string>([ |
| 2565 | 'calls', 'references', 'extends', 'implements', 'overrides', |
| 2566 | 'instantiates', 'returns', 'type_of', 'imports', |
| 2567 | ]); |
| 2568 | const adj: number[][] = Array.from({ length: n }, () => []); |
| 2569 | for (const e of edges) { |
| 2570 | if (!RANK_EDGES.has(e.kind)) continue; |
| 2571 | const i = idx.get(e.source); |
| 2572 | const j = idx.get(e.target); |
| 2573 | if (i === undefined || j === undefined || i === j) continue; |
| 2574 | adj[i]!.push(j); |
| 2575 | adj[j]!.push(i); // undirected — reachable either direction |
| 2576 | } |
| 2577 | |
| 2578 | // Restart vector: uniform over seeds present in the candidate set. (Falls |
| 2579 | // back to uniform-over-all if no seed landed in the set, so we never return |
| 2580 | // all-zero.) |
| 2581 | const r = new Array<number>(n).fill(0); |
| 2582 | let rsum = 0; |
| 2583 | for (const id of seedIds) { |
| 2584 | const i = idx.get(id); |
| 2585 | if (i !== undefined) { r[i] = 1; rsum += 1; } |
| 2586 | } |
| 2587 | if (rsum === 0) { for (let i = 0; i < n; i++) r[i] = 1; rsum = n; } |
| 2588 | for (let i = 0; i < n; i++) r[i]! /= rsum; |
| 2589 | |
| 2590 | const alpha = 0.25; |
| 2591 | let s = r.slice(); |
| 2592 | for (let iter = 0; iter < 25; iter++) { |
| 2593 | const next = new Array<number>(n).fill(0); |
| 2594 | for (let i = 0; i < n; i++) { |
| 2595 | const si = s[i]!; |
| 2596 | if (si === 0) continue; |
| 2597 | const d = adj[i]!.length; |
| 2598 | if (d === 0) { next[i]! += si; continue; } // dangling: keep its mass |
| 2599 | const share = si / d; |
| 2600 | for (const j of adj[i]!) next[j]! += share; |
| 2601 | } |
| 2602 | for (let i = 0; i < n; i++) s[i] = (1 - alpha) * next[i]! + alpha * r[i]!; |
| 2603 | } |
| 2604 | for (let i = 0; i < n; i++) out.set(nodeIds[i]!, s[i]!); |
| 2605 | return out; |
| 2606 | } |
| 2607 | |
| 2608 | /** |
| 2609 | * Handle codegraph_explore — deep exploration in a single call |
no test coverage detected