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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import time
|
|
3
|
+
import typing as t
|
|
3
4
|
from contextlib import suppress
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from pathlib import Path
|
|
@@ -29,7 +30,7 @@ class MetricCard(Widget):
|
|
|
29
30
|
value: str = " --",
|
|
30
31
|
trend: str = "",
|
|
31
32
|
color: str = "white",
|
|
32
|
-
**kwargs,
|
|
33
|
+
**kwargs: t.Any,
|
|
33
34
|
) -> None:
|
|
34
35
|
super().__init__(**kwargs)
|
|
35
36
|
self.label = label
|
|
@@ -43,7 +44,7 @@ class MetricCard(Widget):
|
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
class AgentActivityWidget(Widget):
|
|
46
|
-
def __init__(self, **kwargs) -> None:
|
|
47
|
+
def __init__(self, **kwargs: t.Any) -> None:
|
|
47
48
|
super().__init__(**kwargs)
|
|
48
49
|
self.border_title = "🤖 AI Agent Activity"
|
|
49
50
|
self.border_title_align = "left"
|
|
@@ -93,7 +94,7 @@ class AgentActivityWidget(Widget):
|
|
|
93
94
|
table.zebra_stripes = True
|
|
94
95
|
table.styles.max_height = 6
|
|
95
96
|
|
|
96
|
-
def update_metrics(self, data: dict) -> None:
|
|
97
|
+
def update_metrics(self, data: dict[str, t.Any]) -> None:
|
|
97
98
|
with suppress(Exception):
|
|
98
99
|
activity = data.get("agent_activity", {})
|
|
99
100
|
activity.get("agent_registry", {})
|
|
@@ -116,14 +117,14 @@ class AgentActivityWidget(Widget):
|
|
|
116
117
|
self.query_one(
|
|
117
118
|
"#confidence-metric",
|
|
118
119
|
MetricCard,
|
|
119
|
-
).value = f"{avg_confidence
|
|
120
|
+
).value = f"{avg_confidence: .0%}"
|
|
120
121
|
self.query_one("#cache-hits-metric", MetricCard).value = str(cache_hits)
|
|
121
122
|
|
|
122
123
|
self._update_coordinator_status(activity)
|
|
123
124
|
|
|
124
125
|
self._update_agent_table(active_agents)
|
|
125
126
|
|
|
126
|
-
def _update_coordinator_status(self, activity: dict) -> None:
|
|
127
|
+
def _update_coordinator_status(self, activity: dict[str, t.Any]) -> None:
|
|
127
128
|
status = activity.get("coordinator_status", "idle")
|
|
128
129
|
total_agents = activity.get("agent_registry", {}).get("total_agents", 0)
|
|
129
130
|
|
|
@@ -135,7 +136,7 @@ class AgentActivityWidget(Widget):
|
|
|
135
136
|
f"{icon} Coordinator: {status.title()} ({total_agents} agents available)",
|
|
136
137
|
)
|
|
137
138
|
|
|
138
|
-
def _update_agent_table(self, agents: list) -> None:
|
|
139
|
+
def _update_agent_table(self, agents: list[t.Any]) -> None:
|
|
139
140
|
table = self.query_one("#agents-detail-table", DataTable)
|
|
140
141
|
table.clear()
|
|
141
142
|
|
|
@@ -147,8 +148,8 @@ class AgentActivityWidget(Widget):
|
|
|
147
148
|
name = agent.get("agent_type", "Unknown")
|
|
148
149
|
status = agent.get("status", "idle")
|
|
149
150
|
issue_type = agent.get("issue_type", "-")
|
|
150
|
-
confidence = f"{agent.get('confidence', 0)
|
|
151
|
-
time_elapsed = f"{agent.get('processing_time', 0)
|
|
151
|
+
confidence = f"{agent.get('confidence', 0): .0%}"
|
|
152
|
+
time_elapsed = f"{agent.get('processing_time', 0): .1f}s"
|
|
152
153
|
|
|
153
154
|
status_emoji = {
|
|
154
155
|
"processing": "🔄",
|
|
@@ -176,7 +177,7 @@ class AgentActivityWidget(Widget):
|
|
|
176
177
|
|
|
177
178
|
|
|
178
179
|
class JobProgressPanel(Widget):
|
|
179
|
-
def __init__(self, job_data: dict, **kwargs) -> None:
|
|
180
|
+
def __init__(self, job_data: dict[str, t.Any], **kwargs) -> None:
|
|
180
181
|
super().__init__(**kwargs)
|
|
181
182
|
self.job_data = job_data
|
|
182
183
|
self.start_time = time.time()
|
|
@@ -198,10 +199,12 @@ class JobProgressPanel(Widget):
|
|
|
198
199
|
|
|
199
200
|
with Horizontal():
|
|
200
201
|
with Vertical(id="job-progress-section"):
|
|
201
|
-
|
|
202
|
+
for widget in self._compose_progress_section():
|
|
203
|
+
yield widget
|
|
202
204
|
|
|
203
205
|
with Vertical(id="job-metrics-section"):
|
|
204
|
-
|
|
206
|
+
for widget in self._compose_metrics_section():
|
|
207
|
+
yield widget
|
|
205
208
|
|
|
206
209
|
def _compose_progress_section(self) -> ComposeResult:
|
|
207
210
|
iteration = self.job_data.get("iteration", 1)
|
|
@@ -247,16 +250,16 @@ class JobProgressPanel(Widget):
|
|
|
247
250
|
if total_issues > 0:
|
|
248
251
|
success_rate = (fixed / total_issues) * 100
|
|
249
252
|
yield Label(
|
|
250
|
-
f"Success Rate: {success_rate
|
|
253
|
+
f"Success Rate: {success_rate: .1f}%",
|
|
251
254
|
classes="success-rate",
|
|
252
255
|
)
|
|
253
256
|
|
|
254
257
|
def _format_time(self, seconds: float) -> str:
|
|
255
258
|
if seconds < 60:
|
|
256
|
-
return f"{seconds
|
|
259
|
+
return f"{seconds: .0f}s"
|
|
257
260
|
if seconds < 3600:
|
|
258
|
-
return f"{seconds / 60
|
|
259
|
-
return f"{seconds / 3600
|
|
261
|
+
return f"{seconds / 60: .0f}m {seconds % 60: .0f}s"
|
|
262
|
+
return f"{seconds / 3600: .0f}h {(seconds % 3600) / 60: .0f}m"
|
|
260
263
|
|
|
261
264
|
|
|
262
265
|
class ServiceHealthPanel(Widget):
|
|
@@ -279,7 +282,7 @@ class ServiceHealthPanel(Widget):
|
|
|
279
282
|
)
|
|
280
283
|
table.zebra_stripes = True
|
|
281
284
|
|
|
282
|
-
def update_services(self, services: list[dict]) -> None:
|
|
285
|
+
def update_services(self, services: list[dict[str, t.Any]]) -> None:
|
|
283
286
|
table = self.query_one("#services-table", DataTable)
|
|
284
287
|
table.clear()
|
|
285
288
|
|
|
@@ -308,7 +311,7 @@ class ServiceHealthPanel(Widget):
|
|
|
308
311
|
|
|
309
312
|
if isinstance(last_check, int | float):
|
|
310
313
|
last_check_str = datetime.fromtimestamp(last_check).strftime(
|
|
311
|
-
"%H
|
|
314
|
+
"%H: %M: %S",
|
|
312
315
|
)
|
|
313
316
|
else:
|
|
314
317
|
last_check_str = str(last_check)
|
|
@@ -323,12 +326,12 @@ class ServiceHealthPanel(Widget):
|
|
|
323
326
|
|
|
324
327
|
def _format_uptime(self, seconds: float) -> str:
|
|
325
328
|
if seconds < 60:
|
|
326
|
-
return f"{seconds
|
|
329
|
+
return f"{seconds: .0f}s"
|
|
327
330
|
if seconds < 3600:
|
|
328
|
-
return f"{seconds / 60
|
|
331
|
+
return f"{seconds / 60: .0f}m"
|
|
329
332
|
if seconds < 86400:
|
|
330
|
-
return f"{seconds / 3600
|
|
331
|
-
return f"{seconds / 86400
|
|
333
|
+
return f"{seconds / 3600: .1f}h"
|
|
334
|
+
return f"{seconds / 86400: .1f}d"
|
|
332
335
|
|
|
333
336
|
|
|
334
337
|
class EnhancedCrackerjackDashboard(App):
|
|
@@ -336,7 +339,7 @@ class EnhancedCrackerjackDashboard(App):
|
|
|
336
339
|
CSS_PATH = Path(__file__).parent / "enhanced_progress_monitor.tcss"
|
|
337
340
|
|
|
338
341
|
def __init__(
|
|
339
|
-
self, progress_dir: Path, websocket_url: str = "ws
|
|
342
|
+
self, progress_dir: Path, websocket_url: str = "ws: //localhost: 8675"
|
|
340
343
|
) -> None:
|
|
341
344
|
super().__init__()
|
|
342
345
|
self.progress_dir = progress_dir
|
|
@@ -367,7 +370,7 @@ class EnhancedCrackerjackDashboard(App):
|
|
|
367
370
|
jobs_result = await self.data_collector.discover_jobs()
|
|
368
371
|
jobs_data = jobs_result.get("data", {})
|
|
369
372
|
|
|
370
|
-
services = self.service_manager.
|
|
373
|
+
services = self.service_manager.collect_services_data()
|
|
371
374
|
self.query_one("#service-panel", ServiceHealthPanel).update_services(
|
|
372
375
|
services,
|
|
373
376
|
)
|
|
@@ -385,8 +388,8 @@ class EnhancedCrackerjackDashboard(App):
|
|
|
385
388
|
except Exception as e:
|
|
386
389
|
self.console.print(f"[red]Dashboard update error: {e}[/]")
|
|
387
390
|
|
|
388
|
-
def _aggregate_agent_data(self, jobs: list[dict]) -> dict:
|
|
389
|
-
aggregated = {
|
|
391
|
+
def _aggregate_agent_data(self, jobs: list[dict[str, t.Any]]) -> dict[str, t.Any]:
|
|
392
|
+
aggregated: dict[str, dict[str, t.Any]] = {
|
|
390
393
|
"agent_activity": {
|
|
391
394
|
"active_agents": [],
|
|
392
395
|
"coordinator_status": "idle",
|
|
@@ -418,7 +421,7 @@ class EnhancedCrackerjackDashboard(App):
|
|
|
418
421
|
|
|
419
422
|
return aggregated
|
|
420
423
|
|
|
421
|
-
def _update_job_panels(self, jobs: list[dict]) -> None:
|
|
424
|
+
def _update_job_panels(self, jobs: list[dict[str, t.Any]]) -> None:
|
|
422
425
|
container = self.query_one("#jobs-container", Container)
|
|
423
426
|
|
|
424
427
|
with suppress(Exception):
|
|
@@ -445,7 +448,7 @@ class EnhancedCrackerjackDashboard(App):
|
|
|
445
448
|
|
|
446
449
|
async def run_enhanced_progress_monitor(
|
|
447
450
|
progress_dir: Path | None = None,
|
|
448
|
-
websocket_url: str = "ws
|
|
451
|
+
websocket_url: str = "ws: //localhost: 8675",
|
|
449
452
|
dev_mode: bool = False,
|
|
450
453
|
) -> None:
|
|
451
454
|
if progress_dir is None:
|
|
@@ -474,6 +477,6 @@ if __name__ == "__main__":
|
|
|
474
477
|
import tempfile
|
|
475
478
|
|
|
476
479
|
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
|
|
480
|
+
websocket_url = sys.argv[2] if len(sys.argv) > 2 else "ws: //localhost: 8675"
|
|
478
481
|
|
|
479
482
|
asyncio.run(run_enhanced_progress_monitor(progress_dir, websocket_url))
|
crackerjack/mcp/file_monitor.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
+
import typing as t
|
|
4
5
|
from collections.abc import Callable
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -10,9 +11,10 @@ try:
|
|
|
10
11
|
|
|
11
12
|
WATCHDOG_AVAILABLE = True
|
|
12
13
|
except ImportError:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# Type stubs for when watchdog is not available
|
|
15
|
+
FileSystemEvent = t.Any
|
|
16
|
+
FileSystemEventHandler = t.Any
|
|
17
|
+
Observer = t.Any
|
|
16
18
|
WATCHDOG_AVAILABLE = False
|
|
17
19
|
|
|
18
20
|
import contextlib
|
|
@@ -28,7 +30,7 @@ if WATCHDOG_AVAILABLE:
|
|
|
28
30
|
|
|
29
31
|
class ProgressFileHandler(FileSystemEventHandler):
|
|
30
32
|
def __init__(
|
|
31
|
-
self, callback: Callable[[str, dict], None], progress_dir: Path
|
|
33
|
+
self, callback: Callable[[str, dict[str, t.Any]], None], progress_dir: Path
|
|
32
34
|
) -> None:
|
|
33
35
|
super().__init__()
|
|
34
36
|
self.callback = callback
|
|
@@ -43,7 +45,6 @@ if WATCHDOG_AVAILABLE:
|
|
|
43
45
|
try:
|
|
44
46
|
file_path = Path(event.src_path)
|
|
45
47
|
|
|
46
|
-
# Validate that the file path is within our allowed progress directory
|
|
47
48
|
validated_path = SecurePathValidator.validate_safe_path(
|
|
48
49
|
file_path, self.progress_dir
|
|
49
50
|
)
|
|
@@ -66,11 +67,9 @@ if WATCHDOG_AVAILABLE:
|
|
|
66
67
|
|
|
67
68
|
job_id = validated_path.stem.replace("job-", "")
|
|
68
69
|
except Exception:
|
|
69
|
-
# If path validation fails, skip processing this file
|
|
70
70
|
return
|
|
71
71
|
|
|
72
72
|
try:
|
|
73
|
-
# Validate file size before reading
|
|
74
73
|
SecurePathValidator.validate_file_size(validated_path)
|
|
75
74
|
|
|
76
75
|
with validated_path.open() as f:
|
|
@@ -88,7 +87,7 @@ if WATCHDOG_AVAILABLE:
|
|
|
88
87
|
else:
|
|
89
88
|
|
|
90
89
|
class ProgressFileHandler:
|
|
91
|
-
def __init__(self, callback: Callable[[str, dict], None]) -> None:
|
|
90
|
+
def __init__(self, callback: Callable[[str, dict[str, t.Any]], None]) -> None:
|
|
92
91
|
pass
|
|
93
92
|
|
|
94
93
|
|
|
@@ -96,7 +95,7 @@ class AsyncProgressMonitor:
|
|
|
96
95
|
def __init__(self, progress_dir: Path) -> None:
|
|
97
96
|
self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
|
|
98
97
|
self.observer: Observer | None = None
|
|
99
|
-
self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
|
|
98
|
+
self.subscribers: dict[str, set[Callable[[dict[str, t.Any]], None]]] = {}
|
|
100
99
|
self._running = False
|
|
101
100
|
|
|
102
101
|
self.progress_dir.mkdir(exist_ok=True)
|
|
@@ -132,14 +131,18 @@ class AsyncProgressMonitor:
|
|
|
132
131
|
|
|
133
132
|
console.print("[yellow]📁 Stopped progress directory monitoring[/ yellow]")
|
|
134
133
|
|
|
135
|
-
def subscribe(
|
|
134
|
+
def subscribe(
|
|
135
|
+
self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
|
|
136
|
+
) -> None:
|
|
136
137
|
if job_id not in self.subscribers:
|
|
137
138
|
self.subscribers[job_id] = set()
|
|
138
139
|
|
|
139
140
|
self.subscribers[job_id].add(callback)
|
|
140
141
|
console.print(f"[cyan]📋 Subscribed to job updates: {job_id}[/ cyan]")
|
|
141
142
|
|
|
142
|
-
def unsubscribe(
|
|
143
|
+
def unsubscribe(
|
|
144
|
+
self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
|
|
145
|
+
) -> None:
|
|
143
146
|
if job_id in self.subscribers:
|
|
144
147
|
self.subscribers[job_id].discard(callback)
|
|
145
148
|
|
|
@@ -148,7 +151,7 @@ class AsyncProgressMonitor:
|
|
|
148
151
|
|
|
149
152
|
console.print(f"[cyan]📋 Unsubscribed from job updates: {job_id}[/ cyan]")
|
|
150
153
|
|
|
151
|
-
def _on_file_changed(self, job_id: str, progress_data: dict) -> None:
|
|
154
|
+
def _on_file_changed(self, job_id: str, progress_data: dict[str, t.Any]) -> None:
|
|
152
155
|
if job_id in self.subscribers:
|
|
153
156
|
for callback in self.subscribers[job_id].copy():
|
|
154
157
|
try:
|
|
@@ -160,7 +163,7 @@ class AsyncProgressMonitor:
|
|
|
160
163
|
|
|
161
164
|
self.subscribers[job_id].discard(callback)
|
|
162
165
|
|
|
163
|
-
async def get_current_progress(self, job_id: str) -> dict | None:
|
|
166
|
+
async def get_current_progress(self, job_id: str) -> dict[str, t.Any] | None:
|
|
164
167
|
progress_file = self.progress_dir / f"job-{job_id}.json"
|
|
165
168
|
|
|
166
169
|
if not progress_file.exists():
|
|
@@ -168,7 +171,8 @@ class AsyncProgressMonitor:
|
|
|
168
171
|
|
|
169
172
|
try:
|
|
170
173
|
with progress_file.open() as f:
|
|
171
|
-
|
|
174
|
+
json_result = json.load(f)
|
|
175
|
+
return t.cast(dict[str, t.Any] | None, json_result)
|
|
172
176
|
except (json.JSONDecodeError, OSError):
|
|
173
177
|
return None
|
|
174
178
|
|
|
@@ -208,9 +212,9 @@ class AsyncProgressMonitor:
|
|
|
208
212
|
class PollingProgressMonitor:
|
|
209
213
|
def __init__(self, progress_dir: Path) -> None:
|
|
210
214
|
self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
|
|
211
|
-
self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
|
|
215
|
+
self.subscribers: dict[str, set[Callable[[dict[str, t.Any]], None]]] = {}
|
|
212
216
|
self._running = False
|
|
213
|
-
self._poll_task: asyncio.Task | None = None
|
|
217
|
+
self._poll_task: asyncio.Task[None] | None = None
|
|
214
218
|
self._file_mtimes: dict[str, float] = {}
|
|
215
219
|
|
|
216
220
|
self.progress_dir.mkdir(exist_ok=True)
|
|
@@ -252,7 +256,6 @@ class PollingProgressMonitor:
|
|
|
252
256
|
|
|
253
257
|
for progress_file in self.progress_dir.glob("job-*.json"):
|
|
254
258
|
try:
|
|
255
|
-
# Validate file path is within our allowed directory
|
|
256
259
|
validated_file = SecurePathValidator.validate_safe_path(
|
|
257
260
|
progress_file, self.progress_dir
|
|
258
261
|
)
|
|
@@ -267,7 +270,6 @@ class PollingProgressMonitor:
|
|
|
267
270
|
job_id = validated_file.stem.replace("job-", "")
|
|
268
271
|
|
|
269
272
|
try:
|
|
270
|
-
# Validate file size before reading
|
|
271
273
|
SecurePathValidator.validate_file_size(validated_file)
|
|
272
274
|
with validated_file.open() as f:
|
|
273
275
|
progress_data = json.load(f)
|
|
@@ -284,7 +286,7 @@ class PollingProgressMonitor:
|
|
|
284
286
|
|
|
285
287
|
self._file_mtimes = current_files
|
|
286
288
|
|
|
287
|
-
def _notify_subscribers(self, job_id: str, progress_data: dict) -> None:
|
|
289
|
+
def _notify_subscribers(self, job_id: str, progress_data: dict[str, t.Any]) -> None:
|
|
288
290
|
if job_id in self.subscribers:
|
|
289
291
|
for callback in self.subscribers[job_id].copy():
|
|
290
292
|
try:
|
|
@@ -295,14 +297,18 @@ class PollingProgressMonitor:
|
|
|
295
297
|
)
|
|
296
298
|
self.subscribers[job_id].discard(callback)
|
|
297
299
|
|
|
298
|
-
def subscribe(
|
|
300
|
+
def subscribe(
|
|
301
|
+
self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
|
|
302
|
+
) -> None:
|
|
299
303
|
if job_id not in self.subscribers:
|
|
300
304
|
self.subscribers[job_id] = set()
|
|
301
305
|
|
|
302
306
|
self.subscribers[job_id].add(callback)
|
|
303
307
|
console.print(f"[cyan]📋 Subscribed to job updates: {job_id} (polling)[/ cyan]")
|
|
304
308
|
|
|
305
|
-
def unsubscribe(
|
|
309
|
+
def unsubscribe(
|
|
310
|
+
self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
|
|
311
|
+
) -> None:
|
|
306
312
|
if job_id in self.subscribers:
|
|
307
313
|
self.subscribers[job_id].discard(callback)
|
|
308
314
|
|
|
@@ -313,7 +319,7 @@ class PollingProgressMonitor:
|
|
|
313
319
|
f"[cyan]📋 Unsubscribed from job updates: {job_id} (polling)[/ cyan]",
|
|
314
320
|
)
|
|
315
321
|
|
|
316
|
-
async def get_current_progress(self, job_id: str) -> dict | None:
|
|
322
|
+
async def get_current_progress(self, job_id: str) -> dict[str, t.Any] | None:
|
|
317
323
|
progress_file = self.progress_dir / f"job-{job_id}.json"
|
|
318
324
|
|
|
319
325
|
if not progress_file.exists():
|
|
@@ -321,7 +327,8 @@ class PollingProgressMonitor:
|
|
|
321
327
|
|
|
322
328
|
try:
|
|
323
329
|
with progress_file.open() as f:
|
|
324
|
-
|
|
330
|
+
json_result = json.load(f)
|
|
331
|
+
return t.cast(dict[str, t.Any] | None, json_result)
|
|
325
332
|
except (json.JSONDecodeError, OSError):
|
|
326
333
|
return None
|
|
327
334
|
|
|
@@ -109,7 +109,7 @@ class JobDataCollector:
|
|
|
109
109
|
status = data.get("status", "unknown")
|
|
110
110
|
stage = data.get("current_stage", "Unknown")
|
|
111
111
|
iteration = data.get("iteration", 0)
|
|
112
|
-
max_iterations = data.get("max_iterations",
|
|
112
|
+
max_iterations = data.get("max_iterations", 5)
|
|
113
113
|
|
|
114
114
|
status_emoji = {
|
|
115
115
|
"running": "🚀 Running",
|
|
@@ -138,7 +138,7 @@ class JobDataCollector:
|
|
|
138
138
|
)
|
|
139
139
|
|
|
140
140
|
async def _discover_jobs_websocket(self) -> dict[str, Any]:
|
|
141
|
-
jobs_data = {
|
|
141
|
+
jobs_data: dict[str, Any] = {
|
|
142
142
|
"active": 0,
|
|
143
143
|
"completed": 0,
|
|
144
144
|
"failed": 0,
|
|
@@ -155,11 +155,13 @@ class JobDataCollector:
|
|
|
155
155
|
with suppress(Exception):
|
|
156
156
|
async with timeout_manager.timeout_context(
|
|
157
157
|
"network_operations",
|
|
158
|
-
timeout=5.0,
|
|
158
|
+
timeout=5.0,
|
|
159
159
|
):
|
|
160
|
-
websocket_base = self.websocket_url.replace(
|
|
161
|
-
"
|
|
162
|
-
|
|
160
|
+
websocket_base = self.websocket_url.replace(
|
|
161
|
+
"ws: //", "http: //"
|
|
162
|
+
).replace(
|
|
163
|
+
"wss: //",
|
|
164
|
+
"https: //",
|
|
163
165
|
)
|
|
164
166
|
|
|
165
167
|
async with (
|
|
@@ -239,13 +241,13 @@ class ServiceHealthChecker:
|
|
|
239
241
|
try:
|
|
240
242
|
async with timeout_manager.timeout_context(
|
|
241
243
|
"network_operations",
|
|
242
|
-
timeout=3.0,
|
|
244
|
+
timeout=3.0,
|
|
243
245
|
):
|
|
244
246
|
async with (
|
|
245
247
|
aiohttp.ClientSession(
|
|
246
248
|
timeout=aiohttp.ClientTimeout(total=2),
|
|
247
249
|
) as session,
|
|
248
|
-
session.get("http
|
|
250
|
+
session.get("http: //localhost: 8675/") as response,
|
|
249
251
|
):
|
|
250
252
|
if response.status == 200:
|
|
251
253
|
data = await response.json()
|
|
@@ -267,7 +269,7 @@ class ServiceHealthChecker:
|
|
|
267
269
|
check=False,
|
|
268
270
|
capture_output=True,
|
|
269
271
|
text=True,
|
|
270
|
-
timeout=5.0,
|
|
272
|
+
timeout=5.0,
|
|
271
273
|
)
|
|
272
274
|
if result.returncode == 0:
|
|
273
275
|
return ("MCP Server", "🟢 Process Active", "0")
|
|
@@ -284,7 +286,7 @@ class ServiceHealthChecker:
|
|
|
284
286
|
check=False,
|
|
285
287
|
capture_output=True,
|
|
286
288
|
text=True,
|
|
287
|
-
timeout=5.0,
|
|
289
|
+
timeout=5.0,
|
|
288
290
|
)
|
|
289
291
|
if result.returncode == 0:
|
|
290
292
|
return ("Service Watchdog", "🟢 Active", "0")
|
|
@@ -300,7 +302,7 @@ class ErrorCollector:
|
|
|
300
302
|
self.console = Console()
|
|
301
303
|
|
|
302
304
|
async def collect_recent_errors(self) -> list[tuple[str, str, str, str]]:
|
|
303
|
-
errors = []
|
|
305
|
+
errors: list[tuple[str, str, str, str]] = []
|
|
304
306
|
|
|
305
307
|
errors.extend(self._check_debug_logs())
|
|
306
308
|
|
|
@@ -320,7 +322,7 @@ class ErrorCollector:
|
|
|
320
322
|
return errors[-5:]
|
|
321
323
|
|
|
322
324
|
def _check_debug_logs(self) -> list[tuple[str, str, str, str]]:
|
|
323
|
-
errors = []
|
|
325
|
+
errors: list[tuple[str, str, str, str]] = []
|
|
324
326
|
|
|
325
327
|
with suppress(Exception):
|
|
326
328
|
debug_log = Path(tempfile.gettempdir()) / "tui_debug.log"
|
|
@@ -333,7 +335,7 @@ class ErrorCollector:
|
|
|
333
335
|
self,
|
|
334
336
|
debug_log: Path,
|
|
335
337
|
) -> list[tuple[str, str, str, str]]:
|
|
336
|
-
errors = []
|
|
338
|
+
errors: list[tuple[str, str, str, str]] = []
|
|
337
339
|
|
|
338
340
|
with debug_log.open() as f:
|
|
339
341
|
lines = f.readlines()[-10:]
|
|
@@ -362,7 +364,7 @@ class ErrorCollector:
|
|
|
362
364
|
return message[:40] + "..." if len(message) > 40 else message
|
|
363
365
|
|
|
364
366
|
def _check_crackerjack_logs(self) -> list[tuple[str, str, str, str]]:
|
|
365
|
-
errors = []
|
|
367
|
+
errors: list[tuple[str, str, str, str]] = []
|
|
366
368
|
|
|
367
369
|
with suppress(Exception):
|
|
368
370
|
for log_file in Path(tempfile.gettempdir()).glob(
|
|
@@ -380,7 +382,7 @@ class ErrorCollector:
|
|
|
380
382
|
self,
|
|
381
383
|
log_file: Path,
|
|
382
384
|
) -> list[tuple[str, str, str, str]]:
|
|
383
|
-
errors = []
|
|
385
|
+
errors: list[tuple[str, str, str, str]] = []
|
|
384
386
|
|
|
385
387
|
with log_file.open() as f:
|
|
386
388
|
lines = f.readlines()[-5:]
|
|
@@ -415,7 +417,7 @@ class ErrorCollector:
|
|
|
415
417
|
|
|
416
418
|
class ServiceManager:
|
|
417
419
|
def __init__(self) -> None:
|
|
418
|
-
self.started_services: list[tuple[str, subprocess.Popen]] = []
|
|
420
|
+
self.started_services: list[tuple[str, subprocess.Popen[bytes]]] = []
|
|
419
421
|
self.console = Console()
|
|
420
422
|
|
|
421
423
|
async def ensure_services_running(self) -> None:
|
|
@@ -446,12 +448,12 @@ class ServiceManager:
|
|
|
446
448
|
with suppress(Exception):
|
|
447
449
|
async with timeout_manager.timeout_context(
|
|
448
450
|
"network_operations",
|
|
449
|
-
timeout=3.0,
|
|
451
|
+
timeout=3.0,
|
|
450
452
|
):
|
|
451
453
|
async with aiohttp.ClientSession(
|
|
452
454
|
timeout=aiohttp.ClientTimeout(total=2),
|
|
453
455
|
) as session:
|
|
454
|
-
async with session.get("http
|
|
456
|
+
async with session.get("http: //localhost: 8675/") as response:
|
|
455
457
|
return response.status == 200
|
|
456
458
|
return False
|
|
457
459
|
|
|
@@ -462,11 +464,19 @@ class ServiceManager:
|
|
|
462
464
|
check=False,
|
|
463
465
|
capture_output=True,
|
|
464
466
|
text=True,
|
|
465
|
-
timeout=5.0,
|
|
467
|
+
timeout=5.0,
|
|
466
468
|
)
|
|
467
469
|
return result.returncode == 0
|
|
468
470
|
return False
|
|
469
471
|
|
|
472
|
+
def collect_services_data(self) -> list[tuple[str, str, str]]:
|
|
473
|
+
"""Check all services and return their status information."""
|
|
474
|
+
mcp_status = "running" if self._check_mcp_server() else "stopped"
|
|
475
|
+
return [
|
|
476
|
+
("mcp_server", mcp_status, "localhost:8675"),
|
|
477
|
+
("websocket_server", "unknown", "localhost:8676"),
|
|
478
|
+
]
|
|
479
|
+
|
|
470
480
|
async def _start_websocket_server(self) -> None:
|
|
471
481
|
with suppress(Exception):
|
|
472
482
|
process = subprocess.Popen(
|
|
@@ -496,7 +506,7 @@ class ServiceManager:
|
|
|
496
506
|
check=False,
|
|
497
507
|
capture_output=True,
|
|
498
508
|
text=True,
|
|
499
|
-
timeout=5.0,
|
|
509
|
+
timeout=5.0,
|
|
500
510
|
)
|
|
501
511
|
if result.returncode == 0:
|
|
502
512
|
return
|
|
@@ -514,7 +524,7 @@ class ServiceManager:
|
|
|
514
524
|
self._cleanup_single_service(process)
|
|
515
525
|
self.started_services.clear()
|
|
516
526
|
|
|
517
|
-
def _cleanup_single_service(self, process: subprocess.Popen) -> None:
|
|
527
|
+
def _cleanup_single_service(self, process: subprocess.Popen[bytes]) -> None:
|
|
518
528
|
with suppress(Exception):
|
|
519
529
|
if process.poll() is not None:
|
|
520
530
|
return
|