@zuvia-software-solutions/code-mapper 1.4.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 (213) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/ai-context.d.ts +19 -0
  3. package/dist/cli/ai-context.js +168 -0
  4. package/dist/cli/analyze.d.ts +7 -0
  5. package/dist/cli/analyze.js +325 -0
  6. package/dist/cli/augment.d.ts +7 -0
  7. package/dist/cli/augment.js +27 -0
  8. package/dist/cli/clean.d.ts +5 -0
  9. package/dist/cli/clean.js +56 -0
  10. package/dist/cli/eval-server.d.ts +25 -0
  11. package/dist/cli/eval-server.js +365 -0
  12. package/dist/cli/index.d.ts +6 -0
  13. package/dist/cli/index.js +102 -0
  14. package/dist/cli/lazy-action.d.ts +6 -0
  15. package/dist/cli/lazy-action.js +19 -0
  16. package/dist/cli/list.d.ts +2 -0
  17. package/dist/cli/list.js +27 -0
  18. package/dist/cli/mcp.d.ts +8 -0
  19. package/dist/cli/mcp.js +35 -0
  20. package/dist/cli/refresh.d.ts +12 -0
  21. package/dist/cli/refresh.js +165 -0
  22. package/dist/cli/serve.d.ts +5 -0
  23. package/dist/cli/serve.js +8 -0
  24. package/dist/cli/setup.d.ts +6 -0
  25. package/dist/cli/setup.js +218 -0
  26. package/dist/cli/status.d.ts +2 -0
  27. package/dist/cli/status.js +33 -0
  28. package/dist/cli/tool.d.ts +28 -0
  29. package/dist/cli/tool.js +87 -0
  30. package/dist/config/ignore-service.d.ts +32 -0
  31. package/dist/config/ignore-service.js +282 -0
  32. package/dist/config/supported-languages.d.ts +23 -0
  33. package/dist/config/supported-languages.js +52 -0
  34. package/dist/core/augmentation/engine.d.ts +22 -0
  35. package/dist/core/augmentation/engine.js +232 -0
  36. package/dist/core/embeddings/embedder.d.ts +35 -0
  37. package/dist/core/embeddings/embedder.js +171 -0
  38. package/dist/core/embeddings/embedding-pipeline.d.ts +41 -0
  39. package/dist/core/embeddings/embedding-pipeline.js +402 -0
  40. package/dist/core/embeddings/index.d.ts +5 -0
  41. package/dist/core/embeddings/index.js +6 -0
  42. package/dist/core/embeddings/text-generator.d.ts +20 -0
  43. package/dist/core/embeddings/text-generator.js +159 -0
  44. package/dist/core/embeddings/types.d.ts +60 -0
  45. package/dist/core/embeddings/types.js +23 -0
  46. package/dist/core/graph/graph.d.ts +4 -0
  47. package/dist/core/graph/graph.js +65 -0
  48. package/dist/core/graph/types.d.ts +69 -0
  49. package/dist/core/graph/types.js +3 -0
  50. package/dist/core/incremental/child-process.d.ts +8 -0
  51. package/dist/core/incremental/child-process.js +649 -0
  52. package/dist/core/incremental/refresh-coordinator.d.ts +32 -0
  53. package/dist/core/incremental/refresh-coordinator.js +147 -0
  54. package/dist/core/incremental/types.d.ts +78 -0
  55. package/dist/core/incremental/types.js +153 -0
  56. package/dist/core/incremental/watcher.d.ts +63 -0
  57. package/dist/core/incremental/watcher.js +338 -0
  58. package/dist/core/ingestion/ast-cache.d.ts +12 -0
  59. package/dist/core/ingestion/ast-cache.js +34 -0
  60. package/dist/core/ingestion/call-processor.d.ts +34 -0
  61. package/dist/core/ingestion/call-processor.js +937 -0
  62. package/dist/core/ingestion/call-routing.d.ts +40 -0
  63. package/dist/core/ingestion/call-routing.js +97 -0
  64. package/dist/core/ingestion/cluster-enricher.d.ts +30 -0
  65. package/dist/core/ingestion/cluster-enricher.js +151 -0
  66. package/dist/core/ingestion/community-processor.d.ts +26 -0
  67. package/dist/core/ingestion/community-processor.js +272 -0
  68. package/dist/core/ingestion/constants.d.ts +5 -0
  69. package/dist/core/ingestion/constants.js +8 -0
  70. package/dist/core/ingestion/entry-point-scoring.d.ts +23 -0
  71. package/dist/core/ingestion/entry-point-scoring.js +317 -0
  72. package/dist/core/ingestion/export-detection.d.ts +11 -0
  73. package/dist/core/ingestion/export-detection.js +203 -0
  74. package/dist/core/ingestion/filesystem-walker.d.ts +18 -0
  75. package/dist/core/ingestion/filesystem-walker.js +64 -0
  76. package/dist/core/ingestion/framework-detection.d.ts +42 -0
  77. package/dist/core/ingestion/framework-detection.js +405 -0
  78. package/dist/core/ingestion/heritage-processor.d.ts +15 -0
  79. package/dist/core/ingestion/heritage-processor.js +237 -0
  80. package/dist/core/ingestion/import-processor.d.ts +31 -0
  81. package/dist/core/ingestion/import-processor.js +416 -0
  82. package/dist/core/ingestion/language-config.d.ts +32 -0
  83. package/dist/core/ingestion/language-config.js +161 -0
  84. package/dist/core/ingestion/mro-processor.d.ts +32 -0
  85. package/dist/core/ingestion/mro-processor.js +343 -0
  86. package/dist/core/ingestion/named-binding-extraction.d.ts +51 -0
  87. package/dist/core/ingestion/named-binding-extraction.js +343 -0
  88. package/dist/core/ingestion/parsing-processor.d.ts +20 -0
  89. package/dist/core/ingestion/parsing-processor.js +282 -0
  90. package/dist/core/ingestion/pipeline.d.ts +3 -0
  91. package/dist/core/ingestion/pipeline.js +416 -0
  92. package/dist/core/ingestion/process-processor.d.ts +42 -0
  93. package/dist/core/ingestion/process-processor.js +357 -0
  94. package/dist/core/ingestion/resolution-context.d.ts +40 -0
  95. package/dist/core/ingestion/resolution-context.js +171 -0
  96. package/dist/core/ingestion/resolvers/csharp.d.ts +10 -0
  97. package/dist/core/ingestion/resolvers/csharp.js +101 -0
  98. package/dist/core/ingestion/resolvers/go.d.ts +8 -0
  99. package/dist/core/ingestion/resolvers/go.js +33 -0
  100. package/dist/core/ingestion/resolvers/index.d.ts +14 -0
  101. package/dist/core/ingestion/resolvers/index.js +10 -0
  102. package/dist/core/ingestion/resolvers/jvm.d.ts +9 -0
  103. package/dist/core/ingestion/resolvers/jvm.js +74 -0
  104. package/dist/core/ingestion/resolvers/php.d.ts +7 -0
  105. package/dist/core/ingestion/resolvers/php.js +30 -0
  106. package/dist/core/ingestion/resolvers/ruby.d.ts +9 -0
  107. package/dist/core/ingestion/resolvers/ruby.js +13 -0
  108. package/dist/core/ingestion/resolvers/rust.d.ts +5 -0
  109. package/dist/core/ingestion/resolvers/rust.js +62 -0
  110. package/dist/core/ingestion/resolvers/standard.d.ts +16 -0
  111. package/dist/core/ingestion/resolvers/standard.js +144 -0
  112. package/dist/core/ingestion/resolvers/utils.d.ts +18 -0
  113. package/dist/core/ingestion/resolvers/utils.js +113 -0
  114. package/dist/core/ingestion/structure-processor.d.ts +4 -0
  115. package/dist/core/ingestion/structure-processor.js +39 -0
  116. package/dist/core/ingestion/symbol-table.d.ts +34 -0
  117. package/dist/core/ingestion/symbol-table.js +48 -0
  118. package/dist/core/ingestion/tree-sitter-queries.d.ts +20 -0
  119. package/dist/core/ingestion/tree-sitter-queries.js +691 -0
  120. package/dist/core/ingestion/type-env.d.ts +52 -0
  121. package/dist/core/ingestion/type-env.js +349 -0
  122. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +4 -0
  123. package/dist/core/ingestion/type-extractors/c-cpp.js +214 -0
  124. package/dist/core/ingestion/type-extractors/csharp.d.ts +4 -0
  125. package/dist/core/ingestion/type-extractors/csharp.js +224 -0
  126. package/dist/core/ingestion/type-extractors/go.d.ts +4 -0
  127. package/dist/core/ingestion/type-extractors/go.js +261 -0
  128. package/dist/core/ingestion/type-extractors/index.d.ts +20 -0
  129. package/dist/core/ingestion/type-extractors/index.js +30 -0
  130. package/dist/core/ingestion/type-extractors/jvm.d.ts +5 -0
  131. package/dist/core/ingestion/type-extractors/jvm.js +386 -0
  132. package/dist/core/ingestion/type-extractors/php.d.ts +4 -0
  133. package/dist/core/ingestion/type-extractors/php.js +280 -0
  134. package/dist/core/ingestion/type-extractors/python.d.ts +4 -0
  135. package/dist/core/ingestion/type-extractors/python.js +175 -0
  136. package/dist/core/ingestion/type-extractors/ruby.d.ts +12 -0
  137. package/dist/core/ingestion/type-extractors/ruby.js +218 -0
  138. package/dist/core/ingestion/type-extractors/rust.d.ts +4 -0
  139. package/dist/core/ingestion/type-extractors/rust.js +290 -0
  140. package/dist/core/ingestion/type-extractors/shared.d.ts +81 -0
  141. package/dist/core/ingestion/type-extractors/shared.js +322 -0
  142. package/dist/core/ingestion/type-extractors/swift.d.ts +4 -0
  143. package/dist/core/ingestion/type-extractors/swift.js +140 -0
  144. package/dist/core/ingestion/type-extractors/types.d.ts +111 -0
  145. package/dist/core/ingestion/type-extractors/types.js +4 -0
  146. package/dist/core/ingestion/type-extractors/typescript.d.ts +4 -0
  147. package/dist/core/ingestion/type-extractors/typescript.js +227 -0
  148. package/dist/core/ingestion/utils.d.ts +73 -0
  149. package/dist/core/ingestion/utils.js +992 -0
  150. package/dist/core/ingestion/workers/parse-worker.d.ts +99 -0
  151. package/dist/core/ingestion/workers/parse-worker.js +1055 -0
  152. package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
  153. package/dist/core/ingestion/workers/worker-pool.js +123 -0
  154. package/dist/core/lbug/csv-generator.d.ts +28 -0
  155. package/dist/core/lbug/csv-generator.js +355 -0
  156. package/dist/core/lbug/lbug-adapter.d.ts +96 -0
  157. package/dist/core/lbug/lbug-adapter.js +753 -0
  158. package/dist/core/lbug/schema.d.ts +46 -0
  159. package/dist/core/lbug/schema.js +402 -0
  160. package/dist/core/search/bm25-index.d.ts +20 -0
  161. package/dist/core/search/bm25-index.js +123 -0
  162. package/dist/core/search/hybrid-search.d.ts +32 -0
  163. package/dist/core/search/hybrid-search.js +131 -0
  164. package/dist/core/search/query-cache.d.ts +18 -0
  165. package/dist/core/search/query-cache.js +47 -0
  166. package/dist/core/search/query-expansion.d.ts +19 -0
  167. package/dist/core/search/query-expansion.js +75 -0
  168. package/dist/core/search/reranker.d.ts +29 -0
  169. package/dist/core/search/reranker.js +122 -0
  170. package/dist/core/search/types.d.ts +154 -0
  171. package/dist/core/search/types.js +51 -0
  172. package/dist/core/semantic/tsgo-service.d.ts +67 -0
  173. package/dist/core/semantic/tsgo-service.js +355 -0
  174. package/dist/core/tree-sitter/parser-loader.d.ts +12 -0
  175. package/dist/core/tree-sitter/parser-loader.js +71 -0
  176. package/dist/lib/memory-guard.d.ts +35 -0
  177. package/dist/lib/memory-guard.js +70 -0
  178. package/dist/lib/utils.d.ts +3 -0
  179. package/dist/lib/utils.js +6 -0
  180. package/dist/mcp/compatible-stdio-transport.d.ts +32 -0
  181. package/dist/mcp/compatible-stdio-transport.js +209 -0
  182. package/dist/mcp/core/embedder.d.ts +24 -0
  183. package/dist/mcp/core/embedder.js +168 -0
  184. package/dist/mcp/core/lbug-adapter.d.ts +29 -0
  185. package/dist/mcp/core/lbug-adapter.js +330 -0
  186. package/dist/mcp/local/local-backend.d.ts +188 -0
  187. package/dist/mcp/local/local-backend.js +2759 -0
  188. package/dist/mcp/resources.d.ts +22 -0
  189. package/dist/mcp/resources.js +379 -0
  190. package/dist/mcp/server.d.ts +10 -0
  191. package/dist/mcp/server.js +217 -0
  192. package/dist/mcp/staleness.d.ts +10 -0
  193. package/dist/mcp/staleness.js +25 -0
  194. package/dist/mcp/tools.d.ts +21 -0
  195. package/dist/mcp/tools.js +202 -0
  196. package/dist/server/api.d.ts +5 -0
  197. package/dist/server/api.js +340 -0
  198. package/dist/server/mcp-http.d.ts +7 -0
  199. package/dist/server/mcp-http.js +95 -0
  200. package/dist/storage/git.d.ts +6 -0
  201. package/dist/storage/git.js +35 -0
  202. package/dist/storage/repo-manager.d.ts +87 -0
  203. package/dist/storage/repo-manager.js +249 -0
  204. package/dist/types/pipeline.d.ts +35 -0
  205. package/dist/types/pipeline.js +20 -0
  206. package/hooks/claude/code-mapper-hook.cjs +238 -0
  207. package/hooks/claude/pre-tool-use.sh +79 -0
  208. package/hooks/claude/session-start.sh +42 -0
  209. package/models/mlx-embedder.py +185 -0
  210. package/package.json +100 -0
  211. package/scripts/patch-tree-sitter-swift.cjs +74 -0
  212. package/vendor/leiden/index.cjs +355 -0
  213. package/vendor/leiden/utils.cjs +392 -0
@@ -0,0 +1,330 @@
1
+ // code-mapper/src/mcp/core/lbug-adapter.ts
2
+ /** @file lbug-adapter.ts
3
+ * @description LadybugDB connection pool adapter keyed by repoId
4
+ * Provides checkout/return pool so each concurrent query gets its own Connection
5
+ * from the same Database (Connections are NOT thread-safe)
6
+ * @see https://docs.ladybugdb.com/concurrency */
7
+ import fs from 'fs/promises';
8
+ import lbug from '@ladybugdb/core';
9
+ const pool = new Map();
10
+ const dbCache = new Map();
11
+ /** Max repos in the pool (LRU eviction) */
12
+ const MAX_POOL_SIZE = 5;
13
+ /** Idle timeout before closing a repo's connections */
14
+ const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
15
+ /** Max connections per repo (caps concurrent queries per repo) */
16
+ const MAX_CONNS_PER_REPO = 8;
17
+ /** Connections created eagerly on init */
18
+ const INITIAL_CONNS_PER_REPO = 2;
19
+ let idleTimer = null;
20
+ /** Start the idle cleanup timer (runs every 60s) */
21
+ function ensureIdleTimer() {
22
+ if (idleTimer)
23
+ return;
24
+ idleTimer = setInterval(() => {
25
+ const now = Date.now();
26
+ for (const [repoId, entry] of pool) {
27
+ if (now - entry.lastUsed > IDLE_TIMEOUT_MS && entry.checkedOut === 0) {
28
+ closeOne(repoId);
29
+ }
30
+ }
31
+ }, 60_000);
32
+ if (idleTimer && typeof idleTimer === 'object' && 'unref' in idleTimer) {
33
+ idleTimer.unref();
34
+ }
35
+ }
36
+ /** Evict the least-recently-used repo if pool is at capacity */
37
+ function evictLRU() {
38
+ if (pool.size < MAX_POOL_SIZE)
39
+ return;
40
+ let oldestId = null;
41
+ let oldestTime = Infinity;
42
+ for (const [id, entry] of pool) {
43
+ if (entry.checkedOut === 0 && entry.lastUsed < oldestTime) {
44
+ oldestTime = entry.lastUsed;
45
+ oldestId = id;
46
+ }
47
+ }
48
+ if (oldestId) {
49
+ closeOne(oldestId);
50
+ }
51
+ }
52
+ /**
53
+ * Remove a repo from the pool and release its shared Database ref
54
+ * Skips .closeSync() which triggers N-API segfaults on Linux/macOS;
55
+ * pools are read-only so GC reclamation is safe
56
+ */
57
+ function closeOne(repoId) {
58
+ const entry = pool.get(repoId);
59
+ if (entry) {
60
+ const shared = dbCache.get(entry.dbPath);
61
+ if (shared && shared.refCount > 0) {
62
+ shared.refCount--;
63
+ }
64
+ }
65
+ pool.delete(repoId);
66
+ }
67
+ function createConnection(db) {
68
+ return new lbug.Connection(db);
69
+ }
70
+ /** Query timeout in milliseconds */
71
+ const QUERY_TIMEOUT_MS = 30_000;
72
+ /** Waiter queue timeout in milliseconds */
73
+ const WAITER_TIMEOUT_MS = 15_000;
74
+ const LOCK_RETRY_ATTEMPTS = 3;
75
+ const LOCK_RETRY_DELAY_MS = 2000;
76
+ /**
77
+ * Initialize (or reuse) a Database + connection pool for a specific repo
78
+ * Retries on lock errors (e.g., when `code-mapper analyze` is running)
79
+ */
80
+ export const initLbug = async (repoId, dbPath) => {
81
+ const existing = pool.get(repoId);
82
+ if (existing) {
83
+ existing.lastUsed = Date.now();
84
+ return;
85
+ }
86
+ // Check if database exists
87
+ try {
88
+ await fs.stat(dbPath);
89
+ }
90
+ catch {
91
+ throw new Error(`LadybugDB not found at ${dbPath}. Run: code-mapper analyze`);
92
+ }
93
+ evictLRU();
94
+ // Reuse an existing native Database if another repoId already opened this path
95
+ // Prevents buffer manager exhaustion from multiple mmap regions on the same file
96
+ let shared = dbCache.get(dbPath);
97
+ if (!shared) {
98
+ // Open read-write so incremental refresh can write through the same handle.
99
+ // Lock conflicts with `code-mapper analyze` are handled by retry logic.
100
+ let lastError = null;
101
+ for (let attempt = 1; attempt <= LOCK_RETRY_ATTEMPTS; attempt++) {
102
+ try {
103
+ const db = new lbug.Database(dbPath, 0, // bufferManagerSize (default)
104
+ false, // enableCompression (default)
105
+ false);
106
+ shared = { db, refCount: 0, ftsLoaded: false };
107
+ dbCache.set(dbPath, shared);
108
+ break;
109
+ }
110
+ catch (err) {
111
+ lastError = err instanceof Error ? err : new Error(String(err));
112
+ const isLockError = lastError.message.includes('Could not set lock')
113
+ || lastError.message.includes('lock');
114
+ if (!isLockError || attempt === LOCK_RETRY_ATTEMPTS)
115
+ break;
116
+ await new Promise(resolve => setTimeout(resolve, LOCK_RETRY_DELAY_MS * attempt));
117
+ }
118
+ }
119
+ if (!shared) {
120
+ throw new Error(`LadybugDB unavailable for ${repoId}. Another process may be rebuilding the index. ` +
121
+ `Retry later. (${lastError?.message || 'unknown error'})`);
122
+ }
123
+ }
124
+ shared.refCount++;
125
+ const db = shared.db;
126
+ // Pre-create a small pool of connections
127
+ const available = [];
128
+ for (let i = 0; i < INITIAL_CONNS_PER_REPO; i++) {
129
+ available.push(createConnection(db));
130
+ }
131
+ pool.set(repoId, { db, available, checkedOut: 0, waiters: [], lastUsed: Date.now(), dbPath });
132
+ ensureIdleTimer();
133
+ // Load FTS + VECTOR extensions once per shared Database
134
+ if (!shared.ftsLoaded) {
135
+ try {
136
+ await available[0].query('LOAD EXTENSION fts');
137
+ shared.ftsLoaded = true;
138
+ }
139
+ catch {
140
+ // Extension may not be installed — FTS queries will fail gracefully
141
+ }
142
+ }
143
+ // Load VECTOR extension for semantic search (HNSW index queries)
144
+ try {
145
+ await available[0].query('LOAD EXTENSION vector');
146
+ }
147
+ catch {
148
+ // Extension may not be installed — vector queries will fall back to BM25-only
149
+ }
150
+ };
151
+ /**
152
+ * Checkout a connection from the pool
153
+ * Returns an available connection, creates a new one if under cap,
154
+ * or queues the caller until one is returned
155
+ */
156
+ function checkout(entry) {
157
+ // Fast path: grab an available connection
158
+ if (entry.available.length > 0) {
159
+ entry.checkedOut++;
160
+ return Promise.resolve(entry.available.pop());
161
+ }
162
+ // Grow the pool if under the cap
163
+ const totalConns = entry.available.length + entry.checkedOut;
164
+ if (totalConns < MAX_CONNS_PER_REPO) {
165
+ entry.checkedOut++;
166
+ return Promise.resolve(createConnection(entry.db));
167
+ }
168
+ // At capacity — queue the caller with a timeout.
169
+ return new Promise((resolve, reject) => {
170
+ const waiter = (conn) => {
171
+ clearTimeout(timer);
172
+ resolve(conn);
173
+ };
174
+ const timer = setTimeout(() => {
175
+ const idx = entry.waiters.indexOf(waiter);
176
+ if (idx !== -1)
177
+ entry.waiters.splice(idx, 1);
178
+ reject(new Error(`Connection pool exhausted: timed out after ${WAITER_TIMEOUT_MS}ms waiting for a free connection`));
179
+ }, WAITER_TIMEOUT_MS);
180
+ entry.waiters.push(waiter);
181
+ });
182
+ }
183
+ /**
184
+ * Return a connection to the pool after use
185
+ * Hands directly to queued waiters to avoid race conditions
186
+ */
187
+ function checkin(entry, conn) {
188
+ if (entry.waiters.length > 0) {
189
+ // Hand directly to the next waiter (no intermediate available state)
190
+ const waiter = entry.waiters.shift();
191
+ waiter(conn);
192
+ }
193
+ else {
194
+ entry.checkedOut--;
195
+ entry.available.push(conn);
196
+ }
197
+ }
198
+ /** Race a promise against a timeout */
199
+ function withTimeout(promise, ms, label) {
200
+ let timer;
201
+ const timeout = new Promise((_, reject) => {
202
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
203
+ });
204
+ return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
205
+ }
206
+ export const executeQuery = async (repoId, cypher) => {
207
+ const entry = pool.get(repoId);
208
+ if (!entry) {
209
+ throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
210
+ }
211
+ entry.lastUsed = Date.now();
212
+ const conn = await checkout(entry);
213
+ try {
214
+ const queryResult = await withTimeout(conn.query(cypher), QUERY_TIMEOUT_MS, 'Query');
215
+ const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
216
+ const rows = await result.getAll();
217
+ return rows;
218
+ }
219
+ finally {
220
+ checkin(entry, conn);
221
+ }
222
+ };
223
+ /** Execute a parameterized query using prepare/execute to prevent Cypher injection */
224
+ export const executeParameterized = async (repoId, cypher, params) => {
225
+ const entry = pool.get(repoId);
226
+ if (!entry) {
227
+ throw new Error(`LadybugDB not initialized for repo "${repoId}". Call initLbug first.`);
228
+ }
229
+ entry.lastUsed = Date.now();
230
+ const conn = await checkout(entry);
231
+ try {
232
+ const stmt = await withTimeout(conn.prepare(cypher), QUERY_TIMEOUT_MS, 'Prepare');
233
+ if (!stmt.isSuccess()) {
234
+ const errMsg = await stmt.getErrorMessage();
235
+ throw new Error(`Prepare failed: ${errMsg}`);
236
+ }
237
+ const queryResult = await withTimeout(conn.execute(stmt, params), QUERY_TIMEOUT_MS, 'Execute');
238
+ const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
239
+ const rows = await result.getAll();
240
+ return rows;
241
+ }
242
+ finally {
243
+ checkin(entry, conn);
244
+ }
245
+ };
246
+ /** Close one or all repo pools (provide repoId for single, omit for all) */
247
+ export const closeLbug = async (repoId) => {
248
+ if (repoId) {
249
+ closeOne(repoId);
250
+ return;
251
+ }
252
+ for (const id of [...pool.keys()]) {
253
+ closeOne(id);
254
+ }
255
+ if (idleTimer) {
256
+ clearInterval(idleTimer);
257
+ idleTimer = null;
258
+ }
259
+ };
260
+ /** Invalidate a repo's pool and shared DB cache, forcing fresh connections on next init.
261
+ * Closes the native Database handle to release the file lock so a child process
262
+ * can open the DB in read-write mode for incremental refresh.
263
+ * Used before AND after the incremental refresh child process writes to the DB. */
264
+ export const invalidateAndReopen = async (repoId) => {
265
+ const entry = pool.get(repoId);
266
+ if (!entry)
267
+ return;
268
+ const entryDbPath = entry.dbPath;
269
+ closeOne(repoId);
270
+ // Close and remove the native Database handle to release the file lock.
271
+ // Without this, the child process cannot acquire a write lock on the DB file.
272
+ const shared = dbCache.get(entryDbPath);
273
+ if (shared && shared.refCount <= 0) {
274
+ try {
275
+ await shared.db.close();
276
+ console.error('Code Mapper: Native DB handle closed successfully');
277
+ }
278
+ catch (err) {
279
+ console.error(`Code Mapper: DB close failed (may still release lock on GC): ${err instanceof Error ? err.message : err}`);
280
+ }
281
+ dbCache.delete(entryDbPath);
282
+ }
283
+ else if (shared) {
284
+ console.error(`Code Mapper: Cannot close DB — ${shared.refCount} other refs still active`);
285
+ }
286
+ };
287
+ /** Check if a specific repo's pool is active */
288
+ export const isLbugReady = (repoId) => pool.has(repoId);
289
+ /** Write-connection retry config */
290
+ const WRITE_RETRY_ATTEMPTS = 3;
291
+ const WRITE_RETRY_DELAY_MS = 1500;
292
+ /**
293
+ * Open a short-lived read-write connection, execute the callback, then close.
294
+ * The read-only pool must be invalidated BEFORE calling this to avoid lock
295
+ * conflicts. Retries on lock errors (e.g., concurrent `code-mapper analyze`).
296
+ */
297
+ export const withWriteConnection = async (dbPath, fn) => {
298
+ let lastError = null;
299
+ for (let attempt = 1; attempt <= WRITE_RETRY_ATTEMPTS; attempt++) {
300
+ let db = null;
301
+ let conn = null;
302
+ try {
303
+ db = new lbug.Database(dbPath);
304
+ conn = new lbug.Connection(db);
305
+ const result = await fn(conn);
306
+ return result;
307
+ }
308
+ catch (err) {
309
+ lastError = err instanceof Error ? err : new Error(String(err));
310
+ const isLockError = lastError.message.includes('Could not set lock') ||
311
+ lastError.message.includes('lock');
312
+ if (!isLockError || attempt === WRITE_RETRY_ATTEMPTS)
313
+ break;
314
+ await new Promise((resolve) => setTimeout(resolve, WRITE_RETRY_DELAY_MS * attempt));
315
+ }
316
+ finally {
317
+ try {
318
+ if (conn)
319
+ await conn.close();
320
+ }
321
+ catch { }
322
+ try {
323
+ if (db)
324
+ await db.close();
325
+ }
326
+ catch { }
327
+ }
328
+ }
329
+ throw lastError ?? new Error('withWriteConnection failed');
330
+ };
@@ -0,0 +1,188 @@
1
+ /** @file local-backend.ts
2
+ * @description Tool implementations using local .code-mapper/ indexes
3
+ * Supports multiple indexed repositories via a global registry
4
+ * LadybugDB connections are opened lazily per repo on first query */
5
+ import { type RegistryEntry } from '../../storage/repo-manager.js';
6
+ /** Quick test-file detection for filtering impact results across all supported languages */
7
+ export declare function isTestFilePath(filePath: string): boolean;
8
+ /** Valid LadybugDB node labels for safe Cypher query construction */
9
+ export declare const VALID_NODE_LABELS: Set<string>;
10
+ /** Valid relation types for impact analysis filtering */
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 */
15
+ export declare function isWriteQuery(query: string): boolean;
16
+ export interface CodebaseContext {
17
+ projectName: string;
18
+ stats: {
19
+ fileCount: number;
20
+ functionCount: number;
21
+ communityCount: number;
22
+ processCount: number;
23
+ };
24
+ }
25
+ interface RepoHandle {
26
+ id: string;
27
+ name: string;
28
+ repoPath: string;
29
+ storagePath: string;
30
+ lbugPath: string;
31
+ indexedAt: string;
32
+ lastCommit: string;
33
+ stats?: RegistryEntry['stats'];
34
+ }
35
+ export declare class LocalBackend {
36
+ private repos;
37
+ private contextCache;
38
+ private initializedRepos;
39
+ private watchers;
40
+ /** Per-repo promise chain that serializes ensureFresh calls.
41
+ * Prevents race: Call 2 skipping refresh while Call 1 is still writing. */
42
+ private refreshLocks;
43
+ /** Hard ceiling — beyond this, incremental is unreliable, warn prominently */
44
+ private static readonly MAX_INCREMENTAL_FILES;
45
+ /** Optional tsgo LSP service for confidence-1.0 semantic resolution */
46
+ private tsgoEnabled;
47
+ /** Start file system watcher for a repo to detect source changes */
48
+ private startWatcher;
49
+ /**
50
+ * Seed the watcher with file changes that happened while the MCP server was
51
+ * not running. Compares meta.json lastCommit against current git HEAD and
52
+ * injects any changed files as dirty entries.
53
+ */
54
+ private seedWatcherFromGit;
55
+ /**
56
+ * Serialized entry point — ensures only one refresh runs per repo at a time.
57
+ * Call 2 waits for Call 1 to finish, then re-checks the watcher.
58
+ */
59
+ private ensureFresh;
60
+ /** Check for file changes and refresh the DB + embeddings before a tool call */
61
+ private doEnsureFresh;
62
+ /**
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.
77
+ */
78
+ 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>;
102
+ /**
103
+ * Re-read the global registry and update the in-memory repo map
104
+ * LadybugDB connections for removed repos idle-timeout naturally
105
+ */
106
+ private refreshRepos;
107
+ /** Generate a stable repo ID from name + path (appends hash on collision) */
108
+ private repoId;
109
+ /**
110
+ * Resolve which repo to use (by name, path, or single-repo default)
111
+ * Re-reads the registry once on miss in case a new repo was indexed
112
+ */
113
+ resolveRepo(repoParam?: string): Promise<RepoHandle>;
114
+ /** Try to resolve a repo from the in-memory cache, returns null on miss */
115
+ private resolveRepoFromCache;
116
+ private ensureInitialized;
117
+ /** Get context for a specific repo (or the single repo if only one) */
118
+ getContext(repoId?: string): CodebaseContext | null;
119
+ /** List all registered repos, re-reading the registry to discover new ones */
120
+ listRepos(): Promise<Array<{
121
+ name: string;
122
+ path: string;
123
+ indexedAt: string;
124
+ lastCommit: string;
125
+ stats?: any;
126
+ }>>;
127
+ /** Extract signature from content: the declaration line(s), not the full body */
128
+ private extractSignature;
129
+ /** Short file path: strip common prefix if all paths share it */
130
+ private shortPath;
131
+ /** C2: Count how many initial steps two flows share (by symbol name) */
132
+ private sharedPrefixLength;
133
+ /** D1: Generate a readable flow description from step names */
134
+ /** Generate readable flow description from step names.
135
+ * Uses step 2 (first dispatch point) as the middle — it's where the entry
136
+ * point specializes and is the most discriminating step in most flows. */
137
+ private describeFlow;
138
+ private formatQueryAsText;
139
+ private formatContextAsText;
140
+ private formatImpactAsText;
141
+ private formatDetectChangesAsText;
142
+ /** C3: Check if index is behind HEAD and return a warning prefix */
143
+ private getStalenessWarning;
144
+ callTool(method: string, params: any): Promise<any>;
145
+ /**
146
+ * Query tool: process-grouped search
147
+ * Hybrid BM25+semantic search, trace to processes, rank by relevance + cohesion
148
+ */
149
+ private query;
150
+ /**
151
+ * BM25 keyword search helper - uses LadybugDB FTS for always-fresh results
152
+ */
153
+ private bm25Search;
154
+ /**
155
+ * Semantic vector search helper
156
+ */
157
+ 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;
162
+ /** Aggregate same-named clusters by heuristicLabel, filtering tiny clusters (<5 symbols) */
163
+ private aggregateClusters;
164
+ private overview;
165
+ /** Context tool: 360-degree symbol view with categorized refs and disambiguation */
166
+ private context;
167
+ /** Legacy explore for backwards compatibility with resources.ts */
168
+ private explore;
169
+ /** Detect changes: git-diff impact analysis mapping changed lines to symbols and processes */
170
+ private detectChanges;
171
+ /** Rename tool: multi-file coordinated rename using graph (high confidence) + text search */
172
+ private rename;
173
+ private impact;
174
+ /** Query clusters (communities) directly from graph for getClustersResource */
175
+ queryClusters(repoName?: string, limit?: number): Promise<{
176
+ clusters: any[];
177
+ }>;
178
+ /** Query processes directly from graph for getProcessesResource */
179
+ queryProcesses(repoName?: string, limit?: number): Promise<{
180
+ processes: any[];
181
+ }>;
182
+ /** Query cluster detail (members) for getClusterDetailResource */
183
+ queryClusterDetail(name: string, repoName?: string): Promise<any>;
184
+ /** Query process detail (steps) for getProcessDetailResource */
185
+ queryProcessDetail(name: string, repoName?: string): Promise<any>;
186
+ disconnect(): Promise<void>;
187
+ }
188
+ export {};