@zuvia-software-solutions/code-mapper 2.3.3 → 2.3.4

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.
@@ -739,7 +739,7 @@ function isTypeScriptOrJavaScript(filePath) {
739
739
  *
740
740
  * Call key format: "sourceId\0calledName\0callLine" — unique per call site.
741
741
  */
742
- async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath) {
742
+ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath, onProgress) {
743
743
  const results = new Map();
744
744
  // Collect eligible calls (TS/JS files with line+column info)
745
745
  const eligible = [];
@@ -764,13 +764,46 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
764
764
  }
765
765
  list.push(call);
766
766
  }
767
+ // Pre-filter: skip free-form calls ONLY when the function name is unambiguous
768
+ // in the symbol table. Heuristic resolves unique names perfectly.
769
+ // Ambiguous names (multiple symbols with same name) need tsgo for disambiguation.
770
+ const tsgoEligible = [];
771
+ let skippedHeuristic = 0;
772
+ for (const call of eligible) {
773
+ if (call.callForm === 'free' || call.callForm === undefined) {
774
+ const resolved = ctx.resolve(call.calledName, call.filePath);
775
+ // Unique match — heuristic handles this at high confidence
776
+ if (resolved && resolved.candidates.length === 1) {
777
+ skippedHeuristic++;
778
+ continue;
779
+ }
780
+ }
781
+ tsgoEligible.push(call);
782
+ }
783
+ // Regroup filtered calls by file
784
+ const tsgoByFile = new Map();
785
+ for (const call of tsgoEligible) {
786
+ let list = tsgoByFile.get(call.filePath);
787
+ if (!list) {
788
+ list = [];
789
+ tsgoByFile.set(call.filePath, list);
790
+ }
791
+ list.push(call);
792
+ }
767
793
  let resolved = 0;
768
794
  let failed = 0;
769
795
  const t0 = Date.now();
770
- console.error(`Code Mapper: tsgo resolving ${eligible.length} calls across ${byFile.size} files...`);
771
- for (const [filePath, calls] of byFile) {
796
+ console.error(`Code Mapper: tsgo resolving ${tsgoEligible.length} calls across ${tsgoByFile.size} files (skipped ${skippedHeuristic} heuristic-resolvable)...`);
797
+ let tsgoFilesProcessed = 0;
798
+ const tsgoTotalFiles = tsgoByFile.size;
799
+ for (const [filePath, calls] of tsgoByFile) {
800
+ tsgoFilesProcessed++;
801
+ if (tsgoFilesProcessed % 25 === 0) {
802
+ onProgress?.(tsgoFilesProcessed, tsgoTotalFiles);
803
+ await yieldToEventLoop();
804
+ }
772
805
  const absFilePath = path.resolve(repoPath, filePath);
773
- // Resolve all calls in this file sequentially
806
+ // Sequential LSP requests tsgo processes over stdio, concurrent floods cause hangs
774
807
  for (const call of calls) {
775
808
  try {
776
809
  const def = await tsgoService.resolveDefinition(absFilePath, call.callLine - 1, call.callColumn);
@@ -806,10 +839,7 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
806
839
  }
807
840
  }
808
841
  if (bestMatch) {
809
- // Drop self-referencing tsgo edges: these come from property access
810
- // on parameters (req.params, res.json) that tsgo resolves back to
811
- // the enclosing function's definition. Legitimate recursion is captured
812
- // by the heuristic path (free-form calls to the function's own name).
842
+ // Drop self-referencing tsgo edges
813
843
  if (bestMatch.nodeId === call.sourceId) {
814
844
  failed++;
815
845
  continue;
@@ -871,13 +901,13 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
871
901
  // Batch pre-resolve via tsgo LSP (highest confidence, TS/JS only)
872
902
  let tsgoResolved;
873
903
  if (tsgoService?.isReady() && repoPath) {
874
- tsgoResolved = await batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath);
904
+ tsgoResolved = await batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath, onProgress);
875
905
  }
876
906
  const totalFiles = byFile.size;
877
907
  let filesProcessed = 0;
878
908
  for (const [filePath, calls] of byFile) {
879
909
  filesProcessed++;
880
- if (filesProcessed % 100 === 0) {
910
+ if (filesProcessed % 25 === 0) {
881
911
  onProgress?.(filesProcessed, totalFiles);
882
912
  await yieldToEventLoop();
883
913
  }
@@ -103,7 +103,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
103
103
  if (totalParseable === 0) {
104
104
  onProgress({
105
105
  phase: 'parsing',
106
- percent: 82,
106
+ percent: 70,
107
107
  message: 'No parseable files found — skipping parsing phase',
108
108
  stats: { filesProcessed: 0, totalFiles: 0, nodesCreated: graph.nodeCount },
109
109
  });
@@ -186,7 +186,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
186
186
  const parseStart = Date.now();
187
187
  const chunkWorkerData = await processParsing(graph, chunkFiles, symbolTable, astCache, (current, _total, filePath) => {
188
188
  const globalCurrent = filesParsedSoFar + current;
189
- const parsingProgress = 20 + ((globalCurrent / totalParseable) * 62);
189
+ const parsingProgress = 20 + ((globalCurrent / totalParseable) * 50);
190
190
  onProgress({
191
191
  phase: 'parsing',
192
192
  percent: Math.round(parsingProgress),
@@ -197,7 +197,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
197
197
  }, workerPool);
198
198
  const parseMs = Date.now() - parseStart;
199
199
  verbose(`[parse] chunk ${chunkIdx + 1}/${numChunks}: parsed ${parseMs}ms (${memoryGuard.summary()})`);
200
- const chunkBasePercent = 20 + ((filesParsedSoFar / totalParseable) * 62);
200
+ const chunkBasePercent = 20 + ((filesParsedSoFar / totalParseable) * 50);
201
201
  if (chunkWorkerData) {
202
202
  // Resolve imports per-chunk (file-level, doesn't need full symbol table)
203
203
  await processImportsFromExtracted(graph, allPathObjects, chunkWorkerData.imports, ctx, (current, total) => {
@@ -274,10 +274,11 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
274
274
  }
275
275
  let tsgoWasUsed = false;
276
276
  // Phase B: Resolve ALL deferred calls now that every symbol is registered
277
+ // Progress range: 70-82% (advancing, not fixed)
277
278
  if (allExtractedCalls.length > 0) {
278
279
  onProgress({
279
- phase: 'parsing',
280
- percent: 82,
280
+ phase: 'calls',
281
+ percent: 70,
281
282
  message: `Resolving ${allExtractedCalls.length} calls across all files...`,
282
283
  stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
283
284
  });
@@ -297,9 +298,10 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
297
298
  }
298
299
  try {
299
300
  await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total) => {
301
+ const callPercent = 70 + Math.round((current / Math.max(total, 1)) * 12);
300
302
  onProgress({
301
- phase: 'parsing',
302
- percent: 82,
303
+ phase: 'calls',
304
+ percent: callPercent,
303
305
  message: `Resolving calls: ${current}/${total} files...`,
304
306
  stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
305
307
  });
@@ -337,7 +339,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
337
339
  // Phase 4.5b: Method Resolution Order
338
340
  onProgress({
339
341
  phase: 'parsing',
340
- percent: 81,
342
+ percent: 82,
341
343
  message: 'Computing method resolution order...',
342
344
  stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
343
345
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
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",