7ef5ebb695
This should automatically check for problems on build.
141 lines
4.7 KiB
Python
141 lines
4.7 KiB
Python
#!/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())
|