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
@@ -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
 
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import time
3
+ import typing as t
3
4
  from contextlib import suppress
4
5
  from datetime import datetime
5
6
  from pathlib import Path
@@ -29,7 +30,7 @@ class MetricCard(Widget):
29
30
  value: str = " --",
30
31
  trend: str = "",
31
32
  color: str = "white",
32
- **kwargs,
33
+ **kwargs: t.Any,
33
34
  ) -> None:
34
35
  super().__init__(**kwargs)
35
36
  self.label = label
@@ -43,7 +44,7 @@ class MetricCard(Widget):
43
44
 
44
45
 
45
46
  class AgentActivityWidget(Widget):
46
- def __init__(self, **kwargs) -> None:
47
+ def __init__(self, **kwargs: t.Any) -> None:
47
48
  super().__init__(**kwargs)
48
49
  self.border_title = "🤖 AI Agent Activity"
49
50
  self.border_title_align = "left"
@@ -93,7 +94,7 @@ class AgentActivityWidget(Widget):
93
94
  table.zebra_stripes = True
94
95
  table.styles.max_height = 6
95
96
 
96
- def update_metrics(self, data: dict) -> None:
97
+ def update_metrics(self, data: dict[str, t.Any]) -> None:
97
98
  with suppress(Exception):
98
99
  activity = data.get("agent_activity", {})
99
100
  activity.get("agent_registry", {})
@@ -116,14 +117,14 @@ class AgentActivityWidget(Widget):
116
117
  self.query_one(
117
118
  "#confidence-metric",
118
119
  MetricCard,
119
- ).value = f"{avg_confidence:.0%}"
120
+ ).value = f"{avg_confidence: .0%}"
120
121
  self.query_one("#cache-hits-metric", MetricCard).value = str(cache_hits)
121
122
 
122
123
  self._update_coordinator_status(activity)
123
124
 
124
125
  self._update_agent_table(active_agents)
125
126
 
126
- def _update_coordinator_status(self, activity: dict) -> None:
127
+ def _update_coordinator_status(self, activity: dict[str, t.Any]) -> None:
127
128
  status = activity.get("coordinator_status", "idle")
128
129
  total_agents = activity.get("agent_registry", {}).get("total_agents", 0)
129
130
 
@@ -135,7 +136,7 @@ class AgentActivityWidget(Widget):
135
136
  f"{icon} Coordinator: {status.title()} ({total_agents} agents available)",
136
137
  )
137
138
 
138
- def _update_agent_table(self, agents: list) -> None:
139
+ def _update_agent_table(self, agents: list[t.Any]) -> None:
139
140
  table = self.query_one("#agents-detail-table", DataTable)
140
141
  table.clear()
141
142
 
@@ -147,8 +148,8 @@ class AgentActivityWidget(Widget):
147
148
  name = agent.get("agent_type", "Unknown")
148
149
  status = agent.get("status", "idle")
149
150
  issue_type = agent.get("issue_type", "-")
150
- confidence = f"{agent.get('confidence', 0):.0%}"
151
- time_elapsed = f"{agent.get('processing_time', 0):.1f}s"
151
+ confidence = f"{agent.get('confidence', 0): .0%}"
152
+ time_elapsed = f"{agent.get('processing_time', 0): .1f}s"
152
153
 
153
154
  status_emoji = {
154
155
  "processing": "🔄",
@@ -176,7 +177,7 @@ class AgentActivityWidget(Widget):
176
177
 
177
178
 
178
179
  class JobProgressPanel(Widget):
179
- def __init__(self, job_data: dict, **kwargs) -> None:
180
+ def __init__(self, job_data: dict[str, t.Any], **kwargs) -> None:
180
181
  super().__init__(**kwargs)
181
182
  self.job_data = job_data
182
183
  self.start_time = time.time()
@@ -198,10 +199,12 @@ class JobProgressPanel(Widget):
198
199
 
199
200
  with Horizontal():
200
201
  with Vertical(id="job-progress-section"):
201
- yield self._compose_progress_section()
202
+ for widget in self._compose_progress_section():
203
+ yield widget
202
204
 
203
205
  with Vertical(id="job-metrics-section"):
204
- yield self._compose_metrics_section()
206
+ for widget in self._compose_metrics_section():
207
+ yield widget
205
208
 
206
209
  def _compose_progress_section(self) -> ComposeResult:
207
210
  iteration = self.job_data.get("iteration", 1)
@@ -247,16 +250,16 @@ class JobProgressPanel(Widget):
247
250
  if total_issues > 0:
248
251
  success_rate = (fixed / total_issues) * 100
249
252
  yield Label(
250
- f"Success Rate: {success_rate:.1f}%",
253
+ f"Success Rate: {success_rate: .1f}%",
251
254
  classes="success-rate",
252
255
  )
253
256
 
254
257
  def _format_time(self, seconds: float) -> str:
255
258
  if seconds < 60:
256
- return f"{seconds:.0f}s"
259
+ return f"{seconds: .0f}s"
257
260
  if seconds < 3600:
258
- return f"{seconds / 60:.0f}m {seconds % 60:.0f}s"
259
- return f"{seconds / 3600:.0f}h {(seconds % 3600) / 60:.0f}m"
261
+ return f"{seconds / 60: .0f}m {seconds % 60: .0f}s"
262
+ return f"{seconds / 3600: .0f}h {(seconds % 3600) / 60: .0f}m"
260
263
 
261
264
 
262
265
  class ServiceHealthPanel(Widget):
@@ -279,7 +282,7 @@ class ServiceHealthPanel(Widget):
279
282
  )
280
283
  table.zebra_stripes = True
281
284
 
282
- def update_services(self, services: list[dict]) -> None:
285
+ def update_services(self, services: list[dict[str, t.Any]]) -> None:
283
286
  table = self.query_one("#services-table", DataTable)
284
287
  table.clear()
285
288
 
@@ -308,7 +311,7 @@ class ServiceHealthPanel(Widget):
308
311
 
309
312
  if isinstance(last_check, int | float):
310
313
  last_check_str = datetime.fromtimestamp(last_check).strftime(
311
- "%H:%M:%S",
314
+ "%H: %M: %S",
312
315
  )
313
316
  else:
314
317
  last_check_str = str(last_check)
@@ -323,12 +326,12 @@ class ServiceHealthPanel(Widget):
323
326
 
324
327
  def _format_uptime(self, seconds: float) -> str:
325
328
  if seconds < 60:
326
- return f"{seconds:.0f}s"
329
+ return f"{seconds: .0f}s"
327
330
  if seconds < 3600:
328
- return f"{seconds / 60:.0f}m"
331
+ return f"{seconds / 60: .0f}m"
329
332
  if seconds < 86400:
330
- return f"{seconds / 3600:.1f}h"
331
- return f"{seconds / 86400:.1f}d"
333
+ return f"{seconds / 3600: .1f}h"
334
+ return f"{seconds / 86400: .1f}d"
332
335
 
333
336
 
334
337
  class EnhancedCrackerjackDashboard(App):
@@ -336,7 +339,7 @@ class EnhancedCrackerjackDashboard(App):
336
339
  CSS_PATH = Path(__file__).parent / "enhanced_progress_monitor.tcss"
337
340
 
338
341
  def __init__(
339
- self, progress_dir: Path, websocket_url: str = "ws://localhost:8675"
342
+ self, progress_dir: Path, websocket_url: str = "ws: //localhost: 8675"
340
343
  ) -> None:
341
344
  super().__init__()
342
345
  self.progress_dir = progress_dir
@@ -367,7 +370,7 @@ class EnhancedCrackerjackDashboard(App):
367
370
  jobs_result = await self.data_collector.discover_jobs()
368
371
  jobs_data = jobs_result.get("data", {})
369
372
 
370
- services = self.service_manager.check_all_services()
373
+ services = self.service_manager.collect_services_data()
371
374
  self.query_one("#service-panel", ServiceHealthPanel).update_services(
372
375
  services,
373
376
  )
@@ -385,8 +388,8 @@ class EnhancedCrackerjackDashboard(App):
385
388
  except Exception as e:
386
389
  self.console.print(f"[red]Dashboard update error: {e}[/]")
387
390
 
388
- def _aggregate_agent_data(self, jobs: list[dict]) -> dict:
389
- aggregated = {
391
+ def _aggregate_agent_data(self, jobs: list[dict[str, t.Any]]) -> dict[str, t.Any]:
392
+ aggregated: dict[str, dict[str, t.Any]] = {
390
393
  "agent_activity": {
391
394
  "active_agents": [],
392
395
  "coordinator_status": "idle",
@@ -418,7 +421,7 @@ class EnhancedCrackerjackDashboard(App):
418
421
 
419
422
  return aggregated
420
423
 
421
- def _update_job_panels(self, jobs: list[dict]) -> None:
424
+ def _update_job_panels(self, jobs: list[dict[str, t.Any]]) -> None:
422
425
  container = self.query_one("#jobs-container", Container)
423
426
 
424
427
  with suppress(Exception):
@@ -445,7 +448,7 @@ class EnhancedCrackerjackDashboard(App):
445
448
 
446
449
  async def run_enhanced_progress_monitor(
447
450
  progress_dir: Path | None = None,
448
- websocket_url: str = "ws://localhost:8675",
451
+ websocket_url: str = "ws: //localhost: 8675",
449
452
  dev_mode: bool = False,
450
453
  ) -> None:
451
454
  if progress_dir is None:
@@ -474,6 +477,6 @@ if __name__ == "__main__":
474
477
  import tempfile
475
478
 
476
479
  progress_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else None
477
- websocket_url = sys.argv[2] if len(sys.argv) > 2 else "ws://localhost:8675"
480
+ websocket_url = sys.argv[2] if len(sys.argv) > 2 else "ws: //localhost: 8675"
478
481
 
479
482
  asyncio.run(run_enhanced_progress_monitor(progress_dir, websocket_url))