crackerjack 0.31.10__py3-none-any.whl → 0.31.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +50 -9
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/METADATA +197 -12
- crackerjack-0.31.13.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
"""Test progress tracking and display functionality.
|
|
2
|
-
|
|
3
|
-
This module handles real-time test execution progress tracking, including collection
|
|
4
|
-
and execution phases. Split from test_manager.py for better separation of concerns.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
1
|
import threading
|
|
8
2
|
import time
|
|
9
3
|
import typing as t
|
|
10
4
|
|
|
11
5
|
|
|
12
6
|
class TestProgress:
|
|
13
|
-
"""Tracks test execution progress with thread-safe updates."""
|
|
14
|
-
|
|
15
7
|
def __init__(self) -> None:
|
|
16
8
|
self.total_tests: int = 0
|
|
17
9
|
self.passed: int = 0
|
|
@@ -25,21 +17,18 @@ class TestProgress:
|
|
|
25
17
|
self.files_discovered: int = 0
|
|
26
18
|
self.collection_status: str = "Starting collection..."
|
|
27
19
|
self._lock = threading.Lock()
|
|
28
|
-
self._seen_files: set[str] = set()
|
|
20
|
+
self._seen_files: set[str] = set()
|
|
29
21
|
|
|
30
22
|
@property
|
|
31
23
|
def completed(self) -> int:
|
|
32
|
-
"""Total completed tests (passed + failed + skipped + errors)."""
|
|
33
24
|
return self.passed + self.failed + self.skipped + self.errors
|
|
34
25
|
|
|
35
26
|
@property
|
|
36
27
|
def elapsed_time(self) -> float:
|
|
37
|
-
"""Elapsed time since test start."""
|
|
38
28
|
return time.time() - self.start_time if self.start_time else 0
|
|
39
29
|
|
|
40
30
|
@property
|
|
41
31
|
def eta_seconds(self) -> float | None:
|
|
42
|
-
"""Estimated time to completion based on current progress rate."""
|
|
43
32
|
if self.completed <= 0 or self.total_tests <= 0:
|
|
44
33
|
return None
|
|
45
34
|
progress_rate = (
|
|
@@ -49,20 +38,17 @@ class TestProgress:
|
|
|
49
38
|
return remaining / progress_rate if progress_rate > 0 else None
|
|
50
39
|
|
|
51
40
|
def update(self, **kwargs: t.Any) -> None:
|
|
52
|
-
"""Thread-safe update of progress attributes."""
|
|
53
41
|
with self._lock:
|
|
54
42
|
for key, value in kwargs.items():
|
|
55
43
|
if hasattr(self, key):
|
|
56
44
|
setattr(self, key, value)
|
|
57
45
|
|
|
58
46
|
def format_progress(self) -> str:
|
|
59
|
-
"""Format progress display for Rich output."""
|
|
60
47
|
if self.is_collecting:
|
|
61
48
|
return self._format_collection_progress()
|
|
62
49
|
return self._format_execution_progress()
|
|
63
50
|
|
|
64
51
|
def _format_collection_progress(self) -> str:
|
|
65
|
-
"""Format test collection progress display."""
|
|
66
52
|
status_parts = [self.collection_status]
|
|
67
53
|
|
|
68
54
|
if self.files_discovered > 0:
|
|
@@ -70,20 +56,17 @@ class TestProgress:
|
|
|
70
56
|
|
|
71
57
|
elapsed = self.elapsed_time
|
|
72
58
|
if elapsed > 1:
|
|
73
|
-
status_parts.append(f"{elapsed
|
|
59
|
+
status_parts.append(f"{elapsed: .1f}s")
|
|
74
60
|
|
|
75
61
|
return " | ".join(status_parts)
|
|
76
62
|
|
|
77
63
|
def _format_execution_progress(self) -> str:
|
|
78
|
-
"""Format test execution progress display."""
|
|
79
64
|
parts = []
|
|
80
65
|
|
|
81
|
-
# Test progress
|
|
82
66
|
if self.total_tests > 0:
|
|
83
67
|
progress_pct = (self.completed / self.total_tests) * 100
|
|
84
|
-
parts.append(f"{self.completed}/{self.total_tests} ({progress_pct
|
|
68
|
+
parts.append(f"{self.completed}/{self.total_tests} ({progress_pct: .1f}%)")
|
|
85
69
|
|
|
86
|
-
# Status counts
|
|
87
70
|
status_parts = []
|
|
88
71
|
if self.passed > 0:
|
|
89
72
|
status_parts.append(f"✅ {self.passed}")
|
|
@@ -97,7 +80,6 @@ class TestProgress:
|
|
|
97
80
|
if status_parts:
|
|
98
81
|
parts.append(" ".join(status_parts))
|
|
99
82
|
|
|
100
|
-
# Current test (truncated)
|
|
101
83
|
if self.current_test and not self.is_complete:
|
|
102
84
|
test_name = (
|
|
103
85
|
self.current_test[:30] + "..."
|
|
@@ -106,9 +88,8 @@ class TestProgress:
|
|
|
106
88
|
)
|
|
107
89
|
parts.append(f"Running: {test_name}")
|
|
108
90
|
|
|
109
|
-
# Timing
|
|
110
91
|
elapsed = self.elapsed_time
|
|
111
92
|
if elapsed > 1:
|
|
112
|
-
parts.append(f"{elapsed
|
|
93
|
+
parts.append(f"{elapsed: .1f}s")
|
|
113
94
|
|
|
114
95
|
return " | ".join(parts)
|
crackerjack/mcp/cache.py
CHANGED
|
@@ -2,6 +2,7 @@ import asyncio
|
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
4
|
import typing as t
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from dataclasses import asdict, dataclass
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
@@ -188,8 +189,8 @@ class ErrorCache:
|
|
|
188
189
|
def _extract_pyright_info(self, line: str) -> tuple[str, str]:
|
|
189
190
|
error_code = ""
|
|
190
191
|
message_pattern = line
|
|
191
|
-
if "
|
|
192
|
-
parts = line.split("
|
|
192
|
+
if "-error: " in line:
|
|
193
|
+
parts = line.split("-error: ")
|
|
193
194
|
if len(parts) >= 2:
|
|
194
195
|
message_pattern = parts[1].strip()
|
|
195
196
|
if "(" in message_pattern and ")" in message_pattern:
|
|
@@ -314,23 +315,15 @@ class ErrorCache:
|
|
|
314
315
|
self.fix_results = []
|
|
315
316
|
|
|
316
317
|
def _save_patterns(self) -> None:
|
|
317
|
-
|
|
318
|
+
with suppress(OSError, json.JSONEncodeError):
|
|
318
319
|
patterns_data = {
|
|
319
320
|
pid: pattern.to_dict() for pid, pattern in self.patterns.items()
|
|
320
321
|
}
|
|
321
322
|
with self.patterns_file.open("w") as f:
|
|
322
323
|
json.dump(patterns_data, f, indent=2)
|
|
323
|
-
except (OSError, json.JSONEncodeError):
|
|
324
|
-
pass
|
|
325
|
-
except Exception:
|
|
326
|
-
pass
|
|
327
324
|
|
|
328
325
|
def _save_fixes(self) -> None:
|
|
329
|
-
|
|
326
|
+
with suppress(OSError, json.JSONEncodeError):
|
|
330
327
|
fixes_data = [result.to_dict() for result in self.fix_results]
|
|
331
328
|
with self.fixes_file.open("w") as f:
|
|
332
329
|
json.dump(fixes_data, f, indent=2)
|
|
333
|
-
except (OSError, json.JSONEncodeError):
|
|
334
|
-
pass
|
|
335
|
-
except Exception:
|
|
336
|
-
pass
|
crackerjack/mcp/client_runner.py
CHANGED
|
@@ -26,10 +26,10 @@ async def ensure_mcp_server_running() -> subprocess.Popen | None:
|
|
|
26
26
|
console = Console()
|
|
27
27
|
|
|
28
28
|
if is_mcp_server_running():
|
|
29
|
-
console.print("[green]✅ MCP server already running[/green]")
|
|
29
|
+
console.print("[green]✅ MCP server already running[/ green]")
|
|
30
30
|
return None
|
|
31
31
|
|
|
32
|
-
console.print("[yellow]🚀 Starting MCP server...[/yellow]")
|
|
32
|
+
console.print("[yellow]🚀 Starting MCP server...[/ yellow]")
|
|
33
33
|
server_process = subprocess.Popen(
|
|
34
34
|
[sys.executable, "-m", "crackerjack", "--start-mcp-server"],
|
|
35
35
|
stdout=subprocess.PIPE,
|
|
@@ -39,17 +39,17 @@ async def ensure_mcp_server_running() -> subprocess.Popen | None:
|
|
|
39
39
|
|
|
40
40
|
for _i in range(20):
|
|
41
41
|
if is_mcp_server_running():
|
|
42
|
-
console.print("[green]✅ MCP server started successfully[/green]")
|
|
42
|
+
console.print("[green]✅ MCP server started successfully[/ green]")
|
|
43
43
|
return server_process
|
|
44
44
|
await asyncio.sleep(0.5)
|
|
45
45
|
|
|
46
|
-
console.print("[red]❌ Failed to start MCP server[/red]")
|
|
46
|
+
console.print("[red]❌ Failed to start MCP server[/ red]")
|
|
47
47
|
server_process.terminate()
|
|
48
48
|
msg = "Failed to start MCP server within timeout period"
|
|
49
49
|
raise RuntimeError(msg)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def run_with_mcp_server(command: str = "/crackerjack:run") -> None:
|
|
52
|
+
async def run_with_mcp_server(command: str = "/ crackerjack: run") -> None:
|
|
53
53
|
console = Console()
|
|
54
54
|
|
|
55
55
|
server_process = await ensure_mcp_server_running()
|
|
@@ -70,12 +70,12 @@ async def run_with_mcp_server(command: str = "/crackerjack:run") -> None:
|
|
|
70
70
|
try:
|
|
71
71
|
await run_crackerjack_with_progress(session, command)
|
|
72
72
|
except Exception as e:
|
|
73
|
-
console.print(f"[bold red]Error: {e}[/bold red]")
|
|
73
|
+
console.print(f"[bold red]Error: {e}[/ bold red]")
|
|
74
74
|
sys.exit(1)
|
|
75
75
|
finally:
|
|
76
76
|
if server_process:
|
|
77
77
|
console.print(
|
|
78
|
-
"[yellow]Note: MCP server continues running in background[/yellow]",
|
|
78
|
+
"[yellow]Note: MCP server continues running in background[/ yellow]",
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
|
|
@@ -88,15 +88,15 @@ def main() -> None:
|
|
|
88
88
|
parser.add_argument(
|
|
89
89
|
"command",
|
|
90
90
|
nargs="?",
|
|
91
|
-
default="/crackerjack:run",
|
|
92
|
-
help="Command to execute (default: /crackerjack:run)",
|
|
91
|
+
default="/ crackerjack: run",
|
|
92
|
+
help="Command to execute (default: / crackerjack: run)",
|
|
93
93
|
)
|
|
94
94
|
args = parser.parse_args()
|
|
95
95
|
|
|
96
96
|
try:
|
|
97
97
|
asyncio.run(run_with_mcp_server(args.command))
|
|
98
98
|
except KeyboardInterrupt:
|
|
99
|
-
Console().print("\n[yellow]Operation cancelled by user[/yellow]")
|
|
99
|
+
Console().print("\n[yellow]Operation cancelled by user[/ yellow]")
|
|
100
100
|
sys.exit(1)
|
|
101
101
|
|
|
102
102
|
|
crackerjack/mcp/context.py
CHANGED
|
@@ -12,7 +12,13 @@ from types import TracebackType
|
|
|
12
12
|
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
|
|
15
|
+
from crackerjack.core.resource_manager import (
|
|
16
|
+
ResourceManager,
|
|
17
|
+
register_global_resource_manager,
|
|
18
|
+
)
|
|
19
|
+
from crackerjack.core.websocket_lifecycle import NetworkResourceManager
|
|
15
20
|
from crackerjack.core.workflow_orchestrator import WorkflowOrchestrator
|
|
21
|
+
from crackerjack.services.secure_path_utils import SecurePathValidator
|
|
16
22
|
|
|
17
23
|
from .cache import ErrorCache
|
|
18
24
|
from .rate_limiter import RateLimitConfig, RateLimitMiddleware
|
|
@@ -123,11 +129,31 @@ class MCPServerConfig:
|
|
|
123
129
|
state_dir: Path | None = None
|
|
124
130
|
cache_dir: Path | None = None
|
|
125
131
|
|
|
132
|
+
def __post_init__(self) -> None:
|
|
133
|
+
# Validate all paths using secure path validation
|
|
134
|
+
self.project_path = SecurePathValidator.validate_safe_path(self.project_path)
|
|
135
|
+
|
|
136
|
+
if self.progress_dir:
|
|
137
|
+
self.progress_dir = SecurePathValidator.validate_safe_path(
|
|
138
|
+
self.progress_dir
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if self.state_dir:
|
|
142
|
+
self.state_dir = SecurePathValidator.validate_safe_path(self.state_dir)
|
|
143
|
+
|
|
144
|
+
if self.cache_dir:
|
|
145
|
+
self.cache_dir = SecurePathValidator.validate_safe_path(self.cache_dir)
|
|
146
|
+
|
|
126
147
|
|
|
127
148
|
class MCPServerContext:
|
|
128
149
|
def __init__(self, config: MCPServerConfig) -> None:
|
|
129
150
|
self.config = config
|
|
130
151
|
|
|
152
|
+
# Resource management
|
|
153
|
+
self.resource_manager = ResourceManager()
|
|
154
|
+
self.network_manager = NetworkResourceManager()
|
|
155
|
+
register_global_resource_manager(self.resource_manager)
|
|
156
|
+
|
|
131
157
|
self.console: Console | None = None
|
|
132
158
|
self.cli_runner = None
|
|
133
159
|
self.state_manager: StateManager | None = None
|
|
@@ -202,6 +228,7 @@ class MCPServerContext:
|
|
|
202
228
|
if not self._initialized:
|
|
203
229
|
return
|
|
204
230
|
|
|
231
|
+
# Run custom shutdown tasks first
|
|
205
232
|
for task in reversed(self._shutdown_tasks):
|
|
206
233
|
try:
|
|
207
234
|
await task()
|
|
@@ -209,19 +236,40 @@ class MCPServerContext:
|
|
|
209
236
|
if self.console:
|
|
210
237
|
self.console.print(f"[red]Error during shutdown: {e}[/red]")
|
|
211
238
|
|
|
239
|
+
# Cancel health check task
|
|
212
240
|
if self._websocket_health_check_task:
|
|
213
241
|
self._websocket_health_check_task.cancel()
|
|
214
242
|
with contextlib.suppress(asyncio.CancelledError):
|
|
215
243
|
await self._websocket_health_check_task
|
|
216
244
|
self._websocket_health_check_task = None
|
|
217
245
|
|
|
246
|
+
# Stop WebSocket server
|
|
218
247
|
await self._stop_websocket_server()
|
|
219
248
|
|
|
249
|
+
# Stop rate limiter
|
|
220
250
|
if self.rate_limiter:
|
|
221
251
|
await self.rate_limiter.stop()
|
|
222
252
|
|
|
253
|
+
# Stop batched saver
|
|
223
254
|
await self.batched_saver.stop()
|
|
224
255
|
|
|
256
|
+
# Clean up all managed resources
|
|
257
|
+
try:
|
|
258
|
+
await self.network_manager.cleanup_all()
|
|
259
|
+
except Exception as e:
|
|
260
|
+
if self.console:
|
|
261
|
+
self.console.print(
|
|
262
|
+
f"[yellow]Warning: Network resource cleanup error: {e}[/yellow]"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
await self.resource_manager.cleanup_all()
|
|
267
|
+
except Exception as e:
|
|
268
|
+
if self.console:
|
|
269
|
+
self.console.print(
|
|
270
|
+
f"[yellow]Warning: Resource cleanup error: {e}[/yellow]"
|
|
271
|
+
)
|
|
272
|
+
|
|
225
273
|
self._initialized = False
|
|
226
274
|
|
|
227
275
|
def add_startup_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
|
|
@@ -241,9 +289,9 @@ class MCPServerContext:
|
|
|
241
289
|
uuid.UUID(job_id)
|
|
242
290
|
return True
|
|
243
291
|
|
|
244
|
-
import
|
|
292
|
+
from crackerjack.services.regex_patterns import is_valid_job_id
|
|
245
293
|
|
|
246
|
-
if not
|
|
294
|
+
if not is_valid_job_id(job_id):
|
|
247
295
|
return False
|
|
248
296
|
|
|
249
297
|
if ".." in job_id or "/" in job_id or "\\" in job_id:
|
|
@@ -268,7 +316,7 @@ class MCPServerContext:
|
|
|
268
316
|
|
|
269
317
|
if self.console:
|
|
270
318
|
self.console.print(
|
|
271
|
-
f"🚀 Starting WebSocket server on localhost:{self.websocket_server_port}...",
|
|
319
|
+
f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}...",
|
|
272
320
|
)
|
|
273
321
|
|
|
274
322
|
try:
|
|
@@ -321,6 +369,13 @@ class MCPServerContext:
|
|
|
321
369
|
start_new_session=True,
|
|
322
370
|
)
|
|
323
371
|
|
|
372
|
+
# Register the process with the network resource manager for automatic cleanup
|
|
373
|
+
if self.websocket_server_process:
|
|
374
|
+
managed_process = self.network_manager.create_subprocess(
|
|
375
|
+
self.websocket_server_process, timeout=30.0
|
|
376
|
+
)
|
|
377
|
+
await managed_process.start_monitoring()
|
|
378
|
+
|
|
324
379
|
async def _register_websocket_cleanup(self) -> None:
|
|
325
380
|
if not self._websocket_cleanup_registered:
|
|
326
381
|
self.add_shutdown_task(self._stop_websocket_server)
|
|
@@ -351,7 +406,7 @@ class MCPServerContext:
|
|
|
351
406
|
f"✅ WebSocket server started successfully on port {self.websocket_server_port}",
|
|
352
407
|
)
|
|
353
408
|
self.console.print(
|
|
354
|
-
f"📊 Progress available at: ws
|
|
409
|
+
f"📊 Progress available at: ws: / / localhost: {self.websocket_server_port}/ ws / progress /{{job_id}}",
|
|
355
410
|
)
|
|
356
411
|
return True
|
|
357
412
|
|
|
@@ -512,7 +567,11 @@ class MCPServerContext:
|
|
|
512
567
|
if not self.validate_job_id(job_id):
|
|
513
568
|
msg = f"Invalid job_id: {job_id}"
|
|
514
569
|
raise ValueError(msg)
|
|
515
|
-
|
|
570
|
+
|
|
571
|
+
# Use secure path joining to prevent directory traversal
|
|
572
|
+
return SecurePathValidator.secure_path_join(
|
|
573
|
+
self.progress_dir, f"job-{job_id}.json"
|
|
574
|
+
)
|
|
516
575
|
|
|
517
576
|
async def schedule_state_save(
|
|
518
577
|
self,
|
|
@@ -522,7 +581,6 @@ class MCPServerContext:
|
|
|
522
581
|
await self.batched_saver.schedule_save(save_id, save_func)
|
|
523
582
|
|
|
524
583
|
def get_current_time(self) -> str:
|
|
525
|
-
"""Get current timestamp as string for progress tracking."""
|
|
526
584
|
import datetime
|
|
527
585
|
|
|
528
586
|
return datetime.datetime.now().isoformat()
|
crackerjack/mcp/dashboard.py
CHANGED
|
@@ -63,7 +63,7 @@ class MetricCard(Static):
|
|
|
63
63
|
}
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
|
-
value = reactive(" --
|
|
66
|
+
value = reactive(" --")
|
|
67
67
|
label = reactive("Metric")
|
|
68
68
|
trend = reactive("")
|
|
69
69
|
status = reactive("")
|
|
@@ -71,7 +71,7 @@ class MetricCard(Static):
|
|
|
71
71
|
def __init__(
|
|
72
72
|
self,
|
|
73
73
|
label: str,
|
|
74
|
-
value: str = " --
|
|
74
|
+
value: str = " --",
|
|
75
75
|
trend: str = "",
|
|
76
76
|
status: str = "",
|
|
77
77
|
**kwargs,
|
|
@@ -414,7 +414,7 @@ class CrackerjackDashboard(App):
|
|
|
414
414
|
try:
|
|
415
415
|
timeout = aiohttp.ClientTimeout(total=5.0)
|
|
416
416
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
417
|
-
async with session.get("http
|
|
417
|
+
async with session.get("http: / / localhost: 8675 /") as response:
|
|
418
418
|
return response.status == 200
|
|
419
419
|
except Exception:
|
|
420
420
|
return False
|
|
@@ -425,7 +425,7 @@ class CrackerjackDashboard(App):
|
|
|
425
425
|
|
|
426
426
|
cpu_percent = psutil.cpu_percent(interval=None)
|
|
427
427
|
memory = psutil.virtual_memory()
|
|
428
|
-
memory_mb = memory.used
|
|
428
|
+
memory_mb = memory.used / (1024 * 1024)
|
|
429
429
|
|
|
430
430
|
cpu_metric = self.query_one("#cpu_metric", MetricCard)
|
|
431
431
|
memory_metric = self.query_one("#memory_metric", MetricCard)
|
|
@@ -459,7 +459,9 @@ class CrackerjackDashboard(App):
|
|
|
459
459
|
try:
|
|
460
460
|
timeout = aiohttp.ClientTimeout(total=10.0)
|
|
461
461
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
462
|
-
async with session.get(
|
|
462
|
+
async with session.get(
|
|
463
|
+
"http: / / localhost: 8675 / api / jobs"
|
|
464
|
+
) as response:
|
|
463
465
|
if response.status == 200:
|
|
464
466
|
return await response.json()
|
|
465
467
|
return {}
|
|
@@ -474,19 +476,20 @@ class CrackerjackDashboard(App):
|
|
|
474
476
|
import tempfile
|
|
475
477
|
|
|
476
478
|
temp_dir = Path(tempfile.gettempdir())
|
|
477
|
-
debug_files = temp_dir.glob("crackerjack-debug-*.log")
|
|
479
|
+
debug_files = temp_dir.glob("crackerjack - debug-*.log")
|
|
478
480
|
for debug_file in debug_files:
|
|
479
481
|
try:
|
|
480
482
|
if debug_file.stat().st_mtime > (time.time() - 3600):
|
|
481
483
|
debug_file.read_text()
|
|
482
|
-
job_id = debug_file.stem.replace("crackerjack-debug-", "")
|
|
484
|
+
job_id = debug_file.stem.replace("crackerjack-debug -", "")
|
|
483
485
|
jobs[job_id] = {
|
|
484
486
|
"id": job_id,
|
|
485
487
|
"status": "completed",
|
|
486
488
|
"log_file": str(debug_file),
|
|
487
489
|
"timestamp": debug_file.stat().st_mtime,
|
|
488
490
|
}
|
|
489
|
-
except Exception:
|
|
491
|
+
except Exception as e:
|
|
492
|
+
self.log(f"Could not process debug file {debug_file}: {e}")
|
|
490
493
|
continue
|
|
491
494
|
|
|
492
495
|
return jobs
|
|
@@ -512,10 +515,10 @@ class CrackerjackDashboard(App):
|
|
|
512
515
|
reverse=True,
|
|
513
516
|
):
|
|
514
517
|
status = job_data.get("status", "unknown")
|
|
515
|
-
stage = job_data.get("current_stage", "N/A")
|
|
518
|
+
stage = job_data.get("current_stage", "N / A")
|
|
516
519
|
progress = job_data.get("progress", 0)
|
|
517
520
|
started = datetime.fromtimestamp(job_data.get("timestamp", 0)).strftime(
|
|
518
|
-
"%H
|
|
521
|
+
"% H: % M: % S",
|
|
519
522
|
)
|
|
520
523
|
duration = self._format_duration(job_data.get("duration", 0))
|
|
521
524
|
issues = job_data.get("issues_found", 0)
|
|
@@ -577,7 +580,7 @@ class CrackerjackDashboard(App):
|
|
|
577
580
|
try:
|
|
578
581
|
log_display = self.query_one("#log_display", Log)
|
|
579
582
|
|
|
580
|
-
current_time = datetime.now().strftime("%H
|
|
583
|
+
current_time = datetime.now().strftime("% H: % M: % S")
|
|
581
584
|
if len(self.logs_buffer) % 10 == 0:
|
|
582
585
|
log_display.write_line(f"[{current_time}] Dashboard refresh completed")
|
|
583
586
|
|