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
@@ -172,7 +172,7 @@ class TestManagementImpl:
172
172
  self.console.print("[yellow]⚠️[/ yellow] Coverage ratchet disabled")
173
173
 
174
174
  def get_coverage_ratchet_status(self) -> dict[str, t.Any]:
175
- return self.coverage_ratchet.get_status_report()
175
+ return self.coverage_ratchet.get_ratchet_data()
176
176
 
177
177
  def _run_test_command(
178
178
  self,
@@ -482,9 +482,8 @@ class TestManagementImpl:
482
482
  self._handle_running_test(line, progress)
483
483
 
484
484
  def _handle_collection_completion(self, line: str, progress: TestProgress) -> bool:
485
- if match := re.search(
486
- r"collected (\d +) items?", line
487
- ): # REGEX OK: parsing pytest collection output
485
+ match = re.search(r"collected (\d+) items?", line)
486
+ if match:
488
487
  progress.update(
489
488
  total_tests=int(match.group(1)),
490
489
  is_collecting=False,
@@ -728,7 +727,7 @@ class TestManagementImpl:
728
727
  import os
729
728
 
730
729
  cpu_count = os.cpu_count() or 1
731
- test_files = list(self.pkg_path.glob("tests/test_*.py"))
730
+ test_files = list[t.Any](self.pkg_path.glob("tests/test_*.py"))
732
731
  if len(test_files) < 5:
733
732
  return min(2, cpu_count)
734
733
 
@@ -737,7 +736,7 @@ class TestManagementImpl:
737
736
  def _get_test_timeout(self, options: OptionsProtocol) -> int:
738
737
  if options.test_timeout > 0:
739
738
  return options.test_timeout
740
- test_files = list(self.pkg_path.glob("tests/test_*.py"))
739
+ test_files = list[t.Any](self.pkg_path.glob("tests/test_*.py"))
741
740
  base_timeout = 300
742
741
 
743
742
  import math
@@ -1020,7 +1019,9 @@ class TestManagementImpl:
1020
1019
  test_dir = self.pkg_path / "tests"
1021
1020
  if not test_dir.exists():
1022
1021
  issues.append("tests directory not found")
1023
- test_files = list(test_dir.glob("test_ *.py")) if test_dir.exists() else []
1022
+ test_files = (
1023
+ list[t.Any](test_dir.glob("test_ *.py")) if test_dir.exists() else []
1024
+ )
1024
1025
  if not test_files:
1025
1026
  issues.append("no test files found")
1026
1027
  if issues:
@@ -1035,7 +1036,7 @@ class TestManagementImpl:
1035
1036
  test_dir = self.pkg_path / "tests"
1036
1037
  if not test_dir.exists():
1037
1038
  return {"test_files": 0, "total_tests": 0, "test_lines": 0}
1038
- test_files = list(test_dir.glob("test_ *.py"))
1039
+ test_files = list[t.Any](test_dir.glob("test_ *.py"))
1039
1040
  total_lines = 0
1040
1041
  total_tests = 0
1041
1042
  for test_file in test_files:
@@ -1070,5 +1071,5 @@ class TestManagementImpl:
1070
1071
  return None
1071
1072
 
1072
1073
  def has_tests(self) -> bool:
1073
- test_files = list(self.pkg_path.glob("tests/test_*.py"))
1074
+ test_files = list[t.Any](self.pkg_path.glob("tests/test_*.py"))
1074
1075
  return len(test_files) > 0
crackerjack/mcp/cache.py CHANGED
@@ -97,7 +97,7 @@ class ErrorCache:
97
97
  ]
98
98
 
99
99
  def get_common_patterns(self, limit: int = 20) -> list[ErrorPattern]:
100
- patterns = list(self.patterns.values())
100
+ patterns = list[t.Any](self.patterns.values())
101
101
  patterns.sort(key=lambda p: p.frequency, reverse=True)
102
102
  return patterns[:limit]
103
103
 
@@ -252,7 +252,7 @@ class ErrorCache:
252
252
  successful_fixes = sum(1 for result in self.fix_results if result.success)
253
253
  frequencies = [pattern.frequency for pattern in self.patterns.values()]
254
254
  avg_frequency = sum(frequencies) / len(frequencies) if frequencies else 0
255
- type_counts = {}
255
+ type_counts: dict[str, int] = {}
256
256
  for pattern in self.patterns.values():
257
257
  type_counts[pattern.error_type] = type_counts.get(pattern.error_type, 0) + 1
258
258
 
@@ -22,7 +22,7 @@ def is_mcp_server_running(host: str = "localhost", port: int = 5173) -> bool:
22
22
  sock.close()
23
23
 
24
24
 
25
- async def ensure_mcp_server_running() -> subprocess.Popen | None:
25
+ async def ensure_mcp_server_running() -> subprocess.Popen[bytes] | None:
26
26
  console = Console()
27
27
 
28
28
  if is_mcp_server_running():
@@ -33,7 +33,7 @@ class BatchedStateSaver:
33
33
  self._pending_saves: dict[str, t.Callable[[], None]] = {}
34
34
  self._last_save_time: dict[str, float] = {}
35
35
 
36
- self._save_task: asyncio.Task | None = None
36
+ self._save_task: asyncio.Task[None] | None = None
37
37
  self._running = False
38
38
  self._lock = asyncio.Lock()
39
39
 
@@ -85,7 +85,7 @@ class BatchedStateSaver:
85
85
  ready_saves = []
86
86
 
87
87
  async with self._lock:
88
- for save_id, last_time in list(self._last_save_time.items()):
88
+ for save_id, last_time in list[t.Any](self._last_save_time.items()):
89
89
  if now - last_time >= self.debounce_delay:
90
90
  ready_saves.append(save_id)
91
91
 
@@ -106,7 +106,7 @@ class BatchedStateSaver:
106
106
 
107
107
  async def _flush_saves(self) -> None:
108
108
  async with self._lock:
109
- save_ids = list(self._pending_saves.keys())
109
+ save_ids = list[t.Any](self._pending_saves.keys())
110
110
 
111
111
  if save_ids:
112
112
  await self._execute_saves(save_ids)
@@ -130,7 +130,6 @@ class MCPServerConfig:
130
130
  cache_dir: Path | None = None
131
131
 
132
132
  def __post_init__(self) -> None:
133
- # Validate all paths using secure path validation
134
133
  self.project_path = SecurePathValidator.validate_safe_path(self.project_path)
135
134
 
136
135
  if self.progress_dir:
@@ -149,13 +148,12 @@ class MCPServerContext:
149
148
  def __init__(self, config: MCPServerConfig) -> None:
150
149
  self.config = config
151
150
 
152
- # Resource management
153
151
  self.resource_manager = ResourceManager()
154
152
  self.network_manager = NetworkResourceManager()
155
153
  register_global_resource_manager(self.resource_manager)
156
154
 
157
155
  self.console: Console | None = None
158
- self.cli_runner = None
156
+ self.cli_runner: WorkflowOrchestrator | None = None
159
157
  self.state_manager: StateManager | None = None
160
158
  self.error_cache: ErrorCache | None = None
161
159
  self.rate_limiter: RateLimitMiddleware | None = None
@@ -174,61 +172,172 @@ class MCPServerContext:
174
172
  )
175
173
  self._websocket_process_lock = asyncio.Lock()
176
174
  self._websocket_cleanup_registered = False
177
- self._websocket_health_check_task: asyncio.Task | None = None
175
+ self._websocket_health_check_task: asyncio.Task[None] | None = None
178
176
 
179
177
  self._initialized = False
180
178
  self._startup_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
181
179
  self._shutdown_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
182
180
 
183
- async def initialize(self) -> None:
184
- if self._initialized:
185
- return
186
-
181
+ async def _auto_setup_git_working_directory(self) -> None:
182
+ """Auto-detect and setup git working directory for enhanced DX."""
187
183
  try:
188
- if self.config.stdio_mode:
189
- null_file = io.StringIO()
190
- self.console = Console(file=null_file, force_terminal=False)
191
- else:
192
- self.console = Console(force_terminal=True)
184
+ git_root = await self._detect_git_repository()
185
+ if git_root:
186
+ await self._log_git_detection(git_root)
193
187
 
194
- self.progress_dir.mkdir(exist_ok=True)
188
+ except Exception as e:
189
+ self._handle_git_setup_failure(e)
195
190
 
196
- self.cli_runner = WorkflowOrchestrator(
197
- console=self.console,
198
- pkg_path=self.config.project_path,
199
- )
191
+ async def _detect_git_repository(self) -> Path | None:
192
+ """Detect if we're in a git repository and return the root path."""
200
193
 
201
- self.state_manager = StateManager(
202
- self.config.state_dir or Path.home() / ".cache" / "crackerjack-mcp",
203
- self.batched_saver,
204
- )
194
+ current_dir = Path.cwd()
205
195
 
206
- self.error_cache = ErrorCache(
207
- self.config.cache_dir or Path.home() / ".cache" / "crackerjack-mcp",
208
- )
196
+ # Check if we're in a git repository
197
+ if not self._is_git_repository(current_dir):
198
+ return None
209
199
 
210
- self.rate_limiter = RateLimitMiddleware(self.config.rate_limit_config)
200
+ return self._get_git_root_directory(current_dir)
211
201
 
212
- await self.batched_saver.start()
202
+ def _is_git_repository(self, current_dir: Path) -> bool:
203
+ """Check if the current directory is within a git repository."""
204
+ import subprocess
213
205
 
214
- for task in self._startup_tasks:
215
- await task()
206
+ git_check = subprocess.run(
207
+ ["git", "rev-parse", "--is-inside-work-tree"],
208
+ capture_output=True,
209
+ text=True,
210
+ cwd=current_dir,
211
+ )
212
+ return git_check.returncode == 0
213
+
214
+ def _get_git_root_directory(self, current_dir: Path) -> Path | None:
215
+ """Get the git repository root directory."""
216
+ import subprocess
217
+
218
+ git_root_result = subprocess.run(
219
+ ["git", "rev-parse", "--show-toplevel"],
220
+ capture_output=True,
221
+ text=True,
222
+ cwd=current_dir,
223
+ )
224
+
225
+ if git_root_result.returncode == 0:
226
+ git_root = Path(git_root_result.stdout.strip())
227
+ return git_root if git_root.exists() else None
228
+ return None
229
+
230
+ async def _log_git_detection(self, git_root: Path) -> None:
231
+ """Log git repository detection to stderr and console."""
232
+
233
+ # Log to stderr for Claude to see
234
+ self._log_to_stderr(git_root)
235
+
236
+ # Log to console if available
237
+ self._log_to_console(git_root)
238
+
239
+ def _log_to_stderr(self, git_root: Path) -> None:
240
+ """Log git detection messages to stderr."""
241
+ import sys
242
+
243
+ print(
244
+ f"📍 Crackerjack MCP: Git repository detected at {git_root}",
245
+ file=sys.stderr,
246
+ )
247
+ print(
248
+ f"💡 Tip: Auto-setup git working directory with: git_set_working_dir('{git_root}')",
249
+ file=sys.stderr,
250
+ )
251
+
252
+ def _log_to_console(self, git_root: Path) -> None:
253
+ """Log git detection messages to console if available."""
254
+ if self.console:
255
+ self.console.print(f"🔧 Auto-detected git repository: {git_root}")
256
+ self.console.print(
257
+ f"💡 Recommend: Use `mcp__git__git_set_working_dir` with path='{git_root}'"
258
+ )
259
+
260
+ def _handle_git_setup_failure(self, error: Exception) -> None:
261
+ """Handle git setup failure with graceful fallback."""
262
+ if self.console:
263
+ self.console.print(
264
+ f"[dim]Git auto-setup failed (non-critical): {error}[/dim]"
265
+ )
266
+
267
+ async def initialize(self) -> None:
268
+ if self._initialized:
269
+ return
216
270
 
271
+ try:
272
+ await self._perform_initialization_sequence()
217
273
  self._initialized = True
218
274
 
219
275
  except Exception as e:
220
- self.cli_runner = None
221
- self.state_manager = None
222
- self.error_cache = None
223
- self.rate_limiter = None
224
- msg = f"Failed to initialize MCP server context: {e}"
225
- raise RuntimeError(msg) from e
276
+ self._handle_initialization_failure(e)
277
+
278
+ async def _perform_initialization_sequence(self) -> None:
279
+ """Perform the complete initialization sequence."""
280
+ self._setup_console()
281
+ self._setup_directories()
282
+ await self._initialize_components()
283
+ await self._finalize_initialization()
284
+
285
+ def _handle_initialization_failure(self, error: Exception) -> None:
286
+ """Handle initialization failure with cleanup and error propagation."""
287
+ self._cleanup_failed_initialization()
288
+ msg = f"Failed to initialize MCP server context: {error}"
289
+ raise RuntimeError(msg) from error
290
+
291
+ def _setup_console(self) -> None:
292
+ """Setup console based on configuration mode."""
293
+ if self.config.stdio_mode:
294
+ null_file = io.StringIO()
295
+ self.console = Console(file=null_file, force_terminal=False)
296
+ else:
297
+ self.console = Console(force_terminal=True)
298
+
299
+ def _setup_directories(self) -> None:
300
+ """Setup required directories."""
301
+ self.progress_dir.mkdir(exist_ok=True)
302
+
303
+ async def _initialize_components(self) -> None:
304
+ """Initialize all service components."""
305
+ self.cli_runner = WorkflowOrchestrator(
306
+ console=self.console,
307
+ pkg_path=self.config.project_path,
308
+ )
309
+
310
+ self.state_manager = StateManager(
311
+ self.config.state_dir or Path.home() / ".cache" / "crackerjack-mcp",
312
+ self.batched_saver,
313
+ )
314
+
315
+ self.error_cache = ErrorCache(
316
+ self.config.cache_dir or Path.home() / ".cache" / "crackerjack-mcp",
317
+ )
318
+
319
+ self.rate_limiter = RateLimitMiddleware(self.config.rate_limit_config)
320
+ await self.batched_saver.start()
321
+
322
+ async def _finalize_initialization(self) -> None:
323
+ """Complete initialization with optional setup and startup tasks."""
324
+ # Auto-setup git working directory for enhanced DX
325
+ await self._auto_setup_git_working_directory()
326
+
327
+ for task in self._startup_tasks:
328
+ await task()
329
+
330
+ def _cleanup_failed_initialization(self) -> None:
331
+ """Cleanup components after failed initialization."""
332
+ self.cli_runner = None
333
+ self.state_manager = None
334
+ self.error_cache = None
335
+ self.rate_limiter = None
226
336
 
227
337
  async def shutdown(self) -> None:
228
338
  if not self._initialized:
229
339
  return
230
340
 
231
- # Run custom shutdown tasks first
232
341
  for task in reversed(self._shutdown_tasks):
233
342
  try:
234
343
  await task()
@@ -236,24 +345,19 @@ class MCPServerContext:
236
345
  if self.console:
237
346
  self.console.print(f"[red]Error during shutdown: {e}[/red]")
238
347
 
239
- # Cancel health check task
240
348
  if self._websocket_health_check_task:
241
349
  self._websocket_health_check_task.cancel()
242
350
  with contextlib.suppress(asyncio.CancelledError):
243
351
  await self._websocket_health_check_task
244
352
  self._websocket_health_check_task = None
245
353
 
246
- # Stop WebSocket server
247
354
  await self._stop_websocket_server()
248
355
 
249
- # Stop rate limiter
250
356
  if self.rate_limiter:
251
357
  await self.rate_limiter.stop()
252
358
 
253
- # Stop batched saver
254
359
  await self.batched_saver.stop()
255
360
 
256
- # Clean up all managed resources
257
361
  try:
258
362
  await self.network_manager.cleanup_all()
259
363
  except Exception as e:
@@ -314,21 +418,31 @@ class MCPServerContext:
314
418
  if await self._check_existing_websocket_server():
315
419
  return True
316
420
 
317
- if self.console:
318
- self.console.print(
319
- f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}...",
320
- )
421
+ self._print_websocket_startup_message()
422
+ return await self._attempt_websocket_startup()
321
423
 
322
- try:
323
- await self._spawn_websocket_process()
324
- await self._register_websocket_cleanup()
325
- return await self._wait_for_websocket_startup()
424
+ def _print_websocket_startup_message(self) -> None:
425
+ """Print websocket server startup message."""
426
+ if self.console:
427
+ self.console.print(
428
+ f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}...",
429
+ )
326
430
 
327
- except Exception as e:
328
- if self.console:
329
- self.console.print(f"❌ Failed to start WebSocket server: {e}")
330
- await self._cleanup_dead_websocket_process()
331
- return False
431
+ async def _attempt_websocket_startup(self) -> bool:
432
+ """Attempt to start the websocket server with error handling."""
433
+ try:
434
+ await self._spawn_websocket_process()
435
+ await self._register_websocket_cleanup()
436
+ return await self._wait_for_websocket_startup()
437
+ except Exception as e:
438
+ await self._handle_websocket_startup_failure(e)
439
+ return False
440
+
441
+ async def _handle_websocket_startup_failure(self, error: Exception) -> None:
442
+ """Handle websocket server startup failure."""
443
+ if self.console:
444
+ self.console.print(f"❌ Failed to start WebSocket server: {error}")
445
+ await self._cleanup_dead_websocket_process()
332
446
 
333
447
  async def _check_existing_websocket_server(self) -> bool:
334
448
  if (
@@ -369,7 +483,6 @@ class MCPServerContext:
369
483
  start_new_session=True,
370
484
  )
371
485
 
372
- # Register the process with the network resource manager for automatic cleanup
373
486
  if self.websocket_server_process:
374
487
  managed_process = self.network_manager.create_subprocess(
375
488
  self.websocket_server_process, timeout=30.0
@@ -391,7 +504,10 @@ class MCPServerContext:
391
504
  for _attempt in range(max_attempts):
392
505
  await asyncio.sleep(0.5)
393
506
 
394
- if self.websocket_server_process.poll() is not None:
507
+ if (
508
+ self.websocket_server_process is not None
509
+ and self.websocket_server_process.poll() is not None
510
+ ):
395
511
  return_code = self.websocket_server_process.returncode
396
512
  if self.console:
397
513
  self.console.print(
@@ -420,7 +536,10 @@ class MCPServerContext:
420
536
  async def _cleanup_dead_websocket_process(self) -> None:
421
537
  if self.websocket_server_process:
422
538
  try:
423
- if self.websocket_server_process.poll() is None:
539
+ if (
540
+ self.websocket_server_process is not None
541
+ and self.websocket_server_process.poll() is None
542
+ ):
424
543
  self.websocket_server_process.terminate()
425
544
  try:
426
545
  self.websocket_server_process.wait(timeout=2)
@@ -457,7 +576,8 @@ class MCPServerContext:
457
576
  if self.console:
458
577
  self.console.print("🛑 Stopping WebSocket server...")
459
578
 
460
- self.websocket_server_process.terminate()
579
+ if self.websocket_server_process is not None:
580
+ self.websocket_server_process.terminate()
461
581
 
462
582
  if await self._wait_for_graceful_termination():
463
583
  return
@@ -466,7 +586,8 @@ class MCPServerContext:
466
586
 
467
587
  async def _wait_for_graceful_termination(self) -> bool:
468
588
  try:
469
- self.websocket_server_process.wait(timeout=5)
589
+ if self.websocket_server_process is not None:
590
+ self.websocket_server_process.wait(timeout=5)
470
591
  if self.console:
471
592
  self.console.print("✅ WebSocket server stopped gracefully")
472
593
  return True
@@ -477,10 +598,12 @@ class MCPServerContext:
477
598
  if self.console:
478
599
  self.console.print("⚡ Force killing unresponsive WebSocket server...")
479
600
 
480
- self.websocket_server_process.kill()
601
+ if self.websocket_server_process is not None:
602
+ self.websocket_server_process.kill()
481
603
 
482
604
  try:
483
- self.websocket_server_process.wait(timeout=2)
605
+ if self.websocket_server_process is not None:
606
+ self.websocket_server_process.wait(timeout=2)
484
607
  if self.console:
485
608
  self.console.print("💀 WebSocket server force killed")
486
609
  except subprocess.TimeoutExpired:
@@ -538,7 +661,8 @@ class MCPServerContext:
538
661
  await self._handle_unresponsive_websocket_server()
539
662
 
540
663
  async def _handle_dead_websocket_process(self) -> None:
541
- return_code = self.websocket_server_process.returncode
664
+ if self.websocket_server_process is not None:
665
+ return_code = self.websocket_server_process.returncode
542
666
  if self.console:
543
667
  self.console.print(
544
668
  f"⚠️ WebSocket server process died (exit code: {return_code}), attempting restart...",
@@ -559,7 +683,7 @@ class MCPServerContext:
559
683
  elif self.console:
560
684
  self.console.print("❌ Failed to restart WebSocket server")
561
685
 
562
- def safe_print(self, *args, **kwargs) -> None:
686
+ def safe_print(self, *args: t.Any, **kwargs: t.Any) -> None:
563
687
  if not self.config.stdio_mode and self.console:
564
688
  self.console.print(*args, **kwargs)
565
689
 
@@ -568,7 +692,6 @@ class MCPServerContext:
568
692
  msg = f"Invalid job_id: {job_id}"
569
693
  raise ValueError(msg)
570
694
 
571
- # Use secure path joining to prevent directory traversal
572
695
  return SecurePathValidator.secure_path_join(
573
696
  self.progress_dir, f"job-{job_id}.json"
574
697
  )
@@ -671,7 +794,7 @@ def get_rate_limiter() -> RateLimitMiddleware | None:
671
794
  return get_context().rate_limiter
672
795
 
673
796
 
674
- def safe_print(*args, **kwargs) -> None:
797
+ def safe_print(*args: t.Any, **kwargs: t.Any) -> None:
675
798
  get_context().safe_print(*args, **kwargs)
676
799
 
677
800
 
@@ -1,4 +1,5 @@
1
1
  import time
2
+ import typing as t
2
3
  from collections import deque
3
4
  from datetime import datetime
4
5
  from pathlib import Path
@@ -74,7 +75,7 @@ class MetricCard(Static):
74
75
  value: str = " --",
75
76
  trend: str = "",
76
77
  status: str = "",
77
- **kwargs,
78
+ **kwargs: t.Any,
78
79
  ) -> None:
79
80
  super().__init__(**kwargs)
80
81
  self.label = label
@@ -205,7 +206,7 @@ class PerformanceWidget(Static):
205
206
  }
206
207
  """
207
208
 
208
- def __init__(self, **kwargs) -> None:
209
+ def __init__(self, **kwargs: t.Any) -> None:
209
210
  super().__init__(**kwargs)
210
211
  self.cpu_history: deque[float] = deque(maxlen=50)
211
212
  self.memory_history: deque[float] = deque(maxlen=50)
@@ -307,7 +308,7 @@ class CrackerjackDashboard(App):
307
308
  }
308
309
  """
309
310
 
310
- def __init__(self, **kwargs) -> None:
311
+ def __init__(self, **kwargs: t.Any) -> None:
311
312
  super().__init__(**kwargs)
312
313
 
313
314
  self.job_collector = JobDataCollector()
@@ -463,7 +464,8 @@ class CrackerjackDashboard(App):
463
464
  "http: / / localhost: 8675 / api / jobs"
464
465
  ) as response:
465
466
  if response.status == 200:
466
- return await response.json()
467
+ json_result = await response.json()
468
+ return t.cast(dict[str, t.Any], json_result)
467
469
  return {}
468
470
  except Exception as e:
469
471
  self.log(f"Error fetching WebSocket jobs: {e}")
@@ -471,7 +473,7 @@ class CrackerjackDashboard(App):
471
473
 
472
474
  async def _collect_jobs_from_filesystem(self) -> dict[str, Any]:
473
475
  try:
474
- jobs = {}
476
+ jobs: dict[str, Any] = {}
475
477
 
476
478
  import tempfile
477
479