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.

Files changed (155) hide show
  1. crackerjack/CLAUDE.md +288 -705
  2. crackerjack/__main__.py +22 -8
  3. crackerjack/agents/__init__.py +0 -3
  4. crackerjack/agents/architect_agent.py +0 -43
  5. crackerjack/agents/base.py +1 -9
  6. crackerjack/agents/coordinator.py +2 -148
  7. crackerjack/agents/documentation_agent.py +109 -81
  8. crackerjack/agents/dry_agent.py +122 -97
  9. crackerjack/agents/formatting_agent.py +3 -16
  10. crackerjack/agents/import_optimization_agent.py +1174 -130
  11. crackerjack/agents/performance_agent.py +956 -188
  12. crackerjack/agents/performance_helpers.py +229 -0
  13. crackerjack/agents/proactive_agent.py +1 -48
  14. crackerjack/agents/refactoring_agent.py +516 -246
  15. crackerjack/agents/refactoring_helpers.py +282 -0
  16. crackerjack/agents/security_agent.py +393 -90
  17. crackerjack/agents/test_creation_agent.py +1776 -120
  18. crackerjack/agents/test_specialist_agent.py +59 -15
  19. crackerjack/agents/tracker.py +0 -102
  20. crackerjack/api.py +145 -37
  21. crackerjack/cli/handlers.py +48 -30
  22. crackerjack/cli/interactive.py +11 -11
  23. crackerjack/cli/options.py +66 -4
  24. crackerjack/code_cleaner.py +808 -148
  25. crackerjack/config/global_lock_config.py +110 -0
  26. crackerjack/config/hooks.py +43 -64
  27. crackerjack/core/async_workflow_orchestrator.py +247 -97
  28. crackerjack/core/autofix_coordinator.py +192 -109
  29. crackerjack/core/enhanced_container.py +46 -63
  30. crackerjack/core/file_lifecycle.py +549 -0
  31. crackerjack/core/performance.py +9 -8
  32. crackerjack/core/performance_monitor.py +395 -0
  33. crackerjack/core/phase_coordinator.py +281 -94
  34. crackerjack/core/proactive_workflow.py +9 -58
  35. crackerjack/core/resource_manager.py +501 -0
  36. crackerjack/core/service_watchdog.py +490 -0
  37. crackerjack/core/session_coordinator.py +4 -8
  38. crackerjack/core/timeout_manager.py +504 -0
  39. crackerjack/core/websocket_lifecycle.py +475 -0
  40. crackerjack/core/workflow_orchestrator.py +343 -209
  41. crackerjack/dynamic_config.py +50 -9
  42. crackerjack/errors.py +3 -4
  43. crackerjack/executors/async_hook_executor.py +63 -13
  44. crackerjack/executors/cached_hook_executor.py +14 -14
  45. crackerjack/executors/hook_executor.py +100 -37
  46. crackerjack/executors/hook_lock_manager.py +856 -0
  47. crackerjack/executors/individual_hook_executor.py +120 -86
  48. crackerjack/intelligence/__init__.py +0 -7
  49. crackerjack/intelligence/adaptive_learning.py +13 -86
  50. crackerjack/intelligence/agent_orchestrator.py +15 -78
  51. crackerjack/intelligence/agent_registry.py +12 -59
  52. crackerjack/intelligence/agent_selector.py +31 -92
  53. crackerjack/intelligence/integration.py +1 -41
  54. crackerjack/interactive.py +9 -9
  55. crackerjack/managers/async_hook_manager.py +25 -8
  56. crackerjack/managers/hook_manager.py +9 -9
  57. crackerjack/managers/publish_manager.py +57 -59
  58. crackerjack/managers/test_command_builder.py +6 -36
  59. crackerjack/managers/test_executor.py +9 -61
  60. crackerjack/managers/test_manager.py +17 -63
  61. crackerjack/managers/test_manager_backup.py +77 -127
  62. crackerjack/managers/test_progress.py +4 -23
  63. crackerjack/mcp/cache.py +5 -12
  64. crackerjack/mcp/client_runner.py +10 -10
  65. crackerjack/mcp/context.py +64 -6
  66. crackerjack/mcp/dashboard.py +14 -11
  67. crackerjack/mcp/enhanced_progress_monitor.py +55 -55
  68. crackerjack/mcp/file_monitor.py +72 -42
  69. crackerjack/mcp/progress_components.py +103 -84
  70. crackerjack/mcp/progress_monitor.py +122 -49
  71. crackerjack/mcp/rate_limiter.py +12 -12
  72. crackerjack/mcp/server_core.py +16 -22
  73. crackerjack/mcp/service_watchdog.py +26 -26
  74. crackerjack/mcp/state.py +15 -0
  75. crackerjack/mcp/tools/core_tools.py +95 -39
  76. crackerjack/mcp/tools/error_analyzer.py +6 -32
  77. crackerjack/mcp/tools/execution_tools.py +1 -56
  78. crackerjack/mcp/tools/execution_tools_backup.py +35 -131
  79. crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
  80. crackerjack/mcp/tools/intelligence_tools.py +2 -55
  81. crackerjack/mcp/tools/monitoring_tools.py +308 -145
  82. crackerjack/mcp/tools/proactive_tools.py +12 -42
  83. crackerjack/mcp/tools/progress_tools.py +23 -15
  84. crackerjack/mcp/tools/utility_tools.py +3 -40
  85. crackerjack/mcp/tools/workflow_executor.py +40 -60
  86. crackerjack/mcp/websocket/app.py +0 -3
  87. crackerjack/mcp/websocket/endpoints.py +206 -268
  88. crackerjack/mcp/websocket/jobs.py +213 -66
  89. crackerjack/mcp/websocket/server.py +84 -6
  90. crackerjack/mcp/websocket/websocket_handler.py +137 -29
  91. crackerjack/models/config_adapter.py +3 -16
  92. crackerjack/models/protocols.py +162 -3
  93. crackerjack/models/resource_protocols.py +454 -0
  94. crackerjack/models/task.py +3 -3
  95. crackerjack/monitoring/__init__.py +0 -0
  96. crackerjack/monitoring/ai_agent_watchdog.py +25 -71
  97. crackerjack/monitoring/regression_prevention.py +28 -87
  98. crackerjack/orchestration/advanced_orchestrator.py +44 -78
  99. crackerjack/orchestration/coverage_improvement.py +10 -60
  100. crackerjack/orchestration/execution_strategies.py +16 -16
  101. crackerjack/orchestration/test_progress_streamer.py +61 -53
  102. crackerjack/plugins/base.py +1 -1
  103. crackerjack/plugins/managers.py +22 -20
  104. crackerjack/py313.py +65 -21
  105. crackerjack/services/backup_service.py +467 -0
  106. crackerjack/services/bounded_status_operations.py +627 -0
  107. crackerjack/services/cache.py +7 -9
  108. crackerjack/services/config.py +35 -52
  109. crackerjack/services/config_integrity.py +5 -16
  110. crackerjack/services/config_merge.py +542 -0
  111. crackerjack/services/contextual_ai_assistant.py +17 -19
  112. crackerjack/services/coverage_ratchet.py +44 -73
  113. crackerjack/services/debug.py +25 -39
  114. crackerjack/services/dependency_monitor.py +52 -50
  115. crackerjack/services/enhanced_filesystem.py +14 -11
  116. crackerjack/services/file_hasher.py +1 -1
  117. crackerjack/services/filesystem.py +1 -12
  118. crackerjack/services/git.py +71 -47
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +276 -428
  121. crackerjack/services/input_validator.py +760 -0
  122. crackerjack/services/log_manager.py +16 -16
  123. crackerjack/services/logging.py +7 -6
  124. crackerjack/services/metrics.py +43 -43
  125. crackerjack/services/pattern_cache.py +2 -31
  126. crackerjack/services/pattern_detector.py +26 -63
  127. crackerjack/services/performance_benchmarks.py +20 -45
  128. crackerjack/services/regex_patterns.py +2887 -0
  129. crackerjack/services/regex_utils.py +537 -0
  130. crackerjack/services/secure_path_utils.py +683 -0
  131. crackerjack/services/secure_status_formatter.py +534 -0
  132. crackerjack/services/secure_subprocess.py +605 -0
  133. crackerjack/services/security.py +47 -10
  134. crackerjack/services/security_logger.py +492 -0
  135. crackerjack/services/server_manager.py +109 -50
  136. crackerjack/services/smart_scheduling.py +8 -25
  137. crackerjack/services/status_authentication.py +603 -0
  138. crackerjack/services/status_security_manager.py +442 -0
  139. crackerjack/services/thread_safe_status_collector.py +546 -0
  140. crackerjack/services/tool_version_service.py +1 -23
  141. crackerjack/services/unified_config.py +36 -58
  142. crackerjack/services/validation_rate_limiter.py +269 -0
  143. crackerjack/services/version_checker.py +9 -40
  144. crackerjack/services/websocket_resource_limiter.py +572 -0
  145. crackerjack/slash_commands/__init__.py +52 -2
  146. crackerjack/tools/__init__.py +0 -0
  147. crackerjack/tools/validate_input_validator_patterns.py +262 -0
  148. crackerjack/tools/validate_regex_patterns.py +198 -0
  149. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.13.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.10.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
  155. {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 = logging.getLogger("crackerjack.autofix")
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[t.Any]) -> bool:
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
- result = self._apply_fast_stage_fixes()
29
- self.logger.debug(f"Fast stage fixes result: {result}")
30
- return result
31
- if mode == "comprehensive":
32
- result = self._apply_comprehensive_stage_fixes(hook_results)
33
- self.logger.debug(f"Comprehensive stage fixes result: {result}")
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.error(f"Auto-fix error in {mode} mode: {e}", exc_info=True)
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[t.Any]) -> bool:
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: t.Any) -> bool:
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: t.Any) -> bool:
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[t.Any]) -> bool:
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 _execute_fast_fixes(self) -> bool:
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
- fixes_applied = True
72
+ if not self._run_fix_command(cmd, description):
73
+ all_successful = False
94
74
 
95
- return fixes_applied
75
+ return all_successful
96
76
 
97
- def _extract_failed_hooks(self, hook_results: list[t.Any]) -> set[str]:
98
- failed_hooks: set[str] = set()
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 self._validate_hook_result(result):
101
- hook_name: str = getattr(result, "name", "").lower()
102
- hook_status: str = getattr(result, "status", "")
103
- if hook_status == "Failed" and hook_name:
104
- failed_hooks.add(hook_name)
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
- hook_specific_fixes: list[tuple[list[str], str]] = []
90
+ fixes = []
91
+
113
92
  if "bandit" in failed_hooks:
114
- hook_specific_fixes.append(
115
- (["uv", "run", "bandit", "-f", "json", ".", "-ll"], "bandit analysis"),
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
- return hook_specific_fixes
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
- check=False,
119
+ cwd=self.pkg_path,
127
120
  capture_output=True,
128
121
  text=True,
129
- timeout=30,
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
- return bool(result.returncode == 0 or self._is_successful_fix(result))
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
- output = result.stdout.lower()
145
- return "fixed" in output or "reformatted" in output
150
+ success_indicators = [
151
+ "fixed",
152
+ "formatted",
153
+ "reformatted",
154
+ "updated",
155
+ "changed",
156
+ "removed",
157
+ ]
146
158
 
147
- def _check_tool_success_patterns(self, cmd: list[str], result: t.Any) -> bool:
148
- """Check if a tool command result indicates success."""
149
- if not cmd or len(cmd) < 3:
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
- tool_name = cmd[2] if len(cmd) > 2 else ""
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.returncode == 0
184
+ return self._check_process_result_success(result)
157
185
 
158
- # Check for specific tool success patterns
186
+ # Check for string patterns in result
159
187
  if isinstance(result, str):
160
- output_lower = result.lower()
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) < 3:
235
+ if not cmd or len(cmd) < 2:
170
236
  return False
171
- if cmd[0] != "uv" or cmd[1] != "run":
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
- def _validate_hook_result(self, result: t.Any) -> bool:
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
- if not isinstance(name, str) or not name.strip():
183
- self.logger.warning(f"Hook result has invalid name: {name}")
258
+
259
+ if not name or not isinstance(name, str):
184
260
  return False
185
- if status not in ("Passed", "Failed", "Skipped", "Error"):
186
- self.logger.warning(f"Hook result has invalid status: {status}")
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[t.Any]) -> bool:
271
+ def _should_skip_autofix(self, hook_results: list[object]) -> bool:
192
272
  for result in hook_results:
193
- if hasattr(result, "raw_output"):
194
- output = getattr(result, "raw_output", "")
195
- if "ModuleNotFoundError" in output or "ImportError" in output:
196
- self.console.print(
197
- "[dim yellow] → Skipping autofix (import errors)[/dim yellow]",
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