@colbymchenry/codegraph 0.2.9 → 0.4.8

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.

Potentially problematic release.


This version of @colbymchenry/codegraph might be problematic. Click here for more details.

Files changed (113) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +647 -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 +8 -1
  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 +16 -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 +11 -12
  20. package/dist/db/migrations.js.map +1 -1
  21. package/dist/db/queries.d.ts +19 -0
  22. package/dist/db/queries.d.ts.map +1 -1
  23. package/dist/db/queries.js +221 -107
  24. package/dist/db/queries.js.map +1 -1
  25. package/dist/db/schema.sql +154 -149
  26. package/dist/directory.d.ts +13 -1
  27. package/dist/directory.d.ts.map +1 -1
  28. package/dist/directory.js +62 -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.map +1 -1
  39. package/dist/extraction/index.js +119 -7
  40. package/dist/extraction/index.js.map +1 -1
  41. package/dist/extraction/tree-sitter.d.ts +62 -0
  42. package/dist/extraction/tree-sitter.d.ts.map +1 -1
  43. package/dist/extraction/tree-sitter.js +937 -34
  44. package/dist/extraction/tree-sitter.js.map +1 -1
  45. package/dist/graph/traversal.d.ts.map +1 -1
  46. package/dist/graph/traversal.js +6 -2
  47. package/dist/graph/traversal.js.map +1 -1
  48. package/dist/index.d.ts +6 -38
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +85 -74
  51. package/dist/index.js.map +1 -1
  52. package/dist/installer/banner.js +7 -7
  53. package/dist/installer/claude-md-template.js +32 -32
  54. package/dist/installer/config-writer.d.ts +9 -0
  55. package/dist/installer/config-writer.d.ts.map +1 -1
  56. package/dist/installer/config-writer.js +78 -0
  57. package/dist/installer/config-writer.js.map +1 -1
  58. package/dist/installer/index.d.ts.map +1 -1
  59. package/dist/installer/index.js +28 -16
  60. package/dist/installer/index.js.map +1 -1
  61. package/dist/mcp/index.d.ts +14 -3
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +109 -29
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/mcp/tools.d.ts +62 -1
  66. package/dist/mcp/tools.d.ts.map +1 -1
  67. package/dist/mcp/tools.js +414 -43
  68. package/dist/mcp/tools.js.map +1 -1
  69. package/dist/mcp/transport.d.ts.map +1 -1
  70. package/dist/mcp/transport.js +2 -0
  71. package/dist/mcp/transport.js.map +1 -1
  72. package/dist/resolution/frameworks/index.d.ts +1 -0
  73. package/dist/resolution/frameworks/index.d.ts.map +1 -1
  74. package/dist/resolution/frameworks/index.js +5 -1
  75. package/dist/resolution/frameworks/index.js.map +1 -1
  76. package/dist/resolution/frameworks/svelte.d.ts +9 -0
  77. package/dist/resolution/frameworks/svelte.d.ts.map +1 -0
  78. package/dist/resolution/frameworks/svelte.js +268 -0
  79. package/dist/resolution/frameworks/svelte.js.map +1 -0
  80. package/dist/resolution/index.d.ts +11 -2
  81. package/dist/resolution/index.d.ts.map +1 -1
  82. package/dist/resolution/index.js +94 -13
  83. package/dist/resolution/index.js.map +1 -1
  84. package/dist/sentry.d.ts +22 -0
  85. package/dist/sentry.d.ts.map +1 -0
  86. package/dist/sentry.js +159 -0
  87. package/dist/sentry.js.map +1 -0
  88. package/dist/sync/index.d.ts +4 -2
  89. package/dist/sync/index.d.ts.map +1 -1
  90. package/dist/sync/index.js +3 -5
  91. package/dist/sync/index.js.map +1 -1
  92. package/dist/types.d.ts +5 -1
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +11 -0
  95. package/dist/types.js.map +1 -1
  96. package/dist/utils.d.ts +45 -2
  97. package/dist/utils.d.ts.map +1 -1
  98. package/dist/utils.js +114 -3
  99. package/dist/utils.js.map +1 -1
  100. package/dist/vectors/embedder.d.ts +1 -1
  101. package/dist/vectors/embedder.d.ts.map +1 -1
  102. package/dist/vectors/embedder.js +5 -2
  103. package/dist/vectors/embedder.js.map +1 -1
  104. package/dist/vectors/search.d.ts.map +1 -1
  105. package/dist/vectors/search.js +33 -32
  106. package/dist/vectors/search.js.map +1 -1
  107. package/package.json +72 -65
  108. package/scripts/patch-tree-sitter-dart.js +112 -0
  109. package/scripts/postinstall.js +71 -0
  110. package/dist/sync/git-hooks.d.ts +0 -66
  111. package/dist/sync/git-hooks.d.ts.map +0 -1
  112. package/dist/sync/git-hooks.js +0 -281
  113. package/dist/sync/git-hooks.js.map +0 -1
@@ -38,11 +38,12 @@ 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
45
  const grammars_1 = require("./grammars");
46
+ const sentry_1 = require("../sentry");
46
47
  /**
47
48
  * Generate a unique node ID
48
49
  *
@@ -110,8 +111,10 @@ const EXTRACTORS = {
110
111
  interfaceTypes: ['interface_declaration'],
111
112
  structTypes: [],
112
113
  enumTypes: ['enum_declaration'],
114
+ typeAliasTypes: ['type_alias_declaration'],
113
115
  importTypes: ['import_statement'],
114
116
  callTypes: ['call_expression'],
117
+ variableTypes: ['lexical_declaration', 'variable_declaration'],
115
118
  nameField: 'name',
116
119
  bodyField: 'body',
117
120
  paramsField: 'parameters',
@@ -142,13 +145,18 @@ const EXTRACTORS = {
142
145
  }
143
146
  return undefined;
144
147
  },
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');
148
+ isExported: (node, _source) => {
149
+ // Walk the parent chain to find an export_statement ancestor.
150
+ // This correctly handles deeply nested nodes like arrow functions
151
+ // inside variable declarations: `export const X = () => { ... }`
152
+ // where the arrow_function is 3 levels deep under export_statement.
153
+ let current = node.parent;
154
+ while (current) {
155
+ if (current.type === 'export_statement')
156
+ return true;
157
+ current = current.parent;
158
+ }
159
+ return false;
152
160
  },
153
161
  isAsync: (node) => {
154
162
  for (let i = 0; i < node.childCount; i++) {
@@ -166,6 +174,18 @@ const EXTRACTORS = {
166
174
  }
167
175
  return false;
168
176
  },
177
+ isConst: (node) => {
178
+ // For lexical_declaration, check if it's 'const' or 'let'
179
+ // For variable_declaration, it's always 'var'
180
+ if (node.type === 'lexical_declaration') {
181
+ for (let i = 0; i < node.childCount; i++) {
182
+ const child = node.child(i);
183
+ if (child?.type === 'const')
184
+ return true;
185
+ }
186
+ }
187
+ return false;
188
+ },
169
189
  },
170
190
  javascript: {
171
191
  functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
@@ -174,8 +194,10 @@ const EXTRACTORS = {
174
194
  interfaceTypes: [],
175
195
  structTypes: [],
176
196
  enumTypes: [],
197
+ typeAliasTypes: [],
177
198
  importTypes: ['import_statement'],
178
199
  callTypes: ['call_expression'],
200
+ variableTypes: ['lexical_declaration', 'variable_declaration'],
179
201
  nameField: 'name',
180
202
  bodyField: 'body',
181
203
  paramsField: 'parameters',
@@ -183,12 +205,14 @@ const EXTRACTORS = {
183
205
  const params = getChildByField(node, 'parameters');
184
206
  return params ? getNodeText(params, source) : undefined;
185
207
  },
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');
208
+ isExported: (node, _source) => {
209
+ let current = node.parent;
210
+ while (current) {
211
+ if (current.type === 'export_statement')
212
+ return true;
213
+ current = current.parent;
214
+ }
215
+ return false;
192
216
  },
193
217
  isAsync: (node) => {
194
218
  for (let i = 0; i < node.childCount; i++) {
@@ -198,6 +222,16 @@ const EXTRACTORS = {
198
222
  }
199
223
  return false;
200
224
  },
225
+ isConst: (node) => {
226
+ if (node.type === 'lexical_declaration') {
227
+ for (let i = 0; i < node.childCount; i++) {
228
+ const child = node.child(i);
229
+ if (child?.type === 'const')
230
+ return true;
231
+ }
232
+ }
233
+ return false;
234
+ },
201
235
  },
202
236
  python: {
203
237
  functionTypes: ['function_definition'],
@@ -206,8 +240,10 @@ const EXTRACTORS = {
206
240
  interfaceTypes: [],
207
241
  structTypes: [],
208
242
  enumTypes: [],
243
+ typeAliasTypes: [],
209
244
  importTypes: ['import_statement', 'import_from_statement'],
210
245
  callTypes: ['call'],
246
+ variableTypes: ['assignment'], // Python uses assignment for variable declarations
211
247
  nameField: 'name',
212
248
  bodyField: 'body',
213
249
  paramsField: 'parameters',
@@ -244,8 +280,10 @@ const EXTRACTORS = {
244
280
  interfaceTypes: ['interface_type'],
245
281
  structTypes: ['struct_type'],
246
282
  enumTypes: [],
283
+ typeAliasTypes: ['type_spec'], // Go type declarations
247
284
  importTypes: ['import_declaration'],
248
285
  callTypes: ['call_expression'],
286
+ variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
249
287
  nameField: 'name',
250
288
  bodyField: 'body',
251
289
  paramsField: 'parameters',
@@ -269,8 +307,10 @@ const EXTRACTORS = {
269
307
  interfaceTypes: ['trait_item'],
270
308
  structTypes: ['struct_item'],
271
309
  enumTypes: ['enum_item'],
310
+ typeAliasTypes: ['type_item'], // Rust type aliases
272
311
  importTypes: ['use_declaration'],
273
312
  callTypes: ['call_expression'],
313
+ variableTypes: ['let_declaration', 'const_item', 'static_item'],
274
314
  nameField: 'name',
275
315
  bodyField: 'body',
276
316
  paramsField: 'parameters',
@@ -311,8 +351,10 @@ const EXTRACTORS = {
311
351
  interfaceTypes: ['interface_declaration'],
312
352
  structTypes: [],
313
353
  enumTypes: ['enum_declaration'],
354
+ typeAliasTypes: [],
314
355
  importTypes: ['import_declaration'],
315
356
  callTypes: ['method_invocation'],
357
+ variableTypes: ['local_variable_declaration', 'field_declaration'],
316
358
  nameField: 'name',
317
359
  bodyField: 'body',
318
360
  paramsField: 'parameters',
@@ -357,8 +399,10 @@ const EXTRACTORS = {
357
399
  interfaceTypes: [],
358
400
  structTypes: ['struct_specifier'],
359
401
  enumTypes: ['enum_specifier'],
402
+ typeAliasTypes: ['type_definition'], // typedef
360
403
  importTypes: ['preproc_include'],
361
404
  callTypes: ['call_expression'],
405
+ variableTypes: ['declaration'],
362
406
  nameField: 'declarator',
363
407
  bodyField: 'body',
364
408
  paramsField: 'parameters',
@@ -370,8 +414,10 @@ const EXTRACTORS = {
370
414
  interfaceTypes: [],
371
415
  structTypes: ['struct_specifier'],
372
416
  enumTypes: ['enum_specifier'],
417
+ typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
373
418
  importTypes: ['preproc_include'],
374
419
  callTypes: ['call_expression'],
420
+ variableTypes: ['declaration'],
375
421
  nameField: 'declarator',
376
422
  bodyField: 'body',
377
423
  paramsField: 'parameters',
@@ -402,8 +448,10 @@ const EXTRACTORS = {
402
448
  interfaceTypes: ['interface_declaration'],
403
449
  structTypes: ['struct_declaration'],
404
450
  enumTypes: ['enum_declaration'],
451
+ typeAliasTypes: [],
405
452
  importTypes: ['using_directive'],
406
453
  callTypes: ['invocation_expression'],
454
+ variableTypes: ['local_declaration_statement', 'field_declaration'],
407
455
  nameField: 'name',
408
456
  bodyField: 'body',
409
457
  paramsField: 'parameter_list',
@@ -450,8 +498,10 @@ const EXTRACTORS = {
450
498
  interfaceTypes: ['interface_declaration'],
451
499
  structTypes: [],
452
500
  enumTypes: ['enum_declaration'],
501
+ typeAliasTypes: [],
453
502
  importTypes: ['namespace_use_declaration'],
454
503
  callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
504
+ variableTypes: ['property_declaration', 'const_declaration'],
455
505
  nameField: 'name',
456
506
  bodyField: 'body',
457
507
  paramsField: 'parameters',
@@ -487,8 +537,10 @@ const EXTRACTORS = {
487
537
  interfaceTypes: [], // Ruby uses modules
488
538
  structTypes: [],
489
539
  enumTypes: [],
540
+ typeAliasTypes: [],
490
541
  importTypes: ['call'], // require/require_relative
491
542
  callTypes: ['call', 'method_call'],
543
+ variableTypes: ['assignment'], // Ruby uses assignment like Python
492
544
  nameField: 'name',
493
545
  bodyField: 'body',
494
546
  paramsField: 'parameters',
@@ -520,8 +572,10 @@ const EXTRACTORS = {
520
572
  interfaceTypes: ['protocol_declaration'],
521
573
  structTypes: ['struct_declaration'],
522
574
  enumTypes: ['enum_declaration'],
575
+ typeAliasTypes: ['typealias_declaration'],
523
576
  importTypes: ['import_declaration'],
524
577
  callTypes: ['call_expression'],
578
+ variableTypes: ['property_declaration', 'constant_declaration'],
525
579
  nameField: 'name',
526
580
  bodyField: 'body',
527
581
  paramsField: 'parameter',
@@ -584,8 +638,10 @@ const EXTRACTORS = {
584
638
  interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
585
639
  structTypes: [], // Kotlin uses data classes
586
640
  enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
641
+ typeAliasTypes: ['type_alias'],
587
642
  importTypes: ['import_header'],
588
643
  callTypes: ['call_expression'],
644
+ variableTypes: ['property_declaration'],
589
645
  nameField: 'simple_identifier',
590
646
  bodyField: 'function_body',
591
647
  paramsField: 'function_value_parameters',
@@ -636,6 +692,80 @@ const EXTRACTORS = {
636
692
  return false;
637
693
  },
638
694
  },
695
+ dart: {
696
+ functionTypes: ['function_signature'],
697
+ classTypes: ['class_definition'],
698
+ methodTypes: ['method_signature'],
699
+ interfaceTypes: [],
700
+ structTypes: [],
701
+ enumTypes: ['enum_declaration'],
702
+ typeAliasTypes: ['type_alias'],
703
+ importTypes: ['import_or_export'],
704
+ callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
705
+ variableTypes: [],
706
+ nameField: 'name',
707
+ bodyField: 'body', // class_definition uses 'body' field
708
+ paramsField: 'formal_parameter_list',
709
+ returnField: 'type',
710
+ getSignature: (node, source) => {
711
+ // For function_signature: extract params + return type
712
+ // For method_signature: delegate to inner function_signature
713
+ let sig = node;
714
+ if (node.type === 'method_signature') {
715
+ const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
716
+ if (inner)
717
+ sig = inner;
718
+ }
719
+ const params = sig.namedChildren.find((c) => c.type === 'formal_parameter_list');
720
+ const retType = sig.namedChildren.find((c) => c.type === 'type_identifier' || c.type === 'void_type');
721
+ if (!params && !retType)
722
+ return undefined;
723
+ let result = '';
724
+ if (retType)
725
+ result += getNodeText(retType, source) + ' ';
726
+ if (params)
727
+ result += getNodeText(params, source);
728
+ return result.trim() || undefined;
729
+ },
730
+ getVisibility: (node) => {
731
+ // Dart convention: _ prefix means private, otherwise public
732
+ let nameNode = null;
733
+ if (node.type === 'method_signature') {
734
+ const inner = node.namedChildren.find((c) => c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature');
735
+ if (inner)
736
+ nameNode = inner.namedChildren.find((c) => c.type === 'identifier') || null;
737
+ }
738
+ else {
739
+ nameNode = node.childForFieldName('name');
740
+ }
741
+ if (nameNode && nameNode.text.startsWith('_'))
742
+ return 'private';
743
+ return 'public';
744
+ },
745
+ isAsync: (node) => {
746
+ // In Dart, 'async' is on the function_body (next sibling), not the signature
747
+ const nextSibling = node.nextNamedSibling;
748
+ if (nextSibling?.type === 'function_body') {
749
+ for (let i = 0; i < nextSibling.childCount; i++) {
750
+ const child = nextSibling.child(i);
751
+ if (child?.type === 'async')
752
+ return true;
753
+ }
754
+ }
755
+ return false;
756
+ },
757
+ isStatic: (node) => {
758
+ // For method_signature, check for 'static' child
759
+ if (node.type === 'method_signature') {
760
+ for (let i = 0; i < node.childCount; i++) {
761
+ const child = node.child(i);
762
+ if (child?.type === 'static')
763
+ return true;
764
+ }
765
+ }
766
+ return false;
767
+ },
768
+ },
639
769
  };
640
770
  // TSX and JSX use the same extractors as their base languages
641
771
  EXTRACTORS.tsx = EXTRACTORS.typescript;
@@ -654,6 +784,25 @@ function extractName(node, source, extractor) {
654
784
  }
655
785
  return getNodeText(nameNode, source);
656
786
  }
787
+ // For Dart method_signature, look inside inner signature types
788
+ if (node.type === 'method_signature') {
789
+ for (let i = 0; i < node.namedChildCount; i++) {
790
+ const child = node.namedChild(i);
791
+ if (child && (child.type === 'function_signature' ||
792
+ child.type === 'getter_signature' ||
793
+ child.type === 'setter_signature' ||
794
+ child.type === 'constructor_signature' ||
795
+ child.type === 'factory_constructor_signature')) {
796
+ // Find identifier inside the inner signature
797
+ for (let j = 0; j < child.namedChildCount; j++) {
798
+ const inner = child.namedChild(j);
799
+ if (inner?.type === 'identifier') {
800
+ return getNodeText(inner, source);
801
+ }
802
+ }
803
+ }
804
+ }
805
+ }
657
806
  // Fall back to first identifier child
658
807
  for (let i = 0; i < node.namedChildCount; i++) {
659
808
  const child = node.namedChild(i);
@@ -726,6 +875,7 @@ class TreeSitterExtractor {
726
875
  this.visitNode(this.tree.rootNode);
727
876
  }
728
877
  catch (error) {
878
+ (0, sentry_1.captureException)(error, { operation: 'tree-sitter-parse', filePath: this.filePath, language: this.language });
729
879
  this.errors.push({
730
880
  message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
731
881
  severity: 'error',
@@ -775,6 +925,11 @@ class TreeSitterExtractor {
775
925
  }
776
926
  skipChildren = true; // extractClass visits body children
777
927
  }
928
+ // Dart-specific: mixin and extension declarations treated as classes
929
+ else if (this.language === 'dart' && (nodeType === 'mixin_declaration' || nodeType === 'extension_declaration')) {
930
+ this.extractClass(node);
931
+ skipChildren = true;
932
+ }
778
933
  // Check for method declarations (only if not already handled by functionTypes)
779
934
  else if (this.extractor.methodTypes.includes(nodeType)) {
780
935
  this.extractMethod(node);
@@ -795,6 +950,22 @@ class TreeSitterExtractor {
795
950
  this.extractEnum(node);
796
951
  skipChildren = true; // extractEnum visits body children
797
952
  }
953
+ // Check for type alias declarations (e.g. `type X = ...` in TypeScript)
954
+ else if (this.extractor.typeAliasTypes.includes(nodeType)) {
955
+ this.extractTypeAlias(node);
956
+ }
957
+ // Check for variable declarations (const, let, var, etc.)
958
+ // Only extract top-level variables (not inside functions/methods)
959
+ else if (this.extractor.variableTypes.includes(nodeType) && this.nodeStack.length === 0) {
960
+ this.extractVariable(node);
961
+ skipChildren = true; // extractVariable handles children
962
+ }
963
+ // Check for export statements containing non-function variable declarations
964
+ // e.g. `export const X = create(...)`, `export const X = { ... }`
965
+ else if (nodeType === 'export_statement') {
966
+ this.extractExportedVariables(node);
967
+ // Don't skip children — still need to visit inner nodes (functions, calls, etc.)
968
+ }
798
969
  // Check for imports
799
970
  else if (this.extractor.importTypes.includes(nodeType)) {
800
971
  this.extractImport(node);
@@ -879,7 +1050,21 @@ class TreeSitterExtractor {
879
1050
  extractFunction(node) {
880
1051
  if (!this.extractor)
881
1052
  return;
882
- const name = extractName(node, this.source, this.extractor);
1053
+ let name = extractName(node, this.source, this.extractor);
1054
+ // For arrow functions and function expressions assigned to variables,
1055
+ // resolve the name from the parent variable_declarator.
1056
+ // e.g. `export const useAuth = () => { ... }` — the arrow_function node
1057
+ // has no `name` field; the name lives on the variable_declarator.
1058
+ if (name === '<anonymous>' &&
1059
+ (node.type === 'arrow_function' || node.type === 'function_expression')) {
1060
+ const parent = node.parent;
1061
+ if (parent?.type === 'variable_declarator') {
1062
+ const varName = getChildByField(parent, 'name');
1063
+ if (varName) {
1064
+ name = getNodeText(varName, this.source);
1065
+ }
1066
+ }
1067
+ }
883
1068
  if (name === '<anonymous>')
884
1069
  return; // Skip anonymous functions
885
1070
  const docstring = getPrecedingDocstring(node, this.source);
@@ -898,7 +1083,10 @@ class TreeSitterExtractor {
898
1083
  });
899
1084
  // Push to stack and visit body
900
1085
  this.nodeStack.push(funcNode.id);
901
- const body = getChildByField(node, this.extractor.bodyField);
1086
+ // Dart: function_body is a next sibling of function_signature, not a child
1087
+ const body = this.language === 'dart'
1088
+ ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1089
+ : getChildByField(node, this.extractor.bodyField);
902
1090
  if (body) {
903
1091
  this.visitFunctionBody(body, funcNode.id);
904
1092
  }
@@ -923,7 +1111,13 @@ class TreeSitterExtractor {
923
1111
  this.extractInheritance(node, classNode.id);
924
1112
  // Push to stack and visit body
925
1113
  this.nodeStack.push(classNode.id);
926
- const body = getChildByField(node, this.extractor.bodyField) || node;
1114
+ let body = getChildByField(node, this.extractor.bodyField);
1115
+ // Dart: mixin_declaration uses class_body, extension uses extension_body
1116
+ if (!body && this.language === 'dart') {
1117
+ body = node.namedChildren.find((c) => c.type === 'class_body' || c.type === 'extension_body') || null;
1118
+ }
1119
+ if (!body)
1120
+ body = node;
927
1121
  // Visit all children for methods and properties
928
1122
  for (let i = 0; i < body.namedChildCount; i++) {
929
1123
  const child = body.namedChild(i);
@@ -961,7 +1155,10 @@ class TreeSitterExtractor {
961
1155
  });
962
1156
  // Push to stack and visit body
963
1157
  this.nodeStack.push(methodNode.id);
964
- const body = getChildByField(node, this.extractor.bodyField);
1158
+ // Dart: function_body is a next sibling of method_signature, not a child
1159
+ const body = this.language === 'dart'
1160
+ ? node.nextNamedSibling?.type === 'function_body' ? node.nextNamedSibling : null
1161
+ : getChildByField(node, this.extractor.bodyField);
965
1162
  if (body) {
966
1163
  this.visitFunctionBody(body, methodNode.id);
967
1164
  }
@@ -1027,37 +1224,532 @@ class TreeSitterExtractor {
1027
1224
  isExported,
1028
1225
  });
1029
1226
  }
1227
+ /**
1228
+ * Extract a variable declaration (const, let, var, etc.)
1229
+ *
1230
+ * Extracts top-level and module-level variable declarations.
1231
+ * Captures the variable name and first 100 chars of initializer in signature for searchability.
1232
+ */
1233
+ extractVariable(node) {
1234
+ if (!this.extractor)
1235
+ return;
1236
+ // Different languages have different variable declaration structures
1237
+ // TypeScript/JavaScript: lexical_declaration contains variable_declarator children
1238
+ // Python: assignment has left (identifier) and right (value)
1239
+ // Go: var_declaration, short_var_declaration, const_declaration
1240
+ const isConst = this.extractor.isConst?.(node) ?? false;
1241
+ const kind = isConst ? 'constant' : 'variable';
1242
+ const docstring = getPrecedingDocstring(node, this.source);
1243
+ const isExported = this.extractor.isExported?.(node, this.source) ?? false;
1244
+ // Extract variable declarators based on language
1245
+ if (this.language === 'typescript' || this.language === 'javascript' ||
1246
+ this.language === 'tsx' || this.language === 'jsx') {
1247
+ // Handle lexical_declaration and variable_declaration
1248
+ // These contain one or more variable_declarator children
1249
+ for (let i = 0; i < node.namedChildCount; i++) {
1250
+ const child = node.namedChild(i);
1251
+ if (child?.type === 'variable_declarator') {
1252
+ const nameNode = getChildByField(child, 'name');
1253
+ const valueNode = getChildByField(child, 'value');
1254
+ if (nameNode) {
1255
+ const name = getNodeText(nameNode, this.source);
1256
+ // Arrow functions / function expressions: extract as function instead of variable
1257
+ if (valueNode && (valueNode.type === 'arrow_function' || valueNode.type === 'function_expression')) {
1258
+ this.extractFunction(valueNode);
1259
+ continue;
1260
+ }
1261
+ // Capture first 100 chars of initializer for context (stored in signature for searchability)
1262
+ const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
1263
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1264
+ this.createNode(kind, name, child, {
1265
+ docstring,
1266
+ signature: initSignature,
1267
+ isExported,
1268
+ });
1269
+ }
1270
+ }
1271
+ }
1272
+ }
1273
+ else if (this.language === 'python' || this.language === 'ruby') {
1274
+ // Python/Ruby assignment: left = right
1275
+ const left = getChildByField(node, 'left') || node.namedChild(0);
1276
+ const right = getChildByField(node, 'right') || node.namedChild(1);
1277
+ if (left && left.type === 'identifier') {
1278
+ const name = getNodeText(left, this.source);
1279
+ // Skip if name starts with lowercase and looks like a function call result
1280
+ // Python constants are usually UPPER_CASE
1281
+ const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
1282
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1283
+ this.createNode(kind, name, node, {
1284
+ docstring,
1285
+ signature: initSignature,
1286
+ });
1287
+ }
1288
+ }
1289
+ else if (this.language === 'go') {
1290
+ // Go: var_declaration, short_var_declaration, const_declaration
1291
+ // These can have multiple identifiers on the left
1292
+ const specs = node.namedChildren.filter(c => c.type === 'var_spec' || c.type === 'const_spec');
1293
+ for (const spec of specs) {
1294
+ const nameNode = spec.namedChild(0);
1295
+ if (nameNode && nameNode.type === 'identifier') {
1296
+ const name = getNodeText(nameNode, this.source);
1297
+ const valueNode = spec.namedChildCount > 1 ? spec.namedChild(spec.namedChildCount - 1) : null;
1298
+ const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
1299
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1300
+ this.createNode(node.type === 'const_declaration' ? 'constant' : 'variable', name, spec, {
1301
+ docstring,
1302
+ signature: initSignature,
1303
+ });
1304
+ }
1305
+ }
1306
+ // Handle short_var_declaration (:=)
1307
+ if (node.type === 'short_var_declaration') {
1308
+ const left = getChildByField(node, 'left');
1309
+ const right = getChildByField(node, 'right');
1310
+ if (left) {
1311
+ // Can be expression_list with multiple identifiers
1312
+ const identifiers = left.type === 'expression_list'
1313
+ ? left.namedChildren.filter(c => c.type === 'identifier')
1314
+ : [left];
1315
+ for (const id of identifiers) {
1316
+ const name = getNodeText(id, this.source);
1317
+ const initValue = right ? getNodeText(right, this.source).slice(0, 100) : undefined;
1318
+ const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
1319
+ this.createNode('variable', name, node, {
1320
+ docstring,
1321
+ signature: initSignature,
1322
+ });
1323
+ }
1324
+ }
1325
+ }
1326
+ }
1327
+ else {
1328
+ // Generic fallback for other languages
1329
+ // Try to find identifier children
1330
+ for (let i = 0; i < node.namedChildCount; i++) {
1331
+ const child = node.namedChild(i);
1332
+ if (child?.type === 'identifier' || child?.type === 'variable_declarator') {
1333
+ const name = child.type === 'identifier'
1334
+ ? getNodeText(child, this.source)
1335
+ : extractName(child, this.source, this.extractor);
1336
+ if (name && name !== '<anonymous>') {
1337
+ this.createNode(kind, name, child, {
1338
+ docstring,
1339
+ isExported,
1340
+ });
1341
+ }
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ /**
1347
+ * Extract a type alias (e.g. `export type X = ...` in TypeScript)
1348
+ */
1349
+ extractTypeAlias(node) {
1350
+ if (!this.extractor)
1351
+ return;
1352
+ const name = extractName(node, this.source, this.extractor);
1353
+ if (name === '<anonymous>')
1354
+ return;
1355
+ const docstring = getPrecedingDocstring(node, this.source);
1356
+ const isExported = this.extractor.isExported?.(node, this.source);
1357
+ this.createNode('type_alias', name, node, {
1358
+ docstring,
1359
+ isExported,
1360
+ });
1361
+ }
1362
+ /**
1363
+ * Extract an exported variable declaration that isn't a function.
1364
+ * Handles patterns like:
1365
+ * export const X = create(...)
1366
+ * export const X = { ... }
1367
+ * export const X = [...]
1368
+ * export const X = "value"
1369
+ *
1370
+ * This is called for `export_statement` nodes that contain a
1371
+ * `lexical_declaration` with `variable_declarator` children whose
1372
+ * values are NOT already handled by functionTypes (arrow_function,
1373
+ * function_expression).
1374
+ */
1375
+ extractExportedVariables(exportNode) {
1376
+ if (!this.extractor)
1377
+ return;
1378
+ // Find the lexical_declaration or variable_declaration child
1379
+ for (let i = 0; i < exportNode.namedChildCount; i++) {
1380
+ const decl = exportNode.namedChild(i);
1381
+ if (!decl || (decl.type !== 'lexical_declaration' && decl.type !== 'variable_declaration')) {
1382
+ continue;
1383
+ }
1384
+ // Iterate over each variable_declarator in the declaration
1385
+ for (let j = 0; j < decl.namedChildCount; j++) {
1386
+ const declarator = decl.namedChild(j);
1387
+ if (!declarator || declarator.type !== 'variable_declarator')
1388
+ continue;
1389
+ const nameNode = getChildByField(declarator, 'name');
1390
+ if (!nameNode)
1391
+ continue;
1392
+ const name = getNodeText(nameNode, this.source);
1393
+ // Skip if the value is a function type — those are already handled
1394
+ // by extractFunction via the functionTypes dispatch
1395
+ const value = getChildByField(declarator, 'value');
1396
+ if (value) {
1397
+ const valueType = value.type;
1398
+ if (this.extractor.functionTypes.includes(valueType)) {
1399
+ continue; // Already handled by extractFunction
1400
+ }
1401
+ }
1402
+ const docstring = getPrecedingDocstring(exportNode, this.source);
1403
+ this.createNode('variable', name, declarator, {
1404
+ docstring,
1405
+ isExported: true,
1406
+ });
1407
+ }
1408
+ }
1409
+ }
1030
1410
  /**
1031
1411
  * Extract an import
1412
+ *
1413
+ * Creates an import node with the full import statement stored in signature for searchability.
1414
+ * Also creates unresolved references for resolution purposes.
1032
1415
  */
1033
1416
  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);
1417
+ const importText = getNodeText(node, this.source).trim();
1037
1418
  // Extract module/package name based on language
1038
1419
  let moduleName = '';
1039
- if (this.language === 'typescript' || this.language === 'javascript') {
1420
+ if (this.language === 'typescript' || this.language === 'javascript' ||
1421
+ this.language === 'tsx' || this.language === 'jsx') {
1040
1422
  const source = getChildByField(node, 'source');
1041
1423
  if (source) {
1042
1424
  moduleName = getNodeText(source, this.source).replace(/['"]/g, '');
1043
1425
  }
1426
+ // Create import node with full statement as signature for searchability
1427
+ if (moduleName) {
1428
+ this.createNode('import', moduleName, node, {
1429
+ signature: importText,
1430
+ });
1431
+ }
1044
1432
  }
1045
1433
  else if (this.language === 'python') {
1046
- const module = getChildByField(node, 'module_name') || node.namedChild(0);
1047
- if (module) {
1048
- moduleName = getNodeText(module, this.source);
1434
+ // Python has two import forms:
1435
+ // 1. import_statement: import os, sys
1436
+ // 2. import_from_statement: from os import path
1437
+ if (node.type === 'import_from_statement') {
1438
+ const moduleNode = getChildByField(node, 'module_name');
1439
+ if (moduleNode) {
1440
+ moduleName = getNodeText(moduleNode, this.source);
1441
+ }
1442
+ }
1443
+ else {
1444
+ // import_statement - may have multiple modules
1445
+ // Can be dotted_name (import os) or aliased_import (import numpy as np)
1446
+ for (let i = 0; i < node.namedChildCount; i++) {
1447
+ const child = node.namedChild(i);
1448
+ if (child?.type === 'dotted_name') {
1449
+ const name = getNodeText(child, this.source);
1450
+ this.createNode('import', name, node, {
1451
+ signature: importText,
1452
+ });
1453
+ }
1454
+ else if (child?.type === 'aliased_import') {
1455
+ // Extract the module name from inside aliased_import
1456
+ const dottedName = child.namedChildren.find(c => c.type === 'dotted_name');
1457
+ if (dottedName) {
1458
+ const name = getNodeText(dottedName, this.source);
1459
+ this.createNode('import', name, node, {
1460
+ signature: importText,
1461
+ });
1462
+ }
1463
+ }
1464
+ }
1465
+ // Skip creating another node below if we handled import_statement
1466
+ if (node.type === 'import_statement') {
1467
+ return;
1468
+ }
1469
+ }
1470
+ if (moduleName) {
1471
+ this.createNode('import', moduleName, node, {
1472
+ signature: importText,
1473
+ });
1049
1474
  }
1050
1475
  }
1051
1476
  else if (this.language === 'go') {
1052
- const path = node.namedChild(0);
1053
- if (path) {
1054
- moduleName = getNodeText(path, this.source).replace(/['"]/g, '');
1477
+ // Go imports can be single or grouped
1478
+ // Single: import "fmt" - uses import_spec directly as child
1479
+ // Grouped: import ( "fmt" \n "os" ) - uses import_spec_list containing import_spec children
1480
+ // Helper function to extract path from import_spec
1481
+ const extractFromSpec = (spec) => {
1482
+ const stringLiteral = spec.namedChildren.find(c => c.type === 'interpreted_string_literal');
1483
+ if (stringLiteral) {
1484
+ const path = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1485
+ if (path) {
1486
+ this.createNode('import', path, spec, {
1487
+ signature: getNodeText(spec, this.source).trim(),
1488
+ });
1489
+ }
1490
+ }
1491
+ };
1492
+ // Find import_spec_list for grouped imports
1493
+ const importSpecList = node.namedChildren.find(c => c.type === 'import_spec_list');
1494
+ if (importSpecList) {
1495
+ // Grouped imports - iterate through import_spec children
1496
+ const importSpecs = importSpecList.namedChildren.filter(c => c.type === 'import_spec');
1497
+ for (const spec of importSpecs) {
1498
+ extractFromSpec(spec);
1499
+ }
1500
+ }
1501
+ else {
1502
+ // Single import: import "fmt" - import_spec is direct child
1503
+ const importSpec = node.namedChildren.find(c => c.type === 'import_spec');
1504
+ if (importSpec) {
1505
+ extractFromSpec(importSpec);
1506
+ }
1507
+ }
1508
+ return; // Go handled completely above
1509
+ }
1510
+ else if (this.language === 'rust') {
1511
+ // Rust use declarations
1512
+ // use std::{ffi::OsStr, io}; -> scoped_use_list with identifier "std"
1513
+ // use crate::error::Error; -> scoped_identifier starting with "crate"
1514
+ // use super::utils; -> scoped_identifier starting with "super"
1515
+ // Helper to get the root crate/module from a scoped path
1516
+ const getRootModule = (scopedNode) => {
1517
+ // Recursively find the leftmost identifier/crate/super/self
1518
+ const firstChild = scopedNode.namedChild(0);
1519
+ if (!firstChild)
1520
+ return getNodeText(scopedNode, this.source);
1521
+ if (firstChild.type === 'identifier' ||
1522
+ firstChild.type === 'crate' ||
1523
+ firstChild.type === 'super' ||
1524
+ firstChild.type === 'self') {
1525
+ return getNodeText(firstChild, this.source);
1526
+ }
1527
+ else if (firstChild.type === 'scoped_identifier') {
1528
+ return getRootModule(firstChild);
1529
+ }
1530
+ return getNodeText(firstChild, this.source);
1531
+ };
1532
+ // Find the use argument (scoped_use_list or scoped_identifier)
1533
+ const useArg = node.namedChildren.find(c => c.type === 'scoped_use_list' ||
1534
+ c.type === 'scoped_identifier' ||
1535
+ c.type === 'use_list' ||
1536
+ c.type === 'identifier');
1537
+ if (useArg) {
1538
+ moduleName = getRootModule(useArg);
1539
+ this.createNode('import', moduleName, node, {
1540
+ signature: importText,
1541
+ });
1055
1542
  }
1543
+ return; // Rust handled completely above
1544
+ }
1545
+ else if (this.language === 'swift') {
1546
+ // Swift imports: import Foundation, @testable import Alamofire
1547
+ // AST structure: import_declaration -> identifier -> simple_identifier
1548
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1549
+ if (identifier) {
1550
+ moduleName = getNodeText(identifier, this.source);
1551
+ this.createNode('import', moduleName, node, {
1552
+ signature: importText,
1553
+ });
1554
+ }
1555
+ return; // Swift handled completely above
1556
+ }
1557
+ else if (this.language === 'kotlin') {
1558
+ // Kotlin imports: import java.io.IOException, import x.y.Z as Alias, import x.y.*
1559
+ // AST structure: import_header -> identifier (dotted path)
1560
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1561
+ if (identifier) {
1562
+ moduleName = getNodeText(identifier, this.source);
1563
+ this.createNode('import', moduleName, node, {
1564
+ signature: importText,
1565
+ });
1566
+ }
1567
+ return; // Kotlin handled completely above
1568
+ }
1569
+ else if (this.language === 'java') {
1570
+ // Java imports: import java.util.List, import static x.Y.method, import x.y.*
1571
+ // AST structure: import_declaration -> scoped_identifier (dotted path)
1572
+ const scopedId = node.namedChildren.find(c => c.type === 'scoped_identifier');
1573
+ if (scopedId) {
1574
+ moduleName = getNodeText(scopedId, this.source);
1575
+ this.createNode('import', moduleName, node, {
1576
+ signature: importText,
1577
+ });
1578
+ }
1579
+ return; // Java handled completely above
1580
+ }
1581
+ else if (this.language === 'csharp') {
1582
+ // C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
1583
+ // AST structure: using_directive -> qualified_name (dotted) or identifier (simple)
1584
+ // For alias imports: identifier = qualified_name - we want the qualified_name
1585
+ const qualifiedName = node.namedChildren.find(c => c.type === 'qualified_name');
1586
+ if (qualifiedName) {
1587
+ moduleName = getNodeText(qualifiedName, this.source);
1588
+ }
1589
+ else {
1590
+ // Simple namespace like "using System;" - get the first identifier
1591
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1592
+ if (identifier) {
1593
+ moduleName = getNodeText(identifier, this.source);
1594
+ }
1595
+ }
1596
+ if (moduleName) {
1597
+ this.createNode('import', moduleName, node, {
1598
+ signature: importText,
1599
+ });
1600
+ }
1601
+ return; // C# handled completely above
1602
+ }
1603
+ else if (this.language === 'php') {
1604
+ // PHP use declarations: use X\Y\Z, use X as Y, use function X\func, use X\{A, B}
1605
+ // AST structure: namespace_use_declaration -> namespace_use_clause -> qualified_name or name
1606
+ // Check for grouped imports first: use X\{A, B}
1607
+ const namespacePrefix = node.namedChildren.find(c => c.type === 'namespace_name');
1608
+ const useGroup = node.namedChildren.find(c => c.type === 'namespace_use_group');
1609
+ if (namespacePrefix && useGroup) {
1610
+ // Grouped import - create one import per item
1611
+ const prefix = getNodeText(namespacePrefix, this.source);
1612
+ const useClauses = useGroup.namedChildren.filter((c) => c.type === 'namespace_use_clause');
1613
+ for (const clause of useClauses) {
1614
+ const name = clause.namedChildren.find((c) => c.type === 'name');
1615
+ if (name) {
1616
+ const fullPath = `${prefix}\\${getNodeText(name, this.source)}`;
1617
+ this.createNode('import', fullPath, node, {
1618
+ signature: importText,
1619
+ });
1620
+ }
1621
+ }
1622
+ return;
1623
+ }
1624
+ // Single import - find namespace_use_clause
1625
+ const useClause = node.namedChildren.find(c => c.type === 'namespace_use_clause');
1626
+ if (useClause) {
1627
+ // Look for qualified_name (full path) or name (simple)
1628
+ const qualifiedName = useClause.namedChildren.find((c) => c.type === 'qualified_name');
1629
+ if (qualifiedName) {
1630
+ moduleName = getNodeText(qualifiedName, this.source);
1631
+ }
1632
+ else {
1633
+ const name = useClause.namedChildren.find((c) => c.type === 'name');
1634
+ if (name) {
1635
+ moduleName = getNodeText(name, this.source);
1636
+ }
1637
+ }
1638
+ }
1639
+ if (moduleName) {
1640
+ this.createNode('import', moduleName, node, {
1641
+ signature: importText,
1642
+ });
1643
+ }
1644
+ return; // PHP handled completely above
1645
+ }
1646
+ else if (this.language === 'ruby') {
1647
+ // Ruby imports: require 'json', require_relative '../helper'
1648
+ // AST structure: call -> identifier (require/require_relative) + argument_list -> string -> string_content
1649
+ // Check if this is a require/require_relative call
1650
+ const identifier = node.namedChildren.find(c => c.type === 'identifier');
1651
+ if (!identifier)
1652
+ return;
1653
+ const methodName = getNodeText(identifier, this.source);
1654
+ if (methodName !== 'require' && methodName !== 'require_relative') {
1655
+ return; // Not an import, skip
1656
+ }
1657
+ // Find the argument (string)
1658
+ const argList = node.namedChildren.find(c => c.type === 'argument_list');
1659
+ if (argList) {
1660
+ const stringNode = argList.namedChildren.find((c) => c.type === 'string');
1661
+ if (stringNode) {
1662
+ // Get string_content (without quotes)
1663
+ const stringContent = stringNode.namedChildren.find((c) => c.type === 'string_content');
1664
+ if (stringContent) {
1665
+ moduleName = getNodeText(stringContent, this.source);
1666
+ }
1667
+ }
1668
+ }
1669
+ if (moduleName) {
1670
+ this.createNode('import', moduleName, node, {
1671
+ signature: importText,
1672
+ });
1673
+ }
1674
+ return; // Ruby handled completely above
1675
+ }
1676
+ else if (this.language === 'dart') {
1677
+ // Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
1678
+ // AST: import_or_export -> library_import -> import_specification -> configurable_uri -> uri -> string_literal
1679
+ const libraryImport = node.namedChildren.find(c => c.type === 'library_import');
1680
+ if (libraryImport) {
1681
+ const importSpec = libraryImport.namedChildren.find((c) => c.type === 'import_specification');
1682
+ if (importSpec) {
1683
+ const configurableUri = importSpec.namedChildren.find((c) => c.type === 'configurable_uri');
1684
+ if (configurableUri) {
1685
+ const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1686
+ if (uri) {
1687
+ const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1688
+ if (stringLiteral) {
1689
+ moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1690
+ }
1691
+ }
1692
+ }
1693
+ }
1694
+ }
1695
+ // Also handle exports: export 'src/foo.dart';
1696
+ const libraryExport = node.namedChildren.find(c => c.type === 'library_export');
1697
+ if (libraryExport) {
1698
+ const configurableUri = libraryExport.namedChildren.find((c) => c.type === 'configurable_uri');
1699
+ if (configurableUri) {
1700
+ const uri = configurableUri.namedChildren.find((c) => c.type === 'uri');
1701
+ if (uri) {
1702
+ const stringLiteral = uri.namedChildren.find((c) => c.type === 'string_literal');
1703
+ if (stringLiteral) {
1704
+ moduleName = getNodeText(stringLiteral, this.source).replace(/['"]/g, '');
1705
+ }
1706
+ }
1707
+ }
1708
+ }
1709
+ if (moduleName) {
1710
+ this.createNode('import', moduleName, node, {
1711
+ signature: importText,
1712
+ });
1713
+ }
1714
+ return; // Dart handled completely above
1715
+ }
1716
+ else if (this.language === 'c' || this.language === 'cpp') {
1717
+ // C/C++ includes: #include <iostream>, #include "myheader.h"
1718
+ // AST: preproc_include -> system_lib_string (<...>) or string_literal ("...")
1719
+ // Check for system include: <path>
1720
+ const systemLib = node.namedChildren.find(c => c.type === 'system_lib_string');
1721
+ if (systemLib) {
1722
+ // Remove angle brackets: <iostream> -> iostream
1723
+ moduleName = getNodeText(systemLib, this.source).replace(/^<|>$/g, '');
1724
+ }
1725
+ else {
1726
+ // Check for local include: "path"
1727
+ const stringLiteral = node.namedChildren.find(c => c.type === 'string_literal');
1728
+ if (stringLiteral) {
1729
+ const stringContent = stringLiteral.namedChildren.find((c) => c.type === 'string_content');
1730
+ if (stringContent) {
1731
+ moduleName = getNodeText(stringContent, this.source);
1732
+ }
1733
+ }
1734
+ }
1735
+ if (moduleName) {
1736
+ this.createNode('import', moduleName, node, {
1737
+ signature: importText,
1738
+ });
1739
+ }
1740
+ return; // C/C++ handled completely above
1056
1741
  }
1057
1742
  else {
1058
- // Generic extraction
1743
+ // Generic extraction for other languages
1059
1744
  moduleName = importText;
1745
+ if (moduleName) {
1746
+ this.createNode('import', moduleName, node, {
1747
+ signature: importText,
1748
+ });
1749
+ }
1060
1750
  }
1751
+ // Keep unresolved reference creation for resolution purposes
1752
+ // This is used to resolve imports to their target files/modules
1061
1753
  if (moduleName && this.nodeStack.length > 0) {
1062
1754
  const parentId = this.nodeStack[this.nodeStack.length - 1];
1063
1755
  if (parentId) {
@@ -1155,7 +1847,9 @@ class TreeSitterExtractor {
1155
1847
  }
1156
1848
  }
1157
1849
  if (child.type === 'implements_clause' ||
1158
- child.type === 'class_interface_clause') {
1850
+ child.type === 'class_interface_clause' ||
1851
+ child.type === 'interfaces' // Dart
1852
+ ) {
1159
1853
  // Extract implemented interfaces
1160
1854
  for (let j = 0; j < child.namedChildCount; j++) {
1161
1855
  const iface = child.namedChild(j);
@@ -1213,6 +1907,7 @@ class LiquidExtractor {
1213
1907
  this.extractAssignments(fileNode.id);
1214
1908
  }
1215
1909
  catch (error) {
1910
+ (0, sentry_1.captureException)(error, { operation: 'liquid-extraction', filePath: this.filePath });
1216
1911
  this.errors.push({
1217
1912
  message: `Liquid extraction error: ${error instanceof Error ? error.message : String(error)}`,
1218
1913
  severity: 'error',
@@ -1256,8 +1951,31 @@ class LiquidExtractor {
1256
1951
  const renderRegex = /\{%[-]?\s*(render|include)\s+['"]([^'"]+)['"]/g;
1257
1952
  let match;
1258
1953
  while ((match = renderRegex.exec(this.source)) !== null) {
1259
- const [, tagType, snippetName] = match;
1954
+ const [fullMatch, tagType, snippetName] = match;
1260
1955
  const line = this.getLineNumber(match.index);
1956
+ // Create an import node for searchability
1957
+ const importNodeId = generateNodeId(this.filePath, 'import', snippetName, line);
1958
+ const importNode = {
1959
+ id: importNodeId,
1960
+ kind: 'import',
1961
+ name: snippetName,
1962
+ qualifiedName: `${this.filePath}::import:${snippetName}`,
1963
+ filePath: this.filePath,
1964
+ language: 'liquid',
1965
+ signature: fullMatch,
1966
+ startLine: line,
1967
+ endLine: line,
1968
+ startColumn: match.index - this.getLineStart(line),
1969
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
1970
+ updatedAt: Date.now(),
1971
+ };
1972
+ this.nodes.push(importNode);
1973
+ // Add containment edge from file to import
1974
+ this.edges.push({
1975
+ source: fileNodeId,
1976
+ target: importNodeId,
1977
+ kind: 'contains',
1978
+ });
1261
1979
  // Create a component node for the snippet reference
1262
1980
  const nodeId = generateNodeId(this.filePath, 'component', `${tagType}:${snippetName}`, line);
1263
1981
  const node = {
@@ -1270,7 +1988,7 @@ class LiquidExtractor {
1270
1988
  startLine: line,
1271
1989
  endLine: line,
1272
1990
  startColumn: match.index - this.getLineStart(line),
1273
- endColumn: match.index - this.getLineStart(line) + match[0].length,
1991
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
1274
1992
  updatedAt: Date.now(),
1275
1993
  };
1276
1994
  this.nodes.push(node);
@@ -1298,8 +2016,31 @@ class LiquidExtractor {
1298
2016
  const sectionRegex = /\{%[-]?\s*section\s+['"]([^'"]+)['"]/g;
1299
2017
  let match;
1300
2018
  while ((match = sectionRegex.exec(this.source)) !== null) {
1301
- const [, sectionName] = match;
2019
+ const [fullMatch, sectionName] = match;
1302
2020
  const line = this.getLineNumber(match.index);
2021
+ // Create an import node for searchability
2022
+ const importNodeId = generateNodeId(this.filePath, 'import', sectionName, line);
2023
+ const importNode = {
2024
+ id: importNodeId,
2025
+ kind: 'import',
2026
+ name: sectionName,
2027
+ qualifiedName: `${this.filePath}::import:${sectionName}`,
2028
+ filePath: this.filePath,
2029
+ language: 'liquid',
2030
+ signature: fullMatch,
2031
+ startLine: line,
2032
+ endLine: line,
2033
+ startColumn: match.index - this.getLineStart(line),
2034
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
2035
+ updatedAt: Date.now(),
2036
+ };
2037
+ this.nodes.push(importNode);
2038
+ // Add containment edge from file to import
2039
+ this.edges.push({
2040
+ source: fileNodeId,
2041
+ target: importNodeId,
2042
+ kind: 'contains',
2043
+ });
1303
2044
  // Create a component node for the section reference
1304
2045
  const nodeId = generateNodeId(this.filePath, 'component', `section:${sectionName}`, line);
1305
2046
  const node = {
@@ -1312,7 +2053,7 @@ class LiquidExtractor {
1312
2053
  startLine: line,
1313
2054
  endLine: line,
1314
2055
  startColumn: match.index - this.getLineStart(line),
1315
- endColumn: match.index - this.getLineStart(line) + match[0].length,
2056
+ endColumn: match.index - this.getLineStart(line) + fullMatch.length,
1316
2057
  updatedAt: Date.now(),
1317
2058
  };
1318
2059
  this.nodes.push(node);
@@ -1433,11 +2174,173 @@ class LiquidExtractor {
1433
2174
  }
1434
2175
  }
1435
2176
  exports.LiquidExtractor = LiquidExtractor;
2177
+ /**
2178
+ * SvelteExtractor - Extracts code relationships from Svelte component files
2179
+ *
2180
+ * Svelte files are multi-language (script + template + style). Rather than
2181
+ * parsing the full Svelte grammar, we extract the <script> block content
2182
+ * and delegate it to the TypeScript/JavaScript TreeSitterExtractor.
2183
+ *
2184
+ * Every .svelte file produces a component node (Svelte components are always importable).
2185
+ */
2186
+ class SvelteExtractor {
2187
+ filePath;
2188
+ source;
2189
+ nodes = [];
2190
+ edges = [];
2191
+ unresolvedReferences = [];
2192
+ errors = [];
2193
+ constructor(filePath, source) {
2194
+ this.filePath = filePath;
2195
+ this.source = source;
2196
+ }
2197
+ /**
2198
+ * Extract from Svelte source
2199
+ */
2200
+ extract() {
2201
+ const startTime = Date.now();
2202
+ try {
2203
+ // Create component node for the .svelte file itself
2204
+ const componentNode = this.createComponentNode();
2205
+ // Extract and process script blocks
2206
+ const scriptBlocks = this.extractScriptBlocks();
2207
+ for (const block of scriptBlocks) {
2208
+ this.processScriptBlock(block, componentNode.id);
2209
+ }
2210
+ }
2211
+ catch (error) {
2212
+ (0, sentry_1.captureException)(error, { operation: 'svelte-extraction', filePath: this.filePath });
2213
+ this.errors.push({
2214
+ message: `Svelte extraction error: ${error instanceof Error ? error.message : String(error)}`,
2215
+ severity: 'error',
2216
+ });
2217
+ }
2218
+ return {
2219
+ nodes: this.nodes,
2220
+ edges: this.edges,
2221
+ unresolvedReferences: this.unresolvedReferences,
2222
+ errors: this.errors,
2223
+ durationMs: Date.now() - startTime,
2224
+ };
2225
+ }
2226
+ /**
2227
+ * Create a component node for the .svelte file
2228
+ */
2229
+ createComponentNode() {
2230
+ const lines = this.source.split('\n');
2231
+ const fileName = this.filePath.split(/[/\\]/).pop() || this.filePath;
2232
+ const componentName = fileName.replace(/\.svelte$/, '');
2233
+ const id = generateNodeId(this.filePath, 'component', componentName, 1);
2234
+ const node = {
2235
+ id,
2236
+ kind: 'component',
2237
+ name: componentName,
2238
+ qualifiedName: `${this.filePath}::${componentName}`,
2239
+ filePath: this.filePath,
2240
+ language: 'svelte',
2241
+ startLine: 1,
2242
+ endLine: lines.length,
2243
+ startColumn: 0,
2244
+ endColumn: lines[lines.length - 1]?.length || 0,
2245
+ isExported: true, // Svelte components are always importable
2246
+ updatedAt: Date.now(),
2247
+ };
2248
+ this.nodes.push(node);
2249
+ return node;
2250
+ }
2251
+ /**
2252
+ * Extract <script> blocks from the Svelte source
2253
+ */
2254
+ extractScriptBlocks() {
2255
+ const blocks = [];
2256
+ const scriptRegex = /<script(\s[^>]*)?>(?<content>[\s\S]*?)<\/script>/g;
2257
+ let match;
2258
+ while ((match = scriptRegex.exec(this.source)) !== null) {
2259
+ const attrs = match[1] || '';
2260
+ const content = match.groups?.content || match[2] || '';
2261
+ // Detect TypeScript from lang attribute
2262
+ const isTypeScript = /lang\s*=\s*["'](ts|typescript)["']/.test(attrs);
2263
+ // Detect module script
2264
+ const isModule = /context\s*=\s*["']module["']/.test(attrs);
2265
+ // Calculate start line of the script content (line after <script>)
2266
+ const beforeScript = this.source.substring(0, match.index);
2267
+ const scriptTagLine = (beforeScript.match(/\n/g) || []).length;
2268
+ // The content starts on the line after the opening <script> tag
2269
+ const openingTag = match[0].substring(0, match[0].indexOf('>') + 1);
2270
+ const openingTagLines = (openingTag.match(/\n/g) || []).length;
2271
+ const contentStartLine = scriptTagLine + openingTagLines + 1; // 0-indexed line
2272
+ blocks.push({
2273
+ content,
2274
+ startLine: contentStartLine,
2275
+ isModule,
2276
+ isTypeScript,
2277
+ });
2278
+ }
2279
+ return blocks;
2280
+ }
2281
+ /**
2282
+ * Process a script block by delegating to TreeSitterExtractor
2283
+ */
2284
+ processScriptBlock(block, componentNodeId) {
2285
+ const scriptLanguage = block.isTypeScript ? 'typescript' : 'javascript';
2286
+ // Check if the script language parser is available
2287
+ if (!(0, grammars_1.isLanguageSupported)(scriptLanguage)) {
2288
+ this.errors.push({
2289
+ message: `Parser for ${scriptLanguage} not available, cannot parse Svelte script block`,
2290
+ severity: 'warning',
2291
+ });
2292
+ return;
2293
+ }
2294
+ // Delegate to TreeSitterExtractor
2295
+ const extractor = new TreeSitterExtractor(this.filePath, block.content, scriptLanguage);
2296
+ const result = extractor.extract();
2297
+ // Offset line numbers from script block back to .svelte file positions
2298
+ for (const node of result.nodes) {
2299
+ node.startLine += block.startLine;
2300
+ node.endLine += block.startLine;
2301
+ node.language = 'svelte'; // Mark as svelte, not TS/JS
2302
+ this.nodes.push(node);
2303
+ // Add containment edge from component to this node
2304
+ this.edges.push({
2305
+ source: componentNodeId,
2306
+ target: node.id,
2307
+ kind: 'contains',
2308
+ });
2309
+ }
2310
+ // Offset edges (they reference line numbers)
2311
+ for (const edge of result.edges) {
2312
+ if (edge.line) {
2313
+ edge.line += block.startLine;
2314
+ }
2315
+ this.edges.push(edge);
2316
+ }
2317
+ // Offset unresolved references
2318
+ for (const ref of result.unresolvedReferences) {
2319
+ ref.line += block.startLine;
2320
+ ref.filePath = this.filePath;
2321
+ ref.language = 'svelte';
2322
+ this.unresolvedReferences.push(ref);
2323
+ }
2324
+ // Carry over errors
2325
+ for (const error of result.errors) {
2326
+ if (error.line) {
2327
+ error.line += block.startLine;
2328
+ }
2329
+ this.errors.push(error);
2330
+ }
2331
+ }
2332
+ }
2333
+ exports.SvelteExtractor = SvelteExtractor;
1436
2334
  /**
1437
2335
  * Extract nodes and edges from source code
1438
2336
  */
1439
2337
  function extractFromSource(filePath, source, language) {
1440
2338
  const detectedLanguage = language || (0, grammars_1.detectLanguage)(filePath);
2339
+ // Use custom extractor for Svelte
2340
+ if (detectedLanguage === 'svelte') {
2341
+ const extractor = new SvelteExtractor(filePath, source);
2342
+ return extractor.extract();
2343
+ }
1441
2344
  // Use custom extractor for Liquid
1442
2345
  if (detectedLanguage === 'liquid') {
1443
2346
  const extractor = new LiquidExtractor(filePath, source);