Преглед изворни кода

refactor: Trust Claude's seed picks, only add bridge nodes

Instead of expanding all callers/callees of seeds (which pulls in noise
from hub nodes like getSession), now:
1. Find direct edges between Claude's seeds
2. Only add non-seed nodes if they bridge 2+ isolated seeds
3. Cross-connection pass discovers hidden edges between result nodes
4. No more unrelated callers of hub nodes polluting the graph

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Colby McHenry пре 3 месеци
родитељ
комит
c433e7d1a0
1 измењених фајлова са 43 додато и 63 уклоњено
  1. 43 63
      src/visualizer/server.ts

+ 43 - 63
src/visualizer/server.ts

@@ -310,10 +310,11 @@ ${symbolIndex}`;
         const stems = keywords.map(kw => kw.length > 5 ? kw.slice(0, Math.max(4, Math.ceil(kw.length * 0.5))) : kw);
         const uniqueStems = [...new Set(stems)];
 
-        const isRelevant = (node: Node): boolean => {
+        const _isRelevant = (node: Node): boolean => {
           const haystack = `${node.name} ${node.filePath} ${node.qualifiedName}`.toLowerCase();
           return uniqueStems.some(stem => haystack.includes(stem));
         };
+        void _isRelevant; // Used by keyword fallback when Claude is unavailable
 
         // Step 1: Find seed nodes
         const seedMap = new Map<string, Node>();
@@ -370,78 +371,57 @@ ${symbolIndex}`;
           if (!edgeSet.has(ek)) { edgeSet.add(ek); edgeList.push(edge); }
         };
 
-        // Step 2: For each seed, get callers/callees (depth 1)
-        // Only keep neighbors that are relevant or connect to other seeds
-        // Fall back to top-3 non-relevant only if seed has NO relevant neighbors
+        // Step 2: Find edges between seeds (trust Claude's picks)
+        // Only add non-seed nodes if they bridge two seeds
         for (const [seedId] of seedMap) {
-          if (nodeMap.size >= maxNodes) break;
-          const callers = this.cg.getCallers(seedId, 1);
+          // Check if this seed directly connects to another seed
           const callees = this.cg.getCallees(seedId, 1);
-          const neighbors = [...callers, ...callees];
-
-          const relevant: typeof neighbors = [];
-          const irrelevant: typeof neighbors = [];
-
-          for (const item of neighbors) {
-            if (seedMap.has(item.node.id) || isRelevant(item.node)) {
-              relevant.push(item);
-            } else {
-              irrelevant.push(item);
+          const callers = this.cg.getCallers(seedId, 1);
+          for (const item of [...callees, ...callers]) {
+            if (seedMap.has(item.node.id)) {
+              addEdge(item.edge);
             }
           }
+        }
 
-          // Always add relevant neighbors
-          for (const item of relevant) {
-            if (nodeMap.size >= maxNodes && !nodeMap.has(item.node.id)) continue;
-            nodeMap.set(item.node.id, item.node);
-            addEdge(item.edge);
-          }
+        // Step 3: Bridge pass — for isolated seeds, find shared callees
+        // that connect them to other seeds or to each other
+        const connectedAfterDirect = new Set<string>();
+        for (const e of edgeList) {
+          connectedAfterDirect.add(e.source);
+          connectedAfterDirect.add(e.target);
+        }
 
-          // For seeds with no relevant neighbors, add top-3 callees
-          // so they're not completely floating
-          if (relevant.length === 0 && irrelevant.length > 0) {
-            for (const item of irrelevant.slice(0, 3)) {
-              if (nodeMap.size >= maxNodes) break;
-              // Only add if it connects to another node already in the graph
-              if (nodeMap.has(item.node.id)) {
-                addEdge(item.edge);
-              }
+        const isolatedSeeds = Array.from(seedMap.keys()).filter(id => !connectedAfterDirect.has(id));
+
+        // Collect all callees/callers of isolated seeds to find bridges
+        const bridgeCandidates = new Map<string, { node: Node; connectedSeeds: Set<string>; edges: Edge[] }>();
+        for (const seedId of isolatedSeeds) {
+          const callees = this.cg.getCallees(seedId, 1);
+          const callers = this.cg.getCallers(seedId, 1);
+          for (const item of [...callees, ...callers]) {
+            const candidate = bridgeCandidates.get(item.node.id);
+            if (candidate) {
+              candidate.connectedSeeds.add(seedId);
+              candidate.edges.push(item.edge);
+            } else {
+              bridgeCandidates.set(item.node.id, {
+                node: item.node,
+                connectedSeeds: new Set([seedId]),
+                edges: [item.edge],
+              });
             }
           }
         }
 
-        // Step 3: Bridge pass — find shared callees between isolated seeds
-        // If two seeds both call the same function, add it as a bridge node
-        const isolatedSeeds = Array.from(seedMap.keys()).filter(id => {
-          return !edgeList.some(e => e.source === id || e.target === id);
-        });
+        // Add bridges that connect 2+ seeds, or connect an isolated seed to a connected one
+        for (const [bridgeId, { node: bridgeNode, connectedSeeds, edges }] of bridgeCandidates) {
+          const connectsToGraph = connectedAfterDirect.has(bridgeId) || seedMap.has(bridgeId);
+          const connectsMultiple = connectedSeeds.size >= 2;
 
-        if (isolatedSeeds.length > 1) {
-          // Collect callees for each isolated seed
-          const seedCallees = new Map<string, { node: Node; seeds: string[] }>();
-          for (const seedId of isolatedSeeds) {
-            const callees = this.cg.getCallees(seedId, 1);
-            for (const item of callees) {
-              const existing = seedCallees.get(item.node.id);
-              if (existing) {
-                existing.seeds.push(seedId);
-              } else {
-                seedCallees.set(item.node.id, { node: item.node, seeds: [seedId] });
-              }
-            }
-          }
-          // Add bridge nodes that connect 2+ isolated seeds
-          for (const [bridgeId, { node: bridgeNode, seeds }] of seedCallees) {
-            if (seeds.length >= 2 && nodeMap.size < maxNodes) {
-              nodeMap.set(bridgeId, bridgeNode);
-              // Add edges from each seed to the bridge
-              for (const seedId of seeds) {
-                const callees = this.cg.getCallees(seedId, 1);
-                for (const item of callees) {
-                  if (item.node.id === bridgeId) addEdge(item.edge);
-                }
-              }
-            }
+          if ((connectsMultiple || connectsToGraph) && nodeMap.size < maxNodes) {
+            nodeMap.set(bridgeId, bridgeNode);
+            for (const edge of edges) addEdge(edge);
           }
         }
 
@@ -456,7 +436,7 @@ ${symbolIndex}`;
           }
         }
 
-        // Step 5: Filter edges and remove isolated non-root nodes
+        // Step 5: Filter and clean up
         const finalEdges = edgeList.filter(e => nodeMap.has(e.source) && nodeMap.has(e.target));
 
         const connectedIds = new Set<string>();