crackerjack 0.31.10__py3-none-any.whl → 0.31.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/CLAUDE.md +288 -705
- crackerjack/__main__.py +22 -8
- crackerjack/agents/__init__.py +0 -3
- crackerjack/agents/architect_agent.py +0 -43
- crackerjack/agents/base.py +1 -9
- crackerjack/agents/coordinator.py +2 -148
- crackerjack/agents/documentation_agent.py +109 -81
- crackerjack/agents/dry_agent.py +122 -97
- crackerjack/agents/formatting_agent.py +3 -16
- crackerjack/agents/import_optimization_agent.py +1174 -130
- crackerjack/agents/performance_agent.py +956 -188
- crackerjack/agents/performance_helpers.py +229 -0
- crackerjack/agents/proactive_agent.py +1 -48
- crackerjack/agents/refactoring_agent.py +516 -246
- crackerjack/agents/refactoring_helpers.py +282 -0
- crackerjack/agents/security_agent.py +393 -90
- crackerjack/agents/test_creation_agent.py +1776 -120
- crackerjack/agents/test_specialist_agent.py +59 -15
- crackerjack/agents/tracker.py +0 -102
- crackerjack/api.py +145 -37
- crackerjack/cli/handlers.py +48 -30
- crackerjack/cli/interactive.py +11 -11
- crackerjack/cli/options.py +66 -4
- crackerjack/code_cleaner.py +808 -148
- crackerjack/config/global_lock_config.py +110 -0
- crackerjack/config/hooks.py +43 -64
- crackerjack/core/async_workflow_orchestrator.py +247 -97
- crackerjack/core/autofix_coordinator.py +192 -109
- crackerjack/core/enhanced_container.py +46 -63
- crackerjack/core/file_lifecycle.py +549 -0
- crackerjack/core/performance.py +9 -8
- crackerjack/core/performance_monitor.py +395 -0
- crackerjack/core/phase_coordinator.py +281 -94
- crackerjack/core/proactive_workflow.py +9 -58
- crackerjack/core/resource_manager.py +501 -0
- crackerjack/core/service_watchdog.py +490 -0
- crackerjack/core/session_coordinator.py +4 -8
- crackerjack/core/timeout_manager.py +504 -0
- crackerjack/core/websocket_lifecycle.py +475 -0
- crackerjack/core/workflow_orchestrator.py +343 -209
- crackerjack/dynamic_config.py +47 -6
- crackerjack/errors.py +3 -4
- crackerjack/executors/async_hook_executor.py +63 -13
- crackerjack/executors/cached_hook_executor.py +14 -14
- crackerjack/executors/hook_executor.py +100 -37
- crackerjack/executors/hook_lock_manager.py +856 -0
- crackerjack/executors/individual_hook_executor.py +120 -86
- crackerjack/intelligence/__init__.py +0 -7
- crackerjack/intelligence/adaptive_learning.py +13 -86
- crackerjack/intelligence/agent_orchestrator.py +15 -78
- crackerjack/intelligence/agent_registry.py +12 -59
- crackerjack/intelligence/agent_selector.py +31 -92
- crackerjack/intelligence/integration.py +1 -41
- crackerjack/interactive.py +9 -9
- crackerjack/managers/async_hook_manager.py +25 -8
- crackerjack/managers/hook_manager.py +9 -9
- crackerjack/managers/publish_manager.py +57 -59
- crackerjack/managers/test_command_builder.py +6 -36
- crackerjack/managers/test_executor.py +9 -61
- crackerjack/managers/test_manager.py +17 -63
- crackerjack/managers/test_manager_backup.py +77 -127
- crackerjack/managers/test_progress.py +4 -23
- crackerjack/mcp/cache.py +5 -12
- crackerjack/mcp/client_runner.py +10 -10
- crackerjack/mcp/context.py +64 -6
- crackerjack/mcp/dashboard.py +14 -11
- crackerjack/mcp/enhanced_progress_monitor.py +55 -55
- crackerjack/mcp/file_monitor.py +72 -42
- crackerjack/mcp/progress_components.py +103 -84
- crackerjack/mcp/progress_monitor.py +122 -49
- crackerjack/mcp/rate_limiter.py +12 -12
- crackerjack/mcp/server_core.py +16 -22
- crackerjack/mcp/service_watchdog.py +26 -26
- crackerjack/mcp/state.py +15 -0
- crackerjack/mcp/tools/core_tools.py +95 -39
- crackerjack/mcp/tools/error_analyzer.py +6 -32
- crackerjack/mcp/tools/execution_tools.py +1 -56
- crackerjack/mcp/tools/execution_tools_backup.py +35 -131
- crackerjack/mcp/tools/intelligence_tool_registry.py +0 -36
- crackerjack/mcp/tools/intelligence_tools.py +2 -55
- crackerjack/mcp/tools/monitoring_tools.py +308 -145
- crackerjack/mcp/tools/proactive_tools.py +12 -42
- crackerjack/mcp/tools/progress_tools.py +23 -15
- crackerjack/mcp/tools/utility_tools.py +3 -40
- crackerjack/mcp/tools/workflow_executor.py +40 -60
- crackerjack/mcp/websocket/app.py +0 -3
- crackerjack/mcp/websocket/endpoints.py +206 -268
- crackerjack/mcp/websocket/jobs.py +213 -66
- crackerjack/mcp/websocket/server.py +84 -6
- crackerjack/mcp/websocket/websocket_handler.py +137 -29
- crackerjack/models/config_adapter.py +3 -16
- crackerjack/models/protocols.py +162 -3
- crackerjack/models/resource_protocols.py +454 -0
- crackerjack/models/task.py +3 -3
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +25 -71
- crackerjack/monitoring/regression_prevention.py +28 -87
- crackerjack/orchestration/advanced_orchestrator.py +44 -78
- crackerjack/orchestration/coverage_improvement.py +10 -60
- crackerjack/orchestration/execution_strategies.py +16 -16
- crackerjack/orchestration/test_progress_streamer.py +61 -53
- crackerjack/plugins/base.py +1 -1
- crackerjack/plugins/managers.py +22 -20
- crackerjack/py313.py +65 -21
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +627 -0
- crackerjack/services/cache.py +7 -9
- crackerjack/services/config.py +35 -52
- crackerjack/services/config_integrity.py +5 -16
- crackerjack/services/config_merge.py +542 -0
- crackerjack/services/contextual_ai_assistant.py +17 -19
- crackerjack/services/coverage_ratchet.py +44 -73
- crackerjack/services/debug.py +25 -39
- crackerjack/services/dependency_monitor.py +52 -50
- crackerjack/services/enhanced_filesystem.py +14 -11
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/filesystem.py +1 -12
- crackerjack/services/git.py +71 -47
- crackerjack/services/health_metrics.py +31 -27
- crackerjack/services/initialization.py +276 -428
- crackerjack/services/input_validator.py +760 -0
- crackerjack/services/log_manager.py +16 -16
- crackerjack/services/logging.py +7 -6
- crackerjack/services/metrics.py +43 -43
- crackerjack/services/pattern_cache.py +2 -31
- crackerjack/services/pattern_detector.py +26 -63
- crackerjack/services/performance_benchmarks.py +20 -45
- crackerjack/services/regex_patterns.py +2887 -0
- crackerjack/services/regex_utils.py +537 -0
- crackerjack/services/secure_path_utils.py +683 -0
- crackerjack/services/secure_status_formatter.py +534 -0
- crackerjack/services/secure_subprocess.py +605 -0
- crackerjack/services/security.py +47 -10
- crackerjack/services/security_logger.py +492 -0
- crackerjack/services/server_manager.py +109 -50
- crackerjack/services/smart_scheduling.py +8 -25
- crackerjack/services/status_authentication.py +603 -0
- crackerjack/services/status_security_manager.py +442 -0
- crackerjack/services/thread_safe_status_collector.py +546 -0
- crackerjack/services/tool_version_service.py +1 -23
- crackerjack/services/unified_config.py +36 -58
- crackerjack/services/validation_rate_limiter.py +269 -0
- crackerjack/services/version_checker.py +9 -40
- crackerjack/services/websocket_resource_limiter.py +572 -0
- crackerjack/slash_commands/__init__.py +52 -2
- crackerjack/tools/__init__.py +0 -0
- crackerjack/tools/validate_input_validator_patterns.py +262 -0
- crackerjack/tools/validate_regex_patterns.py +198 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/METADATA +197 -12
- crackerjack-0.31.12.dist-info/RECORD +178 -0
- crackerjack/cli/facade.py +0 -104
- crackerjack-0.31.10.dist-info/RECORD +0 -149
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/WHEEL +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.31.10.dist-info → crackerjack-0.31.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,6 +14,8 @@ from textual.containers import Container
|
|
|
14
14
|
from textual.widget import Widget
|
|
15
15
|
from textual.widgets import DataTable, Footer, Label, ProgressBar
|
|
16
16
|
|
|
17
|
+
from crackerjack.core.timeout_manager import TimeoutStrategy, get_timeout_manager
|
|
18
|
+
|
|
17
19
|
from .progress_components import (
|
|
18
20
|
ErrorCollector,
|
|
19
21
|
JobDataCollector,
|
|
@@ -75,7 +77,7 @@ class AgentStatusPanel(Widget):
|
|
|
75
77
|
active_agents = activity.get("active_agents", [])
|
|
76
78
|
|
|
77
79
|
if not active_agents:
|
|
78
|
-
agents_table.add_row("No active agents", "
|
|
80
|
+
agents_table.add_row("No active agents", "-", "-", "-", "-")
|
|
79
81
|
return
|
|
80
82
|
|
|
81
83
|
for agent in active_agents:
|
|
@@ -88,9 +90,9 @@ class AgentStatusPanel(Widget):
|
|
|
88
90
|
|
|
89
91
|
current_issue = agent.get("current_issue", {})
|
|
90
92
|
issue_type = (
|
|
91
|
-
current_issue.get("type", "
|
|
93
|
+
current_issue.get("type", "-")
|
|
92
94
|
if current_issue
|
|
93
|
-
else agent.get("issue_type", "
|
|
95
|
+
else agent.get("issue_type", "-")
|
|
94
96
|
)
|
|
95
97
|
|
|
96
98
|
status_display = f"{self._get_status_emoji(status)} {status.title()}"
|
|
@@ -99,8 +101,8 @@ class AgentStatusPanel(Widget):
|
|
|
99
101
|
f"{emoji} {agent_type}",
|
|
100
102
|
status_display,
|
|
101
103
|
issue_type,
|
|
102
|
-
f"{confidence: .0 % }" if confidence > 0 else "
|
|
103
|
-
f"{processing_time: .1f}s" if processing_time > 0 else "
|
|
104
|
+
f"{confidence: .0 % }" if confidence > 0 else "-",
|
|
105
|
+
f"{processing_time: .1f}s" if processing_time > 0 else "-",
|
|
104
106
|
)
|
|
105
107
|
|
|
106
108
|
def _update_stats(self, data: dict) -> None:
|
|
@@ -212,7 +214,7 @@ class JobPanel(Widget):
|
|
|
212
214
|
errors_container.border_title = "❌ Errors"
|
|
213
215
|
|
|
214
216
|
errors_table = self.query_one(
|
|
215
|
-
f"#job-errors-{self.job_data.get('job_id', 'unknown')}",
|
|
217
|
+
f"#job-errors -{self.job_data.get('job_id', 'unknown')}",
|
|
216
218
|
DataTable,
|
|
217
219
|
)
|
|
218
220
|
errors_table.add_columns("", "", "", "")
|
|
@@ -222,7 +224,7 @@ class JobPanel(Widget):
|
|
|
222
224
|
def _update_errors_table(self) -> None:
|
|
223
225
|
with suppress(Exception):
|
|
224
226
|
errors_table = self.query_one(
|
|
225
|
-
f"#job-errors-{self.job_data.get('job_id', 'unknown')}",
|
|
227
|
+
f"#job-errors -{self.job_data.get('job_id', 'unknown')}",
|
|
226
228
|
DataTable,
|
|
227
229
|
)
|
|
228
230
|
errors_table.clear()
|
|
@@ -276,7 +278,7 @@ class JobPanel(Widget):
|
|
|
276
278
|
def _update_progress_bar(self) -> None:
|
|
277
279
|
with suppress(Exception):
|
|
278
280
|
progress_bar = self.query_one(
|
|
279
|
-
f"#job-progress-{self.job_data.get('job_id', 'unknown')}",
|
|
281
|
+
f"#job-progress -{self.job_data.get('job_id', 'unknown')}",
|
|
280
282
|
ProgressBar,
|
|
281
283
|
)
|
|
282
284
|
progress_value = self.iteration_count / max(self.max_iterations, 1) * 100
|
|
@@ -365,7 +367,7 @@ class JobPanel(Widget):
|
|
|
365
367
|
total=100,
|
|
366
368
|
show_eta=False,
|
|
367
369
|
show_percentage=False,
|
|
368
|
-
id=f"job-progress-{self.job_data.get('job_id', 'unknown')}",
|
|
370
|
+
id=f"job-progress -{self.job_data.get('job_id', 'unknown')}",
|
|
369
371
|
)
|
|
370
372
|
|
|
371
373
|
def _compose_stage_and_status(self) -> ComposeResult:
|
|
@@ -401,7 +403,7 @@ class JobPanel(Widget):
|
|
|
401
403
|
def _compose_errors_column(self) -> ComposeResult:
|
|
402
404
|
with Container(classes="job-errors"):
|
|
403
405
|
yield DataTable(
|
|
404
|
-
id=f"job-errors-{self.job_data.get('job_id', 'unknown')}",
|
|
406
|
+
id=f"job-errors -{self.job_data.get('job_id', 'unknown')}",
|
|
405
407
|
)
|
|
406
408
|
|
|
407
409
|
|
|
@@ -415,12 +417,13 @@ class CrackerjackDashboard(App):
|
|
|
415
417
|
|
|
416
418
|
def __init__(self) -> None:
|
|
417
419
|
super().__init__()
|
|
418
|
-
self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
|
|
420
|
+
self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack - mcp-progress"
|
|
419
421
|
self.websocket_url = "ws://localhost:8675"
|
|
420
422
|
self.refresh_timer = None
|
|
421
423
|
self.active_jobs = {}
|
|
422
424
|
self.completed_jobs_stats = {}
|
|
423
425
|
self.current_polling_method = "File"
|
|
426
|
+
self.timeout_manager = get_timeout_manager()
|
|
424
427
|
|
|
425
428
|
self.job_collector = JobDataCollector(self.progress_dir, self.websocket_url)
|
|
426
429
|
self.service_checker = ServiceHealthChecker()
|
|
@@ -442,7 +445,7 @@ class CrackerjackDashboard(App):
|
|
|
442
445
|
def _compose_left_column(self) -> ComposeResult:
|
|
443
446
|
with Container(id="left-column"):
|
|
444
447
|
yield from self._compose_jobs_panel()
|
|
445
|
-
yield AgentStatusPanel(id="agent-status-panel")
|
|
448
|
+
yield AgentStatusPanel(id="agent - status-panel")
|
|
446
449
|
|
|
447
450
|
def _compose_right_column(self) -> ComposeResult:
|
|
448
451
|
with Container(id="right-column"):
|
|
@@ -470,7 +473,7 @@ class CrackerjackDashboard(App):
|
|
|
470
473
|
|
|
471
474
|
def _compose_discovery_section(self) -> ComposeResult:
|
|
472
475
|
with Container(id="discovery-section"):
|
|
473
|
-
yield Container(id="job-discovery-container")
|
|
476
|
+
yield Container(id="job - discovery-container")
|
|
474
477
|
|
|
475
478
|
def on_mount(self) -> None:
|
|
476
479
|
self._setup_border_titles()
|
|
@@ -528,43 +531,113 @@ class CrackerjackDashboard(App):
|
|
|
528
531
|
self.refresh_timer = self.set_interval(0.5, self._refresh_data)
|
|
529
532
|
|
|
530
533
|
async def _refresh_data(self) -> None:
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
534
|
+
try:
|
|
535
|
+
# Refresh cycle with timeout protection
|
|
536
|
+
async with self.timeout_manager.timeout_context(
|
|
537
|
+
"network_operations",
|
|
538
|
+
timeout=10.0, # Total refresh timeout
|
|
539
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
540
|
+
):
|
|
541
|
+
if hasattr(self, "_refresh_counter"):
|
|
542
|
+
self._refresh_counter += 1
|
|
543
|
+
else:
|
|
544
|
+
self._refresh_counter = 0
|
|
545
|
+
|
|
546
|
+
# Ensure services running less frequently to avoid overhead
|
|
547
|
+
if self._refresh_counter % 20 == 0: # Every 10 seconds instead of 5
|
|
548
|
+
with suppress(Exception):
|
|
549
|
+
await self.timeout_manager.with_timeout(
|
|
550
|
+
"network_operations",
|
|
551
|
+
self._ensure_services_running(),
|
|
552
|
+
timeout=5.0,
|
|
553
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Collect data with individual timeouts
|
|
557
|
+
jobs_data = await self.timeout_manager.with_timeout(
|
|
558
|
+
"network_operations",
|
|
559
|
+
self._discover_jobs(),
|
|
560
|
+
timeout=3.0,
|
|
561
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
562
|
+
)
|
|
541
563
|
|
|
542
|
-
|
|
564
|
+
services_data = await self.timeout_manager.with_timeout(
|
|
565
|
+
"network_operations",
|
|
566
|
+
self._collect_services_data(),
|
|
567
|
+
timeout=2.0,
|
|
568
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
569
|
+
)
|
|
543
570
|
|
|
544
|
-
|
|
571
|
+
errors_data = await self.timeout_manager.with_timeout(
|
|
572
|
+
"file_operations",
|
|
573
|
+
self._collect_recent_errors(),
|
|
574
|
+
timeout=2.0,
|
|
575
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
576
|
+
)
|
|
545
577
|
|
|
546
|
-
|
|
547
|
-
|
|
578
|
+
# Update UI components
|
|
579
|
+
self.query_one("#services-panel").border_title = "🔧 Services"
|
|
548
580
|
|
|
549
|
-
|
|
581
|
+
self._update_jobs_table(jobs_data)
|
|
582
|
+
self._update_services_table(services_data)
|
|
583
|
+
self._update_errors_table(errors_data)
|
|
584
|
+
self._update_job_panels(jobs_data)
|
|
585
|
+
self._update_agent_panel(jobs_data)
|
|
586
|
+
self._update_status_bars(jobs_data)
|
|
550
587
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
self._update_status_bars(jobs_data)
|
|
588
|
+
except Exception as e:
|
|
589
|
+
# Log error but don't crash the dashboard
|
|
590
|
+
with suppress(Exception):
|
|
591
|
+
console = Console()
|
|
592
|
+
console.print(f"[red]Dashboard refresh error: {e}[/red]")
|
|
557
593
|
|
|
558
594
|
async def _discover_jobs(self) -> dict:
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
595
|
+
try:
|
|
596
|
+
result = await self.timeout_manager.with_timeout(
|
|
597
|
+
"network_operations",
|
|
598
|
+
self.job_collector.discover_jobs(),
|
|
599
|
+
timeout=5.0,
|
|
600
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
601
|
+
)
|
|
602
|
+
self.current_polling_method = result["method"]
|
|
603
|
+
return result["data"]
|
|
604
|
+
except Exception:
|
|
605
|
+
# Return empty data structure on timeout or error
|
|
606
|
+
return {
|
|
607
|
+
"active": 0,
|
|
608
|
+
"completed": 0,
|
|
609
|
+
"failed": 0,
|
|
610
|
+
"total": 0,
|
|
611
|
+
"individual_jobs": [],
|
|
612
|
+
"total_issues": 0,
|
|
613
|
+
"errors_fixed": 0,
|
|
614
|
+
"errors_failed": 0,
|
|
615
|
+
"current_errors": 0,
|
|
616
|
+
}
|
|
562
617
|
|
|
563
618
|
async def _collect_services_data(self) -> list:
|
|
564
|
-
|
|
619
|
+
try:
|
|
620
|
+
return await self.timeout_manager.with_timeout(
|
|
621
|
+
"network_operations",
|
|
622
|
+
self.service_checker.collect_services_data(),
|
|
623
|
+
timeout=3.0,
|
|
624
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
625
|
+
)
|
|
626
|
+
except Exception:
|
|
627
|
+
# Return minimal service data on timeout
|
|
628
|
+
return [("Services", "🔴 Timeout", "0")]
|
|
565
629
|
|
|
566
630
|
async def _collect_recent_errors(self) -> list:
|
|
567
|
-
|
|
631
|
+
try:
|
|
632
|
+
return await self.timeout_manager.with_timeout(
|
|
633
|
+
"file_operations",
|
|
634
|
+
self.error_collector.collect_recent_errors(),
|
|
635
|
+
timeout=2.0,
|
|
636
|
+
strategy=TimeoutStrategy.FAIL_FAST,
|
|
637
|
+
)
|
|
638
|
+
except Exception:
|
|
639
|
+
# Return empty error list on timeout
|
|
640
|
+
return []
|
|
568
641
|
|
|
569
642
|
def _update_jobs_table(self, jobs_data: dict) -> None:
|
|
570
643
|
with suppress(Exception):
|
|
@@ -696,7 +769,7 @@ class CrackerjackDashboard(App):
|
|
|
696
769
|
|
|
697
770
|
def _update_agent_panel(self, jobs_data: dict) -> None:
|
|
698
771
|
with suppress(Exception):
|
|
699
|
-
agent_panel = self.query_one("#agent-status-panel", AgentStatusPanel)
|
|
772
|
+
agent_panel = self.query_one("#agent - status-panel", AgentStatusPanel)
|
|
700
773
|
|
|
701
774
|
agent_data = {}
|
|
702
775
|
for job in jobs_data.get("individual_jobs", []):
|
|
@@ -709,7 +782,7 @@ class CrackerjackDashboard(App):
|
|
|
709
782
|
|
|
710
783
|
def _update_job_panels(self, jobs_data: dict) -> None:
|
|
711
784
|
with suppress(Exception):
|
|
712
|
-
container = self.query_one("#job-discovery-container")
|
|
785
|
+
container = self.query_one("#job - discovery-container")
|
|
713
786
|
current_job_ids = self._get_current_job_ids(jobs_data)
|
|
714
787
|
|
|
715
788
|
self._remove_obsolete_panels(current_job_ids)
|
|
@@ -792,17 +865,17 @@ class CrackerjackDashboard(App):
|
|
|
792
865
|
container.mount(job_panel)
|
|
793
866
|
|
|
794
867
|
def _handle_placeholder_visibility(self, container) -> None:
|
|
795
|
-
has_placeholder = bool(container.query("#no-jobs-label"))
|
|
868
|
+
has_placeholder = bool(container.query("#no - jobs-label"))
|
|
796
869
|
|
|
797
870
|
if not self.active_jobs and not has_placeholder:
|
|
798
871
|
container.mount(
|
|
799
872
|
Label(
|
|
800
873
|
"No active jobs detected. Start a Crackerjack job to see progress here.",
|
|
801
|
-
id="no-jobs-label",
|
|
874
|
+
id="no - jobs-label",
|
|
802
875
|
),
|
|
803
876
|
)
|
|
804
877
|
elif self.active_jobs and has_placeholder:
|
|
805
|
-
container.query("#no-jobs-label").remove()
|
|
878
|
+
container.query("#no - jobs-label").remove()
|
|
806
879
|
|
|
807
880
|
def _update_status_bars(self, jobs_data: dict) -> None:
|
|
808
881
|
pass
|
|
@@ -816,7 +889,7 @@ class CrackerjackDashboard(App):
|
|
|
816
889
|
table = self.query_one(table_id, DataTable)
|
|
817
890
|
table.clear()
|
|
818
891
|
|
|
819
|
-
container = self.query_one("#job-discovery-container")
|
|
892
|
+
container = self.query_one("#job - discovery-container")
|
|
820
893
|
container.query("JobPanel").remove()
|
|
821
894
|
container.query("Label").remove()
|
|
822
895
|
self.active_jobs.clear()
|
|
@@ -889,14 +962,14 @@ async def run_progress_monitor(
|
|
|
889
962
|
with suppress(Exception):
|
|
890
963
|
console = Console()
|
|
891
964
|
console.print(
|
|
892
|
-
"[bold green]🚀 Starting Crackerjack Progress Monitor[/bold green]",
|
|
965
|
+
"[bold green]🚀 Starting Crackerjack Progress Monitor[/ bold green]",
|
|
893
966
|
)
|
|
894
967
|
|
|
895
968
|
if enable_watchdog:
|
|
896
|
-
console.print("[bold yellow]🐕 Service Watchdog: Enabled[/bold yellow]")
|
|
969
|
+
console.print("[bold yellow]🐕 Service Watchdog: Enabled[/ bold yellow]")
|
|
897
970
|
|
|
898
971
|
if dev_mode:
|
|
899
|
-
console.print("[bold cyan]🛠️
|
|
972
|
+
console.print("[bold cyan]🛠️ Development Mode: Enabled[/ bold cyan]")
|
|
900
973
|
|
|
901
974
|
app = CrackerjackDashboard()
|
|
902
975
|
|
|
@@ -912,7 +985,7 @@ async def run_crackerjack_with_progress(
|
|
|
912
985
|
with suppress(Exception):
|
|
913
986
|
console = Console()
|
|
914
987
|
console.print(
|
|
915
|
-
"[bold green]🚀 Starting Crackerjack Progress Monitor[/bold green]",
|
|
988
|
+
"[bold green]🚀 Starting Crackerjack Progress Monitor[/ bold green]",
|
|
916
989
|
)
|
|
917
990
|
|
|
918
991
|
app = CrackerjackDashboard()
|
crackerjack/mcp/rate_limiter.py
CHANGED
|
@@ -159,7 +159,7 @@ class ResourceMonitor:
|
|
|
159
159
|
and len(self.active_jobs) >= self.config.max_concurrent_jobs
|
|
160
160
|
):
|
|
161
161
|
console.print(
|
|
162
|
-
f"[yellow]🚫 Job {job_id} rejected: max concurrent jobs ({self.config.max_concurrent_jobs}) reached[/yellow]",
|
|
162
|
+
f"[yellow]🚫 Job {job_id} rejected: max concurrent jobs ({self.config.max_concurrent_jobs}) reached[/ yellow]",
|
|
163
163
|
)
|
|
164
164
|
return False
|
|
165
165
|
|
|
@@ -167,7 +167,7 @@ class ResourceMonitor:
|
|
|
167
167
|
await asyncio.wait_for(self.job_locks.acquire(), timeout=0.1)
|
|
168
168
|
except TimeoutError:
|
|
169
169
|
console.print(
|
|
170
|
-
f"[yellow]🚫 Job {job_id} rejected: max concurrent jobs ({self.config.max_concurrent_jobs}) reached[/yellow]",
|
|
170
|
+
f"[yellow]🚫 Job {job_id} rejected: max concurrent jobs ({self.config.max_concurrent_jobs}) reached[/ yellow]",
|
|
171
171
|
)
|
|
172
172
|
return False
|
|
173
173
|
|
|
@@ -175,12 +175,12 @@ class ResourceMonitor:
|
|
|
175
175
|
self.active_jobs[job_id] = time.time()
|
|
176
176
|
|
|
177
177
|
console.print(
|
|
178
|
-
f"[green]🎯 Job {job_id} acquired slot ({len(self.active_jobs)} / {self.config.max_concurrent_jobs})[/green]",
|
|
178
|
+
f"[green]🎯 Job {job_id} acquired slot ({len(self.active_jobs)} / {self.config.max_concurrent_jobs})[/ green]",
|
|
179
179
|
)
|
|
180
180
|
return True
|
|
181
181
|
|
|
182
182
|
except Exception as e:
|
|
183
|
-
console.print(f"[red]Error acquiring job slot for {job_id}: {e}[/red]")
|
|
183
|
+
console.print(f"[red]Error acquiring job slot for {job_id}: {e}[/ red]")
|
|
184
184
|
return False
|
|
185
185
|
|
|
186
186
|
async def release_job_slot(self, job_id: str) -> None:
|
|
@@ -189,7 +189,7 @@ class ResourceMonitor:
|
|
|
189
189
|
start_time = self.active_jobs.pop(job_id)
|
|
190
190
|
duration = time.time() - start_time
|
|
191
191
|
console.print(
|
|
192
|
-
f"[blue]🏁 Job {job_id} completed in {duration: .1f}s ({len(self.active_jobs)} / {self.config.max_concurrent_jobs} active)[/blue]",
|
|
192
|
+
f"[blue]🏁 Job {job_id} completed in {duration: .1f}s ({len(self.active_jobs)} / {self.config.max_concurrent_jobs} active)[/ blue]",
|
|
193
193
|
)
|
|
194
194
|
|
|
195
195
|
self.job_locks.release()
|
|
@@ -208,7 +208,7 @@ class ResourceMonitor:
|
|
|
208
208
|
|
|
209
209
|
if stale_jobs:
|
|
210
210
|
console.print(
|
|
211
|
-
f"[yellow]🧹 Cleaned up {len(stale_jobs)} stale jobs (exceeded {self.config.max_job_duration_minutes}m)[/yellow]",
|
|
211
|
+
f"[yellow]🧹 Cleaned up {len(stale_jobs)} stale jobs (exceeded {self.config.max_job_duration_minutes}m)[/ yellow]",
|
|
212
212
|
)
|
|
213
213
|
|
|
214
214
|
return len(stale_jobs)
|
|
@@ -221,7 +221,7 @@ class ResourceMonitor:
|
|
|
221
221
|
size_mb = file_path.stat().st_size / (1024 * 1024)
|
|
222
222
|
if size_mb > self.config.max_file_size_mb:
|
|
223
223
|
console.print(
|
|
224
|
-
f"[red]🚫 File {file_path} ({size_mb: .1f}MB) exceeds limit ({self.config.max_file_size_mb}MB)[/red]",
|
|
224
|
+
f"[red]🚫 File {file_path} ({size_mb: .1f}MB) exceeds limit ({self.config.max_file_size_mb}MB)[/ red]",
|
|
225
225
|
)
|
|
226
226
|
return False
|
|
227
227
|
|
|
@@ -234,10 +234,10 @@ class ResourceMonitor:
|
|
|
234
234
|
if not progress_dir.exists():
|
|
235
235
|
return True
|
|
236
236
|
|
|
237
|
-
file_count = len(list(progress_dir.glob("job
|
|
237
|
+
file_count = len(list(progress_dir.glob("job-* .json")))
|
|
238
238
|
if file_count > self.config.max_progress_files:
|
|
239
239
|
console.print(
|
|
240
|
-
f"[red]🚫 Progress files ({file_count}) exceed limit ({self.config.max_progress_files})[/red]",
|
|
240
|
+
f"[red]🚫 Progress files ({file_count}) exceed limit ({self.config.max_progress_files})[/ red]",
|
|
241
241
|
)
|
|
242
242
|
return False
|
|
243
243
|
|
|
@@ -278,7 +278,7 @@ class RateLimitMiddleware:
|
|
|
278
278
|
async def start(self) -> None:
|
|
279
279
|
self._running = True
|
|
280
280
|
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
281
|
-
console.print("[green]🛡️ Rate limiting middleware started[/green]")
|
|
281
|
+
console.print("[green]🛡️ Rate limiting middleware started[/ green]")
|
|
282
282
|
|
|
283
283
|
async def stop(self) -> None:
|
|
284
284
|
self._running = False
|
|
@@ -286,7 +286,7 @@ class RateLimitMiddleware:
|
|
|
286
286
|
self._cleanup_task.cancel()
|
|
287
287
|
with contextlib.suppress(asyncio.CancelledError):
|
|
288
288
|
await self._cleanup_task
|
|
289
|
-
console.print("[yellow]🛡️ Rate limiting middleware stopped[/yellow]")
|
|
289
|
+
console.print("[yellow]🛡️ Rate limiting middleware stopped[/ yellow]")
|
|
290
290
|
|
|
291
291
|
async def check_request_allowed(
|
|
292
292
|
self,
|
|
@@ -314,7 +314,7 @@ class RateLimitMiddleware:
|
|
|
314
314
|
except asyncio.CancelledError:
|
|
315
315
|
break
|
|
316
316
|
except Exception as e:
|
|
317
|
-
console.print(f"[red]Error in cleanup loop: {e}[/red]")
|
|
317
|
+
console.print(f"[red]Error in cleanup loop: {e}[/ red]")
|
|
318
318
|
await asyncio.sleep(60)
|
|
319
319
|
|
|
320
320
|
def get_comprehensive_stats(self) -> dict[str, t.Any]:
|
crackerjack/mcp/server_core.py
CHANGED
|
@@ -65,9 +65,9 @@ def _validate_job_id(job_id: str) -> bool:
|
|
|
65
65
|
if len(job_id) > 50:
|
|
66
66
|
return False
|
|
67
67
|
|
|
68
|
-
import
|
|
68
|
+
from crackerjack.services.regex_patterns import is_valid_job_id
|
|
69
69
|
|
|
70
|
-
return
|
|
70
|
+
return is_valid_job_id(job_id)
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
async def _start_websocket_server() -> bool:
|
|
@@ -81,7 +81,7 @@ def create_mcp_server() -> t.Any | None:
|
|
|
81
81
|
if not MCP_AVAILABLE or FastMCP is None:
|
|
82
82
|
return None
|
|
83
83
|
|
|
84
|
-
mcp_app = FastMCP("crackerjack-mcp-server")
|
|
84
|
+
mcp_app = FastMCP("crackerjack - mcp-server")
|
|
85
85
|
|
|
86
86
|
from crackerjack.slash_commands import get_slash_command_path
|
|
87
87
|
|
|
@@ -129,39 +129,37 @@ def handle_mcp_server_command(
|
|
|
129
129
|
restart: bool = False,
|
|
130
130
|
websocket_port: int | None = None,
|
|
131
131
|
) -> None:
|
|
132
|
-
"""Handle MCP server start/stop/restart commands."""
|
|
133
132
|
if stop or restart:
|
|
134
|
-
console.print("[yellow]Stopping MCP servers...[/yellow]")
|
|
135
|
-
|
|
133
|
+
console.print("[yellow]Stopping MCP servers...[/ yellow]")
|
|
134
|
+
|
|
136
135
|
try:
|
|
137
136
|
result = subprocess.run(
|
|
138
|
-
["pkill", "-f", "crackerjack-mcp-server"],
|
|
137
|
+
["pkill", "- f", "crackerjack - mcp-server"],
|
|
139
138
|
check=False,
|
|
140
139
|
capture_output=True,
|
|
141
140
|
text=True,
|
|
142
141
|
timeout=10,
|
|
143
142
|
)
|
|
144
143
|
if result.returncode == 0:
|
|
145
|
-
console.print("[green]✅ MCP servers stopped[/green]")
|
|
144
|
+
console.print("[green]✅ MCP servers stopped[/ green]")
|
|
146
145
|
else:
|
|
147
|
-
console.print("[dim]No MCP servers were running[/dim]")
|
|
146
|
+
console.print("[dim]No MCP servers were running[/ dim]")
|
|
148
147
|
except subprocess.TimeoutExpired:
|
|
149
|
-
console.print("[red]Timeout stopping MCP servers[/red]")
|
|
148
|
+
console.print("[red]Timeout stopping MCP servers[/ red]")
|
|
150
149
|
except Exception as e:
|
|
151
|
-
console.print(f"[red]Error stopping MCP servers: {e}[/red]")
|
|
150
|
+
console.print(f"[red]Error stopping MCP servers: {e}[/ red]")
|
|
152
151
|
|
|
153
152
|
if stop:
|
|
154
153
|
return
|
|
155
154
|
|
|
156
|
-
# For restart, wait a moment before starting again
|
|
157
155
|
time.sleep(2)
|
|
158
156
|
|
|
159
157
|
if start or restart:
|
|
160
|
-
console.print("[green]Starting MCP server...[/green]")
|
|
158
|
+
console.print("[green]Starting MCP server...[/ green]")
|
|
161
159
|
try:
|
|
162
160
|
main(".", websocket_port)
|
|
163
161
|
except Exception as e:
|
|
164
|
-
console.print(f"[red]Failed to start MCP server: {e}[/red]")
|
|
162
|
+
console.print(f"[red]Failed to start MCP server: {e}[/ red]")
|
|
165
163
|
|
|
166
164
|
|
|
167
165
|
def _initialize_context(context: MCPServerContext) -> None:
|
|
@@ -174,11 +172,8 @@ def _stop_websocket_server() -> None:
|
|
|
174
172
|
from contextlib import suppress
|
|
175
173
|
|
|
176
174
|
with suppress(RuntimeError):
|
|
177
|
-
# Context not initialized, nothing to stop
|
|
178
175
|
context = get_context()
|
|
179
176
|
if context and hasattr(context, "_stop_websocket_server"):
|
|
180
|
-
# The websocket cleanup is handled asynchronously
|
|
181
|
-
# and called from the context's cleanup handlers
|
|
182
177
|
pass
|
|
183
178
|
|
|
184
179
|
|
|
@@ -197,7 +192,6 @@ def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None
|
|
|
197
192
|
context = MCPServerContext(config)
|
|
198
193
|
context.console = console
|
|
199
194
|
|
|
200
|
-
# Set custom WebSocket port if specified
|
|
201
195
|
if websocket_port:
|
|
202
196
|
context.websocket_server_port = websocket_port
|
|
203
197
|
|
|
@@ -205,19 +199,19 @@ def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None
|
|
|
205
199
|
|
|
206
200
|
mcp_app = create_mcp_server()
|
|
207
201
|
if not mcp_app:
|
|
208
|
-
console.print("[red]Failed to create MCP server[/red]")
|
|
202
|
+
console.print("[red]Failed to create MCP server[/ red]")
|
|
209
203
|
return
|
|
210
204
|
|
|
211
|
-
console.print("[green]Starting Crackerjack MCP Server...[/green]")
|
|
205
|
+
console.print("[green]Starting Crackerjack MCP Server...[/ green]")
|
|
212
206
|
console.print(f"Project path: {project_path}")
|
|
213
207
|
if websocket_port:
|
|
214
208
|
console.print(f"WebSocket port: {websocket_port}")
|
|
215
209
|
|
|
216
|
-
console.print("[yellow]MCP app created, about to run...[/yellow]")
|
|
210
|
+
console.print("[yellow]MCP app created, about to run...[/ yellow]")
|
|
217
211
|
try:
|
|
218
212
|
mcp_app.run()
|
|
219
213
|
except Exception as e:
|
|
220
|
-
console.print(f"[red]MCP run failed: {e}[/red]")
|
|
214
|
+
console.print(f"[red]MCP run failed: {e}[/ red]")
|
|
221
215
|
import traceback
|
|
222
216
|
|
|
223
217
|
traceback.print_exc()
|