impact tool test

This commit is contained in:
abhigyanpatwari 2026-01-27 23:05:34 +05:30
parent c2ce19f7a4
commit 32c3544d0d
4 changed files with 182 additions and 17 deletions

View file

@ -11,16 +11,18 @@ export type ImportMap = Map<string, Set<string>>;
export const createImportMap = (): ImportMap => new Map();
// Helper: Resolve relative paths (e.g. "../utils" -> "src/lib/utils.ts")
// Helper: Resolve import paths (relative and absolute/package-style)
const resolveImportPath = (
currentFile: string,
importPath: string,
allFiles: Set<string>
allFiles: Set<string>,
allFileList: string[],
resolveCache: Map<string, string | null>
): string | null => {
// 1. Handle non-relative imports (libraries like 'react')
if (!importPath.startsWith('.')) return null; // We skip node_modules for now
const cacheKey = `${currentFile}::${importPath}`;
if (resolveCache.has(cacheKey)) return resolveCache.get(cacheKey) ?? null;
// 2. Resolve '..' and '.'
// 1. Resolve '..' and '.' for relative imports
const currentDir = currentFile.split('/').slice(0, -1);
const parts = importPath.split('/');
@ -35,7 +37,7 @@ const resolveImportPath = (
const basePath = currentDir.join('/');
// 3. Try extensions for all supported languages
// 2. Try extensions for all supported languages
const extensions = [
'',
// TypeScript/JavaScript
@ -54,11 +56,56 @@ const resolveImportPath = (
'.rs', '/mod.rs'
];
for (const ext of extensions) {
const candidate = basePath + ext;
if (allFiles.has(candidate)) return candidate;
if (importPath.startsWith('.')) {
for (const ext of extensions) {
const candidate = basePath + ext;
if (allFiles.has(candidate)) {
resolveCache.set(cacheKey, candidate);
return candidate;
}
}
resolveCache.set(cacheKey, null);
return null;
}
// 3. Handle absolute/package imports (Java, Go, Python, etc.)
if (importPath.endsWith('.*')) {
resolveCache.set(cacheKey, null);
return null;
}
const pathLike = importPath.includes('/')
? importPath
: importPath.replace(/\./g, '/');
const pathParts = pathLike.split('/').filter(Boolean);
// Normalize all file paths to forward slashes for matching
const normalizedFileList = allFileList.map(p => p.replace(/\\/g, '/'));
for (let i = 0; i < pathParts.length; i++) {
const suffix = pathParts.slice(i).join('/');
for (const ext of extensions) {
const suffixWithExt = suffix + ext;
// Require path separator before match to avoid false positives like "View.java" matching "RootView.java"
const suffixPattern = '/' + suffixWithExt;
const matchIdx = normalizedFileList.findIndex(filePath =>
filePath.endsWith(suffixPattern) || filePath.toLowerCase().endsWith(suffixPattern.toLowerCase())
);
if (matchIdx !== -1) {
const match = allFileList[matchIdx];
if (import.meta.env.DEV) {
console.log(`📦 Import resolved: ${importPath} → ${match}`);
}
resolveCache.set(cacheKey, match);
return match;
}
}
}
if (import.meta.env.DEV && pathParts.length > 0) {
console.log(`⚠️ Import unresolved: ${importPath} (tried suffixes from ${pathParts.join('/')})`);
}
resolveCache.set(cacheKey, null);
return null;
};
@ -72,6 +119,12 @@ export const processImports = async (
// Create a Set of all file paths for fast lookup during resolution
const allFilePaths = new Set(files.map(f => f.path));
const parser = await loadParser();
const resolveCache = new Map<string, string | null>();
const allFileList = files.map(f => f.path);
// Track import statistics
let totalImportsFound = 0;
let totalImportsResolved = 0;
for (let i = 0; i < files.length; i++) {
const file = files[i];
@ -102,6 +155,13 @@ export const processImports = async (
try {
query = parser.getLanguage().query(queryStr);
matches = query.matches(tree.rootNode);
if (import.meta.env.DEV && language === 'java') {
const importMatches = matches.filter(m => m.captures.some(c => c.name === 'import'));
if (importMatches.length > 0) {
console.log(`📋 Java file ${file.path}: ${importMatches.length} import matches found`);
}
}
} catch (queryError: any) {
// Detailed debug logging for query failures
console.group(`🔴 Query Error: ${file.path}`);
@ -123,13 +183,29 @@ export const processImports = async (
if (captureMap['import']) {
const sourceNode = captureMap['import.source'];
if (!sourceNode) return;
if (!sourceNode) {
if (import.meta.env.DEV) {
console.log(`⚠️ Import captured but no source node in ${file.path}`);
}
return;
}
// Clean path (remove quotes)
const rawImportPath = sourceNode.text.replace(/['"]/g, '');
totalImportsFound++;
if (import.meta.env.DEV && language === 'java') {
console.log(`🔍 Java import found in ${file.path}: ${rawImportPath}`);
}
// Resolve to actual file in the system
const resolvedPath = resolveImportPath(file.path, rawImportPath, allFilePaths);
const resolvedPath = resolveImportPath(
file.path,
rawImportPath,
allFilePaths,
allFileList,
resolveCache
);
if (resolvedPath) {
// A. Update Graph (File -> IMPORTS -> File)
@ -137,6 +213,8 @@ export const processImports = async (
const targetId = generateId('File', resolvedPath);
const relId = generateId('IMPORTS', `${file.path}->${resolvedPath}`);
totalImportsResolved++;
graph.addRelationship({
id: relId,
sourceId,
@ -161,6 +239,10 @@ export const processImports = async (
tree.delete();
}
}
if (import.meta.env.DEV) {
console.log(`📊 Import processing complete: ${totalImportsResolved}/${totalImportsFound} imports resolved to graph edges`);
}
};

View file

@ -123,6 +123,16 @@ export const runPipelineFromFiles = async (
stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
});
});
// Debug: Count IMPORTS relationships
if (import.meta.env.DEV) {
const importsCount = graph.relationships.filter(r => r.type === 'IMPORTS').length;
console.log(`📊 Pipeline: After import phase, graph has ${importsCount} IMPORTS relationships (total: ${graph.relationshipCount})`);
if (importsCount > 0) {
const sample = graph.relationships.filter(r => r.type === 'IMPORTS').slice(0, 3);
sample.forEach(r => console.log(` Sample IMPORTS: ${r.sourceId} → ${r.targetId}`));
}
}
// Phase 5: Calls (82-98%)

View file

@ -160,8 +160,8 @@ export const JAVA_QUERIES = `
(method_declaration name: (identifier) @name) @definition.method
(constructor_declaration name: (identifier) @name) @definition.constructor
; Imports
(import_declaration (scoped_identifier) @import.source) @import
; Imports - capture any import declaration child as source
(import_declaration (_) @import.source) @import
; Calls
(method_invocation name: (identifier) @call.name) @call

View file

@ -926,12 +926,40 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
return `Could not find "${target}" in the codebase. Try using the search tool first to find the exact name.`;
}
// Use the first match
const targetNode = targetResults[0];
// Handle multiple matches - require disambiguation
const allPaths = targetResults.map((r: any) => Array.isArray(r) ? r[2] : r.filePath).filter(Boolean);
// If multiple matches and target doesn't look like a specific path, ask for clarification
if (targetResults.length > 1 && !target.includes('/')) {
return `⚠️ AMBIGUOUS TARGET: Multiple files named "${target}" found:\n\n${allPaths.map((p: string, i: number) => `${i + 1}. ${p}`).join('\n')}\n\nPlease specify which file you mean by using a more specific path, e.g.:\n- impact("${allPaths[0].split('/').slice(-3).join('/')}")\n- impact("${allPaths[1]?.split('/').slice(-3).join('/') || allPaths[0]}")`;
}
// If target contains a path, try to find matching file
let targetNode = targetResults[0];
if (target.includes('/') && targetResults.length > 1) {
const exactMatch = targetResults.find((r: any) => {
const path = Array.isArray(r) ? r[2] : r.filePath;
return path && path.toLowerCase().includes(target.toLowerCase());
});
if (exactMatch) {
targetNode = exactMatch;
} else {
// Still ambiguous even with path
return `⚠️ AMBIGUOUS TARGET: Could not uniquely match "${target}". Found:\n\n${allPaths.map((p: string, i: number) => `${i + 1}. ${p}`).join('\n')}\n\nPlease use a more specific path.`;
}
}
const targetId = Array.isArray(targetNode) ? targetNode[0] : targetNode.id;
const targetType = Array.isArray(targetNode) ? targetNode[1] : targetNode.nodeType;
const targetFilePath = Array.isArray(targetNode) ? targetNode[2] : targetNode.filePath;
if (import.meta.env.DEV) {
console.log(`🎯 Impact: Found target "${target}" → id=${targetId}, type=${targetType}, filePath=${targetFilePath}`);
}
// No more multipleMatchWarning needed - we either disambiguated or returned early
const multipleMatchWarning = '';
// For File targets, find what calls code INSIDE the file (by filePath)
// For code elements (Function, Class, etc.), use the direct id
const isFileTarget = targetType === 'File';
@ -1016,7 +1044,18 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
r.reason AS reason
LIMIT 300
`;
depthQueries.push(executeQuery(d1Query).catch(err => {
if (import.meta.env.DEV) {
console.log(`🔍 Impact d=1 query:\n${d1Query}`);
}
depthQueries.push(executeQuery(d1Query).then(results => {
if (import.meta.env.DEV) {
console.log(`📊 Impact d=1 results: ${results.length} rows`);
if (results.length > 0) {
console.log(' Sample:', results.slice(0, 3));
}
}
return results;
}).catch(err => {
if (import.meta.env.DEV) console.warn('Impact d=1 query failed:', err);
return [];
}));
@ -1174,7 +1213,38 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
const totalAffected = allNodeIds.length;
if (totalAffected === 0) {
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}). This code appears to be ${direction === 'upstream' ? 'unused (not called by anything)' : 'self-contained (no outgoing dependencies)'}.`;
if (isFileTarget) {
const escapeRegex = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const targetFileName = (targetFilePath || target).split('/').pop() || target;
const baseName = targetFileName.replace(/\.[^/.]+$/, '');
const refRegex = new RegExp(`\\b${escapeRegex(baseName)}\\b`, 'g');
const hints: Array<{ file: string; line: number; content: string }> = [];
const hintLimit = 15;
for (const [filePath, content] of fileContents.entries()) {
if (filePath === targetFilePath) continue;
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (refRegex.test(lines[i])) {
hints.push({
file: filePath,
line: i + 1,
content: lines[i].trim().slice(0, 150),
});
if (hints.length >= hintLimit) break;
}
refRegex.lastIndex = 0;
}
if (hints.length >= hintLimit) break;
}
if (hints.length > 0) {
const formatted = hints.map(h => `${h.file}:${h.line}: ${h.content}`).join('\n');
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}), but textual references were detected (graph may be incomplete):\n\n${formatted}${multipleMatchWarning}`;
}
}
return `No ${direction} dependencies found for "${target}" (types: ${activeRelTypes.join(', ')}). This code appears to be ${direction === 'upstream' ? 'unused (not called by anything)' : 'self-contained (no outgoing dependencies)'}.${multipleMatchWarning}`;
}
const depth1 = byDepth.get(1) || [];
@ -1361,6 +1431,9 @@ MATCH (n:Function {id: emb.nodeId}) RETURN n`,
// Compact footer
lines.push(`✅ GRAPH ANALYSIS COMPLETE (trusted)`);
lines.push(`⚠️ Optional: grep("${target}") for dynamic patterns`);
if (multipleMatchWarning) {
lines.push(multipleMatchWarning);
}
lines.push(``);
return lines.join('\n');