crackerjack 0.31.10__py3-none-any.whl → 0.31.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +50 -9
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,200 +1,283 @@
|
|
|
1
|
-
"""Autofix coordination and retry logic for crackerjack workflows."""
|
|
2
|
-
|
|
3
|
-
import logging
|
|
4
1
|
import subprocess
|
|
5
|
-
import typing as t
|
|
6
2
|
from pathlib import Path
|
|
7
3
|
|
|
8
4
|
from rich.console import Console
|
|
9
5
|
|
|
6
|
+
from crackerjack.services.logging import get_logger
|
|
7
|
+
|
|
10
8
|
|
|
11
9
|
class AutofixCoordinator:
|
|
12
10
|
def __init__(self, console: Console, pkg_path: Path) -> None:
|
|
13
11
|
self.console = console
|
|
14
12
|
self.pkg_path = pkg_path
|
|
15
|
-
self.logger =
|
|
13
|
+
self.logger = get_logger("crackerjack.autofix")
|
|
14
|
+
# For testing purposes, we need to set a name attribute
|
|
15
|
+
# We use setattr to avoid type checker issues since BoundLogger doesn't have a name attribute
|
|
16
|
+
setattr(self.logger, "name", "crackerjack.autofix")
|
|
16
17
|
|
|
17
|
-
def apply_autofix_for_hooks(self, mode: str, hook_results: list[
|
|
18
|
-
self.logger.debug(
|
|
19
|
-
f"Applying autofix for {mode} mode with {len(hook_results)} hook results",
|
|
20
|
-
)
|
|
18
|
+
def apply_autofix_for_hooks(self, mode: str, hook_results: list[object]) -> bool:
|
|
21
19
|
try:
|
|
22
20
|
if self._should_skip_autofix(hook_results):
|
|
23
|
-
self.logger.info(
|
|
24
|
-
f"Skipping autofix for {mode} - unfixable error patterns detected",
|
|
25
|
-
)
|
|
26
21
|
return False
|
|
22
|
+
|
|
27
23
|
if mode == "fast":
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return result
|
|
35
|
-
self.logger.warning(f"Unknown autofix mode: {mode}")
|
|
36
|
-
return False
|
|
24
|
+
return self._apply_fast_stage_fixes()
|
|
25
|
+
elif mode == "comprehensive":
|
|
26
|
+
return self._apply_comprehensive_stage_fixes(hook_results)
|
|
27
|
+
else:
|
|
28
|
+
self.logger.warning(f"Unknown autofix mode: {mode}")
|
|
29
|
+
return False
|
|
37
30
|
except Exception as e:
|
|
38
|
-
self.logger.
|
|
39
|
-
self.console.print(f"[dim red]Auto-fix error: {e}[/dim red]")
|
|
31
|
+
self.logger.exception("Error applying autofix", error=str(e))
|
|
40
32
|
return False
|
|
41
33
|
|
|
42
34
|
def apply_fast_stage_fixes(self) -> bool:
|
|
43
|
-
"""Public interface for applying fast stage fixes."""
|
|
44
35
|
return self._apply_fast_stage_fixes()
|
|
45
36
|
|
|
46
|
-
def apply_comprehensive_stage_fixes(self, hook_results: list[
|
|
47
|
-
"""Public interface for applying comprehensive stage fixes."""
|
|
37
|
+
def apply_comprehensive_stage_fixes(self, hook_results: list[object]) -> bool:
|
|
48
38
|
return self._apply_comprehensive_stage_fixes(hook_results)
|
|
49
39
|
|
|
50
40
|
def run_fix_command(self, cmd: list[str], description: str) -> bool:
|
|
51
|
-
"""Public interface for running fix commands."""
|
|
52
41
|
return self._run_fix_command(cmd, description)
|
|
53
42
|
|
|
54
|
-
def check_tool_success_patterns(self, cmd: list[str], result:
|
|
55
|
-
"""Public interface for checking tool success patterns."""
|
|
43
|
+
def check_tool_success_patterns(self, cmd: list[str], result: object) -> bool:
|
|
56
44
|
return self._check_tool_success_patterns(cmd, result)
|
|
57
45
|
|
|
58
46
|
def validate_fix_command(self, cmd: list[str]) -> bool:
|
|
59
|
-
"""Public interface for validating fix commands."""
|
|
60
47
|
return self._validate_fix_command(cmd)
|
|
61
48
|
|
|
62
|
-
def validate_hook_result(self, result:
|
|
63
|
-
"""Public interface for validating hook results."""
|
|
49
|
+
def validate_hook_result(self, result: object) -> bool:
|
|
64
50
|
return self._validate_hook_result(result)
|
|
65
51
|
|
|
66
|
-
def should_skip_autofix(self, hook_results: list[
|
|
67
|
-
"""Public interface for checking if autofix should be skipped."""
|
|
52
|
+
def should_skip_autofix(self, hook_results: list[object]) -> bool:
|
|
68
53
|
return self._should_skip_autofix(hook_results)
|
|
69
54
|
|
|
70
55
|
def _apply_fast_stage_fixes(self) -> bool:
|
|
71
56
|
return self._execute_fast_fixes()
|
|
72
57
|
|
|
73
|
-
def
|
|
74
|
-
fixes_applied = False
|
|
75
|
-
fix_commands = [
|
|
76
|
-
(["uv", "run", "ruff", "format", "."], "ruff formatting"),
|
|
77
|
-
(["uv", "run", "ruff", "check", ".", "--fix"], "ruff auto-fixes"),
|
|
78
|
-
]
|
|
79
|
-
for cmd, description in fix_commands:
|
|
80
|
-
if self._run_fix_command(cmd, description):
|
|
81
|
-
fixes_applied = True
|
|
82
|
-
|
|
83
|
-
return fixes_applied
|
|
84
|
-
|
|
85
|
-
def _apply_comprehensive_stage_fixes(self, hook_results: list[t.Any]) -> bool:
|
|
86
|
-
fixes_applied = False
|
|
87
|
-
if self._apply_fast_stage_fixes():
|
|
88
|
-
fixes_applied = True
|
|
58
|
+
def _apply_comprehensive_stage_fixes(self, hook_results: list[object]) -> bool:
|
|
89
59
|
failed_hooks = self._extract_failed_hooks(hook_results)
|
|
60
|
+
if not failed_hooks:
|
|
61
|
+
return True
|
|
62
|
+
|
|
90
63
|
hook_specific_fixes = self._get_hook_specific_fixes(failed_hooks)
|
|
64
|
+
|
|
65
|
+
# Run fast fixes first
|
|
66
|
+
if not self._execute_fast_fixes():
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Apply hook-specific fixes
|
|
70
|
+
all_successful = True
|
|
91
71
|
for cmd, description in hook_specific_fixes:
|
|
92
|
-
if self._run_fix_command(cmd, description):
|
|
93
|
-
|
|
72
|
+
if not self._run_fix_command(cmd, description):
|
|
73
|
+
all_successful = False
|
|
94
74
|
|
|
95
|
-
return
|
|
75
|
+
return all_successful
|
|
96
76
|
|
|
97
|
-
def _extract_failed_hooks(self, hook_results: list[
|
|
98
|
-
failed_hooks
|
|
77
|
+
def _extract_failed_hooks(self, hook_results: list[object]) -> set[str]:
|
|
78
|
+
failed_hooks = set()
|
|
99
79
|
for result in hook_results:
|
|
100
|
-
if
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
80
|
+
if (
|
|
81
|
+
self._validate_hook_result(result)
|
|
82
|
+
and getattr(result, "status", "") == "Failed"
|
|
83
|
+
):
|
|
84
|
+
failed_hooks.add(getattr(result, "name", ""))
|
|
106
85
|
return failed_hooks
|
|
107
86
|
|
|
108
87
|
def _get_hook_specific_fixes(
|
|
109
|
-
self,
|
|
110
|
-
failed_hooks: set[str],
|
|
88
|
+
self, failed_hooks: set[str]
|
|
111
89
|
) -> list[tuple[list[str], str]]:
|
|
112
|
-
|
|
90
|
+
fixes = []
|
|
91
|
+
|
|
113
92
|
if "bandit" in failed_hooks:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
93
|
+
fixes.append((["uv", "run", "bandit", "-r", "."], "bandit analysis"))
|
|
94
|
+
|
|
95
|
+
return fixes
|
|
96
|
+
|
|
97
|
+
def _execute_fast_fixes(self) -> bool:
|
|
98
|
+
fixes = [
|
|
99
|
+
(["uv", "run", "ruff", "format", "."], "format code"),
|
|
100
|
+
(["uv", "run", "ruff", "check", "--fix", "."], "fix code style"),
|
|
101
|
+
]
|
|
117
102
|
|
|
118
|
-
|
|
103
|
+
all_successful = True
|
|
104
|
+
for cmd, description in fixes:
|
|
105
|
+
if not self._run_fix_command(cmd, description):
|
|
106
|
+
all_successful = False
|
|
107
|
+
|
|
108
|
+
return all_successful
|
|
119
109
|
|
|
120
110
|
def _run_fix_command(self, cmd: list[str], description: str) -> bool:
|
|
121
111
|
if not self._validate_fix_command(cmd):
|
|
112
|
+
self.logger.warning(f"Invalid fix command: {cmd}")
|
|
122
113
|
return False
|
|
114
|
+
|
|
123
115
|
try:
|
|
116
|
+
self.logger.info(f"Running fix command: {description}")
|
|
124
117
|
result = subprocess.run(
|
|
125
118
|
cmd,
|
|
126
|
-
|
|
119
|
+
cwd=self.pkg_path,
|
|
127
120
|
capture_output=True,
|
|
128
121
|
text=True,
|
|
129
|
-
timeout=
|
|
130
|
-
cwd=self.pkg_path,
|
|
122
|
+
timeout=300,
|
|
131
123
|
)
|
|
132
124
|
return self._handle_command_result(result, description)
|
|
133
|
-
except Exception:
|
|
125
|
+
except Exception as e:
|
|
126
|
+
self.logger.exception(
|
|
127
|
+
f"Error running fix command: {description}", error=str(e)
|
|
128
|
+
)
|
|
134
129
|
return False
|
|
135
130
|
|
|
136
131
|
def _handle_command_result(
|
|
137
|
-
self,
|
|
138
|
-
result: subprocess.CompletedProcess[str],
|
|
139
|
-
description: str,
|
|
132
|
+
self, result: subprocess.CompletedProcess[str], description: str
|
|
140
133
|
) -> bool:
|
|
141
|
-
|
|
134
|
+
if result.returncode == 0:
|
|
135
|
+
self.logger.info(f"Fix command succeeded: {description}")
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
if self._is_successful_fix(result):
|
|
139
|
+
self.logger.info(f"Fix command applied changes: {description}")
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
self.logger.warning(
|
|
143
|
+
f"Fix command failed: {description}",
|
|
144
|
+
returncode=result.returncode,
|
|
145
|
+
stderr=result.stderr[:200] if result.stderr else "No stderr",
|
|
146
|
+
)
|
|
147
|
+
return False
|
|
142
148
|
|
|
143
149
|
def _is_successful_fix(self, result: subprocess.CompletedProcess[str]) -> bool:
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
success_indicators = [
|
|
151
|
+
"fixed",
|
|
152
|
+
"formatted",
|
|
153
|
+
"reformatted",
|
|
154
|
+
"updated",
|
|
155
|
+
"changed",
|
|
156
|
+
"removed",
|
|
157
|
+
]
|
|
146
158
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
159
|
+
# Handle case where result might be a Mock object in tests
|
|
160
|
+
if hasattr(result, "stdout") and hasattr(result, "stderr"):
|
|
161
|
+
# Handle the case where stdout/stderr might be Mock objects
|
|
162
|
+
stdout = getattr(result, "stdout", "") or ""
|
|
163
|
+
stderr = getattr(result, "stderr", "") or ""
|
|
164
|
+
# If they're Mock objects, convert to string
|
|
165
|
+
if not isinstance(stdout, str):
|
|
166
|
+
stdout = str(stdout)
|
|
167
|
+
if not isinstance(stderr, str):
|
|
168
|
+
stderr = str(stderr)
|
|
169
|
+
output = stdout + stderr
|
|
170
|
+
else:
|
|
171
|
+
# For test mocks or other objects
|
|
172
|
+
output = str(result)
|
|
173
|
+
|
|
174
|
+
output_lower = output.lower()
|
|
175
|
+
|
|
176
|
+
return any(indicator in output_lower for indicator in success_indicators)
|
|
177
|
+
|
|
178
|
+
def _check_tool_success_patterns(self, cmd: list[str], result: object) -> bool:
|
|
179
|
+
if not cmd:
|
|
150
180
|
return False
|
|
151
181
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# Check if result is a subprocess.CompletedProcess
|
|
182
|
+
# Handle CompletedProcess objects or Mock objects with returncode attribute
|
|
155
183
|
if hasattr(result, "returncode"):
|
|
156
|
-
return result
|
|
184
|
+
return self._check_process_result_success(result)
|
|
157
185
|
|
|
158
|
-
# Check for
|
|
186
|
+
# Check for string patterns in result
|
|
159
187
|
if isinstance(result, str):
|
|
160
|
-
|
|
161
|
-
if "ruff" in tool_name:
|
|
162
|
-
return "fixed" in output_lower or "would reformat" in output_lower
|
|
163
|
-
if "trailing-whitespace" in tool_name:
|
|
164
|
-
return "fixing" in output_lower or "fixed" in output_lower
|
|
188
|
+
return self._check_string_result_success(result)
|
|
165
189
|
|
|
166
190
|
return False
|
|
167
191
|
|
|
192
|
+
def _check_process_result_success(self, result: object) -> bool:
|
|
193
|
+
"""Check if a process result indicates success."""
|
|
194
|
+
if getattr(result, "returncode", 1) == 0:
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
# Check output for success patterns if return code is non-zero
|
|
198
|
+
output = self._extract_process_output(result)
|
|
199
|
+
return self._has_success_patterns(output)
|
|
200
|
+
|
|
201
|
+
def _extract_process_output(self, result: object) -> str:
|
|
202
|
+
"""Extract and normalize stdout and stderr from process result."""
|
|
203
|
+
stdout = getattr(result, "stdout", "") or ""
|
|
204
|
+
stderr = getattr(result, "stderr", "") or ""
|
|
205
|
+
|
|
206
|
+
# Convert to strings if they're not already
|
|
207
|
+
if not isinstance(stdout, str):
|
|
208
|
+
stdout = str(stdout)
|
|
209
|
+
if not isinstance(stderr, str):
|
|
210
|
+
stderr = str(stderr)
|
|
211
|
+
|
|
212
|
+
return stdout + stderr
|
|
213
|
+
|
|
214
|
+
def _check_string_result_success(self, result: str) -> bool:
|
|
215
|
+
"""Check if a string result indicates success."""
|
|
216
|
+
return self._has_success_patterns(result)
|
|
217
|
+
|
|
218
|
+
def _has_success_patterns(self, output: str) -> bool:
|
|
219
|
+
"""Check if output contains success patterns."""
|
|
220
|
+
if not output:
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
success_patterns = [
|
|
224
|
+
"fixed",
|
|
225
|
+
"formatted",
|
|
226
|
+
"reformatted",
|
|
227
|
+
"would reformat",
|
|
228
|
+
"fixing",
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
output_lower = output.lower()
|
|
232
|
+
return any(pattern in output_lower for pattern in success_patterns)
|
|
233
|
+
|
|
168
234
|
def _validate_fix_command(self, cmd: list[str]) -> bool:
|
|
169
|
-
if len(cmd) <
|
|
235
|
+
if not cmd or len(cmd) < 2:
|
|
170
236
|
return False
|
|
171
|
-
|
|
237
|
+
|
|
238
|
+
if cmd[0] != "uv":
|
|
172
239
|
return False
|
|
173
|
-
tool_name = cmd[2]
|
|
174
|
-
return tool_name in ("ruff", "bandit")
|
|
175
240
|
|
|
176
|
-
|
|
177
|
-
if not hasattr(result, "name") or not hasattr(result, "status"):
|
|
178
|
-
self.logger.warning(f"Invalid hook result structure: {type(result)}")
|
|
241
|
+
if cmd[1] != "run":
|
|
179
242
|
return False
|
|
243
|
+
|
|
244
|
+
allowed_tools = [
|
|
245
|
+
"ruff",
|
|
246
|
+
"bandit",
|
|
247
|
+
"trailing-whitespace",
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
if len(cmd) > 2 and cmd[2] in allowed_tools:
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
def _validate_hook_result(self, result: object) -> bool:
|
|
180
256
|
name = getattr(result, "name", None)
|
|
181
257
|
status = getattr(result, "status", None)
|
|
182
|
-
|
|
183
|
-
|
|
258
|
+
|
|
259
|
+
if not name or not isinstance(name, str):
|
|
184
260
|
return False
|
|
185
|
-
|
|
186
|
-
|
|
261
|
+
|
|
262
|
+
if not status or not isinstance(status, str):
|
|
263
|
+
return False
|
|
264
|
+
|
|
265
|
+
valid_statuses = ["Passed", "Failed", "Skipped", "Error"]
|
|
266
|
+
if status not in valid_statuses:
|
|
187
267
|
return False
|
|
188
268
|
|
|
189
269
|
return True
|
|
190
270
|
|
|
191
|
-
def _should_skip_autofix(self, hook_results: list[
|
|
271
|
+
def _should_skip_autofix(self, hook_results: list[object]) -> bool:
|
|
192
272
|
for result in hook_results:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
273
|
+
raw_output = getattr(result, "raw_output", None)
|
|
274
|
+
if raw_output:
|
|
275
|
+
output_lower = raw_output.lower()
|
|
276
|
+
# Skip autofix for import errors as they typically require manual intervention
|
|
277
|
+
if (
|
|
278
|
+
"importerror" in output_lower
|
|
279
|
+
or "modulenotfounderror" in output_lower
|
|
280
|
+
):
|
|
281
|
+
self.logger.info("Skipping autofix for import errors")
|
|
199
282
|
return True
|
|
200
283
|
return False
|