@zuvia-software-solutions/code-mapper 2.5.2 → 2.6.0

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.
@@ -2,7 +2,6 @@
2
2
  export interface AnalyzeOptions {
3
3
  force?: boolean;
4
4
  embeddings?: boolean;
5
- tsgo?: boolean;
6
5
  verbose?: boolean;
7
6
  }
8
7
  export declare const analyzeCommand: (inputPath?: string, options?: AnalyzeOptions) => Promise<void>;
@@ -248,7 +248,7 @@ export const analyzeCommand = async (inputPath, options) => {
248
248
  }
249
249
  const scaled = Math.round(progress.percent * 0.6);
250
250
  updateBar(scaled, phaseLabel, baseLabel);
251
- }, options?.tsgo === false ? { tsgo: false } : {});
251
+ });
252
252
  // Phase 2: SQLite (60-85%)
253
253
  recordPhase('sqlite');
254
254
  updateBar(60, 'Loading into database...');
@@ -431,7 +431,6 @@ export const analyzeCommand = async (inputPath, options) => {
431
431
  const wallMs = Date.now() - t0Global || 1;
432
432
  const cpuPct = Math.round(((cpuEnd.user + cpuEnd.system) / 1e3) / wallMs * 100);
433
433
  console.log(` Memory: peak ${peakRssMB}MB RSS | CPU: ${cpuPct}% (${cpuCount} cores)`);
434
- console.log(` tsgo: ${pipelineResult.tsgoEnabled ? 'enabled (compiler-verified call resolution)' : 'disabled — install @typescript/native-preview for higher accuracy'}`);
435
434
  console.log(` ${repoPath}`);
436
435
  if (aiContext.files.length > 0) {
437
436
  console.log(` Context: ${aiContext.files.join(', ')}`);
package/dist/cli/index.js CHANGED
@@ -24,7 +24,6 @@ program
24
24
  .option('-f, --force', 'Force full re-index even if up to date')
25
25
  .option('--embeddings', 'Generate semantic embeddings (bge-small, CPU, fast)')
26
26
  .option('--no-embeddings', 'Skip embedding generation')
27
- .option('--no-tsgo', 'Skip tsgo LSP for call resolution (faster, less accurate)')
28
27
  .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
29
28
  .addHelpText('after', '\nEnvironment variables:\n CODE_MAPPER_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .code-mapperignore)')
30
29
  .action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
@@ -72,7 +71,6 @@ program
72
71
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
73
72
  .option('-f, --file <path>', 'File path to disambiguate common names')
74
73
  .option('--content', 'Include full symbol source code')
75
- .option('--no-tsgo', 'Disable tsgo LSP enrichment (faster, less accurate)')
76
74
  .action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
77
75
  program
78
76
  .command('impact <target>')
@@ -81,7 +79,6 @@ program
81
79
  .option('-r, --repo <name>', 'Target repository')
82
80
  .option('--depth <n>', 'Max relationship depth (default: 3)')
83
81
  .option('--include-tests', 'Include test files in results')
84
- .option('--no-tsgo', 'Disable tsgo LSP enrichment (faster, less accurate)')
85
82
  .action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
86
83
  program
87
84
  .command('sql <query>')
@@ -119,9 +119,6 @@ export const refreshCommand = async (options) => {
119
119
  const result = await refreshFiles(db, repoRoot, entries);
120
120
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
121
121
  console.log(`Refreshed ${result.filesProcessed} file(s) in ${elapsed}s`);
122
- if (!result.tsgoEnabled) {
123
- console.log('Note: tsgo unavailable — call resolution used heuristic matching. Install @typescript/native-preview for higher accuracy.');
124
- }
125
122
  // Refresh embeddings for dirty files (same quality as full analyze — graph context enriched)
126
123
  await refreshEmbeddings(db, entries);
127
124
  // Update metadata with current commit
@@ -15,14 +15,12 @@ export declare function contextCommand(name: string, options?: {
15
15
  file?: string;
16
16
  uid?: string;
17
17
  content?: boolean;
18
- tsgo?: boolean;
19
18
  }): Promise<void>;
20
19
  export declare function impactCommand(target: string, options?: {
21
20
  direction?: string;
22
21
  repo?: string;
23
22
  depth?: string;
24
23
  includeTests?: boolean;
25
- tsgo?: boolean;
26
24
  }): Promise<void>;
27
25
  export declare function sqlCommand(query: string, options?: {
28
26
  repo?: string;
package/dist/cli/tool.js CHANGED
@@ -50,7 +50,6 @@ export async function contextCommand(name, options) {
50
50
  uid: options?.uid,
51
51
  file_path: options?.file,
52
52
  include_content: options?.content ?? false,
53
- tsgo: options?.tsgo,
54
53
  repo: options?.repo,
55
54
  });
56
55
  output(result);
@@ -66,7 +65,6 @@ export async function impactCommand(target, options) {
66
65
  direction: options?.direction || 'upstream',
67
66
  maxDepth: options?.depth ? parseInt(options.depth) : undefined,
68
67
  includeTests: options?.includeTests ?? false,
69
- tsgo: options?.tsgo,
70
68
  repo: options?.repo,
71
69
  });
72
70
  output(result);
@@ -3,6 +3,6 @@
3
3
  * Spawned by buildNlEmbeddings — loads bge-small independently,
4
4
  * embeds texts received via IPC, sends vectors back.
5
5
  *
6
- * Same architecture as parallel tsgo: N processes, each with own model.
6
+ * Spawned by buildNlEmbeddings loads bge-small independently,
7
7
  */
8
8
  export {};
@@ -4,7 +4,7 @@
4
4
  * Spawned by buildNlEmbeddings — loads bge-small independently,
5
5
  * embeds texts received via IPC, sends vectors back.
6
6
  *
7
- * Same architecture as parallel tsgo: N processes, each with own model.
7
+ * Spawned by buildNlEmbeddings loads bge-small independently,
8
8
  */
9
9
  import { pipeline } from '@huggingface/transformers';
10
10
  const MODEL_ID = 'Xenova/bge-small-en-v1.5';
@@ -258,7 +258,7 @@ export async function buildNlEmbeddings(db, onProgress) {
258
258
  db.prepare('DELETE FROM nl_embeddings').run();
259
259
  }
260
260
  catch { /* table may not exist */ }
261
- // Parallel multi-process embedding — same architecture as tsgo
261
+ // Parallel multi-process embedding — N workers, each with own model
262
262
  // Each worker loads its own bge-small model, embeds independently.
263
263
  const os = await import('os');
264
264
  const { fork } = await import('child_process');
@@ -9,8 +9,7 @@
9
9
  * 1. Delete old nodes for dirty files
10
10
  * 2. Parse modified/created files with tree-sitter
11
11
  * 3. Insert File nodes + symbol nodes + DEFINES edges
12
- * 4. Resolve call edges using tsgo LSP (TS/JS files only)
13
- * 5. Repair cross-file edges (callers from unchanged files → changed symbols)
12
+ * 4. Resolve call edges (heuristic name-based matching)
14
13
  */
15
14
  import type Database from 'better-sqlite3';
16
15
  import { type DirtyFileEntry, type RefreshResult } from './types.js';
@@ -20,8 +19,7 @@ import { type DirtyFileEntry, type RefreshResult } from './types.js';
20
19
  * Phase 1: Delete old nodes for all dirty files
21
20
  * Phase 2: Parse modified/created files with tree-sitter
22
21
  * Phase 3: Insert File nodes + symbol nodes + DEFINES edges
23
- * Phase 4: Resolve call edges using tsgo LSP (TS/JS only, optional)
24
- * Phase 5: Repair cross-file edges from unchanged files (tsgo, optional)
22
+ * Phase 4: Resolve call edges (heuristic name-based matching)
25
23
  *
26
24
  * @param db - Open better-sqlite3 database instance
27
25
  * @param repoPath - Absolute path to the repository root
@@ -10,8 +10,7 @@
10
10
  * 1. Delete old nodes for dirty files
11
11
  * 2. Parse modified/created files with tree-sitter
12
12
  * 3. Insert File nodes + symbol nodes + DEFINES edges
13
- * 4. Resolve call edges using tsgo LSP (TS/JS files only)
14
- * 5. Repair cross-file edges (callers from unchanged files → changed symbols)
13
+ * 4. Resolve call edges (heuristic name-based matching)
15
14
  */
16
15
  import path from 'path';
17
16
  import fsSync from 'fs';
@@ -24,17 +23,10 @@ import { generateId } from '../../lib/utils.js';
24
23
  import { deleteNodesByFile, insertNode, insertEdge, findNodeAtLine, findNodesByFile, deleteEmbeddingsByFile, insertEmbeddingsBatch, countEmbeddings, deleteRefsByFile, insertRefsBatch, deleteFileWordsByFile, upsertFileWords } from '../db/adapter.js';
25
24
  import { assertNodeLabel, toNodeId, toEdgeId } from '../db/schema.js';
26
25
  import {} from './types.js';
27
- import { getTsgoService } from '../semantic/tsgo-service.js';
28
26
  import { EMBEDDABLE_LABELS } from '../embeddings/types.js';
29
27
  // ---------------------------------------------------------------------------
30
28
  // Helpers
31
29
  // ---------------------------------------------------------------------------
32
- /** File extensions that tsgo can resolve */
33
- const TS_JS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs']);
34
- function isTypeScriptOrJavaScript(filePath) {
35
- const ext = path.extname(filePath).toLowerCase();
36
- return TS_JS_EXTENSIONS.has(ext);
37
- }
38
30
  /**
39
31
  * Find the innermost symbol node enclosing a given 0-based line in a file.
40
32
  * Excludes structural nodes (File, Folder, Community, Process).
@@ -42,31 +34,13 @@ function isTypeScriptOrJavaScript(filePath) {
42
34
  function findEnclosingNode(db, filePath, line) {
43
35
  return findNodeAtLine(db, filePath, line, 'File');
44
36
  }
45
- /**
46
- * Find a node whose definition starts at a given 0-based line in a file.
47
- * Falls back to the enclosing node if no exact match.
48
- */
49
- function findNodeByFileAndLine(db, filePath, line) {
50
- // Try exact startLine match first (exclude structural labels)
51
- const exact = db.prepare(`SELECT * FROM nodes
52
- WHERE filePath = ? AND startLine = ?
53
- AND label NOT IN ('File', 'Folder', 'Community', 'Process')
54
- LIMIT 1`
55
- // Safe: schema and NodeRow defined together in schema.ts
56
- ).get(filePath, line);
57
- if (exact)
58
- return exact;
59
- // Fall back to enclosing node
60
- return findEnclosingNode(db, filePath, line);
61
- }
62
37
  /**
63
38
  * Parse dirty files with tree-sitter and write nodes/edges to the database.
64
39
  *
65
40
  * Phase 1: Delete old nodes for all dirty files
66
41
  * Phase 2: Parse modified/created files with tree-sitter
67
42
  * Phase 3: Insert File nodes + symbol nodes + DEFINES edges
68
- * Phase 4: Resolve call edges using tsgo LSP (TS/JS only, optional)
69
- * Phase 5: Repair cross-file edges from unchanged files (tsgo, optional)
43
+ * Phase 4: Resolve call edges (heuristic name-based matching)
70
44
  *
71
45
  * @param db - Open better-sqlite3 database instance
72
46
  * @param repoPath - Absolute path to the repository root
@@ -158,10 +132,9 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
158
132
  const callNameNode = captureMap['call.name'];
159
133
  if (callNameNode) {
160
134
  callSites.push({
161
- filePath: relPath, absPath,
135
+ filePath: relPath,
162
136
  name: callNameNode.text,
163
137
  line: callNameNode.startPosition.row,
164
- character: callNameNode.startPosition.column,
165
138
  });
166
139
  }
167
140
  continue;
@@ -283,33 +256,11 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
283
256
  if (wordSet.size > 0)
284
257
  upsertFileWords(db, relPath, [...wordSet].join(' '));
285
258
  }
286
- // Phase 4 + 5: Resolve call edges and cross-file edges using tsgo LSP
287
- // (TS/JS files only — tsgo is optional, skip if unavailable)
288
- console.error(`Code Mapper: refresh tsgo init with repoPath=${repoPath}`);
289
- const tsgoService = getTsgoService(repoPath);
290
- let tsgoReady = false;
291
- try {
292
- tsgoReady = await tsgoService.start();
293
- console.error(`Code Mapper: refresh tsgo ready=${tsgoReady}`);
294
- }
295
- catch (err) {
296
- console.error(`Code Mapper: refresh tsgo failed: ${err instanceof Error ? err.message : err}`);
297
- }
298
- // Phase 4: Resolve call edges from dirty files
299
- // Always runs — tsgo provides 0.99 confidence, heuristic fallback provides 0.5-0.95
259
+ // Phase 4: Resolve call edges from dirty files (heuristic name-based matching)
300
260
  if (callSites.length > 0) {
301
261
  db.exec('BEGIN');
302
262
  try {
303
263
  const { findNodesByName } = await import('../db/adapter.js');
304
- // Notify tsgo about changed files if available
305
- if (tsgoReady) {
306
- for (const entry of filesToProcess) {
307
- const absPath = path.resolve(repoPath, entry.relativePath);
308
- if (isTypeScriptOrJavaScript(entry.relativePath)) {
309
- await tsgoService.notifyFileChanged(absPath);
310
- }
311
- }
312
- }
313
264
  console.error(`Code Mapper: refresh Phase 4 — ${callSites.length} call sites to resolve`);
314
265
  for (const callSite of callSites) {
315
266
  const sourceNode = findEnclosingNode(db, callSite.filePath, callSite.line);
@@ -318,39 +269,23 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
318
269
  let targetNode;
319
270
  let confidence = 0.5;
320
271
  let reason = 'global';
321
- // Try tsgo first for TS/JS files
322
- if (tsgoReady && isTypeScriptOrJavaScript(callSite.filePath)) {
323
- try {
324
- const def = await tsgoService.resolveDefinition(callSite.absPath, callSite.line, callSite.character);
325
- if (def) {
326
- targetNode = findNodeByFileAndLine(db, def.filePath, def.line);
327
- if (targetNode) {
328
- confidence = 0.99;
329
- reason = 'tsgo-lsp';
330
- }
331
- }
332
- }
333
- catch { }
272
+ // Heuristic: name-based lookup in DB
273
+ const candidates = findNodesByName(db, callSite.name, undefined, 5);
274
+ const sameFile = candidates.find(c => c.filePath === callSite.filePath);
275
+ if (sameFile) {
276
+ targetNode = sameFile;
277
+ confidence = 0.95;
278
+ reason = 'same-file';
279
+ }
280
+ else if (candidates.length === 1) {
281
+ targetNode = candidates[0];
282
+ confidence = 0.9;
283
+ reason = 'import-resolved';
334
284
  }
335
- // Heuristic fallback: name-based lookup in DB
336
- if (!targetNode) {
337
- const candidates = findNodesByName(db, callSite.name, undefined, 5);
338
- const sameFile = candidates.find(c => c.filePath === callSite.filePath);
339
- if (sameFile) {
340
- targetNode = sameFile;
341
- confidence = 0.95;
342
- reason = 'same-file';
343
- }
344
- else if (candidates.length === 1) {
345
- targetNode = candidates[0];
346
- confidence = 0.9;
347
- reason = 'import-resolved';
348
- }
349
- else if (candidates.length > 0) {
350
- targetNode = candidates[0];
351
- confidence = 0.5;
352
- reason = 'global';
353
- }
285
+ else if (candidates.length > 0) {
286
+ targetNode = candidates[0];
287
+ confidence = 0.5;
288
+ reason = 'global';
354
289
  }
355
290
  if (!targetNode)
356
291
  continue;
@@ -375,61 +310,11 @@ export async function refreshFiles(db, repoPath, dirtyFiles) {
375
310
  console.error(`Code Mapper: Phase 4 call resolution failed: ${err instanceof Error ? err.message : err}`);
376
311
  }
377
312
  }
378
- // Phase 5: Repair cross-file edges (tsgo only — needs findReferences)
379
- if (tsgoReady) {
380
- const dirtyFilePaths = new Set(dirtyFiles.map(f => f.relativePath));
381
- db.exec('BEGIN');
382
- try {
383
- // Phase 5: Repair cross-file edges
384
- // For each newly inserted definition, find references from UNCHANGED files
385
- // and create CALLS edges so the graph stays consistent.
386
- for (const def of allDefinitions) {
387
- if (!isTypeScriptOrJavaScript(def.filePath))
388
- continue;
389
- const absPath = path.resolve(repoPath, def.filePath);
390
- const refs = await tsgoService.findReferences(absPath, def.startLine, 0);
391
- for (const ref of refs) {
392
- // Skip refs from dirty files — already handled in Phase 4
393
- if (dirtyFilePaths.has(ref.filePath))
394
- continue;
395
- // Skip refs from non-TS/JS files
396
- if (!isTypeScriptOrJavaScript(ref.filePath))
397
- continue;
398
- // This reference is from an UNCHANGED file → create/update the CALLS edge
399
- const sourceNode = findEnclosingNode(db, ref.filePath, ref.line);
400
- if (!sourceNode)
401
- continue;
402
- const targetId = toNodeId(def.nodeId);
403
- // Skip self-edges
404
- if (sourceNode.id === targetId)
405
- continue;
406
- const edgeId = toEdgeId(`${sourceNode.id}_CALLS_${targetId}`);
407
- insertEdge(db, {
408
- id: edgeId,
409
- sourceId: sourceNode.id,
410
- targetId,
411
- type: 'CALLS',
412
- confidence: 0.99,
413
- reason: 'tsgo-cross-file',
414
- callLine: ref.line,
415
- });
416
- edgesInserted++;
417
- }
418
- }
419
- db.exec('COMMIT');
420
- }
421
- catch (err) {
422
- db.exec('ROLLBACK');
423
- // Log but don't fail the entire refresh — Phases 1-3 already committed
424
- console.error(`Code Mapper: tsgo call resolution failed: ${err instanceof Error ? err.message : err}`);
425
- }
426
- }
427
313
  // FTS5 auto-updates via triggers — no manual rebuild needed
428
314
  return {
429
315
  filesProcessed: filesToProcess.length, filesSkipped,
430
316
  nodesDeleted, nodesInserted, edgesInserted,
431
317
  durationMs: Date.now() - t0,
432
- tsgoEnabled: tsgoReady,
433
318
  };
434
319
  }
435
320
  // ---------------------------------------------------------------------------
@@ -55,7 +55,6 @@ export interface RefreshResult {
55
55
  readonly nodesInserted: number;
56
56
  readonly edgesInserted: number;
57
57
  readonly durationMs: number;
58
- readonly tsgoEnabled: boolean;
59
58
  }
60
59
  /** Messages from child to parent */
61
60
  export type ChildToParentMessage = {
@@ -131,7 +131,6 @@ export function parseChildMessage(raw) {
131
131
  nodesInserted: Number(p['nodesInserted']) || 0,
132
132
  edgesInserted: Number(p['edgesInserted']) || 0,
133
133
  durationMs: Number(p['durationMs']) || 0,
134
- tsgoEnabled: Boolean(p['tsgoEnabled']),
135
134
  },
136
135
  };
137
136
  }
@@ -3,14 +3,13 @@ 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 { TsgoService } from '../semantic/tsgo-service.js';
7
6
  export declare const processCalls: (graph: KnowledgeGraph, files: {
8
7
  path: string;
9
8
  content: string;
10
9
  }[], astCache: ASTCache, ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<ExtractedHeritage[]>;
11
10
  export declare const extractReturnTypeName: (raw: string, depth?: number) => string | undefined;
12
11
  /** 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, workerCount?: number) => void, constructorBindings?: FileConstructorBindings[], tsgoService?: TsgoService | null, repoPath?: string) => Promise<void>;
12
+ export declare const processCallsFromExtracted: (graph: KnowledgeGraph, extractedCalls: ExtractedCall[], ctx: ResolutionContext, onProgress?: (current: number, total: number, workerCount?: number) => void, constructorBindings?: FileConstructorBindings[]) => Promise<void>;
14
13
  /** Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods */
15
14
  export declare const processRoutesFromExtracted: (graph: KnowledgeGraph, extractedRoutes: ExtractedRoute[], ctx: ResolutionContext, onProgress?: (current: number, total: number) => void) => Promise<void>;
16
15
  /**
@@ -41,6 +40,6 @@ export declare const createProvidesEdges: (graph: KnowledgeGraph, ctx: Resolutio
41
40
  * Creates CALLS edges: InterfaceMethod → ImplementationFunction.
42
41
  *
43
42
  * This makes the call chain traversable:
44
- * register() →[tsgo]→ EventBus.emit (Method) →[dispatch]→ emit (Function in event-bus.ts)
43
+ * register() → EventBus.emit (Method) →[dispatch]→ emit (Function in event-bus.ts)
45
44
  */
46
45
  export declare const resolveInterfaceDispatches: (graph: KnowledgeGraph, ctx: ResolutionContext) => Promise<number>;
@@ -6,12 +6,10 @@ import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/pa
6
6
  import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
7
7
  import { generateId } from '../../lib/utils.js';
8
8
  import { toNodeId, toEdgeId } from '../db/schema.js';
9
- import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, findEnclosingClassId, CALL_EXPRESSION_TYPES, extractCallChain, } from './utils.js';
9
+ import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, findEnclosingClassId, findEnclosingClassName, SELF_RECEIVER_KEYWORDS, CALL_EXPRESSION_TYPES, extractCallChain, } from './utils.js';
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';
14
- import path from 'node:path';
15
13
  /** Walk up the AST to find the enclosing function/method, or null for top-level code */
16
14
  const findEnclosingFunction = (node, filePath, ctx) => {
17
15
  let current = node.parent;
@@ -302,6 +300,12 @@ export const processCalls = async (graph, files, astCache, ctx, onProgress) => {
302
300
  const callForm = inferCallForm(callNode, nameNode);
303
301
  const receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
304
302
  let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
303
+ // Resolve this/self/base to enclosing class name
304
+ if (!receiverTypeName && receiverName && SELF_RECEIVER_KEYWORDS.has(receiverName)) {
305
+ const className = findEnclosingClassName(callNode);
306
+ if (className)
307
+ receiverTypeName = className;
308
+ }
305
309
  // Fall back to verified constructor bindings
306
310
  if (!receiverTypeName && receiverName && verifiedReceivers.size > 0) {
307
311
  const enclosingFunc = findEnclosingFunction(callNode, file.path, ctx);
@@ -728,223 +732,7 @@ const lookupReceiverType = (map, funcName, varName) => {
728
732
  // Fallback: file-level scope
729
733
  return map.get(fileLevelKey);
730
734
  };
731
- /** Check if a file is TypeScript or JavaScript (extensions tsgo can handle) */
732
- function isTypeScriptOrJavaScript(filePath) {
733
- return /\.(ts|tsx|js|jsx|mts|mjs|cts|cjs)$/.test(filePath);
734
- }
735
- /**
736
- * Batch-resolve call sites via tsgo LSP before heuristic resolution.
737
- *
738
- * For each TS/JS call with line+column info, asks tsgo for go-to-definition.
739
- * Returns a Map from callKey -> ResolveResult for calls that tsgo resolved.
740
- *
741
- * Call key format: "sourceId\0calledName\0callLine" — unique per call site.
742
- */
743
- async function batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath, onProgress) {
744
- const results = new Map();
745
- // Collect eligible calls (TS/JS files with line+column info)
746
- const eligible = [];
747
- for (const call of extractedCalls) {
748
- if (isTypeScriptOrJavaScript(call.filePath) &&
749
- call.callLine !== undefined &&
750
- call.callColumn !== undefined) {
751
- eligible.push(call);
752
- }
753
- }
754
- if (eligible.length === 0)
755
- return results;
756
- // Built-in receiver names that resolve to external types, not project code.
757
- const BUILTIN_RECEIVERS = new Set([
758
- 'console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number', 'Boolean',
759
- 'Date', 'RegExp', 'Error', 'Promise', 'Map', 'Set', 'WeakMap', 'WeakSet',
760
- 'Buffer', 'process', 'globalThis', 'window', 'document', 'navigator',
761
- 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
762
- 'require', 'module', 'exports', '__dirname', '__filename',
763
- 'fs', 'path', 'os', 'url', 'util', 'crypto', 'http', 'https', 'net',
764
- 'child_process', 'stream', 'events', 'assert', 'zlib',
765
- ]);
766
- // Pre-filter calls where tsgo won't add value:
767
- // A. Free-form calls with unambiguous name — heuristic resolves perfectly
768
- // B. Member calls on built-in receivers — tsgo always fails on these
769
- const tsgoEligible = [];
770
- let skippedUnambiguous = 0;
771
- let skippedBuiltin = 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
- if (resolved && resolved.candidates.length === 1) {
776
- skippedUnambiguous++;
777
- continue;
778
- }
779
- }
780
- if (call.callForm === 'member' && call.receiverName && BUILTIN_RECEIVERS.has(call.receiverName)) {
781
- skippedBuiltin++;
782
- continue;
783
- }
784
- tsgoEligible.push(call);
785
- }
786
- // Regroup filtered calls by file
787
- const tsgoByFile = new Map();
788
- for (const call of tsgoEligible) {
789
- let list = tsgoByFile.get(call.filePath);
790
- if (!list) {
791
- list = [];
792
- tsgoByFile.set(call.filePath, list);
793
- }
794
- list.push(call);
795
- }
796
- const t0 = Date.now();
797
- const skippedTotal = skippedUnambiguous + skippedBuiltin;
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.
801
- const osModule = await import('os');
802
- const cpuCount = osModule.cpus().length;
803
- const freeMemGB = osModule.freemem() / (1024 * 1024 * 1024);
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);
809
- if (process.env['CODE_MAPPER_VERBOSE']) {
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)...`);
811
- }
812
- // Dynamic dispatch: shared queue sorted by call count descending
813
- const fileEntries = [...tsgoByFile.entries()];
814
- fileEntries.sort((a, b) => b[1].length - a[1].length);
815
- let totalFilesProcessed = 0;
816
- let nextFileIdx = 0;
817
- const tsgoTotalFiles = tsgoByFile.size;
818
- const getNextFile = () => {
819
- if (nextFileIdx >= fileEntries.length)
820
- return null;
821
- return fileEntries[nextFileIdx++];
822
- };
823
- const resolveWorker = async (service) => {
824
- const sliceResults = new Map();
825
- let sliceResolved = 0;
826
- let sliceFailed = 0;
827
- let entry;
828
- while ((entry = getNextFile()) !== null) {
829
- // Bail out early if tsgo process died — no point sending more requests
830
- if (!service.isReady())
831
- break;
832
- const [filePath, calls] = entry;
833
- totalFilesProcessed++;
834
- if (totalFilesProcessed % 25 === 0) {
835
- onProgress?.(totalFilesProcessed, tsgoTotalFiles, actualWorkers);
836
- }
837
- const absFilePath = path.resolve(repoPath, filePath);
838
- // Pipeline: fire all definition requests for this file concurrently.
839
- // The LSP server processes them serially, but we eliminate round-trip
840
- // latency between requests — major speedup on large files.
841
- const BATCH = 50;
842
- for (let i = 0; i < calls.length; i += BATCH) {
843
- const batch = calls.slice(i, i + BATCH);
844
- const defs = await Promise.all(batch.map(call => service.resolveDefinition(absFilePath, call.callLine - 1, call.callColumn)
845
- .catch(() => null)));
846
- for (let j = 0; j < batch.length; j++) {
847
- const call = batch[j];
848
- const def = defs[j];
849
- if (!def) {
850
- sliceFailed++;
851
- continue;
852
- }
853
- const targetSymbols = ctx.symbols.lookupAllInFile(def.filePath);
854
- if (targetSymbols.length === 0) {
855
- sliceFailed++;
856
- continue;
857
- }
858
- let bestMatch;
859
- for (const sym of targetSymbols) {
860
- const node = graph.getNode(toNodeId(sym.nodeId));
861
- if (node && node.properties.startLine === def.line) {
862
- bestMatch = sym;
863
- break;
864
- }
865
- }
866
- if (!bestMatch) {
867
- for (const sym of targetSymbols) {
868
- const node = graph.getNode(toNodeId(sym.nodeId));
869
- if (node) {
870
- const sl = node.properties.startLine;
871
- const el = node.properties.endLine;
872
- if (sl !== undefined && el !== undefined && def.line >= sl && def.line <= el) {
873
- bestMatch = sym;
874
- break;
875
- }
876
- }
877
- }
878
- }
879
- if (bestMatch) {
880
- if (bestMatch.nodeId === call.sourceId) {
881
- sliceFailed++;
882
- continue;
883
- }
884
- const callKey = `${call.sourceId}\0${call.calledName}\0${call.callLine}`;
885
- sliceResults.set(callKey, {
886
- nodeId: bestMatch.nodeId,
887
- confidence: TIER_CONFIDENCE['tsgo-resolved'],
888
- reason: 'tsgo-lsp',
889
- });
890
- sliceResolved++;
891
- }
892
- else {
893
- sliceFailed++;
894
- }
895
- }
896
- }
897
- service.notifyFileDeleted(absFilePath);
898
- }
899
- return { resolved: sliceResolved, failed: sliceFailed, results: sliceResults };
900
- };
901
- let resolved = 0;
902
- let failed = 0;
903
- if (actualWorkers === 1) {
904
- const outcome = await resolveWorker(tsgoService);
905
- resolved = outcome.resolved;
906
- failed = outcome.failed;
907
- for (const [k, v] of outcome.results)
908
- results.set(k, v);
909
- }
910
- else {
911
- const extraServices = [];
912
- try {
913
- const startPromises = [];
914
- for (let i = 1; i < actualWorkers; i++) {
915
- startPromises.push((async () => {
916
- const svc = new TsgoService(repoPath);
917
- if (await svc.start())
918
- return svc;
919
- return null;
920
- })());
921
- }
922
- const started = await Promise.all(startPromises);
923
- for (const svc of started) {
924
- if (svc)
925
- extraServices.push(svc);
926
- }
927
- const services = [tsgoService, ...extraServices];
928
- if (process.env['CODE_MAPPER_VERBOSE'])
929
- console.error(`Code Mapper: ${services.length} tsgo processes ready, resolving with dynamic dispatch...`);
930
- const outcomes = await Promise.all(services.map(svc => resolveWorker(svc)));
931
- for (const outcome of outcomes) {
932
- resolved += outcome.resolved;
933
- failed += outcome.failed;
934
- for (const [k, v] of outcome.results)
935
- results.set(k, v);
936
- }
937
- }
938
- finally {
939
- for (const svc of extraServices)
940
- svc.stop();
941
- }
942
- }
943
- const elapsed = Date.now() - t0;
944
- if (process.env['CODE_MAPPER_VERBOSE'])
945
- console.error(`Code Mapper: tsgo resolved ${resolved}/${eligible.length} calls in ${elapsed}ms (${failed} unresolvable, ${actualWorkers} process${actualWorkers > 1 ? 'es' : ''})`);
946
- return results;
947
- }
735
+ /** Generic method names that produce false edges when receiver type is unknown (worker-extracted path) */
948
736
  /** Generic method names that produce false edges when receiver type is unknown (worker-extracted path) */
949
737
  const GENERIC_MEMBER_METHODS_WORKER = new Set([
950
738
  'has', 'get', 'set', 'add', 'remove', 'delete', 'close', 'stop', 'clear', 'reset',
@@ -958,7 +746,7 @@ const GENERIC_MEMBER_METHODS_WORKER = new Set([
958
746
  'json', 'text', 'blob', 'status', 'send', 'end', // HTTP response
959
747
  ]);
960
748
  /** Resolve pre-extracted call sites from workers (no AST parsing needed) */
961
- export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onProgress, constructorBindings, tsgoService, repoPath) => {
749
+ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onProgress, constructorBindings) => {
962
750
  // Scope-aware receiver types keyed by filePath -> scope\0varName -> typeName
963
751
  const fileReceiverTypes = new Map();
964
752
  if (constructorBindings) {
@@ -978,11 +766,6 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
978
766
  }
979
767
  list.push(call);
980
768
  }
981
- // Batch pre-resolve via tsgo LSP (highest confidence, TS/JS only)
982
- let tsgoResolved;
983
- if (tsgoService?.isReady() && repoPath) {
984
- tsgoResolved = await batchResolveTsgo(tsgoService, extractedCalls, ctx, graph, repoPath, onProgress);
985
- }
986
769
  const totalFiles = byFile.size;
987
770
  let filesProcessed = 0;
988
771
  for (const [filePath, calls] of byFile) {
@@ -1031,16 +814,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, ctx, onPr
1031
814
  if (effectiveCall.callForm === 'member' && !effectiveCall.receiverTypeName && GENERIC_MEMBER_METHODS_WORKER.has(effectiveCall.calledName)) {
1032
815
  continue;
1033
816
  }
1034
- // Check tsgo pre-resolved map first (highest confidence)
1035
- let resolved;
1036
- if (tsgoResolved && effectiveCall.callLine !== undefined) {
1037
- const callKey = `${effectiveCall.sourceId}\0${effectiveCall.calledName}\0${effectiveCall.callLine}`;
1038
- resolved = tsgoResolved.get(callKey);
1039
- }
1040
- // Fall through to heuristic resolution if tsgo didn't resolve
1041
- if (!resolved) {
1042
- resolved = resolveCallTarget(effectiveCall, effectiveCall.filePath, ctx);
1043
- }
817
+ let resolved = resolveCallTarget(effectiveCall, effectiveCall.filePath, ctx);
1044
818
  // RC-I: Interface dispatch — when receiver type is an Interface, find implementations.
1045
819
  // Strategy 1: Classes with IMPLEMENTS edges to the interface.
1046
820
  // Strategy 2: Factory functions whose return type matches the interface name.
@@ -1371,7 +1145,7 @@ export const createProvidesEdges = async (graph, ctx) => {
1371
1145
  * Creates CALLS edges: InterfaceMethod → ImplementationFunction.
1372
1146
  *
1373
1147
  * This makes the call chain traversable:
1374
- * register() →[tsgo]→ EventBus.emit (Method) →[dispatch]→ emit (Function in event-bus.ts)
1148
+ * register() → EventBus.emit (Method) →[dispatch]→ emit (Function in event-bus.ts)
1375
1149
  */
1376
1150
  export const resolveInterfaceDispatches = async (graph, ctx) => {
1377
1151
  let edgesCreated = 0;
@@ -1,5 +1,3 @@
1
1
  /** @file pipeline.ts @description Main ingestion pipeline that orchestrates scanning, parsing, resolution, community detection, and process detection across chunked file batches */
2
2
  import type { PipelineProgress, PipelineResult } from '../../types/pipeline.js';
3
- export declare const runPipelineFromRepo: (repoPath: string, onProgress: (progress: PipelineProgress) => void, opts?: {
4
- tsgo?: boolean;
5
- }) => Promise<PipelineResult>;
3
+ export declare const runPipelineFromRepo: (repoPath: string, onProgress: (progress: PipelineProgress) => void) => Promise<PipelineResult>;
@@ -20,7 +20,6 @@ import path from 'node:path';
20
20
  import { fileURLToPath, pathToFileURL } from 'node:url';
21
21
  import { memoryGuard } from '../../lib/memory-guard.js';
22
22
  import { toNodeId, toEdgeId } from '../db/schema.js';
23
- import { getTsgoService } from '../semantic/tsgo-service.js';
24
23
  const verbose = (...args) => {
25
24
  if (process.env['CODE_MAPPER_VERBOSE'])
26
25
  console.error(...args);
@@ -33,7 +32,7 @@ const DEFAULT_CHUNK_BYTE_BUDGET = 50 * 1024 * 1024;
33
32
  const WORKING_MEMORY_MULTIPLIER = 20;
34
33
  // Max AST trees to keep in LRU cache
35
34
  const AST_CACHE_CAP = 50;
36
- export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
35
+ export const runPipelineFromRepo = async (repoPath, onProgress) => {
37
36
  const graph = createKnowledgeGraph();
38
37
  const ctx = createResolutionContext();
39
38
  const symbolTable = ctx.symbols;
@@ -272,7 +271,6 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
272
271
  }
273
272
  astCache.clear();
274
273
  }
275
- let tsgoWasUsed = false;
276
274
  // Phase B: Resolve ALL deferred calls now that every symbol is registered
277
275
  // Progress range: 70-82% (advancing, not fixed)
278
276
  if (allExtractedCalls.length > 0) {
@@ -282,35 +280,15 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
282
280
  message: `Resolving ${allExtractedCalls.length} calls across all files...`,
283
281
  stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
284
282
  });
285
- // Start tsgo for high-confidence TS/JS resolution (optional — graceful fallback)
286
- let tsgoService = null;
287
- if (opts?.tsgo !== false) {
288
- try {
289
- const service = getTsgoService(repoPath);
290
- if (await service.start()) {
291
- tsgoService = service;
292
- tsgoWasUsed = true;
293
- }
294
- }
295
- catch {
296
- // tsgo is optional — if @typescript/native-preview isn't installed, skip silently
297
- }
298
- }
299
- try {
300
- await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total, workerCount) => {
301
- const callPercent = 70 + Math.round((current / Math.max(total, 1)) * 12);
302
- onProgress({
303
- phase: 'calls',
304
- percent: callPercent,
305
- message: `Resolving calls: ${current}/${total} files...`,
306
- stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount, ...(workerCount ? { workerCount } : {}) },
307
- });
308
- }, allConstructorBindings.length > 0 ? allConstructorBindings : undefined, tsgoService, repoPath);
309
- }
310
- finally {
311
- // Stop tsgo after call resolution completes
312
- tsgoService?.stop();
313
- }
283
+ await processCallsFromExtracted(graph, allExtractedCalls, ctx, (current, total, workerCount) => {
284
+ const callPercent = 70 + Math.round((current / Math.max(total, 1)) * 12);
285
+ onProgress({
286
+ phase: 'calls',
287
+ percent: callPercent,
288
+ message: `Resolving calls: ${current}/${total} files...`,
289
+ stats: { filesProcessed: current, totalFiles: total, nodesCreated: graph.nodeCount, ...(workerCount ? { workerCount } : {}) },
290
+ });
291
+ }, allConstructorBindings.length > 0 ? allConstructorBindings : undefined);
314
292
  }
315
293
  {
316
294
  const rcStats = ctx.getStats();
@@ -446,7 +424,7 @@ export const runPipelineFromRepo = async (repoPath, onProgress, opts) => {
446
424
  },
447
425
  });
448
426
  astCache.clear();
449
- return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult, tsgoEnabled: tsgoWasUsed };
427
+ return { graph, repoPath, totalFileCount: totalFiles, communityResult, processResult };
450
428
  }
451
429
  catch (error) {
452
430
  cleanup();
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { SymbolTable, SymbolDefinition } from './symbol-table.js';
13
13
  import type { NamedImportBinding } from './import-processor.js';
14
- export type ResolutionTier = 'tsgo-resolved' | 'same-file' | 'import-scoped' | 'global';
14
+ export type ResolutionTier = 'same-file' | 'import-scoped' | 'global';
15
15
  export interface TieredCandidates {
16
16
  readonly candidates: readonly SymbolDefinition[];
17
17
  readonly tier: ResolutionTier;
@@ -15,7 +15,6 @@ import { isFileInPackageDir } from './import-processor.js';
15
15
  import { walkBindingChain } from './named-binding-extraction.js';
16
16
  // Confidence scores per resolution tier
17
17
  export const TIER_CONFIDENCE = {
18
- 'tsgo-resolved': 0.99,
19
18
  'same-file': 0.95,
20
19
  'import-scoped': 0.9,
21
20
  'global': 0.5,
@@ -46,6 +46,11 @@ export declare const CLASS_CONTAINER_TYPES: Set<string>;
46
46
  export declare const CONTAINER_TYPE_TO_LABEL: Record<string, string>;
47
47
  /** Walk up AST to find enclosing class/struct/impl and return its generateId (handles Go receivers) */
48
48
  export declare const findEnclosingClassId: (node: any, filePath: string) => string | null;
49
+ /** Self-receiver keywords that should resolve to the enclosing class */
50
+ export declare const SELF_RECEIVER_KEYWORDS: Set<string>;
51
+ /** Walk up AST to find enclosing class/struct/impl and return its NAME (not ID).
52
+ * Used to resolve `this.method()` / `self.method()` receiver type. */
53
+ export declare const findEnclosingClassName: (node: any) => string | null;
49
54
  /** Extract function name and label from a function/method AST node (handles C/C++ qualified_identifier) */
50
55
  export declare const extractFunctionName: (node: any) => {
51
56
  funcName: string | null;
@@ -369,6 +369,23 @@ export const findEnclosingClassId = (node, filePath) => {
369
369
  }
370
370
  return null;
371
371
  };
372
+ /** Self-receiver keywords that should resolve to the enclosing class */
373
+ export const SELF_RECEIVER_KEYWORDS = new Set(['this', 'self', 'base', 'parent']);
374
+ /** Walk up AST to find enclosing class/struct/impl and return its NAME (not ID).
375
+ * Used to resolve `this.method()` / `self.method()` receiver type. */
376
+ export const findEnclosingClassName = (node) => {
377
+ let current = node.parent;
378
+ while (current) {
379
+ if (CLASS_CONTAINER_TYPES.has(current.type)) {
380
+ const nameNode = current.childForFieldName?.('name')
381
+ ?? current.children?.find((c) => c.type === 'type_identifier' || c.type === 'identifier' || c.type === 'name' || c.type === 'constant');
382
+ if (nameNode)
383
+ return nameNode.text;
384
+ }
385
+ current = current.parent;
386
+ }
387
+ return null;
388
+ };
372
389
  /** Extract function name and label from a function/method AST node (handles C/C++ qualified_identifier) */
373
390
  export const extractFunctionName = (node) => {
374
391
  let funcName = null;
@@ -34,7 +34,7 @@ try {
34
34
  Kotlin = _require('tree-sitter-kotlin');
35
35
  }
36
36
  catch { }
37
- import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, CALL_EXPRESSION_TYPES, extractCallChain, } from '../utils.js';
37
+ import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, findEnclosingClassName, SELF_RECEIVER_KEYWORDS, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, CALL_EXPRESSION_TYPES, extractCallChain, } from '../utils.js';
38
38
  import { buildTypeEnv } from '../type-env.js';
39
39
  import { isNodeExported } from '../export-detection.js';
40
40
  import { detectFrameworkFromAST } from '../framework-detection.js';
@@ -851,6 +851,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
851
851
  const callForm = callForm_pre;
852
852
  let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
853
853
  let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
854
+ // Resolve this/self/base to enclosing class name
855
+ if (!receiverTypeName && receiverName && SELF_RECEIVER_KEYWORDS.has(receiverName)) {
856
+ const className = findEnclosingClassName(callNode);
857
+ if (className)
858
+ receiverTypeName = className;
859
+ }
854
860
  let receiverCallChain;
855
861
  // When the receiver is complex (call chain or member chain),
856
862
  // extractReceiverName returns undefined. Walk the AST to extract
@@ -44,7 +44,7 @@ export declare class TsgoService {
44
44
  /** Whether the server is running and ready for queries */
45
45
  isReady(): boolean;
46
46
  /** Resolve what a symbol at a given position points to (go-to-definition) */
47
- resolveDefinition(absFilePath: string, line: number, character: number): Promise<TsgoDefinition | null>;
47
+ resolveDefinition(absFilePath: string, line: number, character: number, timeoutMs?: number): Promise<TsgoDefinition | null>;
48
48
  /** Find all references to the symbol at the given position */
49
49
  findReferences(absFilePath: string, line: number, character: number): Promise<TsgoReference[]>;
50
50
  /** Get the type signature at a position (hover) */
@@ -68,7 +68,7 @@ export class TsgoService {
68
68
  return this.ready;
69
69
  }
70
70
  /** Resolve what a symbol at a given position points to (go-to-definition) */
71
- async resolveDefinition(absFilePath, line, character) {
71
+ async resolveDefinition(absFilePath, line, character, timeoutMs = 3000) {
72
72
  if (!this.ready) {
73
73
  console.error('[tsgo-service] resolveDefinition called but not ready');
74
74
  return null;
@@ -77,7 +77,7 @@ export class TsgoService {
77
77
  const resp = await this.request('textDocument/definition', {
78
78
  textDocument: { uri: this.fileUri(absFilePath) },
79
79
  position: { line, character },
80
- }, 3000);
80
+ }, timeoutMs);
81
81
  if (!resp) {
82
82
  verbose('definition timeout', absFilePath, line, character);
83
83
  return null;
@@ -38,14 +38,10 @@ export declare class LocalBackend {
38
38
  /** Per-repo promise chain that serializes ensureFresh calls.
39
39
  * Prevents race: Call 2 skipping refresh while Call 1 is still writing. */
40
40
  private refreshLocks;
41
- /** Per-repo tsgo LSP service instances for live semantic enrichment */
42
- private tsgoServices;
43
41
  /** Per-repo in-memory embedding cache: nodeId → Float32Array (256-dim) */
44
42
  private embeddingCaches;
45
43
  /** Per-repo in-memory NL embedding cache: includes source text for match_reason */
46
44
  private nlEmbeddingCaches;
47
- /** Get (or lazily start) a tsgo LSP service for a repo. Returns null if unavailable. */
48
- private getTsgo;
49
45
  /** Get (or lazily open) the SQLite database for a repo. */
50
46
  private getDb;
51
47
  /** Load all embeddings into memory for fast vector search */
@@ -104,7 +100,7 @@ export declare class LocalBackend {
104
100
  lastCommit: string;
105
101
  stats?: any;
106
102
  }>>;
107
- /** Find the narrowest symbol node enclosing a given file position (for tsgo ref merging) */
103
+ /** Find the narrowest symbol node enclosing a given file position */
108
104
  private findNodeAtPosition;
109
105
  /** Extract signature from content. For interfaces/types, returns the full body (fields ARE the signature). */
110
106
  private extractSignature;
@@ -12,7 +12,6 @@ import * as queries from '../../core/db/queries.js';
12
12
  import { refreshFiles, refreshEmbeddings } from '../../core/incremental/refresh.js';
13
13
  import { FileSystemWatcher } from '../../core/incremental/watcher.js';
14
14
  import { toRepoRoot, toRelativeFilePath } from '../../core/incremental/types.js';
15
- import { getTsgoService, stopTsgoService } from '../../core/semantic/tsgo-service.js';
16
15
  import {} from '../../core/search/types.js';
17
16
  import { listRegisteredRepos, } from '../../storage/repo-manager.js';
18
17
  /** Quick test-file detection for filtering impact results across all supported languages */
@@ -53,29 +52,10 @@ export class LocalBackend {
53
52
  /** Per-repo promise chain that serializes ensureFresh calls.
54
53
  * Prevents race: Call 2 skipping refresh while Call 1 is still writing. */
55
54
  refreshLocks = new Map();
56
- /** Per-repo tsgo LSP service instances for live semantic enrichment */
57
- tsgoServices = new Map();
58
55
  /** Per-repo in-memory embedding cache: nodeId → Float32Array (256-dim) */
59
56
  embeddingCaches = new Map();
60
57
  /** Per-repo in-memory NL embedding cache: includes source text for match_reason */
61
58
  nlEmbeddingCaches = new Map();
62
- /** Get (or lazily start) a tsgo LSP service for a repo. Returns null if unavailable. */
63
- async getTsgo(repo) {
64
- const existing = this.tsgoServices.get(repo.id);
65
- if (existing?.isReady())
66
- return existing;
67
- try {
68
- const service = getTsgoService(repo.repoPath);
69
- if (await service.start()) {
70
- this.tsgoServices.set(repo.id, service);
71
- return service;
72
- }
73
- }
74
- catch {
75
- // tsgo not available — completely fine, graph-only mode
76
- }
77
- return null;
78
- }
79
59
  /** Get (or lazily open) the SQLite database for a repo. */
80
60
  getDb(repoId) {
81
61
  const handle = this.repos.get(repoId);
@@ -491,7 +471,7 @@ export class LocalBackend {
491
471
  stats: h.stats,
492
472
  }));
493
473
  }
494
- /** Find the narrowest symbol node enclosing a given file position (for tsgo ref merging) */
474
+ /** Find the narrowest symbol node enclosing a given file position */
495
475
  findNodeAtPosition(db, filePath, line) {
496
476
  try {
497
477
  const rows = rawQuery(db, `SELECT name, label, filePath FROM nodes
@@ -968,11 +948,6 @@ export class LocalBackend {
968
948
  }
969
949
  }
970
950
  catch { }
971
- // Warn if tsgo is unavailable — agents should know call resolution is heuristic-only
972
- const tsgo = this.tsgoServices.get(repo.id);
973
- if (!tsgo?.isReady()) {
974
- warnings.push('⚠ tsgo unavailable — call resolution uses heuristic matching (install @typescript/native-preview for compiler-verified accuracy)');
975
- }
976
951
  return warnings.length > 0 ? warnings.join('\n') + '\n\n' : '';
977
952
  }
978
953
  // ── Tool Dispatch ─────────────────────────────────────────────────
@@ -983,11 +958,6 @@ export class LocalBackend {
983
958
  // Resolve repo from optional param (re-reads registry on miss)
984
959
  const repo = await this.resolveRepo(params?.repo);
985
960
  await this.ensureFresh(repo);
986
- // Eagerly attempt tsgo start so the staleness warning is accurate
987
- // Agents can opt out with tsgo: false for faster responses
988
- if (params?.tsgo !== false) {
989
- await this.getTsgo(repo);
990
- }
991
961
  // C3: Prepend staleness warning to all tool responses
992
962
  const staleWarning = this.getStalenessWarning(repo);
993
963
  switch (method) {
@@ -1933,7 +1903,7 @@ export class LocalBackend {
1933
1903
  * File overview: list all symbols in a file with signatures and caller/callee counts.
1934
1904
  * Triggered when context() is called with file_path only (no name/uid).
1935
1905
  */
1936
- async fileOverview(repo, filePath, includeContent, enableTsgo) {
1906
+ async fileOverview(repo, filePath, includeContent) {
1937
1907
  const db = this.getDb(repo.id);
1938
1908
  // Find the file node and all symbols in it
1939
1909
  const fileNodes = findNodesByFile(db, filePath);
@@ -1955,7 +1925,7 @@ export class LocalBackend {
1955
1925
  };
1956
1926
  }
1957
1927
  // Exact single match — re-query
1958
- return this.fileOverview(repo, allFiles[0].filePath, includeContent, enableTsgo);
1928
+ return this.fileOverview(repo, allFiles[0].filePath, includeContent);
1959
1929
  }
1960
1930
  const symbols = fileNodes.filter(n => n.label !== 'File' && n.label !== 'Folder');
1961
1931
  // Fallback: file has no extractable symbols (e.g., imperative entry points, config files)
@@ -2004,23 +1974,13 @@ export class LocalBackend {
2004
1974
  for (const r of memberRows)
2005
1975
  communityMap.set(r.sourceId, r.heuristicLabel);
2006
1976
  }
2007
- // Optionally enrich with tsgo hover for real type signatures
1977
+ // Extract signatures from stored content using regex-based extraction
2008
1978
  const signatures = new Map();
2009
- if (enableTsgo !== false) {
2010
- const tsgo = await this.getTsgo(repo);
2011
- if (tsgo) {
2012
- const absBase = repo.repoPath;
2013
- for (const sym of symbols.slice(0, 30)) { // cap to avoid slow
2014
- if (sym.startLine != null && /\.(ts|tsx|js|jsx|mts|mjs)$/.test(sym.filePath)) {
2015
- try {
2016
- const hover = await tsgo.getHover(path.resolve(absBase, sym.filePath), sym.startLine - 1, 0);
2017
- if (hover) {
2018
- const clean = hover.replace(/```\w*\n?/g, '').replace(/```/g, '').trim();
2019
- signatures.set(sym.id, clean);
2020
- }
2021
- }
2022
- catch { }
2023
- }
1979
+ for (const sym of symbols) {
1980
+ if (sym.content) {
1981
+ const sig = this.extractSignature(String(sym.content), sym.name, sym.label);
1982
+ if (sig && sig !== sym.name) {
1983
+ signatures.set(sym.id, sig);
2024
1984
  }
2025
1985
  }
2026
1986
  }
@@ -2061,7 +2021,7 @@ export class LocalBackend {
2061
2021
  const { name, uid, file_path, include_content } = params;
2062
2022
  // File overview mode: file_path only, no symbol name
2063
2023
  if (file_path && !name && !uid) {
2064
- return this.fileOverview(repo, file_path, include_content, params.tsgo);
2024
+ return this.fileOverview(repo, file_path, include_content);
2065
2025
  }
2066
2026
  if (!name && !uid) {
2067
2027
  return { error: 'Either "name", "uid", or "file_path" parameter is required.' };
@@ -2088,47 +2048,7 @@ export class LocalBackend {
2088
2048
  }
2089
2049
  symbols = matchedNodes.map(n => ({ id: n.id, name: n.name, type: n.label, filePath: n.filePath, startLine: n.startLine, endLine: n.endLine, content: n.content }));
2090
2050
  }
2091
- // Symbol not found in graph — try tsgo live lookup for recently created symbols
2092
2051
  if (symbols.length === 0) {
2093
- try {
2094
- const tsgoFallback = params?.tsgo !== false ? await this.getTsgo(repo) : null;
2095
- if (tsgoFallback && name) {
2096
- // Search FTS for files that mention this symbol, then ask tsgo for the type
2097
- const searchResults = searchFTS(db, name, 5);
2098
- for (const sr of searchResults) {
2099
- if (sr.filePath && /\.(ts|tsx|js|jsx|mts|mjs)$/.test(sr.filePath)) {
2100
- // Look up the full node to get startLine/endLine
2101
- const fullNode = getNode(db, sr.id);
2102
- const startLine = fullNode?.startLine ?? 1;
2103
- const absPath = path.resolve(repo.repoPath, sr.filePath);
2104
- const hover = await tsgoFallback.getHover(absPath, startLine - 1, 0);
2105
- if (hover) {
2106
- // Found it via tsgo — return a minimal context with live data
2107
- const cleaned = hover.replace(/^```\w*\n?/, '').replace(/\n?```$/, '').trim();
2108
- return {
2109
- status: 'found',
2110
- symbol: {
2111
- uid: sr.id,
2112
- name: sr.name ?? name,
2113
- kind: sr.label ?? 'Symbol',
2114
- filePath: sr.filePath,
2115
- startLine: fullNode?.startLine ?? null,
2116
- endLine: fullNode?.endLine ?? null,
2117
- signature: cleaned || name,
2118
- _source: 'tsgo-live',
2119
- },
2120
- incoming: {},
2121
- outgoing: {},
2122
- processes: [],
2123
- };
2124
- }
2125
- }
2126
- }
2127
- }
2128
- }
2129
- catch {
2130
- // tsgo fallback lookup failed — return normal not-found error
2131
- }
2132
2052
  return { error: `Symbol '${name || uid}' not found` };
2133
2053
  }
2134
2054
  // Step 2: Disambiguation
@@ -2160,41 +2080,6 @@ export class LocalBackend {
2160
2080
  });
2161
2081
  }
2162
2082
  }
2163
- // Enrich incoming refs with tsgo live references (supplements stale graph)
2164
- // Agents can disable with tsgo: false for faster responses
2165
- const tsgo = params?.tsgo !== false ? await this.getTsgo(repo) : null;
2166
- if (tsgo && sym.filePath && /\.(ts|tsx|js|jsx|mts|mjs)$/.test(sym.filePath)) {
2167
- try {
2168
- const absPath = path.resolve(repo.repoPath, sym.filePath);
2169
- const liveRefs = await tsgo.findReferences(absPath, (sym.startLine ?? 1) - 1, 0);
2170
- // Merge live refs with graph refs — live refs may contain callers not in the graph
2171
- for (const ref of liveRefs) {
2172
- // Skip self-references (same file, same line as the symbol definition)
2173
- if (ref.filePath === sym.filePath && Math.abs(ref.line - ((sym.startLine ?? 1) - 1)) <= 1)
2174
- continue;
2175
- // Check if this reference is already known from the graph
2176
- const alreadyKnown = incomingRows.some(row => row.filePath === ref.filePath && Math.abs((row.startLine ?? 0) - (ref.line + 1)) <= 2);
2177
- if (!alreadyKnown) {
2178
- // Find the enclosing function at this reference location
2179
- const refNode = this.findNodeAtPosition(db, ref.filePath, ref.line);
2180
- if (refNode) {
2181
- incomingRows.push({
2182
- relType: 'CALLS',
2183
- uid: '',
2184
- name: refNode.name,
2185
- filePath: ref.filePath,
2186
- kind: refNode.label,
2187
- startLine: ref.line + 1,
2188
- reason: 'tsgo-live',
2189
- });
2190
- }
2191
- }
2192
- }
2193
- }
2194
- catch {
2195
- // tsgo reference lookup failed — non-fatal, graph results still available
2196
- }
2197
- }
2198
2083
  // Supplement callers from refs table (catches callers the graph missed)
2199
2084
  try {
2200
2085
  const refCallers = findRefsBySymbol(db, sym.name, 200);
@@ -2266,25 +2151,9 @@ export class LocalBackend {
2266
2151
  }
2267
2152
  return cats;
2268
2153
  };
2269
- // Always extract signature for compact display
2154
+ // Extract signature for compact display
2270
2155
  const rawContent = sym.content || '';
2271
- let signature = rawContent ? this.extractSignature(String(rawContent), sym.name, sym.type) : sym.name;
2272
- // Enrich with tsgo for live type information (TS/JS files only)
2273
- if (tsgo && sym.filePath && /\.(ts|tsx|js|jsx|mts|mjs)$/.test(sym.filePath)) {
2274
- try {
2275
- const absPath = path.resolve(repo.repoPath, sym.filePath);
2276
- const hover = await tsgo.getHover(absPath, (sym.startLine ?? 1) - 1, 0);
2277
- if (hover) {
2278
- // Strip markdown code fences if present (```typescript ... ```)
2279
- const cleaned = hover.replace(/^```\w*\n?/, '').replace(/\n?```$/, '').trim();
2280
- if (cleaned)
2281
- signature = cleaned;
2282
- }
2283
- }
2284
- catch {
2285
- // tsgo hover failed — non-fatal, use regex-based signature
2286
- }
2287
- }
2156
+ const signature = rawContent ? this.extractSignature(String(rawContent), sym.name, sym.type) : sym.name;
2288
2157
  return {
2289
2158
  status: 'found',
2290
2159
  symbol: {
@@ -2954,12 +2823,6 @@ export class LocalBackend {
2954
2823
  for (const watcher of this.watchers.values())
2955
2824
  watcher.stop();
2956
2825
  this.watchers.clear();
2957
- // Stop all per-repo tsgo LSP services
2958
- for (const service of this.tsgoServices.values()) {
2959
- service.stop();
2960
- }
2961
- this.tsgoServices.clear();
2962
- stopTsgoService(); // also stop the global singleton
2963
2826
  closeDb(); // close all SQLite connections
2964
2827
  // Note: we intentionally do NOT call disposeEmbedder() here.
2965
2828
  // ONNX Runtime's native cleanup segfaults on macOS and some Linux configs,
package/dist/mcp/tools.js CHANGED
@@ -113,7 +113,6 @@ Handles disambiguation: if multiple symbols share the same name, returns candida
113
113
  uid: { type: 'string', description: 'Direct symbol UID from prior tool results (zero-ambiguity lookup)' },
114
114
  file_path: { type: 'string', description: 'File path to disambiguate common names' },
115
115
  include_content: { type: 'boolean', description: 'Include full symbol source code (default: false)', default: false },
116
- tsgo: { type: 'boolean', description: 'Enable tsgo LSP for live compiler-verified callers and type signatures. More accurate but adds ~1-2s latency. Default: true. Set false for speed.', default: true },
117
116
  repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
118
117
  },
119
118
  required: [],
@@ -24,8 +24,6 @@ export interface PipelineResult {
24
24
  totalFileCount: number;
25
25
  communityResult?: CommunityDetectionResult;
26
26
  processResult?: ProcessDetectionResult;
27
- /** Whether tsgo LSP was used for high-confidence call resolution */
28
- tsgoEnabled: boolean;
29
27
  }
30
28
  export interface SerializablePipelineResult {
31
29
  nodes: GraphNode[];
@@ -20,6 +20,5 @@ export const deserializePipelineResult = (serialized, createGraph) => {
20
20
  graph,
21
21
  repoPath: serialized.repoPath,
22
22
  totalFileCount: serialized.totalFileCount,
23
- tsgoEnabled: false,
24
23
  };
25
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zuvia-software-solutions/code-mapper",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
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",