From 94c8d5ccaa0e69afc297b110728ea519b072fff8 Mon Sep 17 00:00:00 2001 From: Colby McHenry Date: Mon, 9 Feb 2026 23:55:00 -0600 Subject: [PATCH] Port extraction quality improvements from PR #15 - Fix arrow function extraction: explicitly call extractFunction() for arrow functions/function expressions in variable declarations instead of silently skipping them (all 6 arrow function tests now pass) - Best-candidate resolution: collect candidates from all strategies and return highest confidence match instead of first match - Fix graph traversal 'both' direction: correctly determine next node for mixed incoming/outgoing edges in BFS and DFS Co-Authored-By: Claude Opus 4.6 --- src/extraction/tree-sitter.ts | 5 +++-- src/graph/traversal.ts | 8 ++++++-- src/resolution/index.ts | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts index dabedf5..648176e 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -1294,9 +1294,10 @@ export class TreeSitterExtractor { if (nameNode) { const name = getNodeText(nameNode, this.source); - // Skip if it looks like a function (arrow function or function expression) + // Arrow functions / function expressions: extract as function instead of variable if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) { - continue; // Already handled by function extraction + this.extractFunction(valueNode); + continue; } // Capture first 100 chars of initializer for context (stored in signature for searchability) diff --git a/src/graph/traversal.ts b/src/graph/traversal.ts index 102eb80..0951fca 100644 --- a/src/graph/traversal.ts +++ b/src/graph/traversal.ts @@ -85,7 +85,9 @@ export class GraphTraverser { const adjacentEdges = this.getAdjacentEdges(node.id, opts.direction, opts.edgeKinds); for (const adjEdge of adjacentEdges) { - const nextNodeId = opts.direction === 'incoming' ? adjEdge.source : adjEdge.target; + // Determine next node: for 'both' direction, edges can be either + // incoming or outgoing, so pick whichever end is not the current node + const nextNodeId = adjEdge.source === node.id ? adjEdge.target : adjEdge.source; if (visited.has(nextNodeId)) { continue; @@ -169,7 +171,9 @@ export class GraphTraverser { const adjacentEdges = this.getAdjacentEdges(node.id, opts.direction, opts.edgeKinds); for (const edge of adjacentEdges) { - const nextNodeId = opts.direction === 'incoming' ? edge.source : edge.target; + // Determine next node: for 'both' direction, edges can be either + // incoming or outgoing, so pick whichever end is not the current node + const nextNodeId = edge.source === node.id ? edge.target : edge.source; if (visited.has(nextNodeId)) { continue; diff --git a/src/resolution/index.ts b/src/resolution/index.ts index 778c007..d3056a6 100644 --- a/src/resolution/index.ts +++ b/src/resolution/index.ts @@ -246,27 +246,36 @@ export class ReferenceResolver { return null; } - // Strategy 1: Try framework-specific resolution first + const candidates: ResolvedRef[] = []; + + // Strategy 1: Try framework-specific resolution for (const framework of this.frameworks) { const result = framework.resolve(ref, this.context); if (result) { - return result; + if (result.confidence >= 0.9) return result; // High confidence, return immediately + candidates.push(result); } } // Strategy 2: Try import-based resolution const importResult = resolveViaImport(ref, this.context); if (importResult) { - return importResult; + if (importResult.confidence >= 0.9) return importResult; + candidates.push(importResult); } // Strategy 3: Try name matching const nameResult = matchReference(ref, this.context); if (nameResult) { - return nameResult; + candidates.push(nameResult); } - return null; + if (candidates.length === 0) return null; + + // Return highest confidence candidate + return candidates.reduce((best, curr) => + curr.confidence > best.confidence ? curr : best + ); } /**