@zuvia-software-solutions/code-mapper 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -259,52 +259,91 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
259
259
  catch (err) {
260
260
  console.error(`Code Mapper: refresh tsgo failed: ${err instanceof Error ? err.message : err}`);
261
261
  }
262
- if (tsgoReady) {
263
- // Build the set of dirty file relative paths for Phase 5 filtering
264
- const dirtyFilePaths = new Set(dirtyFiles.map(f => f.relativePath));
265
- // Wrap Phase 4 + 5 in a transaction for performance
262
+ // Phase 4: Resolve call edges from dirty files
263
+ // Always runs tsgo provides 0.99 confidence, heuristic fallback provides 0.5-0.95
264
+ if (callSites.length > 0) {
266
265
  db.exec('BEGIN');
267
266
  try {
268
- // Notify tsgo about changed files so it has fresh state
269
- for (const entry of filesToProcess) {
270
- const absPath = path.resolve(repoPath, entry.relativePath);
271
- if (isTypeScriptOrJavaScript(entry.relativePath)) {
272
- await tsgoService.notifyFileChanged(absPath);
267
+ const { findNodesByName } = await import('../db/adapter.js');
268
+ // Notify tsgo about changed files if available
269
+ if (tsgoReady) {
270
+ for (const entry of filesToProcess) {
271
+ const absPath = path.resolve(repoPath, entry.relativePath);
272
+ if (isTypeScriptOrJavaScript(entry.relativePath)) {
273
+ await tsgoService.notifyFileChanged(absPath);
274
+ }
273
275
  }
274
276
  }
275
- // Phase 4: Resolve call edges from dirty files
276
277
  console.error(`Code Mapper: refresh Phase 4 — ${callSites.length} call sites to resolve`);
277
- if (callSites.length > 0) {
278
- for (const callSite of callSites) {
279
- if (!isTypeScriptOrJavaScript(callSite.filePath))
280
- continue;
281
- const def = await tsgoService.resolveDefinition(callSite.absPath, callSite.line, callSite.character);
282
- if (!def)
283
- continue;
284
- // Find the source node (enclosing function of the call site)
285
- const sourceNode = findEnclosingNode(db, callSite.filePath, callSite.line);
286
- if (!sourceNode)
287
- continue;
288
- // Find the target node (the definition the call resolves to)
289
- const targetNode = findNodeByFileAndLine(db, def.filePath, def.line);
290
- if (!targetNode)
291
- continue;
292
- // Skip self-edges
293
- if (sourceNode.id === targetNode.id)
294
- continue;
295
- const edgeId = toEdgeId(`${sourceNode.id}_CALLS_${targetNode.id}`);
296
- insertEdge(db, {
297
- id: edgeId,
298
- sourceId: sourceNode.id,
299
- targetId: targetNode.id,
300
- type: 'CALLS',
301
- confidence: 0.99,
302
- reason: 'tsgo-lsp',
303
- callLine: callSite.line,
304
- });
305
- edgesInserted++;
278
+ for (const callSite of callSites) {
279
+ const sourceNode = findEnclosingNode(db, callSite.filePath, callSite.line);
280
+ if (!sourceNode)
281
+ continue;
282
+ let targetNode;
283
+ let confidence = 0.5;
284
+ let reason = 'global';
285
+ // Try tsgo first for TS/JS files
286
+ if (tsgoReady && isTypeScriptOrJavaScript(callSite.filePath)) {
287
+ try {
288
+ const def = await tsgoService.resolveDefinition(callSite.absPath, callSite.line, callSite.character);
289
+ if (def) {
290
+ targetNode = findNodeByFileAndLine(db, def.filePath, def.line);
291
+ if (targetNode) {
292
+ confidence = 0.99;
293
+ reason = 'tsgo-lsp';
294
+ }
295
+ }
296
+ }
297
+ catch { }
306
298
  }
299
+ // Heuristic fallback: name-based lookup in DB
300
+ if (!targetNode) {
301
+ const candidates = findNodesByName(db, callSite.name, undefined, 5);
302
+ const sameFile = candidates.find(c => c.filePath === callSite.filePath);
303
+ if (sameFile) {
304
+ targetNode = sameFile;
305
+ confidence = 0.95;
306
+ reason = 'same-file';
307
+ }
308
+ else if (candidates.length === 1) {
309
+ targetNode = candidates[0];
310
+ confidence = 0.9;
311
+ reason = 'import-resolved';
312
+ }
313
+ else if (candidates.length > 0) {
314
+ targetNode = candidates[0];
315
+ confidence = 0.5;
316
+ reason = 'global';
317
+ }
318
+ }
319
+ if (!targetNode)
320
+ continue;
321
+ if (sourceNode.id === targetNode.id)
322
+ continue;
323
+ const edgeId = toEdgeId(`${sourceNode.id}_CALLS_${targetNode.id}`);
324
+ insertEdge(db, {
325
+ id: edgeId,
326
+ sourceId: sourceNode.id,
327
+ targetId: targetNode.id,
328
+ type: 'CALLS',
329
+ confidence,
330
+ reason,
331
+ callLine: callSite.line,
332
+ });
333
+ edgesInserted++;
307
334
  }
335
+ db.exec('COMMIT');
336
+ }
337
+ catch (err) {
338
+ db.exec('ROLLBACK');
339
+ console.error(`Code Mapper: Phase 4 call resolution failed: ${err instanceof Error ? err.message : err}`);
340
+ }
341
+ }
342
+ // Phase 5: Repair cross-file edges (tsgo only — needs findReferences)
343
+ if (tsgoReady) {
344
+ const dirtyFilePaths = new Set(dirtyFiles.map(f => f.relativePath));
345
+ db.exec('BEGIN');
346
+ try {
308
347
  // Phase 5: Repair cross-file edges
309
348
  // For each newly inserted definition, find references from UNCHANGED files
310
349
  // and create CALLS edges so the graph stays consistent.
@@ -59,11 +59,16 @@ const processParsingWithWorkers = async (graph, files, symbolTable, _astCache, w
59
59
  ...(sym.parameterTypes !== undefined ? { parameterTypes: sym.parameterTypes } : {}),
60
60
  });
61
61
  }
62
- allImports.push(...result.imports);
63
- allCalls.push(...result.calls);
64
- allHeritage.push(...result.heritage);
65
- allRoutes.push(...result.routes);
66
- allConstructorBindings.push(...result.constructorBindings);
62
+ for (const item of result.imports)
63
+ allImports.push(item);
64
+ for (const item of result.calls)
65
+ allCalls.push(item);
66
+ for (const item of result.heritage)
67
+ allHeritage.push(item);
68
+ for (const item of result.routes)
69
+ allRoutes.push(item);
70
+ for (const item of result.constructorBindings)
71
+ allConstructorBindings.push(item);
67
72
  }
68
73
  // Merge and log skipped languages
69
74
  const skippedLanguages = new Map();
@@ -201,9 +201,12 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
201
201
  });
202
202
  }, repoPath, importCtx);
203
203
  // COLLECT calls for deferred resolution (don't resolve yet — callee may be in later chunk)
204
- allExtractedCalls.push(...chunkWorkerData.calls);
204
+ // Use loop instead of spread to avoid stack overflow on large codebases (100K+ calls)
205
+ for (const call of chunkWorkerData.calls)
206
+ allExtractedCalls.push(call);
205
207
  if (chunkWorkerData.constructorBindings) {
206
- allConstructorBindings.push(...chunkWorkerData.constructorBindings);
208
+ for (const cb of chunkWorkerData.constructorBindings)
209
+ allConstructorBindings.push(cb);
207
210
  }
208
211
  // Heritage + Routes can resolve per-chunk (class-level, usually same-file)
209
212
  await Promise.all([
@@ -1072,15 +1072,24 @@ let accumulated = {
1072
1072
  imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
1073
1073
  };
1074
1074
  let cumulativeProcessed = 0;
1075
+ /** Append src arrays into target without spread (avoids stack overflow on large codebases) */
1075
1076
  const mergeResult = (target, src) => {
1076
- target.nodes.push(...src.nodes);
1077
- target.relationships.push(...src.relationships);
1078
- target.symbols.push(...src.symbols);
1079
- target.imports.push(...src.imports);
1080
- target.calls.push(...src.calls);
1081
- target.heritage.push(...src.heritage);
1082
- target.routes.push(...src.routes);
1083
- target.constructorBindings.push(...src.constructorBindings);
1077
+ for (const item of src.nodes)
1078
+ target.nodes.push(item);
1079
+ for (const item of src.relationships)
1080
+ target.relationships.push(item);
1081
+ for (const item of src.symbols)
1082
+ target.symbols.push(item);
1083
+ for (const item of src.imports)
1084
+ target.imports.push(item);
1085
+ for (const item of src.calls)
1086
+ target.calls.push(item);
1087
+ for (const item of src.heritage)
1088
+ target.heritage.push(item);
1089
+ for (const item of src.routes)
1090
+ target.routes.push(item);
1091
+ for (const item of src.constructorBindings)
1092
+ target.constructorBindings.push(item);
1084
1093
  for (const [lang, count] of Object.entries(src.skippedLanguages)) {
1085
1094
  target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
1086
1095
  }
@@ -39,6 +39,8 @@ export declare class TsgoService {
39
39
  * @typescript/native-preview is not installed or tsgo fails to start.
40
40
  */
41
41
  start(): Promise<boolean>;
42
+ /** Get the resolved project root (where tsconfig.json was found) */
43
+ getProjectRoot(): string;
42
44
  /** Whether the server is running and ready for queries */
43
45
  isReady(): boolean;
44
46
  /** Resolve what a symbol at a given position points to (go-to-definition) */
@@ -59,6 +59,10 @@ export class TsgoService {
59
59
  this.initPromise = this.doStart();
60
60
  return this.initPromise;
61
61
  }
62
+ /** Get the resolved project root (where tsconfig.json was found) */
63
+ getProjectRoot() {
64
+ return this.projectRoot;
65
+ }
62
66
  /** Whether the server is running and ready for queries */
63
67
  isReady() {
64
68
  return this.ready;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",