#!/usr/bin/env python3 """ADR compliance checker for CleverAgents. Verifies that code follows the architectural decisions recorded in ADRs. ADR-002: Asyncio Concurrency Model - Async functions should use 'async def', not threading - No direct thread usage in application layer ADR-003: Dependency Injection Framework - Services should receive dependencies via __init__ - No direct instantiation of infrastructure in application layer ADR-007: Repository Pattern for Persistence - Database access only through repository classes - No direct SQLAlchemy session usage in services Usage: python scripts/check-adr-compliance.py """ import ast import sys from pathlib import Path def check_adr002_async_model(src_dir: Path) -> list[str]: """Check ADR-002: Asyncio Concurrency Model. Verifies no direct threading usage in application code. """ violations: list[str] = [] app_dir = src_dir / "application" if not app_dir.exists(): return violations for py_file in app_dir.rglob("*.py"): content = py_file.read_text() if "import threading" in content or "from threading import" in content: violations.append( f"ADR-002 violation: {py_file.relative_to(src_dir)} " "imports threading directly. Use asyncio instead." ) return violations def check_adr003_dependency_injection(src_dir: Path) -> list[str]: """Check ADR-003: Dependency Injection Framework. Verifies services use constructor injection. """ violations: list[str] = [] services_dir = src_dir / "application" / "services" if not services_dir.exists(): return violations for py_file in services_dir.rglob("*.py"): if py_file.name.startswith("__"): continue try: tree = ast.parse(py_file.read_text()) except SyntaxError: continue for node in ast.walk(tree): if isinstance(node, ast.ClassDef): # Check if class has __init__ with parameters (DI) has_init = False for item in node.body: if isinstance(item, ast.FunctionDef) and item.name == "__init__": has_init = True # Check for at least one parameter beyond self if len(item.args.args) < 2: violations.append( f"ADR-003 note: {py_file.relative_to(src_dir)}:" f"{node.lineno} class {node.name} has __init__ " "with no injected dependencies" ) if not has_init and node.body: # Services without __init__ may be okay if they're utility classes pass return violations def check_adr007_repository_pattern(src_dir: Path) -> list[str]: """Check ADR-007: Repository Pattern for Persistence. Verifies no direct SQLAlchemy session usage in service layer. """ violations: list[str] = [] services_dir = src_dir / "application" / "services" if not services_dir.exists(): return violations for py_file in services_dir.rglob("*.py"): content = py_file.read_text() # Check for direct session imports if "from sqlalchemy" in content: violations.append( f"ADR-007 violation: {py_file.relative_to(src_dir)} " "imports SQLAlchemy directly. Use repository pattern instead." ) if "session.query" in content or "session.execute" in content: violations.append( f"ADR-007 violation: {py_file.relative_to(src_dir)} " "uses session directly. Access data through repositories." ) return violations def main() -> int: """Run all ADR compliance checks.""" src_dir = Path("src/cleveragents") if not src_dir.exists(): print("Error: src/cleveragents directory not found") return 1 all_violations: list[str] = [] print("Checking ADR-002: Asyncio Concurrency Model...") all_violations.extend(check_adr002_async_model(src_dir)) print("Checking ADR-003: Dependency Injection Framework...") all_violations.extend(check_adr003_dependency_injection(src_dir)) print("Checking ADR-007: Repository Pattern for Persistence...") all_violations.extend(check_adr007_repository_pattern(src_dir)) if all_violations: print(f"\nFound {len(all_violations)} ADR compliance issue(s):") for violation in all_violations: print(f" - {violation}") return 1 print("\nAll ADR compliance checks passed.") return 0 if __name__ == "__main__": sys.exit(main())