crackerjack 0.30.3__py3-none-any.whl → 0.31.7__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 +1005 -0
- crackerjack/RULES.md +380 -0
- crackerjack/__init__.py +42 -13
- crackerjack/__main__.py +227 -299
- crackerjack/agents/__init__.py +41 -0
- crackerjack/agents/architect_agent.py +281 -0
- crackerjack/agents/base.py +170 -0
- crackerjack/agents/coordinator.py +512 -0
- crackerjack/agents/documentation_agent.py +498 -0
- crackerjack/agents/dry_agent.py +388 -0
- crackerjack/agents/formatting_agent.py +245 -0
- crackerjack/agents/import_optimization_agent.py +281 -0
- crackerjack/agents/performance_agent.py +669 -0
- crackerjack/agents/proactive_agent.py +104 -0
- crackerjack/agents/refactoring_agent.py +788 -0
- crackerjack/agents/security_agent.py +529 -0
- crackerjack/agents/test_creation_agent.py +657 -0
- crackerjack/agents/test_specialist_agent.py +486 -0
- crackerjack/agents/tracker.py +212 -0
- crackerjack/api.py +560 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/facade.py +104 -0
- crackerjack/cli/handlers.py +267 -0
- crackerjack/cli/interactive.py +471 -0
- crackerjack/cli/options.py +409 -0
- crackerjack/cli/utils.py +18 -0
- crackerjack/code_cleaner.py +618 -928
- crackerjack/config/__init__.py +19 -0
- crackerjack/config/hooks.py +218 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +406 -0
- crackerjack/core/autofix_coordinator.py +200 -0
- crackerjack/core/container.py +104 -0
- crackerjack/core/enhanced_container.py +542 -0
- crackerjack/core/performance.py +243 -0
- crackerjack/core/phase_coordinator.py +585 -0
- crackerjack/core/proactive_workflow.py +316 -0
- crackerjack/core/session_coordinator.py +289 -0
- crackerjack/core/workflow_orchestrator.py +826 -0
- crackerjack/dynamic_config.py +94 -103
- crackerjack/errors.py +263 -41
- crackerjack/executors/__init__.py +11 -0
- crackerjack/executors/async_hook_executor.py +431 -0
- crackerjack/executors/cached_hook_executor.py +242 -0
- crackerjack/executors/hook_executor.py +345 -0
- crackerjack/executors/individual_hook_executor.py +669 -0
- crackerjack/intelligence/__init__.py +44 -0
- crackerjack/intelligence/adaptive_learning.py +751 -0
- crackerjack/intelligence/agent_orchestrator.py +551 -0
- crackerjack/intelligence/agent_registry.py +414 -0
- crackerjack/intelligence/agent_selector.py +502 -0
- crackerjack/intelligence/integration.py +290 -0
- crackerjack/interactive.py +576 -315
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +137 -0
- crackerjack/managers/publish_manager.py +433 -0
- crackerjack/managers/test_command_builder.py +151 -0
- crackerjack/managers/test_executor.py +443 -0
- crackerjack/managers/test_manager.py +258 -0
- crackerjack/managers/test_manager_backup.py +1124 -0
- crackerjack/managers/test_progress.py +114 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +336 -0
- crackerjack/mcp/client_runner.py +104 -0
- crackerjack/mcp/context.py +621 -0
- crackerjack/mcp/dashboard.py +636 -0
- crackerjack/mcp/enhanced_progress_monitor.py +479 -0
- crackerjack/mcp/file_monitor.py +336 -0
- crackerjack/mcp/progress_components.py +569 -0
- crackerjack/mcp/progress_monitor.py +949 -0
- crackerjack/mcp/rate_limiter.py +332 -0
- crackerjack/mcp/server.py +22 -0
- crackerjack/mcp/server_core.py +244 -0
- crackerjack/mcp/service_watchdog.py +501 -0
- crackerjack/mcp/state.py +395 -0
- crackerjack/mcp/task_manager.py +257 -0
- crackerjack/mcp/tools/__init__.py +17 -0
- crackerjack/mcp/tools/core_tools.py +249 -0
- crackerjack/mcp/tools/error_analyzer.py +308 -0
- crackerjack/mcp/tools/execution_tools.py +372 -0
- crackerjack/mcp/tools/execution_tools_backup.py +1097 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +80 -0
- crackerjack/mcp/tools/intelligence_tools.py +314 -0
- crackerjack/mcp/tools/monitoring_tools.py +502 -0
- crackerjack/mcp/tools/proactive_tools.py +384 -0
- crackerjack/mcp/tools/progress_tools.py +217 -0
- crackerjack/mcp/tools/utility_tools.py +341 -0
- crackerjack/mcp/tools/workflow_executor.py +565 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +39 -0
- crackerjack/mcp/websocket/endpoints.py +559 -0
- crackerjack/mcp/websocket/jobs.py +253 -0
- crackerjack/mcp/websocket/server.py +116 -0
- crackerjack/mcp/websocket/websocket_handler.py +78 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/__init__.py +31 -0
- crackerjack/models/config.py +93 -0
- crackerjack/models/config_adapter.py +230 -0
- crackerjack/models/protocols.py +118 -0
- crackerjack/models/task.py +154 -0
- crackerjack/monitoring/ai_agent_watchdog.py +450 -0
- crackerjack/monitoring/regression_prevention.py +638 -0
- crackerjack/orchestration/__init__.py +0 -0
- crackerjack/orchestration/advanced_orchestrator.py +970 -0
- crackerjack/orchestration/coverage_improvement.py +223 -0
- crackerjack/orchestration/execution_strategies.py +341 -0
- crackerjack/orchestration/test_progress_streamer.py +636 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +246 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +259 -0
- crackerjack/py313.py +8 -3
- crackerjack/services/__init__.py +22 -0
- crackerjack/services/cache.py +314 -0
- crackerjack/services/config.py +358 -0
- crackerjack/services/config_integrity.py +99 -0
- crackerjack/services/contextual_ai_assistant.py +516 -0
- crackerjack/services/coverage_ratchet.py +356 -0
- crackerjack/services/debug.py +736 -0
- crackerjack/services/dependency_monitor.py +617 -0
- crackerjack/services/enhanced_filesystem.py +439 -0
- crackerjack/services/file_hasher.py +151 -0
- crackerjack/services/filesystem.py +421 -0
- crackerjack/services/git.py +176 -0
- crackerjack/services/health_metrics.py +611 -0
- crackerjack/services/initialization.py +873 -0
- crackerjack/services/log_manager.py +286 -0
- crackerjack/services/logging.py +174 -0
- crackerjack/services/metrics.py +578 -0
- crackerjack/services/pattern_cache.py +362 -0
- crackerjack/services/pattern_detector.py +515 -0
- crackerjack/services/performance_benchmarks.py +653 -0
- crackerjack/services/security.py +163 -0
- crackerjack/services/server_manager.py +234 -0
- crackerjack/services/smart_scheduling.py +144 -0
- crackerjack/services/tool_version_service.py +61 -0
- crackerjack/services/unified_config.py +437 -0
- crackerjack/services/version_checker.py +248 -0
- crackerjack/slash_commands/__init__.py +14 -0
- crackerjack/slash_commands/init.md +122 -0
- crackerjack/slash_commands/run.md +163 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack-0.31.7.dist-info/METADATA +742 -0
- crackerjack-0.31.7.dist-info/RECORD +149 -0
- crackerjack-0.31.7.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -34
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/crackerjack.py +0 -3805
- crackerjack/pyproject.toml +0 -286
- crackerjack-0.30.3.dist-info/METADATA +0 -1290
- crackerjack-0.30.3.dist-info/RECORD +0 -16
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/WHEEL +0 -0
- {crackerjack-0.30.3.dist-info → crackerjack-0.31.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from textual.app import App, ComposeResult
|
|
8
|
+
from textual.containers import Container, Horizontal, Vertical
|
|
9
|
+
from textual.reactive import reactive
|
|
10
|
+
from textual.widget import Widget
|
|
11
|
+
from textual.widgets import DataTable, Footer, Label, ProgressBar
|
|
12
|
+
|
|
13
|
+
from .progress_components import (
|
|
14
|
+
JobDataCollector,
|
|
15
|
+
ServiceManager,
|
|
16
|
+
TerminalRestorer,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MetricCard(Widget):
|
|
21
|
+
value = reactive(" -- ")
|
|
22
|
+
label = reactive("Metric")
|
|
23
|
+
trend = reactive("")
|
|
24
|
+
color = reactive("white")
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
label: str,
|
|
29
|
+
value: str = " -- ",
|
|
30
|
+
trend: str = "",
|
|
31
|
+
color: str = "white",
|
|
32
|
+
**kwargs,
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(**kwargs)
|
|
35
|
+
self.label = label
|
|
36
|
+
self.value = value
|
|
37
|
+
self.trend = trend
|
|
38
|
+
self.color = color
|
|
39
|
+
|
|
40
|
+
def render(self) -> str:
|
|
41
|
+
trend_icon = self.trend or ""
|
|
42
|
+
return f"[{self.color}]{self.label}[/]\n[bold {self.color}]{self.value}[/] {trend_icon}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AgentActivityWidget(Widget):
|
|
46
|
+
def __init__(self, **kwargs) -> None:
|
|
47
|
+
super().__init__(**kwargs)
|
|
48
|
+
self.border_title = "🤖 AI Agent Activity"
|
|
49
|
+
self.border_title_align = "left"
|
|
50
|
+
|
|
51
|
+
def compose(self) -> ComposeResult:
|
|
52
|
+
with Vertical():
|
|
53
|
+
with Horizontal(id="agent - metrics"):
|
|
54
|
+
yield MetricCard(
|
|
55
|
+
"Active Agents",
|
|
56
|
+
"0",
|
|
57
|
+
color="cyan",
|
|
58
|
+
id="active - agents - metric",
|
|
59
|
+
)
|
|
60
|
+
yield MetricCard(
|
|
61
|
+
"Issues Fixed",
|
|
62
|
+
"0",
|
|
63
|
+
"↑",
|
|
64
|
+
color="green",
|
|
65
|
+
id="issues - fixed - metric",
|
|
66
|
+
)
|
|
67
|
+
yield MetricCard(
|
|
68
|
+
"Confidence",
|
|
69
|
+
"0 % ",
|
|
70
|
+
color="yellow",
|
|
71
|
+
id="confidence - metric",
|
|
72
|
+
)
|
|
73
|
+
yield MetricCard(
|
|
74
|
+
"Cache Hits",
|
|
75
|
+
"0",
|
|
76
|
+
color="magenta",
|
|
77
|
+
id="cache - hits - metric",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
yield DataTable(id="agents - detail - table")
|
|
81
|
+
|
|
82
|
+
yield Label("⏸️ Coordinator: Idle", id="coordinator - status - bar")
|
|
83
|
+
|
|
84
|
+
def on_mount(self) -> None:
|
|
85
|
+
table = self.query_one("#agents - detail - table", DataTable)
|
|
86
|
+
table.add_columns(
|
|
87
|
+
("Agent", 20),
|
|
88
|
+
("Status", 10),
|
|
89
|
+
("Type", 15),
|
|
90
|
+
("Conf.", 8),
|
|
91
|
+
("Time", 10),
|
|
92
|
+
)
|
|
93
|
+
table.zebra_stripes = True
|
|
94
|
+
table.styles.max_height = 6
|
|
95
|
+
|
|
96
|
+
def update_metrics(self, data: dict) -> None:
|
|
97
|
+
with suppress(Exception):
|
|
98
|
+
activity = data.get("agent_activity", {})
|
|
99
|
+
activity.get("agent_registry", {})
|
|
100
|
+
active_agents = activity.get("active_agents", [])
|
|
101
|
+
|
|
102
|
+
active_count = len(active_agents)
|
|
103
|
+
self.query_one("#active - agents - metric", MetricCard).value = str(
|
|
104
|
+
active_count,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
total_fixed = sum(agent.get("issues_fixed", 0) for agent in active_agents)
|
|
108
|
+
avg_confidence = sum(
|
|
109
|
+
agent.get("confidence", 0) for agent in active_agents
|
|
110
|
+
) / max(active_count, 1)
|
|
111
|
+
cache_hits = activity.get("cache_hits", 0)
|
|
112
|
+
|
|
113
|
+
self.query_one("#issues - fixed - metric", MetricCard).value = str(
|
|
114
|
+
total_fixed,
|
|
115
|
+
)
|
|
116
|
+
self.query_one(
|
|
117
|
+
"#confidence - metric",
|
|
118
|
+
MetricCard,
|
|
119
|
+
).value = f"{avg_confidence: .0 % }"
|
|
120
|
+
self.query_one("#cache - hits - metric", MetricCard).value = str(cache_hits)
|
|
121
|
+
|
|
122
|
+
self._update_coordinator_status(activity)
|
|
123
|
+
|
|
124
|
+
self._update_agent_table(active_agents)
|
|
125
|
+
|
|
126
|
+
def _update_coordinator_status(self, activity: dict) -> None:
|
|
127
|
+
status = activity.get("coordinator_status", "idle")
|
|
128
|
+
total_agents = activity.get("agent_registry", {}).get("total_agents", 0)
|
|
129
|
+
|
|
130
|
+
status_icons = {"active": "🟢", "processing": "🔄", "idle": "⏸️", "error": "🔴"}
|
|
131
|
+
|
|
132
|
+
icon = status_icons.get(status) or "⏸️"
|
|
133
|
+
status_bar = self.query_one("#coordinator - status - bar", Label)
|
|
134
|
+
status_bar.update(
|
|
135
|
+
f"{icon} Coordinator: {status.title()} ({total_agents} agents available)",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _update_agent_table(self, agents: list) -> None:
|
|
139
|
+
table = self.query_one("#agents - detail - table", DataTable)
|
|
140
|
+
table.clear()
|
|
141
|
+
|
|
142
|
+
if not agents:
|
|
143
|
+
table.add_row("No active agents", " - ", " - ", " - ", " - ")
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
for agent in agents:
|
|
147
|
+
name = agent.get("agent_type", "Unknown")
|
|
148
|
+
status = agent.get("status", "idle")
|
|
149
|
+
issue_type = agent.get("issue_type", " - ")
|
|
150
|
+
confidence = f"{agent.get('confidence', 0): .0 % }"
|
|
151
|
+
time_elapsed = f"{agent.get('processing_time', 0): .1f}s"
|
|
152
|
+
|
|
153
|
+
status_emoji = {
|
|
154
|
+
"processing": "🔄",
|
|
155
|
+
"success": "✅",
|
|
156
|
+
"failed": "❌",
|
|
157
|
+
"idle": "⏸️",
|
|
158
|
+
}.get(status, "❓")
|
|
159
|
+
|
|
160
|
+
agent_emoji = {
|
|
161
|
+
"FormattingAgent": "🎨",
|
|
162
|
+
"SecurityAgent": "🛡️",
|
|
163
|
+
"TestCreationAgent": "🧪",
|
|
164
|
+
"TestSpecialistAgent": "🔬",
|
|
165
|
+
"RefactoringAgent": "🔧",
|
|
166
|
+
"ImportOptimizationAgent": "📦",
|
|
167
|
+
}.get(name, "🤖")
|
|
168
|
+
|
|
169
|
+
table.add_row(
|
|
170
|
+
f"{agent_emoji} {name}",
|
|
171
|
+
f"{status_emoji} {status}",
|
|
172
|
+
issue_type,
|
|
173
|
+
confidence,
|
|
174
|
+
time_elapsed,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class JobProgressPanel(Widget):
|
|
179
|
+
def __init__(self, job_data: dict, **kwargs) -> None:
|
|
180
|
+
super().__init__(**kwargs)
|
|
181
|
+
self.job_data = job_data
|
|
182
|
+
self.start_time = time.time()
|
|
183
|
+
|
|
184
|
+
def compose(self) -> ComposeResult:
|
|
185
|
+
project = self.job_data.get("project", "unknown")
|
|
186
|
+
job_id = self.job_data.get("job_id", "unknown")[:8]
|
|
187
|
+
|
|
188
|
+
status = self.job_data.get("status", "").lower()
|
|
189
|
+
status_emoji = {
|
|
190
|
+
"running": "🔄",
|
|
191
|
+
"completed": "✅",
|
|
192
|
+
"failed": "❌",
|
|
193
|
+
"pending": "⏳",
|
|
194
|
+
}.get(status, "❓")
|
|
195
|
+
|
|
196
|
+
self.border_title = f"{status_emoji} {project} [{job_id}]"
|
|
197
|
+
self.border_title_align = "left"
|
|
198
|
+
|
|
199
|
+
with Horizontal():
|
|
200
|
+
with Vertical(id="job - progress - section"):
|
|
201
|
+
yield self._compose_progress_section()
|
|
202
|
+
|
|
203
|
+
with Vertical(id="job - metrics - section"):
|
|
204
|
+
yield self._compose_metrics_section()
|
|
205
|
+
|
|
206
|
+
def _compose_progress_section(self) -> ComposeResult:
|
|
207
|
+
iteration = self.job_data.get("iteration", 1)
|
|
208
|
+
max_iterations = self.job_data.get("max_iterations", 10)
|
|
209
|
+
progress = self.job_data.get("progress", 0)
|
|
210
|
+
|
|
211
|
+
stage = self.job_data.get("stage", "Unknown")
|
|
212
|
+
status = self.job_data.get("status", "Unknown")
|
|
213
|
+
|
|
214
|
+
yield Label(f"Stage: {stage}", classes="stage - label")
|
|
215
|
+
yield Label(f"Status: {status}", classes="status - label")
|
|
216
|
+
yield Label(f"Iteration: {iteration} / {max_iterations}")
|
|
217
|
+
|
|
218
|
+
yield ProgressBar(
|
|
219
|
+
total=100,
|
|
220
|
+
progress=progress,
|
|
221
|
+
id=f"job - progress - {self.job_data.get('job_id', 'unknown')}",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
elapsed = time.time() - self.start_time
|
|
225
|
+
yield Label(f"⏱️ Elapsed: {self._format_time(elapsed)}")
|
|
226
|
+
|
|
227
|
+
def _compose_metrics_section(self) -> ComposeResult:
|
|
228
|
+
total_issues = self.job_data.get("total_issues", 0)
|
|
229
|
+
fixed = self.job_data.get("errors_fixed", 0)
|
|
230
|
+
remaining = max(0, total_issues - fixed)
|
|
231
|
+
|
|
232
|
+
with Horizontal(classes="metrics - grid"):
|
|
233
|
+
yield MetricCard("Issues Found", str(total_issues), color="yellow")
|
|
234
|
+
yield MetricCard(
|
|
235
|
+
"Fixed",
|
|
236
|
+
str(fixed),
|
|
237
|
+
"↑" if fixed > 0 else "",
|
|
238
|
+
color="green",
|
|
239
|
+
)
|
|
240
|
+
yield MetricCard(
|
|
241
|
+
"Remaining",
|
|
242
|
+
str(remaining),
|
|
243
|
+
"↓" if fixed > 0 else "",
|
|
244
|
+
color="red",
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if total_issues > 0:
|
|
248
|
+
success_rate = (fixed / total_issues) * 100
|
|
249
|
+
yield Label(
|
|
250
|
+
f"Success Rate: {success_rate: .1f} % ",
|
|
251
|
+
classes="success - rate",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def _format_time(self, seconds: float) -> str:
|
|
255
|
+
if seconds < 60:
|
|
256
|
+
return f"{seconds: .0f}s"
|
|
257
|
+
if seconds < 3600:
|
|
258
|
+
return f"{seconds / 60: .0f}m {seconds % 60: .0f}s"
|
|
259
|
+
return f"{seconds / 3600: .0f}h {(seconds % 3600) / 60: .0f}m"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class ServiceHealthPanel(Widget):
|
|
263
|
+
def __init__(self, **kwargs) -> None:
|
|
264
|
+
super().__init__(**kwargs)
|
|
265
|
+
self.border_title = "🏥 Service Health"
|
|
266
|
+
self.border_title_align = "left"
|
|
267
|
+
|
|
268
|
+
def compose(self) -> ComposeResult:
|
|
269
|
+
yield DataTable(id="services - table")
|
|
270
|
+
|
|
271
|
+
def on_mount(self) -> None:
|
|
272
|
+
table = self.query_one("#services - table", DataTable)
|
|
273
|
+
table.add_columns(
|
|
274
|
+
("Service", 20),
|
|
275
|
+
("Status", 12),
|
|
276
|
+
("Health", 10),
|
|
277
|
+
("Uptime", 15),
|
|
278
|
+
("Last Check", 20),
|
|
279
|
+
)
|
|
280
|
+
table.zebra_stripes = True
|
|
281
|
+
|
|
282
|
+
def update_services(self, services: list[dict]) -> None:
|
|
283
|
+
table = self.query_one("#services - table", DataTable)
|
|
284
|
+
table.clear()
|
|
285
|
+
|
|
286
|
+
for service in services:
|
|
287
|
+
name = service.get("name", "Unknown")
|
|
288
|
+
status = service.get("status", "unknown")
|
|
289
|
+
health = service.get("health", "unknown")
|
|
290
|
+
uptime = service.get("uptime", 0)
|
|
291
|
+
last_check = service.get("last_check", "Never")
|
|
292
|
+
|
|
293
|
+
status_indicator = {
|
|
294
|
+
"running": "🟢 Running",
|
|
295
|
+
"stopped": "🔴 Stopped",
|
|
296
|
+
"starting": "🟡 Starting",
|
|
297
|
+
"error": "❌ Error",
|
|
298
|
+
}.get(status, "❓ Unknown")
|
|
299
|
+
|
|
300
|
+
health_indicator = {
|
|
301
|
+
"healthy": "✅",
|
|
302
|
+
"unhealthy": "❌",
|
|
303
|
+
"degraded": "⚠️",
|
|
304
|
+
"unknown": "❓",
|
|
305
|
+
}.get(health, "❓")
|
|
306
|
+
|
|
307
|
+
uptime_str = self._format_uptime(uptime)
|
|
308
|
+
|
|
309
|
+
if isinstance(last_check, int | float):
|
|
310
|
+
last_check_str = datetime.fromtimestamp(last_check).strftime(
|
|
311
|
+
" % H: % M: % S",
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
last_check_str = str(last_check)
|
|
315
|
+
|
|
316
|
+
table.add_row(
|
|
317
|
+
f"🔧 {name}",
|
|
318
|
+
status_indicator,
|
|
319
|
+
health_indicator,
|
|
320
|
+
uptime_str,
|
|
321
|
+
last_check_str,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def _format_uptime(self, seconds: float) -> str:
|
|
325
|
+
if seconds < 60:
|
|
326
|
+
return f"{seconds: .0f}s"
|
|
327
|
+
if seconds < 3600:
|
|
328
|
+
return f"{seconds / 60: .0f}m"
|
|
329
|
+
if seconds < 86400:
|
|
330
|
+
return f"{seconds / 3600: .1f}h"
|
|
331
|
+
return f"{seconds / 86400: .1f}d"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class EnhancedCrackerjackDashboard(App):
|
|
335
|
+
TITLE = "Crackerjack Progress Monitor"
|
|
336
|
+
CSS_PATH = Path(__file__).parent / "enhanced_progress_monitor.tcss"
|
|
337
|
+
|
|
338
|
+
def __init__(
|
|
339
|
+
self, progress_dir: Path, websocket_url: str = "ws://localhost:8675"
|
|
340
|
+
) -> None:
|
|
341
|
+
super().__init__()
|
|
342
|
+
self.progress_dir = progress_dir
|
|
343
|
+
self.websocket_url = websocket_url
|
|
344
|
+
self.data_collector = JobDataCollector(progress_dir, websocket_url)
|
|
345
|
+
self.service_manager = ServiceManager()
|
|
346
|
+
self.update_timer = None
|
|
347
|
+
self.jobs_data = {}
|
|
348
|
+
|
|
349
|
+
def compose(self) -> ComposeResult:
|
|
350
|
+
yield Label("🚀 Crackerjack Progress Monitor", id="header")
|
|
351
|
+
|
|
352
|
+
with Container(id="main - content"):
|
|
353
|
+
yield AgentActivityWidget(id="agent - panel")
|
|
354
|
+
|
|
355
|
+
yield ServiceHealthPanel(id="service - panel")
|
|
356
|
+
|
|
357
|
+
with Container(id="jobs - container"):
|
|
358
|
+
yield Label("Loading jobs...", id="jobs - placeholder")
|
|
359
|
+
|
|
360
|
+
yield Footer()
|
|
361
|
+
|
|
362
|
+
def on_mount(self) -> None:
|
|
363
|
+
self.update_timer = self.set_interval(1.0, self.update_dashboard)
|
|
364
|
+
|
|
365
|
+
async def update_dashboard(self) -> None:
|
|
366
|
+
try:
|
|
367
|
+
jobs_result = await self.data_collector.discover_jobs()
|
|
368
|
+
jobs_data = jobs_result.get("data", {})
|
|
369
|
+
|
|
370
|
+
services = self.service_manager.check_all_services()
|
|
371
|
+
self.query_one("#service - panel", ServiceHealthPanel).update_services(
|
|
372
|
+
services,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
if jobs_data.get("individual_jobs"):
|
|
376
|
+
aggregated_agent_data = self._aggregate_agent_data(
|
|
377
|
+
jobs_data["individual_jobs"],
|
|
378
|
+
)
|
|
379
|
+
self.query_one("#agent - panel", AgentActivityWidget).update_metrics(
|
|
380
|
+
aggregated_agent_data,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
self._update_job_panels(jobs_data.get("individual_jobs", []))
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
self.console.print(f"[red]Dashboard update error: {e}[/]")
|
|
387
|
+
|
|
388
|
+
def _aggregate_agent_data(self, jobs: list[dict]) -> dict:
|
|
389
|
+
aggregated = {
|
|
390
|
+
"agent_activity": {
|
|
391
|
+
"active_agents": [],
|
|
392
|
+
"coordinator_status": "idle",
|
|
393
|
+
"agent_registry": {"total_agents": 6},
|
|
394
|
+
"cache_hits": 0,
|
|
395
|
+
},
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for job in jobs:
|
|
399
|
+
if job.get("status", "").lower() == "running":
|
|
400
|
+
agent_summary = job.get("agent_summary", {})
|
|
401
|
+
if agent_summary:
|
|
402
|
+
aggregated["agent_activity"]["coordinator_status"] = "active"
|
|
403
|
+
|
|
404
|
+
aggregated["agent_activity"]["active_agents"].extend(
|
|
405
|
+
[
|
|
406
|
+
{
|
|
407
|
+
"agent_type": agent_type,
|
|
408
|
+
"status": "processing",
|
|
409
|
+
"confidence": 0.85,
|
|
410
|
+
"processing_time": 2.3,
|
|
411
|
+
"issue_type": "complexity"
|
|
412
|
+
if agent_type == "RefactoringAgent"
|
|
413
|
+
else "formatting",
|
|
414
|
+
}
|
|
415
|
+
for agent_type in ("RefactoringAgent", "FormattingAgent")
|
|
416
|
+
],
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
return aggregated
|
|
420
|
+
|
|
421
|
+
def _update_job_panels(self, jobs: list[dict]) -> None:
|
|
422
|
+
container = self.query_one("#jobs - container", Container)
|
|
423
|
+
|
|
424
|
+
with suppress(Exception):
|
|
425
|
+
container.remove_children("#jobs - placeholder")
|
|
426
|
+
|
|
427
|
+
existing_job_ids = {panel.id for panel in container.query(".job - panel")}
|
|
428
|
+
current_job_ids = {f"job - {job['job_id']}" for job in jobs}
|
|
429
|
+
|
|
430
|
+
for panel_id in existing_job_ids - current_job_ids:
|
|
431
|
+
with suppress(Exception):
|
|
432
|
+
panel = container.query_one(f"#{panel_id}")
|
|
433
|
+
panel.remove()
|
|
434
|
+
|
|
435
|
+
for job in jobs:
|
|
436
|
+
panel_id = f"job - {job['job_id']}"
|
|
437
|
+
if panel_id not in existing_job_ids:
|
|
438
|
+
panel = JobProgressPanel(job, id=panel_id, classes="job - panel")
|
|
439
|
+
container.mount(panel)
|
|
440
|
+
else:
|
|
441
|
+
panel = container.query_one(f"#{panel_id}", JobProgressPanel)
|
|
442
|
+
panel.job_data = job
|
|
443
|
+
panel.refresh()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
async def run_enhanced_progress_monitor(
|
|
447
|
+
progress_dir: Path | None = None,
|
|
448
|
+
websocket_url: str = "ws://localhost:8675",
|
|
449
|
+
dev_mode: bool = False,
|
|
450
|
+
) -> None:
|
|
451
|
+
if progress_dir is None:
|
|
452
|
+
progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
|
|
453
|
+
|
|
454
|
+
restorer = TerminalRestorer()
|
|
455
|
+
restorer.setup_handlers()
|
|
456
|
+
|
|
457
|
+
try:
|
|
458
|
+
app = EnhancedCrackerjackDashboard(progress_dir, websocket_url)
|
|
459
|
+
|
|
460
|
+
if dev_mode:
|
|
461
|
+
from rich.console import Console
|
|
462
|
+
|
|
463
|
+
console = Console()
|
|
464
|
+
console.print("[bold cyan]🛠️ Development Mode: Enabled[/bold cyan]")
|
|
465
|
+
app.dev = True
|
|
466
|
+
|
|
467
|
+
await app.run_async()
|
|
468
|
+
finally:
|
|
469
|
+
restorer.restore()
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
if __name__ == "__main__":
|
|
473
|
+
import sys
|
|
474
|
+
import tempfile
|
|
475
|
+
|
|
476
|
+
progress_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else None
|
|
477
|
+
websocket_url = sys.argv[2] if len(sys.argv) > 2 else "ws://localhost:8675"
|
|
478
|
+
|
|
479
|
+
asyncio.run(run_enhanced_progress_monitor(progress_dir, websocket_url))
|