@colbymchenry/codegraph 0.3.1 → 0.5.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 (126) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +681 -641
  3. package/dist/bin/codegraph.d.ts +7 -2
  4. package/dist/bin/codegraph.d.ts.map +1 -1
  5. package/dist/bin/codegraph.js +360 -140
  6. package/dist/bin/codegraph.js.map +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +43 -10
  9. package/dist/config.js.map +1 -1
  10. package/dist/context/index.d.ts +17 -4
  11. package/dist/context/index.d.ts.map +1 -1
  12. package/dist/context/index.js +182 -15
  13. package/dist/context/index.js.map +1 -1
  14. package/dist/db/index.d.ts.map +1 -1
  15. package/dist/db/index.js +21 -0
  16. package/dist/db/index.js.map +1 -1
  17. package/dist/db/migrations.d.ts +1 -1
  18. package/dist/db/migrations.d.ts.map +1 -1
  19. package/dist/db/migrations.js +19 -12
  20. package/dist/db/migrations.js.map +1 -1
  21. package/dist/db/queries.d.ts +32 -1
  22. package/dist/db/queries.d.ts.map +1 -1
  23. package/dist/db/queries.js +271 -118
  24. package/dist/db/queries.js.map +1 -1
  25. package/dist/db/schema.sql +163 -149
  26. package/dist/directory.d.ts +13 -1
  27. package/dist/directory.d.ts.map +1 -1
  28. package/dist/directory.js +85 -19
  29. package/dist/directory.js.map +1 -1
  30. package/dist/errors.d.ts +1 -1
  31. package/dist/errors.d.ts.map +1 -1
  32. package/dist/errors.js +7 -1
  33. package/dist/errors.js.map +1 -1
  34. package/dist/extraction/grammars.d.ts +9 -4
  35. package/dist/extraction/grammars.d.ts.map +1 -1
  36. package/dist/extraction/grammars.js +133 -65
  37. package/dist/extraction/grammars.js.map +1 -1
  38. package/dist/extraction/index.d.ts +6 -0
  39. package/dist/extraction/index.d.ts.map +1 -1
  40. package/dist/extraction/index.js +209 -44
  41. package/dist/extraction/index.js.map +1 -1
  42. package/dist/extraction/tree-sitter.d.ts +67 -0
  43. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  44. package/dist/extraction/tree-sitter.js +980 -38
  45. package/dist/extraction/tree-sitter.js.map +1 -1
  46. package/dist/graph/traversal.d.ts.map +1 -1
  47. package/dist/graph/traversal.js +6 -2
  48. package/dist/graph/traversal.js.map +1 -1
  49. package/dist/index.d.ts +6 -38
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +91 -73
  52. package/dist/index.js.map +1 -1
  53. package/dist/installer/banner.js +7 -7
  54. package/dist/installer/claude-md-template.js +32 -32
  55. package/dist/installer/config-writer.d.ts +9 -0
  56. package/dist/installer/config-writer.d.ts.map +1 -1
  57. package/dist/installer/config-writer.js +126 -17
  58. package/dist/installer/config-writer.js.map +1 -1
  59. package/dist/installer/index.d.ts.map +1 -1
  60. package/dist/installer/index.js +11 -9
  61. package/dist/installer/index.js.map +1 -1
  62. package/dist/mcp/index.d.ts +14 -3
  63. package/dist/mcp/index.d.ts.map +1 -1
  64. package/dist/mcp/index.js +109 -29
  65. package/dist/mcp/index.js.map +1 -1
  66. package/dist/mcp/tools.d.ts +66 -1
  67. package/dist/mcp/tools.d.ts.map +1 -1
  68. package/dist/mcp/tools.js +442 -49
  69. package/dist/mcp/tools.js.map +1 -1
  70. package/dist/mcp/transport.d.ts.map +1 -1
  71. package/dist/mcp/transport.js +2 -0
  72. package/dist/mcp/transport.js.map +1 -1
  73. package/dist/resolution/frameworks/index.d.ts +1 -0
  74. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  75. package/dist/resolution/frameworks/index.js +5 -1
  76. package/dist/resolution/frameworks/index.js.map +1 -1
  77. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  78. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  79. package/dist/resolution/frameworks/svelte.js +268 -0
  80. package/dist/resolution/frameworks/svelte.js.map +1 -0
  81. package/dist/resolution/import-resolver.d.ts.map +1 -1
  82. package/dist/resolution/import-resolver.js +3 -4
  83. package/dist/resolution/import-resolver.js.map +1 -1
  84. package/dist/resolution/index.d.ts +15 -2
  85. package/dist/resolution/index.d.ts.map +1 -1
  86. package/dist/resolution/index.js +153 -13
  87. package/dist/resolution/index.js.map +1 -1
  88. package/dist/resolution/name-matcher.d.ts.map +1 -1
  89. package/dist/resolution/name-matcher.js +7 -20
  90. package/dist/resolution/name-matcher.js.map +1 -1
  91. package/dist/resolution/types.d.ts +4 -0
  92. package/dist/resolution/types.d.ts.map +1 -1
  93. package/dist/search/query-utils.d.ts +25 -0
  94. package/dist/search/query-utils.d.ts.map +1 -0
  95. package/dist/search/query-utils.js +124 -0
  96. package/dist/search/query-utils.js.map +1 -0
  97. package/dist/sentry.d.ts +22 -0
  98. package/dist/sentry.d.ts.map +1 -0
  99. package/dist/sentry.js +159 -0
  100. package/dist/sentry.js.map +1 -0
  101. package/dist/sync/index.d.ts +4 -2
  102. package/dist/sync/index.d.ts.map +1 -1
  103. package/dist/sync/index.js +3 -5
  104. package/dist/sync/index.js.map +1 -1
  105. package/dist/types.d.ts +7 -1
  106. package/dist/types.d.ts.map +1 -1
  107. package/dist/types.js +13 -0
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils.d.ts +89 -2
  110. package/dist/utils.d.ts.map +1 -1
  111. package/dist/utils.js +252 -3
  112. package/dist/utils.js.map +1 -1
  113. package/dist/vectors/embedder.d.ts +1 -1
  114. package/dist/vectors/embedder.d.ts.map +1 -1
  115. package/dist/vectors/embedder.js +2 -2
  116. package/dist/vectors/embedder.js.map +1 -1
  117. package/dist/vectors/search.d.ts.map +1 -1
  118. package/dist/vectors/search.js +33 -32
  119. package/dist/vectors/search.js.map +1 -1
  120. package/package.json +74 -67
  121. package/scripts/patch-tree-sitter-dart.js +112 -0
  122. package/scripts/postinstall.js +71 -68
  123. package/dist/sync/git-hooks.d.ts +0 -66
  124. package/dist/sync/git-hooks.d.ts.map +0 -1
  125. package/dist/sync/git-hooks.js +0 -281
  126. package/dist/sync/git-hooks.js.map +0 -1
@@ -38,11 +38,13 @@ var __importStar = (this && this.__importStar) || (function () {
38
38
  };
39
39
  })();
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
41
+ exports.SvelteExtractor = exports.LiquidExtractor = exports.TreeSitterExtractor = void 0;
42
42
  exports.generateNodeId = generateNodeId;
43
43
  exports.extractFromSource = extractFromSource;
44
44
  const crypto = __importStar(require("crypto"));
45
+ const path = __importStar(require("path"));
45
46
  const grammars_1 = require("./grammars");
47
+ const sentry_1 = require("../sentry");
46
48
  /**
47
49
  * Generate a unique node ID
48
50
  *
@@ -110,8 +112,10 @@ const EXTRACTORS = {
110
112
  interfaceTypes: ['interface_declaration'],
111
113
  structTypes: [],
112
114
  enumTypes: ['enum_declaration'],
115
+ typeAliasTypes: ['type_alias_declaration'],
113
116
  importTypes: ['import_statement'],
114
117
  callTypes: ['call_expression'],
118
+ variableTypes: ['lexical_declaration', 'variable_declaration'],
115
119
  nameField: 'name',
116
120
  bodyField: 'body',
117
121
  paramsField: 'parameters',
@@ -142,13 +146,18 @@ const EXTRACTORS = {
142
146
  }
143
147
  return undefined;
144
148
  },
145
- isExported: (node, source) => {
146
- const parent = node.parent;
147
- if (parent?.type === 'export_statement')
148
- return true;
149
- // Check for 'export' keyword before declaration
150
- const text = source.substring(Math.max(0, node.startIndex - 10), node.startIndex);
151
- return text.includes('export');
149
+ isExported: (node, _source) => {
150
+ // Walk the parent chain to find an export_statement ancestor.
151
+ // This correctly handles deeply nested nodes like arrow functions
152
+ // inside variable declarations: `export const X = () => { ... }`
153
+ // where the arrow_function is 3 levels deep under export_statement.
154
+ let current = node.parent;
155
+ while (current) {
156
+ if (current.type === 'export_statement')
157
+ return true;
158
+ current = current.parent;
159
+ }
160
+ return false;
152
161
  },
153
162
  isAsync: (node) => {
154
163
  for (let i = 0; i < node.childCount; i++) {
@@ -166,6 +175,18 @@ const EXTRACTORS = {
166
175
  }
167
176
  return false;
168
177
  },
178
+ isConst: (node) => {
179
+ // For lexical_declaration, check if it's 'const' or 'let'
180
+ // For variable_declaration, it's always 'var'
181
+ if (node.type === 'lexical_declaration') {
182
+ for (let i = 0; i < node.childCount; i++) {
183
+ const child = node.child(i);
184
+ if (child?.type === 'const')
185
+ return true;
186
+ }
187
+ }
188
+ return false;
189
+ },
169
190
  },
170
191
  javascript: {
171
192
  functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
@@ -174,8 +195,10 @@ const EXTRACTORS = {
174
195
  interfaceTypes: [],
175
196
  structTypes: [],
176
197
  enumTypes: [],
198
+ typeAliasTypes: [],
177
199
  importTypes: ['import_statement'],
178
200
  callTypes: ['call_expression'],
201
+ variableTypes: ['lexical_declaration', 'variable_declaration'],
179
202
  nameField: 'name',
180
203
  bodyField: 'body',
181
204
  paramsField: 'parameters',
@@ -183,12 +206,14 @@ const EXTRACTORS = {
183
206
  const params = getChildByField(node, 'parameters');
184
207
  return params ? getNodeText(params, source) : undefined;
185
208
  },
186
- isExported: (node, source) => {
187
- const parent = node.parent;
188
- if (parent?.type === 'export_statement')
189
- return true;
190
- const text = source.substring(Math.max(0, node.startIndex - 10), node.startIndex);
191
- return text.includes('export');
209
+ isExported: (node, _source) => {
210
+ let current = node.parent;
211
+ while (current) {
212
+ if (current.type === 'export_statement')
213
+ return true;
214
+ current = current.parent;
215
+ }
216
+ return false;
192
217
  },
193
218
  isAsync: (node) => {
194
219
  for (let i = 0; i < node.childCount; i++) {
@@ -198,6 +223,16 @@ const EXTRACTORS = {
198
223
  }
199
224
  return false;
200
225
  },
226
+ isConst: (node) => {
227
+ if (node.type === 'lexical_declaration') {
228
+ for (let i = 0; i < node.childCount; i++) {
229
+ const child = node.child(i);
230
+ if (child?.type === 'const')
231
+ return true;
232
+ }
233
+ }
234
+ return false;
235
+ },
201
236
  },
202
237
  python: {
203
238
  functionTypes: ['function_definition'],
@@ -206,8 +241,10 @@ const EXTRACTORS = {
206
241
  interfaceTypes: [],
207
242
  structTypes: [],
208
243
  enumTypes: [],
244
+ typeAliasTypes: [],
209
245
  importTypes: ['import_statement', 'import_from_statement'],
210
246
  callTypes: ['call'],
247
+ variableTypes: ['assignment'], // Python uses assignment for variable declarations
211
248
  nameField: 'name',
212
249
  bodyField: 'body',
213
250
  paramsField: 'parameters',
@@ -244,8 +281,10 @@ const EXTRACTORS = {
244
281
  interfaceTypes: ['interface_type'],
245
282
  structTypes: ['struct_type'],
246
283
  enumTypes: [],
284
+ typeAliasTypes: ['type_spec'], // Go type declarations
247
285
  importTypes: ['import_declaration'],
248
286
  callTypes: ['call_expression'],
287
+ variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
249
288
  nameField: 'name',
250
289
  bodyField: 'body',
251
290
  paramsField: 'parameters',
@@ -269,8 +308,10 @@ const EXTRACTORS = {
269
308
  interfaceTypes: ['trait_item'],
270
309
  structTypes: ['struct_item'],
271
310
  enumTypes: ['enum_item'],
311
+ typeAliasTypes: ['type_item'], // Rust type aliases
272
312
  importTypes: ['use_declaration'],
273
313
  callTypes: ['call_expression'],
314
+ variableTypes: ['let_declaration', 'const_item', 'static_item'],
274
315
  nameField: 'name',
275
316
  bodyField: 'body',
276
317
  paramsField: 'parameters',
@@ -311,8 +352,10 @@ const EXTRACTORS = {
311
352
  interfaceTypes: ['interface_declaration'],
312
353
  structTypes: [],
313
354
  enumTypes: ['enum_declaration'],
355
+ typeAliasTypes: [],
314
356
  importTypes: ['import_declaration'],
315
357
  callTypes: ['method_invocation'],
358
+ variableTypes: ['local_variable_declaration', 'field_declaration'],
316
359
  nameField: 'name',
317
360
  bodyField: 'body',
318
361
  paramsField: 'parameters',
@@ -357,8 +400,10 @@ const EXTRACTORS = {
357
400
  interfaceTypes: [],
358
401
  structTypes: ['struct_specifier'],
359
402
  enumTypes: ['enum_specifier'],
403
+ typeAliasTypes: ['type_definition'], // typedef
360
404
  importTypes: ['preproc_include'],
361
405
  callTypes: ['call_expression'],
406
+ variableTypes: ['declaration'],
362
407
  nameField: 'declarator',
363
408
  bodyField: 'body',
364
409
  paramsField: 'parameters',
@@ -370,8 +415,10 @@ const EXTRACTORS = {
370
415
  interfaceTypes: [],
371
416
  structTypes: ['struct_specifier'],
372
417
  enumTypes: ['enum_specifier'],
418
+ typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
373
419
  importTypes: ['preproc_include'],
374
420
  callTypes: ['call_expression'],
421
+ variableTypes: ['declaration'],
375
422
  nameField: 'declarator',
376
423
  bodyField: 'body',
377
424
  paramsField: 'parameters',
@@ -402,8 +449,10 @@ const EXTRACTORS = {
402
449
  interfaceTypes: ['interface_declaration'],
403
450
  structTypes: ['struct_declaration'],
404
451
  enumTypes: ['enum_declaration'],
452
+ typeAliasTypes: [],
405
453
  importTypes: ['using_directive'],
406
454
  callTypes: ['invocation_expression'],
455
+ variableTypes: ['local_declaration_statement', 'field_declaration'],
407
456
  nameField: 'name',
408
457
  bodyField: 'body',
409
458
  paramsField: 'parameter_list',
@@ -450,8 +499,10 @@ const EXTRACTORS = {
450
499
  interfaceTypes: ['interface_declaration'],
451
500
  structTypes: [],
452
501
  enumTypes: ['enum_declaration'],
502
+ typeAliasTypes: [],
453
503
  importTypes: ['namespace_use_declaration'],
454
504
  callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
505
+ variableTypes: ['property_declaration', 'const_declaration'],
455
506
  nameField: 'name',
456
507
  bodyField: 'body',
457
508
  paramsField: 'parameters',
@@ -487,8 +538,10 @@ const EXTRACTORS = {
487
538
  interfaceTypes: [], // Ruby uses modules
488
539
  structTypes: [],
489
540
  enumTypes: [],
541
+ typeAliasTypes: [],
490
542
  importTypes: ['call'], // require/require_relative
491
543
  callTypes: ['call', 'method_call'],
544
+ variableTypes: ['assignment'], // Ruby uses assignment like Python
492
545
  nameField: 'name',
493
546
  bodyField: 'body',
494
547
  paramsField: 'parameters',
@@ -520,8 +573,10 @@ const EXTRACTORS = {
520
573
  interfaceTypes: ['protocol_declaration'],
521
574
  structTypes: ['struct_declaration'],
522
575
  enumTypes: ['enum_declaration'],
576
+ typeAliasTypes: ['typealias_declaration'],
523
577
  importTypes: ['import_declaration'],
524
578
  callTypes: ['call_expression'],
579
+ variableTypes: ['property_declaration', 'constant_declaration'],
525
580
  nameField: 'name',
526
581
  bodyField: 'body',
527
582
  paramsField: 'parameter',
@@ -584,8 +639,10 @@ const EXTRACTORS = {
584
639
  interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
585
640
  structTypes: [], // Kotlin uses data classes
586
641
  enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
642
+ typeAliasTypes: ['type_alias'],
587
643
  importTypes: ['import_header'],
588
644
  callTypes: ['call_expression'],
645
+ variableTypes: ['property_declaration'],
589
646
  nameField: 'simple_identifier',
590
647
  bodyField: 'function_body',
591
648
  paramsField: 'function_value_parameters',
@@ -636,6 +693,80 @@ const EXTRACTORS = {
636
693
  return false;
637
694
  },
638
695
  },
696
+ dart: {
697
+ functionTypes: ['function_signature'],
698
+ classTypes: ['class_definition'],
699
+ methodTypes: ['method_signature'],
700
+ interfaceTypes: [],
701
+ structTypes: [],
702
+ enumTypes: ['enum_declaration'],
703
+ typeAliasTypes: ['type_alias'],
704
+ importTypes: ['import_or_export'],
705
+ callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
706
+ variableTypes: [],
707
+ nameField: 'name',
708
+ bodyField: 'body', // class_definition uses 'body' field
709
+ paramsField: 'formal_parameter_list',
710
+ returnField: 'type',
711
+ getSignature: (node, source) => {
712
+ // For function_signature: extract params + return type
713
+ // For method_signature: delegate to inner function_signature
714
+ let sig = node;
715
+ if (node.type === 'method_signature') {
716
+ const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
717
+ if (inner)
718
+ sig = inner;
719
+ }
720
+ const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
721
+ const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
722
+ if (!params && !retType)
723
+ return undefined;
724
+ let result = '';
725
+ if (retType)
726
+ result += getNodeText(retType, source) + ' ';
727
+ if (params)
728
+ result += getNodeText(params, source);
729
+ return result.trim() || undefined;
730
+ },
731
+ getVisibility: (node) => {
732
+ // Dart convention: _ prefix means private, otherwise public
733
+ let nameNode = null;
734
+ if (node.type === 'method_signature') {
735
+ const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
736
+ if (inner)
737
+ nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
738
+ }
739
+ else {
740
+ nameNode = node.childForFieldName('name');
741
+ }
742
+ if (nameNode && nameNode.text.startsWith('_'))
743
+ return 'private';
744
+ return 'public';
745
+ },
746
+ isAsync: (node) => {
747
+ // In Dart, 'async' is on the function_body (next sibling), not the signature
748
+ const nextSibling = node.nextNamedSibling;
749
+ if (nextSibling?.type === 'function_body') {
750
+ for (let i = 0; i < nextSibling.childCount; i++) {
751
+ const child = nextSibling.child(i);
752
+ if (child?.type === 'async')
753
+ return true;
754
+ }
755
+ }
756
+ return false;
757
+ },
758
+ isStatic: (node) => {
759
+ // For method_signature, check for 'static' child
760
+ if (node.type === 'method_signature') {
761
+ for (let i = 0; i < node.childCount; i++) {
762
+ const child = node.child(i);
763
+ if (child?.type === 'static')
764
+ return true;
765
+ }
766
+ }
767
+ return false;
768
+ },
769
+ },
639
770
  };
640
771
  // TSX and JSX use the same extractors as their base languages
641
772
  EXTRACTORS.tsx = EXTRACTORS.typescript;
@@ -654,6 +785,25 @@ function extractName(node, source, extractor) {
654
785
  }
655
786
  return getNodeText(nameNode, source);
656
787
  }
788
+ // For Dart method_signature, look inside inner signature types
789
+ if (node.type === 'method_signature') {
790
+ for (let i = 0; i < node.namedChildCount; i++) {
791
+ const child = node.namedChild(i);
792
+ if (child && (child.type === 'function_signature' ||
793
+ child.type === 'getter_signature' ||
794
+ child.type === 'setter_signature' ||
795
+ child.type === 'constructor_signature' ||
796
+ child.type === 'factory_constructor_signature')) {
797
+ // Find identifier inside the inner signature
798
+ for (let j = 0; j < child.namedChildCount; j++) {
799
+ const inner = child.namedChild(j);
800
+ if (inner?.type === 'identifier') {
801
+ return getNodeText(inner, source);
802
+ }
803
+ }
804
+ }
805
+ }
806
+ }
657
807
  // Fall back to first identifier child
658
808
  for (let i = 0; i < node.namedChildCount; i++) {
659
809
  const child = node.namedChild(i);
@@ -723,9 +873,29 @@ class TreeSitterExtractor {
723
873
  }
724
874
  try {
725
875
  this.tree = parser.parse(this.source);
876
+ // Create file node representing the source file
877
+ const fileNode = {
878
+ id: `file:${this.filePath}`,
879
+ kind: 'file',
880
+ name: path.basename(this.filePath),
881
+ qualifiedName: this.filePath,
882
+ filePath: this.filePath,
883
+ language: this.language,
884
+ startLine: 1,
885
+ endLine: this.source.split('\n').length,
886
+ startColumn: 0,
887
+ endColumn: 0,
888
+ isExported: false,
889
+ updatedAt: Date.now(),
890
+ };
891
+ this.nodes.push(fileNode);
892
+ // Push file node onto stack so top-level declarations get contains edges
893
+ this.nodeStack.push(fileNode.id);
726
894
  this.visitNode(this.tree.rootNode);
895
+ this.nodeStack.pop();
727
896
  }
728
897
  catch (error) {
898
+ (0, sentry_1.captureException)(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
729
899
  this.errors.push({
730
900
  message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
731
901
  severity: 'error',
@@ -750,7 +920,7 @@ class TreeSitterExtractor {
750
920
  // Check for function declarations
751
921
  // For Python/Ruby, function_definition inside a class should be treated as method
752
922
  if (this.extractor.functionTypes.includes(nodeType)) {
753
- if (this.nodeStack.length > 0 && this.extractor.methodTypes.includes(nodeType)) {
923
+ if (this.isInsideClassLikeNode() && this.extractor.methodTypes.includes(nodeType)) {
754
924
  // Inside a class - treat as method
755
925
  this.extractMethod(node);
756
926
  skipChildren = true; // extractMethod visits children via visitFunctionBody
@@ -775,6 +945,11 @@ class TreeSitterExtractor {
775
945
  }
776
946
  skipChildren = true; // extractClass visits body children
777
947
  }
948
+ // Dart-specific: mixin and extension declarations treated as classes
949
+ else if (this.language === 'dart' && (nodeType === 'mixin_declaration' || nodeType === 'extension_declaration')) {
950
+ this.extractClass(node);
951
+ skipChildren = true;
952
+ }
778
953
  // Check for method declarations (only if not already handled by functionTypes)
779
954
  else if (this.extractor.methodTypes.includes(nodeType)) {
780
955
  this.extractMethod(node);
@@ -795,6 +970,22 @@ class TreeSitterExtractor {
795
970
  this.extractEnum(node);
796
971
  skipChildren = true; // extractEnum visits body children
797
972
  }
973
+ // Check for type alias declarations (e.g. `type X = ...` in TypeScript)
974
+ else if (this.extractor.typeAliasTypes.includes(nodeType)) {
975
+ this.extractTypeAlias(node);
976
+ }
977
+ // Check for variable declarations (const, let, var, etc.)
978
+ // Only extract top-level variables (not inside functions/methods)
979
+ else if (this.extractor.variableTypes.includes(nodeType) && !this.isInsideClassLikeNode()) {
980
+ this.extractVariable(node);
981
+ skipChildren = true; // extractVariable handles children
982
+ }
983
+ // Check for export statements containing non-function variable declarations
984
+ // e.g. `export const X = create(...)`, `export const X = { ... }`
985
+ else if (nodeType === 'export_statement') {
986
+ this.extractExportedVariables(node);
987
+ // Don't skip children — still need to visit inner nodes (functions, calls, etc.)
988
+ }
798
989
  // Check for imports
799
990
  else if (this.extractor.importTypes.includes(nodeType)) {
800
991
  this.extractImport(node);
@@ -873,13 +1064,46 @@ class TreeSitterExtractor {
873
1064
  }
874
1065
  return false;
875
1066
  }
1067
+ /**
1068
+ * Check if the current node stack indicates we are inside a class-like node
1069
+ * (class, struct, interface, trait). File nodes do not count as class-like.
1070
+ */
1071
+ isInsideClassLikeNode() {
1072
+ if (this.nodeStack.length === 0)
1073
+ return false;
1074
+ const parentId = this.nodeStack[this.nodeStack.length - 1];
1075
+ if (!parentId)
1076
+ return false;
1077
+ const parentNode = this.nodes.find((n) => n.id === parentId);
1078
+ if (!parentNode)
1079
+ return false;
1080
+ return (parentNode.kind === 'class' ||
1081
+ parentNode.kind === 'struct' ||
1082
+ parentNode.kind === 'interface' ||
1083
+ parentNode.kind === 'trait' ||
1084
+ parentNode.kind === 'enum');
1085
+ }
876
1086
  /**
877
1087
  * Extract a function
878
1088
  */
879
1089
  extractFunction(node) {
880
1090
  if (!this.extractor)
881
1091
  return;
882
- const name = extractName(node, this.source, this.extractor);
1092
+ let name = extractName(node, this.source, this.extractor);
1093
+ // For arrow functions and function expressions assigned to variables,
1094
+ // resolve the name from the parent variable_declarator.
1095
+ // e.g. `export const useAuth = () => { ... }` — the arrow_function node
1096
+ // has no `name` field; the name lives on the variable_declarator.
1097
+ if (name === '<anonymous>' &&
1098
+ (node.type === 'arrow_function' || node.type === 'function_expression')) {
1099
+ const parent = node.parent;
1100
+ if (parent?.type === 'variable_declarator') {
1101
+ const varName = getChildByField(parent, 'name');
1102
+ if (varName) {
1103
+ name = getNodeText(varName, this.source);
1104
+ }
1105
+ }
1106
+ }
883
1107
  if (name === '<anonymous>')
884
1108
  return; // Skip anonymous functions
885
1109
  const docstring = getPrecedingDocstring(node, this.source);
@@ -898,7 +1122,10 @@ class TreeSitterExtractor {
898
1122
  });
899
1123
  // Push to stack and visit body
900
1124
  this.nodeStack.push(funcNode.id);
901
- const body = getChildByField(node, this.extractor.bodyField);
1125
+ // Dart: function_body is a next sibling of function_signature, not a child
1126
+ const body = this.language === 'dart'
1127
+ ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1128
+ : getChildByField(node, this.extractor.bodyField);
902
1129
  if (body) {
903
1130
  this.visitFunctionBody(body, funcNode.id);
904
1131
  }
@@ -923,7 +1150,13 @@ class TreeSitterExtractor {
923
1150
  this.extractInheritance(node, classNode.id);
924
1151
  // Push to stack and visit body
925
1152
  this.nodeStack.push(classNode.id);
926
- const body = getChildByField(node, this.extractor.bodyField) || node;
1153
+ let body = getChildByField(node, this.extractor.bodyField);
1154
+ // Dart: mixin_declaration uses class_body, extension uses extension_body
1155
+ if (!body && this.language === 'dart') {
1156
+ body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
1157
+ }
1158
+ if (!body)
1159
+ body = node;
927
1160
  // Visit all children for methods and properties
928
1161
  for (let i = 0; i < body.namedChildCount; i++) {
929
1162
  const child = body.namedChild(i);
@@ -939,10 +1172,10 @@ class TreeSitterExtractor {
939
1172
  extractMethod(node) {
940
1173
  if (!this.extractor)
941
1174
  return;
942
- // For most languages, only extract as method if inside a class
1175
+ // For most languages, only extract as method if inside a class-like node
943
1176
  // But Go methods are top-level with a receiver, so always treat them as methods
944
- if (this.nodeStack.length === 0 && this.language !== 'go') {
945
- // Top-level and not Go, treat as function
1177
+ if (!this.isInsideClassLikeNode() && this.language !== 'go') {
1178
+ // Not inside a class-like node and not Go, treat as function
946
1179
  this.extractFunction(node);
947
1180
  return;
948
1181
  }
@@ -961,7 +1194,10 @@ class TreeSitterExtractor {
961
1194
  });
962
1195
  // Push to stack and visit body
963
1196
  this.nodeStack.push(methodNode.id);
964
- const body = getChildByField(node, this.extractor.bodyField);
1197
+ // Dart: function_body is a next sibling of method_signature, not a child
1198
+ const body = this.language === 'dart'
1199
+ ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1200
+ : getChildByField(node, this.extractor.bodyField);
965
1201
  if (body) {
966
1202
  this.visitFunctionBody(body, methodNode.id);
967
1203
  }
@@ -1027,37 +1263,532 @@ class TreeSitterExtractor {
1027
1263
  isExported,
1028
1264
  });
1029
1265
  }
1266
+ /**
1267
+ * Extract a variable declaration (const, let, var, etc.)
1268
+ *
1269
+ * Extracts top-level and module-level variable declarations.
1270
+ * Captures the variable name and first 100 chars of initializer in signature for searchability.
1271
+ */
1272
+ extractVariable(node) {
1273
+ if (!this.extractor)
1274
+ return;
1275
+ // Different languages have different variable declaration structures
1276
+ // TypeScript/JavaScript: lexical_declaration contains variable_declarator children
1277
+ // Python: assignment has left (identifier) and right (value)
1278
+ // Go: var_declaration, short_var_declaration, const_declaration
1279
+ const isConst = this.extractor.isConst?.(node) ?? false;
1280
+ const kind = isConst ? 'constant' : 'variable';
1281
+ const docstring = getPrecedingDocstring(node, this.source);
1282
+ const isExported = this.extractor.isExported?.(node, this.source) ?? false;
1283
+ // Extract variable declarators based on language
1284
+ if (this.language === 'typescript' || this.language === 'javascript' ||
1285
+ this.language === 'tsx' || this.language === 'jsx') {
1286
+ // Handle lexical_declaration and variable_declaration
1287
+ // These contain one or more variable_declarator children
1288
+ for (let i = 0; i < node.namedChildCount; i++) {
1289
+ const child = node.namedChild(i);
1290
+ if (child?.type === 'variable_declarator') {
1291
+ const nameNode = getChildByField(child, 'name');
1292
+ const valueNode = getChildByField(child, 'value');
1293
+ if (nameNode) {
1294
+ const name = getNodeText(nameNode, this.source);
1295
+ // Arrow functions / function expressions: extract as function instead of variable
1296
+ if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
1297
+ this.extractFunction(valueNode);
1298
+ continue;
1299
+ }
1300
+ // Capture first 100 chars of initializer for context (stored in signature for searchability)
1301
+ const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
1302
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1303
+ this.createNode(kind, name, child, {
1304
+ docstring,
1305
+ signature: initSignature,
1306
+ isExported,
1307
+ });
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+ else if (this.language === 'python' || this.language === 'ruby') {
1313
+ // Python/Ruby assignment: left = right
1314
+ const left = getChildByField(node, 'left') || node.namedChild(0);
1315
+ const right = getChildByField(node, 'right') || node.namedChild(1);
1316
+ if (left && left.type === 'identifier') {
1317
+ const name = getNodeText(left, this.source);
1318
+ // Skip if name starts with lowercase and looks like a function call result
1319
+ // Python constants are usually UPPER_CASE
1320
+ const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
1321
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1322
+ this.createNode(kind, name, node, {
1323
+ docstring,
1324
+ signature: initSignature,
1325
+ });
1326
+ }
1327
+ }
1328
+ else if (this.language === 'go') {
1329
+ // Go: var_declaration, short_var_declaration, const_declaration
1330
+ // These can have multiple identifiers on the left
1331
+ const specs = node.namedChildren.filter(c => c.type === 'var_spec' || c.type === 'const_spec');
1332
+ for (const spec of specs) {
1333
+ const nameNode = spec.namedChild(0);
1334
+ if (nameNode && nameNode.type === 'identifier') {
1335
+ const name = getNodeText(nameNode, this.source);
1336
+ const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
1337
+ const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
1338
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1339
+ this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
1340
+ docstring,
1341
+ signature: initSignature,
1342
+ });
1343
+ }
1344
+ }
1345
+ // Handle short_var_declaration (:=)
1346
+ if (node.type === 'short_var_declaration') {
1347
+ const left = getChildByField(node, 'left');
1348
+ const right = getChildByField(node, 'right');
1349
+ if (left) {
1350
+ // Can be expression_list with multiple identifiers
1351
+ const identifiers = left.type === 'expression_list'
1352
+ ? left.namedChildren.filter(c => c.type === 'identifier')
1353
+ : [left];
1354
+ for (const id of identifiers) {
1355
+ const name = getNodeText(id, this.source);
1356
+ const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
1357
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1358
+ this.createNode('variable', name, node, {
1359
+ docstring,
1360
+ signature: initSignature,
1361
+ });
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+ else {
1367
+ // Generic fallback for other languages
1368
+ // Try to find identifier children
1369
+ for (let i = 0; i < node.namedChildCount; i++) {
1370
+ const child = node.namedChild(i);
1371
+ if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
1372
+ const name = child.type === 'identifier'
1373
+ ? getNodeText(child, this.source)
1374
+ : extractName(child, this.source, this.extractor);
1375
+ if (name && name !== '<anonymous>') {
1376
+ this.createNode(kind, name, child, {
1377
+ docstring,
1378
+ isExported,
1379
+ });
1380
+ }
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ /**
1386
+ * Extract a type alias (e.g. `export type X = ...` in TypeScript)
1387
+ */
1388
+ extractTypeAlias(node) {
1389
+ if (!this.extractor)
1390
+ return;
1391
+ const name = extractName(node, this.source, this.extractor);
1392
+ if (name === '<anonymous>')
1393
+ return;
1394
+ const docstring = getPrecedingDocstring(node, this.source);
1395
+ const isExported = this.extractor.isExported?.(node, this.source);
1396
+ this.createNode('type_alias', name, node, {
1397
+ docstring,
1398
+ isExported,
1399
+ });
1400
+ }
1401
+ /**
1402
+ * Extract an exported variable declaration that isn't a function.
1403
+ * Handles patterns like:
1404
+ * export const X = create(...)
1405
+ * export const X = { ... }
1406
+ * export const X = [...]
1407
+ * export const X = "value"
1408
+ *
1409
+ * This is called for `export_statement` nodes that contain a
1410
+ * `lexical_declaration` with `variable_declarator` children whose
1411
+ * values are NOT already handled by functionTypes (arrow_function,
1412
+ * function_expression).
1413
+ */
1414
+ extractExportedVariables(exportNode) {
1415
+ if (!this.extractor)
1416
+ return;
1417
+ // Find the lexical_declaration or variable_declaration child
1418
+ for (let i = 0; i < exportNode.namedChildCount; i++) {
1419
+ const decl = exportNode.namedChild(i);
1420
+ if (!decl || (decl.type !== 'lexical_declaration' && decl.type !== 'variable_declaration')) {
1421
+ continue;
1422
+ }
1423
+ // Iterate over each variable_declarator in the declaration
1424
+ for (let j = 0; j < decl.namedChildCount; j++) {
1425
+ const declarator = decl.namedChild(j);
1426
+ if (!declarator || declarator.type !== 'variable_declarator')
1427
+ continue;
1428
+ const nameNode = getChildByField(declarator, 'name');
1429
+ if (!nameNode)
1430
+ continue;
1431
+ const name = getNodeText(nameNode, this.source);
1432
+ // Skip if the value is a function type — those are already handled
1433
+ // by extractFunction via the functionTypes dispatch
1434
+ const value = getChildByField(declarator, 'value');
1435
+ if (value) {
1436
+ const valueType = value.type;
1437
+ if (this.extractor.functionTypes.includes(valueType)) {
1438
+ continue; // Already handled by extractFunction
1439
+ }
1440
+ }
1441
+ const docstring = getPrecedingDocstring(exportNode, this.source);
1442
+ this.createNode('variable', name, declarator, {
1443
+ docstring,
1444
+ isExported: true,
1445
+ });
1446
+ }
1447
+ }
1448
+ }
1030
1449
  /**
1031
1450
  * Extract an import
1451
+ *
1452
+ * Creates an import node with the full import statement stored in signature for searchability.
1453
+ * Also creates unresolved references for resolution purposes.
1032
1454
  */
1033
1455
  extractImport(node) {
1034
- // Create an edge to track the import
1035
- // For now, we'll create unresolved references
1036
- const importText = getNodeText(node, this.source);
1456
+ const importText = getNodeText(node, this.source).trim();
1037
1457
  // Extract module/package name based on language
1038
1458
  let moduleName = '';
1039
- if (this.language === 'typescript' || this.language === 'javascript') {
1459
+ if (this.language === 'typescript' || this.language === 'javascript' ||
1460
+ this.language === 'tsx' || this.language === 'jsx') {
1040
1461
  const source = getChildByField(node, 'source');
1041
1462
  if (source) {
1042
1463
  moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
1043
1464
  }
1465
+ // Create import node with full statement as signature for searchability
1466
+ if (moduleName) {
1467
+ this.createNode('import', moduleName, node, {
1468
+ signature: importText,
1469
+ });
1470
+ }
1044
1471
  }
1045
1472
  else if (this.language === 'python') {
1046
- const module = getChildByField(node, 'module_name') || node.namedChild(0);
1047
- if (module) {
1048
- moduleName = getNodeText(module, this.source);
1473
+ // Python has two import forms:
1474
+ // 1. import_statement: import os, sys
1475
+ // 2. import_from_statement: from os import path
1476
+ if (node.type === 'import_from_statement') {
1477
+ const moduleNode = getChildByField(node, 'module_name');
1478
+ if (moduleNode) {
1479
+ moduleName = getNodeText(moduleNode, this.source);
1480
+ }
1481
+ }
1482
+ else {
1483
+ // import_statement - may have multiple modules
1484
+ // Can be dotted_name (import os) or aliased_import (import numpy as np)
1485
+ for (let i = 0; i < node.namedChildCount; i++) {
1486
+ const child = node.namedChild(i);
1487
+ if (child?.type === 'dotted_name') {
1488
+ const name = getNodeText(child, this.source);
1489
+ this.createNode('import', name, node, {
1490
+ signature: importText,
1491
+ });
1492
+ }
1493
+ else if (child?.type === 'aliased_import') {
1494
+ // Extract the module name from inside aliased_import
1495
+ const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
1496
+ if (dottedName) {
1497
+ const name = getNodeText(dottedName, this.source);
1498
+ this.createNode('import', name, node, {
1499
+ signature: importText,
1500
+ });
1501
+ }
1502
+ }
1503
+ }
1504
+ // Skip creating another node below if we handled import_statement
1505
+ if (node.type === 'import_statement') {
1506
+ return;
1507
+ }
1508
+ }
1509
+ if (moduleName) {
1510
+ this.createNode('import', moduleName, node, {
1511
+ signature: importText,
1512
+ });
1049
1513
  }
1050
1514
  }
1051
1515
  else if (this.language === 'go') {
1052
- const path = node.namedChild(0);
1053
- if (path) {
1054
- moduleName = getNodeText(path, this.source).replace(/['"]/g, '');
1516
+ // Go imports can be single or grouped
1517
+ // Single: import "fmt" - uses import_spec directly as child
1518
+ // Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
1519
+ // Helper function to extract path from import_spec
1520
+ const extractFromSpec = (spec) => {
1521
+ const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
1522
+ if (stringLiteral) {
1523
+ const path = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1524
+ if (path) {
1525
+ this.createNode('import', path, spec, {
1526
+ signature: getNodeText(spec, this.source).trim(),
1527
+ });
1528
+ }
1529
+ }
1530
+ };
1531
+ // Find import_spec_list for grouped imports
1532
+ const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
1533
+ if (importSpecList) {
1534
+ // Grouped imports - iterate through import_spec children
1535
+ const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
1536
+ for (const spec of importSpecs) {
1537
+ extractFromSpec(spec);
1538
+ }
1539
+ }
1540
+ else {
1541
+ // Single import: import "fmt" - import_spec is direct child
1542
+ const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
1543
+ if (importSpec) {
1544
+ extractFromSpec(importSpec);
1545
+ }
1055
1546
  }
1547
+ return; // Go handled completely above
1548
+ }
1549
+ else if (this.language === 'rust') {
1550
+ // Rust use declarations
1551
+ // use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
1552
+ // use crate::error::Error; -> scoped_identifier starting with "crate"
1553
+ // use super::utils; -> scoped_identifier starting with "super"
1554
+ // Helper to get the root crate/module from a scoped path
1555
+ const getRootModule = (scopedNode) => {
1556
+ // Recursively find the leftmost identifier/crate/super/self
1557
+ const firstChild = scopedNode.namedChild(0);
1558
+ if (!firstChild)
1559
+ return getNodeText(scopedNode, this.source);
1560
+ if (firstChild.type === 'identifier' ||
1561
+ firstChild.type === 'crate' ||
1562
+ firstChild.type === 'super' ||
1563
+ firstChild.type === 'self') {
1564
+ return getNodeText(firstChild, this.source);
1565
+ }
1566
+ else if (firstChild.type === 'scoped_identifier') {
1567
+ return getRootModule(firstChild);
1568
+ }
1569
+ return getNodeText(firstChild, this.source);
1570
+ };
1571
+ // Find the use argument (scoped_use_list or scoped_identifier)
1572
+ const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
1573
+ c.type === 'scoped_identifier' ||
1574
+ c.type === 'use_list' ||
1575
+ c.type === 'identifier');
1576
+ if (useArg) {
1577
+ moduleName = getRootModule(useArg);
1578
+ this.createNode('import', moduleName, node, {
1579
+ signature: importText,
1580
+ });
1581
+ }
1582
+ return; // Rust handled completely above
1583
+ }
1584
+ else if (this.language === 'swift') {
1585
+ // Swift imports: import Foundation, @testable import Alamofire
1586
+ // AST structure: import_declaration -> identifier -> simple_identifier
1587
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1588
+ if (identifier) {
1589
+ moduleName = getNodeText(identifier, this.source);
1590
+ this.createNode('import', moduleName, node, {
1591
+ signature: importText,
1592
+ });
1593
+ }
1594
+ return; // Swift handled completely above
1595
+ }
1596
+ else if (this.language === 'kotlin') {
1597
+ // Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
1598
+ // AST structure: import_header -> identifier (dotted path)
1599
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1600
+ if (identifier) {
1601
+ moduleName = getNodeText(identifier, this.source);
1602
+ this.createNode('import', moduleName, node, {
1603
+ signature: importText,
1604
+ });
1605
+ }
1606
+ return; // Kotlin handled completely above
1607
+ }
1608
+ else if (this.language === 'java') {
1609
+ // Java imports: import java.util.List, import static x.Y.method, import x.y.*
1610
+ // AST structure: import_declaration -> scoped_identifier (dotted path)
1611
+ const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
1612
+ if (scopedId) {
1613
+ moduleName = getNodeText(scopedId, this.source);
1614
+ this.createNode('import', moduleName, node, {
1615
+ signature: importText,
1616
+ });
1617
+ }
1618
+ return; // Java handled completely above
1619
+ }
1620
+ else if (this.language === 'csharp') {
1621
+ // C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
1622
+ // AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
1623
+ // For alias imports: identifier = qualified_name - we want the qualified_name
1624
+ const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
1625
+ if (qualifiedName) {
1626
+ moduleName = getNodeText(qualifiedName, this.source);
1627
+ }
1628
+ else {
1629
+ // Simple namespace like "using System;" - get the first identifier
1630
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1631
+ if (identifier) {
1632
+ moduleName = getNodeText(identifier, this.source);
1633
+ }
1634
+ }
1635
+ if (moduleName) {
1636
+ this.createNode('import', moduleName, node, {
1637
+ signature: importText,
1638
+ });
1639
+ }
1640
+ return; // C# handled completely above
1641
+ }
1642
+ else if (this.language === 'php') {
1643
+ // PHP use declarations: use X\Y\Z, use X as Y, use function X\func, use X\{A, B}
1644
+ // AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
1645
+ // Check for grouped imports first: use X\{A, B}
1646
+ const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
1647
+ const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
1648
+ if (namespacePrefix && useGroup) {
1649
+ // Grouped import - create one import per item
1650
+ const prefix = getNodeText(namespacePrefix, this.source);
1651
+ const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_clause');
1652
+ for (const clause of useClauses) {
1653
+ const name = clause.namedChildren.find((c) => c.type === 'name');
1654
+ if (name) {
1655
+ const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
1656
+ this.createNode('import', fullPath, node, {
1657
+ signature: importText,
1658
+ });
1659
+ }
1660
+ }
1661
+ return;
1662
+ }
1663
+ // Single import - find namespace_use_clause
1664
+ const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
1665
+ if (useClause) {
1666
+ // Look for qualified_name (full path) or name (simple)
1667
+ const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
1668
+ if (qualifiedName) {
1669
+ moduleName = getNodeText(qualifiedName, this.source);
1670
+ }
1671
+ else {
1672
+ const name = useClause.namedChildren.find((c) => c.type === 'name');
1673
+ if (name) {
1674
+ moduleName = getNodeText(name, this.source);
1675
+ }
1676
+ }
1677
+ }
1678
+ if (moduleName) {
1679
+ this.createNode('import', moduleName, node, {
1680
+ signature: importText,
1681
+ });
1682
+ }
1683
+ return; // PHP handled completely above
1684
+ }
1685
+ else if (this.language === 'ruby') {
1686
+ // Ruby imports: require 'json', require_relative '../helper'
1687
+ // AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
1688
+ // Check if this is a require/require_relative call
1689
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1690
+ if (!identifier)
1691
+ return;
1692
+ const methodName = getNodeText(identifier, this.source);
1693
+ if (methodName !== 'require' && methodName !== 'require_relative') {
1694
+ return; // Not an import, skip
1695
+ }
1696
+ // Find the argument (string)
1697
+ const argList = node.namedChildren.find(c => c.type === 'argument_list');
1698
+ if (argList) {
1699
+ const stringNode = argList.namedChildren.find((c) => c.type === 'string');
1700
+ if (stringNode) {
1701
+ // Get string_content (without quotes)
1702
+ const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
1703
+ if (stringContent) {
1704
+ moduleName = getNodeText(stringContent, this.source);
1705
+ }
1706
+ }
1707
+ }
1708
+ if (moduleName) {
1709
+ this.createNode('import', moduleName, node, {
1710
+ signature: importText,
1711
+ });
1712
+ }
1713
+ return; // Ruby handled completely above
1714
+ }
1715
+ else if (this.language === 'dart') {
1716
+ // Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
1717
+ // AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
1718
+ const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
1719
+ if (libraryImport) {
1720
+ const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
1721
+ if (importSpec) {
1722
+ const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
1723
+ if (configurableUri) {
1724
+ const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1725
+ if (uri) {
1726
+ const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1727
+ if (stringLiteral) {
1728
+ moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1729
+ }
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ // Also handle exports: export 'src/foo.dart';
1735
+ const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
1736
+ if (libraryExport) {
1737
+ const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
1738
+ if (configurableUri) {
1739
+ const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1740
+ if (uri) {
1741
+ const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1742
+ if (stringLiteral) {
1743
+ moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1744
+ }
1745
+ }
1746
+ }
1747
+ }
1748
+ if (moduleName) {
1749
+ this.createNode('import', moduleName, node, {
1750
+ signature: importText,
1751
+ });
1752
+ }
1753
+ return; // Dart handled completely above
1754
+ }
1755
+ else if (this.language === 'c' || this.language === 'cpp') {
1756
+ // C/C++ includes: #include <iostream>, #include "myheader.h"
1757
+ // AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
1758
+ // Check for system include: <path>
1759
+ const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
1760
+ if (systemLib) {
1761
+ // Remove angle brackets: <iostream> -> iostream
1762
+ moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
1763
+ }
1764
+ else {
1765
+ // Check for local include: "path"
1766
+ const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
1767
+ if (stringLiteral) {
1768
+ const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
1769
+ if (stringContent) {
1770
+ moduleName = getNodeText(stringContent, this.source);
1771
+ }
1772
+ }
1773
+ }
1774
+ if (moduleName) {
1775
+ this.createNode('import', moduleName, node, {
1776
+ signature: importText,
1777
+ });
1778
+ }
1779
+ return; // C/C++ handled completely above
1056
1780
  }
1057
1781
  else {
1058
- // Generic extraction
1782
+ // Generic extraction for other languages
1059
1783
  moduleName = importText;
1784
+ if (moduleName) {
1785
+ this.createNode('import', moduleName, node, {
1786
+ signature: importText,
1787
+ });
1788
+ }
1060
1789
  }
1790
+ // Keep unresolved reference creation for resolution purposes
1791
+ // This is used to resolve imports to their target files/modules
1061
1792
  if (moduleName && this.nodeStack.length > 0) {
1062
1793
  const parentId = this.nodeStack[this.nodeStack.length - 1];
1063
1794
  if (parentId) {
@@ -1155,7 +1886,9 @@ class TreeSitterExtractor {
1155
1886
  }
1156
1887
  }
1157
1888
  if (child.type === 'implements_clause' ||
1158
- child.type === 'class_interface_clause') {
1889
+ child.type === 'class_interface_clause' ||
1890
+ child.type === 'interfaces' // Dart
1891
+ ) {
1159
1892
  // Extract implemented interfaces
1160
1893
  for (let j = 0; j < child.namedChildCount; j++) {
1161
1894
  const iface = child.namedChild(j);
@@ -1213,6 +1946,7 @@ class LiquidExtractor {
1213
1946
  this.extractAssignments(fileNode.id);
1214
1947
  }
1215
1948
  catch (error) {
1949
+ (0, sentry_1.captureException)(error, { operation: 'liquid-extraction', filePath: this.filePath });
1216
1950
  this.errors.push({
1217
1951
  message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
1218
1952
  severity: 'error',
@@ -1256,8 +1990,31 @@ class LiquidExtractor {
1256
1990
  const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
1257
1991
  let match;
1258
1992
  while ((match = renderRegex.exec(this.source)) !== null) {
1259
- const [, tagType, snippetName] = match;
1993
+ const [fullMatch, tagType, snippetName] = match;
1260
1994
  const line = this.getLineNumber(match.index);
1995
+ // Create an import node for searchability
1996
+ const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
1997
+ const importNode = {
1998
+ id: importNodeId,
1999
+ kind: 'import',
2000
+ name: snippetName,
2001
+ qualifiedName: `${this.filePath}::import:${snippetName}`,
2002
+ filePath: this.filePath,
2003
+ language: 'liquid',
2004
+ signature: fullMatch,
2005
+ startLine: line,
2006
+ endLine: line,
2007
+ startColumn: match.index - this.getLineStart(line),
2008
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2009
+ updatedAt: Date.now(),
2010
+ };
2011
+ this.nodes.push(importNode);
2012
+ // Add containment edge from file to import
2013
+ this.edges.push({
2014
+ source: fileNodeId,
2015
+ target: importNodeId,
2016
+ kind: 'contains',
2017
+ });
1261
2018
  // Create a component node for the snippet reference
1262
2019
  const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
1263
2020
  const node = {
@@ -1270,7 +2027,7 @@ class LiquidExtractor {
1270
2027
  startLine: line,
1271
2028
  endLine: line,
1272
2029
  startColumn: match.index - this.getLineStart(line),
1273
- endColumn: match.index - this.getLineStart(line) + match[0].length,
2030
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
1274
2031
  updatedAt: Date.now(),
1275
2032
  };
1276
2033
  this.nodes.push(node);
@@ -1298,8 +2055,31 @@ class LiquidExtractor {
1298
2055
  const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
1299
2056
  let match;
1300
2057
  while ((match = sectionRegex.exec(this.source)) !== null) {
1301
- const [, sectionName] = match;
2058
+ const [fullMatch, sectionName] = match;
1302
2059
  const line = this.getLineNumber(match.index);
2060
+ // Create an import node for searchability
2061
+ const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
2062
+ const importNode = {
2063
+ id: importNodeId,
2064
+ kind: 'import',
2065
+ name: sectionName,
2066
+ qualifiedName: `${this.filePath}::import:${sectionName}`,
2067
+ filePath: this.filePath,
2068
+ language: 'liquid',
2069
+ signature: fullMatch,
2070
+ startLine: line,
2071
+ endLine: line,
2072
+ startColumn: match.index - this.getLineStart(line),
2073
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2074
+ updatedAt: Date.now(),
2075
+ };
2076
+ this.nodes.push(importNode);
2077
+ // Add containment edge from file to import
2078
+ this.edges.push({
2079
+ source: fileNodeId,
2080
+ target: importNodeId,
2081
+ kind: 'contains',
2082
+ });
1303
2083
  // Create a component node for the section reference
1304
2084
  const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
1305
2085
  const node = {
@@ -1312,7 +2092,7 @@ class LiquidExtractor {
1312
2092
  startLine: line,
1313
2093
  endLine: line,
1314
2094
  startColumn: match.index - this.getLineStart(line),
1315
- endColumn: match.index - this.getLineStart(line) + match[0].length,
2095
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
1316
2096
  updatedAt: Date.now(),
1317
2097
  };
1318
2098
  this.nodes.push(node);
@@ -1433,11 +2213,173 @@ class LiquidExtractor {
1433
2213
  }
1434
2214
  }
1435
2215
  exports.LiquidExtractor = LiquidExtractor;
2216
+ /**
2217
+ * SvelteExtractor - Extracts code relationships from Svelte component files
2218
+ *
2219
+ * Svelte files are multi-language (script + template + style). Rather than
2220
+ * parsing the full Svelte grammar, we extract the <script> block content
2221
+ * and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
2222
+ *
2223
+ * Every .svelte file produces a component node (Svelte components are always importable).
2224
+ */
2225
+ class SvelteExtractor {
2226
+ filePath;
2227
+ source;
2228
+ nodes = [];
2229
+ edges = [];
2230
+ unresolvedReferences = [];
2231
+ errors = [];
2232
+ constructor(filePath, source) {
2233
+ this.filePath = filePath;
2234
+ this.source = source;
2235
+ }
2236
+ /**
2237
+ * Extract from Svelte source
2238
+ */
2239
+ extract() {
2240
+ const startTime = Date.now();
2241
+ try {
2242
+ // Create component node for the .svelte file itself
2243
+ const componentNode = this.createComponentNode();
2244
+ // Extract and process script blocks
2245
+ const scriptBlocks = this.extractScriptBlocks();
2246
+ for (const block of scriptBlocks) {
2247
+ this.processScriptBlock(block, componentNode.id);
2248
+ }
2249
+ }
2250
+ catch (error) {
2251
+ (0, sentry_1.captureException)(error, { operation: 'svelte-extraction', filePath: this.filePath });
2252
+ this.errors.push({
2253
+ message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
2254
+ severity: 'error',
2255
+ });
2256
+ }
2257
+ return {
2258
+ nodes: this.nodes,
2259
+ edges: this.edges,
2260
+ unresolvedReferences: this.unresolvedReferences,
2261
+ errors: this.errors,
2262
+ durationMs: Date.now() - startTime,
2263
+ };
2264
+ }
2265
+ /**
2266
+ * Create a component node for the .svelte file
2267
+ */
2268
+ createComponentNode() {
2269
+ const lines = this.source.split('\n');
2270
+ const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
2271
+ const componentName = fileName.replace(/\.svelte$/, '');
2272
+ const id = generateNodeId(this.filePath, 'component', componentName, 1);
2273
+ const node = {
2274
+ id,
2275
+ kind: 'component',
2276
+ name: componentName,
2277
+ qualifiedName: `${this.filePath}::${componentName}`,
2278
+ filePath: this.filePath,
2279
+ language: 'svelte',
2280
+ startLine: 1,
2281
+ endLine: lines.length,
2282
+ startColumn: 0,
2283
+ endColumn: lines[lines.length - 1]?.length || 0,
2284
+ isExported: true, // Svelte components are always importable
2285
+ updatedAt: Date.now(),
2286
+ };
2287
+ this.nodes.push(node);
2288
+ return node;
2289
+ }
2290
+ /**
2291
+ * Extract <script> blocks from the Svelte source
2292
+ */
2293
+ extractScriptBlocks() {
2294
+ const blocks = [];
2295
+ const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
2296
+ let match;
2297
+ while ((match = scriptRegex.exec(this.source)) !== null) {
2298
+ const attrs = match[1] || '';
2299
+ const content = match.groups?.content || match[2] || '';
2300
+ // Detect TypeScript from lang attribute
2301
+ const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
2302
+ // Detect module script
2303
+ const isModule = /context\s*=\s*["']module["']/.test(attrs);
2304
+ // Calculate start line of the script content (line after <script>)
2305
+ const beforeScript = this.source.substring(0, match.index);
2306
+ const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
2307
+ // The content starts on the line after the opening <script> tag
2308
+ const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
2309
+ const openingTagLines = (openingTag.match(/\n/g) || []).length;
2310
+ const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
2311
+ blocks.push({
2312
+ content,
2313
+ startLine: contentStartLine,
2314
+ isModule,
2315
+ isTypeScript,
2316
+ });
2317
+ }
2318
+ return blocks;
2319
+ }
2320
+ /**
2321
+ * Process a script block by delegating to TreeSitterExtractor
2322
+ */
2323
+ processScriptBlock(block, componentNodeId) {
2324
+ const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
2325
+ // Check if the script language parser is available
2326
+ if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
2327
+ this.errors.push({
2328
+ message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
2329
+ severity: 'warning',
2330
+ });
2331
+ return;
2332
+ }
2333
+ // Delegate to TreeSitterExtractor
2334
+ const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
2335
+ const result = extractor.extract();
2336
+ // Offset line numbers from script block back to .svelte file positions
2337
+ for (const node of result.nodes) {
2338
+ node.startLine += block.startLine;
2339
+ node.endLine += block.startLine;
2340
+ node.language = 'svelte'; // Mark as svelte, not TS/JS
2341
+ this.nodes.push(node);
2342
+ // Add containment edge from component to this node
2343
+ this.edges.push({
2344
+ source: componentNodeId,
2345
+ target: node.id,
2346
+ kind: 'contains',
2347
+ });
2348
+ }
2349
+ // Offset edges (they reference line numbers)
2350
+ for (const edge of result.edges) {
2351
+ if (edge.line) {
2352
+ edge.line += block.startLine;
2353
+ }
2354
+ this.edges.push(edge);
2355
+ }
2356
+ // Offset unresolved references
2357
+ for (const ref of result.unresolvedReferences) {
2358
+ ref.line += block.startLine;
2359
+ ref.filePath = this.filePath;
2360
+ ref.language = 'svelte';
2361
+ this.unresolvedReferences.push(ref);
2362
+ }
2363
+ // Carry over errors
2364
+ for (const error of result.errors) {
2365
+ if (error.line) {
2366
+ error.line += block.startLine;
2367
+ }
2368
+ this.errors.push(error);
2369
+ }
2370
+ }
2371
+ }
2372
+ exports.SvelteExtractor = SvelteExtractor;
1436
2373
  /**
1437
2374
  * Extract nodes and edges from source code
1438
2375
  */
1439
2376
  function extractFromSource(filePath, source, language) {
1440
2377
  const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
2378
+ // Use custom extractor for Svelte
2379
+ if (detectedLanguage === 'svelte') {
2380
+ const extractor = new SvelteExtractor(filePath, source);
2381
+ return extractor.extract();
2382
+ }
1441
2383
  // Use custom extractor for Liquid
1442
2384
  if (detectedLanguage === 'liquid') {
1443
2385
  const extractor = new LiquidExtractor(filePath, source);