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.

Files changed (200) hide show
  1. crackerjack/__main__.py +1350 -34
  2. crackerjack/adapters/__init__.py +17 -0
  3. crackerjack/adapters/lsp_client.py +358 -0
  4. crackerjack/adapters/rust_tool_adapter.py +194 -0
  5. crackerjack/adapters/rust_tool_manager.py +193 -0
  6. crackerjack/adapters/skylos_adapter.py +231 -0
  7. crackerjack/adapters/zuban_adapter.py +560 -0
  8. crackerjack/agents/base.py +7 -3
  9. crackerjack/agents/coordinator.py +271 -33
  10. crackerjack/agents/documentation_agent.py +9 -15
  11. crackerjack/agents/dry_agent.py +3 -15
  12. crackerjack/agents/formatting_agent.py +1 -1
  13. crackerjack/agents/import_optimization_agent.py +36 -180
  14. crackerjack/agents/performance_agent.py +17 -98
  15. crackerjack/agents/performance_helpers.py +7 -31
  16. crackerjack/agents/proactive_agent.py +1 -3
  17. crackerjack/agents/refactoring_agent.py +16 -85
  18. crackerjack/agents/refactoring_helpers.py +7 -42
  19. crackerjack/agents/security_agent.py +9 -48
  20. crackerjack/agents/test_creation_agent.py +356 -513
  21. crackerjack/agents/test_specialist_agent.py +0 -4
  22. crackerjack/api.py +6 -25
  23. crackerjack/cli/cache_handlers.py +204 -0
  24. crackerjack/cli/cache_handlers_enhanced.py +683 -0
  25. crackerjack/cli/facade.py +100 -0
  26. crackerjack/cli/handlers.py +224 -9
  27. crackerjack/cli/interactive.py +6 -4
  28. crackerjack/cli/options.py +642 -55
  29. crackerjack/cli/utils.py +2 -1
  30. crackerjack/code_cleaner.py +58 -117
  31. crackerjack/config/global_lock_config.py +8 -48
  32. crackerjack/config/hooks.py +53 -62
  33. crackerjack/core/async_workflow_orchestrator.py +24 -34
  34. crackerjack/core/autofix_coordinator.py +3 -17
  35. crackerjack/core/enhanced_container.py +64 -6
  36. crackerjack/core/file_lifecycle.py +12 -89
  37. crackerjack/core/performance.py +2 -2
  38. crackerjack/core/performance_monitor.py +15 -55
  39. crackerjack/core/phase_coordinator.py +257 -218
  40. crackerjack/core/resource_manager.py +14 -90
  41. crackerjack/core/service_watchdog.py +62 -95
  42. crackerjack/core/session_coordinator.py +149 -0
  43. crackerjack/core/timeout_manager.py +14 -72
  44. crackerjack/core/websocket_lifecycle.py +13 -78
  45. crackerjack/core/workflow_orchestrator.py +558 -240
  46. crackerjack/docs/INDEX.md +11 -0
  47. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  48. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  49. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  50. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  51. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  52. crackerjack/documentation/__init__.py +31 -0
  53. crackerjack/documentation/ai_templates.py +756 -0
  54. crackerjack/documentation/dual_output_generator.py +765 -0
  55. crackerjack/documentation/mkdocs_integration.py +518 -0
  56. crackerjack/documentation/reference_generator.py +977 -0
  57. crackerjack/dynamic_config.py +55 -50
  58. crackerjack/executors/async_hook_executor.py +10 -15
  59. crackerjack/executors/cached_hook_executor.py +117 -43
  60. crackerjack/executors/hook_executor.py +8 -34
  61. crackerjack/executors/hook_lock_manager.py +26 -183
  62. crackerjack/executors/individual_hook_executor.py +13 -11
  63. crackerjack/executors/lsp_aware_hook_executor.py +270 -0
  64. crackerjack/executors/tool_proxy.py +417 -0
  65. crackerjack/hooks/lsp_hook.py +79 -0
  66. crackerjack/intelligence/adaptive_learning.py +25 -10
  67. crackerjack/intelligence/agent_orchestrator.py +2 -5
  68. crackerjack/intelligence/agent_registry.py +34 -24
  69. crackerjack/intelligence/agent_selector.py +5 -7
  70. crackerjack/interactive.py +17 -6
  71. crackerjack/managers/async_hook_manager.py +0 -1
  72. crackerjack/managers/hook_manager.py +79 -1
  73. crackerjack/managers/publish_manager.py +66 -13
  74. crackerjack/managers/test_command_builder.py +5 -17
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +109 -7
  77. crackerjack/managers/test_manager_backup.py +10 -9
  78. crackerjack/mcp/cache.py +2 -2
  79. crackerjack/mcp/client_runner.py +1 -1
  80. crackerjack/mcp/context.py +191 -68
  81. crackerjack/mcp/dashboard.py +7 -5
  82. crackerjack/mcp/enhanced_progress_monitor.py +31 -28
  83. crackerjack/mcp/file_monitor.py +30 -23
  84. crackerjack/mcp/progress_components.py +31 -21
  85. crackerjack/mcp/progress_monitor.py +50 -53
  86. crackerjack/mcp/rate_limiter.py +6 -6
  87. crackerjack/mcp/server_core.py +161 -32
  88. crackerjack/mcp/service_watchdog.py +2 -1
  89. crackerjack/mcp/state.py +4 -7
  90. crackerjack/mcp/task_manager.py +11 -9
  91. crackerjack/mcp/tools/core_tools.py +174 -33
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +15 -12
  94. crackerjack/mcp/tools/execution_tools_backup.py +42 -30
  95. crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
  96. crackerjack/mcp/tools/intelligence_tools.py +5 -2
  97. crackerjack/mcp/tools/monitoring_tools.py +33 -70
  98. crackerjack/mcp/tools/proactive_tools.py +24 -11
  99. crackerjack/mcp/tools/progress_tools.py +5 -8
  100. crackerjack/mcp/tools/utility_tools.py +20 -14
  101. crackerjack/mcp/tools/workflow_executor.py +62 -40
  102. crackerjack/mcp/websocket/app.py +8 -0
  103. crackerjack/mcp/websocket/endpoints.py +352 -357
  104. crackerjack/mcp/websocket/jobs.py +40 -57
  105. crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
  106. crackerjack/mcp/websocket/server.py +7 -25
  107. crackerjack/mcp/websocket/websocket_handler.py +6 -17
  108. crackerjack/mixins/__init__.py +3 -0
  109. crackerjack/mixins/error_handling.py +145 -0
  110. crackerjack/models/config.py +21 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +176 -107
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/models/task.py +3 -0
  115. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  116. crackerjack/monitoring/metrics_collector.py +426 -0
  117. crackerjack/monitoring/regression_prevention.py +8 -8
  118. crackerjack/monitoring/websocket_server.py +643 -0
  119. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  120. crackerjack/orchestration/coverage_improvement.py +3 -3
  121. crackerjack/orchestration/execution_strategies.py +26 -6
  122. crackerjack/orchestration/test_progress_streamer.py +8 -5
  123. crackerjack/plugins/base.py +2 -2
  124. crackerjack/plugins/hooks.py +7 -0
  125. crackerjack/plugins/managers.py +11 -8
  126. crackerjack/security/__init__.py +0 -1
  127. crackerjack/security/audit.py +90 -105
  128. crackerjack/services/anomaly_detector.py +392 -0
  129. crackerjack/services/api_extractor.py +615 -0
  130. crackerjack/services/backup_service.py +2 -2
  131. crackerjack/services/bounded_status_operations.py +15 -152
  132. crackerjack/services/cache.py +127 -1
  133. crackerjack/services/changelog_automation.py +395 -0
  134. crackerjack/services/config.py +18 -11
  135. crackerjack/services/config_merge.py +30 -85
  136. crackerjack/services/config_template.py +506 -0
  137. crackerjack/services/contextual_ai_assistant.py +48 -22
  138. crackerjack/services/coverage_badge_service.py +171 -0
  139. crackerjack/services/coverage_ratchet.py +41 -17
  140. crackerjack/services/debug.py +3 -3
  141. crackerjack/services/dependency_analyzer.py +460 -0
  142. crackerjack/services/dependency_monitor.py +14 -11
  143. crackerjack/services/documentation_generator.py +491 -0
  144. crackerjack/services/documentation_service.py +675 -0
  145. crackerjack/services/enhanced_filesystem.py +6 -5
  146. crackerjack/services/enterprise_optimizer.py +865 -0
  147. crackerjack/services/error_pattern_analyzer.py +676 -0
  148. crackerjack/services/file_hasher.py +1 -1
  149. crackerjack/services/git.py +41 -45
  150. crackerjack/services/health_metrics.py +10 -8
  151. crackerjack/services/heatmap_generator.py +735 -0
  152. crackerjack/services/initialization.py +30 -33
  153. crackerjack/services/input_validator.py +5 -97
  154. crackerjack/services/intelligent_commit.py +327 -0
  155. crackerjack/services/log_manager.py +15 -12
  156. crackerjack/services/logging.py +4 -3
  157. crackerjack/services/lsp_client.py +628 -0
  158. crackerjack/services/memory_optimizer.py +409 -0
  159. crackerjack/services/metrics.py +42 -33
  160. crackerjack/services/parallel_executor.py +416 -0
  161. crackerjack/services/pattern_cache.py +1 -1
  162. crackerjack/services/pattern_detector.py +6 -6
  163. crackerjack/services/performance_benchmarks.py +250 -576
  164. crackerjack/services/performance_cache.py +382 -0
  165. crackerjack/services/performance_monitor.py +565 -0
  166. crackerjack/services/predictive_analytics.py +510 -0
  167. crackerjack/services/quality_baseline.py +234 -0
  168. crackerjack/services/quality_baseline_enhanced.py +646 -0
  169. crackerjack/services/quality_intelligence.py +785 -0
  170. crackerjack/services/regex_patterns.py +605 -524
  171. crackerjack/services/regex_utils.py +43 -123
  172. crackerjack/services/secure_path_utils.py +5 -164
  173. crackerjack/services/secure_status_formatter.py +30 -141
  174. crackerjack/services/secure_subprocess.py +11 -92
  175. crackerjack/services/security.py +61 -30
  176. crackerjack/services/security_logger.py +18 -22
  177. crackerjack/services/server_manager.py +124 -16
  178. crackerjack/services/status_authentication.py +16 -159
  179. crackerjack/services/status_security_manager.py +4 -131
  180. crackerjack/services/terminal_utils.py +0 -0
  181. crackerjack/services/thread_safe_status_collector.py +19 -125
  182. crackerjack/services/unified_config.py +21 -13
  183. crackerjack/services/validation_rate_limiter.py +5 -54
  184. crackerjack/services/version_analyzer.py +459 -0
  185. crackerjack/services/version_checker.py +1 -1
  186. crackerjack/services/websocket_resource_limiter.py +10 -144
  187. crackerjack/services/zuban_lsp_service.py +390 -0
  188. crackerjack/slash_commands/__init__.py +2 -7
  189. crackerjack/slash_commands/run.md +2 -2
  190. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  191. crackerjack/tools/validate_regex_patterns.py +19 -48
  192. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/METADATA +197 -26
  193. crackerjack-0.33.1.dist-info/RECORD +229 -0
  194. crackerjack/CLAUDE.md +0 -207
  195. crackerjack/RULES.md +0 -380
  196. crackerjack/py313.py +0 -234
  197. crackerjack-0.32.0.dist-info/RECORD +0 -180
  198. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/WHEEL +0 -0
  199. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  200. {crackerjack-0.32.0.dist-info → crackerjack-0.33.1.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", 10)
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 - mcp-progress"
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.active_jobs = {}
424
- self.completed_jobs_stats = {}
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, # Total refresh timeout
540
+ timeout=10.0,
539
541
  strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
540
542
  ):
541
- if hasattr(self, "_refresh_counter"):
542
- self._refresh_counter += 1
543
- else:
544
- self._refresh_counter = 0
543
+ self._refresh_counter += 1
545
544
 
546
- # Ensure services running less frequently to avoid overhead
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
- return result["data"]
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
- return await self.timeout_manager.with_timeout(
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
- return await self.timeout_manager.with_timeout(
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(self, jobs_data: dict, container) -> None:
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 = 10
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(
@@ -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:
@@ -7,12 +7,17 @@ from typing import Final
7
7
  from rich.console import Console
8
8
 
9
9
  try:
10
- from mcp.server.fastmcp import FastMCP
10
+ import tomli
11
+ except ImportError:
12
+ tomli = None # type: ignore[assignment]
13
+
14
+ try:
15
+ from fastmcp import FastMCP
11
16
 
12
17
  _mcp_available = True
13
18
  except ImportError:
14
19
  _mcp_available = False
15
- FastMCP = None
20
+ FastMCP = None # type: ignore[misc,assignment,no-redef]
16
21
 
17
22
  MCP_AVAILABLE: Final[bool] = _mcp_available
18
23
 
@@ -37,6 +42,41 @@ from .tools import (
37
42
  console = Console()
38
43
 
39
44
 
45
+ def _load_mcp_config(project_path: Path) -> dict[str, t.Any]:
46
+ pyproject_path = project_path / "pyproject.toml"
47
+
48
+ if not pyproject_path.exists() or not tomli:
49
+ return {
50
+ "http_port": 8676,
51
+ "http_host": "127.0.0.1",
52
+ "websocket_port": 8675,
53
+ "http_enabled": False,
54
+ }
55
+
56
+ try:
57
+ with pyproject_path.open("rb") as f:
58
+ pyproject_data = tomli.load(f)
59
+
60
+ crackerjack_config = pyproject_data.get("tool", {}).get("crackerjack", {})
61
+
62
+ return {
63
+ "http_port": crackerjack_config.get("mcp_http_port", 8676),
64
+ "http_host": crackerjack_config.get("mcp_http_host", "127.0.0.1"),
65
+ "websocket_port": crackerjack_config.get("mcp_websocket_port", 8675),
66
+ "http_enabled": crackerjack_config.get("mcp_http_enabled", False),
67
+ }
68
+ except Exception as e:
69
+ console.print(
70
+ f"[yellow]Warning: Failed to load MCP config from pyproject.toml: {e}[/yellow]"
71
+ )
72
+ return {
73
+ "http_port": 8676,
74
+ "http_host": "127.0.0.1",
75
+ "websocket_port": 8675,
76
+ "http_enabled": False,
77
+ }
78
+
79
+
40
80
  class MCPOptions:
41
81
  def __init__(self, **kwargs: t.Any) -> None:
42
82
  self.commit: bool = False
@@ -77,11 +117,14 @@ async def _start_websocket_server() -> bool:
77
117
  return False
78
118
 
79
119
 
80
- def create_mcp_server() -> t.Any | None:
120
+ def create_mcp_server(config: dict[str, t.Any] | None = None) -> t.Any | None:
81
121
  if not MCP_AVAILABLE or FastMCP is None:
82
122
  return None
83
123
 
84
- mcp_app = FastMCP("crackerjack - mcp-server")
124
+ if config is None:
125
+ config = {"http_port": 8676, "http_host": "127.0.0.1"}
126
+
127
+ mcp_app = FastMCP("crackerjack-mcp-server", streamable_http_path="/mcp")
85
128
 
86
129
  from crackerjack.slash_commands import get_slash_command_path
87
130
 
@@ -128,6 +171,8 @@ def handle_mcp_server_command(
128
171
  stop: bool = False,
129
172
  restart: bool = False,
130
173
  websocket_port: int | None = None,
174
+ http_mode: bool = False,
175
+ http_port: int | None = None,
131
176
  ) -> None:
132
177
  if stop or restart:
133
178
  console.print("[yellow]Stopping MCP servers...[/ yellow]")
@@ -157,7 +202,7 @@ def handle_mcp_server_command(
157
202
  if start or restart:
158
203
  console.print("[green]Starting MCP server...[/ green]")
159
204
  try:
160
- main(".", websocket_port)
205
+ main(".", websocket_port, http_mode, http_port)
161
206
  except Exception as e:
162
207
  console.print(f"[red]Failed to start MCP server: {e}[/ red]")
163
208
 
@@ -177,45 +222,116 @@ def _stop_websocket_server() -> None:
177
222
  pass
178
223
 
179
224
 
180
- def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None:
225
+ def _merge_config_with_args(
226
+ mcp_config: dict[str, t.Any],
227
+ http_port: int | None,
228
+ http_mode: bool,
229
+ ) -> dict[str, t.Any]:
230
+ if http_port:
231
+ mcp_config["http_port"] = http_port
232
+ if http_mode:
233
+ mcp_config["http_enabled"] = True
234
+ return mcp_config
235
+
236
+
237
+ def _setup_server_context(
238
+ project_path: Path,
239
+ websocket_port: int | None,
240
+ ) -> MCPServerContext:
241
+ config = MCPServerConfig(
242
+ project_path=project_path,
243
+ rate_limit_config=RateLimitConfig(),
244
+ )
245
+
246
+ context = MCPServerContext(config)
247
+ context.console = console
248
+
249
+ if websocket_port:
250
+ context.websocket_server_port = websocket_port
251
+
252
+ _initialize_context(context)
253
+ return context
254
+
255
+
256
+ def _print_server_info(
257
+ project_path: Path,
258
+ mcp_config: dict[str, t.Any],
259
+ websocket_port: int | None,
260
+ http_mode: bool,
261
+ ) -> None:
262
+ console.print("[green]Starting Crackerjack MCP Server...[/ green]")
263
+ console.print(f"Project path: {project_path}")
264
+
265
+ if mcp_config.get("http_enabled", False) or http_mode:
266
+ console.print(
267
+ f"[cyan]HTTP Mode: http: //{mcp_config['http_host']}: {mcp_config['http_port']}/mcp[/ cyan]"
268
+ )
269
+ else:
270
+ console.print("[cyan]STDIO Mode[/ cyan]")
271
+
272
+ if websocket_port:
273
+ console.print(f"WebSocket port: {websocket_port}")
274
+
275
+
276
+ def _run_mcp_server(
277
+ mcp_app: t.Any, mcp_config: dict[str, t.Any], http_mode: bool
278
+ ) -> None:
279
+ console.print("[yellow]MCP app created, about to run...[/ yellow]")
280
+
281
+ try:
282
+ if mcp_config.get("http_enabled", False) or http_mode:
283
+ host = mcp_config.get("http_host", "127.0.0.1")
284
+ port = mcp_config.get("http_port", 8676)
285
+ mcp_app.run(transport="streamable-http", host=host, port=port)
286
+ else:
287
+ mcp_app.run()
288
+ except Exception as e:
289
+ console.print(f"[red]MCP run failed: {e}[/ red]")
290
+ import traceback
291
+
292
+ traceback.print_exc()
293
+ raise
294
+
295
+
296
+ def main(
297
+ project_path_arg: str = ".",
298
+ websocket_port: int | None = None,
299
+ http_mode: bool = False,
300
+ http_port: int | None = None,
301
+ ) -> None:
181
302
  if not MCP_AVAILABLE:
182
303
  return
183
304
 
184
305
  try:
185
306
  project_path = Path(project_path_arg).resolve()
186
307
 
187
- config = MCPServerConfig(
188
- project_path=project_path,
189
- rate_limit_config=RateLimitConfig(),
190
- )
191
-
192
- context = MCPServerContext(config)
193
- context.console = console
194
-
195
- if websocket_port:
196
- context.websocket_server_port = websocket_port
308
+ mcp_config = _load_mcp_config(project_path)
309
+ mcp_config = _merge_config_with_args(mcp_config, http_port, http_mode)
197
310
 
198
- _initialize_context(context)
311
+ _setup_server_context(project_path, websocket_port)
199
312
 
200
- mcp_app = create_mcp_server()
313
+ mcp_app = create_mcp_server(mcp_config)
201
314
  if not mcp_app:
202
315
  console.print("[red]Failed to create MCP server[/ red]")
203
316
  return
204
317
 
205
- console.print("[green]Starting Crackerjack MCP Server...[/ green]")
206
- console.print(f"Project path: {project_path}")
318
+ _print_server_info(project_path, mcp_config, websocket_port, http_mode)
319
+
320
+ # Auto-start WebSocket server if websocket_port is specified
207
321
  if websocket_port:
208
- console.print(f"WebSocket port: {websocket_port}")
322
+ import asyncio
209
323
 
210
- console.print("[yellow]MCP app created, about to run...[/ yellow]")
211
- try:
212
- mcp_app.run()
213
- except Exception as e:
214
- console.print(f"[red]MCP run failed: {e}[/ red]")
215
- import traceback
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
+ )
216
333
 
217
- traceback.print_exc()
218
- raise
334
+ _run_mcp_server(mcp_app, mcp_config, http_mode)
219
335
 
220
336
  except KeyboardInterrupt:
221
337
  console.print("Server stopped by user")
@@ -232,7 +348,20 @@ def main(project_path_arg: str = ".", websocket_port: int | None = None) -> None
232
348
  if __name__ == "__main__":
233
349
  import sys
234
350
 
235
- project_path = sys.argv[1] if len(sys.argv) > 1 else "."
236
- websocket_port = int(sys.argv[2]) if len(sys.argv) > 2 else None
351
+ project_path = "."
352
+ websocket_port = None
353
+ http_mode = "--http" in sys.argv
354
+ http_port = None
355
+
356
+ non_flag_args = [arg for arg in sys.argv[1:] if not arg.startswith("--")]
357
+ if non_flag_args:
358
+ project_path = non_flag_args[0]
359
+ if len(non_flag_args) > 1 and non_flag_args[1].isdigit():
360
+ websocket_port = int(non_flag_args[1])
361
+
362
+ if "--http-port" in sys.argv:
363
+ port_idx = sys.argv.index("--http-port")
364
+ if port_idx + 1 < len(sys.argv):
365
+ http_port = int(sys.argv[port_idx + 1])
237
366
 
238
- main(project_path, websocket_port)
367
+ main(project_path, websocket_port, http_mode, http_port)
@@ -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