@optave/codegraph 3.4.1 → 3.5.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 (112) hide show
  1. package/README.md +22 -21
  2. package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
  3. package/dist/ast-analysis/rules/javascript.js +1 -0
  4. package/dist/ast-analysis/rules/javascript.js.map +1 -1
  5. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/ast-store-visitor.js +103 -35
  7. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  8. package/dist/db/better-sqlite3.d.ts +3 -0
  9. package/dist/db/better-sqlite3.d.ts.map +1 -0
  10. package/dist/db/better-sqlite3.js +19 -0
  11. package/dist/db/better-sqlite3.js.map +1 -0
  12. package/dist/db/connection.d.ts +13 -2
  13. package/dist/db/connection.d.ts.map +1 -1
  14. package/dist/db/connection.js +76 -2
  15. package/dist/db/connection.js.map +1 -1
  16. package/dist/db/index.d.ts +2 -2
  17. package/dist/db/index.d.ts.map +1 -1
  18. package/dist/db/index.js +1 -1
  19. package/dist/db/index.js.map +1 -1
  20. package/dist/db/migrations.d.ts.map +1 -1
  21. package/dist/db/migrations.js +2 -0
  22. package/dist/db/migrations.js.map +1 -1
  23. package/dist/db/query-builder.d.ts +5 -5
  24. package/dist/db/query-builder.d.ts.map +1 -1
  25. package/dist/db/query-builder.js +20 -4
  26. package/dist/db/query-builder.js.map +1 -1
  27. package/dist/db/repository/index.d.ts +1 -0
  28. package/dist/db/repository/index.d.ts.map +1 -1
  29. package/dist/db/repository/index.js +1 -0
  30. package/dist/db/repository/index.js.map +1 -1
  31. package/dist/db/repository/native-repository.d.ts +58 -0
  32. package/dist/db/repository/native-repository.d.ts.map +1 -0
  33. package/dist/db/repository/native-repository.js +261 -0
  34. package/dist/db/repository/native-repository.js.map +1 -0
  35. package/dist/db/repository/nodes.d.ts +4 -4
  36. package/dist/db/repository/nodes.d.ts.map +1 -1
  37. package/dist/db/repository/nodes.js +6 -6
  38. package/dist/db/repository/nodes.js.map +1 -1
  39. package/dist/domain/graph/builder/context.d.ts +2 -1
  40. package/dist/domain/graph/builder/context.d.ts.map +1 -1
  41. package/dist/domain/graph/builder/context.js +1 -0
  42. package/dist/domain/graph/builder/context.js.map +1 -1
  43. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  44. package/dist/domain/graph/builder/pipeline.js +29 -7
  45. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  46. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  47. package/dist/domain/graph/builder/stages/build-edges.js +103 -15
  48. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  49. package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
  50. package/dist/domain/graph/builder/stages/build-structure.js +33 -5
  51. package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
  52. package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
  53. package/dist/domain/graph/builder/stages/collect-files.js +71 -7
  54. package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
  55. package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
  56. package/dist/domain/graph/builder/stages/detect-changes.js +36 -14
  57. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  58. package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
  59. package/dist/domain/graph/builder/stages/finalize.js +43 -20
  60. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  61. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  62. package/dist/domain/graph/builder/stages/insert-nodes.js +104 -9
  63. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  64. package/dist/domain/parser.d.ts.map +1 -1
  65. package/dist/domain/parser.js +1 -0
  66. package/dist/domain/parser.js.map +1 -1
  67. package/dist/extractors/javascript.js +53 -38
  68. package/dist/extractors/javascript.js.map +1 -1
  69. package/dist/extractors/rust.js +2 -1
  70. package/dist/extractors/rust.js.map +1 -1
  71. package/dist/features/ast.d.ts +14 -1
  72. package/dist/features/ast.d.ts.map +1 -1
  73. package/dist/features/ast.js +38 -1
  74. package/dist/features/ast.js.map +1 -1
  75. package/dist/features/branch-compare.d.ts.map +1 -1
  76. package/dist/features/branch-compare.js +3 -1
  77. package/dist/features/branch-compare.js.map +1 -1
  78. package/dist/features/snapshot.d.ts.map +1 -1
  79. package/dist/features/snapshot.js +2 -1
  80. package/dist/features/snapshot.js.map +1 -1
  81. package/dist/mcp/server.d.ts.map +1 -1
  82. package/dist/mcp/server.js +2 -9
  83. package/dist/mcp/server.js.map +1 -1
  84. package/dist/types.d.ts +228 -0
  85. package/dist/types.d.ts.map +1 -1
  86. package/package.json +61 -11
  87. package/src/ast-analysis/rules/javascript.ts +1 -0
  88. package/src/ast-analysis/visitors/ast-store-visitor.ts +102 -33
  89. package/src/db/better-sqlite3.ts +20 -0
  90. package/src/db/connection.ts +94 -3
  91. package/src/db/index.ts +3 -1
  92. package/src/db/migrations.ts +2 -0
  93. package/src/db/query-builder.ts +30 -5
  94. package/src/db/repository/index.ts +1 -0
  95. package/src/db/repository/native-repository.ts +361 -0
  96. package/src/db/repository/nodes.ts +7 -3
  97. package/src/domain/graph/builder/context.ts +2 -0
  98. package/src/domain/graph/builder/pipeline.ts +30 -6
  99. package/src/domain/graph/builder/stages/build-edges.ts +117 -19
  100. package/src/domain/graph/builder/stages/build-structure.ts +47 -11
  101. package/src/domain/graph/builder/stages/collect-files.ts +84 -7
  102. package/src/domain/graph/builder/stages/detect-changes.ts +39 -21
  103. package/src/domain/graph/builder/stages/finalize.ts +49 -20
  104. package/src/domain/graph/builder/stages/insert-nodes.ts +129 -10
  105. package/src/domain/parser.ts +1 -0
  106. package/src/extractors/javascript.ts +54 -36
  107. package/src/extractors/rust.ts +2 -1
  108. package/src/features/ast.ts +66 -1
  109. package/src/features/branch-compare.ts +3 -1
  110. package/src/features/snapshot.ts +2 -1
  111. package/src/mcp/server.ts +2 -10
  112. package/src/types.ts +273 -0
@@ -3,6 +3,10 @@
3
3
  *
4
4
  * Batch-inserts file nodes, definitions, exports, children, and contains/parameter_of edges.
5
5
  * Updates file hashes for incremental builds.
6
+ *
7
+ * When the native engine is available, delegates all SQLite writes to Rust via
8
+ * `bulkInsertNodes` — eliminating JS↔C boundary overhead. Falls back to the
9
+ * JS implementation on failure or when native is unavailable.
6
10
  */
7
11
  import path from 'node:path';
8
12
  import { performance } from 'node:perf_hooks';
@@ -32,7 +36,112 @@ interface PrecomputedFileData {
32
36
  _reverseDepOnly?: boolean;
33
37
  }
34
38
 
35
- // ── Phase 1: Insert file nodes, definitions, exports ────────────────────
39
+ // ── Native fast-path ─────────────────────────────────────────────────
40
+
41
+ function tryNativeInsert(ctx: PipelineContext): boolean {
42
+ // Use NativeDatabase persistent connection (Phase 6.15+).
43
+ // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
44
+ if (!ctx.nativeDb?.bulkInsertNodes) return false;
45
+
46
+ const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
47
+
48
+ // Marshal allSymbols → InsertNodesBatch[]
49
+ const batches: Array<{
50
+ file: string;
51
+ definitions: Array<{
52
+ name: string;
53
+ kind: string;
54
+ line: number;
55
+ endLine?: number | null;
56
+ visibility?: string | null;
57
+ children: Array<{
58
+ name: string;
59
+ kind: string;
60
+ line: number;
61
+ endLine?: number | null;
62
+ visibility?: string | null;
63
+ }>;
64
+ }>;
65
+ exports: Array<{ name: string; kind: string; line: number }>;
66
+ }> = [];
67
+
68
+ for (const [relPath, symbols] of allSymbols) {
69
+ batches.push({
70
+ file: relPath,
71
+ definitions: symbols.definitions.map((def) => ({
72
+ name: def.name,
73
+ kind: def.kind,
74
+ line: def.line,
75
+ endLine: def.endLine ?? null,
76
+ visibility: def.visibility ?? null,
77
+ children: (def.children ?? []).map((c) => ({
78
+ name: c.name,
79
+ kind: c.kind,
80
+ line: c.line,
81
+ endLine: c.endLine ?? null,
82
+ visibility: c.visibility ?? null,
83
+ })),
84
+ })),
85
+ exports: symbols.exports.map((exp) => ({
86
+ name: exp.name,
87
+ kind: exp.kind,
88
+ line: exp.line,
89
+ })),
90
+ });
91
+ }
92
+
93
+ // Build file hash entries
94
+ const precomputedData = new Map<string, PrecomputedFileData>();
95
+ for (const item of filesToParse) {
96
+ if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
97
+ }
98
+
99
+ const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
100
+ for (const [relPath] of allSymbols) {
101
+ const precomputed = precomputedData.get(relPath);
102
+ if (precomputed?._reverseDepOnly) {
103
+ continue; // file unchanged, hash already correct
104
+ }
105
+ if (precomputed?.hash) {
106
+ let mtime: number;
107
+ let size: number;
108
+ if (precomputed.stat) {
109
+ mtime = precomputed.stat.mtime;
110
+ size = precomputed.stat.size;
111
+ } else {
112
+ const rawStat = fileStat(path.join(rootDir, relPath));
113
+ mtime = rawStat ? Math.floor(rawStat.mtimeMs) : 0;
114
+ size = rawStat ? rawStat.size : 0;
115
+ }
116
+ fileHashes.push({ file: relPath, hash: precomputed.hash, mtime, size });
117
+ } else {
118
+ const absPath = path.join(rootDir, relPath);
119
+ let code: string | null;
120
+ try {
121
+ code = readFileSafe(absPath);
122
+ } catch {
123
+ code = null;
124
+ }
125
+ if (code !== null) {
126
+ const stat = fileStat(absPath);
127
+ const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
128
+ const size = stat ? stat.size : 0;
129
+ fileHashes.push({ file: relPath, hash: fileHash(code), mtime, size });
130
+ }
131
+ }
132
+ }
133
+
134
+ // Also include metadata-only updates (self-heal mtime/size without re-parse)
135
+ for (const item of metadataUpdates) {
136
+ const mtime = item.stat ? Math.floor(item.stat.mtime) : 0;
137
+ const size = item.stat ? item.stat.size : 0;
138
+ fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
139
+ }
140
+
141
+ return ctx.nativeDb.bulkInsertNodes(batches, fileHashes, removed);
142
+ }
143
+
144
+ // ── JS fallback: Phase 1 ────────────────────────────────────────────
36
145
 
37
146
  function insertDefinitionsAndExports(
38
147
  db: BetterSqlite3Database,
@@ -90,7 +199,7 @@ function insertDefinitionsAndExports(
90
199
  }
91
200
  }
92
201
 
93
- // ── Phase 2+3: Insert children and containment edges (two nodeIdMap passes) ──
202
+ // ── JS fallback: Phase 2+3 ──────────────────────────────────────────
94
203
 
95
204
  function insertChildrenAndEdges(
96
205
  db: BetterSqlite3Database,
@@ -165,7 +274,7 @@ function insertChildrenAndEdges(
165
274
  batchInsertEdges(db, edgeRows);
166
275
  }
167
276
 
168
- // ── Phase 4: Update file hashes ─────────────────────────────────────────
277
+ // ── JS fallback: Phase 4 ────────────────────────────────────────────
169
278
 
170
279
  function updateFileHashes(
171
280
  _db: BetterSqlite3Database,
@@ -218,11 +327,27 @@ function updateFileHashes(
218
327
  }
219
328
  }
220
329
 
221
- // ── Main entry point ────────────────────────────────────────────────────
330
+ // ── Main entry point ────────────────────────────────────────────────
222
331
 
223
332
  export async function insertNodes(ctx: PipelineContext): Promise<void> {
224
333
  const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
225
334
 
335
+ // Populate fileSymbols before any DB writes (used by later stages)
336
+ for (const [relPath, symbols] of allSymbols) {
337
+ ctx.fileSymbols.set(relPath, symbols);
338
+ }
339
+
340
+ const t0 = performance.now();
341
+
342
+ // Try native Rust path first — single transaction, no JS↔C overhead
343
+ if (ctx.engineName === 'native' && tryNativeInsert(ctx)) {
344
+ ctx.timing.insertMs = performance.now() - t0;
345
+
346
+ // Removed-file hash cleanup is handled inside the native call
347
+ return;
348
+ }
349
+
350
+ // JS fallback
226
351
  const precomputedData = new Map<string, PrecomputedFileData>();
227
352
  for (const item of filesToParse) {
228
353
  if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
@@ -237,18 +362,12 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
237
362
  upsertHash = null;
238
363
  }
239
364
 
240
- // Populate fileSymbols before the transaction so it is a pure input
241
- for (const [relPath, symbols] of allSymbols) {
242
- ctx.fileSymbols.set(relPath, symbols);
243
- }
244
-
245
365
  const insertAll = db.transaction(() => {
246
366
  insertDefinitionsAndExports(db, allSymbols);
247
367
  insertChildrenAndEdges(db, allSymbols);
248
368
  updateFileHashes(db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash);
249
369
  });
250
370
 
251
- const t0 = performance.now();
252
371
  insertAll();
253
372
  ctx.timing.insertMs = performance.now() - t0;
254
373
 
@@ -85,6 +85,7 @@ const COMMON_QUERY_PATTERNS: string[] = [
85
85
  '(variable_declarator name: (identifier) @varfn_name value: (arrow_function) @varfn_value)',
86
86
  '(variable_declarator name: (identifier) @varfn_name value: (function_expression) @varfn_value)',
87
87
  '(method_definition name: (property_identifier) @meth_name) @meth_node',
88
+ '(method_definition name: (private_property_identifier) @meth_name) @meth_node',
88
89
  '(import_statement source: (string) @imp_source) @imp_node',
89
90
  '(export_statement) @exp_node',
90
91
  '(call_expression function: (identifier) @callfn_name) @callfn_node',
@@ -260,51 +260,69 @@ function extractSymbolsQuery(tree: TreeSitterTree, query: TreeSitterQuery): Extr
260
260
  return { definitions, calls, imports, classes, exports: exps, typeMap };
261
261
  }
262
262
 
263
+ /** Node types that define a function scope — constants inside these are skipped. */
264
+ const FUNCTION_SCOPE_TYPES = new Set([
265
+ 'function_declaration',
266
+ 'arrow_function',
267
+ 'function_expression',
268
+ 'method_definition',
269
+ 'generator_function_declaration',
270
+ 'generator_function',
271
+ ]);
272
+
263
273
  /**
264
- * Walk program-level children to extract `const x = <literal>` as constants.
265
- * The query-based fast path has no pattern for lexical_declaration/variable_declaration,
266
- * so constants are missed. This targeted walk fills that gap without a full tree traversal.
274
+ * Recursively walk the AST to extract `const x = <literal>` as constants.
275
+ * Skips nodes inside function scopes so only file-level / block-level constants
276
+ * are captured matching the native engine's behaviour.
267
277
  */
268
- function extractConstantsWalk(rootNode: TreeSitterNode, definitions: Definition[]): void {
269
- for (let i = 0; i < rootNode.childCount; i++) {
270
- const node = rootNode.child(i);
271
- if (!node) continue;
278
+ function extractConstantsWalk(node: TreeSitterNode, definitions: Definition[]): void {
279
+ for (let i = 0; i < node.childCount; i++) {
280
+ const child = node.child(i);
281
+ if (!child) continue;
282
+
283
+ // Don't descend into function scopes
284
+ if (FUNCTION_SCOPE_TYPES.has(child.type)) continue;
272
285
 
273
- let declNode = node;
286
+ let declNode = child;
274
287
  // Handle `export const …` — unwrap the export_statement to its declaration child
275
- if (node.type === 'export_statement') {
276
- const inner = node.childForFieldName('declaration');
277
- if (!inner) continue;
278
- declNode = inner;
288
+ if (child.type === 'export_statement') {
289
+ const inner = child.childForFieldName('declaration');
290
+ if (inner) declNode = inner;
279
291
  }
280
292
 
281
293
  const t = declNode.type;
282
- if (t !== 'lexical_declaration' && t !== 'variable_declaration') continue;
283
- if (!declNode.text.startsWith('const ')) continue;
284
-
285
- for (let j = 0; j < declNode.childCount; j++) {
286
- const declarator = declNode.child(j);
287
- if (!declarator || declarator.type !== 'variable_declarator') continue;
288
- const nameN = declarator.childForFieldName('name');
289
- const valueN = declarator.childForFieldName('value');
290
- if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
291
- // Skip functions — already captured by query patterns
292
- const valType = valueN.type;
293
- if (
294
- valType === 'arrow_function' ||
295
- valType === 'function_expression' ||
296
- valType === 'function'
297
- )
298
- continue;
299
- if (isConstantValue(valueN)) {
300
- definitions.push({
301
- name: nameN.text,
302
- kind: 'constant',
303
- line: declNode.startPosition.row + 1,
304
- endLine: nodeEndLine(declNode),
305
- });
294
+ if (t === 'lexical_declaration' || t === 'variable_declaration') {
295
+ if (declNode.text.startsWith('const ')) {
296
+ for (let j = 0; j < declNode.childCount; j++) {
297
+ const declarator = declNode.child(j);
298
+ if (!declarator || declarator.type !== 'variable_declarator') continue;
299
+ const nameN = declarator.childForFieldName('name');
300
+ const valueN = declarator.childForFieldName('value');
301
+ if (!nameN || nameN.type !== 'identifier' || !valueN) continue;
302
+ // Skip functions already captured by query patterns
303
+ const valType = valueN.type;
304
+ if (
305
+ valType === 'arrow_function' ||
306
+ valType === 'function_expression' ||
307
+ valType === 'function'
308
+ )
309
+ continue;
310
+ if (isConstantValue(valueN)) {
311
+ definitions.push({
312
+ name: nameN.text,
313
+ kind: 'constant',
314
+ line: declNode.startPosition.row + 1,
315
+ endLine: nodeEndLine(declNode),
316
+ });
317
+ }
318
+ }
306
319
  }
307
320
  }
321
+
322
+ // Recurse into non-function, non-export-statement children (blocks, if-statements, etc.)
323
+ if (child.type !== 'export_statement') {
324
+ extractConstantsWalk(child, definitions);
325
+ }
308
326
  }
309
327
  }
310
328
 
@@ -227,7 +227,8 @@ function extractRustParameters(paramListNode: TreeSitterNode | null): SubDeclara
227
227
  const param = paramListNode.child(i);
228
228
  if (!param) continue;
229
229
  if (param.type === 'self_parameter') {
230
- params.push({ name: 'self', kind: 'parameter', line: param.startPosition.row + 1 });
230
+ // Skip self parameters matches native engine behaviour
231
+ continue;
231
232
  } else if (param.type === 'parameter') {
232
233
  const pattern = param.childForFieldName('pattern');
233
234
  if (pattern) {
@@ -65,8 +65,73 @@ export async function buildAstNodes(
65
65
  db: BetterSqlite3Database,
66
66
  fileSymbols: Map<string, FileSymbols>,
67
67
  _rootDir: string,
68
- _engineOpts?: unknown,
68
+ engineOpts?: {
69
+ nativeDb?: {
70
+ bulkInsertAstNodes(
71
+ batches: Array<{
72
+ file: string;
73
+ nodes: Array<{
74
+ line: number;
75
+ kind: string;
76
+ name: string;
77
+ text?: string | null;
78
+ receiver?: string | null;
79
+ }>;
80
+ }>,
81
+ ): number;
82
+ };
83
+ },
69
84
  ): Promise<void> {
85
+ // ── Native bulk-insert fast path ──────────────────────────────────────
86
+ // Uses NativeDatabase persistent connection (Phase 6.15+).
87
+ // Standalone napi functions were removed in 6.17.
88
+ const nativeDb = engineOpts?.nativeDb;
89
+ if (nativeDb?.bulkInsertAstNodes) {
90
+ let needsJsFallback = false;
91
+ const batches: Array<{
92
+ file: string;
93
+ nodes: Array<{
94
+ line: number;
95
+ kind: string;
96
+ name: string;
97
+ text?: string | null;
98
+ receiver?: string | null;
99
+ }>;
100
+ }> = [];
101
+
102
+ for (const [relPath, symbols] of fileSymbols) {
103
+ if (Array.isArray(symbols.astNodes)) {
104
+ batches.push({
105
+ file: relPath,
106
+ nodes: symbols.astNodes.map((n) => ({
107
+ line: n.line,
108
+ kind: n.kind,
109
+ name: n.name,
110
+ text: n.text,
111
+ receiver: n.receiver,
112
+ })),
113
+ });
114
+ } else if (symbols.calls || symbols._tree) {
115
+ needsJsFallback = true;
116
+ break;
117
+ }
118
+ }
119
+
120
+ if (!needsJsFallback) {
121
+ const expectedNodes = batches.reduce((s, b) => s + b.nodes.length, 0);
122
+ const inserted = nativeDb.bulkInsertAstNodes(batches);
123
+ if (inserted === expectedNodes) {
124
+ debug(`AST extraction (native bulk): ${inserted} nodes stored`);
125
+ return;
126
+ }
127
+ debug(
128
+ `AST extraction (native bulk): expected ${expectedNodes}, got ${inserted} — falling back to JS`,
129
+ );
130
+ // fall through to JS path
131
+ }
132
+ }
133
+
134
+ // ── JS fallback path ──────────────────────────────────────────────────
70
135
  let insertStmt: ReturnType<BetterSqlite3Database['prepare']>;
71
136
  try {
72
137
  insertStmt = db.prepare(
@@ -2,7 +2,7 @@ import { execFileSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import Database from 'better-sqlite3';
5
+ import { getDatabase } from '../db/better-sqlite3.js';
6
6
  import { buildGraph } from '../domain/graph/builder.js';
7
7
  import { kindIcon } from '../domain/queries.js';
8
8
  import { isTestFile } from '../infrastructure/test-filter.js';
@@ -105,6 +105,7 @@ function loadSymbolsFromDb(
105
105
  changedFiles: string[],
106
106
  noTests: boolean,
107
107
  ): Map<string, SymbolInfo> {
108
+ const Database = getDatabase();
108
109
  const db = new Database(dbPath, { readonly: true });
109
110
  try {
110
111
  const symbols = new Map<string, SymbolInfo>();
@@ -174,6 +175,7 @@ function loadCallersFromDb(
174
175
  ): CallerInfo[] {
175
176
  if (nodeIds.length === 0) return [];
176
177
 
178
+ const Database = getDatabase();
177
179
  const db = new Database(dbPath, { readonly: true });
178
180
  try {
179
181
  const allCallers = new Set<string>();
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import Database from 'better-sqlite3';
3
+ import { getDatabase } from '../db/better-sqlite3.js';
4
4
  import { findDbPath } from '../db/index.js';
5
5
  import { debug } from '../infrastructure/logger.js';
6
6
  import { ConfigError, DbError } from '../shared/errors.js';
@@ -47,6 +47,7 @@ export function snapshotSave(
47
47
 
48
48
  fs.mkdirSync(dir, { recursive: true });
49
49
 
50
+ const Database = getDatabase();
50
51
  const db = new Database(dbPath, { readonly: true });
51
52
  try {
52
53
  db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
package/src/mcp/server.ts CHANGED
@@ -10,6 +10,7 @@ import { createRequire } from 'node:module';
10
10
  const require = createRequire(import.meta.url);
11
11
  const { version: PKG_VERSION } = require('../../package.json') as { version: string };
12
12
 
13
+ import { getDatabase } from '../db/better-sqlite3.js';
13
14
  import { findDbPath } from '../db/index.js';
14
15
  import { loadConfig } from '../infrastructure/config.js';
15
16
  import { CodegraphError, ConfigError } from '../shared/errors.js';
@@ -64,22 +65,13 @@ async function loadMCPSdk(): Promise<{
64
65
 
65
66
  function createLazyLoaders(): {
66
67
  getQueries(): Promise<unknown>;
67
- getDatabase(): unknown;
68
68
  } {
69
69
  let _queries: unknown;
70
- let _Database: unknown;
71
70
  return {
72
71
  async getQueries(): Promise<unknown> {
73
72
  if (!_queries) _queries = await import('../domain/queries.js');
74
73
  return _queries;
75
74
  },
76
- getDatabase(): unknown {
77
- if (!_Database) {
78
- const require = createRequire(import.meta.url);
79
- _Database = require('better-sqlite3');
80
- }
81
- return _Database;
82
- },
83
75
  };
84
76
  }
85
77
 
@@ -135,7 +127,7 @@ export async function startMCPServer(
135
127
  // `initialize` request while heavy modules (queries, better-sqlite3)
136
128
  // are still loading. These are lazy-loaded on the first tool call
137
129
  // and cached for subsequent calls.
138
- const { getQueries, getDatabase } = createLazyLoaders();
130
+ const { getQueries } = createLazyLoaders();
139
131
 
140
132
  const server = new (Server as any)(
141
133
  { name: 'codegraph', version: PKG_VERSION },