"""Audit: fail if any _send_ in src/ai_client.py contains an inline tool-call loop (i.e., a for loop with MAX_TOOL_ROUNDS in it). The follow-up track's invariant: all tool loops should go through run_with_tool_loop. Inline loops are forbidden EXCEPT for the 3 vendored-call-path vendors (anthropic, gemini, deepseek) which use their own SDKs and are tracked as deferred work (Phase 5 t5_6/7/8 in state.toml). Note: gemini_cli was migrated to run_with_tool_loop via send_func in commit 4748d134. The previous exclusion list incorrectly included 'gemini_native' (a non-existent function name); that was removed on 2026-06-11. Usage: uv run python scripts/audit_no_inline_tool_loops.py Exit code: 0 = pass; 1 = violations found. """ import re import sys from pathlib import Path TARGET = Path("src/ai_client.py") DEFERRED_VENDORS = frozenset(["anthropic", "gemini", "deepseek"]) def main() -> int: text = TARGET.read_text(encoding="utf-8") violations: list[str] = [] for match in re.finditer(r"^def (_send_\w+)\(", text, re.MULTILINE): func_name: str = match.group(1) vendor = func_name[len("_send_"):] if vendor in DEFERRED_VENDORS: continue func_start = match.start() next_def = re.search(r"\n(?:def|async def) _send_\w+\(", text[func_start + 1:]) func_end = func_start + 1 + (next_def.start() if next_def else len(text) - func_start - 1) func_body = text[func_start:func_end] if "for _round_idx in range(MAX_TOOL_ROUNDS" in func_body or "for round_idx in range(MAX_TOOL_ROUNDS" in func_body: if "run_with_tool_loop" not in func_body: violations.append(vendor) if violations: print(f"FAIL: {len(violations)} vendor(s) have inline tool loops: {violations}") print("Use src.ai_client.run_with_tool_loop instead.") return 1 print("OK: all _send_ functions use run_with_tool_loop (deferred vendors excluded)") return 0 if __name__ == "__main__": sys.exit(main())