@zuvia-software-solutions/code-mapper 2.3.7 → 2.3.9

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.
@@ -136,10 +136,12 @@ export const analyzeCommand = async (inputPath, options) => {
136
136
  const t0Global = Date.now();
137
137
  const cpuStart = process.cpuUsage();
138
138
  let peakRssMB = 0;
139
- // Phase timing tracker — records wall time and RSS for each phase
139
+ // Phase timing tracker — records wall time, RSS, file count, and worker count per phase
140
140
  const phaseTimes = [];
141
141
  let currentPhaseName = 'init';
142
142
  let currentPhaseStart = Date.now();
143
+ let currentPhaseFiles = 0;
144
+ let currentPhaseWorkers = 0;
143
145
  const recordPhase = (nextPhase) => {
144
146
  const now = Date.now();
145
147
  const elapsed = now - currentPhaseStart;
@@ -148,10 +150,14 @@ export const analyzeCommand = async (inputPath, options) => {
148
150
  name: currentPhaseName,
149
151
  ms: elapsed,
150
152
  rssMB: Math.round(process.memoryUsage.rss() / (1024 * 1024)),
153
+ ...(currentPhaseFiles > 0 ? { fileCount: currentPhaseFiles } : {}),
154
+ ...(currentPhaseWorkers > 0 ? { workerCount: currentPhaseWorkers } : {}),
151
155
  });
152
156
  }
153
157
  currentPhaseName = nextPhase;
154
158
  currentPhaseStart = now;
159
+ currentPhaseFiles = 0;
160
+ currentPhaseWorkers = 0;
155
161
  };
156
162
  // Live resource stats for the progress bar
157
163
  const cpuCount = os.cpus().length;
@@ -164,26 +170,27 @@ export const analyzeCommand = async (inputPath, options) => {
164
170
  const cpuPct = Math.round(((cpuDelta.user + cpuDelta.system) / 1e3) / wallMs * 100);
165
171
  return `${rssMB}MB | CPU ${cpuPct}%`;
166
172
  };
167
- // Track elapsed time per phase both updateBar and the interval use
168
- // the same format so they don't flicker against each other
169
- let lastPhaseLabel = 'Initializing...';
173
+ // Track elapsed time per BASE phase (without counts) so the timer
174
+ // doesn't reset every time the count updates
175
+ let lastBasePhase = 'Initializing...';
176
+ let lastFullLabel = 'Initializing...';
170
177
  let phaseStart = Date.now();
171
- // Update bar with phase label + elapsed seconds (shown after 3s)
172
- const updateBar = (value, phaseLabel) => {
173
- if (phaseLabel !== lastPhaseLabel) {
174
- lastPhaseLabel = phaseLabel;
178
+ const updateBar = (value, phaseLabel, basePhase) => {
179
+ const base = basePhase ?? phaseLabel;
180
+ if (base !== lastBasePhase) {
181
+ lastBasePhase = base;
175
182
  phaseStart = Date.now();
176
183
  }
184
+ lastFullLabel = phaseLabel;
177
185
  const elapsed = Math.round((Date.now() - phaseStart) / 1000);
178
- const display = elapsed >= 3 ? `${phaseLabel} (${elapsed}s)` : phaseLabel;
186
+ const display = elapsed >= 1 ? `${phaseLabel} (${elapsed}s)` : phaseLabel;
179
187
  bar.update(value, { phase: display, resources: getResourceStats() });
180
188
  };
181
189
  // Tick elapsed seconds for phases with infrequent progress callbacks
182
- // (e.g. CSV streaming, FTS indexing) — uses the same display format as updateBar
183
190
  const elapsedTimer = setInterval(() => {
184
191
  const elapsed = Math.round((Date.now() - phaseStart) / 1000);
185
- if (elapsed >= 3) {
186
- bar.update({ phase: `${lastPhaseLabel} (${elapsed}s)`, resources: getResourceStats() });
192
+ if (elapsed >= 1) {
193
+ bar.update({ phase: `${lastFullLabel} (${elapsed}s)`, resources: getResourceStats() });
187
194
  }
188
195
  }, 1000);
189
196
  // Cache embeddings from existing index before rebuild
@@ -218,13 +225,29 @@ export const analyzeCommand = async (inputPath, options) => {
218
225
  recordPhase(progress.phase);
219
226
  lastPipelinePhase = progress.phase;
220
227
  }
221
- let phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
222
- if (progress.stats && progress.stats.totalFiles > 0 &&
223
- (progress.phase === 'parsing' || progress.phase === 'extracting' || progress.phase === 'calls')) {
224
- phaseLabel += ` (${progress.stats.filesProcessed.toLocaleString()}/${progress.stats.totalFiles.toLocaleString()})`;
228
+ const baseLabel = PHASE_LABELS[progress.phase] || progress.phase;
229
+ let phaseLabel = baseLabel;
230
+ if (progress.stats && progress.stats.totalFiles > 0) {
231
+ const current = progress.stats.filesProcessed;
232
+ const total = progress.stats.totalFiles;
233
+ // Track peak file count and worker count for the summary
234
+ currentPhaseFiles = Math.max(currentPhaseFiles, total);
235
+ if (progress.stats.workerCount)
236
+ currentPhaseWorkers = Math.max(currentPhaseWorkers, progress.stats.workerCount);
237
+ phaseLabel += ` (${current.toLocaleString()}/${total.toLocaleString()})`;
238
+ // Show rate (files/s) after 1s
239
+ const elapsedSec = (Date.now() - phaseStart) / 1000;
240
+ if (elapsedSec >= 1 && current > 0) {
241
+ const rate = Math.round(current / elapsedSec);
242
+ phaseLabel += ` ${rate}/s`;
243
+ }
244
+ // Show worker/process count if available
245
+ if (progress.stats.workerCount && progress.stats.workerCount > 1) {
246
+ phaseLabel += ` [${progress.stats.workerCount}p]`;
247
+ }
225
248
  }
226
249
  const scaled = Math.round(progress.percent * 0.6);
227
- updateBar(scaled, phaseLabel);
250
+ updateBar(scaled, phaseLabel, baseLabel);
228
251
  }, options?.tsgo === false ? { tsgo: false } : {});
229
252
  // Phase 2: SQLite (60-85%)
230
253
  recordPhase('sqlite');
@@ -432,7 +455,16 @@ export const analyzeCommand = async (inputPath, options) => {
432
455
  const pct = Math.round((phase.ms / totalMs) * 100);
433
456
  const name = PHASE_DISPLAY_NAMES[phase.name] || phase.name;
434
457
  const bar = pct >= 2 ? ' ' + '█'.repeat(Math.max(1, Math.round(pct / 3))) : '';
435
- console.log(` ${name.padEnd(22)} ${sec.padStart(6)}s ${String(pct).padStart(3)}% ${phase.rssMB}MB${bar}`);
458
+ // Build extra stats: rate + workers
459
+ let extra = '';
460
+ if (phase.fileCount && phase.ms > 0) {
461
+ const rate = Math.round(phase.fileCount / (phase.ms / 1000));
462
+ extra += ` ${phase.fileCount.toLocaleString()} files (${rate}/s)`;
463
+ }
464
+ if (phase.workerCount && phase.workerCount > 1) {
465
+ extra += ` [${phase.workerCount}p]`;
466
+ }
467
+ console.log(` ${name.padEnd(22)} ${sec.padStart(6)}s ${String(pct).padStart(3)}% ${phase.rssMB}MB${bar}${extra}`);
436
468
  }
437
469
  console.log(` ${'─'.repeat(50)}`);
438
470
  console.log(` ${'Total'.padEnd(22)} ${totalTime.padStart(6)}s 100% ${peakRssMB}MB peak`);
@@ -3,14 +3,14 @@ import type { KnowledgeGraph } from '../graph/types.js';
3
3
  import type { ASTCache } from './ast-cache.js';
4
4
  import type { ResolutionContext } from './resolution-context.js';
5
5
  import type { ExtractedCall, ExtractedHeritage, ExtractedRoute, FileConstructorBindings } from './workers/parse-worker.js';
6
- import type { TsgoService } from '../semantic/tsgo-service.js';
6
+ import { TsgoService } from '../semantic/tsgo-service.js';
7
7
  export declare const processCalls: (graph: KnowledgeGraph, files: {
8
8
  path: string;
9
9
  content: string;
10
10
  }[], astCache: ASTCache, ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<ExtractedHeritage[]>;
11
11
  export declare const extractReturnTypeName: (raw: string, depth?: number) => string | undefined;
12
12
  /** Resolve pre-extracted call sites from workers (no AST parsing needed) */
13
- export declare const processCallsFromExtracted: (graph: KnowledgeGraph, extractedCalls: ExtractedCall[], ctx: ResolutionContext, onProgress?: (current: number, total: number) => void, constructorBindings?: FileConstructorBindings[], tsgoService?: TsgoService | null, repoPath?: string) => Promise<void>;
13
+ export declare const processCallsFromExtracted: (graph: KnowledgeGraph, extractedCalls: ExtractedCall[], ctx: ResolutionContext, onProgress?: (current: number, total: number, workerCount?: number) => void, constructorBindings?: FileConstructorBindings[], tsgoService?: TsgoService | null, repoPath?: string) => Promise<void>;
14
14
  /** Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods */
15
15
  export declare const processRoutesFromExtracted: (graph: KnowledgeGraph, extractedRoutes: ExtractedRoute[], ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<void>;
16
16
  /**
@@ -10,6 +10,7 @@ import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop, F
10
10
  import { buildTypeEnv } from './type-env.js';
11
11
  import { getTreeSitterBufferSize } from './constants.js';
12
12
  import { callRouters } from './call-routing.js';
13
+ import { TsgoService } from '../semantic/tsgo-service.js';
13
14
  import path from 'node:path';
14
15
  /** Walk up the AST to find the enclosing function/method, or null for top-level code */
15
16
  const findEnclosingFunction = (node, filePath, ctx) => {
@@ -777,11 +778,11 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
777
778
  ]);
778
779
  // Pre-filter calls where tsgo won't add value:
779
780
  // A. Free-form calls with unambiguous name — heuristic resolves perfectly
780
- // B. Member calls with known receiver type AND unambiguous method heuristic handles
781
- // C. Member calls on built-in receivers tsgo always fails on these
781
+ // B. Member calls on built-in receivers tsgo always fails on these
782
+ // Note: member calls with known receiver types are NOT skipped tsgo provides
783
+ // compiler-verified 0.99 confidence that the heuristic can't match.
782
784
  const tsgoEligible = [];
783
785
  let skippedUnambiguous = 0;
784
- const skippedKnownType = 0;
785
786
  let skippedBuiltin = 0;
786
787
  for (const call of eligible) {
787
788
  // A. Free-form, unique name match
@@ -809,80 +810,161 @@ async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPat
809
810
  }
810
811
  list.push(call);
811
812
  }
812
- let resolved = 0;
813
- let failed = 0;
814
813
  const t0 = Date.now();
815
- const skippedTotal = skippedUnambiguous + skippedKnownType + skippedBuiltin;
816
- console.error(`Code Mapper: tsgo resolving ${tsgoEligible.length} calls across ${tsgoByFile.size} files (skipped ${skippedTotal}: ${skippedUnambiguous} unambiguous, ${skippedKnownType} known-type, ${skippedBuiltin} builtin)...`);
817
- let tsgoFilesProcessed = 0;
814
+ const skippedTotal = skippedUnambiguous + skippedBuiltin;
815
+ // Adaptive parallelism based on three constraints:
816
+ // 1. CPU: 75% of cores — parsing workers are done, leave 25% for Node.js event loop + OS
817
+ // 2. Memory: each tsgo loads the full project (~500MB estimate) — cap by free system memory
818
+ // 3. Workload: at least 50 files per process to amortize ~0.5s startup cost
819
+ const osModule = await import('os');
820
+ const cpuCount = osModule.cpus().length;
821
+ const freeMemGB = osModule.freemem() / (1024 * 1024 * 1024);
822
+ const maxByCpu = Math.max(1, Math.floor(cpuCount * 0.75));
823
+ const maxByMemory = Math.max(1, Math.floor(freeMemGB / 0.5));
824
+ const maxByWorkload = Math.max(1, Math.floor(tsgoByFile.size / 50));
825
+ const actualWorkers = Math.min(maxByCpu, maxByMemory, maxByWorkload);
826
+ if (process.env['CODE_MAPPER_VERBOSE']) {
827
+ 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)...`);
828
+ }
829
+ // Dynamic dispatch: shared queue, each process grabs the next file when done.
830
+ // Naturally self-balancing — fast processes get more work, zero idle time.
831
+ // Sort heaviest files first so they're assigned early (avoids tail latency).
832
+ const fileEntries = [...tsgoByFile.entries()];
833
+ fileEntries.sort((a, b) => b[1].length - a[1].length);
834
+ // Shared progress counter and file queue (single-threaded, no mutex needed)
835
+ let totalFilesProcessed = 0;
836
+ let nextFileIdx = 0;
818
837
  const tsgoTotalFiles = tsgoByFile.size;
819
- for (const [filePath, calls] of tsgoByFile) {
820
- tsgoFilesProcessed++;
821
- if (tsgoFilesProcessed % 25 === 0) {
822
- onProgress?.(tsgoFilesProcessed, tsgoTotalFiles);
823
- await yieldToEventLoop();
824
- }
825
- const absFilePath = path.resolve(repoPath, filePath);
826
- // Sequential LSP requests — tsgo processes over stdio, concurrent floods cause hangs
827
- for (const call of calls) {
828
- try {
829
- const def = await tsgoService.resolveDefinition(absFilePath, call.callLine - 1, call.callColumn);
830
- if (!def) {
831
- failed++;
832
- continue;
833
- }
834
- const targetSymbols = ctx.symbols.lookupAllInFile(def.filePath);
835
- if (targetSymbols.length === 0) {
836
- failed++;
837
- continue;
838
- }
839
- // Match by exact startLine, then by range containment
840
- let bestMatch;
841
- for (const sym of targetSymbols) {
842
- const node = graph.getNode(toNodeId(sym.nodeId));
843
- if (node && node.properties.startLine === def.line) {
844
- bestMatch = sym;
845
- break;
838
+ const getNextFile = () => {
839
+ if (nextFileIdx >= fileEntries.length)
840
+ return null;
841
+ return fileEntries[nextFileIdx++];
842
+ };
843
+ /** Resolve files from the shared queue using a single tsgo service */
844
+ const resolveWorker = async (service) => {
845
+ const sliceResults = new Map();
846
+ let sliceResolved = 0;
847
+ let sliceFailed = 0;
848
+ let entry;
849
+ while ((entry = getNextFile()) !== null) {
850
+ const [filePath, calls] = entry;
851
+ totalFilesProcessed++;
852
+ if (totalFilesProcessed % 25 === 0) {
853
+ onProgress?.(totalFilesProcessed, tsgoTotalFiles, actualWorkers);
854
+ }
855
+ const absFilePath = path.resolve(repoPath, filePath);
856
+ for (const call of calls) {
857
+ try {
858
+ const def = await service.resolveDefinition(absFilePath, call.callLine - 1, call.callColumn);
859
+ if (!def) {
860
+ sliceFailed++;
861
+ continue;
846
862
  }
847
- }
848
- if (!bestMatch) {
863
+ const targetSymbols = ctx.symbols.lookupAllInFile(def.filePath);
864
+ if (targetSymbols.length === 0) {
865
+ sliceFailed++;
866
+ continue;
867
+ }
868
+ // Match by exact startLine, then by range containment
869
+ let bestMatch;
849
870
  for (const sym of targetSymbols) {
850
871
  const node = graph.getNode(toNodeId(sym.nodeId));
851
- if (node) {
852
- const sl = node.properties.startLine;
853
- const el = node.properties.endLine;
854
- if (sl !== undefined && el !== undefined && def.line >= sl && def.line <= el) {
855
- bestMatch = sym;
856
- break;
872
+ if (node && node.properties.startLine === def.line) {
873
+ bestMatch = sym;
874
+ break;
875
+ }
876
+ }
877
+ if (!bestMatch) {
878
+ for (const sym of targetSymbols) {
879
+ const node = graph.getNode(toNodeId(sym.nodeId));
880
+ if (node) {
881
+ const sl = node.properties.startLine;
882
+ const el = node.properties.endLine;
883
+ if (sl !== undefined && el !== undefined && def.line >= sl && def.line <= el) {
884
+ bestMatch = sym;
885
+ break;
886
+ }
857
887
  }
858
888
  }
859
889
  }
860
- }
861
- if (bestMatch) {
862
- // Drop self-referencing tsgo edges
863
- if (bestMatch.nodeId === call.sourceId) {
864
- failed++;
865
- continue;
890
+ if (bestMatch) {
891
+ if (bestMatch.nodeId === call.sourceId) {
892
+ sliceFailed++;
893
+ continue;
894
+ }
895
+ const callKey = `${call.sourceId}\0${call.calledName}\0${call.callLine}`;
896
+ sliceResults.set(callKey, {
897
+ nodeId: bestMatch.nodeId,
898
+ confidence: TIER_CONFIDENCE['tsgo-resolved'],
899
+ reason: 'tsgo-lsp',
900
+ });
901
+ sliceResolved++;
902
+ }
903
+ else {
904
+ sliceFailed++;
866
905
  }
867
- const callKey = `${call.sourceId}\0${call.calledName}\0${call.callLine}`;
868
- results.set(callKey, {
869
- nodeId: bestMatch.nodeId,
870
- confidence: TIER_CONFIDENCE['tsgo-resolved'],
871
- reason: 'tsgo-lsp',
872
- });
873
- resolved++;
874
906
  }
875
- else {
876
- failed++;
907
+ catch {
908
+ sliceFailed++;
877
909
  }
878
910
  }
879
- catch {
880
- failed++;
911
+ // Close file after all its calls are resolved — frees tsgo memory,
912
+ // prevents progressive slowdown as the type graph grows
913
+ service.notifyFileDeleted(absFilePath);
914
+ }
915
+ return { resolved: sliceResolved, failed: sliceFailed, results: sliceResults };
916
+ };
917
+ let resolved = 0;
918
+ let failed = 0;
919
+ if (actualWorkers === 1) {
920
+ // Single process — use the existing service (already started)
921
+ const outcome = await resolveWorker(tsgoService);
922
+ resolved = outcome.resolved;
923
+ failed = outcome.failed;
924
+ for (const [k, v] of outcome.results)
925
+ results.set(k, v);
926
+ }
927
+ else {
928
+ // Parallel — spawn extra services, all pull from shared queue
929
+ const extraServices = [];
930
+ try {
931
+ // Start extra tsgo processes in parallel
932
+ const startPromises = [];
933
+ for (let i = 1; i < actualWorkers; i++) {
934
+ startPromises.push((async () => {
935
+ const svc = new TsgoService(repoPath);
936
+ if (await svc.start())
937
+ return svc;
938
+ return null;
939
+ })());
940
+ }
941
+ const started = await Promise.all(startPromises);
942
+ for (const svc of started) {
943
+ if (svc)
944
+ extraServices.push(svc);
881
945
  }
946
+ // Build final service list: original + extras that started successfully
947
+ const services = [tsgoService, ...extraServices];
948
+ if (process.env['CODE_MAPPER_VERBOSE'])
949
+ console.error(`Code Mapper: ${services.length} tsgo processes ready, resolving with dynamic dispatch...`);
950
+ // All workers pull from the shared queue — naturally self-balancing
951
+ const outcomes = await Promise.all(services.map(svc => resolveWorker(svc)));
952
+ for (const outcome of outcomes) {
953
+ resolved += outcome.resolved;
954
+ failed += outcome.failed;
955
+ for (const [k, v] of outcome.results)
956
+ results.set(k, v);
957
+ }
958
+ }
959
+ finally {
960
+ // Stop extra services (the original is stopped by the caller)
961
+ for (const svc of extraServices)
962
+ svc.stop();
882
963
  }
883
964
  }
884
965
  const elapsed = Date.now() - t0;
885
- console.error(`Code Mapper: tsgo resolved ${resolved}/${eligible.length} calls in ${elapsed}ms (${failed} unresolvable)`);
966
+ if (process.env['CODE_MAPPER_VERBOSE'])
967
+ console.error(`Code Mapper: tsgo resolved ${resolved}/${eligible.length} calls in ${elapsed}ms (${failed} unresolvable, ${actualWorkers} process${actualWorkers > 1 ? 'es' : ''})`);
886
968
  return results;
887
969
  }
888
970
  /** Generic method names that produce false edges when receiver type is unknown (worker-extracted path) */
@@ -928,7 +1010,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
928
1010
  for (const [filePath, calls] of byFile) {
929
1011
  filesProcessed++;
930
1012
  if (filesProcessed % 25 === 0) {
931
- onProgress?.(filesProcessed, totalFiles);
1013
+ onProgress?.(filesProcessed, totalFiles, 1);
932
1014
  await yieldToEventLoop();
933
1015
  }
934
1016
  ctx.enableCache(filePath);
@@ -192,7 +192,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
192
192
  percent: Math.round(parsingProgress),
193
193
  message: `Parsing chunk ${chunkIdx + 1}/${numChunks}...`,
194
194
  detail: filePath,
195
- stats: { filesProcessed: globalCurrent, totalFiles: totalParseable, nodesCreated: graph.nodeCount },
195
+ stats: { filesProcessed: globalCurrent, totalFiles: totalParseable, nodesCreated: graph.nodeCount, ...(workerPool ? { workerCount: workerPool.size } : {}) },
196
196
  });
197
197
  }, workerPool);
198
198
  const parseMs = Date.now() - parseStart;
@@ -297,13 +297,13 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
297
297
  }
298
298
  }
299
299
  try {
300
- await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total) => {
300
+ await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total, workerCount) => {
301
301
  const callPercent = 70 + Math.round((current / Math.max(total, 1)) * 12);
302
302
  onProgress({
303
303
  phase: 'calls',
304
304
  percent: callPercent,
305
305
  message: `Resolving calls: ${current}/${total} files...`,
306
- stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount },
306
+ stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount, ...(workerCount ? { workerCount } : {}) },
307
307
  });
308
308
  }, allConstructorBindings.length > 0 ? allConstructorBindings : undefined, tsgoService, repoPath);
309
309
  }
@@ -258,10 +258,10 @@ export class TsgoService {
258
258
  this.process.stderr.on('data', (chunk) => {
259
259
  const msg = chunk.toString().trim();
260
260
  if (msg)
261
- console.error(`[tsgo-service] stderr: ${msg}`);
261
+ verbose('stderr:', msg);
262
262
  });
263
263
  this.process.on('exit', (code, signal) => {
264
- console.error(`[tsgo-service] process exited (code=${code}, signal=${signal})`);
264
+ verbose(`process exited (code=${code}, signal=${signal})`);
265
265
  this.ready = false;
266
266
  this.process = null;
267
267
  });
@@ -284,7 +284,7 @@ export class TsgoService {
284
284
  // Send initialized notification
285
285
  this.send({ jsonrpc: '2.0', method: 'initialized', params: {} });
286
286
  this.ready = true;
287
- console.error('Code Mapper: tsgo LSP ready (semantic resolution enabled)');
287
+ verbose('LSP ready');
288
288
  return true;
289
289
  }
290
290
  catch (err) {
@@ -13,6 +13,7 @@ export interface PipelineProgress {
13
13
  filesProcessed: number;
14
14
  totalFiles: number;
15
15
  nodesCreated: number;
16
+ workerCount?: number;
16
17
  };
17
18
  }
18
19
  export interface PipelineResult {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.3.7",
3
+ "version": "2.3.9",
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",