crackerjack 0.31.9__py3-none-any.whl → 0.31.12__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 +282 -95
  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 +355 -204
  41. crackerjack/dynamic_config.py +47 -6
  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 +52 -62
  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 +51 -76
  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 +78 -44
  119. crackerjack/services/health_metrics.py +31 -27
  120. crackerjack/services/initialization.py +281 -433
  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.9.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
  150. crackerjack-0.31.12.dist-info/RECORD +178 -0
  151. crackerjack/cli/facade.py +0 -104
  152. crackerjack-0.31.9.dist-info/RECORD +0 -149
  153. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
  154. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
  155. {crackerjack-0.31.9.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
@@ -5,8 +5,10 @@ from pathlib import Path
5
5
 
6
6
  from rich.console import Console
7
7
 
8
- from crackerjack.code_cleaner import CodeCleaner
8
+ from crackerjack.code_cleaner import CodeCleaner, PackageCleaningResult
9
+ from crackerjack.core.autofix_coordinator import AutofixCoordinator
9
10
  from crackerjack.models.protocols import (
11
+ ConfigMergeServiceProtocol,
10
12
  FileSystemInterface,
11
13
  GitInterface,
12
14
  HookManager,
@@ -30,6 +32,7 @@ class PhaseCoordinator:
30
32
  hook_manager: HookManager,
31
33
  test_manager: TestManagerProtocol,
32
34
  publish_manager: PublishManager,
35
+ config_merge_service: ConfigMergeServiceProtocol,
33
36
  ) -> None:
34
37
  self.console = console
35
38
  self.pkg_path = pkg_path
@@ -40,9 +43,22 @@ class PhaseCoordinator:
40
43
  self.hook_manager = hook_manager
41
44
  self.test_manager = test_manager
42
45
  self.publish_manager = publish_manager
43
-
44
- self.code_cleaner = CodeCleaner(console=console)
46
+ self.config_merge_service = config_merge_service
47
+
48
+ self.code_cleaner = CodeCleaner(
49
+ console=console,
50
+ base_directory=pkg_path,
51
+ file_processor=None,
52
+ error_handler=None,
53
+ pipeline=None,
54
+ logger=None,
55
+ security_logger=None,
56
+ backup_service=None,
57
+ )
45
58
  self.config_service = ConfigurationService(console=console, pkg_path=pkg_path)
59
+ self.autofix_coordinator = AutofixCoordinator(
60
+ console=console, pkg_path=pkg_path
61
+ )
46
62
 
47
63
  self.logger = logging.getLogger("crackerjack.phases")
48
64
 
@@ -55,82 +71,226 @@ class PhaseCoordinator:
55
71
  self._display_cleaning_header()
56
72
  return self._execute_cleaning_process()
57
73
  except Exception as e:
58
- self.console.print(f"[red]❌[/red] Cleaning failed: {e}")
74
+ self.console.print(f"[red]❌[/ red] Cleaning failed: {e}")
59
75
  self.session.fail_task("cleaning", str(e))
60
76
  return False
61
77
 
62
78
  def _display_cleaning_header(self) -> None:
63
- self.console.print("\n" + " - " * 80)
79
+ self.console.print("\n" + "-" * 80)
64
80
  self.console.print(
65
81
  "[bold bright_magenta]🛠️ SETUP[/bold bright_magenta] [bold bright_white]Initializing project structure[/bold bright_white]",
66
82
  )
67
- self.console.print(" - " * 80 + "\n")
83
+ self.console.print("-" * 80 + "\n")
68
84
  self.console.print("[yellow]🧹[/yellow] Starting code cleaning...")
69
85
 
70
86
  def _execute_cleaning_process(self) -> bool:
71
- python_files = list(self.pkg_path.rglob("*.py"))
72
-
73
- if not python_files:
74
- return self._handle_no_files_to_clean()
75
-
76
- cleaned_files = self._clean_python_files(python_files)
77
- self._report_cleaning_results(cleaned_files)
78
- return True
87
+ # Use the comprehensive backup cleaning system for safety
88
+ cleaning_result = self.code_cleaner.clean_files(self.pkg_path, use_backup=True)
89
+
90
+ if isinstance(cleaning_result, list):
91
+ # Legacy mode (should not happen with use_backup=True, but handle gracefully)
92
+ cleaned_files = [str(r.file_path) for r in cleaning_result if r.success]
93
+ self._report_cleaning_results(cleaned_files)
94
+ return all(r.success for r in cleaning_result) if cleaning_result else True
95
+ else:
96
+ # PackageCleaningResult from backup mode
97
+ self._report_package_cleaning_results(cleaning_result)
98
+ return cleaning_result.overall_success
79
99
 
80
100
  def _handle_no_files_to_clean(self) -> bool:
81
- self.console.print("[yellow]⚠️[/yellow] No Python files found to clean")
101
+ self.console.print("[yellow]⚠️[/ yellow] No Python files found to clean")
82
102
  self.session.complete_task("cleaning", "No files to clean")
83
103
  return True
84
104
 
85
- def _clean_python_files(self, python_files: list[Path]) -> list[str]:
86
- cleaned_files: list[str] = []
87
- for file_path in python_files:
88
- if self.code_cleaner.should_process_file(file_path):
89
- if self.code_cleaner.clean_file(file_path):
90
- cleaned_files.append(str(file_path))
91
- return cleaned_files
92
-
93
105
  def _report_cleaning_results(self, cleaned_files: list[str]) -> None:
94
106
  if cleaned_files:
95
- self.console.print(f"[green]✅[/green] Cleaned {len(cleaned_files)} files")
107
+ self.console.print(f"[green]✅[/ green] Cleaned {len(cleaned_files)} files")
96
108
  self.session.complete_task(
97
109
  "cleaning",
98
110
  f"Cleaned {len(cleaned_files)} files",
99
111
  )
100
112
  else:
101
- self.console.print("[green]✅[/green] No cleaning needed")
113
+ self.console.print("[green]✅[/ green] No cleaning needed")
102
114
  self.session.complete_task("cleaning", "No cleaning needed")
103
115
 
116
+ def _report_package_cleaning_results(self, result: PackageCleaningResult) -> None:
117
+ """Report package cleaning results with backup information."""
118
+ if result.overall_success:
119
+ self.console.print(
120
+ f"[green]✅[/ green] Package cleaning completed successfully! "
121
+ f"({result.successful_files}/{result.total_files} files cleaned)"
122
+ )
123
+ self.session.complete_task(
124
+ "cleaning",
125
+ f"Cleaned {result.successful_files}/{result.total_files} files with backup protection",
126
+ )
127
+ else:
128
+ self.console.print(
129
+ f"[red]❌[/ red] Package cleaning failed! "
130
+ f"({result.failed_files}/{result.total_files} files failed)"
131
+ )
132
+
133
+ if result.backup_restored:
134
+ self.console.print(
135
+ "[yellow]⚠️[/ yellow] Files were automatically restored from backup"
136
+ )
137
+ self.session.complete_task(
138
+ "cleaning", "Failed with automatic backup restoration"
139
+ )
140
+ else:
141
+ self.session.fail_task(
142
+ "cleaning", f"Failed to clean {result.failed_files} files"
143
+ )
144
+
145
+ if result.backup_metadata:
146
+ self.console.print(
147
+ f"[blue]📦[/ blue] Backup available at: {result.backup_metadata.backup_directory}"
148
+ )
149
+
104
150
  def run_configuration_phase(self, options: OptionsProtocol) -> bool:
105
151
  if options.no_config_updates:
106
152
  return True
107
153
  self.session.track_task("configuration", "Configuration updates")
108
154
  try:
109
- success = True
155
+ success = self._execute_configuration_steps(options)
156
+ self._complete_configuration_task(success)
157
+ return success
158
+ except Exception as e:
159
+ self.session.fail_task("configuration", str(e))
160
+ return False
161
+
162
+ def _execute_configuration_steps(self, options: OptionsProtocol) -> bool:
163
+ """Execute all configuration steps and return overall success."""
164
+ success = True
165
+
166
+ # FIRST STEP: Smart config merge before all other operations
167
+ self._handle_smart_config_merge(options)
168
+
169
+ # Handle crackerjack project specific configuration
170
+ if self._is_crackerjack_project() and not self._copy_config_files_to_package():
171
+ success = False
172
+
173
+ # Update configuration files
174
+ success &= self._update_configuration_files(options)
175
+
176
+ return success
177
+
178
+ def _handle_smart_config_merge(self, options: OptionsProtocol) -> None:
179
+ """Handle smart config merge with warning on failure."""
180
+ if not self._perform_smart_config_merge(options):
181
+ self.console.print(
182
+ "[yellow]⚠️[/yellow] Smart config merge encountered issues (continuing)"
183
+ )
184
+ # Don't fail the entire configuration phase, just log the warning
185
+
186
+ def _update_configuration_files(self, options: OptionsProtocol) -> bool:
187
+ """Update precommit and pyproject configuration files."""
188
+ success = True
189
+ if not self.config_service.update_precommit_config(options):
190
+ success = False
191
+ if not self.config_service.update_pyproject_config(options):
192
+ success = False
193
+ return success
194
+
195
+ def _complete_configuration_task(self, success: bool) -> None:
196
+ """Complete the configuration task with appropriate message."""
197
+ message = (
198
+ "Configuration updated successfully"
199
+ if success
200
+ else "Some configuration updates failed"
201
+ )
202
+ self.session.complete_task("configuration", message)
203
+
204
+ def _perform_smart_config_merge(self, options: OptionsProtocol) -> bool:
205
+ """Perform smart config merge before git operations."""
206
+ try:
207
+ self.logger.debug("Starting smart config merge process")
110
208
 
111
- # Check if we're running from the crackerjack project root
209
+ # Smart merge for critical configuration files
210
+ merged_files = []
211
+
212
+ # Skip smart merge if explicitly requested or in specific modes
213
+ if hasattr(options, "skip_config_merge") and options.skip_config_merge:
214
+ self.logger.debug("Config merge skipped by option")
215
+ return True
216
+
217
+ # Merge .gitignore patterns (always safe to do)
218
+ if self._smart_merge_gitignore():
219
+ merged_files.append(".gitignore")
220
+
221
+ # Merge configuration files (pyproject.toml, .pre-commit-config.yaml)
222
+ # Only for crackerjack projects to avoid breaking user projects
112
223
  if self._is_crackerjack_project():
113
- if not self._copy_config_files_to_package():
114
- success = False
224
+ if self._smart_merge_project_configs():
225
+ merged_files.extend(["pyproject.toml", ".pre-commit-config.yaml"])
115
226
 
116
- if not self.config_service.update_precommit_config(options):
117
- success = False
118
- if not self.config_service.update_pyproject_config(options):
119
- success = False
120
- self.session.complete_task(
121
- "configuration",
122
- "Configuration updated successfully"
123
- if success
124
- else "Some configuration updates failed",
227
+ if merged_files:
228
+ files_str = ", ".join(merged_files)
229
+ self.console.print(
230
+ f"[green]🔧[/green] Smart-merged configurations: {files_str}"
231
+ )
232
+ self.logger.info(f"Smart config merge completed: {merged_files}")
233
+ else:
234
+ self.logger.debug("No configuration files needed smart merging")
235
+
236
+ return True
237
+
238
+ except Exception as e:
239
+ self.console.print(f"[yellow]⚠️[/yellow] Smart config merge failed: {e}")
240
+ self.logger.warning(
241
+ f"Smart config merge failed: {e} (type: {type(e).__name__})"
125
242
  )
126
- return success
243
+ # Return True to not block the workflow - this is fail-safe
244
+ return True
245
+
246
+ def _smart_merge_gitignore(self) -> bool:
247
+ """Smart merge .gitignore patterns."""
248
+ try:
249
+ gitignore_path = self.pkg_path / ".gitignore"
250
+ if not gitignore_path.exists():
251
+ return False
252
+
253
+ # Standard crackerjack ignore patterns to merge
254
+ standard_patterns = [
255
+ "# Crackerjack generated files",
256
+ ".crackerjack/",
257
+ "*.crackerjack.bak",
258
+ ".coverage.*",
259
+ "crackerjack-debug-*.log",
260
+ "__pycache__/",
261
+ "*.py[cod]",
262
+ "*$py.class",
263
+ ".pytest_cache/",
264
+ ".tox/",
265
+ ".mypy_cache/",
266
+ ".ruff_cache/",
267
+ ]
268
+
269
+ self.config_merge_service.smart_merge_gitignore(
270
+ patterns=standard_patterns, target_path=str(gitignore_path)
271
+ )
272
+
273
+ return True
274
+
127
275
  except Exception as e:
128
- self.session.fail_task("configuration", str(e))
276
+ self.logger.warning(f"Failed to smart merge .gitignore: {e}")
277
+ return False
278
+
279
+ def _smart_merge_project_configs(self) -> bool:
280
+ """Smart merge pyproject.toml and pre-commit config for crackerjack projects."""
281
+ try:
282
+ # This would be where we implement project config merging
283
+ # For now, just return True as the existing config service handles this
284
+ self.logger.debug(
285
+ "Project config smart merge placeholder - handled by existing config service"
286
+ )
287
+ return True
288
+
289
+ except Exception as e:
290
+ self.logger.warning(f"Failed to smart merge project configs: {e}")
129
291
  return False
130
292
 
131
293
  def _is_crackerjack_project(self) -> bool:
132
- """Check if we're running from the crackerjack project root."""
133
- # Check for crackerjack-specific markers
134
294
  pyproject_path = self.pkg_path / "pyproject.toml"
135
295
  if not pyproject_path.exists():
136
296
  return False
@@ -141,30 +301,27 @@ class PhaseCoordinator:
141
301
  with pyproject_path.open("rb") as f:
142
302
  data = tomllib.load(f)
143
303
 
144
- # Check if this is the crackerjack project
145
304
  project_name = data.get("project", {}).get("name", "")
146
305
  return project_name == "crackerjack"
147
306
  except Exception:
148
307
  return False
149
308
 
150
309
  def _copy_config_files_to_package(self) -> bool:
151
- """Copy configuration files from project root to package root."""
152
310
  try:
153
- # Files to copy from project root to package root
154
311
  files_to_copy = [
155
312
  "pyproject.toml",
156
313
  ".pre-commit-config.yaml",
157
314
  "CLAUDE.md",
158
315
  "RULES.md",
159
316
  ".gitignore",
160
- "mcp.json",
317
+ "example.mcp.json",
161
318
  "uv.lock",
162
319
  ]
163
320
 
164
321
  package_dir = self.pkg_path / "crackerjack"
165
322
  if not package_dir.exists():
166
323
  self.console.print(
167
- "[yellow]⚠️[/yellow] Package directory not found: crackerjack/",
324
+ "[yellow]⚠️[/ yellow] Package directory not found: crackerjack /",
168
325
  )
169
326
  return False
170
327
 
@@ -181,18 +338,18 @@ class PhaseCoordinator:
181
338
  self.logger.debug(f"Copied {filename} to package directory")
182
339
  except Exception as e:
183
340
  self.console.print(
184
- f"[yellow]⚠️[/yellow] Failed to copy {filename}: {e}",
341
+ f"[yellow]⚠️[/ yellow] Failed to copy {filename}: {e}",
185
342
  )
186
343
 
187
344
  if copied_count > 0:
188
345
  self.console.print(
189
- f"[green]✅[/green] Copied {copied_count} config files to package directory",
346
+ f"[green]✅[/ green] Copied {copied_count} config files to package directory",
190
347
  )
191
348
 
192
349
  return True
193
350
  except Exception as e:
194
351
  self.console.print(
195
- f"[red]❌[/red] Failed to copy config files to package: {e}",
352
+ f"[red]❌[/ red] Failed to copy config files to package: {e}",
196
353
  )
197
354
  return False
198
355
 
@@ -236,7 +393,7 @@ class PhaseCoordinator:
236
393
  try:
237
394
  self.console.print("\n" + "-" * 80)
238
395
  self.console.print(
239
- "[bold bright_blue]🧪 TESTS[/bold bright_blue] [bold bright_white]Running test suite[/bold bright_white]",
396
+ "[bold bright_blue]🧪 TESTS[/ bold bright_blue] [bold bright_white]Running test suite[/ bold bright_white]",
240
397
  )
241
398
  self.console.print("-" * 80 + "\n")
242
399
  if not self.test_manager.validate_test_environment():
@@ -247,7 +404,7 @@ class PhaseCoordinator:
247
404
  coverage_info = self.test_manager.get_coverage()
248
405
  self.session.complete_task(
249
406
  "testing",
250
- f"Tests passed, coverage: {coverage_info.get('total_coverage', 0):.1f}%",
407
+ f"Tests passed, coverage: {coverage_info.get('total_coverage', 0): .1f}%",
251
408
  )
252
409
  else:
253
410
  self.session.fail_task("testing", "Tests failed")
@@ -267,7 +424,7 @@ class PhaseCoordinator:
267
424
  try:
268
425
  return self._execute_publishing_workflow(options, version_type)
269
426
  except Exception as e:
270
- self.console.print(f"[red]❌[/red] Publishing failed: {e}")
427
+ self.console.print(f"[red]❌[/ red] Publishing failed: {e}")
271
428
  self.session.fail_task("publishing", str(e))
272
429
  return False
273
430
 
@@ -302,7 +459,7 @@ class PhaseCoordinator:
302
459
  options: OptionsProtocol,
303
460
  new_version: str,
304
461
  ) -> None:
305
- self.console.print(f"[green]🚀[/green] Successfully published {new_version}!")
462
+ self.console.print(f"[green]🚀[/ green] Successfully published {new_version}!")
306
463
 
307
464
  if options.cleanup_pypi:
308
465
  self.publish_manager.cleanup_old_releases(options.keep_releases)
@@ -320,12 +477,12 @@ class PhaseCoordinator:
320
477
  commit_message = self._get_commit_message(changed_files, options)
321
478
  return self._execute_commit_and_push(changed_files, commit_message)
322
479
  except Exception as e:
323
- self.console.print(f"[red]❌[/red] Commit failed: {e}")
480
+ self.console.print(f"[red]❌[/ red] Commit failed: {e}")
324
481
  self.session.fail_task("commit", str(e))
325
482
  return False
326
483
 
327
484
  def _handle_no_changes_to_commit(self) -> bool:
328
- self.console.print("[yellow]ℹ️[/yellow] No changes to commit")
485
+ self.console.print("[yellow]ℹ️[/ yellow] No changes to commit")
329
486
  self.session.complete_task("commit", "No changes to commit")
330
487
  return True
331
488
 
@@ -347,7 +504,7 @@ class PhaseCoordinator:
347
504
  def _handle_push_result(self, commit_message: str) -> bool:
348
505
  if self.git_service.push():
349
506
  self.console.print(
350
- f"[green]🎉[/green] Committed and pushed: {commit_message}",
507
+ f"[green]🎉[/ green] Committed and pushed: {commit_message}",
351
508
  )
352
509
  self.session.complete_task(
353
510
  "commit",
@@ -355,7 +512,7 @@ class PhaseCoordinator:
355
512
  )
356
513
  else:
357
514
  self.console.print(
358
- f"[yellow]⚠️[/yellow] Committed but push failed: {commit_message}",
515
+ f"[yellow]⚠️[/ yellow] Committed but push failed: {commit_message}",
359
516
  )
360
517
  self.session.complete_task(
361
518
  "commit",
@@ -375,11 +532,11 @@ class PhaseCoordinator:
375
532
  self.session.track_task("version_bump", f"Version bump ({bump_type})")
376
533
  try:
377
534
  new_version = self.publish_manager.bump_version(bump_type)
378
- self.console.print(f"[green]🎯[/green] Version bumped to {new_version}")
535
+ self.console.print(f"[green]🎯[/ green] Version bumped to {new_version}")
379
536
  self.session.complete_task("version_bump", f"Bumped to {new_version}")
380
537
  return True
381
538
  except Exception as e:
382
- self.console.print(f"[red]❌[/red] Version bump failed: {e}")
539
+ self.console.print(f"[red]❌[/ red] Version bump failed: {e}")
383
540
  self.session.fail_task("version_bump", str(e))
384
541
  return False
385
542
 
@@ -403,14 +560,14 @@ class PhaseCoordinator:
403
560
 
404
561
  try:
405
562
  choice = self.console.input(
406
- f"\nSelect message (1 - {len(suggestions)}) or enter custom: ",
563
+ f"\nSelect message (1-{len(suggestions)}) or enter custom: ",
407
564
  )
408
565
  return self._process_commit_choice(choice, suggestions)
409
566
  except (KeyboardInterrupt, EOFError):
410
567
  return suggestions[0]
411
568
 
412
569
  def _display_commit_suggestions(self, suggestions: list[str]) -> None:
413
- self.console.print("[cyan]📝[/cyan] Commit message suggestions: ")
570
+ self.console.print("[cyan]📝[/ cyan] Commit message suggestions: ")
414
571
  for i, suggestion in enumerate(suggestions, 1):
415
572
  self.console.print(f" {i}. {suggestion}")
416
573
 
@@ -480,11 +637,21 @@ class PhaseCoordinator:
480
637
  if hook_type == "fast" and attempt < max_retries - 1:
481
638
  if self._should_retry_fast_hooks(results):
482
639
  self.console.print(
483
- "[yellow]🔄[/yellow] Fast hooks modified files, retrying all fast hooks...",
640
+ "[yellow]🔄[/ yellow] Fast hooks modified files, retrying all fast hooks...",
484
641
  )
485
642
  return True
486
643
  return False
487
644
 
645
+ def _attempt_autofix_for_fast_hooks(self, results: list[t.Any]) -> bool:
646
+ """Attempt to autofix fast hook failures."""
647
+ try:
648
+ self.logger.info("Attempting autofix for fast hook failures")
649
+ # Apply autofixes for fast hooks
650
+ return self.autofix_coordinator.apply_fast_stage_fixes()
651
+ except Exception as e:
652
+ self.logger.warning(f"Autofix attempt failed: {e}")
653
+ return False
654
+
488
655
  def _handle_hook_failures(
489
656
  self,
490
657
  hook_type: str,
@@ -494,15 +661,26 @@ class PhaseCoordinator:
494
661
  attempt: int,
495
662
  max_retries: int,
496
663
  ) -> bool:
497
- self.logger.warning(
664
+ self.logger.debug(
498
665
  f"{hook_type} hooks failed: {summary['failed']} failed, {summary['errors']} errors",
499
666
  )
500
667
 
501
668
  self.console.print(
502
- f"[red]❌[/red] {hook_type.title()} hooks failed: {summary['failed']} failed, {summary['errors']} errors",
669
+ f"[red]❌[/ red] {hook_type.title()} hooks failed: {summary['failed']} failed, {summary['errors']} errors",
503
670
  )
504
671
 
505
- # Collect detailed hook failure information for AI agent processing
672
+ # Try autofix for fast hooks before giving up
673
+ if hook_type == "fast" and attempt < max_retries - 1:
674
+ if self._attempt_autofix_for_fast_hooks(results):
675
+ self.console.print(
676
+ "[yellow]🔧[/ yellow] Applied autofixes for fast hooks, retrying...",
677
+ )
678
+ return True # Return True to continue the retry loop
679
+
680
+ # Display detailed hook errors in verbose mode
681
+ if getattr(options, "verbose", False):
682
+ self._display_verbose_hook_errors(results, hook_type)
683
+
506
684
  detailed_error_msg = self._build_detailed_hook_error_message(results, summary)
507
685
 
508
686
  self.session.fail_task(
@@ -511,13 +689,40 @@ class PhaseCoordinator:
511
689
  )
512
690
  return False
513
691
 
692
+ def _display_verbose_hook_errors(
693
+ self, results: list[t.Any], hook_type: str
694
+ ) -> None:
695
+ """Display detailed hook error output in verbose mode."""
696
+ self.console.print(
697
+ f"\n[bold yellow]📋 Detailed {hook_type} hook errors:[/bold yellow]"
698
+ )
699
+
700
+ for result in results:
701
+ # Check if this hook failed
702
+ status = getattr(result, "status", "")
703
+ if status not in ("failed", "error", "timeout"):
704
+ continue
705
+
706
+ hook_name = getattr(result, "name", "unknown")
707
+ issues = getattr(result, "issues_found", [])
708
+
709
+ self.console.print(f"\n[red]❌ {hook_name}[/red]")
710
+
711
+ if issues:
712
+ for issue in issues:
713
+ if isinstance(issue, str) and issue.strip():
714
+ # Clean up the issue text and display with proper indentation
715
+ cleaned_issue = issue.strip()
716
+ self.console.print(f" {cleaned_issue}")
717
+ else:
718
+ # If no specific issues, show generic failure message
719
+ self.console.print(f" Hook failed with exit code (status: {status})")
720
+
514
721
  def _build_detailed_hook_error_message(
515
722
  self, results: list[t.Any], summary: dict[str, t.Any]
516
723
  ) -> str:
517
- """Build detailed error message with specific hook failure information."""
518
724
  error_parts = [f"{summary['failed']} failed, {summary['errors']} errors"]
519
725
 
520
- # Extract specific hook failures
521
726
  failed_hooks = []
522
727
  for result in results:
523
728
  if hasattr(result, "failed") and result.failed:
@@ -532,38 +737,20 @@ class PhaseCoordinator:
532
737
  return " | ".join(error_parts)
533
738
 
534
739
  def _should_retry_fast_hooks(self, results: list[t.Any]) -> bool:
535
- formatting_hooks = {
536
- "ruff-format",
537
- "ruff-check",
538
- "trailing-whitespace",
539
- "end-of-file-fixer",
540
- }
541
-
542
740
  for result in results:
543
- hook_id = getattr(result, "hook_id", "") or getattr(result, "name", "")
544
- if (
545
- hook_id in formatting_hooks
546
- and hasattr(result, "failed")
547
- and result.failed
548
- ):
549
- output = getattr(result, "output", "") or getattr(result, "stdout", "")
550
- if any(
551
- phrase in output.lower()
552
- for phrase in (
553
- "files were modified",
554
- "fixed",
555
- "reformatted",
556
- "fixing",
557
- )
558
- ):
559
- return True
741
+ if hasattr(result, "failed") and result.failed:
742
+ return True
743
+
744
+ status = getattr(result, "status", "")
745
+ if status in ("failed", "error", "timeout"):
746
+ return True
560
747
  return False
561
748
 
562
749
  def _apply_retry_backoff(self, attempt: int) -> None:
563
750
  if attempt > 0:
564
751
  backoff_delay = 2 ** (attempt - 1)
565
752
  self.logger.debug(f"Applying exponential backoff: {backoff_delay}s")
566
- self.console.print(f"[dim]Waiting {backoff_delay}s before retry...[/dim]")
753
+ self.console.print(f"[dim]Waiting {backoff_delay}s before retry...[/ dim]")
567
754
  time.sleep(backoff_delay)
568
755
 
569
756
  def _handle_hook_success(self, hook_type: str, summary: dict[str, t.Any]) -> bool:
@@ -571,7 +758,7 @@ class PhaseCoordinator:
571
758
  f"{hook_type} hooks passed: {summary['passed']} / {summary['total']}",
572
759
  )
573
760
  self.console.print(
574
- f"[green]✅[/green] {hook_type.title()} hooks passed: {summary['passed']} / {summary['total']}",
761
+ f"[green]✅[/ green] {hook_type.title()} hooks passed: {summary['passed']} / {summary['total']}",
575
762
  )
576
763
  self.session.complete_task(
577
764
  f"{hook_type}_hooks",
@@ -580,6 +767,6 @@ class PhaseCoordinator:
580
767
  return True
581
768
 
582
769
  def _handle_hook_exception(self, hook_type: str, e: Exception) -> bool:
583
- self.console.print(f"[red]❌[/red] {hook_type.title()} hooks error: {e}")
770
+ self.console.print(f"[red]❌[/ red] {hook_type.title()} hooks error: {e}")
584
771
  self.session.fail_task(f"{hook_type}_hooks", str(e))
585
772
  return False