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,485 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Cross-file resolution utilities for frontend semantic indexing.
4
+
5
+ Provides path normalization, alias resolution, and extension inference
6
+ for resolving file-path references (@import, <script src>, <link href>,
7
+ CSS module imports) during inline indexing.
8
+
9
+ These functions are called inline during _insert_frontend_data(), not
10
+ as a post-indexing pass.
11
+ """
12
+
13
+ import json
14
+ import os
15
+ from pathlib import Path
16
+ from typing import Dict, Optional
17
+
18
+
19
+ # Extensions to try when a path has no extension
20
+ CANDIDATE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js", ".css", ".html", ".mjs", ".cjs"]
21
+
22
+
23
+ def normalize_import_path(import_path: str, source_file_path: str, root_dir: str,
24
+ aliases: Optional[Dict[str, str]] = None) -> str:
25
+ """Resolve a relative or aliased import path to a normalized project-relative path.
26
+
27
+ Args:
28
+ import_path: The raw import path (e.g. "./Button", "../styles/global.css", "@/components/Card").
29
+ source_file_path: Absolute path of the file making the import.
30
+ root_dir: Absolute path of the project root directory.
31
+ aliases: Optional pre-loaded path aliases from load_path_aliases(). If None, will load lazily.
32
+
33
+ Returns:
34
+ Normalized project-relative path (e.g. "src/components/Button.tsx").
35
+ For external URLs (http://, https://, //), returns the original path unchanged.
36
+ """
37
+ # External URLs — return as-is
38
+ if import_path.startswith(("http://", "https://", "//")):
39
+ return import_path
40
+
41
+ root = Path(root_dir).resolve()
42
+ source_dir = Path(source_file_path).resolve().parent
43
+
44
+ # Handle path aliases (@/, ~/, etc.)
45
+ if aliases is None:
46
+ aliases = load_path_aliases(root_dir)
47
+ resolved = resolve_path_alias(import_path, aliases, root_dir)
48
+ if resolved != import_path:
49
+ # Alias was applied — resolve relative to root
50
+ candidate = root / resolved
51
+ elif import_path.startswith("/"):
52
+ # Root-relative path
53
+ candidate = root / import_path.lstrip("/")
54
+ else:
55
+ # Relative path — resolve against source file directory
56
+ candidate = (source_dir / import_path).resolve()
57
+
58
+ # Try to infer extension if the path doesn't exist as-is
59
+ if not candidate.exists():
60
+ candidate = infer_extension_path(candidate)
61
+
62
+ # Return project-relative path
63
+ try:
64
+ return str(candidate.relative_to(root))
65
+ except ValueError:
66
+ # Path is outside root — return best-effort normalized
67
+ return str(candidate)
68
+
69
+
70
+ def infer_extension_path(base_path: Path) -> Path:
71
+ """Try to find a file by appending common extensions to the path.
72
+
73
+ Args:
74
+ base_path: Path without an extension (or with one that doesn't exist).
75
+
76
+ Returns:
77
+ The first matching path with an extension, or the original path if none match.
78
+ """
79
+ # If the path already has an extension and exists, return it
80
+ if base_path.suffix and base_path.exists():
81
+ return base_path
82
+
83
+ # If the path has an extension but doesn't exist, try index files in that directory
84
+ if base_path.suffix:
85
+ # e.g. "./components" might be a directory with index.tsx
86
+ if base_path.with_suffix("").is_dir():
87
+ for ext in CANDIDATE_EXTENSIONS:
88
+ index_file = base_path.with_suffix("") / f"index{ext}"
89
+ if index_file.exists():
90
+ return index_file
91
+ return base_path
92
+
93
+ # No extension — try each candidate
94
+ for ext in CANDIDATE_EXTENSIONS:
95
+ candidate = base_path.with_suffix(ext)
96
+ if candidate.exists():
97
+ return candidate
98
+
99
+ # Try as a directory with index file
100
+ if base_path.is_dir():
101
+ for ext in CANDIDATE_EXTENSIONS:
102
+ index_file = base_path / f"index{ext}"
103
+ if index_file.exists():
104
+ return index_file
105
+
106
+ return base_path
107
+
108
+
109
+ def infer_extension(base_path: str) -> str:
110
+ """Try to infer the file extension for a base path string.
111
+
112
+ Args:
113
+ base_path: Path string without an extension.
114
+
115
+ Returns:
116
+ The first extension (including dot) that matches an existing file,
117
+ or the first candidate extension if none match.
118
+ """
119
+ p = Path(base_path)
120
+ for ext in CANDIDATE_EXTENSIONS:
121
+ candidate = p.with_suffix(ext)
122
+ if candidate.exists():
123
+ return ext
124
+ return CANDIDATE_EXTENSIONS[0]
125
+
126
+
127
+ def load_path_aliases(root_dir: str) -> Dict[str, str]:
128
+ """Load path aliases from tsconfig.json or jsconfig.json.
129
+
130
+ Reads compilerOptions.paths from the config file and returns a mapping
131
+ of alias patterns to resolved paths.
132
+
133
+ Args:
134
+ root_dir: Project root directory path.
135
+
136
+ Returns:
137
+ Dict mapping alias patterns (e.g. "@/*") to resolved path prefixes (e.g. "src/*").
138
+ Empty dict if no config file found or no paths configured.
139
+ """
140
+ root = Path(root_dir)
141
+
142
+ for config_name in ("tsconfig.json", "jsconfig.json"):
143
+ config_path = root / config_name
144
+ if config_path.exists():
145
+ try:
146
+ with open(config_path, "r", encoding="utf-8") as f:
147
+ config = json.load(f)
148
+ paths = config.get("compilerOptions", {}).get("paths", {})
149
+ if paths:
150
+ # Also get baseUrl for resolution
151
+ base_url = config.get("compilerOptions", {}).get("baseUrl", "")
152
+ result = {}
153
+ for alias, targets in paths.items():
154
+ if targets:
155
+ target = targets[0]
156
+ if base_url:
157
+ target = str(Path(base_url) / target)
158
+ result[alias] = target
159
+ return result
160
+ except (json.JSONDecodeError, OSError):
161
+ continue
162
+
163
+ return {}
164
+
165
+
166
+ def resolve_path_alias(import_path: str, aliases: Dict[str, str], root_dir: str) -> str:
167
+ """Apply alias mapping to an import path.
168
+
169
+ Args:
170
+ import_path: The raw import path (e.g. "@/components/Button").
171
+ aliases: Alias mapping from load_path_aliases().
172
+ root_dir: Project root directory path.
173
+
174
+ Returns:
175
+ Resolved path if alias matched, or the original import_path if no alias matched.
176
+ """
177
+ for alias_pattern, target_pattern in aliases.items():
178
+ # Handle wildcard patterns like "@/*"
179
+ if alias_pattern.endswith("/*"):
180
+ prefix = alias_pattern[:-2] # "@/"
181
+ if import_path.startswith(prefix):
182
+ suffix = import_path[len(prefix):]
183
+ resolved = target_pattern.rstrip("*").rstrip("/") + "/" + suffix.lstrip("/")
184
+ return resolved
185
+ elif alias_pattern == import_path:
186
+ return target_pattern
187
+ else:
188
+ # Exact prefix match without wildcard
189
+ if import_path.startswith(alias_pattern + "/"):
190
+ suffix = import_path[len(alias_pattern):]
191
+ resolved = target_pattern + suffix
192
+ return resolved
193
+
194
+ return import_path
195
+
196
+
197
+ class FrontendResolver:
198
+ """Post-indexing cross-file resolution pass.
199
+
200
+ Called after all files have been indexed to refresh cross-file relationships
201
+ that couldn't be resolved during inline insertion (e.g. because the target
202
+ file hadn't been indexed yet, or was indexed in the wrong order).
203
+ """
204
+
205
+ def __init__(self, conn, root_dir):
206
+ self.conn = conn
207
+ self.root_dir = root_dir
208
+ self.aliases = load_path_aliases(root_dir)
209
+
210
+ def resolve_all(self):
211
+ """Run all cross-file resolution passes.
212
+
213
+ Resolves:
214
+ 1. render_relationships.child_component_id by name lookup
215
+ 2. style_imports.resolved_file_id by path lookup
216
+ 3. style_custom_property_usages.resolved_property_id by name lookup
217
+ 4. frontend_events.handler_symbol_id by expression lookup
218
+ 5. style_selector_matches — recompute from selectors and elements
219
+ """
220
+ self._resolve_render_relationships()
221
+ self._resolve_style_imports()
222
+ self._resolve_custom_property_usages()
223
+ self._resolve_event_handlers()
224
+ self._resolve_selector_matches()
225
+ self.conn.commit()
226
+
227
+ def _resolve_render_relationships(self):
228
+ """Resolve render_relationships.child_component_id by looking up child_component_name."""
229
+ cursor = self.conn.cursor()
230
+ cursor.execute(
231
+ "SELECT rr.id, rr.child_component_name FROM render_relationships rr "
232
+ "WHERE rr.child_component_id IS NULL AND rr.child_component_name IS NOT NULL"
233
+ )
234
+ rows = cursor.fetchall()
235
+ if not rows:
236
+ return
237
+
238
+ # Batch-load all component name -> id mappings
239
+ cursor.execute("SELECT name, id FROM frontend_components")
240
+ component_map = {}
241
+ for name, comp_id in cursor.fetchall():
242
+ if name not in component_map:
243
+ component_map[name] = comp_id
244
+
245
+ batch = []
246
+ for row_id, child_name in rows:
247
+ comp_id = component_map.get(child_name)
248
+ if comp_id:
249
+ batch.append((comp_id, row_id))
250
+ if batch:
251
+ cursor.executemany(
252
+ "UPDATE render_relationships SET child_component_id = ? WHERE id = ?",
253
+ batch
254
+ )
255
+
256
+ def _resolve_style_imports(self):
257
+ """Resolve style_imports.resolved_file_id by looking up import paths."""
258
+ cursor = self.conn.cursor()
259
+ cursor.execute(
260
+ "SELECT si.id, si.import_path, f.absolute_path "
261
+ "FROM style_imports si "
262
+ "JOIN files f ON si.file_id = f.id "
263
+ "WHERE si.resolved_file_id IS NULL AND si.is_external = 0"
264
+ )
265
+ rows = cursor.fetchall()
266
+ if not rows:
267
+ return
268
+
269
+ # Batch-load all file path -> id mappings
270
+ cursor.execute("SELECT path, id FROM files")
271
+ file_map = {path: fid for path, fid in cursor.fetchall()}
272
+
273
+ batch = []
274
+ for row_id, import_path, source_file_path in rows:
275
+ resolved_path = normalize_import_path(
276
+ import_path, source_file_path, self.root_dir, self.aliases
277
+ )
278
+ file_id = file_map.get(resolved_path)
279
+ if file_id:
280
+ batch.append((file_id, row_id))
281
+ if batch:
282
+ cursor.executemany(
283
+ "UPDATE style_imports SET resolved_file_id = ? WHERE id = ?",
284
+ batch
285
+ )
286
+
287
+ def _resolve_custom_property_usages(self):
288
+ """Resolve style_custom_property_usages.resolved_property_id by name lookup."""
289
+ cursor = self.conn.cursor()
290
+ cursor.execute(
291
+ "SELECT id, property_name FROM style_custom_property_usages "
292
+ "WHERE resolved_property_id IS NULL"
293
+ )
294
+ rows = cursor.fetchall()
295
+ if not rows:
296
+ return
297
+
298
+ # Batch-load all property name -> id mappings
299
+ cursor.execute("SELECT name, id FROM style_custom_properties")
300
+ prop_map = {}
301
+ for name, prop_id in cursor.fetchall():
302
+ if name not in prop_map:
303
+ prop_map[name] = prop_id
304
+
305
+ batch = []
306
+ for row_id, prop_name in rows:
307
+ prop_id = prop_map.get(prop_name)
308
+ if prop_id:
309
+ batch.append((prop_id, row_id))
310
+ if batch:
311
+ cursor.executemany(
312
+ "UPDATE style_custom_property_usages SET resolved_property_id = ? WHERE id = ?",
313
+ batch
314
+ )
315
+
316
+ def _resolve_event_handlers(self):
317
+ """Resolve frontend_events.handler_symbol_id by looking up handler expressions."""
318
+ cursor = self.conn.cursor()
319
+ cursor.execute(
320
+ "SELECT fe.id, fe.handler_expression, fe.file_id "
321
+ "FROM frontend_events fe "
322
+ "WHERE fe.handler_symbol_id IS NULL "
323
+ "AND fe.handler_type = 'inline' "
324
+ "AND fe.handler_expression IS NOT NULL"
325
+ )
326
+ rows = cursor.fetchall()
327
+ if not rows:
328
+ return
329
+
330
+ # Group unresolved events by file_id for batch function lookups
331
+ events_by_file = {}
332
+ for row_id, handler_expr, file_id in rows:
333
+ expr = handler_expr.strip()
334
+ func_name = expr.rstrip("()").strip()
335
+ if func_name and "(" not in func_name:
336
+ events_by_file.setdefault(file_id, []).append((row_id, func_name))
337
+
338
+ batch = []
339
+ for file_id, events in events_by_file.items():
340
+ func_names = set(fn for _, fn in events)
341
+ placeholders = ','.join('?' * len(func_names))
342
+ cursor.execute(
343
+ f"SELECT name, id FROM functions WHERE file_id = ? AND name IN ({placeholders})",
344
+ [file_id] + list(func_names)
345
+ )
346
+ func_map = {name: fid for name, fid in cursor.fetchall()}
347
+ for row_id, func_name in events:
348
+ func_id = func_map.get(func_name)
349
+ if func_id:
350
+ batch.append((func_id, 'resolved', row_id))
351
+
352
+ if batch:
353
+ cursor.executemany(
354
+ "UPDATE frontend_events SET handler_symbol_id = ?, resolution_status = ? WHERE id = ?",
355
+ batch
356
+ )
357
+
358
+ def _resolve_selector_matches(self):
359
+ """Recompute style_selector_matches from selectors and elements.
360
+
361
+ Clears all existing matches and recomputes them by matching selectors
362
+ against markup elements across all files.
363
+
364
+ Optimized: pre-decodes element JSON once, groups selectors by type to
365
+ reduce cross-product work, and batch-inserts results.
366
+ """
367
+ cursor = self.conn.cursor()
368
+ cursor.execute("DELETE FROM style_selector_matches")
369
+
370
+ cursor.execute(
371
+ "SELECT ss.id, ss.selector_text, ss.selector_type "
372
+ "FROM style_selectors ss"
373
+ )
374
+ selectors = cursor.fetchall()
375
+
376
+ cursor.execute(
377
+ "SELECT me.id, me.tag_name, me.element_id_attr, me.static_classes "
378
+ "FROM markup_elements me "
379
+ "WHERE me.element_type IN ('element', 'custom_component', 'native')"
380
+ )
381
+ elements = cursor.fetchall()
382
+
383
+ # Pre-decode static_classes JSON once per element
384
+ decoded_elements = []
385
+ for elem_id, tag_name, elem_id_attr, static_classes in elements:
386
+ classes = json.loads(static_classes) if static_classes else []
387
+ decoded_elements.append((elem_id, tag_name, elem_id_attr, classes))
388
+
389
+ # Group selectors by type for targeted matching
390
+ selectors_by_type = {}
391
+ for sel_id, sel_text, sel_type in selectors:
392
+ selectors_by_type.setdefault(sel_type, []).append((sel_id, sel_text))
393
+
394
+ # Build lookup indexes from elements
395
+ elements_by_tag = {}
396
+ elements_by_id_attr = {}
397
+ elements_by_class = {}
398
+ for elem_id, tag_name, elem_id_attr, classes in decoded_elements:
399
+ tag_lower = tag_name.lower()
400
+ elements_by_tag.setdefault(tag_lower, []).append(elem_id)
401
+ if elem_id_attr:
402
+ elements_by_id_attr.setdefault(elem_id_attr, []).append(elem_id)
403
+ for cls in classes:
404
+ elements_by_class.setdefault(cls, []).append(elem_id)
405
+
406
+ batch = []
407
+ # Match class selectors using class index
408
+ for sel_id, sel_text in selectors_by_type.get("class", []):
409
+ target_class = sel_text.lstrip(".")
410
+ matched_ids = elements_by_class.get(target_class, [])
411
+ for elem_id in matched_ids:
412
+ batch.append((sel_id, elem_id, 'static', 'high'))
413
+
414
+ # Match id selectors using id index
415
+ for sel_id, sel_text in selectors_by_type.get("id", []):
416
+ target_id = sel_text.lstrip("#")
417
+ matched_ids = elements_by_id_attr.get(target_id, [])
418
+ for elem_id in matched_ids:
419
+ batch.append((sel_id, elem_id, 'static', 'high'))
420
+
421
+ # Match tag selectors using tag index
422
+ for sel_id, sel_text in selectors_by_type.get("tag", []):
423
+ tag_lower = sel_text.lower()
424
+ matched_ids = elements_by_tag.get(tag_lower, [])
425
+ for elem_id in matched_ids:
426
+ batch.append((sel_id, elem_id, 'static', 'high'))
427
+
428
+ # Match compound selectors (still requires cross-product but only for compound selectors)
429
+ compound_selectors = selectors_by_type.get("compound", [])
430
+ if compound_selectors:
431
+ for sel_id, sel_text in compound_selectors:
432
+ for elem_id, tag_name, elem_id_attr, classes in decoded_elements:
433
+ if self._selector_matches_element(sel_text, "compound", tag_name, elem_id_attr, classes):
434
+ batch.append((sel_id, elem_id, 'static', 'high'))
435
+
436
+ # Match any remaining selector types with full cross-product
437
+ remaining_types = set(selectors_by_type.keys()) - {"class", "id", "tag", "compound"}
438
+ if remaining_types:
439
+ for sel_type in remaining_types:
440
+ for sel_id, sel_text in selectors_by_type[sel_type]:
441
+ for elem_id, tag_name, elem_id_attr, classes in decoded_elements:
442
+ if self._selector_matches_element(sel_text, sel_type, tag_name, elem_id_attr, classes):
443
+ batch.append((sel_id, elem_id, 'static', 'high'))
444
+
445
+ # Batch insert all matches
446
+ if batch:
447
+ cursor.executemany(
448
+ "INSERT INTO style_selector_matches (selector_id, element_id, match_type, confidence) "
449
+ "VALUES (?, ?, ?, ?)",
450
+ batch
451
+ )
452
+
453
+ @staticmethod
454
+ def _selector_matches_element(selector_text, selector_type, tag_name, elem_id_attr, static_classes):
455
+ """Check if a CSS selector matches a markup element.
456
+
457
+ static_classes can be a pre-decoded list or a JSON string.
458
+ """
459
+ if isinstance(static_classes, str):
460
+ classes = json.loads(static_classes) if static_classes else []
461
+ else:
462
+ classes = static_classes
463
+
464
+ if selector_type == "class":
465
+ target_class = selector_text.lstrip(".")
466
+ return target_class in classes
467
+ elif selector_type == "id":
468
+ target_id = selector_text.lstrip("#")
469
+ return elem_id_attr == target_id
470
+ elif selector_type == "tag":
471
+ return tag_name.lower() == selector_text.lower()
472
+ elif selector_type == "compound":
473
+ parts = selector_text.replace(".", " .").replace("#", " #").split()
474
+ for part in parts:
475
+ if part.startswith("."):
476
+ if part[1:] not in classes:
477
+ return False
478
+ elif part.startswith("#"):
479
+ if elem_id_attr != part[1:]:
480
+ return False
481
+ else:
482
+ if tag_name.lower() != part.lower():
483
+ return False
484
+ return True
485
+ return False