crackerjack 0.33.0__py3-none-any.whl → 0.33.2__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 +618 -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.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.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.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,6 +5,7 @@ import subprocess
|
|
|
5
5
|
import sys
|
|
6
6
|
import tempfile
|
|
7
7
|
import time
|
|
8
|
+
import typing as t
|
|
8
9
|
from contextlib import suppress
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
@@ -26,7 +27,7 @@ from .progress_components import (
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class AgentStatusPanel(Widget):
|
|
29
|
-
def __init__(self, **kwargs) -> None:
|
|
30
|
+
def __init__(self, **kwargs: t.Any) -> None:
|
|
30
31
|
super().__init__(**kwargs)
|
|
31
32
|
self.border_title = "🤖 AI Agents"
|
|
32
33
|
self.border_title_align = "left"
|
|
@@ -49,13 +50,13 @@ class AgentStatusPanel(Widget):
|
|
|
49
50
|
|
|
50
51
|
agents_table.styles.max_height = "8"
|
|
51
52
|
|
|
52
|
-
def update_agent_data(self, agent_data: dict) -> None:
|
|
53
|
+
def update_agent_data(self, agent_data: dict[str, t.Any]) -> None:
|
|
53
54
|
with suppress(Exception):
|
|
54
55
|
self._update_coordinator_status(agent_data)
|
|
55
56
|
self._update_agents_table(agent_data)
|
|
56
57
|
self._update_stats(agent_data)
|
|
57
58
|
|
|
58
|
-
def _update_coordinator_status(self, data: dict) -> None:
|
|
59
|
+
def _update_coordinator_status(self, data: dict[str, t.Any]) -> None:
|
|
59
60
|
with suppress(Exception):
|
|
60
61
|
activity = data.get("agent_activity", {})
|
|
61
62
|
registry = activity.get("agent_registry", {})
|
|
@@ -68,7 +69,7 @@ class AgentStatusPanel(Widget):
|
|
|
68
69
|
f"Coordinator: {status_emoji} {coordinator_status.title()} ({total_agents} agents)",
|
|
69
70
|
)
|
|
70
71
|
|
|
71
|
-
def _update_agents_table(self, data: dict) -> None:
|
|
72
|
+
def _update_agents_table(self, data: dict[str, t.Any]) -> None:
|
|
72
73
|
with suppress(Exception):
|
|
73
74
|
agents_table = self.query_one("#agents-table", DataTable)
|
|
74
75
|
agents_table.clear()
|
|
@@ -105,7 +106,7 @@ class AgentStatusPanel(Widget):
|
|
|
105
106
|
f"{processing_time: .1f}s" if processing_time > 0 else "-",
|
|
106
107
|
)
|
|
107
108
|
|
|
108
|
-
def _update_stats(self, data: dict) -> None:
|
|
109
|
+
def _update_stats(self, data: dict[str, t.Any]) -> None:
|
|
109
110
|
with suppress(Exception):
|
|
110
111
|
performance = data.get("agent_performance", {})
|
|
111
112
|
total_issues = performance.get("total_issues_processed", 0)
|
|
@@ -152,12 +153,12 @@ class AgentStatusPanel(Widget):
|
|
|
152
153
|
|
|
153
154
|
|
|
154
155
|
class JobPanel(Widget):
|
|
155
|
-
def __init__(self, job_data: dict, **kwargs) -> None:
|
|
156
|
+
def __init__(self, job_data: dict[str, t.Any], **kwargs: t.Any) -> None:
|
|
156
157
|
super().__init__(**kwargs)
|
|
157
158
|
self.job_data = job_data
|
|
158
159
|
self.completion_time = None
|
|
159
160
|
self.iteration_count = job_data.get("iteration", 0)
|
|
160
|
-
self.max_iterations = job_data.get("max_iterations",
|
|
161
|
+
self.max_iterations = job_data.get("max_iterations", 5)
|
|
161
162
|
self.fade_timer = None
|
|
162
163
|
self.remove_timer = None
|
|
163
164
|
self.fade_level = 0
|
|
@@ -417,11 +418,13 @@ class CrackerjackDashboard(App):
|
|
|
417
418
|
|
|
418
419
|
def __init__(self) -> None:
|
|
419
420
|
super().__init__()
|
|
420
|
-
self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack
|
|
421
|
+
self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
|
|
421
422
|
self.websocket_url = "ws://localhost:8675"
|
|
422
423
|
self.refresh_timer = None
|
|
423
|
-
self.
|
|
424
|
-
self.
|
|
424
|
+
self._refresh_counter = 0
|
|
425
|
+
self.dev = False
|
|
426
|
+
self.active_jobs: dict[str, t.Any] = {}
|
|
427
|
+
self.completed_jobs_stats: dict[str, t.Any] = {}
|
|
425
428
|
self.current_polling_method = "File"
|
|
426
429
|
self.timeout_manager = get_timeout_manager()
|
|
427
430
|
|
|
@@ -532,19 +535,14 @@ class CrackerjackDashboard(App):
|
|
|
532
535
|
|
|
533
536
|
async def _refresh_data(self) -> None:
|
|
534
537
|
try:
|
|
535
|
-
# Refresh cycle with timeout protection
|
|
536
538
|
async with self.timeout_manager.timeout_context(
|
|
537
539
|
"network_operations",
|
|
538
|
-
timeout=10.0,
|
|
540
|
+
timeout=10.0,
|
|
539
541
|
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
540
542
|
):
|
|
541
|
-
|
|
542
|
-
self._refresh_counter += 1
|
|
543
|
-
else:
|
|
544
|
-
self._refresh_counter = 0
|
|
543
|
+
self._refresh_counter += 1
|
|
545
544
|
|
|
546
|
-
|
|
547
|
-
if self._refresh_counter % 20 == 0: # Every 10 seconds instead of 5
|
|
545
|
+
if self._refresh_counter % 20 == 0:
|
|
548
546
|
with suppress(Exception):
|
|
549
547
|
await self.timeout_manager.with_timeout(
|
|
550
548
|
"network_operations",
|
|
@@ -553,7 +551,6 @@ class CrackerjackDashboard(App):
|
|
|
553
551
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
554
552
|
)
|
|
555
553
|
|
|
556
|
-
# Collect data with individual timeouts
|
|
557
554
|
jobs_data = await self.timeout_manager.with_timeout(
|
|
558
555
|
"network_operations",
|
|
559
556
|
self._discover_jobs(),
|
|
@@ -575,7 +572,6 @@ class CrackerjackDashboard(App):
|
|
|
575
572
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
576
573
|
)
|
|
577
574
|
|
|
578
|
-
# Update UI components
|
|
579
575
|
self.query_one("#services-panel").border_title = "🔧 Services"
|
|
580
576
|
|
|
581
577
|
self._update_jobs_table(jobs_data)
|
|
@@ -586,12 +582,11 @@ class CrackerjackDashboard(App):
|
|
|
586
582
|
self._update_status_bars(jobs_data)
|
|
587
583
|
|
|
588
584
|
except Exception as e:
|
|
589
|
-
# Log error but don't crash the dashboard
|
|
590
585
|
with suppress(Exception):
|
|
591
586
|
console = Console()
|
|
592
587
|
console.print(f"[red]Dashboard refresh error: {e}[/red]")
|
|
593
588
|
|
|
594
|
-
async def _discover_jobs(self) -> dict:
|
|
589
|
+
async def _discover_jobs(self) -> dict[str, t.Any]:
|
|
595
590
|
try:
|
|
596
591
|
result = await self.timeout_manager.with_timeout(
|
|
597
592
|
"network_operations",
|
|
@@ -600,9 +595,9 @@ class CrackerjackDashboard(App):
|
|
|
600
595
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
601
596
|
)
|
|
602
597
|
self.current_polling_method = result["method"]
|
|
603
|
-
|
|
598
|
+
data_result = result["data"]
|
|
599
|
+
return t.cast(dict[str, t.Any], data_result)
|
|
604
600
|
except Exception:
|
|
605
|
-
# Return empty data structure on timeout or error
|
|
606
601
|
return {
|
|
607
602
|
"active": 0,
|
|
608
603
|
"completed": 0,
|
|
@@ -615,31 +610,31 @@ class CrackerjackDashboard(App):
|
|
|
615
610
|
"current_errors": 0,
|
|
616
611
|
}
|
|
617
612
|
|
|
618
|
-
async def _collect_services_data(self) -> list:
|
|
613
|
+
async def _collect_services_data(self) -> list[t.Any]:
|
|
619
614
|
try:
|
|
620
|
-
|
|
615
|
+
services_data = await self.timeout_manager.with_timeout(
|
|
621
616
|
"network_operations",
|
|
622
617
|
self.service_checker.collect_services_data(),
|
|
623
618
|
timeout=3.0,
|
|
624
619
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
625
620
|
)
|
|
621
|
+
return t.cast(list[t.Any], services_data)
|
|
626
622
|
except Exception:
|
|
627
|
-
# Return minimal service data on timeout
|
|
628
623
|
return [("Services", "🔴 Timeout", "0")]
|
|
629
624
|
|
|
630
|
-
async def _collect_recent_errors(self) -> list:
|
|
625
|
+
async def _collect_recent_errors(self) -> list[t.Any]:
|
|
631
626
|
try:
|
|
632
|
-
|
|
627
|
+
errors_data = await self.timeout_manager.with_timeout(
|
|
633
628
|
"file_operations",
|
|
634
629
|
self.error_collector.collect_recent_errors(),
|
|
635
630
|
timeout=2.0,
|
|
636
631
|
strategy=TimeoutStrategy.FAIL_FAST,
|
|
637
632
|
)
|
|
633
|
+
return t.cast(list[t.Any], errors_data)
|
|
638
634
|
except Exception:
|
|
639
|
-
# Return empty error list on timeout
|
|
640
635
|
return []
|
|
641
636
|
|
|
642
|
-
def _update_jobs_table(self, jobs_data: dict) -> None:
|
|
637
|
+
def _update_jobs_table(self, jobs_data: dict[str, t.Any]) -> None:
|
|
643
638
|
with suppress(Exception):
|
|
644
639
|
jobs_table = self.query_one("#jobs-table", DataTable)
|
|
645
640
|
jobs_table.clear()
|
|
@@ -681,7 +676,7 @@ class CrackerjackDashboard(App):
|
|
|
681
676
|
],
|
|
682
677
|
)
|
|
683
678
|
|
|
684
|
-
def _update_services_table(self, services_data: list) -> None:
|
|
679
|
+
def _update_services_table(self, services_data: list[t.Any]) -> None:
|
|
685
680
|
with suppress(Exception):
|
|
686
681
|
services_table = self.query_one("#services-table", DataTable)
|
|
687
682
|
services_table.clear()
|
|
@@ -705,7 +700,7 @@ class CrackerjackDashboard(App):
|
|
|
705
700
|
polling_status += " 🟢"
|
|
706
701
|
services_table.add_row("Polling", polling_status, "")
|
|
707
702
|
|
|
708
|
-
def _update_errors_table(self, errors_data: list) -> None:
|
|
703
|
+
def _update_errors_table(self, errors_data: list[t.Any]) -> None:
|
|
709
704
|
with suppress(Exception):
|
|
710
705
|
errors_table = self.query_one("#errors-table", DataTable)
|
|
711
706
|
errors_table.clear()
|
|
@@ -767,7 +762,7 @@ class CrackerjackDashboard(App):
|
|
|
767
762
|
],
|
|
768
763
|
)
|
|
769
764
|
|
|
770
|
-
def _update_agent_panel(self, jobs_data: dict) -> None:
|
|
765
|
+
def _update_agent_panel(self, jobs_data: dict[str, t.Any]) -> None:
|
|
771
766
|
with suppress(Exception):
|
|
772
767
|
agent_panel = self.query_one("#agent - status-panel", AgentStatusPanel)
|
|
773
768
|
|
|
@@ -780,7 +775,7 @@ class CrackerjackDashboard(App):
|
|
|
780
775
|
if agent_data:
|
|
781
776
|
agent_panel.update_agent_data(agent_data)
|
|
782
777
|
|
|
783
|
-
def _update_job_panels(self, jobs_data: dict) -> None:
|
|
778
|
+
def _update_job_panels(self, jobs_data: dict[str, t.Any]) -> None:
|
|
784
779
|
with suppress(Exception):
|
|
785
780
|
container = self.query_one("#job - discovery-container")
|
|
786
781
|
current_job_ids = self._get_current_job_ids(jobs_data)
|
|
@@ -789,14 +784,14 @@ class CrackerjackDashboard(App):
|
|
|
789
784
|
self._update_or_create_panels(jobs_data, container)
|
|
790
785
|
self._handle_placeholder_visibility(container)
|
|
791
786
|
|
|
792
|
-
def _get_current_job_ids(self, jobs_data: dict) -> set:
|
|
787
|
+
def _get_current_job_ids(self, jobs_data: dict[str, t.Any]) -> set[t.Any]:
|
|
793
788
|
return (
|
|
794
789
|
{job["job_id"] for job in jobs_data["individual_jobs"]}
|
|
795
790
|
if jobs_data["individual_jobs"]
|
|
796
791
|
else set()
|
|
797
792
|
)
|
|
798
793
|
|
|
799
|
-
def _remove_obsolete_panels(self, current_job_ids: set) -> None:
|
|
794
|
+
def _remove_obsolete_panels(self, current_job_ids: set[t.Any]) -> None:
|
|
800
795
|
jobs_to_remove = []
|
|
801
796
|
for job_id, panel in self.active_jobs.items():
|
|
802
797
|
panel_status = panel.job_data.get("status", "").lower()
|
|
@@ -811,7 +806,9 @@ class CrackerjackDashboard(App):
|
|
|
811
806
|
panel = self.active_jobs.pop(job_id)
|
|
812
807
|
panel.remove()
|
|
813
808
|
|
|
814
|
-
def _update_or_create_panels(
|
|
809
|
+
def _update_or_create_panels(
|
|
810
|
+
self, jobs_data: dict[str, t.Any], container: t.Any
|
|
811
|
+
) -> None:
|
|
815
812
|
if not jobs_data["individual_jobs"]:
|
|
816
813
|
return
|
|
817
814
|
|
|
@@ -822,7 +819,7 @@ class CrackerjackDashboard(App):
|
|
|
822
819
|
else:
|
|
823
820
|
self._create_new_panel(job, container)
|
|
824
821
|
|
|
825
|
-
def _update_existing_panel(self, job: dict) -> None:
|
|
822
|
+
def _update_existing_panel(self, job: dict[str, t.Any]) -> None:
|
|
826
823
|
existing_panel = self.active_jobs[job["job_id"]]
|
|
827
824
|
existing_panel.job_data = job
|
|
828
825
|
existing_panel.iteration_count = job.get("iteration", 0)
|
|
@@ -833,7 +830,7 @@ class CrackerjackDashboard(App):
|
|
|
833
830
|
self._handle_job_completion(existing_panel, job)
|
|
834
831
|
self._update_panel_border(existing_panel)
|
|
835
832
|
|
|
836
|
-
def _update_panel_title(self, panel, job: dict) -> None:
|
|
833
|
+
def _update_panel_title(self, panel: t.Any, job: dict[str, t.Any]) -> None:
|
|
837
834
|
project_name = job.get("project", "crackerjack")
|
|
838
835
|
status = job.get("status", "").lower()
|
|
839
836
|
|
|
@@ -846,25 +843,25 @@ class CrackerjackDashboard(App):
|
|
|
846
843
|
else:
|
|
847
844
|
panel.border_subtitle = ""
|
|
848
845
|
|
|
849
|
-
def _handle_job_completion(self, panel, job: dict) -> None:
|
|
846
|
+
def _handle_job_completion(self, panel: t.Any, job: dict[str, t.Any]) -> None:
|
|
850
847
|
job_status = job.get("status", "").lower()
|
|
851
848
|
if job_status in ("completed", "failed") and panel.completion_time is None:
|
|
852
849
|
panel.completion_time = time.time()
|
|
853
850
|
panel.fade_timer = panel.set_timer(300.0, panel._start_fade)
|
|
854
851
|
panel.remove_timer = panel.set_timer(1200.0, panel._remove_panel)
|
|
855
852
|
|
|
856
|
-
def _update_panel_border(self, panel) -> None:
|
|
853
|
+
def _update_panel_border(self, panel: t.Any) -> None:
|
|
857
854
|
new_border = panel._calculate_border_style()
|
|
858
855
|
if new_border != panel.border_style:
|
|
859
856
|
panel.border_style = new_border
|
|
860
857
|
panel.refresh()
|
|
861
858
|
|
|
862
|
-
def _create_new_panel(self, job: dict, container) -> None:
|
|
859
|
+
def _create_new_panel(self, job: dict[str, t.Any], container: t.Any) -> None:
|
|
863
860
|
job_panel = JobPanel(job)
|
|
864
861
|
self.active_jobs[job["job_id"]] = job_panel
|
|
865
862
|
container.mount(job_panel)
|
|
866
863
|
|
|
867
|
-
def _handle_placeholder_visibility(self, container) -> None:
|
|
864
|
+
def _handle_placeholder_visibility(self, container: t.Any) -> None:
|
|
868
865
|
has_placeholder = bool(container.query("#no - jobs-label"))
|
|
869
866
|
|
|
870
867
|
if not self.active_jobs and not has_placeholder:
|
|
@@ -877,7 +874,7 @@ class CrackerjackDashboard(App):
|
|
|
877
874
|
elif self.active_jobs and has_placeholder:
|
|
878
875
|
container.query("#no - jobs-label").remove()
|
|
879
876
|
|
|
880
|
-
def _update_status_bars(self, jobs_data: dict) -> None:
|
|
877
|
+
def _update_status_bars(self, jobs_data: dict[str, t.Any]) -> None:
|
|
881
878
|
pass
|
|
882
879
|
|
|
883
880
|
def action_refresh(self) -> None:
|
|
@@ -908,7 +905,7 @@ class CrackerjackDashboard(App):
|
|
|
908
905
|
def _restore_terminal_fallback(self) -> None:
|
|
909
906
|
self.terminal_restorer.restore_terminal()
|
|
910
907
|
|
|
911
|
-
def _signal_handler(self, _signum, _frame) -> None:
|
|
908
|
+
def _signal_handler(self, _signum: t.Any, _frame: t.Any) -> None:
|
|
912
909
|
with suppress(Exception):
|
|
913
910
|
self._restore_terminal()
|
|
914
911
|
self._cleanup_started_services()
|
|
@@ -941,18 +938,18 @@ class JobMetrics:
|
|
|
941
938
|
self.completion_time: float | None = None
|
|
942
939
|
|
|
943
940
|
self.iteration = 0
|
|
944
|
-
self.max_iterations =
|
|
941
|
+
self.max_iterations = 5
|
|
945
942
|
self.current_stage = "Initializing"
|
|
946
943
|
self.status = "running"
|
|
947
944
|
self.message = ""
|
|
948
945
|
|
|
949
|
-
self.stages_completed = set()
|
|
950
|
-
self.stages_failed = set()
|
|
946
|
+
self.stages_completed: set[str] = set()
|
|
947
|
+
self.stages_failed: set[str] = set()
|
|
951
948
|
|
|
952
|
-
self.errors = []
|
|
953
|
-
self.warnings = []
|
|
954
|
-
self.hook_failures = []
|
|
955
|
-
self.test_failures = []
|
|
949
|
+
self.errors: list[str] = []
|
|
950
|
+
self.warnings: list[str] = []
|
|
951
|
+
self.hook_failures: list[str] = []
|
|
952
|
+
self.test_failures: list[str] = []
|
|
956
953
|
|
|
957
954
|
|
|
958
955
|
async def run_progress_monitor(
|
crackerjack/mcp/rate_limiter.py
CHANGED
|
@@ -35,10 +35,10 @@ class RateLimiter:
|
|
|
35
35
|
self.requests_per_hour = requests_per_hour
|
|
36
36
|
|
|
37
37
|
self.minute_windows: dict[str, deque[float]] = defaultdict(
|
|
38
|
-
lambda: deque(maxlen=requests_per_minute),
|
|
38
|
+
lambda: deque(maxlen=requests_per_minute), # type: ignore[misc]
|
|
39
39
|
)
|
|
40
40
|
self.hour_windows: dict[str, deque[float]] = defaultdict(
|
|
41
|
-
lambda: deque(maxlen=requests_per_hour),
|
|
41
|
+
lambda: deque(maxlen=requests_per_hour), # type: ignore[misc]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
self.global_minute_window: deque[float] = deque(maxlen=requests_per_minute * 10)
|
|
@@ -110,7 +110,7 @@ class RateLimiter:
|
|
|
110
110
|
self._cleanup_global_windows(minute_cutoff, hour_cutoff)
|
|
111
111
|
|
|
112
112
|
def _cleanup_client_windows(self, minute_cutoff: float, hour_cutoff: float) -> None:
|
|
113
|
-
for client_id in list(self.minute_windows.keys()):
|
|
113
|
+
for client_id in list[t.Any](self.minute_windows.keys()):
|
|
114
114
|
minute_window = self.minute_windows[client_id]
|
|
115
115
|
hour_window = self.hour_windows[client_id]
|
|
116
116
|
|
|
@@ -200,7 +200,7 @@ class ResourceMonitor:
|
|
|
200
200
|
stale_jobs = []
|
|
201
201
|
|
|
202
202
|
async with self._lock:
|
|
203
|
-
for job_id, start_time in list(self.active_jobs.items()):
|
|
203
|
+
for job_id, start_time in list[t.Any](self.active_jobs.items()):
|
|
204
204
|
if now - start_time > max_duration:
|
|
205
205
|
stale_jobs.append(job_id)
|
|
206
206
|
del self.active_jobs[job_id]
|
|
@@ -234,7 +234,7 @@ class ResourceMonitor:
|
|
|
234
234
|
if not progress_dir.exists():
|
|
235
235
|
return True
|
|
236
236
|
|
|
237
|
-
file_count = len(list(progress_dir.glob("job-* .json")))
|
|
237
|
+
file_count = len(list[t.Any](progress_dir.glob("job-* .json")))
|
|
238
238
|
if file_count > self.config.max_progress_files:
|
|
239
239
|
console.print(
|
|
240
240
|
f"[red]🚫 Progress files ({file_count}) exceed limit ({self.config.max_progress_files})[/ red]",
|
|
@@ -272,7 +272,7 @@ class RateLimitMiddleware:
|
|
|
272
272
|
)
|
|
273
273
|
self.resource_monitor = ResourceMonitor(self.config)
|
|
274
274
|
|
|
275
|
-
self._cleanup_task: asyncio.Task | None = None
|
|
275
|
+
self._cleanup_task: asyncio.Task[None] | None = None
|
|
276
276
|
self._running = False
|
|
277
277
|
|
|
278
278
|
async def start(self) -> None:
|
crackerjack/mcp/server_core.py
CHANGED
|
@@ -9,7 +9,7 @@ from rich.console import Console
|
|
|
9
9
|
try:
|
|
10
10
|
import tomli
|
|
11
11
|
except ImportError:
|
|
12
|
-
tomli = None
|
|
12
|
+
tomli = None # type: ignore[assignment]
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from fastmcp import FastMCP
|
|
@@ -17,7 +17,7 @@ try:
|
|
|
17
17
|
_mcp_available = True
|
|
18
18
|
except ImportError:
|
|
19
19
|
_mcp_available = False
|
|
20
|
-
FastMCP = None
|
|
20
|
+
FastMCP = None # type: ignore[misc,assignment,no-redef]
|
|
21
21
|
|
|
22
22
|
MCP_AVAILABLE: Final[bool] = _mcp_available
|
|
23
23
|
|
|
@@ -43,7 +43,6 @@ console = Console()
|
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def _load_mcp_config(project_path: Path) -> dict[str, t.Any]:
|
|
46
|
-
"""Load MCP server configuration from pyproject.toml."""
|
|
47
46
|
pyproject_path = project_path / "pyproject.toml"
|
|
48
47
|
|
|
49
48
|
if not pyproject_path.exists() or not tomli:
|
|
@@ -228,7 +227,6 @@ def _merge_config_with_args(
|
|
|
228
227
|
http_port: int | None,
|
|
229
228
|
http_mode: bool,
|
|
230
229
|
) -> dict[str, t.Any]:
|
|
231
|
-
"""Merge MCP configuration with command line arguments."""
|
|
232
230
|
if http_port:
|
|
233
231
|
mcp_config["http_port"] = http_port
|
|
234
232
|
if http_mode:
|
|
@@ -240,7 +238,6 @@ def _setup_server_context(
|
|
|
240
238
|
project_path: Path,
|
|
241
239
|
websocket_port: int | None,
|
|
242
240
|
) -> MCPServerContext:
|
|
243
|
-
"""Set up and initialize the MCP server context."""
|
|
244
241
|
config = MCPServerConfig(
|
|
245
242
|
project_path=project_path,
|
|
246
243
|
rate_limit_config=RateLimitConfig(),
|
|
@@ -262,13 +259,12 @@ def _print_server_info(
|
|
|
262
259
|
websocket_port: int | None,
|
|
263
260
|
http_mode: bool,
|
|
264
261
|
) -> None:
|
|
265
|
-
"""Print server startup information."""
|
|
266
262
|
console.print("[green]Starting Crackerjack MCP Server...[/ green]")
|
|
267
263
|
console.print(f"Project path: {project_path}")
|
|
268
264
|
|
|
269
265
|
if mcp_config.get("http_enabled", False) or http_mode:
|
|
270
266
|
console.print(
|
|
271
|
-
f"[cyan]HTTP Mode: http
|
|
267
|
+
f"[cyan]HTTP Mode: http: //{mcp_config['http_host']}: {mcp_config['http_port']}/mcp[/ cyan]"
|
|
272
268
|
)
|
|
273
269
|
else:
|
|
274
270
|
console.print("[cyan]STDIO Mode[/ cyan]")
|
|
@@ -280,7 +276,6 @@ def _print_server_info(
|
|
|
280
276
|
def _run_mcp_server(
|
|
281
277
|
mcp_app: t.Any, mcp_config: dict[str, t.Any], http_mode: bool
|
|
282
278
|
) -> None:
|
|
283
|
-
"""Execute the MCP server with appropriate transport mode."""
|
|
284
279
|
console.print("[yellow]MCP app created, about to run...[/ yellow]")
|
|
285
280
|
|
|
286
281
|
try:
|
|
@@ -310,23 +305,32 @@ def main(
|
|
|
310
305
|
try:
|
|
311
306
|
project_path = Path(project_path_arg).resolve()
|
|
312
307
|
|
|
313
|
-
# Load and merge configuration
|
|
314
308
|
mcp_config = _load_mcp_config(project_path)
|
|
315
309
|
mcp_config = _merge_config_with_args(mcp_config, http_port, http_mode)
|
|
316
310
|
|
|
317
|
-
# Set up server context
|
|
318
311
|
_setup_server_context(project_path, websocket_port)
|
|
319
312
|
|
|
320
|
-
# Create MCP server
|
|
321
313
|
mcp_app = create_mcp_server(mcp_config)
|
|
322
314
|
if not mcp_app:
|
|
323
315
|
console.print("[red]Failed to create MCP server[/ red]")
|
|
324
316
|
return
|
|
325
317
|
|
|
326
|
-
# Print server information
|
|
327
318
|
_print_server_info(project_path, mcp_config, websocket_port, http_mode)
|
|
328
319
|
|
|
329
|
-
#
|
|
320
|
+
# Auto-start WebSocket server if websocket_port is specified
|
|
321
|
+
if websocket_port:
|
|
322
|
+
import asyncio
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
asyncio.run(_start_websocket_server())
|
|
326
|
+
console.print(
|
|
327
|
+
f"[green]✅ WebSocket server auto-started on port {websocket_port}[/green]"
|
|
328
|
+
)
|
|
329
|
+
except Exception as e:
|
|
330
|
+
console.print(
|
|
331
|
+
f"[yellow]⚠️ WebSocket server auto-start failed: {e}[/yellow]"
|
|
332
|
+
)
|
|
333
|
+
|
|
330
334
|
_run_mcp_server(mcp_app, mcp_config, http_mode)
|
|
331
335
|
|
|
332
336
|
except KeyboardInterrupt:
|
|
@@ -344,20 +348,17 @@ def main(
|
|
|
344
348
|
if __name__ == "__main__":
|
|
345
349
|
import sys
|
|
346
350
|
|
|
347
|
-
# Initialize defaults
|
|
348
351
|
project_path = "."
|
|
349
352
|
websocket_port = None
|
|
350
353
|
http_mode = "--http" in sys.argv
|
|
351
354
|
http_port = None
|
|
352
355
|
|
|
353
|
-
# Parse project path from non-flag arguments
|
|
354
356
|
non_flag_args = [arg for arg in sys.argv[1:] if not arg.startswith("--")]
|
|
355
357
|
if non_flag_args:
|
|
356
358
|
project_path = non_flag_args[0]
|
|
357
359
|
if len(non_flag_args) > 1 and non_flag_args[1].isdigit():
|
|
358
360
|
websocket_port = int(non_flag_args[1])
|
|
359
361
|
|
|
360
|
-
# Parse HTTP port flag
|
|
361
362
|
if "--http-port" in sys.argv:
|
|
362
363
|
port_idx = sys.argv.index("--http-port")
|
|
363
364
|
if port_idx + 1 < len(sys.argv):
|
|
@@ -34,11 +34,12 @@ class ServiceConfig:
|
|
|
34
34
|
self.max_restarts = max_restarts
|
|
35
35
|
self.restart_window = restart_window
|
|
36
36
|
|
|
37
|
-
self.process: subprocess.Popen | None = None
|
|
37
|
+
self.process: subprocess.Popen[bytes] | None = None
|
|
38
38
|
self.restart_count = 0
|
|
39
39
|
self.restart_timestamps: list[float] = []
|
|
40
40
|
self.last_health_check = 0.0
|
|
41
41
|
self.is_healthy = False
|
|
42
|
+
self._port_acknowledged = False
|
|
42
43
|
self.last_error: str | None = None
|
|
43
44
|
|
|
44
45
|
|
crackerjack/mcp/state.py
CHANGED
|
@@ -92,7 +92,9 @@ class SessionState:
|
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
class StateManager:
|
|
95
|
-
def __init__(
|
|
95
|
+
def __init__(
|
|
96
|
+
self, state_dir: Path | None = None, batched_saver: t.Any | None = None
|
|
97
|
+
) -> None:
|
|
96
98
|
self._lock = asyncio.Lock()
|
|
97
99
|
self.state_dir = state_dir or Path.home() / ".cache" / "crackerjack-mcp"
|
|
98
100
|
self.state_dir.mkdir(exist_ok=True)
|
|
@@ -258,7 +260,7 @@ class StateManager:
|
|
|
258
260
|
priority_counts = {}
|
|
259
261
|
for priority in Priority:
|
|
260
262
|
priority_counts[priority.value] = len(self.get_issues_by_priority(priority))
|
|
261
|
-
type_counts = {}
|
|
263
|
+
type_counts: dict[str, int] = {}
|
|
262
264
|
for issue in issues:
|
|
263
265
|
type_counts[issue.type] = type_counts.get(issue.type, 0) + 1
|
|
264
266
|
stage_status = {}
|
|
@@ -351,14 +353,9 @@ class StateManager:
|
|
|
351
353
|
return checkpoints
|
|
352
354
|
|
|
353
355
|
def start_session(self) -> None:
|
|
354
|
-
"""Start or initialize a session."""
|
|
355
|
-
# Session is already initialized in __init__, this is a no-op
|
|
356
|
-
# but provided for API compatibility
|
|
357
356
|
self._save_state()
|
|
358
357
|
|
|
359
358
|
def complete_session(self) -> None:
|
|
360
|
-
"""Complete the current session."""
|
|
361
|
-
# Mark session as complete in metadata
|
|
362
359
|
if not self.session_state.metadata:
|
|
363
360
|
self.session_state.metadata = {}
|
|
364
361
|
self.session_state.metadata["status"] = "completed"
|
crackerjack/mcp/task_manager.py
CHANGED
|
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
|
|
|
14
14
|
@dataclass
|
|
15
15
|
class TaskInfo:
|
|
16
16
|
task_id: str
|
|
17
|
-
task: asyncio.Task
|
|
17
|
+
task: asyncio.Task[t.Any]
|
|
18
18
|
created_at: float
|
|
19
19
|
description: str = ""
|
|
20
20
|
timeout_seconds: float | None = None
|
|
@@ -25,7 +25,7 @@ class AsyncTaskManager:
|
|
|
25
25
|
self.max_concurrent_tasks = max_concurrent_tasks
|
|
26
26
|
self._tasks: dict[str, TaskInfo] = {}
|
|
27
27
|
self._task_semaphore = asyncio.Semaphore(max_concurrent_tasks)
|
|
28
|
-
self._cleanup_task: asyncio.Task | None = None
|
|
28
|
+
self._cleanup_task: asyncio.Task[t.Any] | None = None
|
|
29
29
|
self._running = False
|
|
30
30
|
self._lock = asyncio.Lock()
|
|
31
31
|
|
|
@@ -61,11 +61,11 @@ class AsyncTaskManager:
|
|
|
61
61
|
|
|
62
62
|
async def create_task(
|
|
63
63
|
self,
|
|
64
|
-
coro: t.Coroutine,
|
|
64
|
+
coro: t.Coroutine[t.Any, t.Any, t.Any],
|
|
65
65
|
task_id: str,
|
|
66
66
|
description: str = "",
|
|
67
67
|
timeout_seconds: float | None = None,
|
|
68
|
-
) -> asyncio.Task:
|
|
68
|
+
) -> asyncio.Task[t.Any]:
|
|
69
69
|
async with self._lock:
|
|
70
70
|
if task_id in self._tasks:
|
|
71
71
|
msg = f"Task {task_id} already exists"
|
|
@@ -100,7 +100,9 @@ class AsyncTaskManager:
|
|
|
100
100
|
console.print(f"[blue]🚀 Task {task_id} created: {description}[ / blue]")
|
|
101
101
|
return task
|
|
102
102
|
|
|
103
|
-
async def _wrap_task(
|
|
103
|
+
async def _wrap_task(
|
|
104
|
+
self, coro: t.Coroutine[t.Any, t.Any, t.Any], task_id: str
|
|
105
|
+
) -> t.Any:
|
|
104
106
|
try:
|
|
105
107
|
async with self._task_semaphore:
|
|
106
108
|
result = await coro
|
|
@@ -185,11 +187,11 @@ class AsyncTaskManager:
|
|
|
185
187
|
@asynccontextmanager
|
|
186
188
|
async def managed_task(
|
|
187
189
|
self,
|
|
188
|
-
coro: t.Coroutine,
|
|
190
|
+
coro: t.Coroutine[t.Any, t.Any, t.Any],
|
|
189
191
|
task_id: str,
|
|
190
192
|
description: str = "",
|
|
191
193
|
timeout_seconds: float | None = None,
|
|
192
|
-
):
|
|
194
|
+
) -> t.AsyncGenerator[asyncio.Task[t.Any]]:
|
|
193
195
|
task = await self.create_task(coro, task_id, description, timeout_seconds)
|
|
194
196
|
try:
|
|
195
197
|
yield task
|
|
@@ -201,7 +203,7 @@ class AsyncTaskManager:
|
|
|
201
203
|
|
|
202
204
|
async def _cancel_all_tasks(self) -> None:
|
|
203
205
|
async with self._lock:
|
|
204
|
-
tasks_to_cancel = list(self._tasks.values())
|
|
206
|
+
tasks_to_cancel = list[t.Any](self._tasks.values())
|
|
205
207
|
|
|
206
208
|
if not tasks_to_cancel:
|
|
207
209
|
return
|
|
@@ -240,7 +242,7 @@ class AsyncTaskManager:
|
|
|
240
242
|
async def _cleanup_completed_tasks(self) -> None:
|
|
241
243
|
async with self._lock:
|
|
242
244
|
completed_tasks = []
|
|
243
|
-
for task_id, task_info in list(self._tasks.items()):
|
|
245
|
+
for task_id, task_info in list[t.Any](self._tasks.items()):
|
|
244
246
|
if task_info.task.done():
|
|
245
247
|
completed_tasks.append(task_id)
|
|
246
248
|
del self._tasks[task_id]
|