@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.
Files changed (137) hide show
  1. package/dist/cli/ai-context.js +1 -1
  2. package/dist/cli/analyze.d.ts +1 -0
  3. package/dist/cli/analyze.js +73 -82
  4. package/dist/cli/augment.js +0 -2
  5. package/dist/cli/eval-server.d.ts +2 -2
  6. package/dist/cli/eval-server.js +6 -6
  7. package/dist/cli/index.js +6 -10
  8. package/dist/cli/mcp.d.ts +1 -3
  9. package/dist/cli/mcp.js +3 -3
  10. package/dist/cli/refresh.d.ts +2 -2
  11. package/dist/cli/refresh.js +24 -29
  12. package/dist/cli/status.js +4 -13
  13. package/dist/cli/tool.d.ts +5 -4
  14. package/dist/cli/tool.js +8 -10
  15. package/dist/config/ignore-service.js +14 -34
  16. package/dist/core/augmentation/engine.js +53 -83
  17. package/dist/core/db/adapter.d.ts +99 -0
  18. package/dist/core/db/adapter.js +402 -0
  19. package/dist/core/db/graph-loader.d.ts +27 -0
  20. package/dist/core/db/graph-loader.js +148 -0
  21. package/dist/core/db/queries.d.ts +160 -0
  22. package/dist/core/db/queries.js +441 -0
  23. package/dist/core/db/schema.d.ts +108 -0
  24. package/dist/core/db/schema.js +136 -0
  25. package/dist/core/embeddings/embedder.d.ts +21 -12
  26. package/dist/core/embeddings/embedder.js +104 -50
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +48 -22
  28. package/dist/core/embeddings/embedding-pipeline.js +220 -262
  29. package/dist/core/embeddings/text-generator.js +4 -19
  30. package/dist/core/embeddings/types.d.ts +1 -1
  31. package/dist/core/graph/graph.d.ts +1 -1
  32. package/dist/core/graph/graph.js +1 -0
  33. package/dist/core/graph/types.d.ts +11 -9
  34. package/dist/core/graph/types.js +4 -1
  35. package/dist/core/incremental/refresh.d.ts +46 -0
  36. package/dist/core/incremental/refresh.js +503 -0
  37. package/dist/core/incremental/types.d.ts +2 -1
  38. package/dist/core/incremental/types.js +42 -44
  39. package/dist/core/ingestion/ast-cache.js +1 -0
  40. package/dist/core/ingestion/call-processor.d.ts +15 -3
  41. package/dist/core/ingestion/call-processor.js +448 -60
  42. package/dist/core/ingestion/cluster-enricher.d.ts +1 -1
  43. package/dist/core/ingestion/cluster-enricher.js +2 -0
  44. package/dist/core/ingestion/community-processor.d.ts +1 -1
  45. package/dist/core/ingestion/community-processor.js +8 -3
  46. package/dist/core/ingestion/export-detection.d.ts +1 -1
  47. package/dist/core/ingestion/export-detection.js +1 -1
  48. package/dist/core/ingestion/filesystem-walker.js +1 -1
  49. package/dist/core/ingestion/heritage-processor.d.ts +2 -2
  50. package/dist/core/ingestion/heritage-processor.js +22 -11
  51. package/dist/core/ingestion/import-processor.d.ts +2 -2
  52. package/dist/core/ingestion/import-processor.js +24 -9
  53. package/dist/core/ingestion/language-config.js +7 -4
  54. package/dist/core/ingestion/mro-processor.d.ts +1 -1
  55. package/dist/core/ingestion/mro-processor.js +23 -11
  56. package/dist/core/ingestion/named-binding-extraction.js +5 -5
  57. package/dist/core/ingestion/parsing-processor.d.ts +4 -4
  58. package/dist/core/ingestion/parsing-processor.js +26 -18
  59. package/dist/core/ingestion/pipeline.d.ts +4 -2
  60. package/dist/core/ingestion/pipeline.js +50 -20
  61. package/dist/core/ingestion/process-processor.d.ts +2 -2
  62. package/dist/core/ingestion/process-processor.js +28 -14
  63. package/dist/core/ingestion/resolution-context.d.ts +1 -1
  64. package/dist/core/ingestion/resolution-context.js +14 -4
  65. package/dist/core/ingestion/resolvers/csharp.js +4 -3
  66. package/dist/core/ingestion/resolvers/go.js +3 -1
  67. package/dist/core/ingestion/resolvers/jvm.js +13 -4
  68. package/dist/core/ingestion/resolvers/standard.js +2 -2
  69. package/dist/core/ingestion/resolvers/utils.js +6 -2
  70. package/dist/core/ingestion/route-stitcher.d.ts +15 -0
  71. package/dist/core/ingestion/route-stitcher.js +92 -0
  72. package/dist/core/ingestion/structure-processor.d.ts +1 -1
  73. package/dist/core/ingestion/structure-processor.js +3 -2
  74. package/dist/core/ingestion/symbol-table.d.ts +2 -0
  75. package/dist/core/ingestion/symbol-table.js +5 -1
  76. package/dist/core/ingestion/tree-sitter-queries.d.ts +2 -2
  77. package/dist/core/ingestion/tree-sitter-queries.js +177 -0
  78. package/dist/core/ingestion/type-env.js +20 -0
  79. package/dist/core/ingestion/type-extractors/csharp.js +4 -3
  80. package/dist/core/ingestion/type-extractors/go.js +23 -12
  81. package/dist/core/ingestion/type-extractors/php.js +18 -10
  82. package/dist/core/ingestion/type-extractors/ruby.js +15 -3
  83. package/dist/core/ingestion/type-extractors/rust.js +3 -2
  84. package/dist/core/ingestion/type-extractors/shared.js +3 -2
  85. package/dist/core/ingestion/type-extractors/typescript.js +11 -5
  86. package/dist/core/ingestion/utils.d.ts +27 -4
  87. package/dist/core/ingestion/utils.js +145 -100
  88. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  89. package/dist/core/ingestion/workers/parse-worker.js +97 -29
  90. package/dist/core/ingestion/workers/worker-pool.js +3 -0
  91. package/dist/core/search/bm25-index.d.ts +15 -8
  92. package/dist/core/search/bm25-index.js +48 -98
  93. package/dist/core/search/hybrid-search.d.ts +9 -3
  94. package/dist/core/search/hybrid-search.js +30 -25
  95. package/dist/core/search/reranker.js +9 -7
  96. package/dist/core/search/types.d.ts +0 -4
  97. package/dist/core/semantic/tsgo-service.d.ts +7 -1
  98. package/dist/core/semantic/tsgo-service.js +165 -66
  99. package/dist/lib/tsgo-test.d.ts +2 -0
  100. package/dist/lib/tsgo-test.js +6 -0
  101. package/dist/lib/type-utils.d.ts +25 -0
  102. package/dist/lib/type-utils.js +22 -0
  103. package/dist/lib/utils.d.ts +3 -2
  104. package/dist/lib/utils.js +3 -2
  105. package/dist/mcp/compatible-stdio-transport.js +1 -1
  106. package/dist/mcp/local/local-backend.d.ts +29 -56
  107. package/dist/mcp/local/local-backend.js +808 -1118
  108. package/dist/mcp/resources.js +35 -25
  109. package/dist/mcp/server.d.ts +1 -1
  110. package/dist/mcp/server.js +5 -5
  111. package/dist/mcp/tools.js +24 -25
  112. package/dist/storage/repo-manager.d.ts +2 -12
  113. package/dist/storage/repo-manager.js +1 -47
  114. package/dist/types/pipeline.d.ts +8 -5
  115. package/dist/types/pipeline.js +5 -0
  116. package/package.json +18 -11
  117. package/dist/cli/serve.d.ts +0 -5
  118. package/dist/cli/serve.js +0 -8
  119. package/dist/core/incremental/child-process.d.ts +0 -8
  120. package/dist/core/incremental/child-process.js +0 -649
  121. package/dist/core/incremental/refresh-coordinator.d.ts +0 -32
  122. package/dist/core/incremental/refresh-coordinator.js +0 -147
  123. package/dist/core/lbug/csv-generator.d.ts +0 -28
  124. package/dist/core/lbug/csv-generator.js +0 -355
  125. package/dist/core/lbug/lbug-adapter.d.ts +0 -96
  126. package/dist/core/lbug/lbug-adapter.js +0 -753
  127. package/dist/core/lbug/schema.d.ts +0 -46
  128. package/dist/core/lbug/schema.js +0 -402
  129. package/dist/mcp/core/embedder.d.ts +0 -24
  130. package/dist/mcp/core/embedder.js +0 -168
  131. package/dist/mcp/core/lbug-adapter.d.ts +0 -29
  132. package/dist/mcp/core/lbug-adapter.js +0 -330
  133. package/dist/server/api.d.ts +0 -5
  134. package/dist/server/api.js +0 -340
  135. package/dist/server/mcp-http.d.ts +0 -7
  136. package/dist/server/mcp-http.js +0 -95
  137. 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
- const result = resp.result;
71
- if (!result)
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.result;
100
- return refs.map((r) => ({
101
- filePath: this.uriToRelative(r.uri),
102
- line: r.range.start.line,
103
- character: r.range.start.character,
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.result.contents;
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.value;
124
- if (Array.isArray(contents))
125
- return contents.map((c) => c.value || c).join('\n');
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
- console.error('Code Mapper: tsgo not found install @typescript/native-preview for semantic resolution');
187
- return false;
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', () => { }); // suppress
196
- this.process.on('exit', () => {
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
- return false;
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 platform = process.platform;
233
- const arch = process.arch;
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 npx tsgo path
306
+ // Try which/where
242
307
  try {
243
- const { execFileSync } = require('child_process');
244
- const result = execFileSync('which', ['tsgo'], { encoding: 'utf-8', timeout: 3000 }).trim();
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
- this.buf += chunk.toString();
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
- const m = this.buf.match(/Content-Length: (\d+)\r\n\r\n([\s\S]*)/);
255
- if (!m)
321
+ // Find header/body separator
322
+ const sep = this.rawBuf.indexOf('\r\n\r\n');
323
+ if (sep === -1)
256
324
  break;
257
- const len = parseInt(m[1]);
258
- if (m[2].length < len)
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
- const body = m[2].slice(0, len);
261
- this.buf = m[2].slice(len);
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(body);
264
- if (msg.id !== undefined && msg.method) {
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.id, result: null });
345
+ this.send({ jsonrpc: '2.0', id: msg['id'], result: null });
267
346
  }
268
- else if (msg.id !== undefined) {
269
- // Response to our request
270
- this.responses.set(msg.id, msg);
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 = 15000) {
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 start = Date.now();
290
- const check = () => {
291
- if (this.responses.has(id)) {
292
- const resp = this.responses.get(id);
293
- this.responses.delete(id);
294
- return resolve(resp);
295
- }
296
- if (Date.now() - start > timeoutMs)
297
- return resolve(null);
298
- setTimeout(check, 20);
299
- };
300
- check();
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,2 @@
1
+ /** tsgo test: calls generateId cross-file — should get confidence 1.0 edge */
2
+ export declare function makeNodeId(label: string, name: string): string;
@@ -0,0 +1,6 @@
1
+ import { generateId } from './utils.js';
2
+ /** tsgo test: calls generateId cross-file — should get confidence 1.0 edge */
3
+ export function makeNodeId(label, name) {
4
+ const id = generateId(label, name);
5
+ return `verified-${id}`;
6
+ }
@@ -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
+ }
@@ -1,3 +1,4 @@
1
1
  /** @file utils.ts @description Shared utility functions for the code-mapper pipeline */
2
- /** Generate a graph node ID from its label and name */
3
- export declare const generateId: (label: string, name: string) => string;
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
- /** Generate a graph node ID from its label and name */
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
- * LadybugDB connections are opened lazily per repo on first query */
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 LadybugDB node labels for safe Cypher query construction */
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 Cypher queries */
13
- export declare const CYPHER_WRITE_RE: RegExp;
14
- /** Check if a Cypher query contains write operations */
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
- * Tables that are global metadata NOT deleted per-file during incremental refresh.
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 refreshdelegates to the shared refreshFiles module.
77
66
  */
78
67
  private inProcessRefresh;
79
- /**
80
- * Update CodeEmbedding rows for dirty files so semantic search is never stale.
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
- * LadybugDB connections for removed repos idle-timeout naturally
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
- /** Extract signature from content: the declaration line(s), not the full body */
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 - uses LadybugDB FTS for always-fresh results
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
- executeCypher(repoName: string, query: string): Promise<any>;
159
- private cypher;
160
- /** Format raw Cypher result rows as a markdown table, with raw fallback */
161
- private formatCypherAsMarkdown;
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
- /** Context tool: 360-degree symbol view with categorized refs and disambiguation */
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 */