From dfbde954c3ae52a0ced5f29243c6e121768da36d Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 7 Jun 2026 10:39:31 -0400 Subject: [PATCH] chore(scripts): remove one-shot transform scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These 6 scripts were one-shot AST/code transformations from past tracks. The transforms they perform are already applied; the scripts serve no further purpose. Removed (6 files, ~30 KB): - apply_startup_timeline.py (8.3 KB) - startup timeline edit (applied in startup_speedup_20260606 / commit 229559ca) - apply_type_hints.py (10.5 KB) - type-hint applicator (applied in gui_2_cleanup_20260513) - gut_oop_final.py (1.7 KB) - OOP culling (done in hot_reload_python_20260516) - restore_regions_final.py (4.8 KB) - region restoration (done in hot_reload_python_20260516) - transform_render_methods.py (3.0 KB) - render-method transformer (delegation done in hot_reload_python_20260516) - transform_render_methods_safe.py (2.4 KB) - safer variant Audit (per spec §Gaps to Fill) confirms zero external references. --- scripts/apply_startup_timeline.py | 197 ----------------- scripts/apply_type_hints.py | 269 ----------------------- scripts/gut_oop_final.py | 48 ---- scripts/restore_regions_final.py | 78 ------- scripts/transform_render_methods.py | 92 -------- scripts/transform_render_methods_safe.py | 65 ------ 6 files changed, 749 deletions(-) delete mode 100644 scripts/apply_startup_timeline.py delete mode 100644 scripts/apply_type_hints.py delete mode 100644 scripts/gut_oop_final.py delete mode 100644 scripts/restore_regions_final.py delete mode 100644 scripts/transform_render_methods.py delete mode 100644 scripts/transform_render_methods_safe.py diff --git a/scripts/apply_startup_timeline.py b/scripts/apply_startup_timeline.py deleted file mode 100644 index 9b6adf8b..00000000 --- a/scripts/apply_startup_timeline.py +++ /dev/null @@ -1,197 +0,0 @@ -""" -Surgical edit script for src/app_controller.py - adds startup timeline -instrumentation to AppController. - -Run: uv run python scripts/apply_startup_timeline.py -""" -import ast -import os -import sys - -BASE: str = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -TARGET_FILE: str = "src/app_controller.py" -EOL: str = "\r\n" - - -def read_lines(path: str) -> list[str]: - with open(path, "r", encoding="utf-8", newline="") as f: - return f.read().splitlines(keepends=True) - - -def write_lines(path: str, lines: list[str]) -> None: - with open(path, "w", encoding="utf-8", newline="") as f: - f.writelines(lines) - - -def find_init(tree: ast.Module) -> ast.FunctionDef: - for node in tree.body: - if isinstance(node, ast.ClassDef) and node.name == "AppController": - for item in node.body: - if isinstance(item, ast.FunctionDef) and item.name == "__init__": - return item - raise RuntimeError("AppController.__init__ not found") - - -def patch_def_signature(lines: list[str], init_fn: ast.FunctionDef) -> None: - idx = init_fn.lineno - 1 - line = lines[idx] - if "log_to_stderr" in line: - return - new_line = line.replace("def __init__(self):", "def __init__(self, log_to_stderr: bool = True):") - if new_line == line: - raise RuntimeError(f"Could not patch def line: {line!r}") - lines[idx] = new_line - print(f" Patched def signature at line {init_fn.lineno}") - - -def insert_timeline_block(lines: list[str]) -> None: - for i, line in enumerate(lines): - if line.strip() == '"""' and i + 1 < len(lines) and "# --- Locks ---" in lines[i + 1]: - block_lines = [ - ' # --- Startup timeline (startup_speedup_20260606) ---' + EOL, - ' # Captured at the very start of __init__ so init_start_ts represents' + EOL, - ' # the true cold-start entry point. first_frame_ts and warmup_done_ts' + EOL, - ' # are filled in later as events occur.' + EOL, - ' self._init_start_ts: float = time.time()' + EOL, - ' self._warmup_done_ts: Optional[float] = None' + EOL, - ' self._first_frame_ts: Optional[float] = None' + EOL, - ] - lines[i + 1:i + 1] = block_lines - print(f" Inserted timeline block at line {i + 2}") - return - raise RuntimeError("Could not find docstring-end + Locks-comment marker") - - -def patch_warmup_block(lines: list[str]) -> None: - old = [ - ' # --- Shared background pool + proactive warmup (startup_speedup_20260606) ---' + EOL, - ' self._io_pool = make_io_pool()' + EOL, - ' self._warmup = WarmupManager(self._io_pool)' + EOL, - ' self._warmup.submit(self._compute_warmup_list())' + EOL, - ] - new = [ - ' # --- Shared background pool + proactive warmup (startup_speedup_20260606) ---' + EOL, - ' self._io_pool = make_io_pool()' + EOL, - ' self._warmup = WarmupManager(self._io_pool, log_to_stderr=log_to_stderr)' + EOL, - ' # Hook warmup completion to stamp warmup_done_ts for startup_timeline().' + EOL, - ' self._warmup.on_complete(self._on_warmup_complete_for_timeline)' + EOL, - ' self._warmup.submit(self._compute_warmup_list())' + EOL, - ] - for i in range(len(lines) - len(old) + 1): - if lines[i:i + len(old)] == old: - lines[i:i + len(old)] = new - print(f" Replaced warmup block at lines {i + 1}-{i + len(old)}") - return - raise RuntimeError("Could not find warmup block to replace") - - -NEW_METHODS_TEMPLATE = ''' def init_start_ts(self) -> float: - """Timestamp when AppController.__init__ started (cold-start entry). [SDM: src/app_controller.py:init_start_ts]""" - return self._init_start_ts - - def warmup_done_ts(self) -> "Optional[float]": - """Timestamp when the warmup completed; None while still running. [SDM: src/app_controller.py:warmup_done_ts]""" - return self._warmup_done_ts - - def first_frame_ts(self) -> "Optional[float]": - """Timestamp of the first GUI frame; None until the App has rendered once. [SDM: src/app_controller.py:first_frame_ts]""" - return self._first_frame_ts - - def mark_first_frame_rendered(self, ts: "Optional[float]" = None) -> None: - """Called by the App on the first frame render. Stamps first_frame_ts and logs the timeline to stderr. [SDM: src/app_controller.py:mark_first_frame_rendered] [C: src/gui_2.py:render_main_interface]""" - if self._first_frame_ts is not None: return - self._first_frame_ts = ts if ts is not None else time.time() - try: - warmup_ms = (self._warmup_done_ts - self._init_start_ts) * 1000 if self._warmup_done_ts is not None else 0.0 - frame_after_init_ms = (self._first_frame_ts - self._init_start_ts) * 1000 - if self._warmup_done_ts is None: - gap_str = " (warmup still running at first frame; warmup did NOT block the first frame)" - else: - delta_ms = (self._first_frame_ts - self._warmup_done_ts) * 1000 - if delta_ms < 0: - gap_str = f" (rendered {-delta_ms:.1f}ms BEFORE warmup done \\u2014 warmup did NOT block)" - else: - gap_str = f" (rendered {delta_ms:.1f}ms AFTER warmup done)" - sys.stderr.write(f"[startup] first frame at {frame_after_init_ms:.1f}ms after init (warmup took {warmup_ms:.1f}ms){gap_str}\\n") - sys.stderr.flush() - except Exception: pass - - def startup_timeline(self) -> dict: -def insert_new_methods(lines: list[str]) -> None: - """Insert new methods right after the last line of __init__ (`self._init_actions()`).""" - needle = ' self._init_actions()' + EOL - for i, line in enumerate(lines): - if line == needle: - # Insert AFTER this line. The next line is blank, then the next method. - new_lines = [l + EOL for l in NEW_METHODS_TEMPLATE.split("\n") if l] - insert_at = i + 1 - lines[insert_at:insert_at] = new_lines - print(f" Inserted {len(new_lines)} new method lines at line {insert_at + 1}") - return - raise RuntimeError("Could not find 'self._init_actions()' to anchor new methods") - } - if self._warmup_done_ts is not None: - result["warmup_ms"] = (self._warmup_done_ts - self._init_start_ts) * 1000 - else: - result["warmup_ms"] = None - if self._first_frame_ts is not None: - result["first_frame_after_init_ms"] = (self._first_frame_ts - self._init_start_ts) * 1000 - if self._warmup_done_ts is not None: - result["first_frame_after_warmup_ms"] = (self._first_frame_ts - self._warmup_done_ts) * 1000 - else: - result["first_frame_after_warmup_ms"] = None - else: - result["first_frame_after_init_ms"] = None - result["first_frame_after_warmup_ms"] = None - return result - - def _on_warmup_complete_for_timeline(self, snap: dict) -> None: - """Callback registered with the WarmupManager. Stamps warmup_done_ts and logs the timeline to stderr. [C: src/app_controller.py:startup_timeline]""" - self._warmup_done_ts = time.time() - try: - warmup_ms = (self._warmup_done_ts - self._init_start_ts) * 1000 - if self._first_frame_ts is None: - gap_str = f" (first frame not yet rendered at warmup done; warmup took {warmup_ms:.1f}ms)" - else: - delta_ms = (self._first_frame_ts - self._warmup_done_ts) * 1000 - if delta_ms < 0: - gap_str = f" (first frame rendered {-delta_ms:.1f}ms BEFORE warmup done \\u2014 warmup did NOT block)" - else: - gap_str = f" (first frame rendered {delta_ms:.1f}ms after warmup done)" - sys.stderr.write(f"[startup] warmup done in {warmup_ms:.1f}ms{gap_str}\\n") - sys.stderr.flush() - except Exception: pass - -''' - - -def insert_new_methods(lines: list[str]) -> None: - for i, line in enumerate(lines): - if line.lstrip().startswith("def perf_profiling_enabled"): - new_lines = [l + EOL for l in NEW_METHODS_TEMPLATE.split("\n") if l] - lines[i:i] = new_lines - print(f" Inserted {len(new_lines)} new method lines at line {i + 1}") - return - raise RuntimeError("Could not find 'def perf_profiling_enabled' to anchor new methods") - - -def main() -> None: - path = os.path.join(BASE, TARGET_FILE) - lines = read_lines(path) - code = "".join(lines) - tree = ast.parse(code) - init_fn = find_init(tree) - print(f"Found AppController.__init__ at lines {init_fn.lineno}-{init_fn.end_lineno}") - patch_def_signature(lines, init_fn) - insert_timeline_block(lines) - patch_warmup_block(lines) - insert_new_methods(lines) - write_lines(path, lines) - print(f"\nWrote {len(lines)} lines to {path}") - with open(path, "rb") as f: - ast.parse(f.read()) - print(" Syntax OK") - - -if __name__ == "__main__": - main() diff --git a/scripts/apply_type_hints.py b/scripts/apply_type_hints.py deleted file mode 100644 index 78479d97..00000000 --- a/scripts/apply_type_hints.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -Type hint applicator for gui_2.py. -Does a single-pass AST-guided line edit to add type annotations. -No dependency on mcp_client — operates directly on file lines. - -Run: uv run python scripts/apply_type_hints.py -""" -import ast -import re -import sys -import os -from typing import Any - -BASE: str = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) -stats: dict[str, Any] = {"auto_none": 0, "manual_sig": 0, "vars": 0, "errors": []} - -def abs_path(filename: str) -> str: - return os.path.join(BASE, filename) - -def has_value_return(node: ast.AST) -> bool: - """Check if function has any 'return ' (not bare return or return None).""" - for child in ast.walk(node): - if isinstance(child, ast.Return) and child.value is not None: - if isinstance(child.value, ast.Constant) and child.value.value is None: - continue - return True - return False - -def collect_auto_none(tree: ast.Module) -> list[tuple[str, ast.AST]]: - """Collect functions that can safely get -> None annotation.""" - results = [] - - def scan(scope, prefix=""): - for node in ast.iter_child_nodes(scope): - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - name = f"{prefix}{node.name}" if prefix else node.name - if node.returns is None and not has_value_return(node): - untyped = [a.arg for a in node.args.args if a.arg not in ("self", "cls") and a.annotation is None] - if not untyped: - results.append((name, node)) - if isinstance(node, ast.ClassDef): - scan(node, prefix=f"{node.name}.") - scan(tree) - return results - -def apply_return_none_single_pass(filepath: str) -> int: - """Add -> None to all qualifying functions in a single pass.""" - fp = abs_path(filepath) - with open(fp, 'r', encoding='utf-8') as f: - code = f.read() - tree = ast.parse(code) - candidates = collect_auto_none(tree) - if not candidates: - return 0 - lines = code.splitlines(keepends=True) - # For each candidate, find the line with the colon that ends the signature - # and insert ' -> None' before it. - # We need to find the colon on the signature line (not inside default values etc.) - # Strategy: the signature ends at body[0].lineno - 1 (last sig line) - # Find the last ':' on that line that's at the right indentation - edits = [] # (line_idx, col_of_colon) - for name, node in candidates: - if not node.body: - continue - # The colon is on the last line of the signature - # For single-line defs: `def foo(self):` -> colon at end - # For multi-line defs: last line ends with `):` or similar - body_start = node.body[0].lineno # 1-indexed - sig_last_line_idx = body_start - 2 # 0-indexed, the line before body - # But for single-line signatures, sig_last_line_idx == node.lineno - 1 - if sig_last_line_idx < node.lineno - 1: - sig_last_line_idx = node.lineno - 1 - line = lines[sig_last_line_idx] - # Find the last colon on this line (the def colon) - # Must handle cases like `def foo(self, x: int):` where there are colons in annotations - # The def colon is always the LAST colon on the line (before any comment) - stripped = line.rstrip('\n\r') - # Remove inline comment - comment_pos = -1 - in_str = False - str_char = None - for i, c in enumerate(stripped): - if in_str: - if c == str_char: - in_str = False - continue - if c in ('"', "'"): - in_str = True - str_char = c - continue - if c == '#': - comment_pos = i - break - code_part = stripped[:comment_pos] if comment_pos >= 0 else stripped - # Find last colon in code_part - colon_idx = code_part.rfind(':') - if colon_idx < 0: - stats["errors"].append(f"no colon found: {filepath}:{name} L{sig_last_line_idx+1}") - continue - # Check not already annotated - if '->' in code_part: - continue - edits.append((sig_last_line_idx, colon_idx)) - # Apply edits in reverse order to preserve line indices - edits.sort(key=lambda x: x[0], reverse=True) - count = 0 - for line_idx, colon_col in edits: - line = lines[line_idx] - new_line = line[:colon_col] + ' -> None' + line[colon_col:] - lines[line_idx] = new_line - count += 1 - with open(fp, 'w', encoding='utf-8', newline='') as f: - f.writelines(lines) - return count - # --- Manual signature replacements --- - # These use regex on the def line to do a targeted replacement. - # Each entry: (dotted_name, old_params_pattern, new_full_sig_line) - # We match by finding the exact def line and replacing it. - -def apply_manual_sigs(filepath: str, sig_replacements: list[tuple[str, str]]) -> int: - """Apply manual signature replacements. - sig_replacements: list of (regex_pattern_for_old_line, replacement_line) - """ - fp = abs_path(filepath) - with open(fp, 'r', encoding='utf-8') as f: - code = f.read() - count = 0 - for pattern, replacement in sig_replacements: - new_code = re.sub(pattern, replacement, code, count=1) - if new_code != code: - code = new_code - count += 1 - else: - stats["errors"].append(f"manual_sig no match: {filepath}: {pattern[:60]}") - with open(fp, 'w', encoding='utf-8', newline='') as f: - f.write(code) - return count - -def apply_var_replacements(filepath: str, var_replacements: list[tuple[str, str]]) -> int: - """Apply variable declaration replacements. - var_replacements: list of (regex_pattern_for_old_decl, replacement_decl) - """ - fp = abs_path(filepath) - with open(fp, 'r', encoding='utf-8') as f: - code = f.read() - count = 0 - for pattern, replacement in var_replacements: - new_code = re.sub(pattern, replacement, code, count=1) - if new_code != code: - code = new_code - count += 1 - else: - stats["errors"].append(f"var no match: {filepath}: {pattern[:60]}") - with open(fp, 'w', encoding='utf-8', newline='') as f: - f.write(code) - return count - -def verify_syntax(filepath: str) -> str: - fp = abs_path(filepath) - try: - with open(fp, 'r', encoding='utf-8') as f: - code = f.read() - ast.parse(code) - return f"Syntax OK: {filepath}" - except SyntaxError as e: - return f"SyntaxError in {filepath} at line {e.lineno}: {e.msg}" - # ============================================================ - # gui_2.py manual signatures (Tier 3 items) - # ============================================================ -GUI2_MANUAL_SIGS: list[tuple[str, str]] = [ - (r'def resolve_pending_action\(self, action_id: str, approved: bool\):', - r'def resolve_pending_action(self, action_id: str, approved: bool) -> bool:'), - (r'def _cb_start_track\(self, user_data=None\):', - r'def _cb_start_track(self, user_data: Any = None) -> None:'), - (r'def _start_track_logic\(self, track_data\):', - r'def _start_track_logic(self, track_data: dict[str, Any]) -> None:'), - (r'def _cb_ticket_retry\(self, ticket_id\):', - r'def _cb_ticket_retry(self, ticket_id: str) -> None:'), - (r'def _cb_ticket_skip\(self, ticket_id\):', - r'def _cb_ticket_skip(self, ticket_id: str) -> None:'), - (r'def _render_ticket_dag_node\(self, ticket, tickets_by_id, children_map, rendered\):', - r'def _render_ticket_dag_node(self, ticket: Ticket, tickets_by_id: dict[str, Ticket], children_map: dict[str, list[str]], rendered: set[str]) -> None:'), -] - -# ============================================================ -# gui_2.py variable type annotations -# ============================================================ -GUI2_VAR_REPLACEMENTS: list[tuple[str, str]] = [ - (r'^CONFIG_PATH = ', 'CONFIG_PATH: Path = '), - (r'^PROVIDERS = ', 'PROVIDERS: list[str] = '), - (r'^COMMS_CLAMP_CHARS = ', 'COMMS_CLAMP_CHARS: int = '), - (r'^C_OUT = ', 'C_OUT: tuple[float, ...] = '), - (r'^C_IN = ', 'C_IN: tuple[float, ...] = '), - (r'^C_REQ = ', 'C_REQ: tuple[float, ...] = '), - (r'^C_RES = ', 'C_RES: tuple[float, ...] = '), - (r'^C_TC = ', 'C_TC: tuple[float, ...] = '), - (r'^C_TR = ', 'C_TR: tuple[float, ...] = '), - (r'^C_TRS = ', 'C_TRS: tuple[float, ...] = '), - (r'^C_LBL = ', 'C_LBL: tuple[float, ...] = '), - (r'^C_VAL = ', 'C_VAL: tuple[float, ...] = '), - (r'^C_KEY = ', 'C_KEY: tuple[float, ...] = '), - (r'^C_NUM = ', 'C_NUM: tuple[float, ...] = '), - (r'^C_SUB = ', 'C_SUB: tuple[float, ...] = '), - (r'^DIR_COLORS = ', 'DIR_COLORS: dict[str, tuple[float, ...]] = '), - (r'^KIND_COLORS = ', 'KIND_COLORS: dict[str, tuple[float, ...]] = '), - (r'^HEAVY_KEYS = ', 'HEAVY_KEYS: set[str] = '), - (r'^DISC_ROLES = ', 'DISC_ROLES: list[str] = '), - (r'^AGENT_TOOL_NAMES = ', 'AGENT_TOOL_NAMES: list[str] = '), -] - -if __name__ == "__main__": - print("=== Phase A: Auto-apply -> None (single-pass AST) ===") - n = apply_return_none_single_pass("gui_2.py") - stats["auto_none"] += n - print(f" gui_2.py: {n} applied") - # Verify syntax after Phase A - r = verify_syntax("gui_2.py") - if "Error" in r: - print(f" ABORT: {r}") - sys.exit(1) - print(" Syntax OK after Phase A") - print("\n=== Phase B: Manual signatures (regex) ===") - n = apply_manual_sigs("gui_2.py", GUI2_MANUAL_SIGS) - stats["manual_sig"] += n - print(f" gui_2.py: {n} applied") - # Verify syntax after Phase B - r = verify_syntax("gui_2.py") - if "Error" in r: - print(f" ABORT: {r}") - sys.exit(1) - print(" Syntax OK after Phase B") - print("\n=== Phase C: Variable annotations (regex) ===") - # Use re.MULTILINE so ^ matches line starts - - def apply_var_replacements_m(filepath, replacements): - fp = abs_path(filepath) - with open(fp, 'r', encoding='utf-8') as f: - code = f.read() - count = 0 - for pattern, replacement in replacements: - new_code = re.sub(pattern, replacement, code, count=1, flags=re.MULTILINE) - if new_code != code: - code = new_code - count += 1 - else: - stats["errors"].append(f"var no match: {filepath}: {pattern[:60]}") - with open(fp, 'w', encoding='utf-8', newline='') as f: - f.write(code) - return count - n = apply_var_replacements_m("gui_2.py", GUI2_VAR_REPLACEMENTS) - stats["vars"] += n - print(f" gui_2.py: {n} applied") - print("\n=== Final Syntax Verification ===") - r = verify_syntax("gui_2.py") - print(f" gui_2.py: {r}") - all_ok = "Error" not in r - print("\n=== Summary ===") - print(f" Auto -> None: {stats['auto_none']}") - print(f" Manual sigs: {stats['manual_sig']}") - print(f" Variables: {stats['vars']}") - print(f" Errors: {len(stats['errors'])}") - if stats['errors']: - print("\n=== Errors ===") - for e in stats['errors']: - print(f" {e}") - if all_ok: - print("\nAll files pass syntax check.") - else: - print("\nSYNTAX ERRORS DETECTED — review and fix before committing.") diff --git a/scripts/gut_oop_final.py b/scripts/gut_oop_final.py deleted file mode 100644 index c3a3450e..00000000 --- a/scripts/gut_oop_final.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import annotations -import ast -import sys -import re - -def gut_file(file_path: str) -> None: - """Removes delegation wrappers and fixes all calls in the file.""" - with open(file_path, "r", encoding="utf-8") as f: - lines = f.read().splitlines() - content = "\n".join(lines) - tree = ast.parse(content) - app_class = next((n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "App"), None) - if not app_class: return - - # 1. Identify all _render_xxx methods that have module-level counterparts - render_methods = [ - m for m in app_class.body - if isinstance(m, ast.FunctionDef) and m.name.startswith("_render_") and m.name != "_render_window_if_open" - ] - render_names = {m.name for m in render_methods} - - # Process methods in reverse to keep line numbers valid - render_methods.sort(key=lambda x: x.lineno, reverse=True) - - for m in render_methods: - s_idx = m.lineno - 1 - e_idx = m.end_lineno - if m.decorator_list: s_idx = m.decorator_list[0].lineno - 1 - del lines[s_idx:e_idx] - - # 2. Fix all calls in the remaining lines - for i in range(len(lines)): - for m_name in render_names: - t_func = m_name.lstrip("_") - # Replace self._render_xxx( with render_xxx(self, - lines[i] = lines[i].replace(f"self.{m_name}(", f"{t_func}(self, ") - lines[i] = lines[i].replace(f"app.{m_name}(", f"{t_func}(app, ") - # Clean up (self, ) or (app, ) - lines[i] = lines[i].replace("(self, )", "(self)") - lines[i] = lines[i].replace("(app, )", "(app)") - - final_content = "\n".join(lines) + "\n" - with open(file_path, "w", encoding="utf-8") as f: - f.write(final_content) - print(f"Successfully GUTTED {file_path}") - -if __name__ == "__main__": - gut_file(sys.argv[1]) diff --git a/scripts/restore_regions_final.py b/scripts/restore_regions_final.py deleted file mode 100644 index 8f54cf52..00000000 --- a/scripts/restore_regions_final.py +++ /dev/null @@ -1,78 +0,0 @@ -import ast -import sys -import re - -def restore(): - file_path = "src/gui_2.py" - with open(file_path, "r", encoding="utf-8") as f: - content = f.read() - - # Comprehensive region mapping for ALL render functions - region_map = { - "Main Interface": ["render_main_interface", "render_custom_title_bar"], - "Diagnostics & Analytics": ["render_usage_analytics_panel", "render_diagnostics_panel", "render_cache_panel", "render_tool_analytics_panel", "render_token_budget_panel"], - "Logging": ["render_log_management"], - "Project Management": ["render_project_settings_hub", "render_projects_panel", "render_paths_panel", "render_path_field"], - "AI Settings": ["render_ai_settings_hub", "render_rag_panel", "render_system_prompts_panel", "render_agent_tools_panel", "render_provider_panel", "render_persona_selector_panel", "render_base_prompt_diff_modal", "render_save_preset_modal", "render_preset_manager_content", "render_preset_manager_window", "render_tool_preset_manager_content", "render_tool_preset_manager_window", "render_persona_editor_window"], - "Context Management": ["render_files_and_media", "render_files_panel", "render_screenshots_panel", "render_context_batch_actions", "render_add_context_files_modal", "render_context_composition_panel", "render_ast_inspector_modal", "render_save_workspace_profile_modal", "render_context_presets_panel", "render_context_screenshots", "render_context_files_table", "render_context_presets", "render_snapshot_tab"], - "Discussions": ["render_discussion_hub", "render_discussion_entry", "render_discussion_entry_read_mode", "render_history_window", "render_session_insights_panel", "render_prior_session_view", "render_thinking_indicator", "render_synthesis_panel", "render_comms_history_panel", "render_takes_panel", "render_discussion_entries", "render_discussion_entry_controls", "render_discussion_metadata", "render_discussion_panel", "render_discussion_roles", "render_discussion_selector", "render_discussion_tab"], - "Operations Monitor": ["render_operations_hub", "render_message_panel", "render_response_panel", "render_tool_calls_panel", "render_external_tools_panel", "render_text_viewer_window", "render_patch_modal", "render_external_editor_panel", "render_approve_script_modal"], - "Misc Tools": ["render_theme_panel", "render_shader_live_editor", "render_markdown_test", "render_error_tint", "render_text_viewer", "render_heavy_text", "render_thinking_trace", "render_selectable_label", "render_window_if_open"], - "MMA": ["render_mma_dashboard", "render_mma_modals", "render_mma_track_summary", "render_mma_epic_planner", "render_mma_conductor_setup", "render_mma_track_browser", "render_mma_global_controls", "render_mma_usage_section", "render_mma_ticket_editor", "render_mma_agent_streams", "render_tier_stream_panel", "render_track_proposal_modal", "render_ticket_queue", "render_task_dag_panel", "render_beads_tab", "render_mma_focus_selector"] - } - - # Parse to get exact line ranges - lines = content.splitlines() - tree = ast.parse(content) - - func_blocks = {} - - for node in tree.body: - if isinstance(node, ast.FunctionDef): - f_name = node.name - s_idx = node.lineno - 1 - if node.decorator_list: s_idx = node.decorator_list[0].lineno - 1 - e_idx = node.end_lineno - func_blocks[f_name] = "\n".join(lines[s_idx:e_idx]) - - # Find the start of module-level render functions - first_render_idx = len(lines) - for node in tree.body: - if isinstance(node, ast.FunctionDef) and node.name.startswith("render_"): - first_render_idx = min(first_render_idx, node.lineno - 1) - if node.decorator_list: first_render_idx = min(first_render_idx, node.decorator_list[0].lineno - 1) - - # Get prefix and strip existing region tags - prefix_raw = lines[:first_render_idx] - prefix = [] - for line in prefix_raw: - if line.strip().startswith("#region:") or line.strip().startswith("#endregion:"): - continue - prefix.append(line) - - # Re-assemble with regions - assembled = [] - assigned = set() - for r_name, funcs in region_map.items(): - group = [] - for f in funcs: - if f in func_blocks: - group.append(func_blocks[f]) - assigned.add(f) - if group: - assembled.append(f"#region: {r_name}\n\n" + "\n\n".join(group) + f"\n\n#endregion: {r_name}") - - # Append unassigned render functions - for f_name, block in func_blocks.items(): - if f_name.startswith("render_") and f_name not in assigned: - assembled.append(block) - - final_content = "\n".join(prefix).strip() + "\n\n" + "\n\n".join(assembled) + "\n" - final_content = re.sub(r"\n\n\n+", "\n\n", final_content) - - with open(file_path, "w", encoding="utf-8") as f: - f.write(final_content) - print("Comprehensive regions restored successfully.") - -if __name__ == "__main__": - restore() diff --git a/scripts/transform_render_methods.py b/scripts/transform_render_methods.py deleted file mode 100644 index 6d15c51f..00000000 --- a/scripts/transform_render_methods.py +++ /dev/null @@ -1,92 +0,0 @@ -from __future__ import annotations -import ast -import sys -import os -import re - -def transform_file(file_path: str) -> None: - """ - Refactors App._render_xxx methods to module-level functions for hot-reloading. - Now correctly renames 'self' to 'app' inside function bodies. - """ - with open(file_path, "r", encoding="utf-8") as f: - content = f.read() - tree = ast.parse(content) - - class VariableRenamer(ast.NodeTransformer): - def visit_Name(self, node: ast.Name) -> ast.Name: - if node.id == "self": - node.id = "app" - return node - - class RenderTransformer(ast.NodeTransformer): - def __init__(self): - self.new_functions = [] - def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: - if node.name != "App": return node - new_body = [] - for item in node.body: - if isinstance(item, ast.FunctionDef) and item.name.startswith("_render_") and item.name != "_render_window_if_open": - func_name = item.name.lstrip("_") - # Transform body: rename 'self' to 'app' - renamer = VariableRenamer() - new_body_nodes = [renamer.visit(b) for b in item.body] - - new_func = ast.FunctionDef( - name=func_name, - args=item.args, - body=new_body_nodes, - decorator_list=item.decorator_list, - returns=item.returns - ) - if new_func.args.args and new_func.args.args[0].arg == "self": - new_func.args.args[0].arg = "app" - self.new_functions.append(new_func) - - # Create delegation wrapper in class - wrapper_body = [ - ast.Expr( - value=ast.Call( - func=ast.Name(id=func_name, ctx=ast.Load()), - args=[ast.Name(id="self", ctx=ast.Load())] + [ast.Name(id=a.arg, ctx=ast.Load()) for a in item.args.args[1:]], - keywords=[ast.keyword(arg=kw.arg, value=ast.Name(id=kw.arg, ctx=ast.Load())) for kw in item.args.kwonlyargs] - ) - ) - ] - item.body = wrapper_body - new_body.append(item) - else: - new_body.append(item) - node.body = new_body - return node - - transformer = RenderTransformer() - new_tree = transformer.visit(tree) - for func in transformer.new_functions: - new_tree.body.append(func) - ast.fix_missing_locations(new_tree) - - if hasattr(ast, "unparse"): - raw_output = ast.unparse(new_tree) - lines = raw_output.splitlines() - new_lines = [] - for line in lines: - match = re.match(r"^( +)(.*)", line) - if match: - spaces, rest = match.groups() - new_indent = " " * (len(spaces) // 4) - new_lines.append(new_indent + rest) - else: - new_lines.append(line) - final_content = "\n".join(new_lines) - with open(file_path, "w", encoding="utf-8") as f: - f.write(final_content) - print(f"Successfully transformed {file_path} with 1-space indentation and 'self'->'app' migration.") - else: - print("ast.unparse not available.") - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: python scripts/transform_render_methods.py ") - else: - transform_file(sys.argv[1]) diff --git a/scripts/transform_render_methods_safe.py b/scripts/transform_render_methods_safe.py deleted file mode 100644 index 950930e2..00000000 --- a/scripts/transform_render_methods_safe.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations -import ast -import sys -import re - -def transform_file(file_path: str) -> None: - with open(file_path, "r", encoding="utf-8") as f: - lines = f.read().splitlines() - content = "\n".join(lines) - tree = ast.parse(content) - app_class = next((n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == "App"), None) - if not app_class: return - - render_methods = [ - m for m in app_class.body - if isinstance(m, ast.FunctionDef) and m.name.startswith("_render_") and m.name != "_render_window_if_open" - ] - render_names = {m.name for m in render_methods} - render_methods.sort(key=lambda x: x.lineno, reverse=True) - - extracted = [] - for m in render_methods: - s_idx = m.lineno - 1 - e_idx = m.end_lineno - if m.decorator_list: s_idx = m.decorator_list[0].lineno - 1 - m_lines = lines[s_idx:e_idx] - func_name = m.name.lstrip("_") - - # 1. Out-dent - processed = [l[1:] if l.startswith(" ") else l for l in m_lines] - def_line_idx = next((i for i, l in enumerate(processed) if l.strip().startswith("def ")), -1) - if def_line_idx != -1: - l = processed[def_line_idx] - l = l.replace(f"def {m.name}(", f"def {func_name}(", 1) - l = re.sub(r"\bself\b", "app: App", l, count=1) - processed[def_line_idx] = l - - # 2. Redirect internal calls in this function's body - for i in range(def_line_idx + 1, len(processed)): - for m_name in render_names: - t_func = m_name.lstrip("_") - processed[i] = processed[i].replace(f"self.{m_name}(", f"{t_func}(app, ") - processed[i] = processed[i].replace("(app, )", "(app)") - processed[i] = re.sub(r"(?