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.

Files changed (198) 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 +4 -13
  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 +104 -204
  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 +171 -174
  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 +44 -8
  74. crackerjack/managers/test_command_builder.py +1 -15
  75. crackerjack/managers/test_executor.py +1 -3
  76. crackerjack/managers/test_manager.py +98 -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 +17 -16
  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 +173 -32
  92. crackerjack/mcp/tools/error_analyzer.py +3 -2
  93. crackerjack/mcp/tools/execution_tools.py +8 -10
  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 +0 -2
  109. crackerjack/mixins/error_handling.py +1 -70
  110. crackerjack/models/config.py +12 -1
  111. crackerjack/models/config_adapter.py +49 -1
  112. crackerjack/models/protocols.py +122 -122
  113. crackerjack/models/resource_protocols.py +55 -210
  114. crackerjack/monitoring/ai_agent_watchdog.py +13 -13
  115. crackerjack/monitoring/metrics_collector.py +426 -0
  116. crackerjack/monitoring/regression_prevention.py +8 -8
  117. crackerjack/monitoring/websocket_server.py +643 -0
  118. crackerjack/orchestration/advanced_orchestrator.py +11 -6
  119. crackerjack/orchestration/coverage_improvement.py +3 -3
  120. crackerjack/orchestration/execution_strategies.py +26 -6
  121. crackerjack/orchestration/test_progress_streamer.py +8 -5
  122. crackerjack/plugins/base.py +2 -2
  123. crackerjack/plugins/hooks.py +7 -0
  124. crackerjack/plugins/managers.py +11 -8
  125. crackerjack/security/__init__.py +0 -1
  126. crackerjack/security/audit.py +6 -35
  127. crackerjack/services/anomaly_detector.py +392 -0
  128. crackerjack/services/api_extractor.py +615 -0
  129. crackerjack/services/backup_service.py +2 -2
  130. crackerjack/services/bounded_status_operations.py +15 -152
  131. crackerjack/services/cache.py +127 -1
  132. crackerjack/services/changelog_automation.py +395 -0
  133. crackerjack/services/config.py +15 -9
  134. crackerjack/services/config_merge.py +19 -80
  135. crackerjack/services/config_template.py +506 -0
  136. crackerjack/services/contextual_ai_assistant.py +48 -22
  137. crackerjack/services/coverage_badge_service.py +171 -0
  138. crackerjack/services/coverage_ratchet.py +27 -25
  139. crackerjack/services/debug.py +3 -3
  140. crackerjack/services/dependency_analyzer.py +460 -0
  141. crackerjack/services/dependency_monitor.py +14 -11
  142. crackerjack/services/documentation_generator.py +491 -0
  143. crackerjack/services/documentation_service.py +675 -0
  144. crackerjack/services/enhanced_filesystem.py +6 -5
  145. crackerjack/services/enterprise_optimizer.py +865 -0
  146. crackerjack/services/error_pattern_analyzer.py +676 -0
  147. crackerjack/services/file_hasher.py +1 -1
  148. crackerjack/services/git.py +8 -25
  149. crackerjack/services/health_metrics.py +10 -8
  150. crackerjack/services/heatmap_generator.py +735 -0
  151. crackerjack/services/initialization.py +11 -30
  152. crackerjack/services/input_validator.py +5 -97
  153. crackerjack/services/intelligent_commit.py +327 -0
  154. crackerjack/services/log_manager.py +15 -12
  155. crackerjack/services/logging.py +4 -3
  156. crackerjack/services/lsp_client.py +628 -0
  157. crackerjack/services/memory_optimizer.py +19 -87
  158. crackerjack/services/metrics.py +42 -33
  159. crackerjack/services/parallel_executor.py +9 -67
  160. crackerjack/services/pattern_cache.py +1 -1
  161. crackerjack/services/pattern_detector.py +6 -6
  162. crackerjack/services/performance_benchmarks.py +18 -59
  163. crackerjack/services/performance_cache.py +20 -81
  164. crackerjack/services/performance_monitor.py +27 -95
  165. crackerjack/services/predictive_analytics.py +510 -0
  166. crackerjack/services/quality_baseline.py +234 -0
  167. crackerjack/services/quality_baseline_enhanced.py +646 -0
  168. crackerjack/services/quality_intelligence.py +785 -0
  169. crackerjack/services/regex_patterns.py +618 -524
  170. crackerjack/services/regex_utils.py +43 -123
  171. crackerjack/services/secure_path_utils.py +5 -164
  172. crackerjack/services/secure_status_formatter.py +30 -141
  173. crackerjack/services/secure_subprocess.py +11 -92
  174. crackerjack/services/security.py +9 -41
  175. crackerjack/services/security_logger.py +12 -24
  176. crackerjack/services/server_manager.py +124 -16
  177. crackerjack/services/status_authentication.py +16 -159
  178. crackerjack/services/status_security_manager.py +4 -131
  179. crackerjack/services/thread_safe_status_collector.py +19 -125
  180. crackerjack/services/unified_config.py +21 -13
  181. crackerjack/services/validation_rate_limiter.py +5 -54
  182. crackerjack/services/version_analyzer.py +459 -0
  183. crackerjack/services/version_checker.py +1 -1
  184. crackerjack/services/websocket_resource_limiter.py +10 -144
  185. crackerjack/services/zuban_lsp_service.py +390 -0
  186. crackerjack/slash_commands/__init__.py +2 -7
  187. crackerjack/slash_commands/run.md +2 -2
  188. crackerjack/tools/validate_input_validator_patterns.py +14 -40
  189. crackerjack/tools/validate_regex_patterns.py +19 -48
  190. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.2.dist-info/RECORD +229 -0
  192. crackerjack/CLAUDE.md +0 -207
  193. crackerjack/RULES.md +0 -380
  194. crackerjack/py313.py +0 -234
  195. crackerjack-0.33.0.dist-info/RECORD +0 -187
  196. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
  198. {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", 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:
@@ -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://{mcp_config['http_host']}:{mcp_config['http_port']}/mcp[/ cyan]"
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
- # Run the server
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__(self, state_dir: Path | None = None, batched_saver=None) -> None:
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"
@@ -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(self, coro: t.Coroutine, task_id: str) -> t.Any:
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]