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:
@@ -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()
|
||||
@@ -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.")
|
||||
@@ -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])
|
||||
@@ -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()
|
||||
@@ -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])
|
||||
@@ -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])
|
||||
Reference in New Issue
Block a user