@zuvia-software-solutions/code-mapper 2.5.0 → 2.5.1

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.
@@ -795,14 +795,17 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
795
795
  }
796
796
  const t0 = Date.now();
797
797
  const skippedTotal = skippedUnambiguous + skippedBuiltin;
798
- // Adaptive parallelism
798
+ // Adaptive parallelism — conservative to avoid freezing the machine.
799
+ // Each tsgo LSP process loads the full project into memory (~1.5-3GB for
800
+ // large codebases) and pins a CPU core at 100%, so we cap aggressively.
799
801
  const osModule = await import('os');
800
802
  const cpuCount = osModule.cpus().length;
801
803
  const freeMemGB = osModule.freemem() / (1024 * 1024 * 1024);
802
- const maxByCpu = Math.max(1, Math.floor(cpuCount * 0.75));
803
- const maxByMemory = Math.max(1, Math.floor(freeMemGB / 0.5));
804
- const maxByWorkload = Math.max(1, Math.floor(tsgoByFile.size / 50));
805
- const actualWorkers = Math.min(maxByCpu, maxByMemory, maxByWorkload);
804
+ const maxByCpu = Math.max(1, Math.floor(cpuCount * 0.5));
805
+ const maxByMemory = Math.max(1, Math.floor(freeMemGB / 2)); // ~2GB per process
806
+ const maxByWorkload = Math.max(1, Math.floor(tsgoByFile.size / 100));
807
+ const HARD_CAP = 4; // never more than 4 tsgo processes regardless of hardware
808
+ const actualWorkers = Math.min(maxByCpu, maxByMemory, maxByWorkload, HARD_CAP);
806
809
  if (process.env['CODE_MAPPER_VERBOSE']) {
807
810
  console.error(`Code Mapper: tsgo resolving ${tsgoEligible.length} calls across ${tsgoByFile.size} files with ${actualWorkers} process${actualWorkers > 1 ? 'es' : ''} (skipped ${skippedTotal}: ${skippedUnambiguous} unambiguous, ${skippedBuiltin} builtin)...`);
808
811
  }
@@ -823,6 +826,9 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
823
826
  let sliceFailed = 0;
824
827
  let entry;
825
828
  while ((entry = getNextFile()) !== null) {
829
+ // Bail out early if tsgo process died — no point sending more requests
830
+ if (!service.isReady())
831
+ break;
826
832
  const [filePath, calls] = entry;
827
833
  totalFilesProcessed++;
828
834
  if (totalFilesProcessed % 25 === 0) {
@@ -260,9 +260,25 @@ export class TsgoService {
260
260
  if (msg)
261
261
  verbose('stderr:', msg);
262
262
  });
263
+ // Handle EPIPE / write errors on stdin — without this handler,
264
+ // a dead tsgo process causes an unhandled 'error' event that crashes Node.
265
+ this.process.stdin.on('error', (err) => {
266
+ verbose(`stdin error: ${err.code ?? err.message}`);
267
+ this.ready = false;
268
+ // Reject all pending requests so callers don't hang
269
+ for (const cb of this.pending.values()) {
270
+ cb({ id: -1, error: { message: `tsgo stdin error: ${err.code}` } });
271
+ }
272
+ this.pending.clear();
273
+ });
263
274
  this.process.on('exit', (code, signal) => {
264
275
  verbose(`process exited (code=${code}, signal=${signal})`);
265
276
  this.ready = false;
277
+ // Reject all pending requests on unexpected exit
278
+ for (const cb of this.pending.values()) {
279
+ cb({ id: -1, error: { message: `tsgo exited (code=${code}, signal=${signal})` } });
280
+ }
281
+ this.pending.clear();
266
282
  this.process = null;
267
283
  });
268
284
  // Initialize LSP handshake
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
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",