@zipbul/gildash 0.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.ko.md +386 -0
  3. package/README.md +445 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +2736 -0
  6. package/dist/index.js.map +42 -0
  7. package/dist/migrations/0000_soft_revanche.sql +56 -0
  8. package/dist/migrations/meta/0000_snapshot.json +408 -0
  9. package/dist/migrations/meta/_journal.json +13 -0
  10. package/dist/migrations/migrations/0000_soft_revanche.sql +56 -0
  11. package/dist/migrations/migrations/meta/0000_snapshot.json +408 -0
  12. package/dist/migrations/migrations/meta/_journal.json +13 -0
  13. package/dist/src/codeledger.d.ts +103 -0
  14. package/dist/src/common/hasher.d.ts +2 -0
  15. package/dist/src/common/index.d.ts +5 -0
  16. package/dist/src/common/lru-cache.d.ts +10 -0
  17. package/dist/src/common/path-utils.d.ts +2 -0
  18. package/dist/src/common/project-discovery.d.ts +6 -0
  19. package/dist/src/common/tsconfig-resolver.d.ts +6 -0
  20. package/dist/src/errors.d.ts +42 -0
  21. package/dist/src/extractor/calls-extractor.d.ts +3 -0
  22. package/dist/src/extractor/extractor-utils.d.ts +5 -0
  23. package/dist/src/extractor/heritage-extractor.d.ts +3 -0
  24. package/dist/src/extractor/imports-extractor.d.ts +4 -0
  25. package/dist/src/extractor/index.d.ts +7 -0
  26. package/dist/src/extractor/relation-extractor.d.ts +4 -0
  27. package/dist/src/extractor/symbol-extractor.d.ts +3 -0
  28. package/dist/src/extractor/types.d.ts +162 -0
  29. package/dist/src/gildash.d.ts +284 -0
  30. package/dist/src/index.d.ts +8 -0
  31. package/dist/src/indexer/file-indexer.d.ts +27 -0
  32. package/dist/src/indexer/index-coordinator.d.ts +80 -0
  33. package/dist/src/indexer/index.d.ts +8 -0
  34. package/dist/src/indexer/relation-indexer.d.ts +24 -0
  35. package/dist/src/indexer/symbol-indexer.d.ts +29 -0
  36. package/dist/src/parser/ast-utils.d.ts +10 -0
  37. package/dist/src/parser/index.d.ts +6 -0
  38. package/dist/src/parser/jsdoc-parser.d.ts +2 -0
  39. package/dist/src/parser/parse-cache.d.ts +10 -0
  40. package/dist/src/parser/parse-source.d.ts +3 -0
  41. package/dist/src/parser/source-position.d.ts +3 -0
  42. package/dist/src/parser/types.d.ts +16 -0
  43. package/dist/src/search/dependency-graph.d.ts +71 -0
  44. package/dist/src/search/index.d.ts +6 -0
  45. package/dist/src/search/relation-search.d.ts +45 -0
  46. package/dist/src/search/symbol-search.d.ts +76 -0
  47. package/dist/src/store/connection.d.ts +30 -0
  48. package/dist/src/store/index.d.ts +10 -0
  49. package/dist/src/store/repositories/file.repository.d.ts +18 -0
  50. package/dist/src/store/repositories/fts-utils.d.ts +1 -0
  51. package/dist/src/store/repositories/relation.repository.d.ts +29 -0
  52. package/dist/src/store/repositories/symbol.repository.d.ts +46 -0
  53. package/dist/src/store/schema.d.ts +634 -0
  54. package/dist/src/watcher/index.d.ts +3 -0
  55. package/dist/src/watcher/ownership.d.ts +22 -0
  56. package/dist/src/watcher/project-watcher.d.ts +13 -0
  57. package/dist/src/watcher/types.d.ts +11 -0
  58. package/package.json +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,2736 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+
13
+ // src/gildash.ts
14
+ import path5 from "path";
15
+ import { existsSync as existsSync2 } from "fs";
16
+
17
+ // src/parser/parse-source.ts
18
+ import { parseSync as defaultParseSync } from "oxc-parser";
19
+
20
+ // src/errors.ts
21
+ class GildashError extends Error {
22
+ constructor(message, options) {
23
+ super(message, options);
24
+ this.name = "GildashError";
25
+ }
26
+ }
27
+
28
+ class WatcherError extends GildashError {
29
+ constructor(message, options) {
30
+ super(message, options);
31
+ this.name = "WatcherError";
32
+ }
33
+ }
34
+
35
+ class ParseError extends GildashError {
36
+ constructor(message, options) {
37
+ super(message, options);
38
+ this.name = "ParseError";
39
+ }
40
+ }
41
+
42
+ class ExtractError extends GildashError {
43
+ constructor(message, options) {
44
+ super(message, options);
45
+ this.name = "ExtractError";
46
+ }
47
+ }
48
+
49
+ class IndexError extends GildashError {
50
+ constructor(message, options) {
51
+ super(message, options);
52
+ this.name = "IndexError";
53
+ }
54
+ }
55
+
56
+ class StoreError extends GildashError {
57
+ constructor(message, options) {
58
+ super(message, options);
59
+ this.name = "StoreError";
60
+ }
61
+ }
62
+
63
+ class SearchError extends GildashError {
64
+ constructor(message, options) {
65
+ super(message, options);
66
+ this.name = "SearchError";
67
+ }
68
+ }
69
+
70
+ // src/parser/parse-source.ts
71
+ function parseSource(filePath, sourceText, parseSyncFn = defaultParseSync) {
72
+ try {
73
+ const { program, errors, comments } = parseSyncFn(filePath, sourceText);
74
+ return { filePath, program, errors, comments, sourceText };
75
+ } catch (err) {
76
+ throw new ParseError(`Failed to parse file: ${filePath}`, { cause: err });
77
+ }
78
+ }
79
+
80
+ // src/common/lru-cache.ts
81
+ class LruCache {
82
+ #capacity;
83
+ #map = new Map;
84
+ constructor(capacity) {
85
+ this.#capacity = Math.max(1, capacity);
86
+ }
87
+ get size() {
88
+ return this.#map.size;
89
+ }
90
+ has(key) {
91
+ return this.#map.has(key);
92
+ }
93
+ get(key) {
94
+ if (!this.#map.has(key)) {
95
+ return;
96
+ }
97
+ const value = this.#map.get(key);
98
+ this.#map.delete(key);
99
+ this.#map.set(key, value);
100
+ return value;
101
+ }
102
+ set(key, value) {
103
+ if (this.#map.has(key)) {
104
+ this.#map.delete(key);
105
+ }
106
+ this.#map.set(key, value);
107
+ if (this.#map.size > this.#capacity) {
108
+ const oldestKey = this.#map.keys().next().value;
109
+ if (oldestKey !== undefined) {
110
+ this.#map.delete(oldestKey);
111
+ }
112
+ }
113
+ }
114
+ delete(key) {
115
+ return this.#map.delete(key);
116
+ }
117
+ clear() {
118
+ this.#map.clear();
119
+ }
120
+ }
121
+
122
+ // src/parser/parse-cache.ts
123
+ class ParseCache {
124
+ lru;
125
+ constructor(capacity = 500) {
126
+ this.lru = new LruCache(capacity);
127
+ }
128
+ get(filePath) {
129
+ return this.lru.get(filePath);
130
+ }
131
+ set(filePath, parsed) {
132
+ this.lru.set(filePath, parsed);
133
+ }
134
+ invalidate(filePath) {
135
+ this.lru.delete(filePath);
136
+ }
137
+ invalidateAll() {
138
+ this.lru.clear();
139
+ }
140
+ size() {
141
+ return this.lru.size;
142
+ }
143
+ }
144
+
145
+ // src/parser/source-position.ts
146
+ function buildLineOffsets(sourceText) {
147
+ const offsets = [0];
148
+ for (let i = 0;i < sourceText.length; i++) {
149
+ if (sourceText[i] === `
150
+ `) {
151
+ offsets.push(i + 1);
152
+ }
153
+ }
154
+ return offsets;
155
+ }
156
+ function getLineColumn(offsets, offset) {
157
+ let lo = 0;
158
+ let hi = offsets.length - 1;
159
+ while (lo < hi) {
160
+ const mid = lo + hi + 1 >> 1;
161
+ if (offsets[mid] <= offset) {
162
+ lo = mid;
163
+ } else {
164
+ hi = mid - 1;
165
+ }
166
+ }
167
+ return { line: lo + 1, column: offset - offsets[lo] };
168
+ }
169
+
170
+ // src/parser/jsdoc-parser.ts
171
+ import { parse } from "comment-parser";
172
+ function parseJsDoc(commentText) {
173
+ try {
174
+ let stripped = commentText.trim();
175
+ if (stripped.startsWith("/**"))
176
+ stripped = stripped.slice(3);
177
+ if (stripped.endsWith("*/"))
178
+ stripped = stripped.slice(0, -2);
179
+ const blocks = parse(`/** ${stripped} */`);
180
+ const block = blocks[0] ?? { description: "", tags: [] };
181
+ return {
182
+ description: (block.description ?? "").trim(),
183
+ tags: (block.tags ?? []).map((t) => ({
184
+ tag: t.tag ?? "",
185
+ name: t.name ?? "",
186
+ type: t.type ?? "",
187
+ description: t.description ?? "",
188
+ optional: t.optional ?? false,
189
+ ...t.default !== undefined ? { default: t.default } : {}
190
+ }))
191
+ };
192
+ } catch (err) {
193
+ throw new ParseError(`Failed to parse JSDoc comment`, { cause: err });
194
+ }
195
+ }
196
+
197
+ // src/extractor/symbol-extractor.ts
198
+ function extractSymbols(parsed) {
199
+ const { program, sourceText, comments } = parsed;
200
+ const lineOffsets = buildLineOffsets(sourceText);
201
+ function span(start, end) {
202
+ return {
203
+ start: getLineColumn(lineOffsets, start),
204
+ end: getLineColumn(lineOffsets, end)
205
+ };
206
+ }
207
+ function findJsDocComment(nodeStart) {
208
+ let best = null;
209
+ for (const c of comments) {
210
+ if (c.type !== "Block")
211
+ continue;
212
+ if (c.end > nodeStart)
213
+ continue;
214
+ if (!c.value.startsWith("*"))
215
+ continue;
216
+ if (!best || c.end > best.end) {
217
+ best = { value: `/*${c.value}*/`, end: c.end };
218
+ }
219
+ }
220
+ if (!best)
221
+ return;
222
+ for (const stmt of program.body) {
223
+ const stmtStart = stmt.start ?? 0;
224
+ if (stmtStart === nodeStart)
225
+ continue;
226
+ if (stmtStart > best.end && stmtStart < nodeStart) {
227
+ return;
228
+ }
229
+ }
230
+ return best.value;
231
+ }
232
+ function typeText(typeAnnotation) {
233
+ if (!typeAnnotation)
234
+ return;
235
+ const inner = typeAnnotation.typeAnnotation ?? typeAnnotation;
236
+ return sourceText.slice(inner.start, inner.end);
237
+ }
238
+ function extractDecorators(decorators) {
239
+ if (!decorators || decorators.length === 0)
240
+ return [];
241
+ return decorators.map((d) => {
242
+ const expr = d.expression;
243
+ if (!expr)
244
+ return { name: "unknown" };
245
+ if (expr.type === "CallExpression") {
246
+ const name = expr.callee?.name ?? expr.callee?.property?.name ?? "unknown";
247
+ const args = (expr.arguments ?? []).map((a) => sourceText.slice(a.start, a.end));
248
+ return { name, arguments: args.length > 0 ? args : undefined };
249
+ }
250
+ if (expr.type === "Identifier")
251
+ return { name: expr.name ?? "unknown" };
252
+ return { name: sourceText.slice(expr.start, expr.end) };
253
+ });
254
+ }
255
+ function extractParam(p) {
256
+ const inner = p.type === "TSParameterProperty" ? p.parameter : p;
257
+ if (inner?.type === "RestElement") {
258
+ const argName = inner.argument?.name ?? "unknown";
259
+ const name2 = `...${argName}`;
260
+ const typeAnn2 = inner.typeAnnotation;
261
+ const type2 = typeAnn2 ? typeText(typeAnn2) : undefined;
262
+ const param2 = { name: name2, isOptional: false };
263
+ if (type2)
264
+ param2.type = type2;
265
+ return param2;
266
+ }
267
+ if (inner?.type === "AssignmentPattern") {
268
+ const left = inner.left;
269
+ const right = inner.right;
270
+ const name2 = left?.name ?? "unknown";
271
+ const typeAnn2 = left?.typeAnnotation;
272
+ const type2 = typeAnn2 ? typeText(typeAnn2) : undefined;
273
+ const defaultValue = sourceText.slice(right.start, right.end);
274
+ const decos2 = extractDecorators(left?.decorators ?? []);
275
+ const param2 = { name: name2, isOptional: true, defaultValue };
276
+ if (type2)
277
+ param2.type = type2;
278
+ if (decos2.length > 0)
279
+ param2.decorators = decos2;
280
+ return param2;
281
+ }
282
+ const name = inner?.name ?? inner?.pattern?.name ?? "unknown";
283
+ const optional = !!inner?.optional;
284
+ const typeAnn = inner?.typeAnnotation;
285
+ const type = typeAnn ? typeText(typeAnn) : undefined;
286
+ const decos = extractDecorators(inner?.decorators ?? []);
287
+ const param = { name, isOptional: optional };
288
+ if (type)
289
+ param.type = type;
290
+ if (decos.length > 0)
291
+ param.decorators = decos;
292
+ return param;
293
+ }
294
+ function extractModifiers(node, fn) {
295
+ const mods = [];
296
+ if (fn?.async)
297
+ mods.push("async");
298
+ if (node.static)
299
+ mods.push("static");
300
+ if (node.abstract)
301
+ mods.push("abstract");
302
+ if (node.readonly)
303
+ mods.push("readonly");
304
+ if (node.override)
305
+ mods.push("override");
306
+ if (node.declare)
307
+ mods.push("declare");
308
+ if (node.const)
309
+ mods.push("const");
310
+ const acc = node.accessibility;
311
+ if (acc === "private")
312
+ mods.push("private");
313
+ else if (acc === "protected")
314
+ mods.push("protected");
315
+ else if (acc === "public")
316
+ mods.push("public");
317
+ return mods;
318
+ }
319
+ function classHeritage(node) {
320
+ const heritage = [];
321
+ if (node.superClass) {
322
+ const name = sourceText.slice(node.superClass.start, node.superClass.end);
323
+ heritage.push({ kind: "extends", name });
324
+ }
325
+ const impls = node.implements ?? [];
326
+ for (const impl of impls) {
327
+ const expr = impl.expression ?? impl;
328
+ const name = sourceText.slice(expr.start, expr.end);
329
+ heritage.push({ kind: "implements", name });
330
+ }
331
+ return heritage;
332
+ }
333
+ function interfaceHeritage(node) {
334
+ const heritage = [];
335
+ for (const ext of node.extends ?? []) {
336
+ const expr = ext.expression ?? ext;
337
+ const name = sourceText.slice(expr.start, expr.end);
338
+ heritage.push({ kind: "extends", name });
339
+ }
340
+ return heritage;
341
+ }
342
+ function extractClassMembers(bodyNodes) {
343
+ const members = [];
344
+ for (const m of bodyNodes) {
345
+ if (m.type === "MethodDefinition") {
346
+ const name = m.key?.name ?? "unknown";
347
+ const fnValue = m.value;
348
+ const rawKind = m.kind ?? "method";
349
+ const methodKind = rawKind === "constructor" ? "constructor" : rawKind === "get" ? "getter" : rawKind === "set" ? "setter" : "method";
350
+ const mods = extractModifiers(m, fnValue);
351
+ const params = (fnValue?.params ?? []).map(extractParam);
352
+ const returnType = typeText(fnValue?.returnType);
353
+ const s = {
354
+ kind: "method",
355
+ name,
356
+ span: span(m.start, m.end),
357
+ isExported: false,
358
+ methodKind,
359
+ modifiers: mods,
360
+ parameters: params.length > 0 ? params : undefined,
361
+ returnType
362
+ };
363
+ members.push(s);
364
+ } else if (m.type === "PropertyDefinition") {
365
+ const name = m.key?.name ?? "unknown";
366
+ const mods = extractModifiers(m);
367
+ const s = {
368
+ kind: "property",
369
+ name,
370
+ span: span(m.start, m.end),
371
+ isExported: false,
372
+ modifiers: mods
373
+ };
374
+ members.push(s);
375
+ }
376
+ }
377
+ return members;
378
+ }
379
+ function extractInterfaceMembers(bodyNodes) {
380
+ const members = [];
381
+ for (const m of bodyNodes) {
382
+ if (m.type === "TSMethodSignature") {
383
+ const name = m.key?.name ?? "unknown";
384
+ const params = (m.params ?? []).map(extractParam);
385
+ const returnType = typeText(m.returnType);
386
+ members.push({
387
+ kind: "method",
388
+ name,
389
+ span: span(m.start, m.end),
390
+ isExported: false,
391
+ modifiers: [],
392
+ methodKind: "method",
393
+ parameters: params.length > 0 ? params : undefined,
394
+ returnType
395
+ });
396
+ } else if (m.type === "TSPropertySignature") {
397
+ const name = m.key?.name ?? "unknown";
398
+ const typeAnn = typeText(m.typeAnnotation);
399
+ const s = {
400
+ kind: "property",
401
+ name,
402
+ span: span(m.start, m.end),
403
+ isExported: false,
404
+ modifiers: m.readonly ? ["readonly"] : [],
405
+ returnType: typeAnn
406
+ };
407
+ members.push(s);
408
+ }
409
+ }
410
+ return members;
411
+ }
412
+ function buildSymbol(node, isExported) {
413
+ const type = node.type ?? "";
414
+ if (type === "FunctionDeclaration") {
415
+ const name = node.id?.name ?? "default";
416
+ const params = (node.params ?? []).map(extractParam);
417
+ const returnType = typeText(node.returnType);
418
+ const mods = extractModifiers(node, node);
419
+ const decos = extractDecorators(node.decorators ?? []);
420
+ const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
421
+ const sym = {
422
+ kind: "function",
423
+ name,
424
+ span: span(node.start, node.end),
425
+ isExported,
426
+ modifiers: mods,
427
+ parameters: params.length > 0 ? params : undefined,
428
+ returnType,
429
+ decorators: decos.length > 0 ? decos : undefined
430
+ };
431
+ if (typeParameters && typeParameters.length > 0)
432
+ sym.typeParameters = typeParameters;
433
+ return sym;
434
+ }
435
+ if (type === "ClassDeclaration" || type === "ClassExpression") {
436
+ const name = node.id?.name ?? "default";
437
+ const heritage = classHeritage(node);
438
+ const members = extractClassMembers(node.body?.body ?? []);
439
+ const decos = extractDecorators(node.decorators ?? []);
440
+ const mods = extractModifiers(node, node);
441
+ const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
442
+ const sym = {
443
+ kind: "class",
444
+ name,
445
+ span: span(node.start, node.end),
446
+ isExported,
447
+ modifiers: mods,
448
+ heritage: heritage.length > 0 ? heritage : undefined,
449
+ members: members.length > 0 ? members : undefined,
450
+ decorators: decos.length > 0 ? decos : undefined
451
+ };
452
+ if (typeParameters && typeParameters.length > 0)
453
+ sym.typeParameters = typeParameters;
454
+ return sym;
455
+ }
456
+ if (type === "VariableDeclaration") {
457
+ const symbols = [];
458
+ for (const decl of node.declarations ?? []) {
459
+ const id = decl.id;
460
+ const init = decl.init;
461
+ if (id?.type === "ObjectPattern") {
462
+ for (const prop of id.properties ?? []) {
463
+ const propName = prop.value?.name ?? prop.key?.name ?? "unknown";
464
+ symbols.push({
465
+ kind: "variable",
466
+ name: propName,
467
+ span: span(prop.start ?? decl.start, prop.end ?? decl.end),
468
+ isExported,
469
+ modifiers: []
470
+ });
471
+ }
472
+ continue;
473
+ }
474
+ if (id?.type === "ArrayPattern") {
475
+ for (const elem of id.elements ?? []) {
476
+ if (!elem || elem.type !== "Identifier")
477
+ continue;
478
+ const elemName = elem.name ?? "unknown";
479
+ symbols.push({
480
+ kind: "variable",
481
+ name: elemName,
482
+ span: span(elem.start ?? decl.start, elem.end ?? decl.end),
483
+ isExported,
484
+ modifiers: []
485
+ });
486
+ }
487
+ continue;
488
+ }
489
+ const name = id?.name ?? "unknown";
490
+ let kind = "variable";
491
+ let params;
492
+ let returnType;
493
+ if (init?.type === "FunctionExpression" || init?.type === "ArrowFunctionExpression") {
494
+ kind = "function";
495
+ const rawParams = init.params ?? [];
496
+ params = rawParams.map(extractParam);
497
+ returnType = typeText(init.returnType);
498
+ }
499
+ const mods = [];
500
+ symbols.push({
501
+ kind,
502
+ name,
503
+ span: span(decl.start, decl.end),
504
+ isExported,
505
+ modifiers: mods,
506
+ parameters: params,
507
+ returnType
508
+ });
509
+ }
510
+ if (symbols.length === 0)
511
+ return null;
512
+ if (symbols.length === 1)
513
+ return symbols[0];
514
+ return symbols;
515
+ }
516
+ if (type === "TSTypeAliasDeclaration") {
517
+ const name = node.id?.name ?? "unknown";
518
+ return {
519
+ kind: "type",
520
+ name,
521
+ span: span(node.start, node.end),
522
+ isExported,
523
+ modifiers: []
524
+ };
525
+ }
526
+ if (type === "TSInterfaceDeclaration") {
527
+ const name = node.id?.name ?? "unknown";
528
+ const heritage = interfaceHeritage(node);
529
+ const members = extractInterfaceMembers(node.body?.body ?? []);
530
+ const typeParameters = node.typeParameters?.params?.map((p) => p.name?.name).filter(Boolean) || undefined;
531
+ const sym = {
532
+ kind: "interface",
533
+ name,
534
+ span: span(node.start, node.end),
535
+ isExported,
536
+ modifiers: [],
537
+ heritage: heritage.length > 0 ? heritage : undefined,
538
+ members: members.length > 0 ? members : undefined
539
+ };
540
+ if (typeParameters && typeParameters.length > 0)
541
+ sym.typeParameters = typeParameters;
542
+ return sym;
543
+ }
544
+ if (type === "TSEnumDeclaration") {
545
+ const name = node.id?.name ?? "unknown";
546
+ const mods = extractModifiers(node);
547
+ const rawMembers = node.body?.members ?? [];
548
+ const members = rawMembers.map((m) => ({
549
+ kind: "property",
550
+ name: m.id?.name ?? m.id?.value ?? "unknown",
551
+ span: span(m.start, m.end),
552
+ isExported: false,
553
+ modifiers: []
554
+ }));
555
+ return {
556
+ kind: "enum",
557
+ name,
558
+ span: span(node.start, node.end),
559
+ isExported,
560
+ modifiers: mods,
561
+ members: members.length > 0 ? members : undefined
562
+ };
563
+ }
564
+ return null;
565
+ }
566
+ const result = [];
567
+ for (const node of program.body) {
568
+ let sym = null;
569
+ const record = node;
570
+ const type = typeof record.type === "string" ? record.type : "";
571
+ if (type === "ExportNamedDeclaration") {
572
+ const n = node;
573
+ if (n.declaration) {
574
+ sym = buildSymbol(n.declaration, true);
575
+ if (sym && !Array.isArray(sym)) {
576
+ sym.span = span(n.start, n.end);
577
+ } else if (Array.isArray(sym)) {
578
+ for (const s of sym)
579
+ s.span = span(n.start, n.end);
580
+ }
581
+ }
582
+ } else if (type === "ExportDefaultDeclaration") {
583
+ const n = node;
584
+ const decl = n.declaration;
585
+ if (decl) {
586
+ sym = buildSymbol(decl, true);
587
+ if (sym && !Array.isArray(sym)) {
588
+ sym.name = decl.id?.name ?? "default";
589
+ sym.isExported = true;
590
+ sym.span = span(n.start, n.end);
591
+ }
592
+ }
593
+ } else {
594
+ sym = buildSymbol(node, false);
595
+ }
596
+ const syms = Array.isArray(sym) ? sym : sym ? [sym] : [];
597
+ for (const s of syms) {
598
+ const nodeStart = node.start ?? 0;
599
+ const jsdocText = findJsDocComment(nodeStart);
600
+ if (jsdocText) {
601
+ s.jsDoc = parseJsDoc(jsdocText);
602
+ }
603
+ result.push(s);
604
+ }
605
+ }
606
+ return result;
607
+ }
608
+
609
+ // src/extractor/extractor-utils.ts
610
+ import { resolve, dirname, extname } from "path";
611
+ function resolveImport(currentFilePath, importPath, tsconfigPaths) {
612
+ const withTypeScriptCandidates = (resolved) => {
613
+ const extension = extname(resolved);
614
+ if (extension === "") {
615
+ return [
616
+ resolved + ".ts",
617
+ resolved + "/index.ts",
618
+ resolved + ".mts",
619
+ resolved + "/index.mts",
620
+ resolved + ".cts",
621
+ resolved + "/index.cts"
622
+ ];
623
+ }
624
+ if (extension === ".js")
625
+ return [resolved.slice(0, -3) + ".ts"];
626
+ if (extension === ".mjs")
627
+ return [resolved.slice(0, -4) + ".mts"];
628
+ if (extension === ".cjs")
629
+ return [resolved.slice(0, -4) + ".cts"];
630
+ return [resolved];
631
+ };
632
+ if (importPath.startsWith(".")) {
633
+ const resolved = resolve(dirname(currentFilePath), importPath);
634
+ return withTypeScriptCandidates(resolved);
635
+ }
636
+ if (tsconfigPaths) {
637
+ for (const [pattern, targets] of tsconfigPaths.paths) {
638
+ if (targets.length === 0)
639
+ continue;
640
+ const starIdx = pattern.indexOf("*");
641
+ if (starIdx === -1) {
642
+ if (importPath === pattern) {
643
+ const candidates = [];
644
+ for (const t of targets) {
645
+ candidates.push(...withTypeScriptCandidates(resolve(tsconfigPaths.baseUrl, t)));
646
+ }
647
+ return candidates;
648
+ }
649
+ } else {
650
+ const prefix = pattern.slice(0, starIdx);
651
+ const suffix = pattern.slice(starIdx + 1);
652
+ if (importPath.startsWith(prefix) && (suffix === "" || importPath.endsWith(suffix))) {
653
+ const captured = importPath.slice(prefix.length, suffix === "" ? undefined : importPath.length - suffix.length);
654
+ const candidates = [];
655
+ for (const t of targets) {
656
+ candidates.push(...withTypeScriptCandidates(resolve(tsconfigPaths.baseUrl, t.replace("*", captured))));
657
+ }
658
+ return candidates;
659
+ }
660
+ }
661
+ }
662
+ }
663
+ return [];
664
+ }
665
+ function buildImportMap(ast, currentFilePath, tsconfigPaths, resolveImportFn = resolveImport) {
666
+ const map = new Map;
667
+ const body = ast.body ?? [];
668
+ for (const node of body) {
669
+ if (node.type !== "ImportDeclaration")
670
+ continue;
671
+ const sourcePath = node.source?.value ?? "";
672
+ const candidates = resolveImportFn(currentFilePath, sourcePath, tsconfigPaths);
673
+ if (candidates.length === 0)
674
+ continue;
675
+ const resolved = candidates[0];
676
+ const specifiers = node.specifiers ?? [];
677
+ for (const spec of specifiers) {
678
+ switch (spec.type) {
679
+ case "ImportSpecifier":
680
+ map.set(spec.local.name, {
681
+ path: resolved,
682
+ importedName: spec.imported.name
683
+ });
684
+ break;
685
+ case "ImportDefaultSpecifier":
686
+ map.set(spec.local.name, {
687
+ path: resolved,
688
+ importedName: "default"
689
+ });
690
+ break;
691
+ case "ImportNamespaceSpecifier":
692
+ map.set(spec.local.name, {
693
+ path: resolved,
694
+ importedName: "*"
695
+ });
696
+ break;
697
+ }
698
+ }
699
+ }
700
+ return map;
701
+ }
702
+
703
+ // src/parser/ast-utils.ts
704
+ var SKIP_KEYS = new Set(["loc", "start", "end", "scope"]);
705
+ function visit(node, callback) {
706
+ if (!node || typeof node !== "object")
707
+ return;
708
+ if (Array.isArray(node)) {
709
+ for (const item of node)
710
+ visit(item, callback);
711
+ return;
712
+ }
713
+ const record = node;
714
+ callback(record);
715
+ for (const key of Object.keys(record)) {
716
+ if (SKIP_KEYS.has(key))
717
+ continue;
718
+ const child = record[key];
719
+ if (child && typeof child === "object") {
720
+ visit(child, callback);
721
+ }
722
+ }
723
+ }
724
+ function getStringLiteralValue(node) {
725
+ if (!node || typeof node !== "object" || Array.isArray(node))
726
+ return null;
727
+ const record = node;
728
+ if ((record.type === "StringLiteral" || record.type === "Literal") && typeof record.value === "string") {
729
+ return record.value;
730
+ }
731
+ return null;
732
+ }
733
+ function getQualifiedName(expr) {
734
+ if (!expr || typeof expr !== "object" || Array.isArray(expr))
735
+ return null;
736
+ const node = expr;
737
+ if (node.type === "Identifier") {
738
+ const name = node.name;
739
+ return { root: name, parts: [], full: name };
740
+ }
741
+ if (node.type === "ThisExpression") {
742
+ return { root: "this", parts: [], full: "this" };
743
+ }
744
+ if (node.type === "Super") {
745
+ return { root: "super", parts: [], full: "super" };
746
+ }
747
+ if (node.type === "MemberExpression") {
748
+ const parts = [];
749
+ let current = node;
750
+ while (current.type === "MemberExpression") {
751
+ const prop = current.property;
752
+ if (!prop || typeof prop.name !== "string")
753
+ return null;
754
+ parts.unshift(prop.name);
755
+ current = current.object;
756
+ }
757
+ let root;
758
+ if (current.type === "Identifier") {
759
+ root = current.name;
760
+ } else if (current.type === "ThisExpression") {
761
+ root = "this";
762
+ } else if (current.type === "Super") {
763
+ root = "super";
764
+ } else {
765
+ return null;
766
+ }
767
+ const full = [root, ...parts].join(".");
768
+ return { root, parts, full };
769
+ }
770
+ return null;
771
+ }
772
+
773
+ // src/extractor/imports-extractor.ts
774
+ function extractImports(ast, filePath, tsconfigPaths, resolveImportFn = resolveImport) {
775
+ const relations = [];
776
+ const body = ast.body ?? [];
777
+ for (const node of body) {
778
+ if (node.type === "ImportDeclaration") {
779
+ const sourcePath = node.source?.value ?? "";
780
+ const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
781
+ if (candidates.length === 0)
782
+ continue;
783
+ const resolved = candidates[0];
784
+ const isType = node.importKind === "type";
785
+ relations.push({
786
+ type: "imports",
787
+ srcFilePath: filePath,
788
+ srcSymbolName: null,
789
+ dstFilePath: resolved,
790
+ dstSymbolName: null,
791
+ ...isType ? { metaJson: JSON.stringify({ isType: true }) } : {}
792
+ });
793
+ continue;
794
+ }
795
+ if (node.type === "ExportAllDeclaration" && node.source) {
796
+ const sourcePath = node.source?.value ?? "";
797
+ const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
798
+ if (candidates.length === 0)
799
+ continue;
800
+ const resolved = candidates[0];
801
+ const isType = node.exportKind === "type";
802
+ const meta = { isReExport: true };
803
+ if (isType)
804
+ meta.isType = true;
805
+ relations.push({
806
+ type: "imports",
807
+ srcFilePath: filePath,
808
+ srcSymbolName: null,
809
+ dstFilePath: resolved,
810
+ dstSymbolName: null,
811
+ metaJson: JSON.stringify(meta)
812
+ });
813
+ continue;
814
+ }
815
+ if (node.type === "ExportNamedDeclaration" && node.source) {
816
+ const sourcePath = node.source?.value ?? "";
817
+ const candidates = resolveImportFn(filePath, sourcePath, tsconfigPaths);
818
+ if (candidates.length === 0)
819
+ continue;
820
+ const resolved = candidates[0];
821
+ relations.push({
822
+ type: "imports",
823
+ srcFilePath: filePath,
824
+ srcSymbolName: null,
825
+ dstFilePath: resolved,
826
+ dstSymbolName: null,
827
+ metaJson: JSON.stringify({ isReExport: true })
828
+ });
829
+ }
830
+ }
831
+ visit(ast, (node) => {
832
+ if (node.type !== "ImportExpression")
833
+ return;
834
+ const sourceValue = getStringLiteralValue(node.source);
835
+ if (!sourceValue)
836
+ return;
837
+ const candidates = resolveImportFn(filePath, sourceValue, tsconfigPaths);
838
+ if (candidates.length === 0)
839
+ return;
840
+ const resolved = candidates[0];
841
+ relations.push({
842
+ type: "imports",
843
+ srcFilePath: filePath,
844
+ srcSymbolName: null,
845
+ dstFilePath: resolved,
846
+ dstSymbolName: null,
847
+ metaJson: JSON.stringify({ isDynamic: true })
848
+ });
849
+ });
850
+ return relations;
851
+ }
852
+
853
+ // src/extractor/calls-extractor.ts
854
+ function extractCalls(ast, filePath, importMap) {
855
+ const relations = [];
856
+ const functionStack = [];
857
+ const classStack = [];
858
+ function currentCaller() {
859
+ if (functionStack.length > 0)
860
+ return functionStack[functionStack.length - 1] ?? null;
861
+ return null;
862
+ }
863
+ function resolveCallee(qn) {
864
+ if (!qn)
865
+ return null;
866
+ const ref = importMap.get(qn.root);
867
+ if (qn.parts.length === 0) {
868
+ if (ref) {
869
+ return { dstFilePath: ref.path, dstSymbolName: ref.importedName, resolution: "import" };
870
+ }
871
+ return { dstFilePath: filePath, dstSymbolName: qn.root, resolution: "local" };
872
+ } else {
873
+ if (ref && ref.importedName === "*") {
874
+ const dstSymbolName = qn.parts[qn.parts.length - 1];
875
+ return { dstFilePath: ref.path, dstSymbolName, resolution: "namespace" };
876
+ }
877
+ return { dstFilePath: filePath, dstSymbolName: qn.full, resolution: "local-member" };
878
+ }
879
+ }
880
+ function walk(node) {
881
+ if (!node || typeof node !== "object")
882
+ return;
883
+ if (Array.isArray(node)) {
884
+ for (const item of node)
885
+ walk(item);
886
+ return;
887
+ }
888
+ const record = node;
889
+ const type = typeof record.type === "string" ? record.type : "";
890
+ if (type === "ClassDeclaration" || type === "ClassExpression") {
891
+ const classNode = record;
892
+ const className = classNode.id?.name ?? "AnonymousClass";
893
+ classStack.push(className);
894
+ walk(classNode.body);
895
+ classStack.pop();
896
+ return;
897
+ }
898
+ if (type === "FunctionDeclaration") {
899
+ const functionNode = record;
900
+ const name = functionNode.id?.name ?? "anonymous";
901
+ functionStack.push(name);
902
+ walk(functionNode.body);
903
+ functionStack.pop();
904
+ return;
905
+ }
906
+ if (type === "VariableDeclarator" && record.init && (record.init?.type === "FunctionExpression" || record.init?.type === "ArrowFunctionExpression")) {
907
+ const declarator = record;
908
+ const name = declarator.id?.name ?? "anonymous";
909
+ functionStack.push(name);
910
+ walk(declarator.init?.body ?? declarator.init);
911
+ functionStack.pop();
912
+ return;
913
+ }
914
+ if (type === "MethodDefinition" && record.value) {
915
+ const method = record;
916
+ const className = classStack[classStack.length - 1] ?? "";
917
+ const methodName = method.key?.name ?? "anonymous";
918
+ const fullName = className ? `${className}.${methodName}` : methodName;
919
+ functionStack.push(fullName);
920
+ walk(method.value?.body);
921
+ functionStack.pop();
922
+ return;
923
+ }
924
+ if (type === "FunctionExpression" || type === "ArrowFunctionExpression") {
925
+ const parentCaller = currentCaller();
926
+ const anonymousName = parentCaller ? `${parentCaller}.<anonymous>` : "<anonymous>";
927
+ functionStack.push(anonymousName);
928
+ walk(record.body);
929
+ functionStack.pop();
930
+ return;
931
+ }
932
+ if (type === "CallExpression") {
933
+ const call = record;
934
+ const qn = getQualifiedName(call.callee);
935
+ const dst = resolveCallee(qn);
936
+ if (dst) {
937
+ const srcSymbolName = currentCaller();
938
+ const meta = {};
939
+ if (srcSymbolName === null)
940
+ meta.scope = "module";
941
+ relations.push({
942
+ type: "calls",
943
+ srcFilePath: filePath,
944
+ srcSymbolName,
945
+ dstFilePath: dst.dstFilePath,
946
+ dstSymbolName: dst.dstSymbolName,
947
+ ...Object.keys(meta).length > 0 ? { metaJson: JSON.stringify(meta) } : {}
948
+ });
949
+ }
950
+ walk(call.callee);
951
+ for (const arg of call.arguments ?? [])
952
+ walk(arg);
953
+ return;
954
+ }
955
+ if (type === "NewExpression") {
956
+ const ctorCall = record;
957
+ const qn = getQualifiedName(ctorCall.callee);
958
+ const dst = resolveCallee(qn);
959
+ if (dst) {
960
+ const srcSymbolName = currentCaller();
961
+ const meta = { isNew: true };
962
+ if (srcSymbolName === null)
963
+ meta.scope = "module";
964
+ relations.push({
965
+ type: "calls",
966
+ srcFilePath: filePath,
967
+ srcSymbolName,
968
+ dstFilePath: dst.dstFilePath,
969
+ dstSymbolName: dst.dstSymbolName,
970
+ metaJson: JSON.stringify(meta)
971
+ });
972
+ }
973
+ for (const arg of ctorCall.arguments ?? [])
974
+ walk(arg);
975
+ return;
976
+ }
977
+ for (const key of Object.keys(record)) {
978
+ if (key === "loc" || key === "start" || key === "end" || key === "scope")
979
+ continue;
980
+ const child = record[key];
981
+ if (child && typeof child === "object") {
982
+ walk(child);
983
+ }
984
+ }
985
+ }
986
+ walk(ast);
987
+ return relations;
988
+ }
989
+
990
+ // src/extractor/heritage-extractor.ts
991
+ function extractHeritage(ast, filePath, importMap) {
992
+ const relations = [];
993
+ visit(ast, (node) => {
994
+ if (node.type === "TSInterfaceDeclaration") {
995
+ const interfaceName = node.id?.name ?? "AnonymousInterface";
996
+ const interfaces = node.extends ?? [];
997
+ for (const item of interfaces) {
998
+ const expr = item.expression ?? item;
999
+ const qn = getQualifiedName(expr);
1000
+ if (!qn)
1001
+ continue;
1002
+ const rel = resolveHeritageDst(qn, filePath, importMap);
1003
+ relations.push({
1004
+ type: "extends",
1005
+ srcFilePath: filePath,
1006
+ srcSymbolName: interfaceName,
1007
+ ...rel
1008
+ });
1009
+ }
1010
+ return;
1011
+ }
1012
+ if (node.type !== "ClassDeclaration" && node.type !== "ClassExpression")
1013
+ return;
1014
+ const className = node.id?.name ?? "AnonymousClass";
1015
+ if (node.superClass) {
1016
+ const qn = getQualifiedName(node.superClass);
1017
+ if (qn) {
1018
+ const rel = resolveHeritageDst(qn, filePath, importMap);
1019
+ relations.push({
1020
+ type: "extends",
1021
+ srcFilePath: filePath,
1022
+ srcSymbolName: className,
1023
+ ...rel
1024
+ });
1025
+ }
1026
+ }
1027
+ const impls = node.implements ?? [];
1028
+ for (const impl of impls) {
1029
+ const expr = impl.expression ?? impl;
1030
+ const qn = getQualifiedName(expr);
1031
+ if (!qn)
1032
+ continue;
1033
+ const rel = resolveHeritageDst(qn, filePath, importMap);
1034
+ relations.push({
1035
+ type: "implements",
1036
+ srcFilePath: filePath,
1037
+ srcSymbolName: className,
1038
+ ...rel
1039
+ });
1040
+ }
1041
+ });
1042
+ return relations;
1043
+ }
1044
+ function resolveHeritageDst(qn, currentFilePath, importMap) {
1045
+ const ref = importMap.get(qn.root);
1046
+ if (ref) {
1047
+ if (ref.importedName === "*") {
1048
+ const dstSymbolName = qn.parts[qn.parts.length - 1] ?? qn.root;
1049
+ return {
1050
+ dstFilePath: ref.path,
1051
+ dstSymbolName,
1052
+ metaJson: JSON.stringify({ isNamespaceImport: true })
1053
+ };
1054
+ }
1055
+ return {
1056
+ dstFilePath: ref.path,
1057
+ dstSymbolName: qn.parts.length > 0 ? qn.full : ref.importedName
1058
+ };
1059
+ }
1060
+ return {
1061
+ dstFilePath: currentFilePath,
1062
+ dstSymbolName: qn.full,
1063
+ metaJson: JSON.stringify({ isLocal: true })
1064
+ };
1065
+ }
1066
+
1067
+ // src/extractor/relation-extractor.ts
1068
+ function extractRelations(ast, filePath, tsconfigPaths) {
1069
+ const importMap = buildImportMap(ast, filePath, tsconfigPaths);
1070
+ const imports = extractImports(ast, filePath, tsconfigPaths);
1071
+ const calls = extractCalls(ast, filePath, importMap);
1072
+ const heritage = extractHeritage(ast, filePath, importMap);
1073
+ return [...imports, ...calls, ...heritage];
1074
+ }
1075
+
1076
+ // src/store/connection.ts
1077
+ import { Database } from "bun:sqlite";
1078
+ import { mkdirSync, unlinkSync, existsSync } from "fs";
1079
+ import { dirname as dirname2, join } from "path";
1080
+ import { drizzle } from "drizzle-orm/bun-sqlite";
1081
+ import { migrate } from "drizzle-orm/bun-sqlite/migrator";
1082
+
1083
+ // src/store/schema.ts
1084
+ var exports_schema = {};
1085
+ __export(exports_schema, {
1086
+ watcherOwner: () => watcherOwner,
1087
+ symbols: () => symbols,
1088
+ relations: () => relations,
1089
+ files: () => files,
1090
+ FTS_SETUP_SQL: () => FTS_SETUP_SQL
1091
+ });
1092
+ import { sql } from "drizzle-orm";
1093
+ import {
1094
+ sqliteTable,
1095
+ text,
1096
+ integer,
1097
+ real,
1098
+ index,
1099
+ primaryKey,
1100
+ foreignKey,
1101
+ check
1102
+ } from "drizzle-orm/sqlite-core";
1103
+ var files = sqliteTable("files", {
1104
+ project: text("project").notNull(),
1105
+ filePath: text("file_path").notNull(),
1106
+ mtimeMs: real("mtime_ms").notNull(),
1107
+ size: integer("size").notNull(),
1108
+ contentHash: text("content_hash").notNull(),
1109
+ updatedAt: text("updated_at").notNull()
1110
+ }, (table) => [primaryKey({ columns: [table.project, table.filePath] })]);
1111
+ var symbols = sqliteTable("symbols", {
1112
+ id: integer("id").primaryKey({ autoIncrement: true }),
1113
+ project: text("project").notNull(),
1114
+ filePath: text("file_path").notNull(),
1115
+ kind: text("kind").notNull(),
1116
+ name: text("name").notNull(),
1117
+ startLine: integer("start_line").notNull(),
1118
+ startColumn: integer("start_column").notNull(),
1119
+ endLine: integer("end_line").notNull(),
1120
+ endColumn: integer("end_column").notNull(),
1121
+ isExported: integer("is_exported").notNull().default(0),
1122
+ signature: text("signature"),
1123
+ fingerprint: text("fingerprint"),
1124
+ detailJson: text("detail_json"),
1125
+ contentHash: text("content_hash").notNull(),
1126
+ indexedAt: text("indexed_at").notNull()
1127
+ }, (table) => [
1128
+ index("idx_symbols_project_file").on(table.project, table.filePath),
1129
+ index("idx_symbols_project_kind").on(table.project, table.kind),
1130
+ index("idx_symbols_project_name").on(table.project, table.name),
1131
+ index("idx_symbols_fingerprint").on(table.project, table.fingerprint),
1132
+ foreignKey({
1133
+ columns: [table.project, table.filePath],
1134
+ foreignColumns: [files.project, files.filePath]
1135
+ }).onDelete("cascade")
1136
+ ]);
1137
+ var relations = sqliteTable("relations", {
1138
+ id: integer("id").primaryKey({ autoIncrement: true }),
1139
+ project: text("project").notNull(),
1140
+ type: text("type").notNull(),
1141
+ srcFilePath: text("src_file_path").notNull(),
1142
+ srcSymbolName: text("src_symbol_name"),
1143
+ dstFilePath: text("dst_file_path").notNull(),
1144
+ dstSymbolName: text("dst_symbol_name"),
1145
+ metaJson: text("meta_json")
1146
+ }, (table) => [
1147
+ index("idx_relations_src").on(table.project, table.srcFilePath),
1148
+ index("idx_relations_dst").on(table.project, table.dstFilePath),
1149
+ index("idx_relations_type").on(table.project, table.type),
1150
+ foreignKey({
1151
+ columns: [table.project, table.srcFilePath],
1152
+ foreignColumns: [files.project, files.filePath]
1153
+ }).onDelete("cascade"),
1154
+ foreignKey({
1155
+ columns: [table.project, table.dstFilePath],
1156
+ foreignColumns: [files.project, files.filePath]
1157
+ }).onDelete("cascade")
1158
+ ]);
1159
+ var watcherOwner = sqliteTable("watcher_owner", {
1160
+ id: integer("id").primaryKey(),
1161
+ pid: integer("pid").notNull(),
1162
+ startedAt: text("started_at").notNull(),
1163
+ heartbeatAt: text("heartbeat_at").notNull()
1164
+ }, (table) => [check("watcher_owner_singleton", sql`${table.id} = 1`)]);
1165
+ var FTS_SETUP_SQL = [
1166
+ `CREATE VIRTUAL TABLE IF NOT EXISTS symbols_fts USING fts5(
1167
+ name,
1168
+ file_path,
1169
+ kind,
1170
+ content=symbols,
1171
+ content_rowid=id
1172
+ )`,
1173
+ `CREATE TRIGGER IF NOT EXISTS symbols_ai
1174
+ AFTER INSERT ON symbols BEGIN
1175
+ INSERT INTO symbols_fts(rowid, name, file_path, kind)
1176
+ VALUES (new.id, new.name, new.file_path, new.kind);
1177
+ END`,
1178
+ `CREATE TRIGGER IF NOT EXISTS symbols_ad
1179
+ AFTER DELETE ON symbols BEGIN
1180
+ INSERT INTO symbols_fts(symbols_fts, rowid, name, file_path, kind)
1181
+ VALUES ('delete', old.id, old.name, old.file_path, old.kind);
1182
+ END`,
1183
+ `CREATE TRIGGER IF NOT EXISTS symbols_au
1184
+ AFTER UPDATE ON symbols BEGIN
1185
+ INSERT INTO symbols_fts(symbols_fts, rowid, name, file_path, kind)
1186
+ VALUES ('delete', old.id, old.name, old.file_path, old.kind);
1187
+ INSERT INTO symbols_fts(rowid, name, file_path, kind)
1188
+ VALUES (new.id, new.name, new.file_path, new.kind);
1189
+ END`
1190
+ ];
1191
+
1192
+ // src/store/connection.ts
1193
+ class DbConnection {
1194
+ client = null;
1195
+ drizzle = null;
1196
+ dbPath;
1197
+ txDepth = 0;
1198
+ constructor(opts) {
1199
+ this.dbPath = join(opts.projectRoot, ".zipbul", "gildash.db");
1200
+ }
1201
+ get drizzleDb() {
1202
+ if (!this.drizzle)
1203
+ throw new StoreError("Database is not open. Call open() first.");
1204
+ return this.drizzle;
1205
+ }
1206
+ open() {
1207
+ try {
1208
+ mkdirSync(dirname2(this.dbPath), { recursive: true });
1209
+ this.client = new Database(this.dbPath);
1210
+ this.client.run("PRAGMA journal_mode = WAL");
1211
+ this.client.run("PRAGMA foreign_keys = ON");
1212
+ this.client.run("PRAGMA busy_timeout = 5000");
1213
+ this.drizzle = drizzle(this.client, { schema: exports_schema });
1214
+ migrate(this.drizzle, {
1215
+ migrationsFolder: join(import.meta.dirname, "migrations")
1216
+ });
1217
+ for (const sql2 of FTS_SETUP_SQL) {
1218
+ this.client.run(sql2);
1219
+ }
1220
+ } catch (err) {
1221
+ if (this.isCorruptionError(err) && existsSync(this.dbPath)) {
1222
+ this.closeClient();
1223
+ unlinkSync(this.dbPath);
1224
+ for (const ext of ["-wal", "-shm"]) {
1225
+ const p = this.dbPath + ext;
1226
+ if (existsSync(p))
1227
+ unlinkSync(p);
1228
+ }
1229
+ try {
1230
+ this.open();
1231
+ return;
1232
+ } catch (retryErr) {
1233
+ throw new StoreError(`Failed to recover database at ${this.dbPath}`, { cause: retryErr });
1234
+ }
1235
+ }
1236
+ if (err instanceof StoreError)
1237
+ throw err;
1238
+ throw new StoreError(`Failed to open database at ${this.dbPath}`, { cause: err });
1239
+ }
1240
+ }
1241
+ close() {
1242
+ this.closeClient();
1243
+ this.drizzle = null;
1244
+ }
1245
+ transaction(fn) {
1246
+ const db = this.requireClient();
1247
+ if (this.txDepth === 0) {
1248
+ this.txDepth++;
1249
+ try {
1250
+ return db.transaction(() => fn(this))();
1251
+ } finally {
1252
+ this.txDepth--;
1253
+ }
1254
+ }
1255
+ const sp = `sp_${this.txDepth++}`;
1256
+ db.run(`SAVEPOINT "${sp}"`);
1257
+ try {
1258
+ const result = fn(this);
1259
+ db.run(`RELEASE SAVEPOINT "${sp}"`);
1260
+ return result;
1261
+ } catch (err) {
1262
+ db.run(`ROLLBACK TO SAVEPOINT "${sp}"`);
1263
+ db.run(`RELEASE SAVEPOINT "${sp}"`);
1264
+ throw err;
1265
+ } finally {
1266
+ this.txDepth--;
1267
+ }
1268
+ }
1269
+ immediateTransaction(fn) {
1270
+ const db = this.requireClient();
1271
+ this.txDepth++;
1272
+ db.run("BEGIN IMMEDIATE");
1273
+ try {
1274
+ const result = fn();
1275
+ db.run("COMMIT");
1276
+ return result;
1277
+ } catch (err) {
1278
+ db.run("ROLLBACK");
1279
+ throw err;
1280
+ } finally {
1281
+ this.txDepth--;
1282
+ }
1283
+ }
1284
+ query(sql2) {
1285
+ const row = this.requireClient().prepare(sql2).get();
1286
+ if (!row)
1287
+ return null;
1288
+ return Object.values(row)[0];
1289
+ }
1290
+ getTableNames() {
1291
+ const rows = this.requireClient().query("SELECT name FROM sqlite_master WHERE type = 'table'").all();
1292
+ return rows.map((r) => r.name);
1293
+ }
1294
+ selectOwner() {
1295
+ const row = this.requireClient().prepare("SELECT pid, heartbeat_at FROM watcher_owner WHERE id = 1").get();
1296
+ return row ?? undefined;
1297
+ }
1298
+ insertOwner(pid) {
1299
+ const now = new Date().toISOString();
1300
+ this.requireClient().prepare("INSERT INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(pid, now, now);
1301
+ }
1302
+ replaceOwner(pid) {
1303
+ const now = new Date().toISOString();
1304
+ this.requireClient().prepare("INSERT OR REPLACE INTO watcher_owner (id, pid, started_at, heartbeat_at) VALUES (1, ?, ?, ?)").run(pid, now, now);
1305
+ }
1306
+ touchOwner(pid) {
1307
+ const now = new Date().toISOString();
1308
+ this.requireClient().prepare("UPDATE watcher_owner SET heartbeat_at = ? WHERE id = 1 AND pid = ?").run(now, pid);
1309
+ }
1310
+ deleteOwner(pid) {
1311
+ this.requireClient().prepare("DELETE FROM watcher_owner WHERE id = 1 AND pid = ?").run(pid);
1312
+ }
1313
+ requireClient() {
1314
+ if (!this.client)
1315
+ throw new StoreError("Database is not open. Call open() first.");
1316
+ return this.client;
1317
+ }
1318
+ closeClient() {
1319
+ if (this.client) {
1320
+ this.client.close();
1321
+ this.client = null;
1322
+ }
1323
+ }
1324
+ isCorruptionError(err) {
1325
+ if (!(err instanceof Error))
1326
+ return false;
1327
+ const msg = err.message.toLowerCase();
1328
+ return msg.includes("malformed") || msg.includes("corrupt") || msg.includes("not a database") || msg.includes("disk i/o error") || msg.includes("sqlite_corrupt");
1329
+ }
1330
+ }
1331
+
1332
+ // src/store/repositories/file.repository.ts
1333
+ import { eq, and } from "drizzle-orm";
1334
+ class FileRepository {
1335
+ db;
1336
+ constructor(db) {
1337
+ this.db = db;
1338
+ }
1339
+ getFile(project, filePath) {
1340
+ return this.db.drizzleDb.select().from(files).where(and(eq(files.project, project), eq(files.filePath, filePath))).get() ?? null;
1341
+ }
1342
+ upsertFile(record) {
1343
+ this.db.drizzleDb.insert(files).values({
1344
+ project: record.project,
1345
+ filePath: record.filePath,
1346
+ mtimeMs: record.mtimeMs,
1347
+ size: record.size,
1348
+ contentHash: record.contentHash,
1349
+ updatedAt: record.updatedAt
1350
+ }).onConflictDoUpdate({
1351
+ target: [files.project, files.filePath],
1352
+ set: {
1353
+ mtimeMs: record.mtimeMs,
1354
+ size: record.size,
1355
+ contentHash: record.contentHash,
1356
+ updatedAt: record.updatedAt
1357
+ }
1358
+ }).run();
1359
+ }
1360
+ getAllFiles(project) {
1361
+ return this.db.drizzleDb.select().from(files).where(eq(files.project, project)).all();
1362
+ }
1363
+ getFilesMap(project) {
1364
+ const rows = this.getAllFiles(project);
1365
+ const map = new Map;
1366
+ for (const r of rows)
1367
+ map.set(r.filePath, r);
1368
+ return map;
1369
+ }
1370
+ deleteFile(project, filePath) {
1371
+ this.db.drizzleDb.delete(files).where(and(eq(files.project, project), eq(files.filePath, filePath))).run();
1372
+ }
1373
+ }
1374
+
1375
+ // src/store/repositories/symbol.repository.ts
1376
+ import { eq as eq2, and as and2, sql as sql2, count } from "drizzle-orm";
1377
+
1378
+ // src/store/repositories/fts-utils.ts
1379
+ function toFtsPrefixQuery(text2) {
1380
+ return text2.trim().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 0).map((token) => `"${token.replaceAll('"', '""')}"*`).join(" ");
1381
+ }
1382
+
1383
+ // src/store/repositories/symbol.repository.ts
1384
+ class SymbolRepository {
1385
+ db;
1386
+ constructor(db) {
1387
+ this.db = db;
1388
+ }
1389
+ replaceFileSymbols(project, filePath, contentHash, syms) {
1390
+ this.db.drizzleDb.delete(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).run();
1391
+ if (!syms.length)
1392
+ return;
1393
+ const now = new Date().toISOString();
1394
+ for (const sym of syms) {
1395
+ this.db.drizzleDb.insert(symbols).values({
1396
+ project,
1397
+ filePath,
1398
+ kind: sym.kind ?? "unknown",
1399
+ name: sym.name ?? "",
1400
+ startLine: sym.startLine ?? 0,
1401
+ startColumn: sym.startColumn ?? 0,
1402
+ endLine: sym.endLine ?? 0,
1403
+ endColumn: sym.endColumn ?? 0,
1404
+ isExported: sym.isExported ?? 0,
1405
+ signature: sym.signature ?? null,
1406
+ fingerprint: sym.fingerprint ?? null,
1407
+ detailJson: sym.detailJson ?? null,
1408
+ contentHash,
1409
+ indexedAt: sym.indexedAt ?? now
1410
+ }).run();
1411
+ }
1412
+ }
1413
+ getFileSymbols(project, filePath) {
1414
+ return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).all();
1415
+ }
1416
+ searchByName(project, query, opts = {}) {
1417
+ const limit = opts.limit ?? 50;
1418
+ const ftsQuery = toFtsPrefixQuery(query);
1419
+ if (!ftsQuery)
1420
+ return [];
1421
+ let builder = this.db.drizzleDb.select().from(symbols).where(and2(sql2`${symbols.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${ftsQuery})`, eq2(symbols.project, project), opts.kind ? eq2(symbols.kind, opts.kind) : undefined)).orderBy(symbols.name).limit(limit);
1422
+ return builder.all();
1423
+ }
1424
+ searchByKind(project, kind) {
1425
+ return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.kind, kind))).orderBy(symbols.name).all();
1426
+ }
1427
+ getStats(project) {
1428
+ const row = this.db.drizzleDb.select({
1429
+ symbolCount: count(),
1430
+ fileCount: sql2`COUNT(DISTINCT ${symbols.filePath})`
1431
+ }).from(symbols).where(eq2(symbols.project, project)).get();
1432
+ return {
1433
+ symbolCount: row?.symbolCount ?? 0,
1434
+ fileCount: row?.fileCount ?? 0
1435
+ };
1436
+ }
1437
+ getByFingerprint(project, fingerprint) {
1438
+ return this.db.drizzleDb.select().from(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.fingerprint, fingerprint))).all();
1439
+ }
1440
+ deleteFileSymbols(project, filePath) {
1441
+ this.db.drizzleDb.delete(symbols).where(and2(eq2(symbols.project, project), eq2(symbols.filePath, filePath))).run();
1442
+ }
1443
+ searchByQuery(opts) {
1444
+ return this.db.drizzleDb.select().from(symbols).where(and2(opts.ftsQuery ? sql2`${symbols.id} IN (SELECT rowid FROM symbols_fts WHERE symbols_fts MATCH ${opts.ftsQuery})` : undefined, opts.project !== undefined ? eq2(symbols.project, opts.project) : undefined, opts.kind ? eq2(symbols.kind, opts.kind) : undefined, opts.filePath !== undefined ? eq2(symbols.filePath, opts.filePath) : undefined, opts.isExported !== undefined ? eq2(symbols.isExported, opts.isExported ? 1 : 0) : undefined)).orderBy(symbols.name).limit(opts.limit).all();
1445
+ }
1446
+ }
1447
+
1448
+ // src/store/repositories/relation.repository.ts
1449
+ import { eq as eq3, and as and3, isNull, or } from "drizzle-orm";
1450
+ class RelationRepository {
1451
+ db;
1452
+ constructor(db) {
1453
+ this.db = db;
1454
+ }
1455
+ replaceFileRelations(project, srcFilePath, rels) {
1456
+ this.db.drizzleDb.delete(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).run();
1457
+ if (!rels.length)
1458
+ return;
1459
+ for (const rel of rels) {
1460
+ this.db.drizzleDb.insert(relations).values({
1461
+ project,
1462
+ type: rel.type ?? "unknown",
1463
+ srcFilePath: rel.srcFilePath ?? srcFilePath,
1464
+ srcSymbolName: rel.srcSymbolName ?? null,
1465
+ dstFilePath: rel.dstFilePath ?? "",
1466
+ dstSymbolName: rel.dstSymbolName ?? null,
1467
+ metaJson: rel.metaJson ?? null
1468
+ }).run();
1469
+ }
1470
+ }
1471
+ getOutgoing(project, srcFilePath, srcSymbolName) {
1472
+ if (srcSymbolName !== undefined) {
1473
+ return this.db.drizzleDb.select({
1474
+ project: relations.project,
1475
+ type: relations.type,
1476
+ srcFilePath: relations.srcFilePath,
1477
+ srcSymbolName: relations.srcSymbolName,
1478
+ dstFilePath: relations.dstFilePath,
1479
+ dstSymbolName: relations.dstSymbolName,
1480
+ metaJson: relations.metaJson
1481
+ }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath), or(eq3(relations.srcSymbolName, srcSymbolName), isNull(relations.srcSymbolName)))).all();
1482
+ }
1483
+ return this.db.drizzleDb.select({
1484
+ project: relations.project,
1485
+ type: relations.type,
1486
+ srcFilePath: relations.srcFilePath,
1487
+ srcSymbolName: relations.srcSymbolName,
1488
+ dstFilePath: relations.dstFilePath,
1489
+ dstSymbolName: relations.dstSymbolName,
1490
+ metaJson: relations.metaJson
1491
+ }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).all();
1492
+ }
1493
+ getIncoming(project, dstFilePath) {
1494
+ return this.db.drizzleDb.select({
1495
+ project: relations.project,
1496
+ type: relations.type,
1497
+ srcFilePath: relations.srcFilePath,
1498
+ srcSymbolName: relations.srcSymbolName,
1499
+ dstFilePath: relations.dstFilePath,
1500
+ dstSymbolName: relations.dstSymbolName,
1501
+ metaJson: relations.metaJson
1502
+ }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.dstFilePath, dstFilePath))).all();
1503
+ }
1504
+ getByType(project, type) {
1505
+ return this.db.drizzleDb.select({
1506
+ project: relations.project,
1507
+ type: relations.type,
1508
+ srcFilePath: relations.srcFilePath,
1509
+ srcSymbolName: relations.srcSymbolName,
1510
+ dstFilePath: relations.dstFilePath,
1511
+ dstSymbolName: relations.dstSymbolName,
1512
+ metaJson: relations.metaJson
1513
+ }).from(relations).where(and3(eq3(relations.project, project), eq3(relations.type, type))).all();
1514
+ }
1515
+ deleteFileRelations(project, srcFilePath) {
1516
+ this.db.drizzleDb.delete(relations).where(and3(eq3(relations.project, project), eq3(relations.srcFilePath, srcFilePath))).run();
1517
+ }
1518
+ searchRelations(opts) {
1519
+ return this.db.drizzleDb.select({
1520
+ project: relations.project,
1521
+ type: relations.type,
1522
+ srcFilePath: relations.srcFilePath,
1523
+ srcSymbolName: relations.srcSymbolName,
1524
+ dstFilePath: relations.dstFilePath,
1525
+ dstSymbolName: relations.dstSymbolName,
1526
+ metaJson: relations.metaJson
1527
+ }).from(relations).where(and3(opts.project !== undefined ? eq3(relations.project, opts.project) : undefined, opts.srcFilePath !== undefined ? eq3(relations.srcFilePath, opts.srcFilePath) : undefined, opts.srcSymbolName !== undefined ? eq3(relations.srcSymbolName, opts.srcSymbolName) : undefined, opts.dstFilePath !== undefined ? eq3(relations.dstFilePath, opts.dstFilePath) : undefined, opts.dstSymbolName !== undefined ? eq3(relations.dstSymbolName, opts.dstSymbolName) : undefined, opts.type !== undefined ? eq3(relations.type, opts.type) : undefined)).limit(opts.limit).all();
1528
+ }
1529
+ retargetRelations(project, oldFile, oldSymbol, newFile, newSymbol) {
1530
+ const condition = oldSymbol === null ? and3(eq3(relations.project, project), eq3(relations.dstFilePath, oldFile), isNull(relations.dstSymbolName)) : and3(eq3(relations.project, project), eq3(relations.dstFilePath, oldFile), eq3(relations.dstSymbolName, oldSymbol));
1531
+ this.db.drizzleDb.update(relations).set({ dstFilePath: newFile, dstSymbolName: newSymbol }).where(condition).run();
1532
+ }
1533
+ }
1534
+
1535
+ // src/watcher/project-watcher.ts
1536
+ import { subscribe as parcelSubscribe } from "@parcel/watcher";
1537
+ import path from "path";
1538
+ var WATCHER_IGNORE_GLOBS = [
1539
+ "**/.git/**",
1540
+ "**/.zipbul/**",
1541
+ "**/dist/**",
1542
+ "**/node_modules/**"
1543
+ ];
1544
+ var CONFIG_FILE_NAMES = new Set(["package.json", "tsconfig.json"]);
1545
+ function normalizePath(value) {
1546
+ return value.replaceAll("\\", "/");
1547
+ }
1548
+ function mapEventType(type) {
1549
+ if (type === "update") {
1550
+ return "change";
1551
+ }
1552
+ if (type === "create") {
1553
+ return "create";
1554
+ }
1555
+ return "delete";
1556
+ }
1557
+
1558
+ class ProjectWatcher {
1559
+ #subscription;
1560
+ #rootPath;
1561
+ #ignoreGlobs;
1562
+ #extensions;
1563
+ #subscribe;
1564
+ #logger;
1565
+ constructor(options, subscribeFn = parcelSubscribe, logger = console) {
1566
+ this.#rootPath = options.projectRoot;
1567
+ this.#ignoreGlobs = [...WATCHER_IGNORE_GLOBS, ...options.ignorePatterns ?? []];
1568
+ this.#extensions = new Set((options.extensions ?? [".ts", ".mts", ".cts"]).map((ext) => ext.toLowerCase()));
1569
+ this.#subscribe = subscribeFn;
1570
+ this.#logger = logger;
1571
+ }
1572
+ async start(onChange) {
1573
+ try {
1574
+ this.#subscription = await this.#subscribe(this.#rootPath, (error, events) => {
1575
+ if (error) {
1576
+ this.#logger.error(new WatcherError("Callback error", { cause: error }));
1577
+ return;
1578
+ }
1579
+ try {
1580
+ for (const rawEvent of events) {
1581
+ const relativePath = normalizePath(path.relative(this.#rootPath, rawEvent.path));
1582
+ if (relativePath.startsWith("..")) {
1583
+ continue;
1584
+ }
1585
+ const baseName = path.basename(relativePath);
1586
+ const extension = path.extname(relativePath).toLowerCase();
1587
+ const isConfigFile = CONFIG_FILE_NAMES.has(baseName);
1588
+ if (!isConfigFile && !this.#extensions.has(extension)) {
1589
+ continue;
1590
+ }
1591
+ if (relativePath.endsWith(".d.ts")) {
1592
+ continue;
1593
+ }
1594
+ onChange({
1595
+ eventType: mapEventType(rawEvent.type),
1596
+ filePath: relativePath
1597
+ });
1598
+ }
1599
+ } catch (callbackError) {
1600
+ this.#logger.error(new WatcherError("Callback error", { cause: callbackError }));
1601
+ }
1602
+ }, {
1603
+ ignore: this.#ignoreGlobs
1604
+ });
1605
+ } catch (error) {
1606
+ throw new WatcherError("Failed to subscribe watcher", { cause: error });
1607
+ }
1608
+ }
1609
+ async close() {
1610
+ if (!this.#subscription) {
1611
+ return;
1612
+ }
1613
+ try {
1614
+ await this.#subscription.unsubscribe();
1615
+ this.#subscription = undefined;
1616
+ } catch (error) {
1617
+ throw new WatcherError("Failed to close watcher", { cause: error });
1618
+ }
1619
+ }
1620
+ }
1621
+
1622
+ // src/common/project-discovery.ts
1623
+ import path2 from "path";
1624
+ import { promises as fs } from "fs";
1625
+ var DISCOVERY_EXCLUDE = ["**/node_modules/**", "**/.git/**", "**/.zipbul/**", "**/dist/**"];
1626
+ async function discoverProjects(projectRoot) {
1627
+ const boundaries = [];
1628
+ for await (const relativePackageJson of fs.glob("**/package.json", {
1629
+ cwd: projectRoot,
1630
+ exclude: DISCOVERY_EXCLUDE
1631
+ })) {
1632
+ const packageDir = path2.dirname(relativePackageJson).replaceAll("\\", "/");
1633
+ const packagePath = path2.join(projectRoot, relativePackageJson);
1634
+ const content = await Bun.file(packagePath).json();
1635
+ const packageName = typeof content?.name === "string" && content.name.length > 0 ? content.name : path2.basename(packageDir === "." ? projectRoot : packageDir);
1636
+ boundaries.push({
1637
+ dir: packageDir,
1638
+ project: packageName
1639
+ });
1640
+ }
1641
+ boundaries.sort((left, right) => right.dir.length - left.dir.length);
1642
+ return boundaries;
1643
+ }
1644
+ function resolveFileProject(filePath, boundaries, rootProject = "default") {
1645
+ const normalizedFilePath = filePath.replaceAll("\\", "/");
1646
+ for (const boundary of boundaries) {
1647
+ if (boundary.dir === ".") {
1648
+ return boundary.project;
1649
+ }
1650
+ if (normalizedFilePath === boundary.dir || normalizedFilePath.startsWith(`${boundary.dir}/`)) {
1651
+ return boundary.project;
1652
+ }
1653
+ }
1654
+ return rootProject;
1655
+ }
1656
+
1657
+ // src/common/tsconfig-resolver.ts
1658
+ import path3 from "path";
1659
+ var cache = new Map;
1660
+ async function readConfig(configPath) {
1661
+ const file = Bun.file(configPath);
1662
+ if (!await file.exists()) {
1663
+ return null;
1664
+ }
1665
+ const parsed = await file.json();
1666
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
1667
+ }
1668
+ async function loadTsconfigPaths(projectRoot) {
1669
+ if (cache.has(projectRoot)) {
1670
+ return cache.get(projectRoot) ?? null;
1671
+ }
1672
+ const tsconfigPath = path3.join(projectRoot, "tsconfig.json");
1673
+ const config = await readConfig(tsconfigPath);
1674
+ if (!config) {
1675
+ cache.set(projectRoot, null);
1676
+ return null;
1677
+ }
1678
+ const compilerOptions = typeof config.compilerOptions === "object" && config.compilerOptions !== null ? config.compilerOptions : null;
1679
+ if (!compilerOptions) {
1680
+ cache.set(projectRoot, null);
1681
+ return null;
1682
+ }
1683
+ const rawBaseUrl = typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : null;
1684
+ const rawPaths = typeof compilerOptions.paths === "object" && compilerOptions.paths !== null ? compilerOptions.paths : null;
1685
+ if (!rawBaseUrl && !rawPaths) {
1686
+ cache.set(projectRoot, null);
1687
+ return null;
1688
+ }
1689
+ const resolvedBaseUrl = rawBaseUrl ? path3.resolve(projectRoot, rawBaseUrl) : projectRoot;
1690
+ const paths = new Map;
1691
+ if (rawPaths) {
1692
+ for (const [pattern, targets] of Object.entries(rawPaths)) {
1693
+ if (!Array.isArray(targets)) {
1694
+ continue;
1695
+ }
1696
+ const normalizedTargets = targets.filter((value) => typeof value === "string");
1697
+ paths.set(pattern, normalizedTargets);
1698
+ }
1699
+ }
1700
+ const result = {
1701
+ baseUrl: resolvedBaseUrl,
1702
+ paths
1703
+ };
1704
+ cache.set(projectRoot, result);
1705
+ return result;
1706
+ }
1707
+ function clearTsconfigPathsCache(projectRoot) {
1708
+ if (projectRoot) {
1709
+ cache.delete(projectRoot);
1710
+ return;
1711
+ }
1712
+ cache.clear();
1713
+ }
1714
+
1715
+ // src/common/path-utils.ts
1716
+ import path4 from "path";
1717
+ function toRelativePath(projectRoot, absolutePath) {
1718
+ return path4.relative(projectRoot, absolutePath).replaceAll("\\", "/");
1719
+ }
1720
+ function toAbsolutePath(projectRoot, relativePath) {
1721
+ return path4.resolve(projectRoot, relativePath);
1722
+ }
1723
+
1724
+ // src/common/hasher.ts
1725
+ function hashString(input) {
1726
+ const raw = Bun.hash.xxHash64(input);
1727
+ const unsigned = BigInt.asUintN(64, BigInt(raw));
1728
+ return unsigned.toString(16).padStart(16, "0");
1729
+ }
1730
+
1731
+ // src/indexer/file-indexer.ts
1732
+ import { promises as fsPromises } from "fs";
1733
+ import { join as join2 } from "path";
1734
+ async function detectChanges(opts) {
1735
+ const { projectRoot, extensions, ignorePatterns, fileRepo } = opts;
1736
+ const existingMap = fileRepo.getFilesMap();
1737
+ const seenPaths = new Set;
1738
+ const changed = [];
1739
+ const unchanged = [];
1740
+ const ignoreGlobs = ignorePatterns.map((p) => new Bun.Glob(p));
1741
+ for await (const relativePath of fsPromises.glob("**/*", { cwd: projectRoot })) {
1742
+ if (!extensions.some((ext) => relativePath.endsWith(ext)))
1743
+ continue;
1744
+ if (ignoreGlobs.some((g) => g.match(relativePath)))
1745
+ continue;
1746
+ seenPaths.add(relativePath);
1747
+ const absPath = join2(projectRoot, relativePath);
1748
+ const bunFile = Bun.file(absPath);
1749
+ const { size, lastModified: mtimeMs } = bunFile;
1750
+ const existing = existingMap.get(relativePath);
1751
+ if (!existing) {
1752
+ const text3 = await bunFile.text();
1753
+ const contentHash2 = hashString(text3);
1754
+ changed.push({ filePath: relativePath, contentHash: contentHash2, mtimeMs, size });
1755
+ continue;
1756
+ }
1757
+ if (existing.mtimeMs === mtimeMs && existing.size === size) {
1758
+ unchanged.push({ filePath: relativePath, contentHash: existing.contentHash, mtimeMs, size });
1759
+ continue;
1760
+ }
1761
+ const text2 = await bunFile.text();
1762
+ const contentHash = hashString(text2);
1763
+ if (contentHash === existing.contentHash) {
1764
+ unchanged.push({ filePath: relativePath, contentHash, mtimeMs, size });
1765
+ } else {
1766
+ changed.push({ filePath: relativePath, contentHash, mtimeMs, size });
1767
+ }
1768
+ }
1769
+ const deleted = [];
1770
+ for (const filePath of existingMap.keys()) {
1771
+ if (!seenPaths.has(filePath)) {
1772
+ deleted.push(filePath);
1773
+ }
1774
+ }
1775
+ return { changed, unchanged, deleted };
1776
+ }
1777
+
1778
+ // src/indexer/symbol-indexer.ts
1779
+ function buildSignature(sym) {
1780
+ if (sym.kind === "function" || sym.kind === "method") {
1781
+ const paramCount = sym.parameters?.length ?? 0;
1782
+ const isAsync = sym.modifiers.includes("async") ? 1 : 0;
1783
+ return `params:${paramCount}|async:${isAsync}`;
1784
+ }
1785
+ return null;
1786
+ }
1787
+ function buildDetailJson(sym) {
1788
+ const detail = {};
1789
+ if (sym.jsDoc)
1790
+ detail.jsDoc = sym.jsDoc;
1791
+ if (sym.kind === "function" || sym.kind === "method") {
1792
+ if (sym.parameters !== undefined)
1793
+ detail.parameters = sym.parameters;
1794
+ if (sym.returnType !== undefined)
1795
+ detail.returnType = sym.returnType;
1796
+ }
1797
+ if (sym.heritage?.length)
1798
+ detail.heritage = sym.heritage;
1799
+ if (sym.decorators?.length)
1800
+ detail.decorators = sym.decorators;
1801
+ if (sym.typeParameters?.length)
1802
+ detail.typeParameters = sym.typeParameters;
1803
+ if (sym.modifiers?.length)
1804
+ detail.modifiers = sym.modifiers;
1805
+ if (sym.members?.length)
1806
+ detail.members = sym.members.map((m) => m.name);
1807
+ return Object.keys(detail).length > 0 ? JSON.stringify(detail) : null;
1808
+ }
1809
+ function buildRow(sym, name, project, filePath, contentHash) {
1810
+ const signature = buildSignature(sym);
1811
+ const fingerprint = hashString(`${name}|${sym.kind}|${signature ?? ""}`);
1812
+ return {
1813
+ project,
1814
+ filePath,
1815
+ kind: sym.kind,
1816
+ name,
1817
+ startLine: sym.span.start.line,
1818
+ startColumn: sym.span.start.column,
1819
+ endLine: sym.span.end.line,
1820
+ endColumn: sym.span.end.column,
1821
+ isExported: sym.isExported ? 1 : 0,
1822
+ signature,
1823
+ fingerprint,
1824
+ detailJson: buildDetailJson(sym),
1825
+ contentHash,
1826
+ indexedAt: new Date().toISOString()
1827
+ };
1828
+ }
1829
+ function indexFileSymbols(opts) {
1830
+ const { parsed, project, filePath, contentHash, symbolRepo } = opts;
1831
+ const extracted = extractSymbols(parsed);
1832
+ const rows = [];
1833
+ for (const sym of extracted) {
1834
+ rows.push(buildRow(sym, sym.name, project, filePath, contentHash));
1835
+ for (const member of sym.members ?? []) {
1836
+ rows.push(buildRow(member, `${sym.name}.${member.name}`, project, filePath, contentHash));
1837
+ }
1838
+ }
1839
+ symbolRepo.replaceFileSymbols(project, filePath, contentHash, rows);
1840
+ }
1841
+
1842
+ // src/indexer/relation-indexer.ts
1843
+ function indexFileRelations(opts) {
1844
+ const { ast, project, filePath, relationRepo, projectRoot, tsconfigPaths } = opts;
1845
+ const absFilePath = toAbsolutePath(projectRoot, filePath);
1846
+ const rawRelations = extractRelations(ast, absFilePath, tsconfigPaths);
1847
+ const rows = [];
1848
+ for (const rel of rawRelations) {
1849
+ const relDst = toRelativePath(projectRoot, rel.dstFilePath);
1850
+ if (relDst.startsWith(".."))
1851
+ continue;
1852
+ const relSrc = toRelativePath(projectRoot, rel.srcFilePath);
1853
+ rows.push({
1854
+ project,
1855
+ type: rel.type,
1856
+ srcFilePath: relSrc,
1857
+ srcSymbolName: rel.srcSymbolName ?? null,
1858
+ dstFilePath: relDst,
1859
+ dstSymbolName: rel.dstSymbolName ?? null,
1860
+ metaJson: rel.metaJson ?? null
1861
+ });
1862
+ }
1863
+ relationRepo.replaceFileRelations(project, filePath, rows);
1864
+ return rows.length;
1865
+ }
1866
+
1867
+ // src/indexer/index-coordinator.ts
1868
+ var WATCHER_DEBOUNCE_MS = 100;
1869
+
1870
+ class IndexCoordinator {
1871
+ opts;
1872
+ logger;
1873
+ callbacks = new Set;
1874
+ indexingLock = false;
1875
+ pendingEvents = [];
1876
+ debounceTimer = null;
1877
+ currentIndexing = null;
1878
+ pendingFullIndex = false;
1879
+ pendingFullIndexWaiters = [];
1880
+ tsconfigPathsRaw;
1881
+ boundariesRefresh = null;
1882
+ constructor(opts) {
1883
+ this.opts = opts;
1884
+ this.logger = opts.logger ?? console;
1885
+ this.tsconfigPathsRaw = loadTsconfigPaths(opts.projectRoot);
1886
+ }
1887
+ get tsconfigPaths() {
1888
+ return this.tsconfigPathsRaw;
1889
+ }
1890
+ fullIndex() {
1891
+ return this.startIndex(undefined, true);
1892
+ }
1893
+ incrementalIndex(events) {
1894
+ return this.startIndex(events, false);
1895
+ }
1896
+ onIndexed(cb) {
1897
+ this.callbacks.add(cb);
1898
+ return () => this.callbacks.delete(cb);
1899
+ }
1900
+ handleWatcherEvent(event) {
1901
+ if (event.filePath.endsWith("tsconfig.json")) {
1902
+ clearTsconfigPathsCache(this.opts.projectRoot);
1903
+ this.tsconfigPathsRaw = loadTsconfigPaths(this.opts.projectRoot);
1904
+ this.fullIndex().catch((err) => {
1905
+ this.logger.error("[IndexCoordinator] fullIndex failed after tsconfig change:", err);
1906
+ });
1907
+ return;
1908
+ }
1909
+ if (event.filePath.endsWith("package.json")) {
1910
+ const discover = this.opts.discoverProjectsFn ?? discoverProjects;
1911
+ this.boundariesRefresh = discover(this.opts.projectRoot).then((b) => {
1912
+ this.opts.boundaries = b;
1913
+ });
1914
+ }
1915
+ this.pendingEvents.push(event);
1916
+ if (this.debounceTimer === null) {
1917
+ this.debounceTimer = setTimeout(() => {
1918
+ this.debounceTimer = null;
1919
+ this.flushPending();
1920
+ }, WATCHER_DEBOUNCE_MS);
1921
+ }
1922
+ }
1923
+ async shutdown() {
1924
+ if (this.debounceTimer !== null) {
1925
+ clearTimeout(this.debounceTimer);
1926
+ this.debounceTimer = null;
1927
+ }
1928
+ if (this.currentIndexing) {
1929
+ await this.currentIndexing;
1930
+ }
1931
+ }
1932
+ startIndex(events, useTransaction) {
1933
+ if (this.indexingLock) {
1934
+ if (useTransaction) {
1935
+ this.pendingFullIndex = true;
1936
+ return new Promise((resolve2, reject) => {
1937
+ this.pendingFullIndexWaiters.push({ resolve: resolve2, reject });
1938
+ });
1939
+ }
1940
+ return this.currentIndexing;
1941
+ }
1942
+ this.indexingLock = true;
1943
+ const work = this.doIndex(events, useTransaction).then((result) => {
1944
+ this.fireCallbacks(result);
1945
+ return result;
1946
+ }).finally(() => {
1947
+ this.indexingLock = false;
1948
+ this.currentIndexing = null;
1949
+ if (this.pendingFullIndex) {
1950
+ this.pendingFullIndex = false;
1951
+ const waiters = this.pendingFullIndexWaiters.splice(0);
1952
+ this.startIndex(undefined, true).then((result) => {
1953
+ for (const waiter of waiters)
1954
+ waiter.resolve(result);
1955
+ }).catch((error) => {
1956
+ for (const waiter of waiters)
1957
+ waiter.reject(error);
1958
+ });
1959
+ } else if (this.pendingEvents.length > 0) {
1960
+ const drained = this.pendingEvents.splice(0);
1961
+ this.startIndex(drained, false).catch((err) => this.logger.error("[IndexCoordinator] incremental drain error", err));
1962
+ }
1963
+ });
1964
+ this.currentIndexing = work;
1965
+ return work;
1966
+ }
1967
+ async doIndex(events, useTransaction) {
1968
+ const start = Date.now();
1969
+ const { fileRepo, symbolRepo, relationRepo, dbConnection } = this.opts;
1970
+ if (this.boundariesRefresh) {
1971
+ await this.boundariesRefresh;
1972
+ this.boundariesRefresh = null;
1973
+ }
1974
+ let changed;
1975
+ let deleted;
1976
+ if (events !== undefined) {
1977
+ changed = events.filter((e) => e.eventType === "create" || e.eventType === "change").map((e) => ({
1978
+ filePath: e.filePath,
1979
+ contentHash: "",
1980
+ mtimeMs: 0,
1981
+ size: 0
1982
+ }));
1983
+ deleted = events.filter((e) => e.eventType === "delete").map((e) => e.filePath);
1984
+ } else {
1985
+ const existingMap = new Map;
1986
+ for (const boundary of this.opts.boundaries) {
1987
+ for (const [key, val] of fileRepo.getFilesMap(boundary.project)) {
1988
+ existingMap.set(key, val);
1989
+ }
1990
+ }
1991
+ const result = await detectChanges({
1992
+ projectRoot: this.opts.projectRoot,
1993
+ extensions: this.opts.extensions,
1994
+ ignorePatterns: this.opts.ignorePatterns,
1995
+ fileRepo: { getFilesMap: () => existingMap }
1996
+ });
1997
+ changed = result.changed;
1998
+ deleted = result.deleted;
1999
+ }
2000
+ const tsconfigPaths = await this.tsconfigPathsRaw ?? undefined;
2001
+ const deletedSymbols = new Map;
2002
+ for (const filePath of deleted) {
2003
+ const project = resolveFileProject(filePath, this.opts.boundaries);
2004
+ const syms = symbolRepo.getFileSymbols(project, filePath);
2005
+ deletedSymbols.set(filePath, syms);
2006
+ }
2007
+ const processDeleted = () => {
2008
+ for (const filePath of deleted) {
2009
+ const project = resolveFileProject(filePath, this.opts.boundaries);
2010
+ symbolRepo.deleteFileSymbols(project, filePath);
2011
+ relationRepo.deleteFileRelations(project, filePath);
2012
+ fileRepo.deleteFile(project, filePath);
2013
+ }
2014
+ };
2015
+ const processChanged = async () => {
2016
+ let symbols2 = 0;
2017
+ let relations2 = 0;
2018
+ const failedFiles = [];
2019
+ for (const file of changed) {
2020
+ try {
2021
+ const r = await this.processFile(file.filePath, file.contentHash || undefined, tsconfigPaths);
2022
+ symbols2 += r.symbolCount;
2023
+ relations2 += r.relCount;
2024
+ } catch (err) {
2025
+ this.logger.error(`[IndexCoordinator] Failed to index ${file.filePath}:`, err);
2026
+ failedFiles.push(file.filePath);
2027
+ }
2028
+ }
2029
+ return { symbols: symbols2, relations: relations2, failedFiles };
2030
+ };
2031
+ let totalSymbols = 0;
2032
+ let totalRelations = 0;
2033
+ let allFailedFiles = [];
2034
+ if (useTransaction) {
2035
+ const { projectRoot, boundaries } = this.opts;
2036
+ const { parseCache } = this.opts;
2037
+ const prereadResults = await Promise.allSettled(changed.map(async (file) => {
2038
+ const absPath = toAbsolutePath(projectRoot, file.filePath);
2039
+ const bunFile = Bun.file(absPath);
2040
+ const text2 = await bunFile.text();
2041
+ const contentHash = file.contentHash || hashString(text2);
2042
+ return { filePath: file.filePath, text: text2, contentHash, mtimeMs: bunFile.lastModified, size: bunFile.size };
2043
+ }));
2044
+ const preread = prereadResults.filter((r) => r.status === "fulfilled").map((r) => r.value);
2045
+ for (const r of prereadResults) {
2046
+ if (r.status === "rejected") {
2047
+ this.logger.error("[IndexCoordinator] Failed to pre-read file:", r.reason);
2048
+ }
2049
+ }
2050
+ const parsedCacheEntries = [];
2051
+ dbConnection.transaction(() => {
2052
+ for (const boundary of boundaries) {
2053
+ const projectFiles = fileRepo.getAllFiles(boundary.project);
2054
+ for (const f of projectFiles) {
2055
+ fileRepo.deleteFile(f.project, f.filePath);
2056
+ }
2057
+ }
2058
+ const parseFn = this.opts.parseSourceFn ?? parseSource;
2059
+ for (const fd of preread) {
2060
+ const project = resolveFileProject(fd.filePath, boundaries);
2061
+ const parsed = parseFn(toAbsolutePath(projectRoot, fd.filePath), fd.text);
2062
+ parsedCacheEntries.push({ filePath: fd.filePath, parsed });
2063
+ fileRepo.upsertFile({
2064
+ project,
2065
+ filePath: fd.filePath,
2066
+ mtimeMs: fd.mtimeMs,
2067
+ size: fd.size,
2068
+ contentHash: fd.contentHash,
2069
+ updatedAt: new Date().toISOString()
2070
+ });
2071
+ indexFileSymbols({ parsed, project, filePath: fd.filePath, contentHash: fd.contentHash, symbolRepo });
2072
+ totalRelations += indexFileRelations({
2073
+ ast: parsed.program,
2074
+ project,
2075
+ filePath: fd.filePath,
2076
+ relationRepo,
2077
+ projectRoot,
2078
+ tsconfigPaths
2079
+ });
2080
+ totalSymbols += symbolRepo.getFileSymbols(project, fd.filePath).length;
2081
+ }
2082
+ });
2083
+ for (const entry of parsedCacheEntries) {
2084
+ parseCache.set(entry.filePath, entry.parsed);
2085
+ }
2086
+ } else {
2087
+ processDeleted();
2088
+ const counts = await processChanged();
2089
+ totalSymbols = counts.symbols;
2090
+ totalRelations = counts.relations;
2091
+ allFailedFiles = counts.failedFiles;
2092
+ }
2093
+ if (!useTransaction) {
2094
+ for (const [oldFile, syms] of deletedSymbols) {
2095
+ for (const sym of syms) {
2096
+ if (!sym.fingerprint)
2097
+ continue;
2098
+ const oldProject = resolveFileProject(oldFile, this.opts.boundaries);
2099
+ const matches = symbolRepo.getByFingerprint(oldProject, sym.fingerprint);
2100
+ if (matches.length === 1) {
2101
+ const newSym = matches[0];
2102
+ relationRepo.retargetRelations(oldProject, oldFile, sym.name, newSym.filePath, newSym.name);
2103
+ }
2104
+ }
2105
+ }
2106
+ }
2107
+ return {
2108
+ indexedFiles: changed.length,
2109
+ removedFiles: deleted.length,
2110
+ totalSymbols,
2111
+ totalRelations,
2112
+ durationMs: Date.now() - start,
2113
+ changedFiles: changed.map((f) => f.filePath),
2114
+ deletedFiles: [...deleted],
2115
+ failedFiles: allFailedFiles
2116
+ };
2117
+ }
2118
+ async processFile(filePath, knownHash, tsconfigPaths) {
2119
+ const { projectRoot, boundaries } = this.opts;
2120
+ const { fileRepo, symbolRepo, relationRepo, parseCache } = this.opts;
2121
+ const absPath = toAbsolutePath(projectRoot, filePath);
2122
+ const bunFile = Bun.file(absPath);
2123
+ const text2 = await bunFile.text();
2124
+ const contentHash = knownHash || hashString(text2);
2125
+ const project = resolveFileProject(filePath, boundaries);
2126
+ const parseFn = this.opts.parseSourceFn ?? parseSource;
2127
+ const parsed = parseFn(absPath, text2);
2128
+ parseCache.set(filePath, parsed);
2129
+ fileRepo.upsertFile({
2130
+ project,
2131
+ filePath,
2132
+ mtimeMs: bunFile.lastModified,
2133
+ size: bunFile.size,
2134
+ contentHash,
2135
+ updatedAt: new Date().toISOString()
2136
+ });
2137
+ indexFileSymbols({ parsed, project, filePath, contentHash, symbolRepo });
2138
+ const relCount = indexFileRelations({
2139
+ ast: parsed.program,
2140
+ project,
2141
+ filePath,
2142
+ relationRepo,
2143
+ projectRoot,
2144
+ tsconfigPaths
2145
+ });
2146
+ const symbolCount = symbolRepo.getFileSymbols(project, filePath).length;
2147
+ return { symbolCount, relCount };
2148
+ }
2149
+ fireCallbacks(result) {
2150
+ for (const cb of this.callbacks) {
2151
+ try {
2152
+ cb(result);
2153
+ } catch (err) {
2154
+ this.logger.error("[IndexCoordinator] onIndexed callback threw:", err);
2155
+ }
2156
+ }
2157
+ }
2158
+ flushPending() {
2159
+ if (this.indexingLock) {
2160
+ return;
2161
+ }
2162
+ if (this.pendingEvents.length > 0) {
2163
+ const events = this.pendingEvents.splice(0);
2164
+ this.startIndex(events, false).catch((err) => this.logger.error("[IndexCoordinator] flushPending startIndex error:", err));
2165
+ }
2166
+ }
2167
+ }
2168
+
2169
+ // src/watcher/ownership.ts
2170
+ function defaultIsAlive(pid) {
2171
+ try {
2172
+ process.kill(pid, 0);
2173
+ return true;
2174
+ } catch (error) {
2175
+ if (typeof error === "object" && error && "code" in error) {
2176
+ return error.code !== "ESRCH";
2177
+ }
2178
+ return true;
2179
+ }
2180
+ }
2181
+ function toEpochMs(value) {
2182
+ const ms = new Date(value).getTime();
2183
+ return Number.isNaN(ms) ? 0 : ms;
2184
+ }
2185
+ function acquireWatcherRole(db, pid, options = {}) {
2186
+ const now = options.now ?? Date.now;
2187
+ const isAlive = options.isAlive ?? defaultIsAlive;
2188
+ const staleAfterSeconds = options.staleAfterSeconds ?? 90;
2189
+ return db.immediateTransaction(() => {
2190
+ const owner = db.selectOwner();
2191
+ if (!owner) {
2192
+ db.insertOwner(pid);
2193
+ return "owner";
2194
+ }
2195
+ const heartbeatAgeSeconds = Math.floor((now() - toEpochMs(owner.heartbeat_at)) / 1000);
2196
+ if (isAlive(owner.pid) && heartbeatAgeSeconds < staleAfterSeconds) {
2197
+ return "reader";
2198
+ }
2199
+ db.replaceOwner(pid);
2200
+ return "owner";
2201
+ });
2202
+ }
2203
+ function releaseWatcherRole(db, pid) {
2204
+ db.deleteOwner(pid);
2205
+ }
2206
+ function updateHeartbeat(db, pid) {
2207
+ db.touchOwner(pid);
2208
+ }
2209
+
2210
+ // src/search/symbol-search.ts
2211
+ function symbolSearch(options) {
2212
+ const { symbolRepo, project, query } = options;
2213
+ const effectiveProject = query.project ?? project;
2214
+ const limit = query.limit ?? 100;
2215
+ const opts = {
2216
+ kind: query.kind,
2217
+ filePath: query.filePath,
2218
+ isExported: query.isExported,
2219
+ project: effectiveProject,
2220
+ limit
2221
+ };
2222
+ if (query.text) {
2223
+ const ftsQuery = toFtsPrefixQuery(query.text);
2224
+ if (ftsQuery)
2225
+ opts.ftsQuery = ftsQuery;
2226
+ }
2227
+ const records = symbolRepo.searchByQuery(opts);
2228
+ return records.map((r) => ({
2229
+ id: r.id,
2230
+ filePath: r.filePath,
2231
+ kind: r.kind,
2232
+ name: r.name,
2233
+ span: {
2234
+ start: { line: r.startLine, column: r.startColumn },
2235
+ end: { line: r.endLine, column: r.endColumn }
2236
+ },
2237
+ isExported: r.isExported === 1,
2238
+ signature: r.signature,
2239
+ fingerprint: r.fingerprint,
2240
+ detail: r.detailJson ? (() => {
2241
+ try {
2242
+ return JSON.parse(r.detailJson);
2243
+ } catch {
2244
+ return {};
2245
+ }
2246
+ })() : {}
2247
+ }));
2248
+ }
2249
+
2250
+ // src/search/relation-search.ts
2251
+ function relationSearch(options) {
2252
+ const { relationRepo, project, query } = options;
2253
+ const effectiveProject = query.project ?? project;
2254
+ const limit = query.limit ?? 500;
2255
+ const records = relationRepo.searchRelations({
2256
+ srcFilePath: query.srcFilePath,
2257
+ srcSymbolName: query.srcSymbolName,
2258
+ dstFilePath: query.dstFilePath,
2259
+ dstSymbolName: query.dstSymbolName,
2260
+ type: query.type,
2261
+ project: effectiveProject,
2262
+ limit
2263
+ });
2264
+ return records.map((r) => ({
2265
+ type: r.type,
2266
+ srcFilePath: r.srcFilePath,
2267
+ srcSymbolName: r.srcSymbolName,
2268
+ dstFilePath: r.dstFilePath,
2269
+ dstSymbolName: r.dstSymbolName,
2270
+ metaJson: r.metaJson ?? undefined
2271
+ }));
2272
+ }
2273
+
2274
+ // src/search/dependency-graph.ts
2275
+ class DependencyGraph {
2276
+ options;
2277
+ adjacencyList = new Map;
2278
+ reverseAdjacencyList = new Map;
2279
+ constructor(options) {
2280
+ this.options = options;
2281
+ }
2282
+ build() {
2283
+ this.adjacencyList = new Map;
2284
+ this.reverseAdjacencyList = new Map;
2285
+ const relations2 = this.options.relationRepo.getByType(this.options.project, "imports");
2286
+ for (const rel of relations2) {
2287
+ const { srcFilePath, dstFilePath } = rel;
2288
+ if (!this.adjacencyList.has(srcFilePath)) {
2289
+ this.adjacencyList.set(srcFilePath, new Set);
2290
+ }
2291
+ this.adjacencyList.get(srcFilePath).add(dstFilePath);
2292
+ if (!this.reverseAdjacencyList.has(dstFilePath)) {
2293
+ this.reverseAdjacencyList.set(dstFilePath, new Set);
2294
+ }
2295
+ this.reverseAdjacencyList.get(dstFilePath).add(srcFilePath);
2296
+ }
2297
+ }
2298
+ getDependencies(filePath) {
2299
+ return Array.from(this.adjacencyList.get(filePath) ?? []);
2300
+ }
2301
+ getDependents(filePath) {
2302
+ return Array.from(this.reverseAdjacencyList.get(filePath) ?? []);
2303
+ }
2304
+ getTransitiveDependents(filePath) {
2305
+ const visited = new Set;
2306
+ const queue = [filePath];
2307
+ while (queue.length > 0) {
2308
+ const current = queue.shift();
2309
+ for (const dependent of this.reverseAdjacencyList.get(current) ?? []) {
2310
+ if (!visited.has(dependent)) {
2311
+ visited.add(dependent);
2312
+ queue.push(dependent);
2313
+ }
2314
+ }
2315
+ }
2316
+ return Array.from(visited);
2317
+ }
2318
+ hasCycle() {
2319
+ const visited = new Set;
2320
+ const inPath = new Set;
2321
+ for (const startNode of this.adjacencyList.keys()) {
2322
+ if (visited.has(startNode))
2323
+ continue;
2324
+ const stack = [{ node: startNode, entered: false }];
2325
+ while (stack.length > 0) {
2326
+ const current = stack.pop();
2327
+ if (current.entered) {
2328
+ inPath.delete(current.node);
2329
+ continue;
2330
+ }
2331
+ if (inPath.has(current.node)) {
2332
+ return true;
2333
+ }
2334
+ if (visited.has(current.node)) {
2335
+ continue;
2336
+ }
2337
+ visited.add(current.node);
2338
+ inPath.add(current.node);
2339
+ stack.push({ node: current.node, entered: true });
2340
+ for (const neighbor of this.adjacencyList.get(current.node) ?? []) {
2341
+ if (inPath.has(neighbor)) {
2342
+ return true;
2343
+ }
2344
+ if (!visited.has(neighbor)) {
2345
+ stack.push({ node: neighbor, entered: false });
2346
+ }
2347
+ }
2348
+ }
2349
+ }
2350
+ return false;
2351
+ }
2352
+ getAffectedByChange(changedFiles) {
2353
+ const allAffected = new Set;
2354
+ for (const file of changedFiles) {
2355
+ for (const dep of this.getTransitiveDependents(file)) {
2356
+ allAffected.add(dep);
2357
+ }
2358
+ }
2359
+ return Array.from(allAffected);
2360
+ }
2361
+ }
2362
+
2363
+ // src/gildash.ts
2364
+ var HEARTBEAT_INTERVAL_MS = 30000;
2365
+ var HEALTHCHECK_INTERVAL_MS = 60000;
2366
+ var MAX_HEALTHCHECK_RETRIES = 10;
2367
+
2368
+ class Gildash {
2369
+ projectRoot;
2370
+ db;
2371
+ symbolRepo;
2372
+ relationRepo;
2373
+ parseCache;
2374
+ coordinator;
2375
+ watcher;
2376
+ releaseWatcherRoleFn;
2377
+ parseSourceFn;
2378
+ extractSymbolsFn;
2379
+ extractRelationsFn;
2380
+ symbolSearchFn;
2381
+ relationSearchFn;
2382
+ logger;
2383
+ defaultProject;
2384
+ role;
2385
+ timer = null;
2386
+ signalHandlers = [];
2387
+ closed = false;
2388
+ tsconfigPaths = null;
2389
+ boundaries = [];
2390
+ onIndexedCallbacks = new Set;
2391
+ constructor(opts) {
2392
+ this.projectRoot = opts.projectRoot;
2393
+ this.db = opts.db;
2394
+ this.symbolRepo = opts.symbolRepo;
2395
+ this.relationRepo = opts.relationRepo;
2396
+ this.parseCache = opts.parseCache;
2397
+ this.coordinator = opts.coordinator;
2398
+ this.watcher = opts.watcher;
2399
+ this.releaseWatcherRoleFn = opts.releaseWatcherRoleFn;
2400
+ this.parseSourceFn = opts.parseSourceFn;
2401
+ this.extractSymbolsFn = opts.extractSymbolsFn;
2402
+ this.extractRelationsFn = opts.extractRelationsFn;
2403
+ this.symbolSearchFn = opts.symbolSearchFn;
2404
+ this.relationSearchFn = opts.relationSearchFn;
2405
+ this.logger = opts.logger;
2406
+ this.defaultProject = opts.defaultProject;
2407
+ this.role = opts.role;
2408
+ }
2409
+ static async open(options) {
2410
+ const {
2411
+ projectRoot,
2412
+ extensions = [".ts", ".mts", ".cts"],
2413
+ ignorePatterns = [],
2414
+ parseCacheCapacity = 500,
2415
+ logger = console,
2416
+ existsSyncFn = existsSync2,
2417
+ dbConnectionFactory,
2418
+ watcherFactory,
2419
+ coordinatorFactory,
2420
+ repositoryFactory,
2421
+ acquireWatcherRoleFn = acquireWatcherRole,
2422
+ releaseWatcherRoleFn = releaseWatcherRole,
2423
+ updateHeartbeatFn = updateHeartbeat,
2424
+ discoverProjectsFn = discoverProjects,
2425
+ parseSourceFn = parseSource,
2426
+ extractSymbolsFn = extractSymbols,
2427
+ extractRelationsFn = extractRelations,
2428
+ symbolSearchFn = symbolSearch,
2429
+ relationSearchFn = relationSearch,
2430
+ loadTsconfigPathsFn = loadTsconfigPaths
2431
+ } = options;
2432
+ if (!path5.isAbsolute(projectRoot)) {
2433
+ throw new Error(`Gildash: projectRoot must be an absolute path, got: "${projectRoot}"`);
2434
+ }
2435
+ if (!existsSyncFn(projectRoot)) {
2436
+ throw new Error(`Gildash: projectRoot does not exist: "${projectRoot}"`);
2437
+ }
2438
+ const db = dbConnectionFactory ? dbConnectionFactory() : new DbConnection({ projectRoot });
2439
+ db.open();
2440
+ try {
2441
+ const boundaries = await discoverProjectsFn(projectRoot);
2442
+ const defaultProject = boundaries[0]?.project ?? path5.basename(projectRoot);
2443
+ const repos = repositoryFactory ? repositoryFactory() : (() => {
2444
+ const connection = db;
2445
+ return {
2446
+ fileRepo: new FileRepository(connection),
2447
+ symbolRepo: new SymbolRepository(connection),
2448
+ relationRepo: new RelationRepository(connection),
2449
+ parseCache: new ParseCache(parseCacheCapacity)
2450
+ };
2451
+ })();
2452
+ const role = await Promise.resolve(acquireWatcherRoleFn(db, process.pid, {}));
2453
+ let coordinator = null;
2454
+ let watcher = null;
2455
+ const instance = new Gildash({
2456
+ projectRoot,
2457
+ db,
2458
+ symbolRepo: repos.symbolRepo,
2459
+ relationRepo: repos.relationRepo,
2460
+ parseCache: repos.parseCache,
2461
+ coordinator,
2462
+ watcher,
2463
+ releaseWatcherRoleFn,
2464
+ parseSourceFn,
2465
+ extractSymbolsFn,
2466
+ extractRelationsFn,
2467
+ symbolSearchFn,
2468
+ relationSearchFn,
2469
+ logger,
2470
+ defaultProject,
2471
+ role
2472
+ });
2473
+ clearTsconfigPathsCache(projectRoot);
2474
+ instance.tsconfigPaths = await loadTsconfigPathsFn(projectRoot);
2475
+ instance.boundaries = boundaries;
2476
+ if (role === "owner") {
2477
+ const w = watcherFactory ? watcherFactory() : new ProjectWatcher({ projectRoot, ignorePatterns, extensions }, undefined, logger);
2478
+ const c = coordinatorFactory ? coordinatorFactory() : new IndexCoordinator({
2479
+ projectRoot,
2480
+ boundaries,
2481
+ extensions,
2482
+ ignorePatterns,
2483
+ dbConnection: db,
2484
+ parseCache: repos.parseCache,
2485
+ fileRepo: repos.fileRepo,
2486
+ symbolRepo: repos.symbolRepo,
2487
+ relationRepo: repos.relationRepo,
2488
+ logger
2489
+ });
2490
+ instance.coordinator = c;
2491
+ instance.watcher = w;
2492
+ await w.start((event) => c.handleWatcherEvent?.(event));
2493
+ const timer = setInterval(() => {
2494
+ updateHeartbeatFn(db, process.pid);
2495
+ }, HEARTBEAT_INTERVAL_MS);
2496
+ instance.timer = timer;
2497
+ await c.fullIndex();
2498
+ } else {
2499
+ let retryCount = 0;
2500
+ const healthcheck = async () => {
2501
+ try {
2502
+ const newRole = await Promise.resolve(acquireWatcherRoleFn(db, process.pid, {}));
2503
+ retryCount = 0;
2504
+ if (newRole === "owner") {
2505
+ clearInterval(instance.timer);
2506
+ instance.timer = null;
2507
+ let promotedWatcher = null;
2508
+ let promotedCoordinator = null;
2509
+ try {
2510
+ promotedWatcher = watcherFactory ? watcherFactory() : new ProjectWatcher({ projectRoot, ignorePatterns, extensions }, undefined, logger);
2511
+ promotedCoordinator = coordinatorFactory ? coordinatorFactory() : new IndexCoordinator({
2512
+ projectRoot,
2513
+ boundaries,
2514
+ extensions,
2515
+ ignorePatterns,
2516
+ dbConnection: db,
2517
+ parseCache: repos.parseCache,
2518
+ fileRepo: repos.fileRepo,
2519
+ symbolRepo: repos.symbolRepo,
2520
+ relationRepo: repos.relationRepo,
2521
+ logger
2522
+ });
2523
+ for (const cb of instance.onIndexedCallbacks) {
2524
+ promotedCoordinator.onIndexed(cb);
2525
+ }
2526
+ await promotedWatcher.start((event) => promotedCoordinator?.handleWatcherEvent?.(event));
2527
+ const hbTimer = setInterval(() => {
2528
+ updateHeartbeatFn(db, process.pid);
2529
+ }, HEARTBEAT_INTERVAL_MS);
2530
+ instance.timer = hbTimer;
2531
+ instance.coordinator = promotedCoordinator;
2532
+ instance.watcher = promotedWatcher;
2533
+ await promotedCoordinator.fullIndex();
2534
+ } catch (setupErr) {
2535
+ logger.error("[Gildash] owner promotion failed, reverting to reader", setupErr);
2536
+ if (promotedWatcher) {
2537
+ await promotedWatcher.close().catch((e) => logger.error("[Gildash] watcher close error during promotion rollback", e));
2538
+ instance.watcher = null;
2539
+ }
2540
+ if (promotedCoordinator) {
2541
+ await promotedCoordinator.shutdown().catch((e) => logger.error("[Gildash] coordinator shutdown error during promotion rollback", e));
2542
+ instance.coordinator = null;
2543
+ }
2544
+ if (instance.timer === null) {
2545
+ instance.timer = setInterval(healthcheck, HEALTHCHECK_INTERVAL_MS);
2546
+ }
2547
+ }
2548
+ }
2549
+ } catch (err) {
2550
+ retryCount++;
2551
+ logger.error("[Gildash] healthcheck error", err);
2552
+ if (retryCount >= MAX_HEALTHCHECK_RETRIES) {
2553
+ logger.error("[Gildash] healthcheck failed too many times, shutting down");
2554
+ clearInterval(instance.timer);
2555
+ instance.timer = null;
2556
+ instance.close().catch((closeErr) => logger.error("[Gildash] close error during healthcheck shutdown", closeErr));
2557
+ }
2558
+ }
2559
+ };
2560
+ const timer = setInterval(healthcheck, HEALTHCHECK_INTERVAL_MS);
2561
+ instance.timer = timer;
2562
+ }
2563
+ const signals = ["SIGTERM", "SIGINT", "beforeExit"];
2564
+ for (const sig of signals) {
2565
+ const handler = () => {
2566
+ instance.close().catch((err) => logger.error("[Gildash] close error during signal", sig, err));
2567
+ };
2568
+ if (sig === "beforeExit") {
2569
+ process.on("beforeExit", handler);
2570
+ } else {
2571
+ process.on(sig, handler);
2572
+ }
2573
+ instance.signalHandlers.push([sig, handler]);
2574
+ }
2575
+ return instance;
2576
+ } catch (err) {
2577
+ db.close();
2578
+ throw err;
2579
+ }
2580
+ }
2581
+ async close() {
2582
+ if (this.closed)
2583
+ return;
2584
+ this.closed = true;
2585
+ const closeErrors = [];
2586
+ for (const [sig, handler] of this.signalHandlers) {
2587
+ if (sig === "beforeExit") {
2588
+ process.off("beforeExit", handler);
2589
+ } else {
2590
+ process.off(sig, handler);
2591
+ }
2592
+ }
2593
+ this.signalHandlers = [];
2594
+ if (this.coordinator) {
2595
+ try {
2596
+ await this.coordinator.shutdown();
2597
+ } catch (err) {
2598
+ closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2599
+ }
2600
+ }
2601
+ if (this.watcher) {
2602
+ try {
2603
+ await this.watcher.close();
2604
+ } catch (err) {
2605
+ closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2606
+ }
2607
+ }
2608
+ if (this.timer !== null) {
2609
+ clearInterval(this.timer);
2610
+ this.timer = null;
2611
+ }
2612
+ try {
2613
+ this.releaseWatcherRoleFn(this.db, process.pid);
2614
+ } catch (err) {
2615
+ closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2616
+ }
2617
+ try {
2618
+ this.db.close();
2619
+ } catch (err) {
2620
+ closeErrors.push(err instanceof Error ? err : new Error(String(err)));
2621
+ }
2622
+ if (closeErrors.length > 0) {
2623
+ throw new AggregateError(closeErrors, "Gildash: one or more errors occurred during close()");
2624
+ }
2625
+ }
2626
+ onIndexed(callback) {
2627
+ this.onIndexedCallbacks.add(callback);
2628
+ if (!this.coordinator) {
2629
+ return () => {
2630
+ this.onIndexedCallbacks.delete(callback);
2631
+ };
2632
+ }
2633
+ const unsubscribe = this.coordinator.onIndexed(callback);
2634
+ return () => {
2635
+ this.onIndexedCallbacks.delete(callback);
2636
+ unsubscribe();
2637
+ };
2638
+ }
2639
+ parseSource(filePath, sourceText) {
2640
+ if (this.closed)
2641
+ throw new Error("Gildash: instance is closed");
2642
+ const parsed = this.parseSourceFn(filePath, sourceText);
2643
+ this.parseCache.set(filePath, parsed);
2644
+ return parsed;
2645
+ }
2646
+ extractSymbols(parsed) {
2647
+ if (this.closed)
2648
+ throw new Error("Gildash: instance is closed");
2649
+ return this.extractSymbolsFn(parsed);
2650
+ }
2651
+ extractRelations(parsed) {
2652
+ if (this.closed)
2653
+ throw new Error("Gildash: instance is closed");
2654
+ return this.extractRelationsFn(parsed.program, parsed.filePath, this.tsconfigPaths ?? undefined);
2655
+ }
2656
+ async reindex() {
2657
+ if (this.closed)
2658
+ throw new Error("Gildash: instance is closed");
2659
+ if (!this.coordinator) {
2660
+ throw new Error("Gildash: reindex() is not available for readers");
2661
+ }
2662
+ return this.coordinator.fullIndex();
2663
+ }
2664
+ get projects() {
2665
+ return [...this.boundaries];
2666
+ }
2667
+ getStats(project) {
2668
+ if (this.closed)
2669
+ throw new Error("Gildash: instance is closed");
2670
+ return this.symbolRepo.getStats(project ?? this.defaultProject);
2671
+ }
2672
+ searchSymbols(query) {
2673
+ if (this.closed)
2674
+ throw new Error("Gildash: instance is closed");
2675
+ return this.symbolSearchFn({ symbolRepo: this.symbolRepo, project: this.defaultProject, query });
2676
+ }
2677
+ searchRelations(query) {
2678
+ if (this.closed)
2679
+ throw new Error("Gildash: instance is closed");
2680
+ return this.relationSearchFn({ relationRepo: this.relationRepo, project: this.defaultProject, query });
2681
+ }
2682
+ getDependencies(filePath, project, limit = 1e4) {
2683
+ if (this.closed)
2684
+ throw new Error("Gildash: instance is closed");
2685
+ return this.relationSearchFn({
2686
+ relationRepo: this.relationRepo,
2687
+ project: project ?? this.defaultProject,
2688
+ query: { srcFilePath: filePath, type: "imports", project: project ?? this.defaultProject, limit }
2689
+ }).map((r) => r.dstFilePath);
2690
+ }
2691
+ getDependents(filePath, project, limit = 1e4) {
2692
+ if (this.closed)
2693
+ throw new Error("Gildash: instance is closed");
2694
+ return this.relationSearchFn({
2695
+ relationRepo: this.relationRepo,
2696
+ project: project ?? this.defaultProject,
2697
+ query: { dstFilePath: filePath, type: "imports", project: project ?? this.defaultProject, limit }
2698
+ }).map((r) => r.srcFilePath);
2699
+ }
2700
+ async getAffected(changedFiles, project) {
2701
+ if (this.closed)
2702
+ throw new Error("Gildash: instance is closed");
2703
+ const g = new DependencyGraph({
2704
+ relationRepo: this.relationRepo,
2705
+ project: project ?? this.defaultProject
2706
+ });
2707
+ await g.build();
2708
+ return g.getAffectedByChange(changedFiles);
2709
+ }
2710
+ async hasCycle(project) {
2711
+ if (this.closed)
2712
+ throw new Error("Gildash: instance is closed");
2713
+ const g = new DependencyGraph({
2714
+ relationRepo: this.relationRepo,
2715
+ project: project ?? this.defaultProject
2716
+ });
2717
+ await g.build();
2718
+ return g.hasCycle();
2719
+ }
2720
+ }
2721
+ export {
2722
+ symbolSearch,
2723
+ relationSearch,
2724
+ WatcherError,
2725
+ StoreError,
2726
+ SearchError,
2727
+ ParseError,
2728
+ IndexError,
2729
+ GildashError,
2730
+ Gildash,
2731
+ ExtractError,
2732
+ DependencyGraph
2733
+ };
2734
+
2735
+ //# debugId=C2664325F1F8637F64756E2164756E21
2736
+ //# sourceMappingURL=index.js.map