From 0649677e4dcd61606c1195483a65462e15c15749 Mon Sep 17 00:00:00 2001 From: Travis Vasceannie Date: Wed, 17 Sep 2025 17:01:02 +0000 Subject: [PATCH] x --- hooks/__init__.py | 0 hooks/code_quality_guard.py | 3 +-- hooks/internal_duplicate_detector.py | 10 ++++++---- pyproject.toml | 2 +- src/quality/cli/main.py | 1 - src/quality/complexity/analyzer.py | 9 +++++++++ src/quality/core/exceptions.py | 12 +++++++++++- src/quality/detection/engine.py | 7 +++++++ tests/hooks/test_helper_functions.py | 7 ++++++- tests/hooks/test_integration.py | 20 +++++++++++--------- tests/hooks/test_posttooluse.py | 5 ++++- tests/hooks/test_pretooluse.py | 5 ++++- 12 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 hooks/__init__.py diff --git a/hooks/__init__.py b/hooks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hooks/code_quality_guard.py b/hooks/code_quality_guard.py index d42e187..aa8dc72 100644 --- a/hooks/code_quality_guard.py +++ b/hooks/code_quality_guard.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Unified quality hook for Claude Code supporting both PreToolUse and PostToolUse. Prevents writing duplicate, complex, or non-modernized code and verifies quality @@ -872,7 +871,7 @@ def pretooluse_hook(hook_data: JsonObject, config: QualityConfig) -> JsonObject: any_usage_issues = _detect_any_usage(content) try: - has_issues, issues = _perform_quality_check( + _has_issues, issues = _perform_quality_check( file_path, content, config, diff --git a/hooks/internal_duplicate_detector.py b/hooks/internal_duplicate_detector.py index 7cc06b0..e1f7bdf 100644 --- a/hooks/internal_duplicate_detector.py +++ b/hooks/internal_duplicate_detector.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Internal duplicate detection for analyzing code blocks within a single file. Uses AST analysis and multiple similarity algorithms to detect redundant patterns. @@ -12,7 +11,6 @@ from collections import defaultdict from dataclasses import dataclass from typing import Any - COMMON_DUPLICATE_METHODS = { "__init__", "__enter__", @@ -212,7 +210,9 @@ class InternalDuplicateDetector: return if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - block_type = "method" if isinstance(parent, ast.ClassDef) else "function" + block_type = ( + "method" if isinstance(parent, ast.ClassDef) else "function" + ) if block := create_block(node, block_type, lines): blocks.append(block) @@ -460,7 +460,9 @@ class InternalDuplicateDetector: return False if all(block.name in COMMON_DUPLICATE_METHODS for block in group.blocks): - max_lines = max(block.end_line - block.start_line + 1 for block in group.blocks) + max_lines = max( + block.end_line - block.start_line + 1 for block in group.blocks + ) max_complexity = max(block.complexity for block in group.blocks) # Allow simple lifecycle dunder methods to repeat across classes. diff --git a/pyproject.toml b/pyproject.toml index 63bd7a0..3121f42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,7 +79,7 @@ select = [ ignore = [ "D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107", "S101", "B008", "PLR0913", "TRY003", "ANN204", "TID252", "RUF012", - "PLC0415", "PTH123", "UP038", "PLW0603", "PLR0915", "PLR0912", + "PLC0415", "PTH123", "PLW0603", "PLR0915", "PLR0912", "PLR0911", "C901", "PLR2004", "PLW1514", "SIM108", "SIM117" ] fixable = ["ALL"] diff --git a/src/quality/cli/main.py b/src/quality/cli/main.py index 307815e..59f4412 100644 --- a/src/quality/cli/main.py +++ b/src/quality/cli/main.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """Main CLI interface for code quality analysis.""" import ast diff --git a/src/quality/complexity/analyzer.py b/src/quality/complexity/analyzer.py index 9d4a7ce..31f3001 100644 --- a/src/quality/complexity/analyzer.py +++ b/src/quality/complexity/analyzer.py @@ -1,5 +1,6 @@ """High-level complexity analysis interface.""" +import logging from pathlib import Path from typing import Any @@ -7,6 +8,8 @@ from ..config.schemas import ComplexityConfig from .metrics import ComplexityMetrics from .radon_integration import RadonComplexityAnalyzer +logger = logging.getLogger(__name__) + class ComplexityAnalyzer: """High-level interface for code complexity analysis.""" @@ -112,6 +115,12 @@ class ComplexityAnalyzer: ) ) if should_suppress: + if self.exception_filter.config.debug: + logger.debug( + "Suppressed complexity issue for %s (reason: %s)", + path, + reason or "", + ) continue summary = self.get_complexity_summary(metrics) diff --git a/src/quality/core/exceptions.py b/src/quality/core/exceptions.py index 93ce785..0bd8643 100644 --- a/src/quality/core/exceptions.py +++ b/src/quality/core/exceptions.py @@ -1,6 +1,7 @@ """Exception handling system for quality analysis.""" import fnmatch +import logging import re from collections.abc import Callable from datetime import UTC, datetime @@ -12,6 +13,8 @@ from ..config.schemas import ( QualityConfig, ) +logger = logging.getLogger(__name__) + class ExceptionFilter: """Filters analysis results based on configured exception rules.""" @@ -213,7 +216,14 @@ class ExceptionFilter: if not should_suppress: filtered_issues.append(issue) elif self.config.debug: - pass + logger.debug( + "Suppressed %s issue %s at %s:%s (reason: %s)", + analysis_type, + issue_type or "unknown", + file_path or "", + line_number or "", + reason or "", + ) return filtered_issues diff --git a/src/quality/detection/engine.py b/src/quality/detection/engine.py index 351d11f..30b2417 100644 --- a/src/quality/detection/engine.py +++ b/src/quality/detection/engine.py @@ -307,6 +307,13 @@ class DuplicateDetectionEngine: block.content, ) if should_suppress: + if self.config.debug: + logging.debug( + "Suppressed duplicate match in %s at line %s (reason: %s)", + block.file_path, + block.start_line, + reason or "", + ) should_suppress_match = True break diff --git a/tests/hooks/test_helper_functions.py b/tests/hooks/test_helper_functions.py index d299ecc..12bd3be 100644 --- a/tests/hooks/test_helper_functions.py +++ b/tests/hooks/test_helper_functions.py @@ -68,7 +68,9 @@ class TestHelperFunctions: store_pre_state(test_path, test_content) # Verify cache directory created - mock_mkdir.assert_called_once_with(exist_ok=True) + mock_mkdir.assert_called_once() + _, mkdir_kwargs = mock_mkdir.call_args + assert mkdir_kwargs.get("exist_ok") is True # Verify state was written mock_write.assert_called_once() @@ -226,6 +228,9 @@ class TestHelperFunctions: duplicate_enabled=False, complexity_enabled=False, modernization_enabled=False, + sourcery_enabled=False, + basedpyright_enabled=False, + pyrefly_enabled=False, ) with patch("code_quality_guard.detect_internal_duplicates") as mock_dup: diff --git a/tests/hooks/test_integration.py b/tests/hooks/test_integration.py index fb0dd6f..657aecd 100644 --- a/tests/hooks/test_integration.py +++ b/tests/hooks/test_integration.py @@ -6,6 +6,8 @@ import tempfile from pathlib import Path from unittest.mock import patch +import pytest + class TestHookIntegration: """Test complete hook integration scenarios.""" @@ -179,11 +181,9 @@ class TestHookIntegration: }, }, ): - try: + with pytest.raises(SystemExit) as exc_info: main() - raise AssertionError("Expected SystemExit") - except SystemExit as exc: - assert exc.code == 2 + assert exc_info.value.code == 2 response = json.loads(mock_print.call_args[0][0]) assert ( @@ -223,7 +223,11 @@ class TestHookIntegration: "tool_name": "Write", "tool_input": { "file_path": str(temp_python_file), - "content": "def func1(): pass\ndef func2(): pass\ndef func3(): pass", + "content": ( + "def func1(): pass\n" + "def func2(): pass\n" + "def func3(): pass" + ), }, } @@ -315,11 +319,9 @@ class TestHookIntegration: }, ): if expected in {"deny", "ask"}: - try: + with pytest.raises(SystemExit) as exc_info: main() - raise AssertionError("Expected SystemExit") - except SystemExit as exc: - assert exc.code == 2 + assert exc_info.value.code == 2 else: main() diff --git a/tests/hooks/test_posttooluse.py b/tests/hooks/test_posttooluse.py index 864d9a3..ee7c76b 100644 --- a/tests/hooks/test_posttooluse.py +++ b/tests/hooks/test_posttooluse.py @@ -149,7 +149,10 @@ class TestPostToolUseHook: with patch("pathlib.Path.read_text", return_value=clean_code): result = posttooluse_hook(hook_data, config) assert result["decision"] == "approve" - assert "passed post-write verification" in result["systemMessage"].lower() + assert ( + "passed post-write verification" + in result["systemMessage"].lower() + ) def test_no_message_when_success_disabled(self, clean_code): """Test no message when show_success is disabled.""" diff --git a/tests/hooks/test_pretooluse.py b/tests/hooks/test_pretooluse.py index 9fec21d..5db2d85 100644 --- a/tests/hooks/test_pretooluse.py +++ b/tests/hooks/test_pretooluse.py @@ -356,7 +356,10 @@ class TestPreToolUseHook: }, { "old_string": "pass", - "new_string": "def handler(arg: Any) -> str:\n return str(arg)\n", + "new_string": ( + "def handler(arg: Any) -> str:\n" + " return str(arg)\n" + ), }, ], },