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,705 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Structured semantic output formatters for frontend files (Phase 10).
4
+
5
+ Converts indexed frontend data into compressed, structured text for agent
6
+ consumption. Instead of dumping raw HTML/CSS/JSX source, these formatters
7
+ produce a concise summary of components, markup, styles, events, and bindings.
8
+
9
+ Four public functions:
10
+ - format_html_semantics(conn, file_id) → HTML/JSX/TSX files
11
+ - format_css_semantics(conn, file_id) → CSS files
12
+ - format_component_semantics(conn, component_id) → single component deep-dive
13
+ - format_frontend_overview(conn, file_id) → combined view for any frontend file
14
+ """
15
+
16
+ import json
17
+ import sqlite3
18
+ from typing import Dict, List, Optional, Any
19
+
20
+
21
+ # ──────────────────────────────────────────────────────────────
22
+ # Helpers
23
+ # ──────────────────────────────────────────────────────────────
24
+
25
+ def _parse_json(raw: Optional[str]) -> Optional[Any]:
26
+ if not raw:
27
+ return None
28
+ try:
29
+ return json.loads(raw)
30
+ except (json.JSONDecodeError, TypeError):
31
+ return None
32
+
33
+
34
+ def _get_file(conn, file_id: int) -> Optional[Dict[str, Any]]:
35
+ row = conn.execute(
36
+ "SELECT id, path, language, content_hash FROM files WHERE id = ?",
37
+ (file_id,)
38
+ ).fetchone()
39
+ return dict(row) if row else None
40
+
41
+
42
+ def _get_file_by_path(conn, file_path: str) -> Optional[Dict[str, Any]]:
43
+ normalized = file_path[2:] if file_path.startswith("./") else file_path
44
+ row = conn.execute(
45
+ "SELECT id, path, language, content_hash FROM files WHERE path = ?",
46
+ (normalized,)
47
+ ).fetchone()
48
+ if not row:
49
+ import os
50
+ filename = os.path.basename(normalized)
51
+ row = conn.execute(
52
+ "SELECT id, path, language, content_hash FROM files WHERE path LIKE ?",
53
+ (f"%{filename}",)
54
+ ).fetchone()
55
+ return dict(row) if row else None
56
+
57
+
58
+ def _is_frontend_file(file_info: Dict[str, Any]) -> bool:
59
+ lang = file_info.get("language", "")
60
+ return lang in ("html", "css", "javascript", "tsx", "typescript")
61
+
62
+
63
+ def _is_html_like(file_info: Dict[str, Any]) -> bool:
64
+ lang = file_info.get("language", "")
65
+ path = file_info.get("path", "")
66
+ return lang in ("html", "tsx", "javascript") or path.endswith((".html", ".htm", ".tsx", ".jsx"))
67
+
68
+
69
+ def _is_css(file_info: Dict[str, Any]) -> bool:
70
+ lang = file_info.get("language", "")
71
+ path = file_info.get("path", "")
72
+ return lang == "css" or path.endswith(".css")
73
+
74
+
75
+ def _format_source_range(sr: Optional[Dict]) -> str:
76
+ if not sr:
77
+ return "?"
78
+ return f"L{sr.get('start_line', '?')}"
79
+
80
+
81
+ def _format_classes(classes: Optional[List[str]]) -> str:
82
+ if not classes:
83
+ return ""
84
+ return "." + ".".join(classes)
85
+
86
+
87
+ def _format_element_id(elem_id: Optional[str]) -> str:
88
+ if not elem_id:
89
+ return ""
90
+ return f"#{elem_id}"
91
+
92
+
93
+ def _relationship_tag(status: str) -> str:
94
+ """Tag for distinguishing relationship confidence."""
95
+ if status == "resolved":
96
+ return "exact"
97
+ elif status == "conditional":
98
+ return "conditional"
99
+ elif status == "heuristic":
100
+ return "heuristic"
101
+ elif status == "unresolved":
102
+ return "unresolved"
103
+ return status
104
+
105
+
106
+ # ──────────────────────────────────────────────────────────────
107
+ # format_html_semantics
108
+ # ──────────────────────────────────────────────────────────────
109
+
110
+ def format_html_semantics(conn: sqlite3.Connection, file_id: int) -> str:
111
+ """Structured semantic output for HTML/JSX/TSX files.
112
+
113
+ Returns a concise summary of components, markup elements, events,
114
+ bindings, and style relationships — not raw HTML.
115
+ """
116
+ file_info = _get_file(conn, file_id)
117
+ if not file_info:
118
+ return f"Error: File not found (id={file_id})"
119
+
120
+ lines = [f'file "{file_info["path"]}" (frontend semantics):']
121
+
122
+ # 1. Components
123
+ components = conn.execute(
124
+ """SELECT c.id, c.name, c.framework, c.is_exported, c.source_range,
125
+ c.impl_function_id, c.impl_class_id
126
+ FROM frontend_components c
127
+ WHERE c.file_id = ?
128
+ ORDER BY c.id""",
129
+ (file_id,)
130
+ ).fetchall()
131
+
132
+ if components:
133
+ lines.append(" components:")
134
+ for comp in components:
135
+ exported = " (exported)" if comp["is_exported"] else ""
136
+ sr = _parse_json(comp["source_range"])
137
+ impl = ""
138
+ if comp["impl_function_id"]:
139
+ impl = f" → function#{comp['impl_function_id']}"
140
+ elif comp["impl_class_id"]:
141
+ impl = f" → class#{comp['impl_class_id']}"
142
+ lines.append(f" - {comp['name']} [{comp['framework']}]{exported} @ {_format_source_range(sr)}{impl}")
143
+
144
+ # Rendered children
145
+ children = conn.execute(
146
+ """SELECT rr.child_component_id, rr.child_component_name, rr.render_type,
147
+ rr.controlling_expr, rr.child_element_id
148
+ FROM render_relationships rr
149
+ WHERE rr.parent_component_id = ?
150
+ ORDER BY rr.id""",
151
+ (comp["id"],)
152
+ ).fetchall()
153
+ if children:
154
+ lines.append(" renders:")
155
+ for ch in children:
156
+ if ch["child_component_id"]:
157
+ tag = "exact"
158
+ cond = f" (conditional: {ch['controlling_expr']})" if ch["controlling_expr"] else ""
159
+ lines.append(f" - component {ch['child_component_name']} [{tag}]{cond}")
160
+ elif ch["child_component_name"]:
161
+ tag = "unresolved"
162
+ lines.append(f" - component {ch['child_component_name']} [{tag}] (unresolved)")
163
+ elif ch["child_element_id"]:
164
+ elem = conn.execute(
165
+ "SELECT tag_name FROM markup_elements WHERE id = ?",
166
+ (ch["child_element_id"],)
167
+ ).fetchone()
168
+ tag_name = elem["tag_name"] if elem else "?"
169
+ lines.append(f" - element <{tag_name}> [exact]")
170
+
171
+ # 2. Markup elements (summary)
172
+ elements = conn.execute(
173
+ """SELECT m.id, m.tag_name, m.element_type, m.element_id_attr,
174
+ m.static_classes, m.is_conditional, m.is_repeated,
175
+ m.source_range, m.component_id
176
+ FROM markup_elements m
177
+ WHERE m.file_id = ?
178
+ ORDER BY m.id""",
179
+ (file_id,)
180
+ ).fetchall()
181
+
182
+ if elements:
183
+ lines.append(" markup:")
184
+ for elem in elements:
185
+ etype = elem["element_type"]
186
+ if etype in ("text", "#text"):
187
+ continue # Skip text nodes in summary
188
+ if etype == "expression":
189
+ expr_tag = " (expression)"
190
+ sr = _parse_json(elem["source_range"])
191
+ lines.append(f" - {{expr}}{expr_tag} @ {_format_source_range(sr)}")
192
+ continue
193
+
194
+ classes = _parse_json(elem["static_classes"]) or []
195
+ class_str = _format_classes(classes)
196
+ id_str = _format_element_id(elem["element_id_attr"])
197
+ cond = " (conditional)" if elem["is_conditional"] else ""
198
+ rep = " (repeated)" if elem["is_repeated"] else ""
199
+ sr = _parse_json(elem["source_range"])
200
+ comp_ref = f" in {elem['component_id']}" if elem["component_id"] else ""
201
+ lines.append(f" - <{elem['tag_name']}>{id_str}{class_str}{cond}{rep} @ {_format_source_range(sr)}{comp_ref}")
202
+
203
+ # 3. Events
204
+ events = conn.execute(
205
+ """SELECT e.id, e.element_id, e.event_name, e.handler_type,
206
+ e.handler_expression, e.handler_symbol_id, e.resolution_status
207
+ FROM frontend_events e
208
+ WHERE e.file_id = ?
209
+ ORDER BY e.id""",
210
+ (file_id,)
211
+ ).fetchall()
212
+
213
+ if events:
214
+ lines.append(" events:")
215
+ for ev in events:
216
+ elem = conn.execute(
217
+ "SELECT tag_name, element_id_attr FROM markup_elements WHERE id = ?",
218
+ (ev["element_id"],)
219
+ ).fetchone()
220
+ elem_desc = f"<{elem['tag_name']}>" if elem else "?"
221
+ if elem and elem["element_id_attr"]:
222
+ elem_desc += f"#{elem['element_id_attr']}"
223
+ handler = ev["handler_expression"] or "?"
224
+ sym = f" → function#{ev['handler_symbol_id']}" if ev["handler_symbol_id"] else ""
225
+ tag = _relationship_tag(ev["resolution_status"])
226
+ lines.append(f" - {ev['event_name']} on {elem_desc} → {handler}{sym} [{tag}]")
227
+
228
+ # 4. Bindings
229
+ bindings = conn.execute(
230
+ """SELECT b.id, b.element_id, b.binding_type, b.binding_name,
231
+ b.binding_expression, b.resolution_status
232
+ FROM frontend_bindings b
233
+ WHERE b.file_id = ?
234
+ ORDER BY b.id""",
235
+ (file_id,)
236
+ ).fetchall()
237
+
238
+ if bindings:
239
+ lines.append(" bindings:")
240
+ for b in bindings:
241
+ elem = conn.execute(
242
+ "SELECT tag_name, element_id_attr FROM markup_elements WHERE id = ?",
243
+ (b["element_id"],)
244
+ ).fetchone()
245
+ elem_desc = f"<{elem['tag_name']}>" if elem else "?"
246
+ if elem and elem["element_id_attr"]:
247
+ elem_desc += f"#{elem['element_id_attr']}"
248
+ tag = _relationship_tag(b["resolution_status"])
249
+ lines.append(f" - {b['binding_type']}:{b['binding_name']} on {elem_desc} = {b['binding_expression']} [{tag}]")
250
+
251
+ # 5. Style relationships (selectors defined in this file)
252
+ selectors = conn.execute(
253
+ """SELECT s.id, s.selector_text, s.selector_type, s.is_scoped
254
+ FROM style_selectors s
255
+ WHERE s.file_id = ?
256
+ ORDER BY s.id""",
257
+ (file_id,)
258
+ ).fetchall()
259
+
260
+ if selectors:
261
+ lines.append(" styles defined:")
262
+ for sel in selectors:
263
+ scoped = " (scoped)" if sel["is_scoped"] else ""
264
+ lines.append(f" - {sel['selector_text']} [{sel['selector_type']}]{scoped}")
265
+
266
+ # 6. Stylesheet imports
267
+ imports = conn.execute(
268
+ """SELECT si.import_path, si.is_external, si.resolved_file_id
269
+ FROM style_imports si
270
+ WHERE si.file_id = ?
271
+ ORDER BY si.id""",
272
+ (file_id,)
273
+ ).fetchall()
274
+
275
+ if imports:
276
+ lines.append(" imports:")
277
+ for imp in imports:
278
+ if imp["is_external"]:
279
+ tag = "external"
280
+ elif imp["resolved_file_id"]:
281
+ tag = "resolved"
282
+ else:
283
+ tag = "unresolved"
284
+ lines.append(f" - {imp['import_path']} [{tag}]")
285
+
286
+ # 7. Embedded scripts
287
+ scripts = conn.execute(
288
+ """SELECT DISTINCT f2.id, f2.path
289
+ FROM files f2
290
+ WHERE f2.path LIKE '%script%' AND f2.id != ?
291
+ LIMIT 10""",
292
+ (file_id,)
293
+ ).fetchall()
294
+
295
+ # Check for inline scripts via diagnostics or other markers
296
+ # This is a simplified approach — in practice inline scripts are parsed
297
+ # as part of the HTML file's functions
298
+ funcs = conn.execute(
299
+ "SELECT id, name, type FROM functions WHERE file_id = ? ORDER BY id",
300
+ (file_id,)
301
+ ).fetchall()
302
+ if funcs:
303
+ lines.append(" embedded symbols:")
304
+ for fn in funcs:
305
+ lines.append(f" - {fn['type']} {fn['name']}")
306
+
307
+ if len(lines) == 1:
308
+ lines.append(" (no frontend semantic entities found)")
309
+
310
+ return "\n".join(lines)
311
+
312
+
313
+ # ──────────────────────────────────────────────────────────────
314
+ # format_css_semantics
315
+ # ──────────────────────────────────────────────────────────────
316
+
317
+ def format_css_semantics(conn: sqlite3.Connection, file_id: int) -> str:
318
+ """Structured semantic output for CSS files.
319
+
320
+ Returns selectors, custom properties, keyframes, imports, and
321
+ selector usages — not raw CSS.
322
+ """
323
+ file_info = _get_file(conn, file_id)
324
+ if not file_info:
325
+ return f"Error: File not found (id={file_id})"
326
+
327
+ lines = [f'file "{file_info["path"]}" (CSS semantics):']
328
+
329
+ # 1. Selectors
330
+ selectors = conn.execute(
331
+ """SELECT s.id, s.selector_text, s.selector_type, s.is_scoped,
332
+ s.component_id, s.source_range
333
+ FROM style_selectors s
334
+ WHERE s.file_id = ?
335
+ ORDER BY s.id""",
336
+ (file_id,)
337
+ ).fetchall()
338
+
339
+ if selectors:
340
+ lines.append(" selectors:")
341
+ for sel in selectors:
342
+ scoped = " (scoped)" if sel["is_scoped"] else ""
343
+ comp = f" → component#{sel['component_id']}" if sel["component_id"] else ""
344
+ sr = _parse_json(sel["source_range"])
345
+ lines.append(f" - {sel['selector_text']} [{sel['selector_type']}]{scoped} @ {_format_source_range(sr)}{comp}")
346
+
347
+ # Selector usages (which elements match)
348
+ matches = conn.execute(
349
+ """SELECT sm.element_id, sm.match_type, sm.confidence,
350
+ m.tag_name, m.element_id_attr, m.static_classes
351
+ FROM style_selector_matches sm
352
+ JOIN markup_elements m ON sm.element_id = m.id
353
+ WHERE sm.selector_id = ?
354
+ ORDER BY sm.id""",
355
+ (sel["id"],)
356
+ ).fetchall()
357
+ if matches:
358
+ for m in matches:
359
+ classes = _parse_json(m["static_classes"]) or []
360
+ class_str = _format_classes(classes)
361
+ id_str = _format_element_id(m["element_id_attr"])
362
+ tag = _relationship_tag(m["match_type"])
363
+ conf = ""
364
+ if m["confidence"]:
365
+ try:
366
+ conf = f" (conf={m['confidence']})" if float(m["confidence"]) < 1.0 else ""
367
+ except (ValueError, TypeError):
368
+ pass
369
+ lines.append(f" matches: <{m['tag_name']}>{id_str}{class_str} [{tag}]{conf}")
370
+
371
+ # 2. Custom properties
372
+ custom_props = conn.execute(
373
+ """SELECT cp.id, cp.name, cp.value, cp.scope_selector, cp.source_range
374
+ FROM style_custom_properties cp
375
+ WHERE cp.file_id = ?
376
+ ORDER BY cp.id""",
377
+ (file_id,)
378
+ ).fetchall()
379
+
380
+ if custom_props:
381
+ lines.append(" custom properties:")
382
+ for cp in custom_props:
383
+ scope = f" (scope: {cp['scope_selector']})" if cp["scope_selector"] else ""
384
+ sr = _parse_json(cp["source_range"])
385
+ lines.append(f" - {cp['name']}: {cp['value']}{scope} @ {_format_source_range(sr)}")
386
+
387
+ # 3. Custom property usages
388
+ prop_usages = conn.execute(
389
+ """SELECT cpu.id, cpu.property_name, cpu.resolved_property_id, cpu.selector_id
390
+ FROM style_custom_property_usages cpu
391
+ WHERE cpu.file_id = ?
392
+ ORDER BY cpu.id""",
393
+ (file_id,)
394
+ ).fetchall()
395
+
396
+ if prop_usages:
397
+ lines.append(" custom property usages:")
398
+ for pu in prop_usages:
399
+ if pu["resolved_property_id"]:
400
+ tag = "resolved"
401
+ ref = f" → property#{pu['resolved_property_id']}"
402
+ else:
403
+ tag = "unresolved"
404
+ ref = ""
405
+ sel_ref = f" in selector#{pu['selector_id']}" if pu["selector_id"] else ""
406
+ lines.append(f" - var({pu['property_name']}) [{tag}]{ref}{sel_ref}")
407
+
408
+ # 4. Keyframes
409
+ keyframes = conn.execute(
410
+ """SELECT k.id, k.name, k.source_range
411
+ FROM style_keyframes k
412
+ WHERE k.file_id = ?
413
+ ORDER BY k.id""",
414
+ (file_id,)
415
+ ).fetchall()
416
+
417
+ if keyframes:
418
+ lines.append(" keyframes:")
419
+ for kf in keyframes:
420
+ sr = _parse_json(kf["source_range"])
421
+ lines.append(f" - @keyframes {kf['name']} @ {_format_source_range(sr)}")
422
+
423
+ # 5. Imports
424
+ imports = conn.execute(
425
+ """SELECT si.import_path, si.is_external, si.resolved_file_id
426
+ FROM style_imports si
427
+ WHERE si.file_id = ?
428
+ ORDER BY si.id""",
429
+ (file_id,)
430
+ ).fetchall()
431
+
432
+ if imports:
433
+ lines.append(" imports:")
434
+ for imp in imports:
435
+ if imp["is_external"]:
436
+ tag = "external"
437
+ elif imp["resolved_file_id"]:
438
+ tag = "resolved"
439
+ else:
440
+ tag = "unresolved"
441
+ lines.append(f" - @import {imp['import_path']} [{tag}]")
442
+
443
+ # 6. Component associations
444
+ comp_associations = conn.execute(
445
+ """SELECT DISTINCT s.component_id, c.name
446
+ FROM style_selectors s
447
+ JOIN frontend_components c ON s.component_id = c.id
448
+ WHERE s.file_id = ? AND s.component_id IS NOT NULL
449
+ ORDER BY c.name""",
450
+ (file_id,)
451
+ ).fetchall()
452
+
453
+ if comp_associations:
454
+ lines.append(" component associations:")
455
+ for ca in comp_associations:
456
+ lines.append(f" - {ca['name']} (component#{ca['component_id']})")
457
+
458
+ if len(lines) == 1:
459
+ lines.append(" (no CSS semantic entities found)")
460
+
461
+ return "\n".join(lines)
462
+
463
+
464
+ # ──────────────────────────────────────────────────────────────
465
+ # format_component_semantics
466
+ # ──────────────────────────────────────────────────────────────
467
+
468
+ def format_component_semantics(conn: sqlite3.Connection, component_id: int) -> str:
469
+ """Deep-dive semantic output for a single component.
470
+
471
+ Returns component info, rendered children, markup tree summary,
472
+ events, bindings, styles, and rendering parents.
473
+ """
474
+ comp = conn.execute(
475
+ """SELECT c.*, f.path as file_path
476
+ FROM frontend_components c
477
+ JOIN files f ON c.file_id = f.id
478
+ WHERE c.id = ?""",
479
+ (component_id,)
480
+ ).fetchone()
481
+
482
+ if not comp:
483
+ return f"Error: Component not found (id={component_id})"
484
+
485
+ sr = _parse_json(comp["source_range"])
486
+ exported = " (exported)" if comp["is_exported"] else ""
487
+ impl = ""
488
+ if comp["impl_function_id"]:
489
+ impl = f" → function#{comp['impl_function_id']}"
490
+ elif comp["impl_class_id"]:
491
+ impl = f" → class#{comp['impl_class_id']}"
492
+
493
+ lines = [
494
+ f'component "{comp["name"]}" [{comp["framework"]}]{exported} @ {comp["file_path"]}:{_format_source_range(sr)}{impl}',
495
+ ]
496
+
497
+ # 1. Rendered children
498
+ children = conn.execute(
499
+ """SELECT rr.child_component_id, rr.child_component_name, rr.render_type,
500
+ rr.controlling_expr, rr.child_element_id
501
+ FROM render_relationships rr
502
+ WHERE rr.parent_component_id = ?
503
+ ORDER BY rr.id""",
504
+ (component_id,)
505
+ ).fetchall()
506
+
507
+ if children:
508
+ lines.append(" renders:")
509
+ for ch in children:
510
+ if ch["child_component_id"]:
511
+ tag = "exact"
512
+ cond = f" (conditional: {ch['controlling_expr']})" if ch["controlling_expr"] else ""
513
+ lines.append(f" - component {ch['child_component_name']} [{tag}]{cond}")
514
+ elif ch["child_component_name"]:
515
+ lines.append(f" - component {ch['child_component_name']} [unresolved] (unresolved reference)")
516
+ elif ch["child_element_id"]:
517
+ elem = conn.execute(
518
+ "SELECT tag_name FROM markup_elements WHERE id = ?",
519
+ (ch["child_element_id"],)
520
+ ).fetchone()
521
+ tag_name = elem["tag_name"] if elem else "?"
522
+ lines.append(f" - element <{tag_name}> [exact]")
523
+
524
+ # 2. Markup tree summary
525
+ elements = conn.execute(
526
+ """SELECT m.id, m.tag_name, m.element_type, m.element_id_attr,
527
+ m.static_classes, m.is_conditional, m.is_repeated,
528
+ m.source_range, m.parent_element_id
529
+ FROM markup_elements m
530
+ WHERE m.component_id = ?
531
+ ORDER BY m.id""",
532
+ (component_id,)
533
+ ).fetchall()
534
+
535
+ if elements:
536
+ lines.append(" markup tree:")
537
+ # Build a simple indented tree
538
+ by_id = {e["id"]: e for e in elements}
539
+ roots = [e for e in elements if e["parent_element_id"] is None or e["parent_element_id"] not in by_id]
540
+
541
+ def _render_element(elem, depth=1):
542
+ indent = " " * depth
543
+ etype = elem["element_type"]
544
+ if etype in ("text", "#text"):
545
+ return
546
+ if etype == "expression":
547
+ sr = _parse_json(elem["source_range"])
548
+ lines.append(f"{indent}{{expr}} @ {_format_source_range(sr)}")
549
+ return
550
+
551
+ classes = _parse_json(elem["static_classes"]) or []
552
+ class_str = _format_classes(classes)
553
+ id_str = _format_element_id(elem["element_id_attr"])
554
+ cond = " (conditional)" if elem["is_conditional"] else ""
555
+ rep = " (repeated)" if elem["is_repeated"] else ""
556
+ sr = _parse_json(elem["source_range"])
557
+ lines.append(f"{indent}<{elem['tag_name']}>{id_str}{class_str}{cond}{rep} @ {_format_source_range(sr)}")
558
+
559
+ # Children
560
+ children = [e for e in elements if e["parent_element_id"] == elem["id"]]
561
+ for child in children:
562
+ _render_element(child, depth + 1)
563
+
564
+ for root in roots:
565
+ _render_element(root)
566
+
567
+ # 3. Events
568
+ events = conn.execute(
569
+ """SELECT e.id, e.element_id, e.event_name, e.handler_type,
570
+ e.handler_expression, e.handler_symbol_id, e.resolution_status
571
+ FROM frontend_events e
572
+ WHERE e.element_id IN (
573
+ SELECT id FROM markup_elements WHERE component_id = ?
574
+ )
575
+ ORDER BY e.id""",
576
+ (component_id,)
577
+ ).fetchall()
578
+
579
+ if events:
580
+ lines.append(" events:")
581
+ for ev in events:
582
+ elem = conn.execute(
583
+ "SELECT tag_name, element_id_attr FROM markup_elements WHERE id = ?",
584
+ (ev["element_id"],)
585
+ ).fetchone()
586
+ elem_desc = f"<{elem['tag_name']}>" if elem else "?"
587
+ if elem and elem["element_id_attr"]:
588
+ elem_desc += f"#{elem['element_id_attr']}"
589
+ handler = ev["handler_expression"] or "?"
590
+ sym = f" → function#{ev['handler_symbol_id']}" if ev["handler_symbol_id"] else ""
591
+ tag = _relationship_tag(ev["resolution_status"])
592
+ lines.append(f" - {ev['event_name']} on {elem_desc} → {handler}{sym} [{tag}]")
593
+
594
+ # 4. Bindings
595
+ bindings = conn.execute(
596
+ """SELECT b.id, b.element_id, b.binding_type, b.binding_name,
597
+ b.binding_expression, b.resolution_status
598
+ FROM frontend_bindings b
599
+ WHERE b.element_id IN (
600
+ SELECT id FROM markup_elements WHERE component_id = ?
601
+ )
602
+ ORDER BY b.id""",
603
+ (component_id,)
604
+ ).fetchall()
605
+
606
+ if bindings:
607
+ lines.append(" bindings:")
608
+ for b in bindings:
609
+ elem = conn.execute(
610
+ "SELECT tag_name, element_id_attr FROM markup_elements WHERE id = ?",
611
+ (b["element_id"],)
612
+ ).fetchone()
613
+ elem_desc = f"<{elem['tag_name']}>" if elem else "?"
614
+ if elem and elem["element_id_attr"]:
615
+ elem_desc += f"#{elem['element_id_attr']}"
616
+ tag = _relationship_tag(b["resolution_status"])
617
+ lines.append(f" - {b['binding_type']}:{b['binding_name']} on {elem_desc} = {b['binding_expression']} [{tag}]")
618
+
619
+ # 5. Styles
620
+ selectors = conn.execute(
621
+ """SELECT s.id, s.selector_text, s.selector_type, s.is_scoped
622
+ FROM style_selectors s
623
+ WHERE s.component_id = ?
624
+ ORDER BY s.id""",
625
+ (component_id,)
626
+ ).fetchall()
627
+
628
+ if selectors:
629
+ lines.append(" styles:")
630
+ for sel in selectors:
631
+ scoped = " (scoped)" if sel["is_scoped"] else ""
632
+ lines.append(f" - {sel['selector_text']} [{sel['selector_type']}]{scoped}")
633
+
634
+ # 6. Rendering parents
635
+ parents = conn.execute(
636
+ """SELECT rr.parent_component_id, c.name
637
+ FROM render_relationships rr
638
+ JOIN frontend_components c ON rr.parent_component_id = c.id
639
+ WHERE rr.child_component_id = ?
640
+ ORDER BY c.name""",
641
+ (component_id,)
642
+ ).fetchall()
643
+
644
+ if parents:
645
+ lines.append(" rendered by:")
646
+ for p in parents:
647
+ lines.append(f" - {p['name']} (component#{p['parent_component_id']})")
648
+
649
+ if len(lines) == 1:
650
+ lines.append(" (no semantic data found for this component)")
651
+
652
+ return "\n".join(lines)
653
+
654
+
655
+ # ──────────────────────────────────────────────────────────────
656
+ # format_frontend_overview
657
+ # ──────────────────────────────────────────────────────────────
658
+
659
+ def format_frontend_overview(conn: sqlite3.Connection, file_id: int) -> str:
660
+ """Combined semantic view for any frontend file.
661
+
662
+ Dispatches to format_html_semantics or format_css_semantics based on
663
+ file type, then appends per-component deep-dives.
664
+ """
665
+ file_info = _get_file(conn, file_id)
666
+ if not file_info:
667
+ return f"Error: File not found (id={file_id})"
668
+
669
+ if _is_css(file_info):
670
+ base = format_css_semantics(conn, file_id)
671
+ else:
672
+ base = format_html_semantics(conn, file_id)
673
+
674
+ # Append component deep-dives
675
+ components = conn.execute(
676
+ "SELECT id FROM frontend_components WHERE file_id = ? ORDER BY id",
677
+ (file_id,)
678
+ ).fetchall()
679
+
680
+ if components and len(components) > 1:
681
+ sections = [base, "\n## Component Details"]
682
+ for comp in components:
683
+ sections.append("\n" + format_component_semantics(conn, comp["id"]))
684
+ return "\n".join(sections)
685
+
686
+ return base
687
+
688
+
689
+ # ──────────────────────────────────────────────────────────────
690
+ # Dispatch by file path (for integration with explore_code_structure)
691
+ # ──────────────────────────────────────────────────────────────
692
+
693
+ def format_file_semantics(conn: sqlite3.Connection, file_path: str) -> Optional[str]:
694
+ """Format semantic output for a file by path.
695
+
696
+ Returns None if the file is not a frontend file or not found.
697
+ """
698
+ file_info = _get_file_by_path(conn, file_path)
699
+ if not file_info:
700
+ return None
701
+
702
+ if not _is_frontend_file(file_info):
703
+ return None
704
+
705
+ return format_frontend_overview(conn, file_info["id"])