#!/usr/bin/env python3 """ Type-checking hook for Claude Code Runs mypy/ruff on modified Python files """ import sys import json import subprocess from pathlib import Path def main(): try: # Read input from Claude Code input_data = json.loads(sys.stdin.read()) # Check if this is a file modification tool = input_data.get('tool', '') if tool not in ['edit', 'multi_edit', 'write']: return 0 # Get the file path result = input_data.get('result', {}) file_path = result.get('file_path', '') if not file_path: return 0 # Check if it's a Python file if not file_path.endswith('.py'): return 0 # Convert to Path object file_path = Path(file_path) if not file_path.exists(): return 0 # Run type checking with mypy mypy_result = subprocess.run( ['uv', 'run', 'mypy', str(file_path)], capture_output=True, text=True, cwd='/Users/enias/projects/my-ai-projects/apps/trax' ) # Run linting with ruff ruff_result = subprocess.run( ['uv', 'run', 'ruff', 'check', str(file_path)], capture_output=True, text=True, cwd='/Users/enias/projects/my-ai-projects/apps/trax' ) # Collect errors errors = [] if mypy_result.returncode != 0: errors.append(f"Type errors in {file_path}:\n{mypy_result.stdout}") if ruff_result.returncode != 0: errors.append(f"Linting errors in {file_path}:\n{ruff_result.stdout}") # Report errors if any if errors: error_msg = "\n\n".join(errors) print(f"❌ Quality Check Failed:\n\n{error_msg}", file=sys.stderr) # Exit code 2 = blocking error (Claude Code must fix) # Exit code 1 = warning (Claude Code is informed but continues) return 2 # Success message print(f"✅ Quality checks passed for {file_path}") return 0 except Exception as e: # Don't break Claude Code if hook fails print(f"Hook error (non-blocking): {e}", file=sys.stderr) return 0 if __name__ == "__main__": sys.exit(main())