raggiecode 0.2.1__py3-none-any.whl

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 (93) hide show
  1. Agent/__init__.py +0 -0
  2. Agent/agent.py +891 -0
  3. Agent/chat_history_db.py +1500 -0
  4. Agent/command.py +49 -0
  5. Agent/config.py +46 -0
  6. Agent/effort_levels.py +33 -0
  7. Agent/git_manager.py +727 -0
  8. Agent/tools.py +35 -0
  9. Commands/__init__.py +18 -0
  10. Commands/effort.py +42 -0
  11. Commands/global_todo.py +23 -0
  12. Commands/help.py +22 -0
  13. Commands/reasoning.py +24 -0
  14. Commands/redo.py +11 -0
  15. Commands/reindex.py +27 -0
  16. Commands/shell.py +28 -0
  17. Commands/stream.py +24 -0
  18. Commands/undo.py +13 -0
  19. Commands/unlimited_effort.py +8 -0
  20. Commands/window_size.py +29 -0
  21. RAG/__init__.py +0 -0
  22. RAG/document.py +119 -0
  23. RAG/find.py +408 -0
  24. RAG/graph.py +231 -0
  25. Tools/GetFileCodeStructure.py +43 -0
  26. Tools/GetSymbolSourceCode.py +27 -0
  27. Tools/__init__.py +39 -0
  28. Tools/ask_user.py +102 -0
  29. Tools/dispatch_subagent.py +215 -0
  30. Tools/document.py +35 -0
  31. Tools/edit_symbol.py +250 -0
  32. Tools/fuzzy_search.py +119 -0
  33. Tools/list_dir.py +51 -0
  34. Tools/read.py +49 -0
  35. Tools/read_image.py +75 -0
  36. Tools/remove.py +75 -0
  37. Tools/replace.py +305 -0
  38. Tools/search.py +41 -0
  39. Tools/shell.py +149 -0
  40. Tools/shell_kill.py +87 -0
  41. Tools/temp_background_service.py +113 -0
  42. Tools/todo_list.py +481 -0
  43. Tools/utils.py +116 -0
  44. Tools/view_changes.py +179 -0
  45. Tools/walk_call_tree.py +30 -0
  46. Tools/web_fetch.py +175 -0
  47. Tools/web_search.py +69 -0
  48. Tools/write.py +48 -0
  49. cli.py +111 -0
  50. config/__init__.py +0 -0
  51. config/coder_system_prompt.md +119 -0
  52. config/roles.json +43 -0
  53. config/tools.json +709 -0
  54. indexing/__init__.py +0 -0
  55. indexing/cli.py +128 -0
  56. indexing/code_index_sdk.py +832 -0
  57. indexing/code_indexer.py +1763 -0
  58. indexing/db_schema.py +396 -0
  59. indexing/export_to_json.py +346 -0
  60. indexing/extractors.py +189 -0
  61. indexing/file_utils.py +97 -0
  62. indexing/frontend/__init__.py +0 -0
  63. indexing/frontend/css_extractor.py +195 -0
  64. indexing/frontend/css_parser.py +387 -0
  65. indexing/frontend/css_selector_utils.py +226 -0
  66. indexing/frontend/edit_safety.py +573 -0
  67. indexing/frontend/graph.py +838 -0
  68. indexing/frontend/html_extractor.py +496 -0
  69. indexing/frontend/html_parser.py +314 -0
  70. indexing/frontend/jsx_extractor.py +1204 -0
  71. indexing/frontend/location_lookup.py +247 -0
  72. indexing/frontend/resolver.py +485 -0
  73. indexing/frontend/runtime_resolver.py +862 -0
  74. indexing/frontend/semantic_output.py +705 -0
  75. indexing/frontend/source_location.py +69 -0
  76. indexing/frontend_config.py +72 -0
  77. indexing/frontend_models.py +347 -0
  78. indexing/language_config.py +360 -0
  79. indexing/models.py +284 -0
  80. indexing/node_utils.py +1112 -0
  81. indexing/parse_worker.py +1082 -0
  82. indexing/queries.py +1542 -0
  83. indexing/sdk_examples.py +426 -0
  84. interactive.py +248 -0
  85. raggie.py +673 -0
  86. raggiecode-0.2.1.dist-info/METADATA +944 -0
  87. raggiecode-0.2.1.dist-info/RECORD +93 -0
  88. raggiecode-0.2.1.dist-info/WHEEL +5 -0
  89. raggiecode-0.2.1.dist-info/entry_points.txt +2 -0
  90. raggiecode-0.2.1.dist-info/top_level.txt +10 -0
  91. skills/__init__.py +3 -0
  92. skills/manager.py +114 -0
  93. skills/tool.py +121 -0
indexing/node_utils.py ADDED
@@ -0,0 +1,1112 @@
1
+ """
2
+ Utility functions for working with tree-sitter nodes.
3
+ """
4
+
5
+
6
+
7
+ def get_node_location(node):
8
+ """Get start and end line/column for a node."""
9
+ start_line, start_col = node.start_point
10
+ end_line, end_col = node.end_point
11
+ return {
12
+ "start_line": start_line + 1,
13
+ "start_column": start_col,
14
+ "end_line": end_line + 1,
15
+ "end_column": end_col,
16
+ "start_byte": node.start_byte,
17
+ "end_byte": node.end_byte
18
+ }
19
+
20
+
21
+ def extract_node_text(node, source_code):
22
+ """Extract the text content of a node from source code.
23
+
24
+ Accepts either a str (which will be encoded to UTF-8 each call) or bytes
25
+ (which is used directly, avoiding repeated whole-source encoding).
26
+ """
27
+ if isinstance(source_code, bytes):
28
+ return source_code[node.start_byte:node.end_byte].decode('utf-8')
29
+ source_bytes = source_code.encode('utf-8')
30
+ return source_bytes[node.start_byte:node.end_byte].decode('utf-8')
31
+
32
+
33
+ def extract_field_text(node, field_name, source_code):
34
+ """Extract text from a specific field of a node."""
35
+ field_node = node.child_by_field_name(field_name)
36
+ if field_node:
37
+ return extract_node_text(field_node, source_code)
38
+ return None
39
+
40
+
41
+ def extract_name(node, source_code, language=None):
42
+ """Extract the name from a node (function, class, variable, etc.)."""
43
+ # Try the standard "name" field first
44
+ name = extract_field_text(node, "name", source_code)
45
+ if name:
46
+ return name
47
+
48
+ # TypeScript/JavaScript specific: look for identifier children
49
+ # For function declarations, the name is typically the first identifier
50
+ # We need to be careful not to pick up identifiers from the function body
51
+ if node.type == "function_declaration":
52
+ # The name should be the first identifier child (before parameters)
53
+ for child in node.children:
54
+ # Kotlin uses simple_identifier
55
+ if child.type in ["identifier", "simple_identifier"]:
56
+ return extract_node_text(child, source_code)
57
+ # Stop if we've gone past the name (parameters start)
58
+ if child.type in ["formal_parameters", "function_value_parameters"]:
59
+ break
60
+ elif node.type == "method_declaration":
61
+ if language == "go":
62
+ # Go method: func (recv *Type) Name() - name is field_identifier
63
+ for child in node.children:
64
+ if child.type == "field_identifier":
65
+ return extract_node_text(child, source_code)
66
+ if child.type == "identifier":
67
+ return extract_node_text(child, source_code)
68
+ # C# method declaration - look for identifier
69
+ for child in node.children:
70
+ if child.type == "identifier":
71
+ return extract_node_text(child, source_code)
72
+ # Stop if we've gone past the name (parameters start)
73
+ if child.type == "parameter_list":
74
+ break
75
+ elif node.type == "class_declaration":
76
+ # For classes, look for type_identifier
77
+ for child in node.children:
78
+ if child.type == "type_identifier":
79
+ return extract_node_text(child, source_code)
80
+ if child.type == "class_heritage":
81
+ break
82
+ elif node.type in ("function_definition", "declaration") and language in ("cpp", "c"):
83
+ # C++ function_definition: name is inside function_declarator or declarator
84
+ for child in node.children:
85
+ if child.type in ("function_declarator", "declarator", "reference_declarator", "pointer_declarator"):
86
+ # Find the identifier or field_identifier inside the declarator
87
+ stack = [child]
88
+ while stack:
89
+ n = stack.pop()
90
+ if n.type in ("identifier", "field_identifier"):
91
+ return extract_node_text(n, source_code)
92
+ stack.extend(reversed(list(n.children)))
93
+ elif node.type == "class_specifier" and language == "cpp":
94
+ # C++ class_specifier: name is a type_identifier child
95
+ for child in node.children:
96
+ if child.type == "type_identifier":
97
+ return extract_node_text(child, source_code)
98
+ elif node.type == "type_declaration" and language == "go":
99
+ return extract_go_type_name(node, source_code)
100
+ elif node.type == "struct_specifier" and language in ("cpp", "c"):
101
+ # C/C++ struct_specifier: name is a type_identifier child
102
+ for child in node.children:
103
+ if child.type == "type_identifier":
104
+ return extract_node_text(child, source_code)
105
+ elif node.type == "enum_specifier" and language in ("cpp", "c"):
106
+ # C/C++ enum_specifier: name is a type_identifier child
107
+ for child in node.children:
108
+ if child.type == "type_identifier":
109
+ return extract_node_text(child, source_code)
110
+ elif node.type == "method_signature" and language == "dart":
111
+ # Dart method_signature wraps function_signature/getter_signature/etc.
112
+ # The name is inside the inner signature
113
+ for child in node.children:
114
+ if child.type in ("function_signature", "getter_signature", "setter_signature", "constructor_signature"):
115
+ for grandchild in child.children:
116
+ if grandchild.type == "identifier":
117
+ return extract_node_text(grandchild, source_code)
118
+ else:
119
+ # For other node types, look for the first identifier
120
+ # Kotlin uses simple_identifier
121
+ for child in node.children:
122
+ if child.type in ["identifier", "property_identifier", "type_identifier", "simple_identifier"]:
123
+ return extract_node_text(child, source_code)
124
+
125
+ return None
126
+
127
+
128
+ def extract_parameters(node, source_code, language):
129
+ """Extract parameter names from a function node."""
130
+ params = []
131
+ params_node = node.child_by_field_name("parameters")
132
+
133
+ # C# uses "parameter_list" as a child, not a field
134
+ if not params_node and language == "csharp":
135
+ for child in node.children:
136
+ if child.type == "parameter_list":
137
+ params_node = child
138
+ break
139
+
140
+ # Dart uses "formal_parameter_list" as a child
141
+ if not params_node and language == "dart":
142
+ for child in node.children:
143
+ if child.type == "formal_parameter_list":
144
+ params_node = child
145
+ break
146
+ # For method_signature, look inside the inner signature
147
+ if not params_node:
148
+ for child in node.children:
149
+ if child.type in ("function_signature", "getter_signature", "setter_signature", "constructor_signature"):
150
+ for grandchild in child.children:
151
+ if grandchild.type == "formal_parameter_list":
152
+ params_node = grandchild
153
+ break
154
+ break
155
+
156
+ if not params_node:
157
+ return params
158
+
159
+ # Language-specific parameter extraction
160
+ if language == "python":
161
+ for child in params_node.children:
162
+ if child.type == "identifier":
163
+ params.append(extract_node_text(child, source_code))
164
+ elif language in ["go", "rust", "cpp", "php"]:
165
+ for child in params_node.children:
166
+ if child.type in ["identifier", "parameter"]:
167
+ params.append(extract_node_text(child, source_code))
168
+ elif language in ["javascript", "typescript", "tsx", "csharp"]:
169
+ for child in params_node.children:
170
+ if child.type == "identifier":
171
+ params.append(extract_node_text(child, source_code))
172
+ elif language in ["java", "kotlin", "dart"]:
173
+ for child in params_node.children:
174
+ if child.type in ["identifier", "simple_identifier", "formal_parameter"]:
175
+ if child.type == "formal_parameter":
176
+ # Extract identifier from inside formal_parameter
177
+ for grandchild in child.children:
178
+ if grandchild.type in ["identifier", "simple_identifier"]:
179
+ params.append(extract_node_text(grandchild, source_code))
180
+ break
181
+ else:
182
+ params.append(extract_node_text(child, source_code))
183
+ elif language == "zig":
184
+ for child in params_node.children:
185
+ if child.type in ["identifier", "parameter"]:
186
+ params.append(extract_node_text(child, source_code))
187
+ elif language == "elixir":
188
+ for child in params_node.children:
189
+ if child.type in ["identifier", "atom"]:
190
+ params.append(extract_node_text(child, source_code))
191
+
192
+ return params
193
+
194
+
195
+ def extract_return_type(node, source_code):
196
+ """Extract return type from a function node."""
197
+ return extract_field_text(node, "return_type", source_code)
198
+
199
+
200
+ def is_method(node, language, class_node_type):
201
+ """Check if a function node is a method (inside a class)."""
202
+ if not node.parent:
203
+ return False
204
+ if not class_node_type:
205
+ return False
206
+ if isinstance(class_node_type, list):
207
+ return node.parent.type in class_node_type
208
+ return node.parent.type == class_node_type
209
+
210
+
211
+ def extract_base_classes(node, source_code, language):
212
+ """Extract base class names from a class node."""
213
+ base_classes = []
214
+
215
+ if language == "python":
216
+ arg_list = node.child_by_field_name("superclasses")
217
+ if arg_list:
218
+ for child in arg_list.children:
219
+ if child.type in ["identifier", "attribute"]:
220
+ base_classes.append(extract_node_text(child, source_code))
221
+ elif language in ["csharp", "cpp"]:
222
+ # C# base_list has no field name, find by type
223
+ base_list = None
224
+ for child in node.children:
225
+ if child.type == "base_list":
226
+ base_list = child
227
+ break
228
+ if not base_list:
229
+ base_list = node.child_by_field_name("bases")
230
+ if base_list:
231
+ for child in base_list.children:
232
+ if child.type in ["identifier", "type_identifier"]:
233
+ base_classes.append(extract_node_text(child, source_code))
234
+ elif language in ["javascript", "typescript", "tsx"]:
235
+ # TypeScript/JavaScript uses class_heritage as a child, not a field
236
+ heritage = None
237
+ for child in node.children:
238
+ if child.type == "class_heritage":
239
+ heritage = child
240
+ break
241
+
242
+ if heritage:
243
+ # Look for extends_clause or direct identifiers
244
+ for child in heritage.children:
245
+ if child.type == "extends_clause":
246
+ # Find the identifier inside extends_clause
247
+ for grandchild in child.children:
248
+ if grandchild.type in ["identifier", "type_identifier"]:
249
+ base_classes.append(extract_node_text(grandchild, source_code))
250
+ elif child.type in ["identifier", "type_identifier"]:
251
+ base_classes.append(extract_node_text(child, source_code))
252
+ elif language == "php":
253
+ # PHP uses "extends" keyword
254
+ base_class_node = node.child_by_field_name("base")
255
+ if base_class_node:
256
+ for child in base_class_node.children:
257
+ if child.type in ["identifier", "name"]:
258
+ base_classes.append(extract_node_text(child, source_code))
259
+ elif language == "java":
260
+ # Java: extends superclass, implements interfaces
261
+ superclass = node.child_by_field_name("superclass")
262
+ if superclass:
263
+ for child in superclass.children:
264
+ if child.type in ["type_identifier", "identifier"]:
265
+ base_classes.append(extract_node_text(child, source_code))
266
+ interfaces = node.child_by_field_name("super_interfaces")
267
+ if interfaces:
268
+ for child in interfaces.children:
269
+ if child.type in ["type_identifier", "identifier"]:
270
+ base_classes.append(extract_node_text(child, source_code))
271
+ elif language == "kotlin":
272
+ # Kotlin: superclass and super_type_list
273
+ for child in node.children:
274
+ if child.type in ["superclass", "delegation_specifier"]:
275
+ for grandchild in child.children:
276
+ if grandchild.type in ["type_identifier", "identifier", "user_type", "constructor_invocation"]:
277
+ base_classes.append(extract_node_text(grandchild, source_code))
278
+ elif language == "dart":
279
+ # Dart: extends and with (mixins), implements
280
+ for child in node.children:
281
+ if child.type in ["superclass", "with_clause", "implements_clause"]:
282
+ for grandchild in child.children:
283
+ if grandchild.type in ["type_identifier", "identifier", "mixin_identifier"]:
284
+ base_classes.append(extract_node_text(grandchild, source_code))
285
+ elif language == "rust":
286
+ # Rust traits can have supertraits
287
+ bounds = node.child_by_field_name("bounds")
288
+ if bounds:
289
+ for child in bounds.children:
290
+ if child.type in ["type_identifier", "identifier", "scoped_identifier"]:
291
+ base_classes.append(extract_node_text(child, source_code))
292
+
293
+ return base_classes
294
+
295
+
296
+ def extract_variable_name(node, source_code, language):
297
+ """Extract variable name from an assignment node."""
298
+ name = None
299
+
300
+ if language == "python":
301
+ left_node = node.child_by_field_name("left")
302
+ if left_node:
303
+ if left_node.type == "identifier":
304
+ name = extract_node_text(left_node, source_code)
305
+ elif left_node.type == "attribute":
306
+ return extract_node_text(left_node, source_code), True # Return as attribute
307
+ elif language in ["go", "rust"]:
308
+ name_node = node.child_by_field_name("name")
309
+ if name_node:
310
+ name = extract_node_text(name_node, source_code)
311
+ elif language == "c":
312
+ # C declaration: int x; int x = 5; static char *msg; int arr[1024];
313
+ # Look for any declarator child (init_declarator, array_declarator, etc.)
314
+ # and extract the identifier from within it.
315
+ declarator_types = {"init_declarator", "array_declarator", "pointer_declarator",
316
+ "function_declarator", "parenthesized_declarator"}
317
+ for child in node.children:
318
+ if child.type in declarator_types:
319
+ stack = [child]
320
+ while stack:
321
+ n = stack.pop()
322
+ if n.type == "identifier":
323
+ name = extract_node_text(n, source_code)
324
+ break
325
+ stack.extend(reversed(list(n.children)))
326
+ if name:
327
+ break
328
+ elif language in ["javascript", "typescript", "tsx", "csharp", "cpp", "php"]:
329
+ # Handle lexical_declaration / variable_declaration (const/let/var x = ...)
330
+ if node.type in ("lexical_declaration", "variable_declaration"):
331
+ for child in node.children:
332
+ if child.type == "variable_declarator":
333
+ name_node = child.child_by_field_name("name")
334
+ if name_node:
335
+ name = extract_node_text(name_node, source_code)
336
+ break
337
+ return name, False
338
+ left_node = node.child_by_field_name("left")
339
+ if left_node:
340
+ if left_node.type == "identifier":
341
+ name = extract_node_text(left_node, source_code)
342
+ elif left_node.type == "member_expression":
343
+ return extract_node_text(left_node, source_code), True # Return as attribute
344
+ elif language in ["java", "dart"]:
345
+ left_node = node.child_by_field_name("left")
346
+ if left_node:
347
+ if left_node.type in ["identifier", "simple_identifier"]:
348
+ name = extract_node_text(left_node, source_code)
349
+ elif left_node.type in ["member_expression", "field_access_expression", "navigation_expression"]:
350
+ return extract_node_text(left_node, source_code), True
351
+ elif language == "kotlin":
352
+ # Kotlin assignment: directly_assignable_expression wraps simple_identifier
353
+ for child in node.children:
354
+ if child.type == "directly_assignable_expression":
355
+ for grandchild in child.children:
356
+ if grandchild.type in ["simple_identifier", "identifier"]:
357
+ name = extract_node_text(grandchild, source_code)
358
+ break
359
+ break
360
+ elif child.type in ["simple_identifier", "identifier"]:
361
+ name = extract_node_text(child, source_code)
362
+ break
363
+ elif language == "zig":
364
+ # Zig variable_declaration: const/var identifier = value
365
+ # The identifier is a direct child, not a 'left' field
366
+ for child in node.children:
367
+ if child.type == "identifier":
368
+ name = extract_node_text(child, source_code)
369
+ break
370
+ elif language == "elixir":
371
+ # Elixir: binary_operator with = is an assignment
372
+ # Only treat as assignment if operator is "="
373
+ op_node = node.child_by_field_name("operator")
374
+ if op_node and extract_node_text(op_node, source_code) == "=":
375
+ left_node = node.child_by_field_name("left")
376
+ if left_node and left_node.type == "identifier":
377
+ name = extract_node_text(left_node, source_code)
378
+
379
+ return name, False
380
+
381
+
382
+ def extract_docstring(node, source_code, language):
383
+ """Extract docstring from a node if present."""
384
+ if language == "python":
385
+ for child in node.children:
386
+ if child.type == "block":
387
+ for grandchild in child.children:
388
+ if grandchild.type == "expression_statement":
389
+ string_node = None
390
+ for gc in grandchild.children:
391
+ if gc.type == "string":
392
+ string_node = gc
393
+ break
394
+ if string_node:
395
+ docstring = extract_node_text(string_node, source_code)
396
+ if docstring.startswith('"""') or docstring.startswith("'''"):
397
+ return docstring[3:-3]
398
+ elif docstring.startswith('"') or docstring.startswith("'"):
399
+ return docstring[1:-1]
400
+ # Other languages would need comment extraction logic
401
+ return None
402
+
403
+
404
+ def extract_go_receiver(node, source_code):
405
+ """Extract receiver information from a Go method declaration."""
406
+ receiver_node = node.child_by_field_name("receiver")
407
+ if receiver_node:
408
+ return extract_node_text(receiver_node, source_code)
409
+ return None
410
+
411
+
412
+ def extract_go_type_name(node, source_code):
413
+ """Extract type name from a Go type_declaration node (struct or interface)."""
414
+ # Go type_declaration has a "name" field for the type identifier
415
+ name_node = node.child_by_field_name("name")
416
+ if name_node:
417
+ return extract_node_text(name_node, source_code)
418
+
419
+ # Look inside type_spec for the type_identifier
420
+ for child in node.children:
421
+ if child.type == "type_spec":
422
+ for grandchild in child.children:
423
+ if grandchild.type == "type_identifier":
424
+ return extract_node_text(grandchild, source_code)
425
+ if child.type == "type_identifier":
426
+ return extract_node_text(child, source_code)
427
+
428
+ return None
429
+
430
+
431
+ def extract_go_type_kind(node, source_code):
432
+ """Determine if a Go type_declaration is a struct, interface, or type alias."""
433
+ # Look for type_spec child which contains the actual type info
434
+ type_spec = None
435
+ for child in node.children:
436
+ if child.type == "type_spec":
437
+ type_spec = child
438
+ break
439
+
440
+ if not type_spec:
441
+ # Fallback: try field name
442
+ type_spec = node.child_by_field_name("type")
443
+
444
+ if not type_spec:
445
+ return "type_alias"
446
+
447
+ # Check the type body to determine kind
448
+ for child in type_spec.children:
449
+ if child.type == "struct_type":
450
+ return "struct"
451
+ elif child.type == "interface_type":
452
+ return "interface"
453
+
454
+ return "type_alias"
455
+
456
+
457
+ def count_branches(node, language, source_code=None):
458
+ """Count the number of conditional branches in a function body (iterative)."""
459
+ branch_count = 0
460
+
461
+ branch_types = {
462
+ 'python': ['if_statement', 'for_statement', 'while_statement', 'match_statement', 'try_statement'],
463
+ 'go': ['if_statement', 'for_statement', 'for_range_clause', 'switch_statement', 'select_statement'],
464
+ 'javascript': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement', 'do_statement'],
465
+ 'typescript': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement'],
466
+ 'tsx': ['if_statement', 'for_statement', 'for_in_statement', 'for_of_statement', 'while_statement', 'switch_statement', 'try_statement'],
467
+ 'csharp': ['if_statement', 'for_statement', 'foreach_statement', 'while_statement', 'switch_statement', 'try_statement'],
468
+ 'rust': ['if_expression', 'for_expression', 'while_expression', 'loop_expression', 'match_expression', 'if_let_expression', 'while_let_expression'],
469
+ 'cpp': ['if_statement', 'for_statement', 'range_based_for_statement', 'while_statement', 'switch_statement', 'try_statement', 'do_statement'],
470
+ 'zig': ['if_statement', 'for_statement', 'while_statement', 'switch_statement'],
471
+ 'elixir': ['call'],
472
+ 'php': ['if_statement', 'for_statement', 'foreach_statement', 'while_statement', 'switch_statement', 'try_statement', 'match_expression'],
473
+ 'dart': ['if_statement', 'for_statement', 'while_statement', 'switch_statement', 'try_statement'],
474
+ 'java': ['if_statement', 'for_statement', 'enhanced_for_statement', 'while_statement', 'switch_statement', 'try_statement', 'try_with_resources_statement', 'do_statement'],
475
+ 'kotlin': ['if_expression', 'for_statement', 'while_statement', 'do_while_statement', 'when_expression', 'try_expression']
476
+ }
477
+
478
+ lang_branch_types = branch_types.get(language, ['if_statement', 'for_statement', 'while_statement', 'switch_statement'])
479
+
480
+ elixir_branch_keywords = {'if', 'case', 'cond', 'try', 'receive', 'for', 'with', 'unless'}
481
+
482
+ stack = [node]
483
+ while stack:
484
+ n = stack.pop()
485
+ if n.type in lang_branch_types:
486
+ if language == 'elixir' and n.type == 'call':
487
+ first_ident = None
488
+ for child in n.children:
489
+ if child.type == 'identifier':
490
+ first_ident = extract_node_text(child, source_code)
491
+ break
492
+ if first_ident in elixir_branch_keywords:
493
+ branch_count += 1
494
+ else:
495
+ branch_count += 1
496
+ stack.extend(reversed(list(n.children)))
497
+
498
+ return branch_count
499
+
500
+
501
+ def extract_imports(node, source_code, language, root_dir=None):
502
+ """Extract import statements from a file (iterative)."""
503
+ imports = []
504
+
505
+ import_types = {
506
+ 'python': ['import_statement', 'import_from_statement'],
507
+ 'go': ['import_declaration'],
508
+ 'javascript': ['import_statement', 'import_declaration'],
509
+ 'typescript': ['import_statement', 'import_declaration'],
510
+ 'tsx': ['import_statement', 'import_declaration'],
511
+ 'csharp': ['using_directive', 'global_using_directive'],
512
+ 'rust': ['use_declaration', 'extern_crate_declaration'],
513
+ 'cpp': ['include_directive', 'preproc_include'],
514
+ 'c': ['include_directive', 'preproc_include'],
515
+ 'zig': ['builtin_function'],
516
+ 'elixir': ['alias'],
517
+ 'php': ['include_expression', 'include_once_expression', 'require_expression', 'require_once_expression', 'namespace_use_declaration'],
518
+ 'dart': ['import_or_export'],
519
+ 'java': ['import_declaration'],
520
+ 'kotlin': ['import_header']
521
+ }
522
+
523
+ lang_import_types = import_types.get(language, ['import_statement'])
524
+
525
+ stack = [node]
526
+ while stack:
527
+ n = stack.pop()
528
+ if n.type in lang_import_types:
529
+ # Zig: only @import builtin_function calls are imports
530
+ if language == "zig" and n.type == "builtin_function":
531
+ text = extract_node_text(n, source_code)
532
+ if not text.startswith("@import"):
533
+ stack.extend(reversed(list(n.children)))
534
+ continue
535
+ # Go: import_declaration may contain import_spec_list with multiple import_spec
536
+ if language == "go" and n.type == "import_declaration":
537
+ specs = []
538
+ for child in n.children:
539
+ if child.type == "import_spec_list":
540
+ for spec in child.children:
541
+ if spec.type == "import_spec":
542
+ specs.append(spec)
543
+ if specs:
544
+ for spec in specs:
545
+ import_text = extract_node_text(spec, source_code)
546
+ is_external = True
547
+ if root_dir:
548
+ is_external = _is_external_import(import_text, language, root_dir)
549
+ imports.append({
550
+ 'name': import_text,
551
+ 'location': get_node_location(spec),
552
+ 'is_external': is_external
553
+ })
554
+ continue
555
+ import_text = extract_node_text(n, source_code)
556
+ is_external = True
557
+ if root_dir:
558
+ is_external = _is_external_import(import_text, language, root_dir)
559
+ imports.append({
560
+ 'name': import_text,
561
+ 'location': get_node_location(n),
562
+ 'is_external': is_external
563
+ })
564
+ stack.extend(reversed(list(n.children)))
565
+
566
+ return imports
567
+
568
+
569
+ def _is_external_import(import_text, language, root_dir):
570
+ """Determine if an import is external (not part of the codebase)."""
571
+ from pathlib import Path
572
+
573
+ root_path = Path(root_dir)
574
+
575
+ if language == 'python':
576
+ # Extract the base module name from the import
577
+ # e.g., "import os.path" -> "os", "from myapp.models import User" -> "myapp"
578
+ if import_text.startswith('from '):
579
+ # "from myapp.models import User"
580
+ parts = import_text.split()
581
+ if len(parts) >= 2:
582
+ module_name = parts[1].split('.')[0]
583
+ else:
584
+ return True
585
+ else:
586
+ # "import os" or "import os.path"
587
+ parts = import_text.split()
588
+ if len(parts) >= 2:
589
+ module_name = parts[1].split('.')[0]
590
+ else:
591
+ return True
592
+
593
+ # Check if this module exists in the codebase
594
+ module_path = root_path / module_name
595
+ if module_path.exists() and (module_path.is_dir() or module_path.with_suffix('.py').exists()):
596
+ return False # Internal
597
+
598
+ # Also check for __init__.py
599
+ init_path = module_path / '__init__.py'
600
+ if init_path.exists():
601
+ return False # Internal
602
+
603
+ elif language == 'go':
604
+ # Go imports are usually full paths like "github.com/user/repo/module"
605
+ # Check if it's a relative import or local package
606
+ if import_text.startswith('"'):
607
+ import_path = import_text.strip('"')
608
+ else:
609
+ import_path = import_text
610
+
611
+ # Relative imports are internal
612
+ if import_path.startswith('.'):
613
+ return False
614
+
615
+ # Check if it matches a directory in the codebase
616
+ module_name = import_path.split('/')[-1]
617
+ if (root_path / module_name).exists():
618
+ return False
619
+
620
+ elif language in ['javascript', 'typescript', 'tsx']:
621
+ # Extract the module path from the import statement
622
+ # e.g. import { foo } from "./local"; -> ./local
623
+ import_path = None
624
+ if 'from ' in import_text:
625
+ # import { x } from "path"
626
+ parts = import_text.split('from ')
627
+ if len(parts) >= 2:
628
+ path_part = parts[-1].strip().rstrip(';').strip()
629
+ import_path = path_part.strip('"\'`')
630
+ elif import_text.startswith('import '):
631
+ # import "path" or import "path";
632
+ path_part = import_text[len('import '):].strip().rstrip(';').strip()
633
+ import_path = path_part.strip('"\'`')
634
+
635
+ if import_path:
636
+ if import_path.startswith('./') or import_path.startswith('../'):
637
+ return False
638
+ if not import_path.startswith('.') and not import_path.startswith('@'):
639
+ module_path = root_path / import_path
640
+ if module_path.exists() or (root_path / f"{import_path}.js").exists() or (root_path / f"{import_path}.ts").exists() or (root_path / f"{import_path}.tsx").exists():
641
+ return False
642
+
643
+ elif language == 'rust':
644
+ # Rust uses crate:: for internal, external packages are just names
645
+ if 'crate::' in import_text or import_text.startswith('super::') or import_text.startswith('self::'):
646
+ return False
647
+
648
+ elif language in ['cpp', 'c']:
649
+ # #include "local.h" is internal, #include <system.h> is external
650
+ if import_text.startswith('#include "'):
651
+ return False # Local include
652
+
653
+ elif language == 'csharp':
654
+ # using System; is external, using MyProject.Models; is internal
655
+ if import_text.startswith('using '):
656
+ namespace = import_text[6:].strip().rstrip(';')
657
+ # Check if it's a standard .NET namespace
658
+ standard_namespaces = ['System', 'Microsoft', 'Newtonsoft', 'Serilog']
659
+ if any(namespace.startswith(std) for std in standard_namespaces):
660
+ return True
661
+ # Check if it matches a directory in the codebase
662
+ if (root_path / namespace.replace('.', '/')).exists():
663
+ return False
664
+
665
+ elif language == 'php':
666
+ # PHP: include/require with relative paths are internal
667
+ if import_text.startswith(('include ', 'include_once ', 'require ', 'require_once ')):
668
+ # Extract the path
669
+ parts = import_text.split()
670
+ if len(parts) >= 2:
671
+ path = parts[1].strip('"\'();')
672
+ # Relative paths are internal
673
+ if path.startswith('./') or path.startswith('../'):
674
+ return False
675
+ # Check if it exists in the codebase
676
+ if (root_path / path).exists():
677
+ return False
678
+ elif import_text.startswith('use '):
679
+ # use statements for namespaces
680
+ namespace = import_text[4:].strip().rstrip(';')
681
+ # Check if it's a standard PHP namespace
682
+ standard_namespaces = ['PHP', 'Symfony', 'Laravel', 'Doctrine']
683
+ if any(namespace.startswith(std) for std in standard_namespaces):
684
+ return True
685
+ # Check if it matches a directory in the codebase
686
+ if (root_path / namespace.replace('\\', '/')).exists():
687
+ return False
688
+
689
+ elif language == 'dart':
690
+ # Dart: package: imports are external, relative imports are internal
691
+ if import_text.startswith('import '):
692
+ # Extract the URI
693
+ if "'" in import_text:
694
+ uri = import_text.split("'")[1]
695
+ elif '"' in import_text:
696
+ uri = import_text.split('"')[1]
697
+ else:
698
+ return True
699
+ if uri.startswith('dart:') or uri.startswith('package:'):
700
+ return True
701
+ if uri.startswith('./') or uri.startswith('../') or uri.startswith('/'):
702
+ return False
703
+ # Check if it exists in the codebase
704
+ if (root_path / uri).exists():
705
+ return False
706
+
707
+ elif language == 'java':
708
+ # Java: import com.example.* is internal if com/example exists
709
+ if import_text.startswith('import '):
710
+ parts = import_text.split()
711
+ if len(parts) >= 2:
712
+ import_path = parts[1].rstrip(';').replace('.', '/')
713
+ if (root_path / import_path).exists() or (root_path / (import_path + '.java')).exists():
714
+ return False
715
+
716
+ elif language == 'kotlin':
717
+ # Kotlin: import is internal if the path exists in the codebase
718
+ if import_text.startswith('import '):
719
+ parts = import_text.split()
720
+ if len(parts) >= 2:
721
+ import_path = parts[1].replace('.', '/')
722
+ if (root_path / import_path).exists() or (root_path / (import_path + '.kt')).exists():
723
+ return False
724
+
725
+ elif language == 'zig':
726
+ # Zig: @import("file.zig") is internal if the file exists
727
+ if '@import' in import_text:
728
+ if '"' in import_text:
729
+ path = import_text.split('"')[1]
730
+ elif "'" in import_text:
731
+ path = import_text.split("'")[1]
732
+ else:
733
+ return True
734
+ if path.startswith('std') or path.startswith('builtin') or path.startswith('root'):
735
+ return True
736
+ if (root_path / path).exists():
737
+ return False
738
+
739
+ elif language == 'elixir':
740
+ # Elixir: alias MyApp.Foo is internal if lib/my_app/foo.ex exists
741
+ # The import text is just the module path (e.g. "Plug.Conn")
742
+ module_name = import_text.strip().rstrip('.')
743
+ if module_name:
744
+ import re
745
+ s1 = re.sub('([a-z0-9])([A-Z])', r'\1_\2', module_name)
746
+ s2 = re.sub('([A-Z]+)([A-Z][a-z])', r'\1_\2', s1)
747
+ snake = s2.lower()
748
+ base_path = root_path / 'lib' / snake.replace('.', '/')
749
+ if base_path.exists() or base_path.with_suffix('.ex').exists():
750
+ return False
751
+
752
+ return True # Default to external
753
+
754
+
755
+ def extract_function_calls(node, source_code, language):
756
+ """Extract function calls from a node (iterative to avoid recursion limits).
757
+
758
+ Handles various call patterns:
759
+ - Direct calls: func()
760
+ - Method calls: obj.method(), self.method()
761
+ - Chained calls: obj.method().another()
762
+ - Attribute calls: module.func()
763
+ - Class instantiations: ClassName() (captured as function_call for dependency tracking)
764
+ """
765
+ calls = []
766
+
767
+ call_types = {
768
+ 'python': ['call'],
769
+ 'go': ['call_expression'],
770
+ 'javascript': ['call_expression'],
771
+ 'typescript': ['call_expression'],
772
+ 'tsx': ['call_expression'],
773
+ 'csharp': ['invocation_expression'],
774
+ 'rust': ['call_expression'],
775
+ 'cpp': ['call_expression'],
776
+ 'c': ['call_expression'],
777
+ 'zig': ['call_expression'],
778
+ 'elixir': ['call'],
779
+ 'php': ['function_call_expression'],
780
+ 'dart': ['expression_statement'],
781
+ 'java': ['method_invocation', 'object_creation_expression'],
782
+ 'kotlin': ['call_expression']
783
+ }
784
+
785
+ lang_call_types = call_types.get(language, ['call_expression'])
786
+
787
+ stack = [node]
788
+ while stack:
789
+ n = stack.pop()
790
+ if n.type in lang_call_types:
791
+ func_name = None
792
+ dep_type = 'function_call'
793
+
794
+ # Dart: expression_statement with identifier + selector(arguments)
795
+ if language == 'dart' and n.type == 'expression_statement':
796
+ has_args = False
797
+ method_name = None
798
+ first_ident = None
799
+ for child in n.children:
800
+ if child.type == 'identifier' and first_ident is None:
801
+ first_ident = extract_node_text(child, source_code)
802
+ elif child.type == 'selector':
803
+ for sel_child in child.children:
804
+ if sel_child.type == 'argument_part':
805
+ for arg_child in sel_child.children:
806
+ if arg_child.type == 'arguments':
807
+ has_args = True
808
+ break
809
+ elif sel_child.type == 'unconditional_assignable_selector':
810
+ for us_child in sel_child.children:
811
+ if us_child.type == 'identifier':
812
+ method_name = extract_node_text(us_child, source_code)
813
+ if has_args:
814
+ if method_name:
815
+ func_name = method_name
816
+ dep_type = 'method_call'
817
+ elif first_ident:
818
+ func_name = first_ident
819
+ dep_type = 'function_call'
820
+ if func_name:
821
+ calls.append({
822
+ 'name': func_name,
823
+ 'location': get_node_location(n),
824
+ 'dependency_type': dep_type
825
+ })
826
+ continue
827
+
828
+ # Try to extract the function name from the call expression
829
+ for child in n.children:
830
+ # Direct identifier: func() or ClassName()
831
+ if child.type in ['identifier', 'simple_identifier']:
832
+ func_name = extract_node_text(child, source_code)
833
+ dep_type = 'function_call'
834
+ break
835
+ # Attribute access: obj.method or module.func
836
+ elif child.type in ['attribute', 'member_expression', 'field_expression', 'member_access_expression', 'field_access_expression', 'navigation_expression']:
837
+ func_name = extract_node_text(child, source_code)
838
+ dep_type = 'method_call'
839
+ break
840
+ # Selector expression (Go): obj.method()
841
+ elif child.type == 'selector_expression':
842
+ func_name = extract_node_text(child, source_code)
843
+ dep_type = 'method_call'
844
+ break
845
+ # Chained calls: obj.method().another()
846
+ elif child.type == 'call':
847
+ # This is a nested call, extract from the inner call
848
+ for grandchild in child.children:
849
+ if grandchild.type in ['identifier', 'simple_identifier', 'attribute', 'member_expression', 'field_access_expression', 'navigation_expression']:
850
+ func_name = extract_node_text(grandchild, source_code)
851
+ dep_type = 'method_call' if grandchild.type not in ['identifier', 'simple_identifier'] else 'function_call'
852
+ break
853
+ if func_name:
854
+ break
855
+
856
+ if func_name:
857
+ calls.append({
858
+ 'name': func_name,
859
+ 'location': get_node_location(n),
860
+ 'dependency_type': dep_type
861
+ })
862
+ stack.extend(reversed(list(n.children)))
863
+
864
+ return calls
865
+
866
+
867
+ def extract_class_references(node, source_code, language):
868
+ """Extract class references (instantiations, type annotations, etc.) from a node (iterative)."""
869
+ references = []
870
+
871
+ stack = [node]
872
+ while stack:
873
+ n = stack.pop()
874
+
875
+ if language == 'python':
876
+ if n.type == 'call':
877
+ for child in n.children:
878
+ if child.type == 'identifier':
879
+ name = extract_node_text(child, source_code)
880
+ if name and name[0].isupper():
881
+ references.append({
882
+ 'name': name,
883
+ 'location': get_node_location(child),
884
+ 'dependency_type': 'class_reference'
885
+ })
886
+ break
887
+ elif n.type == 'type':
888
+ for child in n.children:
889
+ if child.type == 'identifier' or child.type == 'type':
890
+ name = extract_node_text(child, source_code)
891
+ if name and name[0].isupper():
892
+ references.append({
893
+ 'name': name,
894
+ 'location': get_node_location(child),
895
+ 'dependency_type': 'class_reference'
896
+ })
897
+
898
+ elif language in ['javascript', 'typescript', 'tsx']:
899
+ if n.type == 'new_expression':
900
+ for child in n.children:
901
+ if child.type in ['identifier', 'member_expression']:
902
+ name = extract_node_text(child, source_code)
903
+ if name:
904
+ references.append({
905
+ 'name': name,
906
+ 'location': get_node_location(child),
907
+ 'dependency_type': 'class_reference'
908
+ })
909
+ break
910
+ elif n.type == 'type_annotation':
911
+ for child in n.children:
912
+ if child.type in ['identifier', 'type_identifier']:
913
+ name = extract_node_text(child, source_code)
914
+ if name and name[0].isupper():
915
+ references.append({
916
+ 'name': name,
917
+ 'location': get_node_location(child),
918
+ 'dependency_type': 'class_reference'
919
+ })
920
+
921
+ elif language == 'csharp':
922
+ if n.type == 'object_creation_expression':
923
+ for child in n.children:
924
+ if child.type == 'identifier':
925
+ name = extract_node_text(child, source_code)
926
+ if name:
927
+ references.append({
928
+ 'name': name,
929
+ 'location': get_node_location(child),
930
+ 'dependency_type': 'class_reference'
931
+ })
932
+ break
933
+
934
+ elif language == 'go':
935
+ if n.type == 'composite_literal':
936
+ for child in n.children:
937
+ if child.type == 'identifier' or child.type == 'selector_expression':
938
+ name = extract_node_text(child, source_code)
939
+ if name and name[0].isupper():
940
+ references.append({
941
+ 'name': name,
942
+ 'location': get_node_location(child),
943
+ 'dependency_type': 'class_reference'
944
+ })
945
+ break
946
+
947
+ elif language == 'rust':
948
+ if n.type == 'call_expression':
949
+ for child in n.children:
950
+ if child.type == 'identifier' or child.type == 'scoped_identifier':
951
+ name = extract_node_text(child, source_code)
952
+ if name and name[0].isupper():
953
+ references.append({
954
+ 'name': name,
955
+ 'location': get_node_location(child),
956
+ 'dependency_type': 'class_reference'
957
+ })
958
+ break
959
+
960
+ elif language == 'php':
961
+ if n.type == 'new_expression':
962
+ for child in n.children:
963
+ if child.type in ['identifier', 'name']:
964
+ name = extract_node_text(child, source_code)
965
+ if name and name[0].isupper():
966
+ references.append({
967
+ 'name': name,
968
+ 'location': get_node_location(child),
969
+ 'dependency_type': 'class_reference'
970
+ })
971
+ break
972
+
973
+ elif language == 'cpp':
974
+ if n.type == 'new_expression':
975
+ for child in n.children:
976
+ if child.type in ['identifier', 'type_identifier', 'qualified_identifier']:
977
+ name = extract_node_text(child, source_code)
978
+ if name:
979
+ references.append({
980
+ 'name': name,
981
+ 'location': get_node_location(child),
982
+ 'dependency_type': 'class_reference'
983
+ })
984
+ break
985
+ elif n.type == 'class_specifier':
986
+ pass # Skip class definitions themselves
987
+
988
+ elif language == 'c':
989
+ if n.type == 'call_expression':
990
+ for child in n.children:
991
+ if child.type == 'identifier':
992
+ name = extract_node_text(child, source_code)
993
+ if name and name[0].isupper():
994
+ references.append({
995
+ 'name': name,
996
+ 'location': get_node_location(child),
997
+ 'dependency_type': 'class_reference'
998
+ })
999
+ break
1000
+
1001
+ elif language == 'dart':
1002
+ if n.type in ('constructor_invocation', 'const_object_expression'):
1003
+ for child in n.children:
1004
+ if child.type in ['identifier', 'type_identifier']:
1005
+ name = extract_node_text(child, source_code)
1006
+ if name and name[0].isupper():
1007
+ references.append({
1008
+ 'name': name,
1009
+ 'location': get_node_location(child),
1010
+ 'dependency_type': 'class_reference'
1011
+ })
1012
+ break
1013
+ elif n.type == 'type_annotation':
1014
+ for child in n.children:
1015
+ if child.type in ['identifier', 'type_identifier']:
1016
+ name = extract_node_text(child, source_code)
1017
+ if name and name[0].isupper():
1018
+ references.append({
1019
+ 'name': name,
1020
+ 'location': get_node_location(child),
1021
+ 'dependency_type': 'class_reference'
1022
+ })
1023
+
1024
+ elif language == 'java':
1025
+ if n.type == 'object_creation_expression':
1026
+ for child in n.children:
1027
+ if child.type in ['type_identifier', 'identifier']:
1028
+ name = extract_node_text(child, source_code)
1029
+ if name:
1030
+ references.append({
1031
+ 'name': name,
1032
+ 'location': get_node_location(child),
1033
+ 'dependency_type': 'class_reference'
1034
+ })
1035
+ break
1036
+
1037
+ elif language == 'kotlin':
1038
+ if n.type == 'constructor_invocation' or n.type == 'call_expression':
1039
+ for child in n.children:
1040
+ if child.type in ['type_identifier', 'identifier', 'simple_identifier', 'user_type']:
1041
+ name = extract_node_text(child, source_code)
1042
+ if name and name[0].isupper():
1043
+ references.append({
1044
+ 'name': name,
1045
+ 'location': get_node_location(child),
1046
+ 'dependency_type': 'class_reference'
1047
+ })
1048
+ break
1049
+
1050
+ stack.extend(reversed(list(n.children)))
1051
+
1052
+ return references
1053
+
1054
+
1055
+ def extract_variable_references(node, source_code, language):
1056
+ """Extract variable references from a node (iterative to avoid recursion limits)."""
1057
+ references = []
1058
+
1059
+ skip_types = {'function_definition', 'class_definition', 'function_declaration',
1060
+ 'class_declaration', 'method_declaration', 'parameter', 'assignment',
1061
+ 'variable_declaration',
1062
+ 'function_signature', 'method_signature', 'constructor_declaration',
1063
+ 'function_item', 'struct_item', 'enum_item', 'trait_item'}
1064
+
1065
+ stack = [node]
1066
+ while stack:
1067
+ n = stack.pop()
1068
+
1069
+ if n.type in skip_types:
1070
+ continue
1071
+
1072
+ if n.type in ['identifier', 'simple_identifier']:
1073
+ name = extract_node_text(n, source_code)
1074
+ if name and (name[0].islower() or name[0] == '_'):
1075
+ references.append({
1076
+ 'name': name,
1077
+ 'location': get_node_location(n),
1078
+ 'dependency_type': 'variable_reference'
1079
+ })
1080
+
1081
+ stack.extend(reversed(list(n.children)))
1082
+
1083
+ return references
1084
+
1085
+
1086
+ def create_parser(language_module):
1087
+ """Create a tree-sitter parser for a language."""
1088
+ from tree_sitter import Language, Parser
1089
+
1090
+ # Already a Language object (e.g., from tree-sitter-language-pack)
1091
+ if isinstance(language_module, Language):
1092
+ return Parser(language_module)
1093
+
1094
+ # Callable that returns the language pointer
1095
+ if callable(language_module):
1096
+ lang_obj = language_module()
1097
+
1098
+ # New API: PyCapsule — wrap in Language() then pass to Parser
1099
+ if isinstance(lang_obj, int):
1100
+ # Old-style int pointer — try tree_sitter_language_pack to avoid deprecation
1101
+ # This shouldn't normally happen if language_config uses the right modules
1102
+ return Parser(Language(lang_obj))
1103
+
1104
+ # PyCapsule (tree-sitter >= 0.22) or Language object
1105
+ if isinstance(lang_obj, Language):
1106
+ return Parser(lang_obj)
1107
+
1108
+ # PyCapsule — wrap in Language()
1109
+ return Parser(Language(lang_obj))
1110
+
1111
+ # Fallback: treat as raw pointer
1112
+ return Parser(Language(language_module))