#!/usr/bin/env python3 """ Unified Code Quality Hook - Dispatcher for Quality Checks Consolidates 5 code quality hooks into one dispatcher: - auto_format.py (code formatting) - auto_test.py (test execution) - security_scan.py (secret/vulnerability scanning) - enforce_tdd.py (TDD workflow validation) - auto_enforce_coverage.py (coverage enforcement) Hook: PreCommit (runs before git commit completes) Environment Variables (opt-in/opt-out): AUTO_FORMAT=true/false (default: true) AUTO_TEST=true/false (default: true) SECURITY_SCAN=true/false (default: true) ENFORCE_TDD=true/false (default: false, requires strict_mode) ENFORCE_COVERAGE=true/false (default: false) Exit codes: 0: All enabled checks passed 1: One or more checks failed (non-blocking) 2: Critical failure (blocks commit) Usage: # As PreCommit hook (automatic) python unified_code_quality.py # Manual run with specific checks AUTO_FORMAT=false python unified_code_quality.py """ import os import subprocess import sys from pathlib import Path from typing import Callable, List, Tuple, Optional # ============================================================================ # Dynamic Library Discovery # ============================================================================ def find_lib_dir() -> Optional[Path]: """ Find the lib directory dynamically. Searches: 1. Relative to this file: ../lib 2. In project root: plugins/autonomous-dev/lib 3. In global install: ~/.autonomous-dev/lib Returns: Path to lib directory or None if not found """ candidates = [ Path(__file__).parent.parent / "lib", # Relative to hooks/ Path.cwd() / "plugins" / "autonomous-dev" / "lib", # Project root Path.home() / ".autonomous-dev" / "lib", # Global install ] for candidate in candidates: if candidate.exists(): return candidate return None # Add lib to path LIB_DIR = find_lib_dir() if LIB_DIR: sys.path.insert(0, str(LIB_DIR)) # Optional imports with graceful fallback try: from error_messages import formatter_not_found_error, print_warning HAS_ERROR_MESSAGES = True except ImportError: HAS_ERROR_MESSAGES = False def print_warning(msg: str) -> None: print(f"āš ļø {msg}", file=sys.stderr) # ============================================================================ # Configuration # ============================================================================ # Check configuration from environment AUTO_FORMAT = os.environ.get("AUTO_FORMAT", "true").lower() == "true" AUTO_TEST = os.environ.get("AUTO_TEST", "true").lower() == "true" SECURITY_SCAN = os.environ.get("SECURITY_SCAN", "true").lower() == "true" ENFORCE_TDD = os.environ.get("ENFORCE_TDD", "false").lower() == "true" ENFORCE_COVERAGE = os.environ.get("ENFORCE_COVERAGE", "false").lower() == "true" # ============================================================================ # Individual Check Functions # ============================================================================ def check_format() -> Tuple[bool, str]: """ Run code formatting checks. Returns: (success, message) tuple """ try: hook_path = Path(__file__).parent / "auto_format.py" if not hook_path.exists(): return True, "[SKIP] auto_format.py not found" result = subprocess.run( [sys.executable, str(hook_path)], capture_output=True, text=True, timeout=60 ) if result.returncode == 0: return True, "[PASS] Code formatting" else: return False, f"[FAIL] Code formatting\n{result.stderr}" except subprocess.TimeoutExpired: return False, "[FAIL] Code formatting timed out (60s)" except Exception as e: return True, f"[SKIP] Code formatting error: {e}" def check_tests() -> Tuple[bool, str]: """ Run test suite. Returns: (success, message) tuple """ try: hook_path = Path(__file__).parent / "auto_test.py" if not hook_path.exists(): return True, "[SKIP] auto_test.py not found" result = subprocess.run( [sys.executable, str(hook_path)], capture_output=True, text=True, timeout=300 # 5 minutes for tests ) if result.returncode == 0: return True, "[PASS] Test suite" else: return False, f"[FAIL] Test suite\n{result.stderr}" except subprocess.TimeoutExpired: return False, "[FAIL] Test suite timed out (300s)" except Exception as e: return True, f"[SKIP] Test suite error: {e}" def check_security() -> Tuple[bool, str]: """ Run security scanning. Returns: (success, message) tuple """ try: hook_path = Path(__file__).parent / "security_scan.py" if not hook_path.exists(): return True, "[SKIP] security_scan.py not found" result = subprocess.run( [sys.executable, str(hook_path)], capture_output=True, text=True, timeout=120 # 2 minutes for security scan ) if result.returncode == 0: return True, "[PASS] Security scan" elif result.returncode == 2: # Exit code 2 = critical security issue (blocks commit) return False, f"[FAIL] Security scan (CRITICAL)\n{result.stdout}" else: return False, f"[FAIL] Security scan\n{result.stderr}" except subprocess.TimeoutExpired: return False, "[FAIL] Security scan timed out (120s)" except Exception as e: return True, f"[SKIP] Security scan error: {e}" def check_tdd() -> Tuple[bool, str]: """ Validate TDD workflow. Returns: (success, message) tuple """ try: hook_path = Path(__file__).parent / "enforce_tdd.py" if not hook_path.exists(): return True, "[SKIP] enforce_tdd.py not found" result = subprocess.run( [sys.executable, str(hook_path)], capture_output=True, text=True, timeout=30 ) if result.returncode == 0: return True, "[PASS] TDD workflow" elif result.returncode == 2: # Exit code 2 = TDD violation (blocks commit) return False, f"[FAIL] TDD workflow (BLOCKS COMMIT)\n{result.stdout}" else: return False, f"[FAIL] TDD workflow\n{result.stderr}" except subprocess.TimeoutExpired: return False, "[FAIL] TDD workflow timed out (30s)" except Exception as e: return True, f"[SKIP] TDD workflow error: {e}" def check_coverage() -> Tuple[bool, str]: """ Enforce test coverage. Returns: (success, message) tuple """ try: hook_path = Path(__file__).parent / "auto_enforce_coverage.py" if not hook_path.exists(): return True, "[SKIP] auto_enforce_coverage.py not found" result = subprocess.run( [sys.executable, str(hook_path)], capture_output=True, text=True, timeout=300 # 5 minutes for coverage ) if result.returncode == 0: return True, "[PASS] Test coverage" elif result.returncode == 2: # Exit code 2 = coverage below threshold (blocks commit) return False, f"[FAIL] Test coverage (BLOCKS COMMIT)\n{result.stdout}" else: return False, f"[FAIL] Test coverage\n{result.stderr}" except subprocess.TimeoutExpired: return False, "[FAIL] Test coverage timed out (300s)" except Exception as e: return True, f"[SKIP] Test coverage error: {e}" # ============================================================================ # Dispatcher # ============================================================================ def run_quality_checks() -> int: """ Run all enabled quality checks. Returns: Exit code (0=success, 1=failure, 2=critical) """ print("šŸ” Running code quality checks...") print() # Define checks with their configuration checks: List[Tuple[bool, str, Callable[[], Tuple[bool, str]]]] = [ (AUTO_FORMAT, "Code Formatting", check_format), (AUTO_TEST, "Test Suite", check_tests), (SECURITY_SCAN, "Security Scan", check_security), (ENFORCE_TDD, "TDD Workflow", check_tdd), (ENFORCE_COVERAGE, "Test Coverage", check_coverage), ] # Track results results: List[Tuple[str, bool, str]] = [] has_failures = False has_critical_failures = False # Run enabled checks for enabled, name, check_fn in checks: if not enabled: print(f"[SKIP] {name} (disabled)") continue print(f"Running {name}...", end=" ", flush=True) success, message = check_fn() results.append((name, success, message)) if success: print("āœ“") else: print("āœ—") has_failures = True # Check if this is a critical failure (blocks commit) if "BLOCKS COMMIT" in message or "CRITICAL" in message: has_critical_failures = True # Print summary print() print("=" * 60) print("QUALITY CHECK SUMMARY") print("=" * 60) for name, success, message in results: print() print(f"{name}:") print(f" {message}") print() # Determine exit code if has_critical_failures: print("āŒ Critical failures detected - COMMIT BLOCKED") return 2 elif has_failures: print("āš ļø Some checks failed - review above") return 1 else: print("āœ… All quality checks passed") return 0 # ============================================================================ # Main Entry Point # ============================================================================ def main() -> int: """Main entry point.""" try: # Check if any checks are enabled if not any([AUTO_FORMAT, AUTO_TEST, SECURITY_SCAN, ENFORCE_TDD, ENFORCE_COVERAGE]): print("[SKIP] All quality checks disabled") return 0 return run_quality_checks() except KeyboardInterrupt: print("\nāš ļø Quality checks interrupted by user") return 1 except Exception as e: print(f"āš ļø Unexpected error in quality checks: {e}", file=sys.stderr) # Don't block commit on infrastructure errors return 0 if __name__ == "__main__": sys.exit(main())