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
|
@@ -4,6 +4,7 @@ import signal
|
|
|
4
4
|
import subprocess
|
|
5
5
|
import tempfile
|
|
6
6
|
import time
|
|
7
|
+
import typing as t
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
import uvicorn
|
|
@@ -23,9 +24,9 @@ class WebSocketServer:
|
|
|
23
24
|
self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
|
|
24
25
|
self.is_running = True
|
|
25
26
|
self.job_manager: JobManager | None = None
|
|
26
|
-
self.app = None
|
|
27
|
+
self.app: t.Any = None
|
|
27
28
|
self.timeout_manager = get_timeout_manager()
|
|
28
|
-
self.server_task: asyncio.Task | None = None
|
|
29
|
+
self.server_task: asyncio.Task[t.Any] | None = None
|
|
29
30
|
|
|
30
31
|
def setup(self) -> None:
|
|
31
32
|
self.progress_dir.mkdir(exist_ok=True)
|
|
@@ -37,29 +38,22 @@ class WebSocketServer:
|
|
|
37
38
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
38
39
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
39
40
|
|
|
40
|
-
def _signal_handler(self, _signum: int, _frame) -> None:
|
|
41
|
+
def _signal_handler(self, _signum: int, _frame: t.Any) -> None:
|
|
41
42
|
console.print("\n[yellow]Shutting down WebSocket server...[/yellow]")
|
|
42
43
|
self.is_running = False
|
|
43
44
|
|
|
44
|
-
# Cancel server task if running
|
|
45
45
|
if self.server_task and not self.server_task.done():
|
|
46
46
|
self.server_task.cancel()
|
|
47
47
|
|
|
48
|
-
# Clean up job manager connections
|
|
49
48
|
if self.job_manager:
|
|
50
49
|
with contextlib.suppress(Exception):
|
|
51
|
-
# Give existing connections 5 seconds to close
|
|
52
50
|
asyncio.create_task(self._graceful_shutdown())
|
|
53
51
|
|
|
54
52
|
async def _graceful_shutdown(self) -> None:
|
|
55
|
-
"""Gracefully shutdown WebSocket connections."""
|
|
56
53
|
if self.job_manager:
|
|
57
54
|
try:
|
|
58
|
-
# Wait briefly for connections to close naturally
|
|
59
55
|
await asyncio.sleep(2.0)
|
|
60
56
|
|
|
61
|
-
# Force close any remaining connections
|
|
62
|
-
# Note: Implementation depends on JobManager API
|
|
63
57
|
console.print(
|
|
64
58
|
"[yellow]Forcing remaining WebSocket connections to close[/yellow]"
|
|
65
59
|
)
|
|
@@ -80,14 +74,12 @@ class WebSocketServer:
|
|
|
80
74
|
port=self.port,
|
|
81
75
|
host="127.0.0.1",
|
|
82
76
|
log_level="info",
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
timeout_graceful_shutdown=30, # Graceful shutdown timeout
|
|
77
|
+
timeout_keep_alive=30,
|
|
78
|
+
timeout_graceful_shutdown=30,
|
|
86
79
|
)
|
|
87
80
|
|
|
88
81
|
server = uvicorn.Server(config)
|
|
89
82
|
|
|
90
|
-
# Use asyncio event loop for better control
|
|
91
83
|
try:
|
|
92
84
|
asyncio.run(self._run_with_timeout(server))
|
|
93
85
|
except KeyboardInterrupt:
|
|
@@ -101,22 +93,13 @@ class WebSocketServer:
|
|
|
101
93
|
console.print("[green]WebSocket server shutdown complete[/green]")
|
|
102
94
|
|
|
103
95
|
async def _run_with_timeout(self, server: uvicorn.Server) -> None:
|
|
104
|
-
"""Run the server with timeout protection."""
|
|
105
96
|
try:
|
|
106
|
-
# Start server as a background task
|
|
107
97
|
self.server_task = asyncio.create_task(server.serve())
|
|
108
98
|
|
|
109
|
-
# Monitor server health while running
|
|
110
99
|
while self.is_running and not self.server_task.done():
|
|
111
100
|
try:
|
|
112
|
-
# Check server health periodically
|
|
113
101
|
await asyncio.sleep(5.0)
|
|
114
102
|
|
|
115
|
-
# Optional: Add health checks here
|
|
116
|
-
# if not await self._server_health_check():
|
|
117
|
-
# console.print("[yellow]Server health check failed[/yellow]")
|
|
118
|
-
# break
|
|
119
|
-
|
|
120
103
|
except asyncio.CancelledError:
|
|
121
104
|
console.print("[yellow]Server monitoring cancelled[/yellow]")
|
|
122
105
|
break
|
|
@@ -124,7 +107,6 @@ class WebSocketServer:
|
|
|
124
107
|
console.print(f"[red]Server monitoring error: {e}[/red]")
|
|
125
108
|
break
|
|
126
109
|
|
|
127
|
-
# Wait for server task to complete
|
|
128
110
|
if self.server_task and not self.server_task.done():
|
|
129
111
|
try:
|
|
130
112
|
await asyncio.wait_for(self.server_task, timeout=30.0)
|
|
@@ -153,7 +135,7 @@ def handle_websocket_server_command(
|
|
|
153
135
|
|
|
154
136
|
try:
|
|
155
137
|
result = subprocess.run(
|
|
156
|
-
["pkill", "-f", f"uvicorn.*:{port}"],
|
|
138
|
+
["pkill", "-f", f"uvicorn.*: {port}"],
|
|
157
139
|
check=False,
|
|
158
140
|
capture_output=True,
|
|
159
141
|
text=True,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import typing as t
|
|
2
3
|
from contextlib import suppress
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
@@ -24,10 +25,9 @@ class WebSocketHandler:
|
|
|
24
25
|
return
|
|
25
26
|
|
|
26
27
|
try:
|
|
27
|
-
# Add timeout to the entire connection handling
|
|
28
28
|
async with self.timeout_manager.timeout_context(
|
|
29
29
|
"websocket_connection",
|
|
30
|
-
timeout=3600.0,
|
|
30
|
+
timeout=3600.0,
|
|
31
31
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
32
32
|
):
|
|
33
33
|
await self._establish_connection(websocket, job_id)
|
|
@@ -44,13 +44,11 @@ class WebSocketHandler:
|
|
|
44
44
|
await self._cleanup_connection(job_id, websocket)
|
|
45
45
|
|
|
46
46
|
async def _establish_connection(self, websocket: WebSocket, job_id: str) -> None:
|
|
47
|
-
"""Establish WebSocket connection and add to job manager."""
|
|
48
47
|
await websocket.accept()
|
|
49
48
|
self.job_manager.add_connection(job_id, websocket)
|
|
50
49
|
console.print(f"[green]WebSocket connected for job: {job_id}[/green]")
|
|
51
50
|
|
|
52
51
|
async def _send_initial_progress(self, websocket: WebSocket, job_id: str) -> None:
|
|
53
|
-
"""Send initial progress data to the connected WebSocket."""
|
|
54
52
|
try:
|
|
55
53
|
async with self.timeout_manager.timeout_context(
|
|
56
54
|
"websocket_broadcast",
|
|
@@ -69,8 +67,7 @@ class WebSocketHandler:
|
|
|
69
67
|
f"[red]Failed to send initial progress for {job_id}: {e}[/red]"
|
|
70
68
|
)
|
|
71
69
|
|
|
72
|
-
def _create_initial_progress_message(self, job_id: str) -> dict:
|
|
73
|
-
"""Create initial progress message for new jobs."""
|
|
70
|
+
def _create_initial_progress_message(self, job_id: str) -> dict[str, t.Any]:
|
|
74
71
|
return {
|
|
75
72
|
"job_id": job_id,
|
|
76
73
|
"status": "waiting",
|
|
@@ -82,9 +79,8 @@ class WebSocketHandler:
|
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
async def _handle_message_loop(self, websocket: WebSocket, job_id: str) -> None:
|
|
85
|
-
"""Handle the main message processing loop."""
|
|
86
82
|
message_count = 0
|
|
87
|
-
max_messages = 10000
|
|
83
|
+
max_messages = 10000
|
|
88
84
|
|
|
89
85
|
while message_count < max_messages:
|
|
90
86
|
try:
|
|
@@ -105,25 +101,21 @@ class WebSocketHandler:
|
|
|
105
101
|
async def _process_single_message(
|
|
106
102
|
self, websocket: WebSocket, job_id: str, message_count: int
|
|
107
103
|
) -> bool:
|
|
108
|
-
"""Process a single WebSocket message. Returns False to break the loop."""
|
|
109
104
|
try:
|
|
110
|
-
# Add timeout to individual message operations
|
|
111
105
|
async with self.timeout_manager.timeout_context(
|
|
112
106
|
"websocket_message",
|
|
113
|
-
timeout=30.0,
|
|
107
|
+
timeout=30.0,
|
|
114
108
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
115
109
|
):
|
|
116
|
-
# Use asyncio.wait_for for additional protection
|
|
117
110
|
data = await asyncio.wait_for(
|
|
118
111
|
websocket.receive_text(),
|
|
119
|
-
timeout=25.0,
|
|
112
|
+
timeout=25.0,
|
|
120
113
|
)
|
|
121
114
|
|
|
122
115
|
console.print(
|
|
123
116
|
f"[blue]Received message {message_count} for {job_id}: {data[:100]}...[/blue]",
|
|
124
117
|
)
|
|
125
118
|
|
|
126
|
-
# Respond with timeout protection
|
|
127
119
|
await asyncio.wait_for(
|
|
128
120
|
websocket.send_json(
|
|
129
121
|
{
|
|
@@ -151,7 +143,6 @@ class WebSocketHandler:
|
|
|
151
143
|
return False
|
|
152
144
|
|
|
153
145
|
async def _handle_timeout_error(self, websocket: WebSocket, job_id: str) -> None:
|
|
154
|
-
"""Handle timeout errors during connection."""
|
|
155
146
|
console.print(
|
|
156
147
|
f"[yellow]WebSocket connection timeout for job: {job_id}[/yellow]"
|
|
157
148
|
)
|
|
@@ -161,13 +152,11 @@ class WebSocketHandler:
|
|
|
161
152
|
async def _handle_connection_error(
|
|
162
153
|
self, websocket: WebSocket, job_id: str, error: Exception
|
|
163
154
|
) -> None:
|
|
164
|
-
"""Handle connection errors."""
|
|
165
155
|
console.print(f"[red]WebSocket error for job {job_id}: {error}[/red]")
|
|
166
156
|
with suppress(Exception):
|
|
167
157
|
await websocket.close(code=1011, reason="Internal error")
|
|
168
158
|
|
|
169
159
|
async def _cleanup_connection(self, job_id: str, websocket: WebSocket) -> None:
|
|
170
|
-
"""Clean up the connection."""
|
|
171
160
|
try:
|
|
172
161
|
self.job_manager.remove_connection(job_id, websocket)
|
|
173
162
|
except Exception as e:
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import typing as t
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ErrorHandlingMixin:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self.console: Console
|
|
11
|
+
self.logger: t.Any
|
|
12
|
+
|
|
13
|
+
def handle_subprocess_error(
|
|
14
|
+
self,
|
|
15
|
+
error: Exception,
|
|
16
|
+
command: list[str],
|
|
17
|
+
operation_name: str,
|
|
18
|
+
critical: bool = False,
|
|
19
|
+
) -> bool:
|
|
20
|
+
error_msg = f"{operation_name} failed: {error}"
|
|
21
|
+
|
|
22
|
+
if hasattr(self, "logger") and self.logger:
|
|
23
|
+
self.logger.error(
|
|
24
|
+
error_msg,
|
|
25
|
+
command=" ".join(command),
|
|
26
|
+
error_type=type(error).__name__,
|
|
27
|
+
critical=critical,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if critical:
|
|
31
|
+
self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
|
|
32
|
+
else:
|
|
33
|
+
self.console.print(f"[red]❌ {error_msg}[/red]")
|
|
34
|
+
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
def handle_file_operation_error(
|
|
38
|
+
self,
|
|
39
|
+
error: Exception,
|
|
40
|
+
file_path: Path,
|
|
41
|
+
operation: str,
|
|
42
|
+
critical: bool = False,
|
|
43
|
+
) -> bool:
|
|
44
|
+
error_msg = f"Failed to {operation} {file_path}: {error}"
|
|
45
|
+
|
|
46
|
+
if hasattr(self, "logger") and self.logger:
|
|
47
|
+
self.logger.error(
|
|
48
|
+
error_msg,
|
|
49
|
+
file_path=str(file_path),
|
|
50
|
+
operation=operation,
|
|
51
|
+
error_type=type(error).__name__,
|
|
52
|
+
critical=critical,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if critical:
|
|
56
|
+
self.console.print(f"[red]🚨 CRITICAL: {error_msg}[/red]")
|
|
57
|
+
else:
|
|
58
|
+
self.console.print(f"[red]❌ {error_msg}[/red]")
|
|
59
|
+
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
def handle_timeout_error(
|
|
63
|
+
self,
|
|
64
|
+
operation_name: str,
|
|
65
|
+
timeout_seconds: float,
|
|
66
|
+
command: list[str] | None = None,
|
|
67
|
+
) -> bool:
|
|
68
|
+
error_msg = f"{operation_name} timed out after {timeout_seconds}s"
|
|
69
|
+
|
|
70
|
+
if hasattr(self, "logger") and self.logger:
|
|
71
|
+
self.logger.warning(
|
|
72
|
+
error_msg,
|
|
73
|
+
timeout=timeout_seconds,
|
|
74
|
+
command=" ".join(command) if command else None,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
self.console.print(f"[yellow]⏰ {error_msg}[/yellow]")
|
|
78
|
+
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def log_operation_success(
|
|
82
|
+
self,
|
|
83
|
+
operation_name: str,
|
|
84
|
+
details: dict[str, t.Any] | None = None,
|
|
85
|
+
) -> None:
|
|
86
|
+
if hasattr(self, "logger") and self.logger:
|
|
87
|
+
self.logger.info(
|
|
88
|
+
f"{operation_name} completed successfully", **(details or {})
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def validate_required_tools(
|
|
92
|
+
self,
|
|
93
|
+
tools: dict[str, str],
|
|
94
|
+
operation_name: str,
|
|
95
|
+
) -> bool:
|
|
96
|
+
missing_tools = []
|
|
97
|
+
|
|
98
|
+
for tool_name, command in tools.items():
|
|
99
|
+
try:
|
|
100
|
+
subprocess.run(
|
|
101
|
+
[command, "--version"],
|
|
102
|
+
capture_output=True,
|
|
103
|
+
check=True,
|
|
104
|
+
timeout=5,
|
|
105
|
+
)
|
|
106
|
+
except (
|
|
107
|
+
subprocess.CalledProcessError,
|
|
108
|
+
subprocess.TimeoutExpired,
|
|
109
|
+
FileNotFoundError,
|
|
110
|
+
):
|
|
111
|
+
missing_tools.append(tool_name)
|
|
112
|
+
|
|
113
|
+
if missing_tools:
|
|
114
|
+
error_msg = f"Missing required tools for {operation_name}: {', '.join(missing_tools)}"
|
|
115
|
+
|
|
116
|
+
if hasattr(self, "logger") and self.logger:
|
|
117
|
+
self.logger.error(
|
|
118
|
+
error_msg,
|
|
119
|
+
missing_tools=missing_tools,
|
|
120
|
+
operation=operation_name,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
self.console.print(f"[red]❌ {error_msg}[/red]")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
def safe_get_attribute(
|
|
129
|
+
self,
|
|
130
|
+
obj: t.Any,
|
|
131
|
+
attribute: str,
|
|
132
|
+
default: t.Any = None,
|
|
133
|
+
operation_name: str = "attribute access",
|
|
134
|
+
) -> t.Any:
|
|
135
|
+
try:
|
|
136
|
+
return getattr(obj, attribute, default)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
if hasattr(self, "logger") and self.logger:
|
|
139
|
+
self.logger.warning(
|
|
140
|
+
f"Error accessing {attribute} during {operation_name}: {e}",
|
|
141
|
+
attribute=attribute,
|
|
142
|
+
operation=operation_name,
|
|
143
|
+
error_type=type(e).__name__,
|
|
144
|
+
)
|
|
145
|
+
return default
|
crackerjack/models/config.py
CHANGED
|
@@ -18,6 +18,7 @@ class HookConfig:
|
|
|
18
18
|
experimental_hooks: bool = False
|
|
19
19
|
enable_pyrefly: bool = False
|
|
20
20
|
enable_ty: bool = False
|
|
21
|
+
enable_lsp_optimization: bool = False
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@dataclass
|
|
@@ -47,7 +48,7 @@ class GitConfig:
|
|
|
47
48
|
class AIConfig:
|
|
48
49
|
ai_agent: bool = False
|
|
49
50
|
start_mcp_server: bool = False
|
|
50
|
-
max_iterations: int =
|
|
51
|
+
max_iterations: int = 5
|
|
51
52
|
autofix: bool = True
|
|
52
53
|
ai_agent_autofix: bool = False
|
|
53
54
|
|
|
@@ -79,6 +80,23 @@ class EnterpriseConfig:
|
|
|
79
80
|
organization: str | None = None
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
@dataclass
|
|
84
|
+
class MCPServerConfig:
|
|
85
|
+
http_port: int = 8676
|
|
86
|
+
http_host: str = "127.0.0.1"
|
|
87
|
+
websocket_port: int = 8675
|
|
88
|
+
http_enabled: bool = False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class ZubanLSPConfig:
|
|
93
|
+
enabled: bool = True
|
|
94
|
+
auto_start: bool = True
|
|
95
|
+
port: int = 8677
|
|
96
|
+
mode: str = "stdio"
|
|
97
|
+
timeout: int = 30
|
|
98
|
+
|
|
99
|
+
|
|
82
100
|
@dataclass
|
|
83
101
|
class WorkflowOptions:
|
|
84
102
|
cleaning: CleaningConfig = field(default_factory=CleaningConfig)
|
|
@@ -91,3 +109,5 @@ class WorkflowOptions:
|
|
|
91
109
|
progress: ProgressConfig = field(default_factory=ProgressConfig)
|
|
92
110
|
cleanup: CleanupConfig = field(default_factory=CleanupConfig)
|
|
93
111
|
enterprise: EnterpriseConfig = field(default_factory=EnterpriseConfig)
|
|
112
|
+
mcp_server: MCPServerConfig = field(default_factory=MCPServerConfig)
|
|
113
|
+
zuban_lsp: ZubanLSPConfig = field(default_factory=ZubanLSPConfig)
|
|
@@ -3,18 +3,43 @@ import typing as t
|
|
|
3
3
|
from .config import (
|
|
4
4
|
AIConfig,
|
|
5
5
|
CleaningConfig,
|
|
6
|
+
CleanupConfig,
|
|
6
7
|
EnterpriseConfig,
|
|
7
8
|
ExecutionConfig,
|
|
8
9
|
GitConfig,
|
|
9
10
|
HookConfig,
|
|
11
|
+
MCPServerConfig,
|
|
10
12
|
ProgressConfig,
|
|
11
13
|
PublishConfig,
|
|
12
14
|
TestConfig,
|
|
13
15
|
WorkflowOptions,
|
|
16
|
+
ZubanLSPConfig,
|
|
14
17
|
)
|
|
15
18
|
from .protocols import OptionsProtocol
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
def _determine_max_iterations(options: OptionsProtocol) -> int:
|
|
22
|
+
"""Determine max_iterations using effective_max_iterations if available, otherwise fallback logic.
|
|
23
|
+
|
|
24
|
+
Priority:
|
|
25
|
+
1. Use effective_max_iterations property if available (handles quick/thorough flags)
|
|
26
|
+
2. Explicit max_iterations value
|
|
27
|
+
3. Default: 5 iterations
|
|
28
|
+
"""
|
|
29
|
+
# Use effective_max_iterations property if available (Options class has this)
|
|
30
|
+
if hasattr(options, "effective_max_iterations"):
|
|
31
|
+
return getattr(options, "effective_max_iterations") # type: ignore[no-any-return]
|
|
32
|
+
|
|
33
|
+
# Fallback for other OptionsProtocol implementations
|
|
34
|
+
if hasattr(options, "max_iterations") and getattr(
|
|
35
|
+
options, "max_iterations", None
|
|
36
|
+
) not in (0, None):
|
|
37
|
+
return getattr(options, "max_iterations") # type: ignore[no-any-return]
|
|
38
|
+
|
|
39
|
+
# Default to 5 iterations
|
|
40
|
+
return 5
|
|
41
|
+
|
|
42
|
+
|
|
18
43
|
class OptionsAdapter:
|
|
19
44
|
@staticmethod
|
|
20
45
|
def from_options_protocol(options: OptionsProtocol) -> WorkflowOptions:
|
|
@@ -32,6 +57,7 @@ class OptionsAdapter:
|
|
|
32
57
|
experimental_hooks=getattr(options, "experimental_hooks", False),
|
|
33
58
|
enable_pyrefly=getattr(options, "enable_pyrefly", False),
|
|
34
59
|
enable_ty=getattr(options, "enable_ty", False),
|
|
60
|
+
enable_lsp_optimization=getattr(options, "enable_lsp_hooks", False),
|
|
35
61
|
),
|
|
36
62
|
testing=TestConfig(
|
|
37
63
|
test=getattr(options, "test", False),
|
|
@@ -55,7 +81,7 @@ class OptionsAdapter:
|
|
|
55
81
|
autofix=getattr(options, "autofix", True),
|
|
56
82
|
ai_agent_autofix=getattr(options, "ai_agent_autofix", False),
|
|
57
83
|
start_mcp_server=getattr(options, "start_mcp_server", False),
|
|
58
|
-
max_iterations=
|
|
84
|
+
max_iterations=_determine_max_iterations(options),
|
|
59
85
|
),
|
|
60
86
|
execution=ExecutionConfig(
|
|
61
87
|
interactive=getattr(options, "interactive", False),
|
|
@@ -66,11 +92,29 @@ class OptionsAdapter:
|
|
|
66
92
|
progress=ProgressConfig(
|
|
67
93
|
enabled=getattr(options, "track_progress", False),
|
|
68
94
|
),
|
|
95
|
+
cleanup=CleanupConfig(
|
|
96
|
+
auto_cleanup=getattr(options, "auto_cleanup", True),
|
|
97
|
+
keep_debug_logs=getattr(options, "keep_debug_logs", 5),
|
|
98
|
+
keep_coverage_files=getattr(options, "keep_coverage_files", 10),
|
|
99
|
+
),
|
|
69
100
|
enterprise=EnterpriseConfig(
|
|
70
101
|
enabled=getattr(options, "enterprise_batch", None) is not None,
|
|
71
102
|
license_key=getattr(options, "license_key", None),
|
|
72
103
|
organization=getattr(options, "organization", None),
|
|
73
104
|
),
|
|
105
|
+
mcp_server=MCPServerConfig(
|
|
106
|
+
http_port=getattr(options, "http_port", 8676),
|
|
107
|
+
http_host=getattr(options, "http_host", "127.0.0.1"),
|
|
108
|
+
websocket_port=getattr(options, "websocket_port", 8675),
|
|
109
|
+
http_enabled=getattr(options, "http_enabled", False),
|
|
110
|
+
),
|
|
111
|
+
zuban_lsp=ZubanLSPConfig(
|
|
112
|
+
enabled=not getattr(options, "no_zuban_lsp", False),
|
|
113
|
+
auto_start=True,
|
|
114
|
+
port=getattr(options, "zuban_lsp_port", 8677),
|
|
115
|
+
mode=getattr(options, "zuban_lsp_mode", "stdio"),
|
|
116
|
+
timeout=getattr(options, "zuban_lsp_timeout", 30),
|
|
117
|
+
),
|
|
74
118
|
)
|
|
75
119
|
|
|
76
120
|
@staticmethod
|
|
@@ -200,6 +244,10 @@ class LegacyOptionsWrapper:
|
|
|
200
244
|
def enable_ty(self) -> bool:
|
|
201
245
|
return self._options.hooks.enable_ty
|
|
202
246
|
|
|
247
|
+
@property
|
|
248
|
+
def enable_lsp_hooks(self) -> bool:
|
|
249
|
+
return self._options.hooks.enable_lsp_optimization
|
|
250
|
+
|
|
203
251
|
@property
|
|
204
252
|
def no_git_tags(self) -> bool:
|
|
205
253
|
return self._options.publishing.no_git_tags
|