#!/usr/bin/env python3 """Trace Functions — Find dead functions (defined but never called).""" from __future__ import annotations import ast, re, sys, json from pathlib import Path ROOT = Path(__file__).resolve().parent.parent.parent APP = ROOT / "app" FRONTEND = ROOT / "frontend" / "src" # Critical functions that MUST be called CRITICAL_FUNCTIONS = [ "seed_admin", "seed_default_workspace", "sync_plugin_schema", "init_permission_registry", "register_default_entities", "register_default_history_hooks", "discover_builtins", "load_tmp_chats", "initialize_agent", ] def find_function_defs_py() -> list[dict]: defs = [] for py in APP.rglob("*.py"): try: content = py.read_text(); tree = ast.parse(content) except: continue rel = str(py.relative_to(ROOT)) for node in ast.walk(tree): if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): if not node.name.startswith("_") and len(node.name) > 3: defs.append({"name": node.name, "file": rel, "line": node.lineno, "lang": "py"}) return defs def find_function_calls_py() -> set[str]: calls = set() for py in APP.rglob("*.py"): try: content = py.read_text(); tree = ast.parse(content) except: continue for node in ast.walk(tree): if isinstance(node, ast.Call): if isinstance(node.func, ast.Name): calls.add(node.func.id) elif isinstance(node.func, ast.Attribute): calls.add(node.func.attr) return calls def find_function_defs_ts() -> list[dict]: defs = [] for ts in FRONTEND.rglob("*.ts"): try: content = ts.read_text() except: continue rel = str(ts.relative_to(ROOT)) for m in re.finditer(r'(?:export\s+)?(?:async\s+)?function\s+(\w+)', content): name = m.group(1) if not name.startswith("_") and len(name) > 3: defs.append({"name": name, "file": rel, "lang": "ts"}) for m in re.finditer(r'(?:export\s+)?const\s+(\w+)\s*=', content): name = m.group(1) if not name.startswith("_") and len(name) > 3: defs.append({"name": name, "file": rel, "lang": "ts"}) return defs def find_function_calls_ts() -> set[str]: calls = set() for ts in FRONTEND.rglob("*.ts"): try: content = ts.read_text() except: continue # Find function calls: word followed by ( for m in re.finditer(r'\b(\w+)\s*\(', content): calls.add(m.group(1)) return calls def main(): print("=" * 70) print("TRACE FUNCTIONS — Dead Functions (defined but never called)") print("=" * 70) py_defs = find_function_defs_py() py_calls = find_function_calls_py() ts_defs = find_function_defs_ts() ts_calls = find_function_calls_ts() py_dead = [d for d in py_defs if d["name"] not in py_calls] ts_dead = [d for d in ts_defs if d["name"] not in ts_calls] critical_dead = [d for d in py_dead if d["name"] in CRITICAL_FUNCTIONS] print(f"\nPython functions defined: {len(py_defs)}") print(f"Python functions called: {len(py_calls)}") print(f"Python dead functions: {len(py_dead)}") print(f"TS functions defined: {len(ts_defs)}") print(f"TS functions called: {len(ts_calls)}") print(f"TS dead functions: {len(ts_dead)}") print(f"\nCRITICAL dead functions: {len(critical_dead)}") if critical_dead: print("\n--- CRITICAL: Functions that MUST be called but aren't ---") for d in critical_dead: print(f" {d['name']} (in {d['file']}:{d.get('line','?')})") results = {"py_defs": len(py_defs), "py_dead": len(py_dead), "ts_defs": len(ts_defs), "ts_dead": len(ts_dead), "critical_dead": critical_dead} with open(ROOT / "scripts" / "test_suite" / "results_trace_functions.json", "w") as f: json.dump(results, f, indent=2, default=str) return len(critical_dead) if __name__ == "__main__": sys.exit(main())