@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.
- package/dist/cli/analyze.d.ts +0 -1
- package/dist/cli/analyze.js +1 -2
- package/dist/cli/index.js +0 -3
- package/dist/cli/refresh.js +0 -3
- package/dist/cli/tool.d.ts +0 -2
- package/dist/cli/tool.js +0 -2
- package/dist/core/embeddings/nl-embed-worker.d.ts +1 -1
- package/dist/core/embeddings/nl-embed-worker.js +1 -1
- package/dist/core/embeddings/nl-embedder.js +1 -1
- package/dist/core/incremental/refresh.d.ts +2 -4
- package/dist/core/incremental/refresh.js +20 -135
- package/dist/core/incremental/types.d.ts +0 -1
- package/dist/core/incremental/types.js +0 -1
- package/dist/core/ingestion/call-processor.d.ts +2 -3
- package/dist/core/ingestion/call-processor.js +11 -237
- package/dist/core/ingestion/pipeline.d.ts +1 -3
- package/dist/core/ingestion/pipeline.js +11 -33
- package/dist/core/ingestion/resolution-context.d.ts +1 -1
- package/dist/core/ingestion/resolution-context.js +0 -1
- package/dist/core/ingestion/utils.d.ts +5 -0
- package/dist/core/ingestion/utils.js +17 -0
- package/dist/core/ingestion/workers/parse-worker.js +7 -1
- package/dist/core/semantic/tsgo-service.d.ts +1 -1
- package/dist/core/semantic/tsgo-service.js +2 -2
- package/dist/mcp/local/local-backend.d.ts +1 -5
- package/dist/mcp/local/local-backend.js +12 -149
- package/dist/mcp/tools.js +0 -1
- package/dist/types/pipeline.d.ts +0 -2
- package/dist/types/pipeline.js +0 -1
- package/package.json +1 -1
package/dist/cli/analyze.d.ts
CHANGED
package/dist/cli/analyze.js
CHANGED
|
@@ -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
|
-
}
|
|
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>')
|
package/dist/cli/refresh.js
CHANGED
|
@@ -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
|
package/dist/cli/tool.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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 —
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -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[]
|
|
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() →
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
|
|
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() →
|
|
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
|
|
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
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
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 = '
|
|
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
|
-
},
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
1977
|
+
// Extract signatures from stored content using regex-based extraction
|
|
2008
1978
|
const signatures = new Map();
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
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
|
|
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
|
-
//
|
|
2154
|
+
// Extract signature for compact display
|
|
2270
2155
|
const rawContent = sym.content || '';
|
|
2271
|
-
|
|
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: [],
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -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[];
|
package/dist/types/pipeline.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuvia-software-solutions/code-mapper",
|
|
3
|
-
"version": "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",
|