@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
@@ -62,7 +62,7 @@ This project is indexed by Code Mapper as **${projectName}** (${stats.nodes || 0
62
62
  | \`impact\` | Blast radius before editing | \`code-mapper_impact({target: "X", direction: "upstream"})\` |
63
63
  | \`detect_changes\` | Pre-commit scope check | \`code-mapper_detect_changes({scope: "staged"})\` |
64
64
  | \`rename\` | Safe multi-file rename | \`code-mapper_rename({symbol_name: "old", new_name: "new", dry_run: true})\` |
65
- | \`cypher\` | Custom graph queries | \`code-mapper_cypher({query: "MATCH ..."})\` |
65
+ | \`sql\` | Custom SQL queries | \`code-mapper_sql({query: "SELECT ..."})\` |
66
66
 
67
67
  ## Impact Risk Levels
68
68
 
@@ -2,6 +2,7 @@
2
2
  export interface AnalyzeOptions {
3
3
  force?: boolean;
4
4
  embeddings?: boolean;
5
+ tsgo?: boolean;
5
6
  verbose?: boolean;
6
7
  }
7
8
  export declare const analyzeCommand: (inputPath?: string, options?: AnalyzeOptions) => Promise<void>;
@@ -5,11 +5,14 @@ import { execFileSync } from 'child_process';
5
5
  import v8 from 'v8';
6
6
  import cliProgress from 'cli-progress';
7
7
  import { runPipelineFromRepo } from '../core/ingestion/pipeline.js';
8
- import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, createFTSIndex, loadCachedEmbeddings } from '../core/lbug/lbug-adapter.js';
8
+ import { openDb, closeDb, resetDb, getStats, insertEmbeddingsBatch, countEmbeddings } from '../core/db/adapter.js';
9
+ import { loadGraphToDb } from '../core/db/graph-loader.js';
10
+ import { stitchRoutes } from '../core/ingestion/route-stitcher.js';
11
+ import { toNodeId } from '../core/db/schema.js';
9
12
  // Embedding imports are lazy (dynamic import) so onnxruntime-node is never loaded when
10
13
  // embeddings are not requested, avoiding crashes on unsupported Node ABIs (#89)
11
14
  // disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (#38)
12
- import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, cleanupOldKuzuFiles } from '../storage/repo-manager.js';
15
+ import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
13
16
  import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
14
17
  import { generateAIContextFiles } from './ai-context.js';
15
18
  import fs from 'fs/promises';
@@ -17,7 +20,7 @@ const HEAP_MB = 8192;
17
20
  const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
18
21
  /** Re-exec the process with an 8 GB heap if currently below that */
19
22
  function ensureHeap() {
20
- const nodeOpts = process.env.NODE_OPTIONS || '';
23
+ const nodeOpts = process.env['NODE_OPTIONS'] || '';
21
24
  if (nodeOpts.includes('--max-old-space-size'))
22
25
  return false;
23
26
  const v8Heap = v8.getHeapStatistics().heap_size_limit;
@@ -34,7 +37,9 @@ function ensureHeap() {
34
37
  }
35
38
  return true;
36
39
  }
40
+ const ANALYZE_PHASES = ['db', 'fts', 'embeddings', 'done'];
37
41
  const PHASE_LABELS = {
42
+ idle: 'Initializing...',
38
43
  extracting: 'Scanning files',
39
44
  structure: 'Building structure',
40
45
  parsing: 'Parsing code',
@@ -43,8 +48,10 @@ const PHASE_LABELS = {
43
48
  heritage: 'Extracting inheritance',
44
49
  communities: 'Detecting communities',
45
50
  processes: 'Detecting processes',
51
+ enriching: 'Enriching clusters',
46
52
  complete: 'Pipeline complete',
47
- lbug: 'Loading into LadybugDB',
53
+ error: 'Error',
54
+ db: 'Loading into database',
48
55
  fts: 'Creating search indexes',
49
56
  embeddings: 'Generating embeddings',
50
57
  done: 'Done',
@@ -53,7 +60,7 @@ export const analyzeCommand = async (inputPath, options) => {
53
60
  if (ensureHeap())
54
61
  return;
55
62
  if (options?.verbose) {
56
- process.env.CODE_MAPPER_VERBOSE = '1';
63
+ process.env['CODE_MAPPER_VERBOSE'] = '1';
57
64
  }
58
65
  console.log('\n Code Mapper Analyzer\n');
59
66
  let repoPath;
@@ -74,20 +81,14 @@ export const analyzeCommand = async (inputPath, options) => {
74
81
  process.exitCode = 1;
75
82
  return;
76
83
  }
77
- const { storagePath, lbugPath } = getStoragePaths(repoPath);
78
- // Clean up stale KuzuDB files from before the LadybugDB migration
79
- // If kuzu existed but lbug doesn't, we're doing a migration re-index
80
- const kuzuResult = await cleanupOldKuzuFiles(storagePath);
81
- if (kuzuResult.found && kuzuResult.needsReindex) {
82
- console.log(' Migrating from KuzuDB to LadybugDB — rebuilding index...\n');
83
- }
84
+ const { storagePath, dbPath } = getStoragePaths(repoPath);
84
85
  const currentCommit = getCurrentCommit(repoPath);
85
86
  const existingMeta = await loadMeta(storagePath);
86
87
  if (existingMeta && !options?.force && existingMeta.lastCommit === currentCommit) {
87
88
  console.log(' Already up to date\n');
88
89
  return;
89
90
  }
90
- if (process.env.CODE_MAPPER_NO_GITIGNORE) {
91
+ if (process.env['CODE_MAPPER_NO_GITIGNORE']) {
91
92
  console.log(' CODE_MAPPER_NO_GITIGNORE is set — skipping .gitignore (still reading .code-mapperignore)\n');
92
93
  }
93
94
  // Single progress bar for the entire pipeline
@@ -110,7 +111,11 @@ export const analyzeCommand = async (inputPath, options) => {
110
111
  aborted = true;
111
112
  bar.stop();
112
113
  console.log('\n Interrupted — cleaning up...');
113
- closeLbug().catch(() => { }).finally(() => process.exit(130));
114
+ try {
115
+ closeDb(dbPath);
116
+ }
117
+ catch { }
118
+ process.exit(130);
114
119
  };
115
120
  process.on('SIGINT', sigintHandler);
116
121
  // Route all console output through bar.log() so the bar doesn't redraw
@@ -155,58 +160,50 @@ export const analyzeCommand = async (inputPath, options) => {
155
160
  if (options?.embeddings && existingMeta && !options?.force) {
156
161
  try {
157
162
  updateBar(0, 'Caching embeddings...');
158
- await initLbug(lbugPath);
159
- const cached = await loadCachedEmbeddings();
160
- cachedEmbeddingNodeIds = cached.embeddingNodeIds;
161
- cachedEmbeddings = cached.embeddings;
162
- await closeLbug();
163
+ const oldDb = openDb(dbPath);
164
+ const rows = oldDb.prepare('SELECT nodeId, embedding FROM embeddings').all();
165
+ for (const row of rows) {
166
+ const nodeId = row.nodeId;
167
+ if (!nodeId)
168
+ continue;
169
+ cachedEmbeddingNodeIds.add(nodeId);
170
+ const vec = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4);
171
+ cachedEmbeddings.push({ nodeId, embedding: Array.from(vec) });
172
+ }
173
+ closeDb(dbPath);
163
174
  }
164
175
  catch {
165
176
  try {
166
- await closeLbug();
177
+ closeDb(dbPath);
167
178
  }
168
179
  catch { }
169
180
  }
170
181
  }
171
- // Phase 1: Full Pipeline (060%)
182
+ // Phase 1: Full Pipeline (0-60%)
172
183
  const pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
173
184
  const phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
174
185
  const scaled = Math.round(progress.percent * 0.6);
175
186
  updateBar(scaled, phaseLabel);
176
- });
177
- // Phase 2: LadybugDB (6085%)
178
- updateBar(60, 'Loading into LadybugDB...');
179
- await closeLbug();
180
- const lbugFiles = [lbugPath, `${lbugPath}.wal`, `${lbugPath}.lock`];
181
- for (const f of lbugFiles) {
182
- try {
183
- await fs.rm(f, { recursive: true, force: true });
184
- }
185
- catch { }
186
- }
187
- const t0Lbug = Date.now();
188
- await initLbug(lbugPath);
189
- let lbugMsgCount = 0;
190
- const lbugResult = await loadGraphToLbug(pipelineResult.graph, pipelineResult.repoPath, storagePath, (msg) => {
191
- lbugMsgCount++;
192
- const progress = Math.min(84, 60 + Math.round((lbugMsgCount / (lbugMsgCount + 10)) * 24));
187
+ }, options?.tsgo === false ? { tsgo: false } : {});
188
+ // Phase 2: SQLite (60-85%)
189
+ updateBar(60, 'Loading into database...');
190
+ // Reset the database (delete and recreate)
191
+ const t0Db = Date.now();
192
+ const db = resetDb(dbPath);
193
+ let dbMsgCount = 0;
194
+ const dbResult = loadGraphToDb(db, pipelineResult.graph, pipelineResult.repoPath, (msg) => {
195
+ dbMsgCount++;
196
+ const progress = Math.min(84, 60 + Math.round((dbMsgCount / (dbMsgCount + 10)) * 24));
193
197
  updateBar(progress, msg);
194
198
  });
195
- const lbugTime = ((Date.now() - t0Lbug) / 1000).toFixed(1);
196
- const lbugWarnings = lbugResult.warnings;
197
- // Phase 3: FTS (85–90%)
198
- updateBar(85, 'Creating search indexes...');
199
+ const dbTime = ((Date.now() - t0Db) / 1000).toFixed(1);
200
+ const dbWarnings = dbResult.warnings;
201
+ // Phase 2.5: HTTP route stitching (post-DB-load, needs content field)
202
+ stitchRoutes(db);
203
+ // Phase 3: FTS (85-90%)
204
+ // FTS5 is auto-created by schema triggers — no manual index creation needed
205
+ updateBar(85, 'Search indexes ready');
199
206
  const t0Fts = Date.now();
200
- try {
201
- await createFTSIndex('File', 'file_fts', ['name', 'content']);
202
- await createFTSIndex('Function', 'function_fts', ['name', 'content']);
203
- await createFTSIndex('Class', 'class_fts', ['name', 'content']);
204
- await createFTSIndex('Method', 'method_fts', ['name', 'content']);
205
- await createFTSIndex('Interface', 'interface_fts', ['name', 'content']);
206
- }
207
- catch (e) {
208
- // Non-fatal — FTS is best-effort; silently continue
209
- }
210
207
  const ftsTime = ((Date.now() - t0Fts) / 1000).toFixed(1);
211
208
  // Phase 3.5: Re-insert cached embeddings
212
209
  if (cachedEmbeddings.length > 0) {
@@ -214,15 +211,17 @@ export const analyzeCommand = async (inputPath, options) => {
214
211
  const EMBED_BATCH = 200;
215
212
  for (let i = 0; i < cachedEmbeddings.length; i += EMBED_BATCH) {
216
213
  const batch = cachedEmbeddings.slice(i, i + EMBED_BATCH);
217
- const paramsList = batch.map(e => ({ nodeId: e.nodeId, embedding: e.embedding }));
218
214
  try {
219
- await executeWithReusedStatement(`CREATE (e:CodeEmbedding {nodeId: $nodeId, embedding: $embedding})`, paramsList);
215
+ insertEmbeddingsBatch(db, batch.map(e => ({
216
+ nodeId: toNodeId(e.nodeId),
217
+ embedding: e.embedding,
218
+ })));
220
219
  }
221
220
  catch { /* some may fail if node was removed, that's fine */ }
222
221
  }
223
222
  }
224
- // Phase 4: Embeddings (9098%)
225
- const stats = await getLbugStats();
223
+ // Phase 4: Embeddings (90-98%)
224
+ const stats = getStats(db);
226
225
  let embeddingTime = '0.0';
227
226
  let embeddingSkipped = true;
228
227
  let embeddingSkipReason = 'off (use --no-embeddings to skip)';
@@ -233,22 +232,19 @@ export const analyzeCommand = async (inputPath, options) => {
233
232
  updateBar(90, 'Loading embedding model...');
234
233
  const t0Emb = Date.now();
235
234
  const { runEmbeddingPipeline } = await import('../core/embeddings/embedding-pipeline.js');
236
- await runEmbeddingPipeline(executeQuery, executeWithReusedStatement, (progress) => {
235
+ await runEmbeddingPipeline(db, (progress) => {
237
236
  const scaled = 90 + Math.round((progress.percent / 100) * 8);
238
237
  const label = progress.phase === 'loading-model' ? 'Loading embedding model...' : `Embedding ${progress.nodesProcessed || 0}/${progress.totalNodes || '?'}`;
239
238
  updateBar(scaled, label);
240
239
  }, {}, cachedEmbeddingNodeIds.size > 0 ? cachedEmbeddingNodeIds : undefined);
241
240
  embeddingTime = ((Date.now() - t0Emb) / 1000).toFixed(1);
242
241
  }
243
- // Phase 5: Finalize (98100%)
242
+ // Phase 5: Finalize (98-100%)
244
243
  updateBar(98, 'Saving metadata...');
245
244
  // Count embeddings in the index (cached + newly generated) for metadata
246
- let embeddingCount = 0;
247
- try {
248
- const embResult = await executeQuery(`MATCH (e:CodeEmbedding) RETURN count(e) AS cnt`);
249
- embeddingCount = embResult?.[0]?.cnt ?? 0;
250
- }
251
- catch { /* table may not exist if embeddings never ran */ }
245
+ const embeddingCount = countEmbeddings(db);
246
+ const communityCount = pipelineResult.communityResult?.stats.totalCommunities;
247
+ const processCount = pipelineResult.processResult?.stats.totalProcesses;
252
248
  const meta = {
253
249
  repoPath,
254
250
  lastCommit: currentCommit,
@@ -257,8 +253,8 @@ export const analyzeCommand = async (inputPath, options) => {
257
253
  files: pipelineResult.totalFileCount,
258
254
  nodes: stats.nodes,
259
255
  edges: stats.edges,
260
- communities: pipelineResult.communityResult?.stats.totalCommunities,
261
- processes: pipelineResult.processResult?.stats.totalProcesses,
256
+ ...(communityCount !== undefined ? { communities: communityCount } : {}),
257
+ ...(processCount !== undefined ? { processes: processCount } : {}),
262
258
  embeddings: embeddingCount,
263
259
  },
264
260
  };
@@ -279,13 +275,11 @@ export const analyzeCommand = async (inputPath, options) => {
279
275
  files: pipelineResult.totalFileCount,
280
276
  nodes: stats.nodes,
281
277
  edges: stats.edges,
282
- communities: pipelineResult.communityResult?.stats.totalCommunities,
278
+ ...(communityCount !== undefined ? { communities: communityCount } : {}),
283
279
  clusters: aggregatedClusterCount,
284
- processes: pipelineResult.processResult?.stats.totalProcesses,
280
+ ...(processCount !== undefined ? { processes: processCount } : {}),
285
281
  });
286
- await closeLbug();
287
- // NOTE: disposeEmbedder() is intentionally skipped — ONNX Runtime's native cleanup
288
- // segfaults on macOS and some Linux configs; the process exits immediately after
282
+ closeDb(dbPath);
289
283
  const totalTime = ((Date.now() - t0Global) / 1000).toFixed(1);
290
284
  clearInterval(elapsedTimer);
291
285
  process.removeListener('SIGINT', sigintHandler);
@@ -298,18 +292,15 @@ export const analyzeCommand = async (inputPath, options) => {
298
292
  const embeddingsCached = cachedEmbeddings.length > 0;
299
293
  console.log(`\n Repository indexed successfully (${totalTime}s)${embeddingsCached ? ` [${cachedEmbeddings.length} embeddings cached]` : ''}\n`);
300
294
  console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineResult.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineResult.processResult?.stats.totalProcesses || 0} flows`);
301
- console.log(` LadybugDB ${lbugTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
295
+ console.log(` SQLite ${dbTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
296
+ console.log(` tsgo: ${pipelineResult.tsgoEnabled ? 'enabled (compiler-verified call resolution)' : 'disabled — install @typescript/native-preview for higher accuracy'}`);
302
297
  console.log(` ${repoPath}`);
303
298
  if (aiContext.files.length > 0) {
304
299
  console.log(` Context: ${aiContext.files.join(', ')}`);
305
300
  }
306
- // Show a quiet summary if some edge types needed fallback insertion
307
- if (lbugWarnings.length > 0) {
308
- const totalFallback = lbugWarnings.reduce((sum, w) => {
309
- const m = w.match(/\((\d+) edges\)/);
310
- return sum + (m ? parseInt(m[1]) : 0);
311
- }, 0);
312
- console.log(` Note: ${totalFallback} edges across ${lbugWarnings.length} types inserted via fallback (schema will be updated in next release)`);
301
+ // Show a quiet summary if some node types had warnings during load
302
+ if (dbWarnings.length > 0) {
303
+ console.log(` Note: ${dbWarnings.length} warnings during graph load`);
313
304
  }
314
305
  try {
315
306
  await fs.access(getGlobalRegistryPath());
@@ -318,8 +309,8 @@ export const analyzeCommand = async (inputPath, options) => {
318
309
  console.log('\n Tip: Run `code-mapper setup` to configure MCP for your editor.');
319
310
  }
320
311
  console.log('');
321
- // LadybugDB's native module holds open handles that prevent Node from exiting
322
- // ONNX Runtime also registers native atexit hooks that segfault (#38, #40)
323
- // Force-exit to ensure clean termination
312
+ // better-sqlite3 is synchronous and doesn't hold native handles that prevent exit.
313
+ // However, the MLX embedder (Python subprocess) or ONNX Runtime may still hold
314
+ // native handles, so force-exit to ensure clean termination.
324
315
  process.exit(0);
325
316
  };
@@ -14,8 +14,6 @@ export async function augmentCommand(pattern) {
14
14
  const result = await augment(pattern, process.cwd());
15
15
  if (result) {
16
16
  // IMPORTANT: Write to stderr, NOT stdout
17
- // LadybugDB's native module captures stdout fd at OS level during init,
18
- // making stdout permanently broken in subprocess contexts
19
17
  // The hook reads from the subprocess's stderr
20
18
  process.stderr.write(result + '\n');
21
19
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @file eval-server.ts
3
- * @description Lightweight HTTP server for SWE-bench evaluation. Keeps LadybugDB warm in
3
+ * @description Lightweight HTTP server for SWE-bench evaluation. Keeps the database warm in
4
4
  * memory so tool calls from the agent are near-instant. Returns LLM-friendly text (not raw
5
5
  * JSON) and appends next-step hints for productive tool chaining (query -> context -> impact)
6
6
  *
@@ -18,7 +18,7 @@ export interface EvalServerOptions {
18
18
  export declare function formatQueryResult(result: any): string;
19
19
  export declare function formatContextResult(result: any): string;
20
20
  export declare function formatImpactResult(result: any): string;
21
- export declare function formatCypherResult(result: any): string;
21
+ export declare function formatSqlResult(result: any): string;
22
22
  export declare function formatDetectChangesResult(result: any): string;
23
23
  export declare function formatListReposResult(result: any): string;
24
24
  export declare function evalServerCommand(options?: EvalServerOptions): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  // code-mapper/src/cli/eval-server.ts
2
2
  /**
3
3
  * @file eval-server.ts
4
- * @description Lightweight HTTP server for SWE-bench evaluation. Keeps LadybugDB warm in
4
+ * @description Lightweight HTTP server for SWE-bench evaluation. Keeps the database warm in
5
5
  * memory so tool calls from the agent are near-instant. Returns LLM-friendly text (not raw
6
6
  * JSON) and appends next-step hints for productive tool chaining (query -> context -> impact)
7
7
  *
@@ -141,7 +141,7 @@ export function formatImpactResult(result) {
141
141
  }
142
142
  return lines.join('\n').trim();
143
143
  }
144
- export function formatCypherResult(result) {
144
+ export function formatSqlResult(result) {
145
145
  if (result.error)
146
146
  return `Error: ${result.error}`;
147
147
  if (Array.isArray(result)) {
@@ -211,7 +211,7 @@ function formatToolResult(toolName, result) {
211
211
  case 'query': return formatQueryResult(result);
212
212
  case 'context': return formatContextResult(result);
213
213
  case 'impact': return formatImpactResult(result);
214
- case 'cypher': return formatCypherResult(result);
214
+ case 'sql': return formatSqlResult(result);
215
215
  case 'detect_changes': return formatDetectChangesResult(result);
216
216
  case 'list_repos': return formatListReposResult(result);
217
217
  default: return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
@@ -227,7 +227,7 @@ function getNextStepHint(toolName) {
227
227
  return '\n---\nNext: To check what breaks if you change this, run code-mapper-impact "<name>" upstream';
228
228
  case 'impact':
229
229
  return '\n---\nNext: Review d=1 items first (WILL BREAK). Read the source with cat to understand the code, then make your fix.';
230
- case 'cypher':
230
+ case 'sql':
231
231
  return '\n---\nNext: To explore a result symbol in depth, run code-mapper-context "<name>"';
232
232
  case 'detect_changes':
233
233
  return '\n---\nNext: Run code-mapper-context "<symbol>" on high-risk changed symbols to check their callers.';
@@ -284,7 +284,7 @@ export async function evalServerCommand(options) {
284
284
  // POST /tool/:name
285
285
  const toolMatch = req.url?.match(/^\/tool\/(\w+)$/);
286
286
  if (req.method === 'POST' && toolMatch) {
287
- const toolName = toolMatch[1];
287
+ const toolName = toolMatch[1] ?? '';
288
288
  const body = await readBody(req);
289
289
  let args = {};
290
290
  if (body.trim()) {
@@ -323,7 +323,7 @@ export async function evalServerCommand(options) {
323
323
  console.error(` POST /tool/query — search execution flows`);
324
324
  console.error(` POST /tool/context — 360-degree symbol view`);
325
325
  console.error(` POST /tool/impact — blast radius analysis`);
326
- console.error(` POST /tool/cypher — raw Cypher query`);
326
+ console.error(` POST /tool/sql — raw SQL query`);
327
327
  console.error(` GET /health — health check`);
328
328
  console.error(` POST /shutdown — graceful shutdown`);
329
329
  if (idleTimeoutSec > 0) {
package/dist/cli/index.js CHANGED
@@ -24,19 +24,13 @@ program
24
24
  .option('-f, --force', 'Force full re-index even if up to date')
25
25
  .option('--embeddings', 'Enable embedding generation for semantic search (on by default)', true)
26
26
  .option('--no-embeddings', 'Skip embedding generation')
27
+ .option('--no-tsgo', 'Skip tsgo LSP for call resolution (faster, less accurate)')
27
28
  .option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
28
29
  .addHelpText('after', '\nEnvironment variables:\n CODE_MAPPER_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .code-mapperignore)')
29
30
  .action(createLazyAction(() => import('./analyze.js'), 'analyzeCommand'));
30
- program
31
- .command('serve')
32
- .description('Start local HTTP server for web UI connection')
33
- .option('-p, --port <port>', 'Port number', '4747')
34
- .option('--host <host>', 'Bind address (default: 127.0.0.1, use 0.0.0.0 for remote access)')
35
- .action(createLazyAction(() => import('./serve.js'), 'serveCommand'));
36
31
  program
37
32
  .command('mcp')
38
33
  .description('Start MCP server (stdio) — serves all indexed repos')
39
- .option('--tsgo', 'Enable tsgo semantic resolution for confidence-1.0 call edges')
40
34
  .action(createLazyAction(() => import('./mcp.js'), 'mcpCommand'));
41
35
  program
42
36
  .command('list')
@@ -78,6 +72,7 @@ program
78
72
  .option('-u, --uid <uid>', 'Direct symbol UID (zero-ambiguity lookup)')
79
73
  .option('-f, --file <path>', 'File path to disambiguate common names')
80
74
  .option('--content', 'Include full symbol source code')
75
+ .option('--no-tsgo', 'Disable tsgo LSP enrichment (faster, less accurate)')
81
76
  .action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
82
77
  program
83
78
  .command('impact <target>')
@@ -86,12 +81,13 @@ program
86
81
  .option('-r, --repo <name>', 'Target repository')
87
82
  .option('--depth <n>', 'Max relationship depth (default: 3)')
88
83
  .option('--include-tests', 'Include test files in results')
84
+ .option('--no-tsgo', 'Disable tsgo LSP enrichment (faster, less accurate)')
89
85
  .action(createLazyAction(() => import('./tool.js'), 'impactCommand'));
90
86
  program
91
- .command('cypher <query>')
92
- .description('Execute raw Cypher query against the knowledge graph')
87
+ .command('sql <query>')
88
+ .description('Execute raw SQL query against the knowledge graph')
93
89
  .option('-r, --repo <name>', 'Target repository')
94
- .action(createLazyAction(() => import('./tool.js'), 'cypherCommand'));
90
+ .action(createLazyAction(() => import('./tool.js'), 'sqlCommand'));
95
91
  // Eval server — persistent daemon for SWE-bench evaluation
96
92
  program
97
93
  .command('eval-server')
package/dist/cli/mcp.d.ts CHANGED
@@ -3,6 +3,4 @@
3
3
  * @description Starts the MCP server in standalone mode. Loads all indexed repos from the
4
4
  * global registry — works from any directory
5
5
  */
6
- export declare const mcpCommand: (opts?: {
7
- tsgo?: boolean;
8
- }) => Promise<void>;
6
+ export declare const mcpCommand: () => Promise<void>;
package/dist/cli/mcp.js CHANGED
@@ -6,9 +6,9 @@
6
6
  */
7
7
  import { startMCPServer } from '../mcp/server.js';
8
8
  import { LocalBackend } from '../mcp/local/local-backend.js';
9
- export const mcpCommand = async (opts) => {
9
+ export const mcpCommand = async () => {
10
10
  // Prevent unhandled errors from crashing the MCP server process
11
- // LadybugDB lock conflicts and transient errors should degrade gracefully
11
+ // Transient errors should degrade gracefully
12
12
  process.on('uncaughtException', (err) => {
13
13
  console.error(`Code Mapper MCP: uncaught exception — ${err.message}`);
14
14
  // Process is in undefined state after uncaughtException — exit after flushing
@@ -22,7 +22,7 @@ export const mcpCommand = async (opts) => {
22
22
  // Starts even with 0 repos — tools call refreshRepos() lazily,
23
23
  // so repos indexed after server start are discovered automatically
24
24
  const backend = new LocalBackend();
25
- await backend.init({ tsgo: opts?.tsgo });
25
+ await backend.init();
26
26
  const repos = await backend.listRepos();
27
27
  if (repos.length === 0) {
28
28
  console.error('Code Mapper: No indexed repos yet. Run `code-mapper analyze` in a git repo — the server will pick it up automatically.');
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @file refresh.ts
3
3
  * @description CLI command for incremental refresh — re-indexes only files
4
- * changed since the last commit. Reuses the same RefreshCoordinator + child
5
- * process architecture as the MCP server's auto-refresh.
4
+ * changed since the last commit. Calls refreshFiles directly for in-process
5
+ * tree-sitter parsing and SQLite updates.
6
6
  *
7
7
  * Usage: code-mapper refresh [--scope unstaged|staged|committed]
8
8
  */
@@ -2,18 +2,17 @@
2
2
  /**
3
3
  * @file refresh.ts
4
4
  * @description CLI command for incremental refresh — re-indexes only files
5
- * changed since the last commit. Reuses the same RefreshCoordinator + child
6
- * process architecture as the MCP server's auto-refresh.
5
+ * changed since the last commit. Calls refreshFiles directly for in-process
6
+ * tree-sitter parsing and SQLite updates.
7
7
  *
8
8
  * Usage: code-mapper refresh [--scope unstaged|staged|committed]
9
9
  */
10
- import path from 'path';
11
10
  import { execFileSync } from 'child_process';
12
- import { RefreshCoordinator } from '../core/incremental/refresh-coordinator.js';
13
- import { toRepoId, toRepoRoot, toDbPath, toRelativeFilePath, } from '../core/incremental/types.js';
11
+ import { refreshFiles, refreshEmbeddings } from '../core/incremental/refresh.js';
12
+ import { toRelativeFilePath, } from '../core/incremental/types.js';
14
13
  import { getStoragePaths, loadMeta, saveMeta, } from '../storage/repo-manager.js';
15
14
  import { getCurrentCommit, getGitRoot, isGitRepo } from '../storage/git.js';
16
- import { initLbug, closeLbug, getLbugStats, executeQuery } from '../core/lbug/lbug-adapter.js';
15
+ import { openDb, closeDb, getStats } from '../core/db/adapter.js';
17
16
  export const refreshCommand = async (options) => {
18
17
  const cwd = process.cwd();
19
18
  // Find repo root
@@ -26,13 +25,12 @@ export const refreshCommand = async (options) => {
26
25
  console.error('Could not determine git root.');
27
26
  process.exit(1);
28
27
  }
29
- const { storagePath, lbugPath } = getStoragePaths(repoRoot);
28
+ const { storagePath } = getStoragePaths(repoRoot);
30
29
  const meta = await loadMeta(storagePath);
31
30
  if (!meta) {
32
31
  console.error('No Code Mapper index found. Run: code-mapper analyze');
33
32
  process.exit(1);
34
33
  }
35
- const repoName = path.basename(repoRoot);
36
34
  const scope = options?.scope || 'committed';
37
35
  // Get changed files based on scope
38
36
  let diffArgs;
@@ -104,37 +102,33 @@ export const refreshCommand = async (options) => {
104
102
  return;
105
103
  }
106
104
  console.log(`Refreshing ${entries.length} file(s) (${description})...`);
107
- // Initialize LadybugDB, query existing paths, then close before child fork
108
- const repoId = repoName.toLowerCase();
109
- let existingPaths = [];
105
+ // Open SQLite database
106
+ const { dbPath } = getStoragePaths(repoRoot);
107
+ let db;
110
108
  try {
111
- await initLbug(lbugPath);
112
- const rows = await executeQuery('MATCH (f:File) RETURN f.filePath AS filePath');
113
- existingPaths = rows.map((r) => String(r.filePath ?? ''));
114
- await closeLbug();
109
+ db = openDb(dbPath);
115
110
  }
116
111
  catch (err) {
117
- console.error(`Failed to open index: ${err.message}`);
112
+ const message = err instanceof Error ? err.message : String(err);
113
+ console.error(`Failed to open index: ${message}`);
118
114
  process.exit(1);
119
115
  }
120
- // Run incremental refresh via the coordinator
121
- const coordinator = new RefreshCoordinator();
116
+ // Run incremental refresh directly via refreshFiles
122
117
  const t0 = Date.now();
123
118
  try {
124
- const result = await coordinator.refresh({
125
- id: toRepoId(repoId),
126
- repoRoot: toRepoRoot(repoRoot),
127
- dbPath: toDbPath(lbugPath),
128
- storagePath,
129
- }, entries, existingPaths, 60_000);
119
+ const result = await refreshFiles(db, repoRoot, entries);
130
120
  const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
131
121
  console.log(`Refreshed ${result.filesProcessed} file(s) in ${elapsed}s`);
122
+ if (!result.tsgoEnabled) {
123
+ console.log('Note: tsgo unavailable — call resolution used heuristic matching. Install @typescript/native-preview for higher accuracy.');
124
+ }
125
+ // Refresh embeddings for dirty files (same quality as full analyze — graph context enriched)
126
+ await refreshEmbeddings(db, entries);
132
127
  // Update metadata with current commit
133
128
  if (scope === 'committed' || scope === 'staged') {
134
129
  try {
135
- await initLbug(lbugPath);
136
130
  const currentCommitHash = getCurrentCommit(repoRoot);
137
- const stats = await getLbugStats();
131
+ const stats = getStats(db);
138
132
  await saveMeta(storagePath, {
139
133
  repoPath: repoRoot,
140
134
  lastCommit: currentCommitHash,
@@ -145,7 +139,7 @@ export const refreshCommand = async (options) => {
145
139
  edges: stats.edges ?? meta.stats?.edges ?? 0,
146
140
  communities: meta.stats?.communities ?? 0,
147
141
  processes: meta.stats?.processes ?? 0,
148
- embeddings: meta.stats?.embeddings ?? 0,
142
+ embeddings: stats.embeddings ?? meta.stats?.embeddings ?? 0,
149
143
  },
150
144
  });
151
145
  }
@@ -153,12 +147,13 @@ export const refreshCommand = async (options) => {
153
147
  }
154
148
  }
155
149
  catch (err) {
156
- console.error(`Refresh failed: ${err.message}`);
150
+ const message = err instanceof Error ? err.message : String(err);
151
+ console.error(`Refresh failed: ${message}`);
157
152
  process.exit(1);
158
153
  }
159
154
  finally {
160
155
  try {
161
- await closeLbug();
156
+ closeDb(dbPath);
162
157
  }
163
158
  catch { }
164
159
  }
@@ -1,7 +1,7 @@
1
1
  // code-mapper/src/cli/status.ts
2
2
  /** @file status.ts @description Shows the indexing status of the current repository */
3
- import { findRepo, getStoragePaths, hasKuzuIndex } from '../storage/repo-manager.js';
4
- import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
3
+ import { findRepo } from '../storage/repo-manager.js';
4
+ import { getCurrentCommit, isGitRepo } from '../storage/git.js';
5
5
  export const statusCommand = async () => {
6
6
  const cwd = process.cwd();
7
7
  if (!isGitRepo(cwd)) {
@@ -10,17 +10,8 @@ export const statusCommand = async () => {
10
10
  }
11
11
  const repo = await findRepo(cwd);
12
12
  if (!repo) {
13
- // Check if there's a stale KuzuDB index that needs migration
14
- const repoRoot = getGitRoot(cwd) ?? cwd;
15
- const { storagePath } = getStoragePaths(repoRoot);
16
- if (await hasKuzuIndex(storagePath)) {
17
- console.log('Repository has a stale KuzuDB index from a previous version.');
18
- console.log('Run: code-mapper analyze (rebuilds the index with LadybugDB)');
19
- }
20
- else {
21
- console.log('Repository not indexed.');
22
- console.log('Run: code-mapper analyze');
23
- }
13
+ console.log('Repository not indexed.');
14
+ console.log('Run: code-mapper analyze');
24
15
  return;
25
16
  }
26
17
  const currentCommit = getCurrentCommit(repo.repoPath);