crackerjack 0.32.0__py3-none-any.whl → 0.33.1__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/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +64 -6
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +257 -218
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +558 -240
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +66 -13
- crackerjack/managers/test_command_builder.py +5 -17
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +109 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +161 -32
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +174 -33
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +15 -12
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +3 -0
- crackerjack/mixins/error_handling.py +145 -0
- crackerjack/models/config.py +21 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +176 -107
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/models/task.py +3 -0
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +90 -105
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +18 -11
- crackerjack/services/config_merge.py +30 -85
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +41 -17
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +41 -45
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +30 -33
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +409 -0
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +416 -0
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +250 -576
- crackerjack/services/performance_cache.py +382 -0
- crackerjack/services/performance_monitor.py +565 -0
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +605 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +61 -30
- crackerjack/services/security_logger.py +18 -22
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
- crackerjack-0.33.1.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.32.0.dist-info/RECORD +0 -180
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,6 +7,7 @@ from rich.console import Console
|
|
|
7
7
|
|
|
8
8
|
from crackerjack.code_cleaner import CodeCleaner, PackageCleaningResult
|
|
9
9
|
from crackerjack.core.autofix_coordinator import AutofixCoordinator
|
|
10
|
+
from crackerjack.mixins import ErrorHandlingMixin
|
|
10
11
|
from crackerjack.models.protocols import (
|
|
11
12
|
ConfigMergeServiceProtocol,
|
|
12
13
|
FileSystemInterface,
|
|
@@ -16,12 +17,20 @@ from crackerjack.models.protocols import (
|
|
|
16
17
|
PublishManager,
|
|
17
18
|
TestManagerProtocol,
|
|
18
19
|
)
|
|
19
|
-
from crackerjack.services.
|
|
20
|
+
from crackerjack.services.memory_optimizer import (
|
|
21
|
+
create_lazy_service,
|
|
22
|
+
get_memory_optimizer,
|
|
23
|
+
)
|
|
24
|
+
from crackerjack.services.parallel_executor import (
|
|
25
|
+
get_async_executor,
|
|
26
|
+
get_parallel_executor,
|
|
27
|
+
)
|
|
28
|
+
from crackerjack.services.performance_cache import get_filesystem_cache, get_git_cache
|
|
20
29
|
|
|
21
30
|
from .session_coordinator import SessionCoordinator
|
|
22
31
|
|
|
23
32
|
|
|
24
|
-
class PhaseCoordinator:
|
|
33
|
+
class PhaseCoordinator(ErrorHandlingMixin):
|
|
25
34
|
def __init__(
|
|
26
35
|
self,
|
|
27
36
|
console: Console,
|
|
@@ -55,13 +64,26 @@ class PhaseCoordinator:
|
|
|
55
64
|
security_logger=None,
|
|
56
65
|
backup_service=None,
|
|
57
66
|
)
|
|
67
|
+
|
|
68
|
+
from crackerjack.services.config import ConfigurationService
|
|
69
|
+
|
|
58
70
|
self.config_service = ConfigurationService(console=console, pkg_path=pkg_path)
|
|
59
|
-
self.autofix_coordinator = AutofixCoordinator(
|
|
60
|
-
console=console, pkg_path=pkg_path
|
|
61
|
-
)
|
|
62
71
|
|
|
63
72
|
self.logger = logging.getLogger("crackerjack.phases")
|
|
64
73
|
|
|
74
|
+
self._memory_optimizer = get_memory_optimizer()
|
|
75
|
+
self._parallel_executor = get_parallel_executor()
|
|
76
|
+
self._async_executor = get_async_executor()
|
|
77
|
+
self._git_cache = get_git_cache()
|
|
78
|
+
self._filesystem_cache = get_filesystem_cache()
|
|
79
|
+
|
|
80
|
+
self._lazy_autofix = create_lazy_service(
|
|
81
|
+
lambda: AutofixCoordinator(console=console, pkg_path=pkg_path),
|
|
82
|
+
"autofix_coordinator",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
super().__init__()
|
|
86
|
+
|
|
65
87
|
def run_cleaning_phase(self, options: OptionsProtocol) -> bool:
|
|
66
88
|
if not options.clean:
|
|
67
89
|
return True
|
|
@@ -71,29 +93,54 @@ class PhaseCoordinator:
|
|
|
71
93
|
self._display_cleaning_header()
|
|
72
94
|
return self._execute_cleaning_process()
|
|
73
95
|
except Exception as e:
|
|
74
|
-
self.
|
|
96
|
+
self.handle_subprocess_error(e, [], "Code cleaning", critical=False)
|
|
75
97
|
self.session.fail_task("cleaning", str(e))
|
|
76
98
|
return False
|
|
77
99
|
|
|
78
100
|
def _display_cleaning_header(self) -> None:
|
|
79
|
-
self.console.print("\n" + "-" *
|
|
101
|
+
self.console.print("\n" + "-" * 40)
|
|
80
102
|
self.console.print(
|
|
81
103
|
"[bold bright_magenta]🛠️ SETUP[/bold bright_magenta] [bold bright_white]Initializing project structure[/bold bright_white]",
|
|
82
104
|
)
|
|
83
|
-
self.console.print("-" *
|
|
105
|
+
self.console.print("-" * 40 + "\n")
|
|
84
106
|
self.console.print("[yellow]🧹[/yellow] Starting code cleaning...")
|
|
85
107
|
|
|
108
|
+
def _display_version_bump_header(self, version_type: str) -> None:
|
|
109
|
+
self.console.print("\n" + "-" * 74)
|
|
110
|
+
self.console.print(
|
|
111
|
+
f"[bold bright_magenta]📦 BUMP VERSION[/bold bright_magenta] [bold bright_white]Updating package version ({version_type})[/bold bright_white]",
|
|
112
|
+
)
|
|
113
|
+
self.console.print("-" * 74 + "\n")
|
|
114
|
+
|
|
115
|
+
def _display_publish_header(self) -> None:
|
|
116
|
+
self.console.print("\n" + "-" * 74)
|
|
117
|
+
self.console.print(
|
|
118
|
+
"[bold bright_yellow]🚀 PUBLISH[/bold bright_yellow] [bold bright_white]Publishing to PyPI[/bold bright_white]",
|
|
119
|
+
)
|
|
120
|
+
self.console.print("-" * 74 + "\n")
|
|
121
|
+
|
|
122
|
+
def _display_git_staging_header(self) -> None:
|
|
123
|
+
self.console.print("\n" + "-" * 74)
|
|
124
|
+
self.console.print(
|
|
125
|
+
"[bold bright_cyan]🏷️ GIT OPERATIONS[/bold bright_cyan] [bold bright_white]Staging files and creating tags[/bold bright_white]",
|
|
126
|
+
)
|
|
127
|
+
self.console.print("-" * 74 + "\n")
|
|
128
|
+
|
|
129
|
+
def _display_commit_push_header(self) -> None:
|
|
130
|
+
self.console.print("\n" + "-" * 74)
|
|
131
|
+
self.console.print(
|
|
132
|
+
"[bold bright_green]📤 COMMIT & PUSH[/bold bright_green] [bold bright_white]Committing and pushing changes[/bold bright_white]",
|
|
133
|
+
)
|
|
134
|
+
self.console.print("-" * 74 + "\n")
|
|
135
|
+
|
|
86
136
|
def _execute_cleaning_process(self) -> bool:
|
|
87
|
-
# Use the comprehensive backup cleaning system for safety
|
|
88
137
|
cleaning_result = self.code_cleaner.clean_files(self.pkg_path, use_backup=True)
|
|
89
138
|
|
|
90
139
|
if isinstance(cleaning_result, list):
|
|
91
|
-
# Legacy mode (should not happen with use_backup=True, but handle gracefully)
|
|
92
140
|
cleaned_files = [str(r.file_path) for r in cleaning_result if r.success]
|
|
93
141
|
self._report_cleaning_results(cleaned_files)
|
|
94
142
|
return all(r.success for r in cleaning_result) if cleaning_result else True
|
|
95
143
|
else:
|
|
96
|
-
# PackageCleaningResult from backup mode
|
|
97
144
|
self._report_package_cleaning_results(cleaning_result)
|
|
98
145
|
return cleaning_result.overall_success
|
|
99
146
|
|
|
@@ -114,7 +161,6 @@ class PhaseCoordinator:
|
|
|
114
161
|
self.session.complete_task("cleaning", "No cleaning needed")
|
|
115
162
|
|
|
116
163
|
def _report_package_cleaning_results(self, result: PackageCleaningResult) -> None:
|
|
117
|
-
"""Report package cleaning results with backup information."""
|
|
118
164
|
if result.overall_success:
|
|
119
165
|
self.console.print(
|
|
120
166
|
f"[green]✅[/ green] Package cleaning completed successfully! "
|
|
@@ -156,35 +202,18 @@ class PhaseCoordinator:
|
|
|
156
202
|
self._complete_configuration_task(success)
|
|
157
203
|
return success
|
|
158
204
|
except Exception as e:
|
|
205
|
+
self.handle_subprocess_error(e, [], "Configuration phase", critical=False)
|
|
159
206
|
self.session.fail_task("configuration", str(e))
|
|
160
207
|
return False
|
|
161
208
|
|
|
162
209
|
def _execute_configuration_steps(self, options: OptionsProtocol) -> bool:
|
|
163
|
-
"""Execute all configuration steps and return overall success."""
|
|
164
210
|
success = True
|
|
165
211
|
|
|
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
212
|
success &= self._update_configuration_files(options)
|
|
175
213
|
|
|
176
214
|
return success
|
|
177
215
|
|
|
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
216
|
def _update_configuration_files(self, options: OptionsProtocol) -> bool:
|
|
187
|
-
"""Update precommit and pyproject configuration files."""
|
|
188
217
|
success = True
|
|
189
218
|
if not self.config_service.update_precommit_config(options):
|
|
190
219
|
success = False
|
|
@@ -193,7 +222,6 @@ class PhaseCoordinator:
|
|
|
193
222
|
return success
|
|
194
223
|
|
|
195
224
|
def _complete_configuration_task(self, success: bool) -> None:
|
|
196
|
-
"""Complete the configuration task with appropriate message."""
|
|
197
225
|
message = (
|
|
198
226
|
"Configuration updated successfully"
|
|
199
227
|
if success
|
|
@@ -201,95 +229,6 @@ class PhaseCoordinator:
|
|
|
201
229
|
)
|
|
202
230
|
self.session.complete_task("configuration", message)
|
|
203
231
|
|
|
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")
|
|
208
|
-
|
|
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
|
|
223
|
-
if self._is_crackerjack_project():
|
|
224
|
-
if self._smart_merge_project_configs():
|
|
225
|
-
merged_files.extend(["pyproject.toml", ".pre-commit-config.yaml"])
|
|
226
|
-
|
|
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__})"
|
|
242
|
-
)
|
|
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
|
-
|
|
275
|
-
except Exception as 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}")
|
|
291
|
-
return False
|
|
292
|
-
|
|
293
232
|
def _is_crackerjack_project(self) -> bool:
|
|
294
233
|
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
295
234
|
if not pyproject_path.exists():
|
|
@@ -301,58 +240,11 @@ class PhaseCoordinator:
|
|
|
301
240
|
with pyproject_path.open("rb") as f:
|
|
302
241
|
data = tomllib.load(f)
|
|
303
242
|
|
|
304
|
-
project_name = data.get("project", {}).get("name", "")
|
|
243
|
+
project_name: str = data.get("project", {}).get("name", "")
|
|
305
244
|
return project_name == "crackerjack"
|
|
306
245
|
except Exception:
|
|
307
246
|
return False
|
|
308
247
|
|
|
309
|
-
def _copy_config_files_to_package(self) -> bool:
|
|
310
|
-
try:
|
|
311
|
-
files_to_copy = [
|
|
312
|
-
"pyproject.toml",
|
|
313
|
-
".pre-commit-config.yaml",
|
|
314
|
-
"CLAUDE.md",
|
|
315
|
-
"RULES.md",
|
|
316
|
-
".gitignore",
|
|
317
|
-
"example.mcp.json",
|
|
318
|
-
"uv.lock",
|
|
319
|
-
]
|
|
320
|
-
|
|
321
|
-
package_dir = self.pkg_path / "crackerjack"
|
|
322
|
-
if not package_dir.exists():
|
|
323
|
-
self.console.print(
|
|
324
|
-
"[yellow]⚠️[/ yellow] Package directory not found: crackerjack /",
|
|
325
|
-
)
|
|
326
|
-
return False
|
|
327
|
-
|
|
328
|
-
copied_count = 0
|
|
329
|
-
for filename in files_to_copy:
|
|
330
|
-
src_path = self.pkg_path / filename
|
|
331
|
-
if src_path.exists():
|
|
332
|
-
dst_path = package_dir / filename
|
|
333
|
-
try:
|
|
334
|
-
import shutil
|
|
335
|
-
|
|
336
|
-
shutil.copy2(src_path, dst_path)
|
|
337
|
-
copied_count += 1
|
|
338
|
-
self.logger.debug(f"Copied {filename} to package directory")
|
|
339
|
-
except Exception as e:
|
|
340
|
-
self.console.print(
|
|
341
|
-
f"[yellow]⚠️[/ yellow] Failed to copy {filename}: {e}",
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
if copied_count > 0:
|
|
345
|
-
self.console.print(
|
|
346
|
-
f"[green]✅[/ green] Copied {copied_count} config files to package directory",
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
return True
|
|
350
|
-
except Exception as e:
|
|
351
|
-
self.console.print(
|
|
352
|
-
f"[red]❌[/ red] Failed to copy config files to package: {e}",
|
|
353
|
-
)
|
|
354
|
-
return False
|
|
355
|
-
|
|
356
248
|
def run_hooks_phase(self, options: OptionsProtocol) -> bool:
|
|
357
249
|
if options.skip_hooks:
|
|
358
250
|
return True
|
|
@@ -370,32 +262,26 @@ class PhaseCoordinator:
|
|
|
370
262
|
if options.skip_hooks:
|
|
371
263
|
return True
|
|
372
264
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
self.hook_manager.run_fast_hooks,
|
|
376
|
-
options,
|
|
377
|
-
)
|
|
265
|
+
hook_results = self.hook_manager.run_fast_hooks()
|
|
266
|
+
return all(r.status == "passed" for r in hook_results)
|
|
378
267
|
|
|
379
268
|
def run_comprehensive_hooks_only(self, options: OptionsProtocol) -> bool:
|
|
380
269
|
if options.skip_hooks:
|
|
381
270
|
return True
|
|
382
271
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
self.hook_manager.run_comprehensive_hooks,
|
|
386
|
-
options,
|
|
387
|
-
)
|
|
272
|
+
hook_results = self.hook_manager.run_comprehensive_hooks()
|
|
273
|
+
return all(r.status == "passed" for r in hook_results)
|
|
388
274
|
|
|
389
275
|
def run_testing_phase(self, options: OptionsProtocol) -> bool:
|
|
390
276
|
if not options.test:
|
|
391
277
|
return True
|
|
392
278
|
self.session.track_task("testing", "Test execution")
|
|
393
279
|
try:
|
|
394
|
-
self.console.print("\n" + "-" *
|
|
280
|
+
self.console.print("\n" + "-" * 74)
|
|
395
281
|
self.console.print(
|
|
396
282
|
"[bold bright_blue]🧪 TESTS[/ bold bright_blue] [bold bright_white]Running test suite[/ bold bright_white]",
|
|
397
283
|
)
|
|
398
|
-
self.console.print("-" *
|
|
284
|
+
self.console.print("-" * 74 + "\n")
|
|
399
285
|
if not self.test_manager.validate_test_environment():
|
|
400
286
|
self.session.fail_task("testing", "Test environment validation failed")
|
|
401
287
|
return False
|
|
@@ -430,9 +316,15 @@ class PhaseCoordinator:
|
|
|
430
316
|
|
|
431
317
|
def _determine_version_type(self, options: OptionsProtocol) -> str | None:
|
|
432
318
|
if options.publish:
|
|
433
|
-
|
|
319
|
+
publish_value: str | None = (
|
|
320
|
+
options.publish if isinstance(options.publish, str) else None
|
|
321
|
+
)
|
|
322
|
+
return publish_value
|
|
434
323
|
if options.all:
|
|
435
|
-
|
|
324
|
+
all_value: str | None = (
|
|
325
|
+
options.all if isinstance(options.all, str) else None
|
|
326
|
+
)
|
|
327
|
+
return all_value
|
|
436
328
|
if options.bump:
|
|
437
329
|
self._handle_version_bump_only(options.bump)
|
|
438
330
|
return None
|
|
@@ -443,9 +335,12 @@ class PhaseCoordinator:
|
|
|
443
335
|
options: OptionsProtocol,
|
|
444
336
|
version_type: str,
|
|
445
337
|
) -> bool:
|
|
338
|
+
# Display version bump header
|
|
339
|
+
self._display_version_bump_header(version_type)
|
|
446
340
|
new_version = self.publish_manager.bump_version(version_type)
|
|
447
341
|
|
|
448
|
-
#
|
|
342
|
+
# Display git operations header for staging and tagging
|
|
343
|
+
self._display_git_staging_header()
|
|
449
344
|
self.console.print("[blue]📂[/ blue] Staging all changes for publishing...")
|
|
450
345
|
if not self.git_service.add_all_files():
|
|
451
346
|
self.console.print(
|
|
@@ -455,6 +350,8 @@ class PhaseCoordinator:
|
|
|
455
350
|
if not options.no_git_tags:
|
|
456
351
|
self.publish_manager.create_git_tag(new_version)
|
|
457
352
|
|
|
353
|
+
# Display publish header
|
|
354
|
+
self._display_publish_header()
|
|
458
355
|
if self.publish_manager.publish_package():
|
|
459
356
|
self._handle_successful_publish(options, new_version)
|
|
460
357
|
return True
|
|
@@ -476,6 +373,9 @@ class PhaseCoordinator:
|
|
|
476
373
|
def run_commit_phase(self, options: OptionsProtocol) -> bool:
|
|
477
374
|
if not options.commit:
|
|
478
375
|
return True
|
|
376
|
+
|
|
377
|
+
# Display commit & push header
|
|
378
|
+
self._display_commit_push_header()
|
|
479
379
|
self.session.track_task("commit", "Git commit and push")
|
|
480
380
|
try:
|
|
481
381
|
changed_files = self.git_service.get_changed_files()
|
|
@@ -491,7 +391,6 @@ class PhaseCoordinator:
|
|
|
491
391
|
def _handle_no_changes_to_commit(self) -> bool:
|
|
492
392
|
self.console.print("[yellow]ℹ️[/ yellow] No changes to commit")
|
|
493
393
|
|
|
494
|
-
# Check if there are unpushed commits
|
|
495
394
|
from contextlib import suppress
|
|
496
395
|
|
|
497
396
|
with suppress(ValueError, Exception):
|
|
@@ -559,6 +458,8 @@ class PhaseCoordinator:
|
|
|
559
458
|
def _handle_version_bump_only(self, bump_type: str) -> bool:
|
|
560
459
|
self.session.track_task("version_bump", f"Version bump ({bump_type})")
|
|
561
460
|
try:
|
|
461
|
+
# Display version bump header
|
|
462
|
+
self._display_version_bump_header(bump_type)
|
|
562
463
|
new_version = self.publish_manager.bump_version(bump_type)
|
|
563
464
|
self.console.print(f"[green]🎯[/ green] Version bumped to {new_version}")
|
|
564
465
|
self.session.complete_task("version_bump", f"Bumped to {new_version}")
|
|
@@ -573,6 +474,49 @@ class PhaseCoordinator:
|
|
|
573
474
|
changed_files: list[str],
|
|
574
475
|
options: OptionsProtocol,
|
|
575
476
|
) -> str:
|
|
477
|
+
# Check if smart commit is enabled
|
|
478
|
+
if getattr(options, "smart_commit", False):
|
|
479
|
+
try:
|
|
480
|
+
from crackerjack.services.intelligent_commit import (
|
|
481
|
+
CommitMessageGenerator,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
self.console.print(
|
|
485
|
+
"[cyan]🤖[/cyan] Generating intelligent commit message..."
|
|
486
|
+
)
|
|
487
|
+
commit_generator = CommitMessageGenerator(
|
|
488
|
+
console=self.console, git_service=self.git_service
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
intelligent_message = commit_generator.generate_commit_message(
|
|
492
|
+
include_body=False,
|
|
493
|
+
conventional_commits=True,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
if not options.interactive:
|
|
497
|
+
self.console.print(
|
|
498
|
+
f"[green]✨[/green] Generated: {intelligent_message}"
|
|
499
|
+
)
|
|
500
|
+
return intelligent_message
|
|
501
|
+
|
|
502
|
+
# In interactive mode, offer the intelligent message plus fallback suggestions
|
|
503
|
+
suggestions = [intelligent_message]
|
|
504
|
+
fallback_suggestions = self.git_service.get_commit_message_suggestions(
|
|
505
|
+
changed_files
|
|
506
|
+
)
|
|
507
|
+
suggestions.extend(
|
|
508
|
+
fallback_suggestions[:3]
|
|
509
|
+
) # Add up to 3 fallback options
|
|
510
|
+
|
|
511
|
+
return self._interactive_commit_message_selection(suggestions)
|
|
512
|
+
|
|
513
|
+
except Exception as e:
|
|
514
|
+
self.console.print(
|
|
515
|
+
f"[yellow]⚠️[/yellow] Intelligent commit generation failed: {e}"
|
|
516
|
+
)
|
|
517
|
+
# Fallback to original logic
|
|
518
|
+
|
|
519
|
+
# Original logic for non-smart commits
|
|
576
520
|
suggestions = self.git_service.get_commit_message_suggestions(changed_files)
|
|
577
521
|
|
|
578
522
|
if not suggestions:
|
|
@@ -615,33 +559,58 @@ class PhaseCoordinator:
|
|
|
615
559
|
|
|
616
560
|
for attempt in range(max_retries):
|
|
617
561
|
try:
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
return
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
summary,
|
|
634
|
-
results,
|
|
635
|
-
attempt,
|
|
636
|
-
max_retries,
|
|
637
|
-
)
|
|
638
|
-
return self._handle_hook_success(hook_type, summary)
|
|
562
|
+
execution_result = self._execute_single_hook_attempt(hook_runner)
|
|
563
|
+
if execution_result is None:
|
|
564
|
+
return False
|
|
565
|
+
|
|
566
|
+
results, summary = execution_result
|
|
567
|
+
should_continue = self._process_hook_results(
|
|
568
|
+
hook_type, options, summary, results, attempt, max_retries
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
if should_continue == "continue":
|
|
572
|
+
continue
|
|
573
|
+
elif should_continue == "success":
|
|
574
|
+
return True
|
|
575
|
+
else:
|
|
576
|
+
return False
|
|
639
577
|
|
|
640
578
|
except Exception as e:
|
|
641
579
|
return self._handle_hook_exception(hook_type, e)
|
|
642
580
|
|
|
643
581
|
return False
|
|
644
582
|
|
|
583
|
+
def _execute_single_hook_attempt(
|
|
584
|
+
self, hook_runner: t.Callable[[], list[t.Any]]
|
|
585
|
+
) -> tuple[list[t.Any], dict[str, t.Any]] | None:
|
|
586
|
+
try:
|
|
587
|
+
results = hook_runner()
|
|
588
|
+
summary = self.hook_manager.get_hook_summary(results)
|
|
589
|
+
return results, summary
|
|
590
|
+
except Exception:
|
|
591
|
+
return None
|
|
592
|
+
|
|
593
|
+
def _process_hook_results(
|
|
594
|
+
self,
|
|
595
|
+
hook_type: str,
|
|
596
|
+
options: OptionsProtocol,
|
|
597
|
+
summary: dict[str, t.Any],
|
|
598
|
+
results: list[t.Any],
|
|
599
|
+
attempt: int,
|
|
600
|
+
max_retries: int,
|
|
601
|
+
) -> str:
|
|
602
|
+
if not self._has_hook_failures(summary):
|
|
603
|
+
self._handle_hook_success(hook_type, summary)
|
|
604
|
+
return "success"
|
|
605
|
+
|
|
606
|
+
if self._should_retry_hooks(hook_type, attempt, max_retries, results):
|
|
607
|
+
return "continue"
|
|
608
|
+
|
|
609
|
+
self._handle_hook_failures(
|
|
610
|
+
hook_type, options, summary, results, attempt, max_retries
|
|
611
|
+
)
|
|
612
|
+
return "failure"
|
|
613
|
+
|
|
645
614
|
def _initialize_hook_execution(self, hook_type: str) -> None:
|
|
646
615
|
self.logger.info(f"Starting {hook_type} hooks execution")
|
|
647
616
|
self.session.track_task(
|
|
@@ -671,11 +640,12 @@ class PhaseCoordinator:
|
|
|
671
640
|
return False
|
|
672
641
|
|
|
673
642
|
def _attempt_autofix_for_fast_hooks(self, results: list[t.Any]) -> bool:
|
|
674
|
-
"""Attempt to autofix fast hook failures."""
|
|
675
643
|
try:
|
|
676
644
|
self.logger.info("Attempting autofix for fast hook failures")
|
|
677
|
-
|
|
678
|
-
|
|
645
|
+
|
|
646
|
+
autofix_coordinator = self._lazy_autofix.get()
|
|
647
|
+
fix_result: bool = autofix_coordinator.apply_fast_stage_fixes()
|
|
648
|
+
return fix_result
|
|
679
649
|
except Exception as e:
|
|
680
650
|
self.logger.warning(f"Autofix attempt failed: {e}")
|
|
681
651
|
return False
|
|
@@ -697,15 +667,13 @@ class PhaseCoordinator:
|
|
|
697
667
|
f"[red]❌[/ red] {hook_type.title()} hooks failed: {summary['failed']} failed, {summary['errors']} errors",
|
|
698
668
|
)
|
|
699
669
|
|
|
700
|
-
# Try autofix for fast hooks before giving up
|
|
701
670
|
if hook_type == "fast" and attempt < max_retries - 1:
|
|
702
671
|
if self._attempt_autofix_for_fast_hooks(results):
|
|
703
672
|
self.console.print(
|
|
704
673
|
"[yellow]🔧[/ yellow] Applied autofixes for fast hooks, retrying...",
|
|
705
674
|
)
|
|
706
|
-
return True
|
|
675
|
+
return True
|
|
707
676
|
|
|
708
|
-
# Display detailed hook errors in verbose mode
|
|
709
677
|
if getattr(options, "verbose", False):
|
|
710
678
|
self._display_verbose_hook_errors(results, hook_type)
|
|
711
679
|
|
|
@@ -720,13 +688,11 @@ class PhaseCoordinator:
|
|
|
720
688
|
def _display_verbose_hook_errors(
|
|
721
689
|
self, results: list[t.Any], hook_type: str
|
|
722
690
|
) -> None:
|
|
723
|
-
"""Display detailed hook error output in verbose mode."""
|
|
724
691
|
self.console.print(
|
|
725
|
-
f"\n[bold yellow]📋 Detailed {hook_type} hook errors:[/bold yellow]"
|
|
692
|
+
f"\n[bold yellow]📋 Detailed {hook_type} hook errors: [/bold yellow]"
|
|
726
693
|
)
|
|
727
694
|
|
|
728
695
|
for result in results:
|
|
729
|
-
# Check if this hook failed
|
|
730
696
|
status = getattr(result, "status", "")
|
|
731
697
|
if status not in ("failed", "error", "timeout"):
|
|
732
698
|
continue
|
|
@@ -739,12 +705,10 @@ class PhaseCoordinator:
|
|
|
739
705
|
if issues:
|
|
740
706
|
for issue in issues:
|
|
741
707
|
if isinstance(issue, str) and issue.strip():
|
|
742
|
-
# Clean up the issue text and display with proper indentation
|
|
743
708
|
cleaned_issue = issue.strip()
|
|
744
|
-
self.console.print(f"
|
|
709
|
+
self.console.print(f" {cleaned_issue}")
|
|
745
710
|
else:
|
|
746
|
-
|
|
747
|
-
self.console.print(f" Hook failed with exit code (status: {status})")
|
|
711
|
+
self.console.print(f" Hook failed with exit code (status: {status})")
|
|
748
712
|
|
|
749
713
|
def _build_detailed_hook_error_message(
|
|
750
714
|
self, results: list[t.Any], summary: dict[str, t.Any]
|
|
@@ -798,3 +762,78 @@ class PhaseCoordinator:
|
|
|
798
762
|
self.console.print(f"[red]❌[/ red] {hook_type.title()} hooks error: {e}")
|
|
799
763
|
self.session.fail_task(f"{hook_type}_hooks", str(e))
|
|
800
764
|
return False
|
|
765
|
+
|
|
766
|
+
async def _execute_hooks_with_parallel_support(
|
|
767
|
+
self,
|
|
768
|
+
hook_type: str,
|
|
769
|
+
hook_runner: t.Callable[[], list[t.Any]],
|
|
770
|
+
options: OptionsProtocol,
|
|
771
|
+
) -> bool:
|
|
772
|
+
self._initialize_hook_execution(hook_type)
|
|
773
|
+
|
|
774
|
+
try:
|
|
775
|
+
return await self._process_parallel_hook_execution(
|
|
776
|
+
hook_type, hook_runner, options
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
except Exception as e:
|
|
780
|
+
return self._handle_hook_exception(hook_type, e)
|
|
781
|
+
|
|
782
|
+
async def _process_parallel_hook_execution(
|
|
783
|
+
self,
|
|
784
|
+
hook_type: str,
|
|
785
|
+
hook_runner: t.Callable[[], list[t.Any]],
|
|
786
|
+
options: OptionsProtocol,
|
|
787
|
+
) -> bool:
|
|
788
|
+
results = hook_runner()
|
|
789
|
+
summary = self.hook_manager.get_hook_summary(results)
|
|
790
|
+
|
|
791
|
+
if not self._has_hook_failures(summary):
|
|
792
|
+
return self._handle_hook_success(hook_type, summary)
|
|
793
|
+
|
|
794
|
+
return self._handle_parallel_hook_failures(
|
|
795
|
+
hook_type, hook_runner, options, results, summary
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
def _handle_parallel_hook_failures(
|
|
799
|
+
self,
|
|
800
|
+
hook_type: str,
|
|
801
|
+
hook_runner: t.Callable[[], list[t.Any]],
|
|
802
|
+
options: OptionsProtocol,
|
|
803
|
+
results: list[t.Any],
|
|
804
|
+
summary: dict[str, t.Any],
|
|
805
|
+
) -> bool:
|
|
806
|
+
if hook_type != "fast":
|
|
807
|
+
return self._handle_hook_failures(
|
|
808
|
+
hook_type, options, summary, results, 0, 1
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
if not self._attempt_autofix_for_fast_hooks(results):
|
|
812
|
+
return self._handle_hook_failures(
|
|
813
|
+
hook_type, options, summary, results, 0, 1
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
return self._retry_hooks_after_autofix(hook_type, hook_runner, options)
|
|
817
|
+
|
|
818
|
+
def _retry_hooks_after_autofix(
|
|
819
|
+
self,
|
|
820
|
+
hook_type: str,
|
|
821
|
+
hook_runner: t.Callable[[], list[t.Any]],
|
|
822
|
+
options: OptionsProtocol,
|
|
823
|
+
) -> bool:
|
|
824
|
+
self.console.print(
|
|
825
|
+
"[yellow]🔧[/ yellow] Applied autofixes for fast hooks, retrying..."
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
results = hook_runner()
|
|
829
|
+
summary = self.hook_manager.get_hook_summary(results)
|
|
830
|
+
|
|
831
|
+
if not self._has_hook_failures(summary):
|
|
832
|
+
return self._handle_hook_success(hook_type, summary)
|
|
833
|
+
|
|
834
|
+
return self._handle_hook_failures(hook_type, options, summary, results, 0, 1)
|
|
835
|
+
|
|
836
|
+
@property
|
|
837
|
+
def autofix_coordinator(self) -> AutofixCoordinator:
|
|
838
|
+
coordinator: AutofixCoordinator = self._lazy_autofix.get()
|
|
839
|
+
return coordinator
|