@zuvia-software-solutions/code-mapper 1.4.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +73 -82
- package/dist/cli/augment.js +0 -2
- package/dist/cli/eval-server.d.ts +2 -2
- package/dist/cli/eval-server.js +6 -6
- package/dist/cli/index.js +6 -10
- package/dist/cli/mcp.d.ts +1 -3
- package/dist/cli/mcp.js +3 -3
- package/dist/cli/refresh.d.ts +2 -2
- package/dist/cli/refresh.js +24 -29
- package/dist/cli/status.js +4 -13
- package/dist/cli/tool.d.ts +5 -4
- package/dist/cli/tool.js +8 -10
- package/dist/config/ignore-service.js +14 -34
- package/dist/core/augmentation/engine.js +53 -83
- package/dist/core/db/adapter.d.ts +99 -0
- package/dist/core/db/adapter.js +402 -0
- package/dist/core/db/graph-loader.d.ts +27 -0
- package/dist/core/db/graph-loader.js +148 -0
- package/dist/core/db/queries.d.ts +160 -0
- package/dist/core/db/queries.js +441 -0
- package/dist/core/db/schema.d.ts +108 -0
- package/dist/core/db/schema.js +136 -0
- package/dist/core/embeddings/embedder.d.ts +21 -12
- package/dist/core/embeddings/embedder.js +104 -50
- package/dist/core/embeddings/embedding-pipeline.d.ts +48 -22
- package/dist/core/embeddings/embedding-pipeline.js +220 -262
- package/dist/core/embeddings/text-generator.js +4 -19
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/graph.d.ts +1 -1
- package/dist/core/graph/graph.js +1 -0
- package/dist/core/graph/types.d.ts +11 -9
- package/dist/core/graph/types.js +4 -1
- package/dist/core/incremental/refresh.d.ts +46 -0
- package/dist/core/incremental/refresh.js +503 -0
- package/dist/core/incremental/types.d.ts +2 -1
- package/dist/core/incremental/types.js +42 -44
- package/dist/core/ingestion/ast-cache.js +1 -0
- package/dist/core/ingestion/call-processor.d.ts +15 -3
- package/dist/core/ingestion/call-processor.js +448 -60
- package/dist/core/ingestion/cluster-enricher.d.ts +1 -1
- package/dist/core/ingestion/cluster-enricher.js +2 -0
- package/dist/core/ingestion/community-processor.d.ts +1 -1
- package/dist/core/ingestion/community-processor.js +8 -3
- package/dist/core/ingestion/export-detection.d.ts +1 -1
- package/dist/core/ingestion/export-detection.js +1 -1
- package/dist/core/ingestion/filesystem-walker.js +1 -1
- package/dist/core/ingestion/heritage-processor.d.ts +2 -2
- package/dist/core/ingestion/heritage-processor.js +22 -11
- package/dist/core/ingestion/import-processor.d.ts +2 -2
- package/dist/core/ingestion/import-processor.js +24 -9
- package/dist/core/ingestion/language-config.js +7 -4
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +23 -11
- package/dist/core/ingestion/named-binding-extraction.js +5 -5
- package/dist/core/ingestion/parsing-processor.d.ts +4 -4
- package/dist/core/ingestion/parsing-processor.js +26 -18
- package/dist/core/ingestion/pipeline.d.ts +4 -2
- package/dist/core/ingestion/pipeline.js +50 -20
- package/dist/core/ingestion/process-processor.d.ts +2 -2
- package/dist/core/ingestion/process-processor.js +28 -14
- package/dist/core/ingestion/resolution-context.d.ts +1 -1
- package/dist/core/ingestion/resolution-context.js +14 -4
- package/dist/core/ingestion/resolvers/csharp.js +4 -3
- package/dist/core/ingestion/resolvers/go.js +3 -1
- package/dist/core/ingestion/resolvers/jvm.js +13 -4
- package/dist/core/ingestion/resolvers/standard.js +2 -2
- package/dist/core/ingestion/resolvers/utils.js +6 -2
- package/dist/core/ingestion/route-stitcher.d.ts +15 -0
- package/dist/core/ingestion/route-stitcher.js +92 -0
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +3 -2
- package/dist/core/ingestion/symbol-table.d.ts +2 -0
- package/dist/core/ingestion/symbol-table.js +5 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
- package/dist/core/ingestion/tree-sitter-queries.js +177 -0
- package/dist/core/ingestion/type-env.js +20 -0
- package/dist/core/ingestion/type-extractors/csharp.js +4 -3
- package/dist/core/ingestion/type-extractors/go.js +23 -12
- package/dist/core/ingestion/type-extractors/php.js +18 -10
- package/dist/core/ingestion/type-extractors/ruby.js +15 -3
- package/dist/core/ingestion/type-extractors/rust.js +3 -2
- package/dist/core/ingestion/type-extractors/shared.js +3 -2
- package/dist/core/ingestion/type-extractors/typescript.js +11 -5
- package/dist/core/ingestion/utils.d.ts +27 -4
- package/dist/core/ingestion/utils.js +145 -100
- package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +97 -29
- package/dist/core/ingestion/workers/worker-pool.js +3 -0
- package/dist/core/search/bm25-index.d.ts +15 -8
- package/dist/core/search/bm25-index.js +48 -98
- package/dist/core/search/hybrid-search.d.ts +9 -3
- package/dist/core/search/hybrid-search.js +30 -25
- package/dist/core/search/reranker.js +9 -7
- package/dist/core/search/types.d.ts +0 -4
- package/dist/core/semantic/tsgo-service.d.ts +7 -1
- package/dist/core/semantic/tsgo-service.js +165 -66
- package/dist/lib/tsgo-test.d.ts +2 -0
- package/dist/lib/tsgo-test.js +6 -0
- package/dist/lib/type-utils.d.ts +25 -0
- package/dist/lib/type-utils.js +22 -0
- package/dist/lib/utils.d.ts +3 -2
- package/dist/lib/utils.js +3 -2
- package/dist/mcp/compatible-stdio-transport.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +29 -56
- package/dist/mcp/local/local-backend.js +808 -1118
- package/dist/mcp/resources.js +35 -25
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +5 -5
- package/dist/mcp/tools.js +24 -25
- package/dist/storage/repo-manager.d.ts +2 -12
- package/dist/storage/repo-manager.js +1 -47
- package/dist/types/pipeline.d.ts +8 -5
- package/dist/types/pipeline.js +5 -0
- package/package.json +18 -11
- package/dist/cli/serve.d.ts +0 -5
- package/dist/cli/serve.js +0 -8
- package/dist/core/incremental/child-process.d.ts +0 -8
- package/dist/core/incremental/child-process.js +0 -649
- package/dist/core/incremental/refresh-coordinator.d.ts +0 -32
- package/dist/core/incremental/refresh-coordinator.js +0 -147
- package/dist/core/lbug/csv-generator.d.ts +0 -28
- package/dist/core/lbug/csv-generator.js +0 -355
- package/dist/core/lbug/lbug-adapter.d.ts +0 -96
- package/dist/core/lbug/lbug-adapter.js +0 -753
- package/dist/core/lbug/schema.d.ts +0 -46
- package/dist/core/lbug/schema.js +0 -402
- package/dist/mcp/core/embedder.d.ts +0 -24
- package/dist/mcp/core/embedder.js +0 -168
- package/dist/mcp/core/lbug-adapter.d.ts +0 -29
- package/dist/mcp/core/lbug-adapter.js +0 -330
- package/dist/server/api.d.ts +0 -5
- package/dist/server/api.js +0 -340
- package/dist/server/mcp-http.d.ts +0 -7
- package/dist/server/mcp-http.js +0 -95
- package/models/mlx-embedder.py +0 -185
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
*
|
|
14
14
|
* Optional — only starts if the `@typescript/native-preview` package is installed.
|
|
15
15
|
*/
|
|
16
|
-
import { spawn } from 'child_process';
|
|
16
|
+
import { spawn, execFileSync } from 'child_process';
|
|
17
|
+
import { createRequire } from 'module';
|
|
17
18
|
import fs from 'fs';
|
|
18
19
|
import path from 'path';
|
|
20
|
+
const esmRequire = createRequire(import.meta.url);
|
|
21
|
+
function verbose(...args) {
|
|
22
|
+
if (process.env['CODE_MAPPER_VERBOSE'])
|
|
23
|
+
console.error('[tsgo]', ...args);
|
|
24
|
+
}
|
|
19
25
|
// ---------------------------------------------------------------------------
|
|
20
26
|
// LSP message helpers
|
|
21
27
|
// ---------------------------------------------------------------------------
|
|
@@ -28,8 +34,9 @@ function encodeLSP(msg) {
|
|
|
28
34
|
// ---------------------------------------------------------------------------
|
|
29
35
|
export class TsgoService {
|
|
30
36
|
process = null;
|
|
31
|
-
buf = '';
|
|
32
37
|
responses = new Map();
|
|
38
|
+
/** Pending resolve callbacks keyed by request ID — event-driven, no polling */
|
|
39
|
+
pending = new Map();
|
|
33
40
|
nextId = 1;
|
|
34
41
|
projectRoot;
|
|
35
42
|
tsgoPath = null;
|
|
@@ -52,32 +59,50 @@ export class TsgoService {
|
|
|
52
59
|
this.initPromise = this.doStart();
|
|
53
60
|
return this.initPromise;
|
|
54
61
|
}
|
|
62
|
+
/** Get the resolved project root (where tsconfig.json was found) */
|
|
63
|
+
getProjectRoot() {
|
|
64
|
+
return this.projectRoot;
|
|
65
|
+
}
|
|
55
66
|
/** Whether the server is running and ready for queries */
|
|
56
67
|
isReady() {
|
|
57
68
|
return this.ready;
|
|
58
69
|
}
|
|
59
70
|
/** Resolve what a symbol at a given position points to (go-to-definition) */
|
|
60
71
|
async resolveDefinition(absFilePath, line, character) {
|
|
61
|
-
if (!this.ready)
|
|
72
|
+
if (!this.ready) {
|
|
73
|
+
console.error('[tsgo-service] resolveDefinition called but not ready');
|
|
62
74
|
return null;
|
|
75
|
+
}
|
|
63
76
|
await this.ensureOpen(absFilePath);
|
|
64
77
|
const resp = await this.request('textDocument/definition', {
|
|
65
78
|
textDocument: { uri: this.fileUri(absFilePath) },
|
|
66
79
|
position: { line, character },
|
|
67
|
-
});
|
|
68
|
-
if (!resp)
|
|
80
|
+
}, 3000);
|
|
81
|
+
if (!resp) {
|
|
82
|
+
verbose('definition timeout', absFilePath, line, character);
|
|
69
83
|
return null;
|
|
70
|
-
|
|
71
|
-
if (
|
|
84
|
+
}
|
|
85
|
+
if (resp['error']) {
|
|
86
|
+
verbose('definition error', resp['error']);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const result = resp['result'];
|
|
90
|
+
if (!result) {
|
|
91
|
+
verbose('definition null result', absFilePath, line);
|
|
72
92
|
return null;
|
|
93
|
+
}
|
|
73
94
|
const defs = Array.isArray(result) ? result : [result];
|
|
74
|
-
if (defs.length === 0)
|
|
95
|
+
if (defs.length === 0) {
|
|
96
|
+
verbose('definition empty', absFilePath, line);
|
|
75
97
|
return null;
|
|
98
|
+
}
|
|
76
99
|
const d = defs[0];
|
|
77
100
|
const uri = d.uri || d.targetUri || '';
|
|
78
101
|
const range = d.range || d.targetSelectionRange;
|
|
79
|
-
if (!uri || !range)
|
|
102
|
+
if (!uri || !range) {
|
|
103
|
+
verbose('definition missing uri/range', d);
|
|
80
104
|
return null;
|
|
105
|
+
}
|
|
81
106
|
return {
|
|
82
107
|
filePath: this.uriToRelative(uri),
|
|
83
108
|
line: range.start.line,
|
|
@@ -94,14 +119,17 @@ export class TsgoService {
|
|
|
94
119
|
position: { line, character },
|
|
95
120
|
context: { includeDeclaration: false },
|
|
96
121
|
});
|
|
97
|
-
if (!resp?.result)
|
|
122
|
+
if (!resp?.['result'])
|
|
98
123
|
return [];
|
|
99
|
-
const refs = resp
|
|
100
|
-
return refs.map((r) =>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
124
|
+
const refs = resp['result'];
|
|
125
|
+
return refs.map((r) => {
|
|
126
|
+
const range = r['range'];
|
|
127
|
+
return {
|
|
128
|
+
filePath: this.uriToRelative(r['uri']),
|
|
129
|
+
line: range.start.line,
|
|
130
|
+
character: range.start.character,
|
|
131
|
+
};
|
|
132
|
+
});
|
|
105
133
|
}
|
|
106
134
|
/** Get the type signature at a position (hover) */
|
|
107
135
|
async getHover(absFilePath, line, character) {
|
|
@@ -112,17 +140,23 @@ export class TsgoService {
|
|
|
112
140
|
textDocument: { uri: this.fileUri(absFilePath) },
|
|
113
141
|
position: { line, character },
|
|
114
142
|
});
|
|
115
|
-
if (!resp?.result)
|
|
143
|
+
if (!resp?.['result'])
|
|
116
144
|
return null;
|
|
117
|
-
const contents = resp
|
|
145
|
+
const contents = resp['result']['contents'];
|
|
118
146
|
if (!contents)
|
|
119
147
|
return null;
|
|
120
148
|
if (typeof contents === 'string')
|
|
121
149
|
return contents;
|
|
122
|
-
if (contents.value)
|
|
123
|
-
return contents
|
|
124
|
-
|
|
125
|
-
|
|
150
|
+
if (typeof contents === 'object' && !Array.isArray(contents) && 'value' in contents) {
|
|
151
|
+
return contents['value'];
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(contents)) {
|
|
154
|
+
return contents.map((c) => {
|
|
155
|
+
if (typeof c === 'object' && c !== null && 'value' in c)
|
|
156
|
+
return c['value'];
|
|
157
|
+
return c;
|
|
158
|
+
}).join('\n');
|
|
159
|
+
}
|
|
126
160
|
return null;
|
|
127
161
|
}
|
|
128
162
|
/** Notify tsgo that a file changed (call after editing a file) */
|
|
@@ -158,6 +192,30 @@ export class TsgoService {
|
|
|
158
192
|
this.openFiles.delete(uri);
|
|
159
193
|
}
|
|
160
194
|
}
|
|
195
|
+
/** Pre-open multiple files in one batch (fire-and-forget, no await per file) */
|
|
196
|
+
preOpenFiles(absFilePaths) {
|
|
197
|
+
if (!this.ready)
|
|
198
|
+
return;
|
|
199
|
+
for (const absPath of absFilePaths) {
|
|
200
|
+
const uri = this.fileUri(absPath);
|
|
201
|
+
if (this.openFiles.has(uri))
|
|
202
|
+
continue;
|
|
203
|
+
try {
|
|
204
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
205
|
+
this.send({
|
|
206
|
+
jsonrpc: '2.0',
|
|
207
|
+
method: 'textDocument/didOpen',
|
|
208
|
+
params: {
|
|
209
|
+
textDocument: { uri, languageId: 'typescript', version: 1, text: content },
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
this.openFiles.add(uri);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// File may not exist or be unreadable — skip silently
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
161
219
|
/** Stop the tsgo process */
|
|
162
220
|
stop() {
|
|
163
221
|
if (this.process && !this.process.killed) {
|
|
@@ -177,14 +235,19 @@ export class TsgoService {
|
|
|
177
235
|
this.initPromise = null;
|
|
178
236
|
this.openFiles.clear();
|
|
179
237
|
this.responses.clear();
|
|
238
|
+
// Resolve all pending requests as null (timed out)
|
|
239
|
+
for (const cb of this.pending.values()) {
|
|
240
|
+
cb({ id: -1, error: { message: 'tsgo stopped' } });
|
|
241
|
+
}
|
|
242
|
+
this.pending.clear();
|
|
180
243
|
}
|
|
181
244
|
// ── Internal ────────────────────────────────────────────────────────
|
|
182
245
|
async doStart() {
|
|
183
|
-
// Find the tsgo binary
|
|
246
|
+
// Find the tsgo binary — fail fast if not found
|
|
184
247
|
this.tsgoPath = this.findTsgoBinary();
|
|
185
248
|
if (!this.tsgoPath) {
|
|
186
|
-
|
|
187
|
-
|
|
249
|
+
throw new Error('tsgo binary not found. Install: npm install -D @typescript/native-preview\n' +
|
|
250
|
+
'Or remove --tsgo flag to disable semantic resolution.');
|
|
188
251
|
}
|
|
189
252
|
try {
|
|
190
253
|
this.process = spawn(this.tsgoPath, ['--lsp', '--stdio'], {
|
|
@@ -192,8 +255,13 @@ export class TsgoService {
|
|
|
192
255
|
cwd: this.projectRoot,
|
|
193
256
|
});
|
|
194
257
|
this.process.stdout.on('data', (chunk) => this.onData(chunk));
|
|
195
|
-
this.process.stderr.on('data', () => {
|
|
196
|
-
|
|
258
|
+
this.process.stderr.on('data', (chunk) => {
|
|
259
|
+
const msg = chunk.toString().trim();
|
|
260
|
+
if (msg)
|
|
261
|
+
console.error(`[tsgo-service] stderr: ${msg}`);
|
|
262
|
+
});
|
|
263
|
+
this.process.on('exit', (code, signal) => {
|
|
264
|
+
console.error(`[tsgo-service] process exited (code=${code}, signal=${signal})`);
|
|
197
265
|
this.ready = false;
|
|
198
266
|
this.process = null;
|
|
199
267
|
});
|
|
@@ -210,9 +278,8 @@ export class TsgoService {
|
|
|
210
278
|
rootUri: `file://${this.projectRoot}`,
|
|
211
279
|
});
|
|
212
280
|
if (!initResp) {
|
|
213
|
-
console.error('Code Mapper: tsgo LSP initialize timed out');
|
|
214
281
|
this.stop();
|
|
215
|
-
|
|
282
|
+
throw new Error('tsgo LSP initialize timed out — the project may be too large or tsgo crashed');
|
|
216
283
|
}
|
|
217
284
|
// Send initialized notification
|
|
218
285
|
this.send({ jsonrpc: '2.0', method: 'initialized', params: {} });
|
|
@@ -227,51 +294,73 @@ export class TsgoService {
|
|
|
227
294
|
}
|
|
228
295
|
}
|
|
229
296
|
findTsgoBinary() {
|
|
230
|
-
// Try resolving via the @typescript/native-preview package
|
|
297
|
+
// Try resolving via the @typescript/native-preview platform package
|
|
231
298
|
try {
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
const pkgName = `@typescript/native-preview-${platform}-${arch}`;
|
|
235
|
-
const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
|
|
299
|
+
const pkgName = `@typescript/native-preview-${process.platform}-${process.arch}`;
|
|
300
|
+
const pkgJsonPath = esmRequire.resolve(`${pkgName}/package.json`);
|
|
236
301
|
const exe = path.join(path.dirname(pkgJsonPath), 'lib', 'tsgo');
|
|
237
302
|
if (fs.existsSync(exe))
|
|
238
303
|
return exe;
|
|
239
304
|
}
|
|
240
305
|
catch { }
|
|
241
|
-
// Try
|
|
306
|
+
// Try which/where
|
|
242
307
|
try {
|
|
243
|
-
const
|
|
244
|
-
const result = execFileSync(
|
|
308
|
+
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
309
|
+
const result = execFileSync(cmd, ['tsgo'], { encoding: 'utf-8', timeout: 3000 }).trim();
|
|
245
310
|
if (result && fs.existsSync(result))
|
|
246
311
|
return result;
|
|
247
312
|
}
|
|
248
313
|
catch { }
|
|
249
314
|
return null;
|
|
250
315
|
}
|
|
316
|
+
rawBuf = Buffer.alloc(0);
|
|
251
317
|
onData(chunk) {
|
|
252
|
-
|
|
318
|
+
// Accumulate raw bytes — Content-Length is byte count, not character count
|
|
319
|
+
this.rawBuf = Buffer.concat([this.rawBuf, chunk]);
|
|
253
320
|
while (true) {
|
|
254
|
-
|
|
255
|
-
|
|
321
|
+
// Find header/body separator
|
|
322
|
+
const sep = this.rawBuf.indexOf('\r\n\r\n');
|
|
323
|
+
if (sep === -1)
|
|
256
324
|
break;
|
|
257
|
-
|
|
258
|
-
|
|
325
|
+
// Parse Content-Length from header section
|
|
326
|
+
const header = this.rawBuf.subarray(0, sep).toString('ascii');
|
|
327
|
+
const lenMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
328
|
+
if (!lenMatch?.[1]) {
|
|
329
|
+
// Malformed header — skip past separator and retry
|
|
330
|
+
this.rawBuf = this.rawBuf.subarray(sep + 4);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const bodyLen = parseInt(lenMatch[1], 10);
|
|
334
|
+
const bodyStart = sep + 4; // after \r\n\r\n
|
|
335
|
+
// Wait for full body to arrive
|
|
336
|
+
if (this.rawBuf.length < bodyStart + bodyLen)
|
|
259
337
|
break;
|
|
260
|
-
|
|
261
|
-
|
|
338
|
+
// Extract body bytes and advance buffer
|
|
339
|
+
const bodyBuf = this.rawBuf.subarray(bodyStart, bodyStart + bodyLen);
|
|
340
|
+
this.rawBuf = this.rawBuf.subarray(bodyStart + bodyLen);
|
|
262
341
|
try {
|
|
263
|
-
const msg = JSON.parse(
|
|
264
|
-
if (msg
|
|
342
|
+
const msg = JSON.parse(bodyBuf.toString('utf-8'));
|
|
343
|
+
if (msg['id'] !== undefined && msg['method']) {
|
|
265
344
|
// Server request (e.g., client/registerCapability) — acknowledge
|
|
266
|
-
this.send({ jsonrpc: '2.0', id: msg
|
|
345
|
+
this.send({ jsonrpc: '2.0', id: msg['id'], result: null });
|
|
267
346
|
}
|
|
268
|
-
else if (msg
|
|
269
|
-
// Response to our request
|
|
270
|
-
|
|
347
|
+
else if (msg['id'] !== undefined) {
|
|
348
|
+
// Response to our request — dispatch to pending callback immediately
|
|
349
|
+
const id = msg['id'];
|
|
350
|
+
const cb = this.pending.get(id);
|
|
351
|
+
if (cb) {
|
|
352
|
+
cb(msg);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
// Callback not registered yet — store for pickup
|
|
356
|
+
this.responses.set(id, msg);
|
|
357
|
+
}
|
|
271
358
|
}
|
|
272
359
|
// Notifications (no id, has method) are ignored
|
|
273
360
|
}
|
|
274
|
-
catch {
|
|
361
|
+
catch (err) {
|
|
362
|
+
console.error(`[tsgo-service] onData parse error: ${err instanceof Error ? err.message : err}`);
|
|
363
|
+
}
|
|
275
364
|
}
|
|
276
365
|
}
|
|
277
366
|
send(msg) {
|
|
@@ -280,24 +369,31 @@ export class TsgoService {
|
|
|
280
369
|
try {
|
|
281
370
|
this.process.stdin.write(encodeLSP(msg));
|
|
282
371
|
}
|
|
283
|
-
catch {
|
|
372
|
+
catch (err) {
|
|
373
|
+
console.error(`[tsgo-service] send error: ${err instanceof Error ? err.message : err}`);
|
|
374
|
+
}
|
|
284
375
|
}
|
|
285
|
-
request(method, params, timeoutMs =
|
|
376
|
+
request(method, params, timeoutMs = 30000) {
|
|
286
377
|
const id = this.nextId++;
|
|
287
378
|
this.send({ jsonrpc: '2.0', id, method, params });
|
|
288
379
|
return new Promise((resolve) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
380
|
+
const timer = setTimeout(() => {
|
|
381
|
+
this.pending.delete(id);
|
|
382
|
+
resolve(null);
|
|
383
|
+
}, timeoutMs);
|
|
384
|
+
this.pending.set(id, (resp) => {
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
this.pending.delete(id);
|
|
387
|
+
resolve(resp);
|
|
388
|
+
});
|
|
389
|
+
// Check if response already arrived before we registered the callback
|
|
390
|
+
if (this.responses.has(id)) {
|
|
391
|
+
const resp = this.responses.get(id);
|
|
392
|
+
this.responses.delete(id);
|
|
393
|
+
clearTimeout(timer);
|
|
394
|
+
this.pending.delete(id);
|
|
395
|
+
resolve(resp);
|
|
396
|
+
}
|
|
301
397
|
});
|
|
302
398
|
}
|
|
303
399
|
async ensureOpen(absFilePath) {
|
|
@@ -319,8 +415,11 @@ export class TsgoService {
|
|
|
319
415
|
},
|
|
320
416
|
});
|
|
321
417
|
this.openFiles.add(uri);
|
|
418
|
+
verbose('opened', absFilePath);
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
verbose('ensureOpen FAILED', absFilePath, err instanceof Error ? err.message : err);
|
|
322
422
|
}
|
|
323
|
-
catch { }
|
|
324
423
|
}
|
|
325
424
|
fileUri(absPath) {
|
|
326
425
|
return `file://${absPath}`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Shared type-level utilities — single source of truth.
|
|
3
|
+
*
|
|
4
|
+
* Brand<T, B> — nominal branding for stringly-typed values
|
|
5
|
+
* assertNever() — exhaustiveness guard for switch/if chains
|
|
6
|
+
*/
|
|
7
|
+
declare const __brand: unique symbol;
|
|
8
|
+
/**
|
|
9
|
+
* Nominal brand: `Brand<string, 'Foo'>` is assignable FROM nothing
|
|
10
|
+
* except via an explicit constructor that validates at runtime.
|
|
11
|
+
*/
|
|
12
|
+
export type Brand<T, B extends string> = T & {
|
|
13
|
+
readonly [__brand]: B;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Compile-time exhaustiveness check.
|
|
17
|
+
*
|
|
18
|
+
* Place in the `default` / final `else` branch of a switch/if chain
|
|
19
|
+
* over a discriminated union. If a variant is unhandled the compiler
|
|
20
|
+
* will error because `value` won't narrow to `never`.
|
|
21
|
+
*
|
|
22
|
+
* Also throws at runtime as a safety net.
|
|
23
|
+
*/
|
|
24
|
+
export declare function assertNever(value: never, msg?: string): never;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// code-mapper/src/lib/type-utils.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file Shared type-level utilities — single source of truth.
|
|
4
|
+
*
|
|
5
|
+
* Brand<T, B> — nominal branding for stringly-typed values
|
|
6
|
+
* assertNever() — exhaustiveness guard for switch/if chains
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Exhaustiveness guard
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Compile-time exhaustiveness check.
|
|
13
|
+
*
|
|
14
|
+
* Place in the `default` / final `else` branch of a switch/if chain
|
|
15
|
+
* over a discriminated union. If a variant is unhandled the compiler
|
|
16
|
+
* will error because `value` won't narrow to `never`.
|
|
17
|
+
*
|
|
18
|
+
* Also throws at runtime as a safety net.
|
|
19
|
+
*/
|
|
20
|
+
export function assertNever(value, msg) {
|
|
21
|
+
throw new Error(msg ?? `Unexpected value: ${JSON.stringify(value)}`);
|
|
22
|
+
}
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
/** @file utils.ts @description Shared utility functions for the code-mapper pipeline */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { type NodeId } from '../core/db/schema.js';
|
|
3
|
+
/** Generate a branded graph node ID from its label and name */
|
|
4
|
+
export declare const generateId: (label: string, name: string) => NodeId;
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// code-mapper/src/lib/utils.ts
|
|
2
2
|
/** @file utils.ts @description Shared utility functions for the code-mapper pipeline */
|
|
3
|
-
|
|
3
|
+
import { toNodeId } from '../core/db/schema.js';
|
|
4
|
+
/** Generate a branded graph node ID from its label and name */
|
|
4
5
|
export const generateId = (label, name) => {
|
|
5
|
-
return `${label}:${name}
|
|
6
|
+
return toNodeId(`${label}:${name}`);
|
|
6
7
|
};
|
|
@@ -105,7 +105,7 @@ export class CompatibleStdioServerTransport {
|
|
|
105
105
|
this.discardBufferedInput();
|
|
106
106
|
throw new Error('Missing Content-Length header from MCP client');
|
|
107
107
|
}
|
|
108
|
-
const contentLength = Number.parseInt(match[1], 10);
|
|
108
|
+
const contentLength = Number.parseInt(match[1] ?? '', 10);
|
|
109
109
|
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
110
110
|
this.discardBufferedInput();
|
|
111
111
|
throw new Error('Invalid Content-Length header from MCP client');
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/** @file local-backend.ts
|
|
2
2
|
* @description Tool implementations using local .code-mapper/ indexes
|
|
3
3
|
* Supports multiple indexed repositories via a global registry
|
|
4
|
-
*
|
|
4
|
+
* SQLite connections are opened lazily per repo on first query */
|
|
5
5
|
import { type RegistryEntry } from '../../storage/repo-manager.js';
|
|
6
6
|
/** Quick test-file detection for filtering impact results across all supported languages */
|
|
7
7
|
export declare function isTestFilePath(filePath: string): boolean;
|
|
8
|
-
/** Valid
|
|
8
|
+
/** Valid node labels for safe query construction */
|
|
9
9
|
export declare const VALID_NODE_LABELS: Set<string>;
|
|
10
10
|
/** Valid relation types for impact analysis filtering */
|
|
11
11
|
export declare const VALID_RELATION_TYPES: Set<string>;
|
|
12
|
-
/** Regex to detect write operations in user-supplied
|
|
13
|
-
export declare const
|
|
14
|
-
/** Check if a
|
|
12
|
+
/** Regex to detect write operations in user-supplied SQL queries */
|
|
13
|
+
export declare const SQL_WRITE_RE: RegExp;
|
|
14
|
+
/** Check if a SQL query contains write operations */
|
|
15
15
|
export declare function isWriteQuery(query: string): boolean;
|
|
16
16
|
export interface CodebaseContext {
|
|
17
17
|
projectName: string;
|
|
@@ -27,7 +27,6 @@ interface RepoHandle {
|
|
|
27
27
|
name: string;
|
|
28
28
|
repoPath: string;
|
|
29
29
|
storagePath: string;
|
|
30
|
-
lbugPath: string;
|
|
31
30
|
indexedAt: string;
|
|
32
31
|
lastCommit: string;
|
|
33
32
|
stats?: RegistryEntry['stats'];
|
|
@@ -35,15 +34,18 @@ interface RepoHandle {
|
|
|
35
34
|
export declare class LocalBackend {
|
|
36
35
|
private repos;
|
|
37
36
|
private contextCache;
|
|
38
|
-
private initializedRepos;
|
|
39
37
|
private watchers;
|
|
40
38
|
/** Per-repo promise chain that serializes ensureFresh calls.
|
|
41
39
|
* Prevents race: Call 2 skipping refresh while Call 1 is still writing. */
|
|
42
40
|
private refreshLocks;
|
|
41
|
+
/** Per-repo tsgo LSP service instances for live semantic enrichment */
|
|
42
|
+
private tsgoServices;
|
|
43
|
+
/** Get (or lazily start) a tsgo LSP service for a repo. Returns null if unavailable. */
|
|
44
|
+
private getTsgo;
|
|
45
|
+
/** Get (or lazily open) the SQLite database for a repo. */
|
|
46
|
+
private getDb;
|
|
43
47
|
/** Hard ceiling — beyond this, incremental is unreliable, warn prominently */
|
|
44
48
|
private static readonly MAX_INCREMENTAL_FILES;
|
|
45
|
-
/** Optional tsgo LSP service for confidence-1.0 semantic resolution */
|
|
46
|
-
private tsgoEnabled;
|
|
47
49
|
/** Start file system watcher for a repo to detect source changes */
|
|
48
50
|
private startWatcher;
|
|
49
51
|
/**
|
|
@@ -60,48 +62,14 @@ export declare class LocalBackend {
|
|
|
60
62
|
/** Check for file changes and refresh the DB + embeddings before a tool call */
|
|
61
63
|
private doEnsureFresh;
|
|
62
64
|
/**
|
|
63
|
-
*
|
|
64
|
-
*/
|
|
65
|
-
private static readonly SKIP_DELETE_TABLES;
|
|
66
|
-
/** Tables requiring backtick-quoting in Cypher (reserved words) */
|
|
67
|
-
private static readonly BACKTICK_TABLES;
|
|
68
|
-
private static quoteTable;
|
|
69
|
-
private static escapeCypher;
|
|
70
|
-
/**
|
|
71
|
-
* In-process incremental refresh — parses dirty files with tree-sitter and
|
|
72
|
-
* writes directly to the DB through the existing connection pool.
|
|
73
|
-
*
|
|
74
|
-
* This avoids the LadybugDB lock conflict that prevented the child-process
|
|
75
|
-
* approach from working: LadybugDB on macOS holds an exclusive file lock
|
|
76
|
-
* even for read-only connections, and db.close() segfaults via N-API.
|
|
65
|
+
* In-process incremental refresh — delegates to the shared refreshFiles module.
|
|
77
66
|
*/
|
|
78
67
|
private inProcessRefresh;
|
|
79
|
-
/**
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
* Runs ONLY when the repo previously had embeddings (stats.embeddings > 0).
|
|
83
|
-
* Steps:
|
|
84
|
-
* 1. Delete stale CodeEmbedding rows for all dirty file paths (always)
|
|
85
|
-
* 2. Query new embeddable nodes for modified/created files
|
|
86
|
-
* 3. Generate text → batch embed using the warm MCP singleton model
|
|
87
|
-
* 4. Insert new CodeEmbedding rows
|
|
88
|
-
* 5. Drop + recreate HNSW vector index
|
|
89
|
-
*
|
|
90
|
-
* If the embedding model fails to load, stale rows are still deleted —
|
|
91
|
-
* semantic search returns fewer results but never wrong ones.
|
|
92
|
-
*/
|
|
93
|
-
private refreshEmbeddings;
|
|
94
|
-
private rebuildVectorIndex;
|
|
95
|
-
/**
|
|
96
|
-
* Initialize from the global registry, returns true if at least one repo is available.
|
|
97
|
-
* @param opts.tsgo — Enable tsgo semantic resolution (confidence-1.0 call edges)
|
|
98
|
-
*/
|
|
99
|
-
init(opts?: {
|
|
100
|
-
tsgo?: boolean;
|
|
101
|
-
}): Promise<boolean>;
|
|
68
|
+
/** Initialize from the global registry, returns true if at least one repo is available */
|
|
69
|
+
init(): Promise<boolean>;
|
|
102
70
|
/**
|
|
103
71
|
* Re-read the global registry and update the in-memory repo map
|
|
104
|
-
*
|
|
72
|
+
* SQLite connections for removed repos are cleaned up on prune
|
|
105
73
|
*/
|
|
106
74
|
private refreshRepos;
|
|
107
75
|
/** Generate a stable repo ID from name + path (appends hash on collision) */
|
|
@@ -124,7 +92,9 @@ export declare class LocalBackend {
|
|
|
124
92
|
lastCommit: string;
|
|
125
93
|
stats?: any;
|
|
126
94
|
}>>;
|
|
127
|
-
/**
|
|
95
|
+
/** Find the narrowest symbol node enclosing a given file position (for tsgo ref merging) */
|
|
96
|
+
private findNodeAtPosition;
|
|
97
|
+
/** Extract signature from content. For interfaces/types, returns the full body (fields ARE the signature). */
|
|
128
98
|
private extractSignature;
|
|
129
99
|
/** Short file path: strip common prefix if all paths share it */
|
|
130
100
|
private shortPath;
|
|
@@ -148,24 +118,27 @@ export declare class LocalBackend {
|
|
|
148
118
|
*/
|
|
149
119
|
private query;
|
|
150
120
|
/**
|
|
151
|
-
* BM25 keyword search helper
|
|
121
|
+
* BM25 keyword search helper — uses SQLite FTS5 for always-fresh results
|
|
152
122
|
*/
|
|
153
123
|
private bm25Search;
|
|
154
124
|
/**
|
|
155
125
|
* Semantic vector search helper
|
|
156
126
|
*/
|
|
157
127
|
private semanticSearch;
|
|
158
|
-
|
|
159
|
-
private
|
|
160
|
-
/** Format raw
|
|
161
|
-
private
|
|
128
|
+
executeSql(repoName: string, query: string): Promise<any>;
|
|
129
|
+
private sqlQuery;
|
|
130
|
+
/** Format raw SQL result rows as a markdown table, with raw fallback */
|
|
131
|
+
private formatSqlAsMarkdown;
|
|
162
132
|
/** Aggregate same-named clusters by heuristicLabel, filtering tiny clusters (<5 symbols) */
|
|
163
133
|
private aggregateClusters;
|
|
164
134
|
private overview;
|
|
165
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* File overview: list all symbols in a file with signatures and caller/callee counts.
|
|
137
|
+
* Triggered when context() is called with file_path only (no name/uid).
|
|
138
|
+
*/
|
|
139
|
+
private fileOverview;
|
|
140
|
+
/** Context tool: 360-degree symbol view, file overview, or disambiguation */
|
|
166
141
|
private context;
|
|
167
|
-
/** Legacy explore for backwards compatibility with resources.ts */
|
|
168
|
-
private explore;
|
|
169
142
|
/** Detect changes: git-diff impact analysis mapping changed lines to symbols and processes */
|
|
170
143
|
private detectChanges;
|
|
171
144
|
/** Rename tool: multi-file coordinated rename using graph (high confidence) + text search */
|