Private
Public Access
0
0

chore(scripts): remove one-shot transform scripts

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.
This commit is contained in:
2026-06-07 10:39:31 -04:00
parent 62214e3cae
commit dfbde954c3
6 changed files with 0 additions and 749 deletions
-197
View File
@@ -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()
-269
View File
@@ -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 <expr>' (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.")
-48
View File
@@ -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])
-78
View File
@@ -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()
-92
View File
@@ -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 <file_path>")
else:
transform_file(sys.argv[1])
-65
View File
@@ -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"(?<![a-zA-Z0-9_])self\.", "app.", processed[i])
processed[i] = re.sub(r"(?<![a-zA-Z0-9_])self(?![a-zA-Z0-9_:])", "app", processed[i])
extracted.append("\n".join(processed))
# REMOVE from class
del lines[s_idx:e_idx]
# 3. Redirect calls in remaining class methods
for i in range(len(lines)):
for m_name in render_names:
t_func = m_name.lstrip("_")
lines[i] = lines[i].replace(f"self.{m_name}(", f"{t_func}(self, ")
lines[i] = lines[i].replace("(self, )", "(self)")
extracted.reverse()
final_content = "\n".join(lines) + "\n\n" + "\n\n".join(extracted) + "\n"
with open(file_path, "w", encoding="utf-8") as f:
f.write(final_content)
print(f"Successfully GUTTED {file_path}")
if __name__ == "__main__":
transform_file(sys.argv[1])