fix: Prevent false cross-module edges in name-based resolution
Name matching was creating false `calls` edges between unrelated modules in monorepos because `findBestMatch()` had no concept of directory proximity — functions with common names (e.g. `navigate`) in different apps scored identically and resolved to whichever came first. Adds path proximity scoring (shared directory segments) so same-module candidates strongly win over cross-boundary ones, and lowers confidence for distant matches so import-based resolution takes precedence. Closes #67 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
960bed2253
commit
584bd94ecc
@@ -33,10 +33,13 @@ export function matchByExactName(
|
||||
// Multiple matches - try to narrow down
|
||||
const bestMatch = findBestMatch(ref, candidates, context);
|
||||
if (bestMatch) {
|
||||
// Lower confidence when the match is from a distant/unrelated module
|
||||
const proximity = computePathProximity(ref.filePath, bestMatch.filePath);
|
||||
const confidence = proximity >= 30 ? 0.7 : 0.4;
|
||||
return {
|
||||
original: ref,
|
||||
targetNodeId: bestMatch.id,
|
||||
confidence: 0.7,
|
||||
confidence,
|
||||
resolvedBy: 'exact-match',
|
||||
};
|
||||
}
|
||||
@@ -133,6 +136,28 @@ export function matchMethodCall(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute directory proximity between two file paths.
|
||||
* Returns a score based on the number of shared directory segments.
|
||||
* Higher score = closer in directory tree.
|
||||
*/
|
||||
function computePathProximity(filePath1: string, filePath2: string): number {
|
||||
const dir1 = filePath1.split('/').slice(0, -1);
|
||||
const dir2 = filePath2.split('/').slice(0, -1);
|
||||
|
||||
let shared = 0;
|
||||
for (let i = 0; i < Math.min(dir1.length, dir2.length); i++) {
|
||||
if (dir1[i] === dir2[i]) {
|
||||
shared++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Each shared directory segment contributes 15 points, capped at 80
|
||||
return Math.min(shared * 15, 80);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best matching node when there are multiple candidates
|
||||
*/
|
||||
@@ -143,9 +168,10 @@ function findBestMatch(
|
||||
): Node | null {
|
||||
// Prioritization rules:
|
||||
// 1. Same file > different file
|
||||
// 2. Same language > different language
|
||||
// 3. Functions/methods > classes/types (for call references)
|
||||
// 4. Exported > non-exported
|
||||
// 2. Directory proximity (same module/package > different module)
|
||||
// 3. Same language > different language
|
||||
// 4. Functions/methods > classes/types (for call references)
|
||||
// 5. Exported > non-exported
|
||||
|
||||
let bestScore = -1;
|
||||
let bestNode: Node | null = null;
|
||||
@@ -158,6 +184,9 @@ function findBestMatch(
|
||||
score += 100;
|
||||
}
|
||||
|
||||
// Directory proximity bonus — strongly prefer same module/package
|
||||
score += computePathProximity(ref.filePath, candidate.filePath);
|
||||
|
||||
// Same language bonus
|
||||
if (candidate.language === ref.language) {
|
||||
score += 50;
|
||||
|
||||
Reference in New Issue
Block a user