From cba79350decabf4554c30d9f38a960800645b8ce Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 10 May 2026 13:05:32 -0400 Subject: [PATCH] feat(context): Granular AST Control for C/C++ files --- src/aggregate.py | 24 +++++++++++++++++++++--- src/gui_2.py | 7 ++++++- src/models.py | 6 ++++++ tests/test_file_item_model.py | 2 ++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/aggregate.py b/src/aggregate.py index 1ace9d8..7a8f664 100644 --- a/src/aggregate.py +++ b/src/aggregate.py @@ -121,21 +121,27 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[ tier = entry_raw.get("tier") auto_aggregate = entry_raw.get("auto_aggregate", True) force_full = entry_raw.get("force_full", False) + ast_signatures = entry_raw.get("ast_signatures", False) + ast_definitions = entry_raw.get("ast_definitions", False) elif hasattr(entry_raw, "path"): entry = entry_raw.path tier = getattr(entry_raw, "tier", None) auto_aggregate = getattr(entry_raw, "auto_aggregate", True) force_full = getattr(entry_raw, "force_full", False) + ast_signatures = getattr(entry_raw, "ast_signatures", False) + ast_definitions = getattr(entry_raw, "ast_definitions", False) else: entry = entry_raw tier = None auto_aggregate = True force_full = False + ast_signatures = False + ast_definitions = False if not entry or not isinstance(entry, str): continue paths = resolve_paths(base_dir, entry) if not paths: - items.append({"path": None, "entry": entry, "content": f"ERROR: no files matched: {entry}", "error": True, "mtime": 0.0, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full}) + items.append({"path": None, "entry": entry, "content": f"ERROR: no files matched: {entry}", "error": True, "mtime": 0.0, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions}) continue for path in paths: try: @@ -150,7 +156,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[ content = f"ERROR: {e}" mtime = 0.0 error = True - items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full}) + items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions}) return items @@ -264,6 +270,8 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P name = path.name if path else "" tier = item.get("tier") force_full = item.get("force_full") + ast_signatures = item.get("ast_signatures", False) + ast_definitions = item.get("ast_definitions", False) content = item.get("content", "") is_focus = entry in focus_set or (name and name in focus_set) or (path_str and path_str in focus_set) if not is_focus and path_str: @@ -276,7 +284,17 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P suffix = path.suffix.lstrip(".") if path and path.suffix else "text" sections.append(f"### `{original}`\n\n```{suffix}\n{content}\n```") elif path: - if path.suffix == ".py" and not item.get("error"): + if path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc'] and not item.get("error"): + from src import mcp_client + if ast_definitions: + skeleton = mcp_client.ts_cpp_get_skeleton(str(path)) if 'cpp' in path.suffix or 'hpp' in path.suffix or 'cxx' in path.suffix or 'cc' in path.suffix else mcp_client.ts_c_get_skeleton(str(path)) + sections.append(f"### `{original}` (AST Definitions)\n\n```{path.suffix.lstrip('.')}\n{skeleton}\n```") + elif ast_signatures: + outline = mcp_client.ts_cpp_get_code_outline(str(path)) if 'cpp' in path.suffix or 'hpp' in path.suffix or 'cxx' in path.suffix or 'cc' in path.suffix else mcp_client.ts_c_get_code_outline(str(path)) + sections.append(f"### `{original}` (AST Signatures)\n\n```{path.suffix.lstrip('.')}\n{outline}\n```") + else: + sections.append(f"### `{original}`\n\n{summarize.summarise_file(path, content)}") + elif path.suffix == ".py" and not item.get("error"): try: skeleton = parser.get_skeleton(content) sections.append(f"### `{original}` (AST Skeleton)\n\n```python\n{skeleton}\n```") diff --git a/src/gui_2.py b/src/gui_2.py index 8093d97..bd8b533 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -2529,7 +2529,7 @@ class App: imgui.separator() if imgui.begin_table("ctx_comp_table", 2, imgui.TableFlags_.resizable | imgui.TableFlags_.borders): imgui.table_setup_column("File", imgui.TableColumnFlags_.width_stretch) - imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 120) + imgui.table_setup_column("Flags", imgui.TableColumnFlags_.width_fixed, 200) imgui.table_headers_row() for i, f_item in enumerate(self.files): imgui.table_next_row() @@ -2540,6 +2540,11 @@ class App: changed_agg, f_item.auto_aggregate = imgui.checkbox(f"Agg##cc{i}", f_item.auto_aggregate) imgui.same_line() changed_full, f_item.force_full = imgui.checkbox(f"Full##cc{i}", f_item.force_full) + if hasattr(f_item, "ast_signatures"): + imgui.same_line() + _, f_item.ast_signatures = imgui.checkbox(f"Sig##cc{i}", f_item.ast_signatures) + imgui.same_line() + _, f_item.ast_definitions = imgui.checkbox(f"Def##cc{i}", f_item.ast_definitions) imgui.end_table() imgui.separator() imgui.text("Screenshots") diff --git a/src/models.py b/src/models.py index 1e46a75..95cef81 100644 --- a/src/models.py +++ b/src/models.py @@ -495,6 +495,8 @@ class FileItem: path: str auto_aggregate: bool = True force_full: bool = False + ast_signatures: bool = False + ast_definitions: bool = False injected_at: Optional[float] = None def to_dict(self) -> Dict[str, Any]: @@ -505,6 +507,8 @@ class FileItem: "path": self.path, "auto_aggregate": self.auto_aggregate, "force_full": self.force_full, + "ast_signatures": self.ast_signatures, + "ast_definitions": self.ast_definitions, "injected_at": self.injected_at, } @@ -517,6 +521,8 @@ class FileItem: path=data["path"], auto_aggregate=data.get("auto_aggregate", True), force_full=data.get("force_full", False), + ast_signatures=data.get("ast_signatures", False), + ast_definitions=data.get("ast_definitions", False), injected_at=data.get("injected_at"), ) diff --git a/tests/test_file_item_model.py b/tests/test_file_item_model.py index 3229616..2e6537c 100644 --- a/tests/test_file_item_model.py +++ b/tests/test_file_item_model.py @@ -16,6 +16,8 @@ def test_file_item_to_dict(): "path": "test.py", "auto_aggregate": False, "force_full": True, + "ast_signatures": False, + "ast_definitions": False, "injected_at": None } assert item.to_dict() == expected