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
@@ -0,0 +1,1082 @@
1
+ """
2
+ Parallel file parsing worker for the code indexer.
3
+ Runs in separate processes to parallelize tree-sitter parsing.
4
+ """
5
+
6
+ from pathlib import Path
7
+
8
+ from indexing.language_config import LANGUAGE_CONFIG, get_language_for_extension, get_node_types
9
+ from indexing.node_utils import (
10
+ create_parser,
11
+ extract_imports,
12
+ extract_function_calls,
13
+ extract_class_references,
14
+ extract_variable_references,
15
+ extract_node_text,
16
+ get_node_location,
17
+ count_branches,
18
+ extract_go_type_kind,
19
+ )
20
+ from indexing.extractors import (
21
+ extract_function_info,
22
+ extract_class_info,
23
+ extract_variable_info,
24
+ extract_type_alias_info,
25
+ extract_macro_info,
26
+ extract_struct_info,
27
+ extract_interface_info,
28
+ extract_enum_info,
29
+ extract_go_struct_info,
30
+ extract_go_interface_info
31
+ )
32
+ from indexing.file_utils import read_file_content
33
+ import xxhash
34
+
35
+ # Global parser cache - initialized once per worker process
36
+ _parsers = {}
37
+
38
+ def _get_parser(language):
39
+ """Get or create a parser for the given language (cached per worker)."""
40
+ if language not in _parsers:
41
+ lang_module = LANGUAGE_CONFIG[language]["language_module"]
42
+ _parsers[language] = create_parser(lang_module) if lang_module else None
43
+ return _parsers[language]
44
+
45
+
46
+ def parse_file(args):
47
+ """Parse a single file and return extracted symbols as serializable data.
48
+
49
+ Args:
50
+ args: (file_path, root_dir) or (file_path, root_dir, frontend_enabled) tuple
51
+
52
+ Returns:
53
+ dict with extracted symbols, or None if file should be skipped
54
+ """
55
+ if len(args) == 3:
56
+ file_path, root_dir, frontend_enabled = args
57
+ else:
58
+ file_path, root_dir = args
59
+ frontend_enabled = True
60
+ file_path = Path(file_path)
61
+ root_dir = Path(root_dir)
62
+
63
+ try:
64
+ ext = file_path.suffix
65
+ language = get_language_for_extension(ext)
66
+ if not language:
67
+ return None
68
+
69
+ source_bytes = read_file_content(file_path)
70
+ content_hash = xxhash.xxh64(source_bytes).hexdigest()
71
+ file_mtime = file_path.stat().st_mtime
72
+
73
+ # Dispatch HTML files to the HTML semantic extractor
74
+ if language == "html":
75
+ from indexing.frontend_config import load_frontend_config
76
+ config = load_frontend_config(str(root_dir))
77
+ if len(source_bytes) > config.max_frontend_file_size:
78
+ return {
79
+ 'file_path': str(file_path),
80
+ 'language': 'html',
81
+ 'content_hash': content_hash,
82
+ 'file_mtime': file_mtime,
83
+ 'imports': [],
84
+ 'functions': [],
85
+ 'classes': [],
86
+ 'variables': [],
87
+ 'type_aliases': [],
88
+ 'structs': [],
89
+ 'interfaces': [],
90
+ 'enums': [],
91
+ 'namespaces': [],
92
+ 'dependencies': [],
93
+ 'markup_elements': [],
94
+ 'frontend_events': [],
95
+ 'frontend_bindings': [],
96
+ 'render_relationships': [],
97
+ 'style_selector_matches': [],
98
+ 'frontend_diagnostics': [{
99
+ 'diagnostic_type': 'file_too_large',
100
+ 'severity': 'unsupported',
101
+ 'message': f'HTML file is {len(source_bytes)} bytes (threshold: {config.max_frontend_file_size}), skipping',
102
+ }],
103
+ }
104
+ return _parse_html_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config)
105
+
106
+ # Dispatch CSS files to the CSS semantic extractor
107
+ if language == "css":
108
+ from indexing.frontend_config import load_frontend_config
109
+ config = load_frontend_config(str(root_dir))
110
+ return _parse_css_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config)
111
+
112
+ parser = _get_parser(language)
113
+ if parser is None:
114
+ return None
115
+
116
+ tree = parser.parse(source_bytes)
117
+ root_node = tree.root_node
118
+ source_code = source_bytes.decode('utf-8', errors='ignore')
119
+
120
+ # Extract imports — pass source_bytes to avoid whole-source re-encoding
121
+ imports = extract_imports(root_node, source_bytes, language, root_dir)
122
+
123
+ # Extract symbols
124
+ functions = []
125
+ classes = []
126
+ variables = []
127
+ type_aliases = []
128
+ structs = []
129
+ interfaces = []
130
+ enums = []
131
+ namespaces = []
132
+ dependencies = []
133
+
134
+ _extract_symbols(
135
+ root_node, source_bytes, language, file_path, root_dir,
136
+ functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies
137
+ )
138
+
139
+ # Extract JSX semantics for JS/TSX files (additive to existing symbols)
140
+ # Skip JSX extraction for plain JavaScript when frontend is disabled
141
+ jsx_data = {}
142
+ if language == "tsx" or (language == "javascript" and frontend_enabled):
143
+ from indexing.frontend_config import load_frontend_config
144
+ jsx_config = load_frontend_config(str(root_dir))
145
+ jsx_data = _extract_jsx_data(source_bytes, language, jsx_config, tree=tree)
146
+
147
+ return {
148
+ 'file_path': str(file_path),
149
+ 'language': language,
150
+ 'content_hash': content_hash,
151
+ 'file_mtime': file_mtime,
152
+ 'imports': imports,
153
+ 'functions': functions,
154
+ 'classes': classes,
155
+ 'variables': variables,
156
+ 'type_aliases': type_aliases,
157
+ 'structs': structs,
158
+ 'interfaces': interfaces,
159
+ 'enums': enums,
160
+ 'namespaces': namespaces,
161
+ 'dependencies': dependencies,
162
+ 'frontend_components': jsx_data.get('frontend_components', []),
163
+ 'markup_elements': jsx_data.get('markup_elements', []),
164
+ 'frontend_events': jsx_data.get('frontend_events', []),
165
+ 'frontend_bindings': jsx_data.get('frontend_bindings', []),
166
+ 'render_relationships': jsx_data.get('render_relationships', []),
167
+ 'style_selector_matches': jsx_data.get('style_selector_matches', []),
168
+ 'frontend_diagnostics': jsx_data.get('frontend_diagnostics', []),
169
+ }
170
+ except Exception as e:
171
+ return {'error': str(e), 'file_path': str(file_path)}
172
+
173
+
174
+ def _maybe_extract_arrow_function(node, source_code, var_info, functions, dependencies):
175
+ """Check if a variable declaration assigns an arrow function or function expression.
176
+
177
+ If so, register it as a function in addition to being a variable.
178
+ This captures React components like: const MyComponent = () => { ... }
179
+ and handlers like: const handleClick = (e) => { ... }
180
+ """
181
+ # Walk the node's children to find variable_declarator → arrow_function/function_expression
182
+ stack = list(node.children)
183
+ while stack:
184
+ child = stack.pop()
185
+ if child.type == "variable_declarator":
186
+ for vc in child.children:
187
+ if vc.type in ("arrow_function", "function_expression"):
188
+ func_info = {
189
+ "type": "function",
190
+ "name": var_info["name"],
191
+ "location": var_info["location"],
192
+ "parameters": _extract_arrow_params(vc, source_code),
193
+ "return_type": None,
194
+ "docstring": None,
195
+ "branch_count": _count_branches_in_node(vc, source_code),
196
+ }
197
+ func_index = len(functions)
198
+ functions.append(func_info)
199
+ _extract_deps(vc, source_code, "javascript", dependencies, func_index)
200
+ return
201
+ # Also push declarator children for deeper search
202
+ stack.extend(child.children)
203
+
204
+
205
+ def _extract_arrow_params(func_node, source_code):
206
+ """Extract parameter names from an arrow_function or function_expression node."""
207
+ params = []
208
+ for child in func_node.children:
209
+ if child.type == "formal_parameters":
210
+ for p in child.children:
211
+ if p.type == "identifier":
212
+ params.append({"name": extract_node_text(p, source_code), "type": None})
213
+ elif p.type == "assignment_pattern":
214
+ # Default parameter: (x = 1)
215
+ for ap in p.children:
216
+ if ap.type == "identifier":
217
+ params.append({"name": extract_node_text(ap, source_code), "type": None})
218
+ break
219
+ elif child.type == "identifier":
220
+ # Single param without parens: x => ...
221
+ params.append({"name": extract_node_text(child, source_code), "type": None})
222
+ return params
223
+
224
+
225
+ def _count_branches_in_node(node, source_code):
226
+ """Count if/else/switch/for/while/try branches inside a node (non-recursive)."""
227
+ count = 0
228
+ stack = list(node.children)
229
+ while stack:
230
+ child = stack.pop()
231
+ if child.type in ("if_statement", "for_statement", "for_in_statement",
232
+ "while_statement", "switch_statement", "try_statement"):
233
+ count += 1
234
+ stack.extend(child.children)
235
+ return count
236
+
237
+
238
+ def _extract_symbols(root_node, source_code, language, file_path, root_dir,
239
+ functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies):
240
+ """Iteratively extract symbols from a tree-sitter AST using an explicit stack.
241
+ Matches the logic of CodeIndexer._process_node exactly.
242
+ Avoids recursion depth limits on deeply nested ASTs (e.g., Linux kernel C files).
243
+ """
244
+ node_types = get_node_types(language)
245
+ if not node_types:
246
+ return
247
+
248
+ function_type = node_types.get("function")
249
+ class_type = node_types.get("class")
250
+ struct_type = node_types.get("struct")
251
+ enum_type = node_types.get("enum")
252
+ interface_type = node_types.get("interface")
253
+ assignment_type = node_types.get("assignment")
254
+ type_alias_type = node_types.get("type_alias")
255
+
256
+ # C# 9 top-level statements: collect global_statement nodes into a pseudo-function
257
+ if language == "csharp":
258
+ global_stmts = [child for child in root_node.children if child.type == "global_statement"]
259
+ if global_stmts:
260
+ first = global_stmts[0]
261
+ last = global_stmts[-1]
262
+ pseudo_func = {
263
+ "type": "function",
264
+ "name": "top_level_statements",
265
+ "location": {
266
+ "start_line": first.start_point[0] + 1,
267
+ "start_column": first.start_point[1],
268
+ "end_line": last.end_point[0] + 1,
269
+ "end_column": last.end_point[1],
270
+ "start_byte": first.start_byte,
271
+ "end_byte": last.end_byte,
272
+ },
273
+ "parameters": [],
274
+ "return_type": None,
275
+ "docstring": None,
276
+ "branch_count": 0,
277
+ }
278
+ func_index = len(functions)
279
+ functions.append(pseudo_func)
280
+ # Extract deps from all global statements combined
281
+ for gs in global_stmts:
282
+ _extract_deps(gs, source_code, language, dependencies, func_index)
283
+ # Extract top-level variable declarations
284
+ for gs in global_stmts:
285
+ for child in gs.children:
286
+ if child.type == "local_declaration_statement":
287
+ # C# local_declaration_statement: variable_declaration → variable_declarator → identifier
288
+ var_name = None
289
+ var_type = None
290
+ for vc in child.children:
291
+ if vc.type == "variable_declaration":
292
+ # Extract type from first child (implicit_type or type)
293
+ if vc.children:
294
+ var_type = extract_node_text(vc.children[0], source_code)
295
+ for vd in vc.children:
296
+ if vd.type == "variable_declarator":
297
+ for vdc in vd.children:
298
+ if vdc.type == "identifier":
299
+ var_name = extract_node_text(vdc, source_code)
300
+ break
301
+ break
302
+ break
303
+ if var_name:
304
+ variables.append({
305
+ "type": "variable",
306
+ "name": var_name,
307
+ "location": get_node_location(child),
308
+ "field_type": var_type,
309
+ })
310
+
311
+ # Stack entries: (node, in_function, current_class_id, current_namespace)
312
+ stack = [(root_node, False, None, None)]
313
+
314
+ while stack:
315
+ node, in_function, current_class_id, current_namespace = stack.pop()
316
+
317
+ # Skip global_statement nodes — already handled in C# top-level pre-pass
318
+ if node.type == "global_statement":
319
+ continue
320
+
321
+ # C# namespace declaration: extract name and track as current namespace
322
+ if language == "csharp" and node.type == "namespace_declaration":
323
+ ns_name = None
324
+ for child in node.children:
325
+ if child.type in ("qualified_name", "identifier"):
326
+ ns_name = extract_node_text(child, source_code)
327
+ break
328
+ if ns_name:
329
+ # Build fully qualified namespace name
330
+ if current_namespace:
331
+ full_ns = f"{current_namespace}.{ns_name}"
332
+ else:
333
+ full_ns = ns_name
334
+ namespaces.append({
335
+ "name": full_ns,
336
+ "location": get_node_location(node),
337
+ })
338
+ # Push children with the new namespace context
339
+ children = list(node.children)
340
+ for child in reversed(children):
341
+ stack.append((child, in_function, current_class_id, full_ns))
342
+ continue
343
+ # If we couldn't extract the name, still traverse children
344
+ children = list(node.children)
345
+ for child in reversed(children):
346
+ stack.append((child, in_function, current_class_id, current_namespace))
347
+ continue
348
+
349
+ if function_type and node.type in function_type:
350
+ func_info = extract_function_info(node, source_code, language, class_type)
351
+ if func_info:
352
+ if current_class_id is not None:
353
+ func_info['parent_class_id'] = current_class_id
354
+ func_index = len(functions)
355
+ functions.append(func_info)
356
+ # Dart: function_signature and function_body are siblings.
357
+ # Extract deps from both the signature and its sibling body.
358
+ if language == "dart" and node.parent:
359
+ body = None
360
+ found_self = False
361
+ for sibling in node.parent.children:
362
+ if sibling is node:
363
+ found_self = True
364
+ continue
365
+ if found_self and sibling.type == "function_body":
366
+ body = sibling
367
+ break
368
+ if body:
369
+ _extract_deps(node, source_code, language, dependencies, func_index)
370
+ _extract_deps(body, source_code, language, dependencies, func_index)
371
+ else:
372
+ _extract_deps(node, source_code, language, dependencies, func_index)
373
+ else:
374
+ _extract_deps(node, source_code, language, dependencies, func_index)
375
+ # Push children with in_function=True (reverse order for correct traversal)
376
+ children = list(node.children)
377
+ for child in reversed(children):
378
+ stack.append((child, True, current_class_id, current_namespace))
379
+ continue
380
+
381
+ if class_type and node.type in class_type:
382
+ # Kotlin: class_declaration is used for interfaces and enums too
383
+ # Check for interface/enum keyword children to reclassify
384
+ if language == "kotlin" and node.type == "class_declaration":
385
+ is_interface = False
386
+ is_enum = False
387
+ for child in node.children:
388
+ if child.type == "interface":
389
+ is_interface = True
390
+ break
391
+ if child.type == "enum":
392
+ is_enum = True
393
+ break
394
+ if is_interface:
395
+ iface_info = extract_interface_info(node, source_code, language)
396
+ if iface_info:
397
+ interfaces.append(iface_info)
398
+ continue
399
+ if is_enum:
400
+ enum_info = extract_enum_info(node, source_code, language)
401
+ if enum_info:
402
+ enums.append(enum_info)
403
+ continue
404
+
405
+ class_info = extract_class_info(node, source_code, language)
406
+ if class_info:
407
+ class_id = len(classes)
408
+ class_info['_temp_id'] = class_id
409
+ if current_namespace:
410
+ class_info['namespace'] = current_namespace
411
+ classes.append(class_info)
412
+ _process_class_body(node, source_code, language, class_id,
413
+ functions, classes, variables, dependencies)
414
+ continue
415
+
416
+ if assignment_type and node.type in assignment_type:
417
+ if not in_function:
418
+ var_info = extract_variable_info(node, source_code, language)
419
+ if var_info and var_info["type"] == "variable":
420
+ if current_class_id is not None:
421
+ var_info['parent_class_id'] = current_class_id
422
+ variables.append(var_info)
423
+ # JS/TS: detect arrow functions and function expressions assigned to variables
424
+ if language in ("javascript", "tsx", "typescript"):
425
+ _maybe_extract_arrow_function(node, source_code, var_info, functions, dependencies)
426
+ continue
427
+
428
+ # Rust global variables: const and static items
429
+ if language == "rust" and node.type in ("const_item", "static_item"):
430
+ if not in_function:
431
+ var_info = extract_variable_info(node, source_code, language)
432
+ if var_info and var_info["type"] == "variable":
433
+ variables.append(var_info)
434
+ continue
435
+
436
+ # Rust impl blocks: process methods inside impl_item as class methods
437
+ if language == "rust" and node.type == "impl_item":
438
+ # Extract the type name from the impl block
439
+ type_node = node.child_by_field_name("type")
440
+ impl_type_name = None
441
+ if type_node:
442
+ impl_type_name = extract_node_text(type_node, source_code)
443
+ # Create a pseudo-class for the impl block so methods can reference it
444
+ if impl_type_name:
445
+ impl_id = len(classes)
446
+ classes.append({
447
+ "type": "class",
448
+ "name": impl_type_name,
449
+ "location": get_node_location(node),
450
+ "base_classes": [],
451
+ "docstring": None,
452
+ "methods": [],
453
+ "nested_classes": [],
454
+ "variables": [],
455
+ "_temp_id": impl_id,
456
+ })
457
+ _process_class_body(node, source_code, language, impl_id,
458
+ functions, classes, variables, dependencies)
459
+ continue
460
+
461
+ # Elixir: def/defp/defmacro/defmacrop are function definitions
462
+ if language == "elixir" and node.type == "call":
463
+ # Check if this is a def/defp/defmacro/defmacrop call
464
+ func_name = None
465
+ for child in node.children:
466
+ if child.type == "identifier":
467
+ func_name = extract_node_text(child, source_code)
468
+ break
469
+ if func_name in ("def", "defp", "defmacro", "defmacrop"):
470
+ # Extract the function name from the first argument
471
+ elixir_func_name = None
472
+ for child in node.children:
473
+ if child.type == "arguments":
474
+ for arg in child.children:
475
+ if arg.type == "call":
476
+ # def foo(bar) -> the function name is the identifier inside the call
477
+ for grandchild in arg.children:
478
+ if grandchild.type == "identifier":
479
+ elixir_func_name = extract_node_text(grandchild, source_code)
480
+ break
481
+ elif arg.type == "identifier":
482
+ elixir_func_name = extract_node_text(arg, source_code)
483
+ break
484
+ elif arg.type == "atom":
485
+ # def :foo -> atom function name
486
+ atom_text = extract_node_text(arg, source_code)
487
+ elixir_func_name = atom_text.lstrip(':')
488
+ break
489
+ break
490
+ if elixir_func_name:
491
+ func_info = {
492
+ "type": "function" if func_name in ("def", "defmacro") else "function",
493
+ "name": elixir_func_name,
494
+ "location": get_node_location(node),
495
+ "parameters": [],
496
+ "return_type": None,
497
+ "docstring": None,
498
+ "branch_count": count_branches(node, language, source_code),
499
+ }
500
+ func_index = len(functions)
501
+ functions.append(func_info)
502
+ _extract_deps(node, source_code, language, dependencies, func_index)
503
+ continue
504
+ # defmodule creates a module (treated as a class)
505
+ elif func_name == "defmodule":
506
+ # Extract module name
507
+ module_name = None
508
+ for child in node.children:
509
+ if child.type == "arguments":
510
+ for arg in child.children:
511
+ if arg.type == "alias":
512
+ module_name = extract_node_text(arg, source_code)
513
+ break
514
+ break
515
+ if module_name:
516
+ class_id = len(classes)
517
+ classes.append({
518
+ "type": "class",
519
+ "name": module_name,
520
+ "location": get_node_location(node),
521
+ "base_classes": [],
522
+ "docstring": None,
523
+ "methods": [],
524
+ "nested_classes": [],
525
+ "variables": [],
526
+ "_temp_id": class_id,
527
+ })
528
+ # Process the do block as class body
529
+ for child in node.children:
530
+ if child.type == "do_block":
531
+ for stmt in child.children:
532
+ if stmt.type == "stab_clause":
533
+ # Process stab_clause children for inner def/defp
534
+ stack.append((stmt, False, class_id, current_namespace))
535
+ else:
536
+ stack.append((stmt, False, class_id, current_namespace))
537
+ break
538
+ continue
539
+ # defstruct creates a struct - name comes from enclosing module
540
+ elif func_name == "defstruct":
541
+ # defstruct doesn't take a name; use current_class_id to find the module name
542
+ struct_name = None
543
+ if current_class_id is not None and current_class_id < len(classes):
544
+ struct_name = classes[current_class_id].get("name")
545
+ if not struct_name:
546
+ struct_name = "unknown"
547
+ structs.append({
548
+ "type": "struct",
549
+ "name": struct_name,
550
+ "location": get_node_location(node),
551
+ })
552
+ continue
553
+ # defprotocol creates an interface-like construct
554
+ elif func_name == "defprotocol":
555
+ proto_name = None
556
+ for child in node.children:
557
+ if child.type == "arguments":
558
+ for arg in child.children:
559
+ if arg.type == "alias":
560
+ proto_name = extract_node_text(arg, source_code)
561
+ break
562
+ break
563
+ if proto_name:
564
+ interfaces.append({
565
+ "type": "interface",
566
+ "name": proto_name,
567
+ "location": get_node_location(node),
568
+ })
569
+ continue
570
+ # defimpl creates an implementation
571
+ elif func_name == "defimpl":
572
+ impl_name = None
573
+ for child in node.children:
574
+ if child.type == "arguments":
575
+ for arg in child.children:
576
+ if arg.type == "alias":
577
+ impl_name = extract_node_text(arg, source_code)
578
+ break
579
+ break
580
+ if impl_name:
581
+ class_id = len(classes)
582
+ classes.append({
583
+ "type": "class",
584
+ "name": f"{impl_name}Impl",
585
+ "location": get_node_location(node),
586
+ "base_classes": [impl_name],
587
+ "docstring": None,
588
+ "methods": [],
589
+ "nested_classes": [],
590
+ "variables": [],
591
+ "_temp_id": class_id,
592
+ })
593
+ for child in node.children:
594
+ if child.type == "do_block":
595
+ for stmt in child.children:
596
+ stack.append((stmt, False, class_id, current_namespace))
597
+ break
598
+ continue
599
+
600
+ # Macro definitions: C preproc_def/preproc_function_def, Rust macro_definition
601
+ if node.type in ("preproc_def", "preproc_function_def", "macro_definition"):
602
+ macro_info = extract_macro_info(node, source_code, language)
603
+ if macro_info:
604
+ functions.append(macro_info)
605
+ continue
606
+
607
+ # Go: type_declaration covers structs, interfaces, and type aliases
608
+ # Dispatch via extract_go_type_kind before generic branches to avoid conflicts
609
+ if language == "go" and node.type == "type_declaration":
610
+ kind = extract_go_type_kind(node, source_code)
611
+ if kind == "struct":
612
+ struct_info = extract_go_struct_info(node, source_code)
613
+ if struct_info:
614
+ structs.append(struct_info)
615
+ elif kind == "interface":
616
+ iface_info = extract_go_interface_info(node, source_code)
617
+ if iface_info:
618
+ interfaces.append(iface_info)
619
+ else:
620
+ alias_info = extract_type_alias_info(node, source_code, language)
621
+ if alias_info:
622
+ type_aliases.append(alias_info)
623
+ continue
624
+
625
+ if struct_type and node.type in struct_type:
626
+ struct_info = extract_struct_info(node, source_code, language)
627
+ if struct_info:
628
+ structs.append(struct_info)
629
+ # C# and C++ structs have methods and properties like classes — process their bodies
630
+ if language in ("csharp", "cpp"):
631
+ struct_id = len(classes)
632
+ struct_info['_temp_id'] = struct_id
633
+ # Also add to classes so methods can reference it as parent
634
+ classes.append({
635
+ "type": "class",
636
+ "name": struct_info["name"],
637
+ "location": struct_info["location"],
638
+ "base_classes": [],
639
+ "docstring": None,
640
+ "methods": [],
641
+ "nested_classes": [],
642
+ "variables": [],
643
+ "_temp_id": struct_id,
644
+ "namespace": current_namespace,
645
+ })
646
+ _process_class_body(node, source_code, language, struct_id,
647
+ functions, classes, variables, dependencies)
648
+ continue
649
+
650
+ if interface_type and node.type in interface_type:
651
+ iface_info = extract_interface_info(node, source_code, language)
652
+ if iface_info:
653
+ interfaces.append(iface_info)
654
+ # C# interfaces have method declarations — process their bodies
655
+ if language == "csharp":
656
+ iface_id = len(classes)
657
+ iface_info['_temp_id'] = iface_id
658
+ # Also add to classes so methods can reference it as parent
659
+ classes.append({
660
+ "type": "class",
661
+ "name": iface_info["name"],
662
+ "location": iface_info["location"],
663
+ "base_classes": [],
664
+ "docstring": None,
665
+ "methods": [],
666
+ "nested_classes": [],
667
+ "variables": [],
668
+ "_temp_id": iface_id,
669
+ "namespace": current_namespace,
670
+ })
671
+ _process_class_body(node, source_code, language, iface_id,
672
+ functions, classes, variables, dependencies)
673
+ continue
674
+
675
+ if enum_type and node.type in enum_type:
676
+ enum_info = extract_enum_info(node, source_code, language)
677
+ if enum_info:
678
+ enums.append(enum_info)
679
+ continue
680
+
681
+ if type_alias_type and node.type in type_alias_type:
682
+ alias_info = extract_type_alias_info(node, source_code, language)
683
+ if alias_info:
684
+ type_aliases.append(alias_info)
685
+ continue
686
+
687
+ # Push children with same state (reverse order for correct traversal)
688
+ children = list(node.children)
689
+ for child in reversed(children):
690
+ stack.append((child, in_function, current_class_id, current_namespace))
691
+
692
+
693
+ def _process_class_body(class_node, source_code, language, class_id,
694
+ functions, classes, variables, dependencies):
695
+ """Process class body children to extract methods, nested classes, and attributes."""
696
+ node_types = get_node_types(language)
697
+ function_type = node_types.get("function")
698
+ method_type = node_types.get("method")
699
+ class_type = node_types.get("class")
700
+ assignment_type = node_types.get("assignment")
701
+ public_field_type = node_types.get("public_field")
702
+
703
+ # Find the class body
704
+ class_body = None
705
+ for child in class_node.children:
706
+ if child.type in ["class_body", "declaration_list", "block", "field_declaration_list", "class_body"]:
707
+ class_body = child
708
+ break
709
+
710
+ if not class_body:
711
+ return
712
+
713
+ for child in class_body.children:
714
+ if child.type in ["{", "}"]:
715
+ continue
716
+
717
+ if (function_type and child.type in function_type) or \
718
+ (method_type and child.type in method_type):
719
+ func_info = extract_function_info(child, source_code, language, class_type)
720
+ if func_info:
721
+ func_info['parent_class_id'] = class_id
722
+ func_index = len(functions)
723
+ functions.append(func_info)
724
+ # Dart: method_signature and function_body are siblings in class_body
725
+ if language == "dart":
726
+ body = None
727
+ found_self = False
728
+ for sibling in class_body.children:
729
+ if sibling is child:
730
+ found_self = True
731
+ continue
732
+ if found_self and sibling.type == "function_body":
733
+ body = sibling
734
+ break
735
+ if body:
736
+ _extract_deps(child, source_code, language, dependencies, func_index)
737
+ _extract_deps(body, source_code, language, dependencies, func_index)
738
+ else:
739
+ _extract_deps(child, source_code, language, dependencies, func_index)
740
+ else:
741
+ _extract_deps(child, source_code, language, dependencies, func_index)
742
+ elif class_type and child.type in class_type:
743
+ nested_class_info = extract_class_info(child, source_code, language)
744
+ if nested_class_info:
745
+ nested_id = len(classes)
746
+ nested_class_info['_temp_id'] = nested_id
747
+ classes.append(nested_class_info)
748
+ _process_class_body(child, source_code, language, nested_id,
749
+ functions, classes, variables, dependencies)
750
+ elif assignment_type and child.type in assignment_type:
751
+ var_info = extract_variable_info(child, source_code, language)
752
+ if var_info and var_info["type"] == "attribute":
753
+ var_info['parent_class_id'] = class_id
754
+ variables.append(var_info)
755
+ elif public_field_type and child.type in public_field_type:
756
+ name_node = child.child_by_field_name("name")
757
+ if not name_node:
758
+ name_node = child.child_by_field_name("property_identifier")
759
+ name = extract_node_text(name_node, source_code) if name_node else "unknown"
760
+ type_node = child.child_by_field_name("type")
761
+ type_annotation = extract_node_text(type_node, source_code) if type_node else None
762
+ variables.append({
763
+ "type": "variable",
764
+ "name": name,
765
+ "location": get_node_location(child),
766
+ "field_type": type_annotation,
767
+ "parent_class_id": class_id,
768
+ })
769
+
770
+
771
+ def _extract_deps(node, source_code, language, dependencies, func_index=None):
772
+ """Extract dependencies (function calls, class refs, variable refs) from a node.
773
+
774
+ Args:
775
+ func_index: Index of the containing function in the functions list,
776
+ used to later resolve source_function_id.
777
+ """
778
+ func_calls = extract_function_calls(node, source_code, language)
779
+ class_refs = extract_class_references(node, source_code, language)
780
+ var_refs = extract_variable_references(node, source_code, language)
781
+ for call in func_calls:
782
+ dep = {
783
+ 'type': call.get('dependency_type', 'function_call'),
784
+ 'name': call['name'],
785
+ 'location': call.get('location'),
786
+ }
787
+ if func_index is not None:
788
+ dep['_func_index'] = func_index
789
+ dependencies.append(dep)
790
+ for ref in class_refs:
791
+ dep = {
792
+ 'type': 'class_reference',
793
+ 'name': ref['name'],
794
+ 'location': ref.get('location'),
795
+ }
796
+ if func_index is not None:
797
+ dep['_func_index'] = func_index
798
+ dependencies.append(dep)
799
+ for ref in var_refs:
800
+ dep = {
801
+ 'type': 'variable_reference',
802
+ 'name': ref['name'],
803
+ 'location': ref.get('location'),
804
+ }
805
+ if func_index is not None:
806
+ dep['_func_index'] = func_index
807
+ dependencies.append(dep)
808
+
809
+
810
+ def _parse_html_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config=None):
811
+ """Parse an HTML file using the frontend HTML extractor.
812
+
813
+ Returns a dict compatible with the standard parse_file output,
814
+ with additional frontend-specific keys.
815
+ """
816
+ from indexing.frontend.html_extractor import extract_html_semantics
817
+
818
+ try:
819
+ frontend_data = extract_html_semantics(source_bytes, config=config)
820
+ except Exception as e:
821
+ return {
822
+ 'file_path': str(file_path),
823
+ 'language': 'html',
824
+ 'content_hash': content_hash,
825
+ 'file_mtime': file_mtime,
826
+ 'error': f'HTML extraction failed: {e}',
827
+ 'imports': [],
828
+ 'functions': [],
829
+ 'classes': [],
830
+ 'variables': [],
831
+ 'type_aliases': [],
832
+ 'structs': [],
833
+ 'interfaces': [],
834
+ 'enums': [],
835
+ 'namespaces': [],
836
+ 'dependencies': [],
837
+ 'markup_elements': [],
838
+ 'frontend_events': [],
839
+ 'style_selectors': [],
840
+ 'style_custom_properties': [],
841
+ 'style_custom_property_usages': [],
842
+ 'style_imports': [],
843
+ 'style_selector_matches': [],
844
+ 'frontend_diagnostics': [{
845
+ 'diagnostic_type': 'extraction_error',
846
+ 'severity': 'fatal',
847
+ 'message': str(e),
848
+ }],
849
+ }
850
+
851
+ # Extract script src as imports for dependency tracking
852
+ imports = []
853
+ for script in frontend_data.get("inline_scripts", []):
854
+ if script["type"] == "external":
855
+ imports.append({
856
+ 'name': script['src'],
857
+ 'location': script.get('location'),
858
+ 'is_external': True,
859
+ })
860
+
861
+ # Extract stylesheet links as imports too
862
+ for style_import in frontend_data.get("style_imports", []):
863
+ imports.append({
864
+ 'name': style_import['import_path'],
865
+ 'location': style_import.get('source_range'),
866
+ 'is_external': style_import.get('is_external', False),
867
+ })
868
+
869
+ # Parse inline scripts as JavaScript and merge executable symbols
870
+ functions = []
871
+ classes = []
872
+ variables = []
873
+ dependencies = []
874
+ for script in frontend_data.get("inline_scripts", []):
875
+ if script["type"] != "inline":
876
+ continue
877
+ content = script.get("content", "")
878
+ if not content.strip():
879
+ continue
880
+ script_loc = script.get("location", {})
881
+ script_start_line = script_loc.get("start_line", 0)
882
+ script_start_col = script_loc.get("start_col", 0)
883
+ js_result = _parse_inline_js(content, str(file_path), script_start_line, script_start_col)
884
+ functions.extend(js_result.get("functions", []))
885
+ classes.extend(js_result.get("classes", []))
886
+ variables.extend(js_result.get("variables", []))
887
+ dependencies.extend(js_result.get("dependencies", []))
888
+
889
+ return {
890
+ 'file_path': str(file_path),
891
+ 'language': 'html',
892
+ 'content_hash': content_hash,
893
+ 'file_mtime': file_mtime,
894
+ 'imports': imports,
895
+ 'functions': functions,
896
+ 'classes': classes,
897
+ 'variables': variables,
898
+ 'type_aliases': [],
899
+ 'structs': [],
900
+ 'interfaces': [],
901
+ 'enums': [],
902
+ 'namespaces': [],
903
+ 'dependencies': dependencies,
904
+ 'markup_elements': frontend_data.get('markup_elements', []),
905
+ 'frontend_events': frontend_data.get('frontend_events', []),
906
+ 'style_selectors': frontend_data.get('style_selectors', []),
907
+ 'style_custom_properties': frontend_data.get('style_custom_properties', []),
908
+ 'style_custom_property_usages': frontend_data.get('style_custom_property_usages', []),
909
+ 'style_imports': frontend_data.get('style_imports', []),
910
+ 'style_selector_matches': frontend_data.get('style_selector_matches', []),
911
+ 'frontend_diagnostics': frontend_data.get('frontend_diagnostics', []),
912
+ }
913
+
914
+
915
+ def _parse_inline_js(content, file_path, offset_line, offset_col):
916
+ """Parse inline JavaScript content and return extracted symbols.
917
+
918
+ Source locations are adjusted by the script's offset within the HTML file
919
+ so that they point to the correct positions in the parent HTML file.
920
+
921
+ Args:
922
+ content: JavaScript source text from an inline <script> block.
923
+ file_path: Path of the parent HTML file (for symbol location records).
924
+ offset_line: Starting line of the script content within the HTML file.
925
+ offset_col: Starting column of the script content within the HTML file.
926
+
927
+ Returns:
928
+ dict with functions, classes, variables, dependencies lists.
929
+ """
930
+ result = {"functions": [], "classes": [], "variables": [], "dependencies": []}
931
+ try:
932
+ source_bytes = content.encode("utf-8")
933
+ parser = _get_parser("javascript")
934
+ if parser is None:
935
+ return result
936
+ tree = parser.parse(source_bytes)
937
+ root_node = tree.root_node
938
+ source_code = content
939
+
940
+ functions = []
941
+ classes = []
942
+ variables = []
943
+ type_aliases = []
944
+ structs = []
945
+ interfaces = []
946
+ enums = []
947
+ namespaces = []
948
+ dependencies = []
949
+
950
+ _extract_symbols(
951
+ root_node, source_code, "javascript", Path(file_path), Path(file_path).parent,
952
+ functions, classes, variables, type_aliases, structs, interfaces, enums, namespaces, dependencies
953
+ )
954
+
955
+ def _adjust_location(loc):
956
+ if loc is None:
957
+ return None
958
+ adjusted = dict(loc)
959
+ if "start_line" in adjusted:
960
+ adjusted["start_line"] = adjusted["start_line"] + offset_line
961
+ if "end_line" in adjusted:
962
+ adjusted["end_line"] = adjusted["end_line"] + offset_line
963
+ return adjusted
964
+
965
+ for func in functions:
966
+ func["location"] = _adjust_location(func.get("location"))
967
+ result["functions"].append(func)
968
+
969
+ for cls in classes:
970
+ cls["location"] = _adjust_location(cls.get("location"))
971
+ result["classes"].append(cls)
972
+
973
+ for var in variables:
974
+ var["location"] = _adjust_location(var.get("location"))
975
+ result["variables"].append(var)
976
+
977
+ for dep in dependencies:
978
+ dep["location"] = _adjust_location(dep.get("location"))
979
+ result["dependencies"].append(dep)
980
+
981
+ except Exception:
982
+ pass
983
+
984
+ return result
985
+
986
+
987
+ def _parse_css_file(file_path, root_dir, source_bytes, content_hash, file_mtime, config=None):
988
+ """Parse a CSS file using the frontend CSS extractor.
989
+
990
+ Returns a dict compatible with the standard parse_file output,
991
+ with additional frontend-specific keys.
992
+ """
993
+ from indexing.frontend.css_extractor import extract_css_semantics
994
+
995
+ try:
996
+ frontend_data = extract_css_semantics(source_bytes, config=config, file_path=str(file_path))
997
+ except Exception as e:
998
+ return {
999
+ 'file_path': str(file_path),
1000
+ 'language': 'css',
1001
+ 'content_hash': content_hash,
1002
+ 'file_mtime': file_mtime,
1003
+ 'error': f'CSS extraction failed: {e}',
1004
+ 'imports': [],
1005
+ 'functions': [],
1006
+ 'classes': [],
1007
+ 'variables': [],
1008
+ 'type_aliases': [],
1009
+ 'structs': [],
1010
+ 'interfaces': [],
1011
+ 'enums': [],
1012
+ 'namespaces': [],
1013
+ 'dependencies': [],
1014
+ 'style_selectors': [],
1015
+ 'style_custom_properties': [],
1016
+ 'style_custom_property_usages': [],
1017
+ 'style_keyframes': [],
1018
+ 'style_imports': [],
1019
+ 'frontend_diagnostics': [{
1020
+ 'diagnostic_type': 'extraction_error',
1021
+ 'severity': 'fatal',
1022
+ 'message': str(e),
1023
+ }],
1024
+ }
1025
+
1026
+ # Extract @import as imports for dependency tracking
1027
+ imports = []
1028
+ for imp in frontend_data.get("style_imports", []):
1029
+ imports.append({
1030
+ 'name': imp['import_path'],
1031
+ 'location': imp.get('source_range'),
1032
+ 'is_external': imp.get('is_external', False),
1033
+ })
1034
+
1035
+ return {
1036
+ 'file_path': str(file_path),
1037
+ 'language': 'css',
1038
+ 'content_hash': content_hash,
1039
+ 'file_mtime': file_mtime,
1040
+ 'imports': imports,
1041
+ 'functions': [],
1042
+ 'classes': [],
1043
+ 'variables': [],
1044
+ 'type_aliases': [],
1045
+ 'structs': [],
1046
+ 'interfaces': [],
1047
+ 'enums': [],
1048
+ 'namespaces': [],
1049
+ 'dependencies': [],
1050
+ 'style_selectors': frontend_data.get('style_selectors', []),
1051
+ 'style_custom_properties': frontend_data.get('style_custom_properties', []),
1052
+ 'style_custom_property_usages': frontend_data.get('style_custom_property_usages', []),
1053
+ 'style_keyframes': frontend_data.get('style_keyframes', []),
1054
+ 'style_imports': frontend_data.get('style_imports', []),
1055
+ 'frontend_diagnostics': frontend_data.get('frontend_diagnostics', []),
1056
+ }
1057
+
1058
+
1059
+ def _extract_jsx_data(source_bytes, language, config=None, tree=None):
1060
+ """Extract JSX semantics from JS/TSX source bytes.
1061
+
1062
+ Returns a dict with frontend component data, or empty dict on error.
1063
+ If tree is provided (already parsed by the caller), reuses it instead of re-parsing.
1064
+ """
1065
+ from indexing.frontend.jsx_extractor import extract_jsx_semantics
1066
+
1067
+ try:
1068
+ return extract_jsx_semantics(source_bytes, language=language, config=config, tree=tree)
1069
+ except Exception as e:
1070
+ return {
1071
+ "frontend_components": [],
1072
+ "markup_elements": [],
1073
+ "frontend_events": [],
1074
+ "frontend_bindings": [],
1075
+ "render_relationships": [],
1076
+ "style_selector_matches": [],
1077
+ "frontend_diagnostics": [{
1078
+ "diagnostic_type": "jsx_extraction_error",
1079
+ "severity": "recoverable",
1080
+ "message": str(e),
1081
+ }],
1082
+ }