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,346 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Export SQLite code index to JSON format.
4
+ """
5
+
6
+ import json
7
+ import sqlite3
8
+ from pathlib import Path
9
+
10
+
11
+ def export_to_json(db_path, output_file):
12
+ """Export SQLite database to JSON format."""
13
+ print(f"Reading database from {db_path}...")
14
+
15
+ conn = sqlite3.connect(db_path)
16
+ conn.row_factory = sqlite3.Row
17
+
18
+ # Initialize JSON structure
19
+ json_index = {
20
+ "files": {},
21
+ "total_functions": 0,
22
+ "total_classes": 0,
23
+ "total_variables": 0,
24
+ "total_methods": 0,
25
+ "total_type_defs": 0,
26
+ "total_structs": 0,
27
+ "total_interfaces": 0
28
+ }
29
+
30
+ cursor = conn.cursor()
31
+
32
+ # Get all files
33
+ cursor.execute("SELECT * FROM files ORDER BY id")
34
+ files = cursor.fetchall()
35
+
36
+ print(f"Exporting {len(files)} files...")
37
+
38
+ for file_row in files:
39
+ file_id = file_row['id']
40
+ relative_path = file_row['path']
41
+
42
+ file_info = {
43
+ "path": relative_path,
44
+ "absolute_path": file_row['absolute_path'],
45
+ "language": file_row['language'],
46
+ "functions": [],
47
+ "classes": [],
48
+ "variables": [],
49
+ "type_aliases": [],
50
+ "structs": [],
51
+ "interfaces": []
52
+ }
53
+
54
+ # Get top-level functions (no parent)
55
+ cursor.execute(
56
+ """SELECT * FROM functions
57
+ WHERE file_id = ? AND parent_id IS NULL
58
+ ORDER BY id""",
59
+ (file_id,)
60
+ )
61
+ for func_row in cursor.fetchall():
62
+ func_info = {
63
+ "type": func_row['type'],
64
+ "name": func_row['name'],
65
+ "location": json.loads(func_row['location']),
66
+ "parameters": json.loads(func_row['parameters']),
67
+ "return_type": func_row['return_type'],
68
+ "docstring": func_row['docstring']
69
+ }
70
+ if func_row['receiver']:
71
+ func_info['receiver'] = func_row['receiver']
72
+ file_info["functions"].append(func_info)
73
+ json_index["total_functions"] += 1
74
+
75
+ # Get classes (top-level only)
76
+ cursor.execute(
77
+ """SELECT * FROM classes
78
+ WHERE file_id = ? AND parent_id IS NULL
79
+ ORDER BY id""",
80
+ (file_id,)
81
+ )
82
+ classes_map = {} # Map class_id to class info for method lookup
83
+
84
+ for class_row in cursor.fetchall():
85
+ class_id = class_row['id']
86
+ class_info = {
87
+ "type": "class",
88
+ "name": class_row['name'],
89
+ "location": json.loads(class_row['location']),
90
+ "base_classes": json.loads(class_row['base_classes']),
91
+ "docstring": class_row['docstring'],
92
+ "methods": [],
93
+ "nested_classes": [],
94
+ "variables": []
95
+ }
96
+ classes_map[class_id] = class_info
97
+ file_info["classes"].append(class_info)
98
+ json_index["total_classes"] += 1
99
+
100
+ # Get nested classes
101
+ cursor.execute(
102
+ """SELECT * FROM classes
103
+ WHERE file_id = ? AND parent_id IS NOT NULL
104
+ ORDER BY id""",
105
+ (file_id,)
106
+ )
107
+ for class_row in cursor.fetchall():
108
+ class_id = class_row['id']
109
+ parent_id = class_row['parent_id']
110
+
111
+ class_info = {
112
+ "type": "class",
113
+ "name": class_row['name'],
114
+ "location": json.loads(class_row['location']),
115
+ "base_classes": json.loads(class_row['base_classes']),
116
+ "docstring": class_row['docstring'],
117
+ "methods": [],
118
+ "nested_classes": [],
119
+ "variables": []
120
+ }
121
+
122
+ # Add to parent's nested_classes
123
+ if parent_id in classes_map:
124
+ classes_map[parent_id]["nested_classes"].append(class_info)
125
+ classes_map[class_id] = class_info
126
+ json_index["total_classes"] += 1
127
+
128
+ # Get methods (functions with parent)
129
+ cursor.execute(
130
+ """SELECT * FROM functions
131
+ WHERE file_id = ? AND parent_id IS NOT NULL
132
+ ORDER BY id""",
133
+ (file_id,)
134
+ )
135
+ for func_row in cursor.fetchall():
136
+ parent_id = func_row['parent_id']
137
+ parent_type = func_row['parent_type']
138
+
139
+ method_info = {
140
+ "type": func_row['type'],
141
+ "name": func_row['name'],
142
+ "location": json.loads(func_row['location']),
143
+ "parameters": json.loads(func_row['parameters']),
144
+ "return_type": func_row['return_type'],
145
+ "docstring": func_row['docstring']
146
+ }
147
+ if func_row['receiver']:
148
+ method_info['receiver'] = func_row['receiver']
149
+
150
+ # Add to parent class's methods
151
+ if parent_type == 'class' and parent_id in classes_map:
152
+ classes_map[parent_id]["methods"].append(method_info)
153
+ json_index["total_methods"] += 1
154
+
155
+ # Get top-level variables (no parent)
156
+ cursor.execute(
157
+ """SELECT * FROM variables
158
+ WHERE file_id = ? AND parent_id IS NULL
159
+ ORDER BY id""",
160
+ (file_id,)
161
+ )
162
+ for var_row in cursor.fetchall():
163
+ var_info = {
164
+ "type": var_row['type'],
165
+ "name": var_row['name'],
166
+ "location": json.loads(var_row['location'])
167
+ }
168
+ if var_row['field_type']:
169
+ var_info['field_type'] = var_row['field_type']
170
+ file_info["variables"].append(var_info)
171
+ json_index["total_variables"] += 1
172
+
173
+ # Get class attributes (variables with parent)
174
+ cursor.execute(
175
+ """SELECT * FROM variables
176
+ WHERE file_id = ? AND parent_id IS NOT NULL
177
+ ORDER BY id""",
178
+ (file_id,)
179
+ )
180
+ for var_row in cursor.fetchall():
181
+ parent_id = var_row['parent_id']
182
+ parent_type = var_row['parent_type']
183
+
184
+ var_info = {
185
+ "type": var_row['type'],
186
+ "name": var_row['name'],
187
+ "location": json.loads(var_row['location'])
188
+ }
189
+ if var_row['field_type']:
190
+ var_info['field_type'] = var_row['field_type']
191
+
192
+ # Add to parent class's variables
193
+ if parent_type == 'class' and parent_id in classes_map:
194
+ classes_map[parent_id]["variables"].append(var_info)
195
+ json_index["total_variables"] += 1
196
+
197
+ # Get type aliases
198
+ cursor.execute(
199
+ "SELECT * FROM type_aliases WHERE file_id = ? ORDER BY id",
200
+ (file_id,)
201
+ )
202
+ for type_row in cursor.fetchall():
203
+ type_info = {
204
+ "type": "type_alias",
205
+ "name": type_row['name'],
206
+ "location": json.loads(type_row['location']),
207
+ "type_definition": type_row['type_definition']
208
+ }
209
+ file_info["type_aliases"].append(type_info)
210
+ json_index["total_type_defs"] += 1
211
+
212
+ # Get structs
213
+ cursor.execute(
214
+ "SELECT * FROM structs WHERE file_id = ? ORDER BY id",
215
+ (file_id,)
216
+ )
217
+ for struct_row in cursor.fetchall():
218
+ struct_info = {
219
+ "type": "struct",
220
+ "name": struct_row['name'],
221
+ "location": json.loads(struct_row['location']),
222
+ "methods": [],
223
+ "fields": []
224
+ }
225
+ file_info["structs"].append(struct_info)
226
+ json_index["total_structs"] += 1
227
+
228
+ # Get interfaces
229
+ cursor.execute(
230
+ "SELECT * FROM interfaces WHERE file_id = ? ORDER BY id",
231
+ (file_id,)
232
+ )
233
+ for interface_row in cursor.fetchall():
234
+ interface_info = {
235
+ "type": "interface",
236
+ "name": interface_row['name'],
237
+ "location": json.loads(interface_row['location']),
238
+ "methods": []
239
+ }
240
+ file_info["interfaces"].append(interface_info)
241
+ json_index["total_interfaces"] += 1
242
+
243
+ json_index["files"][relative_path] = file_info
244
+
245
+ # Export frontend tables
246
+ _export_frontend_tables(conn, json_index)
247
+
248
+ conn.close()
249
+
250
+ # Write to JSON file
251
+ print(f"Writing JSON to {output_file}...")
252
+ with open(output_file, 'w', encoding='utf-8') as f:
253
+ json.dump(json_index, f, indent=2)
254
+
255
+ print(f"\nExport complete!")
256
+ print(f"JSON saved to {output_file}")
257
+
258
+ # Print summary
259
+ print("\n--- Export Summary ---")
260
+ print(f"Total files: {len(json_index['files'])}")
261
+ print(f"Total functions: {json_index['total_functions']}")
262
+ print(f"Total methods: {json_index['total_methods']}")
263
+ print(f"Total classes: {json_index['total_classes']}")
264
+ print(f"Total structs: {json_index['total_structs']}")
265
+ print(f"Total interfaces: {json_index['total_interfaces']}")
266
+ print(f"Total variables: {json_index['total_variables']}")
267
+ print(f"Total type aliases: {json_index['total_type_defs']}")
268
+
269
+ # Frontend summary
270
+ frontend_keys = ["frontend_components", "markup_elements", "style_selectors",
271
+ "frontend_events", "frontend_bindings", "render_relationships",
272
+ "frontend_diagnostics"]
273
+ has_frontend = any(k in json_index and json_index[k] for k in frontend_keys)
274
+ if has_frontend:
275
+ print("\n--- Frontend Summary ---")
276
+ for k in frontend_keys:
277
+ if k in json_index:
278
+ print(f" {k}: {len(json_index[k])}")
279
+
280
+
281
+ def _export_frontend_tables(conn, json_index):
282
+ """Export frontend semantic tables to the JSON index."""
283
+ cursor = conn.cursor()
284
+
285
+ frontend_tables = [
286
+ ("frontend_components", "frontend_components"),
287
+ ("markup_elements", "markup_elements"),
288
+ ("style_selectors", "style_selectors"),
289
+ ("style_custom_properties", "style_custom_properties"),
290
+ ("style_custom_property_usages", "style_custom_property_usages"),
291
+ ("style_keyframes", "style_keyframes"),
292
+ ("style_imports", "style_imports"),
293
+ ("style_selector_matches", "style_selector_matches"),
294
+ ("frontend_events", "frontend_events"),
295
+ ("frontend_bindings", "frontend_bindings"),
296
+ ("render_relationships", "render_relationships"),
297
+ ("frontend_diagnostics", "frontend_diagnostics"),
298
+ ]
299
+
300
+ for table_name, key in frontend_tables:
301
+ try:
302
+ cursor.execute(f"SELECT * FROM {table_name} ORDER BY id")
303
+ rows = cursor.fetchall()
304
+ json_index[key] = []
305
+ for row in rows:
306
+ entry = {}
307
+ for col in row.keys():
308
+ val = row[col]
309
+ if val is not None and isinstance(val, str) and val.startswith('{'):
310
+ try:
311
+ val = json.loads(val)
312
+ except (json.JSONDecodeError, TypeError):
313
+ pass
314
+ entry[col] = val
315
+ json_index[key].append(entry)
316
+ except sqlite3.OperationalError:
317
+ pass
318
+
319
+
320
+ def main():
321
+ """Main entry point for CLI usage."""
322
+ import sys
323
+
324
+ if len(sys.argv) < 2:
325
+ print("Usage: python export_to_json.py <db_file> [output_json]")
326
+ print("Example: python export_to_json.py code_index.db code_index.json")
327
+ sys.exit(1)
328
+
329
+ db_path = sys.argv[1]
330
+ output_file = sys.argv[2] if len(sys.argv) > 2 else db_path.rsplit('.', 1)[0] + '.json'
331
+
332
+ if not Path(db_path).exists():
333
+ print(f"Error: Database file not found: {db_path}")
334
+ sys.exit(1)
335
+
336
+ try:
337
+ export_to_json(db_path, output_file)
338
+ except Exception as e:
339
+ print(f"Error during export: {e}")
340
+ import traceback
341
+ traceback.print_exc()
342
+ sys.exit(1)
343
+
344
+
345
+ if __name__ == "__main__":
346
+ main()
indexing/extractors.py ADDED
@@ -0,0 +1,189 @@
1
+ """
2
+ Node extraction functions for different language constructs.
3
+ """
4
+
5
+ from indexing.node_utils import (
6
+ get_node_location,
7
+ extract_name,
8
+ extract_node_text,
9
+ extract_parameters,
10
+ extract_return_type,
11
+ is_method,
12
+ extract_base_classes,
13
+ extract_variable_name,
14
+ extract_docstring,
15
+ extract_field_text,
16
+ extract_go_receiver,
17
+ extract_go_type_name,
18
+ count_branches
19
+ )
20
+
21
+
22
+ def extract_function_info(node, source_code, language, class_node_type):
23
+ """Extract information about a function definition."""
24
+ name = extract_name(node, source_code, language) or "unknown"
25
+ params = extract_parameters(node, source_code, language)
26
+ return_type = extract_return_type(node, source_code)
27
+ method_flag = is_method(node, language, class_node_type)
28
+ docstring = extract_docstring(node, source_code, language)
29
+
30
+ # Count branches in function body
31
+ branch_count = count_branches(node, language, source_code)
32
+
33
+ # Handle Go method receivers
34
+ receiver = None
35
+ if language == "go":
36
+ receiver = extract_go_receiver(node, source_code)
37
+ if receiver:
38
+ method_flag = True
39
+
40
+ result = {
41
+ "type": "method" if method_flag else "function",
42
+ "name": name,
43
+ "location": get_node_location(node),
44
+ "parameters": params,
45
+ "return_type": return_type,
46
+ "docstring": docstring,
47
+ "branch_count": branch_count
48
+ }
49
+
50
+ if receiver:
51
+ result["receiver"] = receiver
52
+
53
+ return result
54
+
55
+
56
+ def extract_class_info(node, source_code, language):
57
+ """Extract information about a class definition."""
58
+ name = extract_name(node, source_code, language) or "unknown"
59
+ base_classes = extract_base_classes(node, source_code, language)
60
+ docstring = extract_docstring(node, source_code, language)
61
+
62
+ return {
63
+ "type": "class",
64
+ "name": name,
65
+ "location": get_node_location(node),
66
+ "base_classes": base_classes,
67
+ "docstring": docstring,
68
+ "methods": [],
69
+ "nested_classes": [],
70
+ "variables": []
71
+ }
72
+
73
+
74
+ def extract_variable_info(node, source_code, language):
75
+ """Extract information about a variable assignment."""
76
+ name, is_attribute = extract_variable_name(node, source_code, language)
77
+
78
+ if not name:
79
+ return None
80
+
81
+ if is_attribute:
82
+ return {
83
+ "type": "attribute",
84
+ "name": name,
85
+ "location": get_node_location(node)
86
+ }
87
+
88
+ return {
89
+ "type": "variable",
90
+ "name": name,
91
+ "location": get_node_location(node)
92
+ }
93
+
94
+
95
+ def extract_type_alias_info(node, source_code, language=None):
96
+ """Extract information about type alias definitions."""
97
+ name = extract_name(node, source_code, language) or "unknown"
98
+ type_def = extract_field_text(node, "type", source_code)
99
+
100
+ return {
101
+ "type": "type_alias",
102
+ "name": name,
103
+ "location": get_node_location(node),
104
+ "type_definition": type_def
105
+ }
106
+
107
+
108
+ def extract_macro_info(node, source_code, language):
109
+ """Extract information about a macro definition (C preproc_def, Rust macro_definition)."""
110
+ name = extract_name(node, source_code, language) or "unknown"
111
+
112
+ # Extract parameters for function-like macros (C preproc_function_def)
113
+ params = []
114
+ params_node = node.child_by_field_name("parameters")
115
+ if params_node is None:
116
+ # C preproc_function_def uses preproc_params
117
+ for child in node.children:
118
+ if child.type == "preproc_params":
119
+ params_node = child
120
+ break
121
+ if params_node:
122
+ for child in params_node.children:
123
+ if child.type == "identifier":
124
+ params.append({"name": extract_node_text(child, source_code), "type": None})
125
+
126
+ return {
127
+ "type": "macro",
128
+ "name": name,
129
+ "location": get_node_location(node),
130
+ "parameters": params,
131
+ "return_type": None,
132
+ "docstring": None,
133
+ "branch_count": 0,
134
+ }
135
+
136
+
137
+ def extract_struct_info(node, source_code, language):
138
+ """Extract information about a struct definition (C, C++, etc.)."""
139
+ name = extract_name(node, source_code, language) or "unknown"
140
+ return {
141
+ "type": "struct",
142
+ "name": name,
143
+ "location": get_node_location(node),
144
+ }
145
+
146
+
147
+ def extract_go_struct_info(node, source_code):
148
+ """Extract information about a Go struct definition."""
149
+ name = extract_go_type_name(node, source_code) or "unknown"
150
+
151
+ return {
152
+ "type": "struct",
153
+ "name": name,
154
+ "location": get_node_location(node),
155
+ "methods": [],
156
+ "fields": []
157
+ }
158
+
159
+
160
+ def extract_interface_info(node, source_code, language):
161
+ """Extract information about an interface/trait definition (Rust, etc.)."""
162
+ name = extract_name(node, source_code, language) or "unknown"
163
+ return {
164
+ "type": "interface",
165
+ "name": name,
166
+ "location": get_node_location(node),
167
+ }
168
+
169
+
170
+ def extract_go_interface_info(node, source_code):
171
+ """Extract information about a Go interface definition."""
172
+ name = extract_go_type_name(node, source_code) or "unknown"
173
+
174
+ return {
175
+ "type": "interface",
176
+ "name": name,
177
+ "location": get_node_location(node),
178
+ "methods": []
179
+ }
180
+
181
+
182
+ def extract_enum_info(node, source_code, language):
183
+ """Extract information about an enum definition (Rust, etc.)."""
184
+ name = extract_name(node, source_code, language) or "unknown"
185
+ return {
186
+ "type": "enum",
187
+ "name": name,
188
+ "location": get_node_location(node),
189
+ }
indexing/file_utils.py ADDED
@@ -0,0 +1,97 @@
1
+ """
2
+ File and directory utilities for the code indexer.
3
+ """
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from indexing.language_config import get_language_for_extension, get_extensions_for_languages
8
+ from indexing.frontend_config import load_frontend_config
9
+ from pathspec import PathSpec
10
+ from pathspec.patterns import GitWildMatchPattern
11
+
12
+
13
+ def detect_language(file_path):
14
+ """Detect the language based on file extension."""
15
+ return get_language_for_extension(file_path.suffix)
16
+
17
+
18
+ def load_ignore_patterns(root_dir):
19
+ """Load ignore patterns from .aiignore, falling back to .gitignore.
20
+
21
+ If .aiignore exists in the root directory, its patterns are used to
22
+ exclude files from indexing. If .aiignore does not exist, .gitignore
23
+ is used as a fallback.
24
+ """
25
+ root_path = Path(root_dir)
26
+ aiignore_path = root_path / '.aiignore'
27
+ gitignore_path = root_path / '.gitignore'
28
+
29
+ ignore_path = None
30
+ if aiignore_path.exists():
31
+ ignore_path = aiignore_path
32
+ elif gitignore_path.exists():
33
+ ignore_path = gitignore_path
34
+
35
+ if ignore_path is None:
36
+ return PathSpec.from_lines(GitWildMatchPattern, [])
37
+
38
+ with open(ignore_path, 'r', encoding='utf-8') as f:
39
+ patterns = f.read().splitlines()
40
+
41
+ return PathSpec.from_lines(GitWildMatchPattern, patterns)
42
+
43
+
44
+ def collect_files_to_index(root_dir, languages, exclude_dirs=None):
45
+ """Collect all files to index for given languages, respecting .aiignore (or .gitignore).
46
+
47
+ Args:
48
+ root_dir: Root directory to search.
49
+ languages: List of language names to include.
50
+ exclude_dirs: Optional list of additional directory names to exclude.
51
+ Generated directory exclusions from frontend config are always applied.
52
+ """
53
+ root_path = Path(root_dir)
54
+ extensions = get_extensions_for_languages(languages)
55
+
56
+ # Load ignore patterns (.aiignore takes priority over .gitignore)
57
+ ignore_spec = load_ignore_patterns(root_dir)
58
+
59
+ # Load generated directory exclusions from frontend config
60
+ frontend_cfg = load_frontend_config(root_dir)
61
+ excluded_dirs = set(frontend_cfg.generated_dir_exclusions)
62
+ if exclude_dirs:
63
+ excluded_dirs.update(exclude_dirs)
64
+
65
+ files = []
66
+ ext_set = set(extensions)
67
+
68
+ for dirpath, dirnames, filenames in os.walk(root_path):
69
+ # Prune ignored directories before entering them
70
+ # Build relative path for the current directory to check against ignore spec
71
+ current_rel = os.path.relpath(dirpath, root_path)
72
+ if current_rel != '.':
73
+ # Check if this directory itself is ignored
74
+ if ignore_spec.match_file(current_rel + '/') or ignore_spec.match_file(current_rel):
75
+ dirnames[:] = []
76
+ continue
77
+ # Skip test directories and generated directories
78
+ dirnames[:] = [d for d in dirnames if d not in ('test', 'tests') and d not in excluded_dirs]
79
+ for filename in filenames:
80
+ if any(filename.endswith(ext) for ext in ext_set):
81
+ file_path = Path(dirpath) / filename
82
+ relative_path = file_path.relative_to(root_path)
83
+ if not ignore_spec.match_file(str(relative_path)):
84
+ files.append(file_path)
85
+
86
+ return files
87
+
88
+
89
+ def read_file_content(file_path):
90
+ """Read file content in binary mode for faster processing."""
91
+ with open(file_path, 'rb') as f:
92
+ return f.read()
93
+
94
+
95
+ def get_relative_path(file_path, root_dir):
96
+ """Get relative path from root directory."""
97
+ return str(Path(file_path).relative_to(root_dir))
File without changes