@zuvia-software-solutions/code-mapper 1.4.0 → 2.0.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.
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 +464 -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 +5 -1
  98. package/dist/core/semantic/tsgo-service.js +161 -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;
@@ -58,26 +65,40 @@ export class TsgoService {
58
65
  }
59
66
  /** Resolve what a symbol at a given position points to (go-to-definition) */
60
67
  async resolveDefinition(absFilePath, line, character) {
61
- if (!this.ready)
68
+ if (!this.ready) {
69
+ console.error('[tsgo-service] resolveDefinition called but not ready');
62
70
  return null;
71
+ }
63
72
  await this.ensureOpen(absFilePath);
64
73
  const resp = await this.request('textDocument/definition', {
65
74
  textDocument: { uri: this.fileUri(absFilePath) },
66
75
  position: { line, character },
67
- });
68
- if (!resp)
76
+ }, 3000);
77
+ if (!resp) {
78
+ verbose('definition timeout', absFilePath, line, character);
79
+ return null;
80
+ }
81
+ if (resp['error']) {
82
+ verbose('definition error', resp['error']);
69
83
  return null;
70
- const result = resp.result;
71
- if (!result)
84
+ }
85
+ const result = resp['result'];
86
+ if (!result) {
87
+ verbose('definition null result', absFilePath, line);
72
88
  return null;
89
+ }
73
90
  const defs = Array.isArray(result) ? result : [result];
74
- if (defs.length === 0)
91
+ if (defs.length === 0) {
92
+ verbose('definition empty', absFilePath, line);
75
93
  return null;
94
+ }
76
95
  const d = defs[0];
77
96
  const uri = d.uri || d.targetUri || '';
78
97
  const range = d.range || d.targetSelectionRange;
79
- if (!uri || !range)
98
+ if (!uri || !range) {
99
+ verbose('definition missing uri/range', d);
80
100
  return null;
101
+ }
81
102
  return {
82
103
  filePath: this.uriToRelative(uri),
83
104
  line: range.start.line,
@@ -94,14 +115,17 @@ export class TsgoService {
94
115
  position: { line, character },
95
116
  context: { includeDeclaration: false },
96
117
  });
97
- if (!resp?.result)
118
+ if (!resp?.['result'])
98
119
  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
- }));
120
+ const refs = resp['result'];
121
+ return refs.map((r) => {
122
+ const range = r['range'];
123
+ return {
124
+ filePath: this.uriToRelative(r['uri']),
125
+ line: range.start.line,
126
+ character: range.start.character,
127
+ };
128
+ });
105
129
  }
106
130
  /** Get the type signature at a position (hover) */
107
131
  async getHover(absFilePath, line, character) {
@@ -112,17 +136,23 @@ export class TsgoService {
112
136
  textDocument: { uri: this.fileUri(absFilePath) },
113
137
  position: { line, character },
114
138
  });
115
- if (!resp?.result)
139
+ if (!resp?.['result'])
116
140
  return null;
117
- const contents = resp.result.contents;
141
+ const contents = resp['result']['contents'];
118
142
  if (!contents)
119
143
  return null;
120
144
  if (typeof contents === 'string')
121
145
  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');
146
+ if (typeof contents === 'object' && !Array.isArray(contents) && 'value' in contents) {
147
+ return contents['value'];
148
+ }
149
+ if (Array.isArray(contents)) {
150
+ return contents.map((c) => {
151
+ if (typeof c === 'object' && c !== null && 'value' in c)
152
+ return c['value'];
153
+ return c;
154
+ }).join('\n');
155
+ }
126
156
  return null;
127
157
  }
128
158
  /** Notify tsgo that a file changed (call after editing a file) */
@@ -158,6 +188,30 @@ export class TsgoService {
158
188
  this.openFiles.delete(uri);
159
189
  }
160
190
  }
191
+ /** Pre-open multiple files in one batch (fire-and-forget, no await per file) */
192
+ preOpenFiles(absFilePaths) {
193
+ if (!this.ready)
194
+ return;
195
+ for (const absPath of absFilePaths) {
196
+ const uri = this.fileUri(absPath);
197
+ if (this.openFiles.has(uri))
198
+ continue;
199
+ try {
200
+ const content = fs.readFileSync(absPath, 'utf-8');
201
+ this.send({
202
+ jsonrpc: '2.0',
203
+ method: 'textDocument/didOpen',
204
+ params: {
205
+ textDocument: { uri, languageId: 'typescript', version: 1, text: content },
206
+ },
207
+ });
208
+ this.openFiles.add(uri);
209
+ }
210
+ catch {
211
+ // File may not exist or be unreadable — skip silently
212
+ }
213
+ }
214
+ }
161
215
  /** Stop the tsgo process */
162
216
  stop() {
163
217
  if (this.process && !this.process.killed) {
@@ -177,14 +231,19 @@ export class TsgoService {
177
231
  this.initPromise = null;
178
232
  this.openFiles.clear();
179
233
  this.responses.clear();
234
+ // Resolve all pending requests as null (timed out)
235
+ for (const cb of this.pending.values()) {
236
+ cb({ id: -1, error: { message: 'tsgo stopped' } });
237
+ }
238
+ this.pending.clear();
180
239
  }
181
240
  // ── Internal ────────────────────────────────────────────────────────
182
241
  async doStart() {
183
- // Find the tsgo binary
242
+ // Find the tsgo binary — fail fast if not found
184
243
  this.tsgoPath = this.findTsgoBinary();
185
244
  if (!this.tsgoPath) {
186
- console.error('Code Mapper: tsgo not found install @typescript/native-preview for semantic resolution');
187
- return false;
245
+ throw new Error('tsgo binary not found. Install: npm install -D @typescript/native-preview\n' +
246
+ 'Or remove --tsgo flag to disable semantic resolution.');
188
247
  }
189
248
  try {
190
249
  this.process = spawn(this.tsgoPath, ['--lsp', '--stdio'], {
@@ -192,8 +251,13 @@ export class TsgoService {
192
251
  cwd: this.projectRoot,
193
252
  });
194
253
  this.process.stdout.on('data', (chunk) => this.onData(chunk));
195
- this.process.stderr.on('data', () => { }); // suppress
196
- this.process.on('exit', () => {
254
+ this.process.stderr.on('data', (chunk) => {
255
+ const msg = chunk.toString().trim();
256
+ if (msg)
257
+ console.error(`[tsgo-service] stderr: ${msg}`);
258
+ });
259
+ this.process.on('exit', (code, signal) => {
260
+ console.error(`[tsgo-service] process exited (code=${code}, signal=${signal})`);
197
261
  this.ready = false;
198
262
  this.process = null;
199
263
  });
@@ -210,9 +274,8 @@ export class TsgoService {
210
274
  rootUri: `file://${this.projectRoot}`,
211
275
  });
212
276
  if (!initResp) {
213
- console.error('Code Mapper: tsgo LSP initialize timed out');
214
277
  this.stop();
215
- return false;
278
+ throw new Error('tsgo LSP initialize timed out — the project may be too large or tsgo crashed');
216
279
  }
217
280
  // Send initialized notification
218
281
  this.send({ jsonrpc: '2.0', method: 'initialized', params: {} });
@@ -227,51 +290,73 @@ export class TsgoService {
227
290
  }
228
291
  }
229
292
  findTsgoBinary() {
230
- // Try resolving via the @typescript/native-preview package
293
+ // Try resolving via the @typescript/native-preview platform package
231
294
  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`);
295
+ const pkgName = `@typescript/native-preview-${process.platform}-${process.arch}`;
296
+ const pkgJsonPath = esmRequire.resolve(`${pkgName}/package.json`);
236
297
  const exe = path.join(path.dirname(pkgJsonPath), 'lib', 'tsgo');
237
298
  if (fs.existsSync(exe))
238
299
  return exe;
239
300
  }
240
301
  catch { }
241
- // Try npx tsgo path
302
+ // Try which/where
242
303
  try {
243
- const { execFileSync } = require('child_process');
244
- const result = execFileSync('which', ['tsgo'], { encoding: 'utf-8', timeout: 3000 }).trim();
304
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
305
+ const result = execFileSync(cmd, ['tsgo'], { encoding: 'utf-8', timeout: 3000 }).trim();
245
306
  if (result && fs.existsSync(result))
246
307
  return result;
247
308
  }
248
309
  catch { }
249
310
  return null;
250
311
  }
312
+ rawBuf = Buffer.alloc(0);
251
313
  onData(chunk) {
252
- this.buf += chunk.toString();
314
+ // Accumulate raw bytes — Content-Length is byte count, not character count
315
+ this.rawBuf = Buffer.concat([this.rawBuf, chunk]);
253
316
  while (true) {
254
- const m = this.buf.match(/Content-Length: (\d+)\r\n\r\n([\s\S]*)/);
255
- if (!m)
317
+ // Find header/body separator
318
+ const sep = this.rawBuf.indexOf('\r\n\r\n');
319
+ if (sep === -1)
256
320
  break;
257
- const len = parseInt(m[1]);
258
- if (m[2].length < len)
321
+ // Parse Content-Length from header section
322
+ const header = this.rawBuf.subarray(0, sep).toString('ascii');
323
+ const lenMatch = header.match(/Content-Length:\s*(\d+)/i);
324
+ if (!lenMatch?.[1]) {
325
+ // Malformed header — skip past separator and retry
326
+ this.rawBuf = this.rawBuf.subarray(sep + 4);
327
+ continue;
328
+ }
329
+ const bodyLen = parseInt(lenMatch[1], 10);
330
+ const bodyStart = sep + 4; // after \r\n\r\n
331
+ // Wait for full body to arrive
332
+ if (this.rawBuf.length < bodyStart + bodyLen)
259
333
  break;
260
- const body = m[2].slice(0, len);
261
- this.buf = m[2].slice(len);
334
+ // Extract body bytes and advance buffer
335
+ const bodyBuf = this.rawBuf.subarray(bodyStart, bodyStart + bodyLen);
336
+ this.rawBuf = this.rawBuf.subarray(bodyStart + bodyLen);
262
337
  try {
263
- const msg = JSON.parse(body);
264
- if (msg.id !== undefined && msg.method) {
338
+ const msg = JSON.parse(bodyBuf.toString('utf-8'));
339
+ if (msg['id'] !== undefined && msg['method']) {
265
340
  // Server request (e.g., client/registerCapability) — acknowledge
266
- this.send({ jsonrpc: '2.0', id: msg.id, result: null });
341
+ this.send({ jsonrpc: '2.0', id: msg['id'], result: null });
267
342
  }
268
- else if (msg.id !== undefined) {
269
- // Response to our request
270
- this.responses.set(msg.id, msg);
343
+ else if (msg['id'] !== undefined) {
344
+ // Response to our request — dispatch to pending callback immediately
345
+ const id = msg['id'];
346
+ const cb = this.pending.get(id);
347
+ if (cb) {
348
+ cb(msg);
349
+ }
350
+ else {
351
+ // Callback not registered yet — store for pickup
352
+ this.responses.set(id, msg);
353
+ }
271
354
  }
272
355
  // Notifications (no id, has method) are ignored
273
356
  }
274
- catch { }
357
+ catch (err) {
358
+ console.error(`[tsgo-service] onData parse error: ${err instanceof Error ? err.message : err}`);
359
+ }
275
360
  }
276
361
  }
277
362
  send(msg) {
@@ -280,24 +365,31 @@ export class TsgoService {
280
365
  try {
281
366
  this.process.stdin.write(encodeLSP(msg));
282
367
  }
283
- catch { }
368
+ catch (err) {
369
+ console.error(`[tsgo-service] send error: ${err instanceof Error ? err.message : err}`);
370
+ }
284
371
  }
285
- request(method, params, timeoutMs = 15000) {
372
+ request(method, params, timeoutMs = 30000) {
286
373
  const id = this.nextId++;
287
374
  this.send({ jsonrpc: '2.0', id, method, params });
288
375
  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();
376
+ const timer = setTimeout(() => {
377
+ this.pending.delete(id);
378
+ resolve(null);
379
+ }, timeoutMs);
380
+ this.pending.set(id, (resp) => {
381
+ clearTimeout(timer);
382
+ this.pending.delete(id);
383
+ resolve(resp);
384
+ });
385
+ // Check if response already arrived before we registered the callback
386
+ if (this.responses.has(id)) {
387
+ const resp = this.responses.get(id);
388
+ this.responses.delete(id);
389
+ clearTimeout(timer);
390
+ this.pending.delete(id);
391
+ resolve(resp);
392
+ }
301
393
  });
302
394
  }
303
395
  async ensureOpen(absFilePath) {
@@ -319,8 +411,11 @@ export class TsgoService {
319
411
  },
320
412
  });
321
413
  this.openFiles.add(uri);
414
+ verbose('opened', absFilePath);
415
+ }
416
+ catch (err) {
417
+ verbose('ensureOpen FAILED', absFilePath, err instanceof Error ? err.message : err);
322
418
  }
323
- catch { }
324
419
  }
325
420
  fileUri(absPath) {
326
421
  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 */