crackerjack 0.33.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 +4 -13
- 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 +104 -204
- 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 +171 -174
- 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 +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -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 +17 -16
- 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 +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- 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 +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- 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 +6 -35
- 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 +15 -9
- crackerjack/services/config_merge.py +19 -80
- 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 +27 -25
- 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 +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- 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 +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- 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 +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- 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.33.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +196 -25
- 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.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
"""Unified WebSocket server for real-time monitoring dashboard."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import typing as t
|
|
7
|
+
from contextlib import suppress
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import uvicorn
|
|
12
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
13
|
+
from fastapi.responses import HTMLResponse, JSONResponse
|
|
14
|
+
|
|
15
|
+
from crackerjack.mcp.websocket.monitoring_endpoints import MonitoringWebSocketManager
|
|
16
|
+
from crackerjack.monitoring.ai_agent_watchdog import AIAgentWatchdog
|
|
17
|
+
from crackerjack.monitoring.metrics_collector import (
|
|
18
|
+
MetricsCollector,
|
|
19
|
+
UnifiedDashboardMetrics,
|
|
20
|
+
)
|
|
21
|
+
from crackerjack.services.cache import CrackerjackCache
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CrackerjackMonitoringServer:
|
|
27
|
+
"""
|
|
28
|
+
Unified monitoring server for Crackerjack with real-time WebSocket streaming.
|
|
29
|
+
|
|
30
|
+
Provides comprehensive monitoring capabilities:
|
|
31
|
+
- Real-time metrics via WebSocket
|
|
32
|
+
- Historical data storage and retrieval
|
|
33
|
+
- AI agent performance tracking
|
|
34
|
+
- System health monitoring
|
|
35
|
+
- Interactive dashboard interface
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, port: int = 8675, host: str = "localhost"):
|
|
39
|
+
self.port = port
|
|
40
|
+
self.host = host
|
|
41
|
+
|
|
42
|
+
# Core services
|
|
43
|
+
self.app = FastAPI(title="Crackerjack Monitoring", version="1.0.0")
|
|
44
|
+
self.cache = CrackerjackCache()
|
|
45
|
+
self.metrics_collector = MetricsCollector(self.cache)
|
|
46
|
+
self.websocket_manager = MonitoringWebSocketManager()
|
|
47
|
+
self.ai_watchdog = AIAgentWatchdog()
|
|
48
|
+
|
|
49
|
+
# Server state
|
|
50
|
+
self.is_running = False
|
|
51
|
+
self.server_task: asyncio.Task[None] | None = None
|
|
52
|
+
|
|
53
|
+
# WebSocket connections
|
|
54
|
+
self.active_connections: dict[str, WebSocket] = {}
|
|
55
|
+
self.metrics_subscribers: set[WebSocket] = set()
|
|
56
|
+
self.alerts_subscribers: set[WebSocket] = set()
|
|
57
|
+
|
|
58
|
+
self._setup_routes()
|
|
59
|
+
self._setup_websocket_endpoints()
|
|
60
|
+
|
|
61
|
+
def _setup_routes(self) -> None:
|
|
62
|
+
"""Setup HTTP API routes."""
|
|
63
|
+
|
|
64
|
+
@self.app.get("/")
|
|
65
|
+
async def dashboard() -> HTMLResponse:
|
|
66
|
+
"""Main dashboard interface."""
|
|
67
|
+
return HTMLResponse(content=self._get_dashboard_html())
|
|
68
|
+
|
|
69
|
+
@self.app.get("/api/status")
|
|
70
|
+
async def status() -> JSONResponse:
|
|
71
|
+
"""Get server status."""
|
|
72
|
+
return JSONResponse(
|
|
73
|
+
{
|
|
74
|
+
"status": "running" if self.is_running else "stopped",
|
|
75
|
+
"timestamp": datetime.now().isoformat(),
|
|
76
|
+
"host": self.host,
|
|
77
|
+
"port": self.port,
|
|
78
|
+
"metrics_collecting": self.metrics_collector.is_collecting,
|
|
79
|
+
"active_connections": len(self.active_connections),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@self.app.get("/api/metrics")
|
|
84
|
+
async def current_metrics() -> JSONResponse:
|
|
85
|
+
"""Get current metrics snapshot."""
|
|
86
|
+
return JSONResponse(self.metrics_collector.get_current_metrics().to_dict())
|
|
87
|
+
|
|
88
|
+
@self.app.get("/api/metrics/summary")
|
|
89
|
+
async def metrics_summary() -> JSONResponse:
|
|
90
|
+
"""Get metrics summary for quick display."""
|
|
91
|
+
return JSONResponse(self.metrics_collector.get_metrics_summary())
|
|
92
|
+
|
|
93
|
+
@self.app.get("/api/metrics/history")
|
|
94
|
+
async def metrics_history(hours: int = 1) -> JSONResponse:
|
|
95
|
+
"""Get metrics history."""
|
|
96
|
+
history = self.metrics_collector.get_metrics_history(hours)
|
|
97
|
+
return JSONResponse([m.to_dict() for m in history])
|
|
98
|
+
|
|
99
|
+
@self.app.get("/api/agents/status")
|
|
100
|
+
async def agents_status() -> JSONResponse:
|
|
101
|
+
"""Get AI agent status and performance."""
|
|
102
|
+
return JSONResponse(
|
|
103
|
+
{
|
|
104
|
+
"agents": {
|
|
105
|
+
name: {
|
|
106
|
+
"total_handled": metrics.total_issues_handled,
|
|
107
|
+
"success_rate": metrics.successful_fixes
|
|
108
|
+
/ max(1, metrics.total_issues_handled),
|
|
109
|
+
"avg_confidence": metrics.average_confidence,
|
|
110
|
+
"avg_execution_time": metrics.average_execution_time,
|
|
111
|
+
"recent_failures": len(metrics.recent_failures),
|
|
112
|
+
}
|
|
113
|
+
for name, metrics in self.ai_watchdog.performance_metrics.items()
|
|
114
|
+
},
|
|
115
|
+
"alerts": [
|
|
116
|
+
{
|
|
117
|
+
"level": alert.level,
|
|
118
|
+
"message": alert.message,
|
|
119
|
+
"agent": alert.agent_name,
|
|
120
|
+
"timestamp": alert.timestamp.isoformat(),
|
|
121
|
+
}
|
|
122
|
+
for alert in self.ai_watchdog.get_recent_alerts(hours=1)
|
|
123
|
+
],
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
@self.app.post("/api/metrics/record")
|
|
128
|
+
async def record_metrics(data: dict[str, Any]) -> JSONResponse:
|
|
129
|
+
"""Record metrics from external sources."""
|
|
130
|
+
try:
|
|
131
|
+
if "job_start" in data:
|
|
132
|
+
self.metrics_collector.record_job_start(data["job_start"]["job_id"])
|
|
133
|
+
|
|
134
|
+
if "job_completion" in data:
|
|
135
|
+
completion = data["job_completion"]
|
|
136
|
+
self.metrics_collector.record_job_completion(
|
|
137
|
+
completion["job_id"], completion.get("success", True)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if "quality_data" in data:
|
|
141
|
+
quality = data["quality_data"]
|
|
142
|
+
self.metrics_collector.record_quality_data(
|
|
143
|
+
quality.get("issues_found", 0),
|
|
144
|
+
quality.get("issues_fixed", 0),
|
|
145
|
+
quality.get("coverage", 0.0),
|
|
146
|
+
quality.get("success_rate", 0.0),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return JSONResponse({"status": "recorded"})
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Error recording metrics: {e}")
|
|
152
|
+
return JSONResponse({"error": str(e)}, status_code=400)
|
|
153
|
+
|
|
154
|
+
def _setup_websocket_endpoints(self) -> None:
|
|
155
|
+
"""Setup WebSocket endpoints for real-time communication."""
|
|
156
|
+
self.app.websocket("/ws/metrics")(self._handle_metrics_websocket)
|
|
157
|
+
self.app.websocket("/ws/alerts")(self._handle_alerts_websocket)
|
|
158
|
+
|
|
159
|
+
async def _handle_metrics_websocket(self, websocket: WebSocket) -> None:
|
|
160
|
+
"""Handle metrics WebSocket connection."""
|
|
161
|
+
client_id = f"metrics_{datetime.now().timestamp()}"
|
|
162
|
+
await websocket.accept()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
await self._setup_metrics_connection(websocket, client_id)
|
|
166
|
+
await self._send_initial_metrics(websocket)
|
|
167
|
+
await self._handle_metrics_messages(websocket)
|
|
168
|
+
finally:
|
|
169
|
+
self._cleanup_connection(websocket, client_id)
|
|
170
|
+
|
|
171
|
+
async def _handle_alerts_websocket(self, websocket: WebSocket) -> None:
|
|
172
|
+
"""Handle alerts WebSocket connection."""
|
|
173
|
+
client_id = f"alerts_{datetime.now().timestamp()}"
|
|
174
|
+
await websocket.accept()
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
await self._setup_alerts_connection(websocket, client_id)
|
|
178
|
+
await self._send_initial_alerts(websocket)
|
|
179
|
+
await self._handle_alerts_messages(websocket)
|
|
180
|
+
finally:
|
|
181
|
+
self._cleanup_connection(websocket, client_id)
|
|
182
|
+
|
|
183
|
+
async def _setup_metrics_connection(
|
|
184
|
+
self, websocket: WebSocket, client_id: str
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Setup metrics websocket connection."""
|
|
187
|
+
self.active_connections[client_id] = websocket
|
|
188
|
+
self.metrics_subscribers.add(websocket)
|
|
189
|
+
|
|
190
|
+
async def _setup_alerts_connection(
|
|
191
|
+
self, websocket: WebSocket, client_id: str
|
|
192
|
+
) -> None:
|
|
193
|
+
"""Setup alerts websocket connection."""
|
|
194
|
+
self.active_connections[client_id] = websocket
|
|
195
|
+
self.alerts_subscribers.add(websocket)
|
|
196
|
+
|
|
197
|
+
async def _send_initial_metrics(self, websocket: WebSocket) -> None:
|
|
198
|
+
"""Send initial metrics to websocket client."""
|
|
199
|
+
current_metrics = self.metrics_collector.get_current_metrics()
|
|
200
|
+
await websocket.send_text(
|
|
201
|
+
json.dumps(
|
|
202
|
+
{
|
|
203
|
+
"type": "initial_metrics",
|
|
204
|
+
"data": current_metrics.to_dict(),
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async def _send_initial_alerts(self, websocket: WebSocket) -> None:
|
|
210
|
+
"""Send initial alerts to websocket client."""
|
|
211
|
+
recent_alerts = self.ai_watchdog.get_recent_alerts(hours=24)
|
|
212
|
+
await websocket.send_text(
|
|
213
|
+
json.dumps(
|
|
214
|
+
{
|
|
215
|
+
"type": "initial_alerts",
|
|
216
|
+
"data": [
|
|
217
|
+
{
|
|
218
|
+
"level": alert.level,
|
|
219
|
+
"message": alert.message,
|
|
220
|
+
"agent": alert.agent_name,
|
|
221
|
+
"timestamp": alert.timestamp.isoformat(),
|
|
222
|
+
}
|
|
223
|
+
for alert in recent_alerts
|
|
224
|
+
],
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def _handle_metrics_messages(self, websocket: WebSocket) -> None:
|
|
230
|
+
"""Handle incoming metrics websocket messages."""
|
|
231
|
+
while True:
|
|
232
|
+
try:
|
|
233
|
+
message = await websocket.receive_text()
|
|
234
|
+
data = json.loads(message)
|
|
235
|
+
await self._process_websocket_message(websocket, data)
|
|
236
|
+
except WebSocketDisconnect:
|
|
237
|
+
break
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.error(f"WebSocket error: {e}")
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
async def _handle_alerts_messages(self, websocket: WebSocket) -> None:
|
|
243
|
+
"""Handle incoming alerts websocket messages."""
|
|
244
|
+
while True:
|
|
245
|
+
try:
|
|
246
|
+
message = await websocket.receive_text()
|
|
247
|
+
data = json.loads(message)
|
|
248
|
+
await self._process_websocket_message(websocket, data)
|
|
249
|
+
except WebSocketDisconnect:
|
|
250
|
+
break
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"WebSocket error: {e}")
|
|
253
|
+
break
|
|
254
|
+
|
|
255
|
+
async def _process_websocket_message(
|
|
256
|
+
self, websocket: WebSocket, data: dict[str, Any]
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Process incoming websocket message."""
|
|
259
|
+
if data.get("type") == "ping":
|
|
260
|
+
await websocket.send_text(
|
|
261
|
+
json.dumps(
|
|
262
|
+
{
|
|
263
|
+
"type": "pong",
|
|
264
|
+
"timestamp": datetime.now().isoformat(),
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def _cleanup_connection(self, websocket: WebSocket, client_id: str) -> None:
|
|
270
|
+
"""Clean up a WebSocket connection."""
|
|
271
|
+
if client_id in self.active_connections:
|
|
272
|
+
del self.active_connections[client_id]
|
|
273
|
+
self.metrics_subscribers.discard(websocket)
|
|
274
|
+
self.alerts_subscribers.discard(websocket)
|
|
275
|
+
|
|
276
|
+
async def start_monitoring(self, port: int | None = None) -> None:
|
|
277
|
+
"""Start the monitoring server with real-time updates."""
|
|
278
|
+
if self.is_running:
|
|
279
|
+
logger.warning("Monitoring server already running")
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
server_port = port or self.port
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
# Start metrics collection
|
|
286
|
+
await self.metrics_collector.start_collection()
|
|
287
|
+
|
|
288
|
+
# Setup metrics listener for WebSocket broadcasting
|
|
289
|
+
self.metrics_collector.add_metrics_listener(self._broadcast_metrics)
|
|
290
|
+
|
|
291
|
+
# Start the web server
|
|
292
|
+
config = uvicorn.Config(
|
|
293
|
+
self.app,
|
|
294
|
+
host=self.host,
|
|
295
|
+
port=server_port,
|
|
296
|
+
log_level="info",
|
|
297
|
+
)
|
|
298
|
+
server = uvicorn.Server(config)
|
|
299
|
+
|
|
300
|
+
self.server_task = asyncio.create_task(server.serve())
|
|
301
|
+
self.is_running = True
|
|
302
|
+
|
|
303
|
+
logger.info(
|
|
304
|
+
f"🚀 Crackerjack Monitoring Server started on http://{self.host}:{server_port}"
|
|
305
|
+
)
|
|
306
|
+
logger.info(f"📊 Dashboard available at http://{self.host}:{server_port}/")
|
|
307
|
+
logger.info("🔗 WebSocket endpoints:")
|
|
308
|
+
logger.info(f" - Metrics: ws://{self.host}:{server_port}/ws/metrics")
|
|
309
|
+
logger.info(f" - Alerts: ws://{self.host}:{server_port}/ws/alerts")
|
|
310
|
+
|
|
311
|
+
# Wait for server to complete
|
|
312
|
+
await self.server_task
|
|
313
|
+
|
|
314
|
+
except Exception as e:
|
|
315
|
+
logger.error(f"Error starting monitoring server: {e}")
|
|
316
|
+
await self.stop_monitoring()
|
|
317
|
+
raise
|
|
318
|
+
|
|
319
|
+
async def stop_monitoring(self) -> None:
|
|
320
|
+
"""Stop the monitoring server."""
|
|
321
|
+
if not self.is_running:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
self.is_running = False
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
# Stop metrics collection
|
|
328
|
+
await self.metrics_collector.stop_collection()
|
|
329
|
+
|
|
330
|
+
# Close all WebSocket connections
|
|
331
|
+
for websocket in list[t.Any](self.active_connections.values()):
|
|
332
|
+
with suppress(Exception):
|
|
333
|
+
await websocket.close()
|
|
334
|
+
self.active_connections.clear()
|
|
335
|
+
self.metrics_subscribers.clear()
|
|
336
|
+
self.alerts_subscribers.clear()
|
|
337
|
+
|
|
338
|
+
# Stop server task
|
|
339
|
+
if self.server_task and not self.server_task.done():
|
|
340
|
+
self.server_task.cancel()
|
|
341
|
+
try:
|
|
342
|
+
await self.server_task
|
|
343
|
+
except asyncio.CancelledError:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
logger.info("🔻 Crackerjack Monitoring Server stopped")
|
|
347
|
+
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.error(f"Error stopping monitoring server: {e}")
|
|
350
|
+
|
|
351
|
+
def _broadcast_metrics(self, metrics: UnifiedDashboardMetrics) -> None:
|
|
352
|
+
"""Broadcast metrics to all connected WebSocket clients."""
|
|
353
|
+
if not self.metrics_subscribers:
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
message = {
|
|
357
|
+
"type": "metrics_update",
|
|
358
|
+
"data": metrics.to_dict(),
|
|
359
|
+
"timestamp": datetime.now().isoformat(),
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
# Use asyncio to broadcast to all subscribers
|
|
363
|
+
asyncio.create_task(self._async_broadcast_metrics(message))
|
|
364
|
+
|
|
365
|
+
async def _async_broadcast_metrics(self, message: dict[str, Any]) -> None:
|
|
366
|
+
"""Async broadcast helper."""
|
|
367
|
+
disconnected = []
|
|
368
|
+
for websocket in self.metrics_subscribers:
|
|
369
|
+
try:
|
|
370
|
+
await websocket.send_text(json.dumps(message))
|
|
371
|
+
except Exception:
|
|
372
|
+
disconnected.append(websocket)
|
|
373
|
+
|
|
374
|
+
# Clean up disconnected clients
|
|
375
|
+
self.metrics_subscribers.difference_update(disconnected)
|
|
376
|
+
|
|
377
|
+
def _get_dashboard_html(self) -> str:
|
|
378
|
+
"""Generate the dashboard HTML interface."""
|
|
379
|
+
html_template = self._get_html_template()
|
|
380
|
+
css_styles = self._get_dashboard_css()
|
|
381
|
+
javascript_code = self._get_dashboard_javascript()
|
|
382
|
+
|
|
383
|
+
return html_template.format(
|
|
384
|
+
css_styles=css_styles, javascript_code=javascript_code
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
def _get_html_template(self) -> str:
|
|
388
|
+
"""Get the HTML template structure."""
|
|
389
|
+
return """<!DOCTYPE html>
|
|
390
|
+
<html lang="en">
|
|
391
|
+
<head>
|
|
392
|
+
<meta charset="UTF-8">
|
|
393
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
394
|
+
<title>Crackerjack Monitoring Dashboard</title>
|
|
395
|
+
<style>{css_styles}</style>
|
|
396
|
+
</head>
|
|
397
|
+
<body>
|
|
398
|
+
<div class="connection-status disconnected" id="connection-status">Connecting...</div>
|
|
399
|
+
|
|
400
|
+
<div class="header">
|
|
401
|
+
<h1>🔧 Crackerjack Monitoring Dashboard</h1>
|
|
402
|
+
<p>Real-time project monitoring and analytics</p>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
<div class="dashboard">
|
|
406
|
+
<div class="card">
|
|
407
|
+
<h3>System Health</h3>
|
|
408
|
+
<div class="metric">
|
|
409
|
+
<span class="metric-label">CPU Usage</span>
|
|
410
|
+
<span class="metric-value" id="cpu">--</span>
|
|
411
|
+
</div>
|
|
412
|
+
<div class="metric">
|
|
413
|
+
<span class="metric-label">Memory</span>
|
|
414
|
+
<span class="metric-value" id="memory">--</span>
|
|
415
|
+
</div>
|
|
416
|
+
<div class="metric">
|
|
417
|
+
<span class="metric-label">Uptime</span>
|
|
418
|
+
<span class="metric-value" id="uptime">--</span>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
<div class="card">
|
|
423
|
+
<h3>Quality Metrics</h3>
|
|
424
|
+
<div class="metric">
|
|
425
|
+
<span class="metric-label">Success Rate</span>
|
|
426
|
+
<span class="metric-value" id="success-rate">--</span>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="metric">
|
|
429
|
+
<span class="metric-label">Issues Fixed</span>
|
|
430
|
+
<span class="metric-value" id="issues-fixed">--</span>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="metric">
|
|
433
|
+
<span class="metric-label">Test Coverage</span>
|
|
434
|
+
<span class="metric-value" id="coverage">--</span>
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div class="card">
|
|
439
|
+
<h3>Workflow Status</h3>
|
|
440
|
+
<div class="metric">
|
|
441
|
+
<span class="metric-label">Jobs Completed</span>
|
|
442
|
+
<span class="metric-value" id="jobs-completed">--</span>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="metric">
|
|
445
|
+
<span class="metric-label">Avg Duration</span>
|
|
446
|
+
<span class="metric-value" id="avg-duration">--</span>
|
|
447
|
+
</div>
|
|
448
|
+
<div class="metric">
|
|
449
|
+
<span class="metric-label">Throughput</span>
|
|
450
|
+
<span class="metric-value" id="throughput">--</span>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
<div class="card">
|
|
455
|
+
<h3>AI Agents</h3>
|
|
456
|
+
<div class="metric">
|
|
457
|
+
<span class="metric-label">Active Agents</span>
|
|
458
|
+
<span class="metric-value" id="active-agents">--</span>
|
|
459
|
+
</div>
|
|
460
|
+
<div class="metric">
|
|
461
|
+
<span class="metric-label">Total Fixes</span>
|
|
462
|
+
<span class="metric-value" id="total-fixes">--</span>
|
|
463
|
+
</div>
|
|
464
|
+
<div class="metric">
|
|
465
|
+
<span class="metric-label">Cache Hit Rate</span>
|
|
466
|
+
<span class="metric-value" id="cache-hit-rate">--</span>
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
|
|
470
|
+
<div class="card" style="grid-column: 1/-1;">
|
|
471
|
+
<h3>Activity Log</h3>
|
|
472
|
+
<div id="logs"></div>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<script>{javascript_code}</script>
|
|
477
|
+
</body>
|
|
478
|
+
</html>"""
|
|
479
|
+
|
|
480
|
+
def _get_dashboard_css(self) -> str:
|
|
481
|
+
"""Get the CSS styles for the dashboard."""
|
|
482
|
+
return """
|
|
483
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #1a1a1a; color: #fff; }
|
|
484
|
+
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; }
|
|
485
|
+
.card { background: #2d2d2d; border-radius: 8px; padding: 20px; border: 1px solid #404040; }
|
|
486
|
+
.metric { display: flex; justify-content: space-between; margin: 10px 0; }
|
|
487
|
+
.metric-label { color: #aaa; }
|
|
488
|
+
.metric-value { color: #fff; font-weight: bold; }
|
|
489
|
+
.status-good { color: #4ade80; }
|
|
490
|
+
.status-warning { color: #fbbf24; }
|
|
491
|
+
.status-error { color: #ef4444; }
|
|
492
|
+
.header { text-align: center; margin-bottom: 30px; }
|
|
493
|
+
.connection-status { position: fixed; top: 20px; right: 20px; padding: 10px; border-radius: 4px; }
|
|
494
|
+
.connected { background: #16a34a; }
|
|
495
|
+
.disconnected { background: #dc2626; }
|
|
496
|
+
#logs { height: 200px; overflow-y: auto; background: #000; padding: 10px; border-radius: 4px; font-family: monospace; font-size: 12px; }
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
def _get_dashboard_javascript(self) -> str:
|
|
500
|
+
"""Get the JavaScript code for the dashboard."""
|
|
501
|
+
js_variables = self._get_js_variables()
|
|
502
|
+
js_websocket_handlers = self._get_js_websocket_handlers()
|
|
503
|
+
js_dashboard_functions = self._get_js_dashboard_functions()
|
|
504
|
+
js_initialization = self._get_js_initialization()
|
|
505
|
+
|
|
506
|
+
return f"""
|
|
507
|
+
{js_variables}
|
|
508
|
+
{js_websocket_handlers}
|
|
509
|
+
{js_dashboard_functions}
|
|
510
|
+
{js_initialization}
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
def _get_js_variables(self) -> str:
|
|
514
|
+
"""Get JavaScript variable declarations."""
|
|
515
|
+
return """
|
|
516
|
+
const wsUrl = `ws://${window.location.host}/ws/metrics`;
|
|
517
|
+
let ws = null;
|
|
518
|
+
let reconnectInterval = 5000;
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
def _get_js_websocket_handlers(self) -> str:
|
|
522
|
+
"""Get JavaScript WebSocket connection handlers."""
|
|
523
|
+
return """
|
|
524
|
+
function connect() {
|
|
525
|
+
ws = new WebSocket(wsUrl);
|
|
526
|
+
ws.onopen = handleWebSocketOpen;
|
|
527
|
+
ws.onmessage = handleWebSocketMessage;
|
|
528
|
+
ws.onclose = handleWebSocketClose;
|
|
529
|
+
ws.onerror = handleWebSocketError;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function handleWebSocketOpen() {
|
|
533
|
+
document.getElementById('connection-status').textContent = 'Connected';
|
|
534
|
+
document.getElementById('connection-status').className = 'connection-status connected';
|
|
535
|
+
log('Connected to monitoring server');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function handleWebSocketMessage(event) {
|
|
539
|
+
const message = JSON.parse(event.data);
|
|
540
|
+
if (message.type === 'metrics_update' || message.type === 'initial_metrics') {
|
|
541
|
+
updateDashboard(message.data);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function handleWebSocketClose() {
|
|
546
|
+
document.getElementById('connection-status').textContent = 'Disconnected';
|
|
547
|
+
document.getElementById('connection-status').className = 'connection-status disconnected';
|
|
548
|
+
log('Disconnected from monitoring server');
|
|
549
|
+
setTimeout(connect, reconnectInterval);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function handleWebSocketError(error) {
|
|
553
|
+
log(`WebSocket error: ${error}`);
|
|
554
|
+
}
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
def _get_js_dashboard_functions(self) -> str:
|
|
558
|
+
"""Get JavaScript dashboard utility functions."""
|
|
559
|
+
return """
|
|
560
|
+
function updateDashboard(data) {
|
|
561
|
+
updateSystemMetrics(data.system);
|
|
562
|
+
updateQualityMetrics(data.quality);
|
|
563
|
+
updateWorkflowMetrics(data.workflow);
|
|
564
|
+
updateAgentMetrics(data.agents);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function updateSystemMetrics(system) {
|
|
568
|
+
document.getElementById('cpu').textContent = system.cpu_usage.toFixed(1) + '%';
|
|
569
|
+
document.getElementById('memory').textContent = (system.memory_usage_mb / 1024).toFixed(1) + 'GB';
|
|
570
|
+
document.getElementById('uptime').textContent = formatUptime(system.uptime_seconds);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function updateQualityMetrics(quality) {
|
|
574
|
+
document.getElementById('success-rate').textContent = (quality.success_rate * 100).toFixed(1) + '%';
|
|
575
|
+
document.getElementById('issues-fixed').textContent = quality.issues_fixed;
|
|
576
|
+
document.getElementById('coverage').textContent = (quality.test_coverage * 100).toFixed(1) + '%';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function updateWorkflowMetrics(workflow) {
|
|
580
|
+
document.getElementById('jobs-completed').textContent = workflow.jobs_completed;
|
|
581
|
+
document.getElementById('avg-duration').textContent = workflow.average_job_duration.toFixed(1) + 's';
|
|
582
|
+
document.getElementById('throughput').textContent = workflow.throughput_per_hour.toFixed(1) + '/h';
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function updateAgentMetrics(agents) {
|
|
586
|
+
document.getElementById('active-agents').textContent = agents.active_agents;
|
|
587
|
+
document.getElementById('total-fixes').textContent = agents.total_fixes_applied;
|
|
588
|
+
document.getElementById('cache-hit-rate').textContent = (agents.cache_hit_rate * 100).toFixed(1) + '%';
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function formatUptime(seconds) {
|
|
592
|
+
if (seconds < 3600) return Math.floor(seconds/60) + 'm';
|
|
593
|
+
if (seconds < 86400) return Math.floor(seconds/3600) + 'h';
|
|
594
|
+
return Math.floor(seconds/86400) + 'd';
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function log(message) {
|
|
598
|
+
const logs = document.getElementById('logs');
|
|
599
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
600
|
+
logs.innerHTML += `<div>[${timestamp}] ${message}</div>`;
|
|
601
|
+
logs.scrollTop = logs.scrollHeight;
|
|
602
|
+
}
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
def _get_js_initialization(self) -> str:
|
|
606
|
+
"""Get JavaScript initialization code."""
|
|
607
|
+
return """
|
|
608
|
+
// Start connection
|
|
609
|
+
connect();
|
|
610
|
+
|
|
611
|
+
// Periodic ping to keep connection alive
|
|
612
|
+
setInterval(() => {
|
|
613
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
614
|
+
ws.send(JSON.stringify({type: 'ping'}));
|
|
615
|
+
}
|
|
616
|
+
}, 30000);
|
|
617
|
+
"""
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
# Convenience function for quick server startup
|
|
621
|
+
async def start_crackerjack_monitoring_server(
|
|
622
|
+
port: int = 8675, host: str = "localhost"
|
|
623
|
+
) -> CrackerjackMonitoringServer:
|
|
624
|
+
"""Start a Crackerjack monitoring server."""
|
|
625
|
+
server = CrackerjackMonitoringServer(port=port, host=host)
|
|
626
|
+
await server.start_monitoring()
|
|
627
|
+
return server
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
if __name__ == "__main__":
|
|
631
|
+
import sys
|
|
632
|
+
|
|
633
|
+
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8675
|
|
634
|
+
|
|
635
|
+
async def main() -> None:
|
|
636
|
+
server = CrackerjackMonitoringServer(port=port)
|
|
637
|
+
try:
|
|
638
|
+
await server.start_monitoring()
|
|
639
|
+
except KeyboardInterrupt:
|
|
640
|
+
logger.info("Shutting down monitoring server...")
|
|
641
|
+
await server.stop_monitoring()
|
|
642
|
+
|
|
643
|
+
asyncio.run(main())
|
|
@@ -74,7 +74,7 @@ class CorrelationTracker:
|
|
|
74
74
|
current = self.iteration_data[i]
|
|
75
75
|
previous = self.iteration_data[i - 1]
|
|
76
76
|
|
|
77
|
-
recurring_failures = set(current["failed_hooks"]) & set(
|
|
77
|
+
recurring_failures = set[t.Any](current["failed_hooks"]) & set[t.Any](
|
|
78
78
|
previous["failed_hooks"],
|
|
79
79
|
)
|
|
80
80
|
|
|
@@ -101,8 +101,13 @@ class CorrelationTracker:
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
|
|
104
|
-
class MinimalProgressStreamer:
|
|
105
|
-
def __init__(
|
|
104
|
+
class MinimalProgressStreamer(ProgressStreamer):
|
|
105
|
+
def __init__(
|
|
106
|
+
self,
|
|
107
|
+
config: OrchestrationConfig | None = None,
|
|
108
|
+
session: SessionCoordinator | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
# Minimal implementation doesn't use config or session
|
|
106
111
|
pass
|
|
107
112
|
|
|
108
113
|
def update_stage(self, stage: str, substage: str = "") -> None:
|
|
@@ -387,9 +392,9 @@ class AdvancedWorkflowOrchestrator:
|
|
|
387
392
|
|
|
388
393
|
success = False
|
|
389
394
|
strategy_switches = 0
|
|
390
|
-
hooks_time = 0
|
|
391
|
-
tests_time = 0
|
|
392
|
-
ai_time = 0
|
|
395
|
+
hooks_time: float = 0.0
|
|
396
|
+
tests_time: float = 0.0
|
|
397
|
+
ai_time: float = 0.0
|
|
393
398
|
|
|
394
399
|
for iteration in range(1, max_iterations + 1):
|
|
395
400
|
self.console.print(
|