#!/usr/bin/env python """ Audit top-level imports in src/gui_2.py and classify them. For each top-level `import X` or `from X import Y` statement in gui_2.py, report: - file:line - the imported module - whether it's at module level (always loaded on main thread) or inside a function (potentially feature-gated) This is a static analysis tool for the startup_speedup_20260606 track. The output is meant to be read by a human who knows which functions are first-frame vs feature-gated. Output format (text): MODULE-LEVEL imports (these run on the main thread's import chain): src/gui_2.py:1: import imgui_bundle src/gui_2.py:15: from src.app_controller import AppController ... FUNCTION-LEVEL imports (potentially feature-gated; candidates for _require_warmed): src/gui_2.py:42 (inside _render_command_palette): from src.command_palette import ... ... """ import ast import sys from pathlib import Path from typing import Iterable def classify_imports(source: str) -> tuple[list[tuple[int, str, str]], list[tuple[int, str, str, str]]]: """Parse a Python source and return (module_level, function_level) imports. Each entry is (line, imported_name, full_statement). """ tree = ast.parse(source) module_level: list[tuple[int, str, str]] = [] function_level: list[tuple[int, str, str, str]] = [] def imported_names(node: ast.stmt) -> list[str]: if isinstance(node, ast.Import): return [alias.name for alias in node.names] if isinstance(node, ast.ImportFrom): if not node.module or node.level != 0: return [] return [node.module] return [] for node in tree.body: names = imported_names(node) if not names: continue for name in names: stmt = ast.unparse(node).strip().replace("\n", " ") module_level.append((node.lineno, name, stmt)) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): for child in node.body: names = imported_names(child) if not names: continue for name in names: stmt = ast.unparse(child).strip().replace("\n", " ") function_level.append((child.lineno, node.name, name, stmt)) return module_level, function_level def render_report(source_path: Path) -> str: source = source_path.read_text(encoding="utf-8", errors="replace") module_level, function_level = classify_imports(source) lines: list[str] = [] lines.append(f"Audit of {source_path}") lines.append("=" * 80) lines.append("") lines.append(f"MODULE-LEVEL imports: {len(module_level)} (these run on the main thread's import chain)") lines.append("-" * 80) for lineno, name, stmt in module_level: lines.append(f" L{lineno:>5} {name:<40} {stmt[:60]}") lines.append("") lines.append(f"FUNCTION-LEVEL imports: {len(function_level)} (potentially feature-gated)") lines.append("-" * 80) if function_level: by_function: dict[str, list[tuple[int, str, str]]] = {} for lineno, fname, name, stmt in function_level: by_function.setdefault(fname, []).append((lineno, name, stmt)) for fname in sorted(by_function): entries = by_function[fname] lines.append(f" {fname} ({len(entries)} imports)") for lineno, name, stmt in entries: lines.append(f" L{lineno:>5} {name:<40} {stmt[:60]}") else: lines.append(" (none)") lines.append("") return "\n".join(lines) def main(argv: list[str]) -> int: if len(argv) < 2: print("usage: audit_gui2_imports.py ", file=sys.stderr) return 2 path = Path(argv[1]) if not path.exists(): print(f"file not found: {path}", file=sys.stderr) return 2 print(render_report(path)) return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))