crackerjack 0.33.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 (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 +605 -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.1.dist-info}/METADATA +196 -25
  191. crackerjack-0.33.1.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.1.dist-info}/WHEEL +0 -0
  197. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/entry_points.txt +0 -0
  198. {crackerjack-0.33.0.dist-info → crackerjack-0.33.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,7 @@
1
1
  import asyncio
2
2
  import json
3
3
  import time
4
+ import typing as t
4
5
  from collections.abc import Callable
5
6
  from pathlib import Path
6
7
 
@@ -10,9 +11,10 @@ try:
10
11
 
11
12
  WATCHDOG_AVAILABLE = True
12
13
  except ImportError:
13
- FileSystemEvent = None
14
- FileSystemEventHandler = None
15
- Observer = None
14
+ # Type stubs for when watchdog is not available
15
+ FileSystemEvent = t.Any
16
+ FileSystemEventHandler = t.Any
17
+ Observer = t.Any
16
18
  WATCHDOG_AVAILABLE = False
17
19
 
18
20
  import contextlib
@@ -28,7 +30,7 @@ if WATCHDOG_AVAILABLE:
28
30
 
29
31
  class ProgressFileHandler(FileSystemEventHandler):
30
32
  def __init__(
31
- self, callback: Callable[[str, dict], None], progress_dir: Path
33
+ self, callback: Callable[[str, dict[str, t.Any]], None], progress_dir: Path
32
34
  ) -> None:
33
35
  super().__init__()
34
36
  self.callback = callback
@@ -43,7 +45,6 @@ if WATCHDOG_AVAILABLE:
43
45
  try:
44
46
  file_path = Path(event.src_path)
45
47
 
46
- # Validate that the file path is within our allowed progress directory
47
48
  validated_path = SecurePathValidator.validate_safe_path(
48
49
  file_path, self.progress_dir
49
50
  )
@@ -66,11 +67,9 @@ if WATCHDOG_AVAILABLE:
66
67
 
67
68
  job_id = validated_path.stem.replace("job-", "")
68
69
  except Exception:
69
- # If path validation fails, skip processing this file
70
70
  return
71
71
 
72
72
  try:
73
- # Validate file size before reading
74
73
  SecurePathValidator.validate_file_size(validated_path)
75
74
 
76
75
  with validated_path.open() as f:
@@ -88,7 +87,7 @@ if WATCHDOG_AVAILABLE:
88
87
  else:
89
88
 
90
89
  class ProgressFileHandler:
91
- def __init__(self, callback: Callable[[str, dict], None]) -> None:
90
+ def __init__(self, callback: Callable[[str, dict[str, t.Any]], None]) -> None:
92
91
  pass
93
92
 
94
93
 
@@ -96,7 +95,7 @@ class AsyncProgressMonitor:
96
95
  def __init__(self, progress_dir: Path) -> None:
97
96
  self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
98
97
  self.observer: Observer | None = None
99
- self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
98
+ self.subscribers: dict[str, set[Callable[[dict[str, t.Any]], None]]] = {}
100
99
  self._running = False
101
100
 
102
101
  self.progress_dir.mkdir(exist_ok=True)
@@ -132,14 +131,18 @@ class AsyncProgressMonitor:
132
131
 
133
132
  console.print("[yellow]📁 Stopped progress directory monitoring[/ yellow]")
134
133
 
135
- def subscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
134
+ def subscribe(
135
+ self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
136
+ ) -> None:
136
137
  if job_id not in self.subscribers:
137
138
  self.subscribers[job_id] = set()
138
139
 
139
140
  self.subscribers[job_id].add(callback)
140
141
  console.print(f"[cyan]📋 Subscribed to job updates: {job_id}[/ cyan]")
141
142
 
142
- def unsubscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
143
+ def unsubscribe(
144
+ self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
145
+ ) -> None:
143
146
  if job_id in self.subscribers:
144
147
  self.subscribers[job_id].discard(callback)
145
148
 
@@ -148,7 +151,7 @@ class AsyncProgressMonitor:
148
151
 
149
152
  console.print(f"[cyan]📋 Unsubscribed from job updates: {job_id}[/ cyan]")
150
153
 
151
- def _on_file_changed(self, job_id: str, progress_data: dict) -> None:
154
+ def _on_file_changed(self, job_id: str, progress_data: dict[str, t.Any]) -> None:
152
155
  if job_id in self.subscribers:
153
156
  for callback in self.subscribers[job_id].copy():
154
157
  try:
@@ -160,7 +163,7 @@ class AsyncProgressMonitor:
160
163
 
161
164
  self.subscribers[job_id].discard(callback)
162
165
 
163
- async def get_current_progress(self, job_id: str) -> dict | None:
166
+ async def get_current_progress(self, job_id: str) -> dict[str, t.Any] | None:
164
167
  progress_file = self.progress_dir / f"job-{job_id}.json"
165
168
 
166
169
  if not progress_file.exists():
@@ -168,7 +171,8 @@ class AsyncProgressMonitor:
168
171
 
169
172
  try:
170
173
  with progress_file.open() as f:
171
- return json.load(f)
174
+ json_result = json.load(f)
175
+ return t.cast(dict[str, t.Any] | None, json_result)
172
176
  except (json.JSONDecodeError, OSError):
173
177
  return None
174
178
 
@@ -208,9 +212,9 @@ class AsyncProgressMonitor:
208
212
  class PollingProgressMonitor:
209
213
  def __init__(self, progress_dir: Path) -> None:
210
214
  self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
211
- self.subscribers: dict[str, set[Callable[[dict], None]]] = {}
215
+ self.subscribers: dict[str, set[Callable[[dict[str, t.Any]], None]]] = {}
212
216
  self._running = False
213
- self._poll_task: asyncio.Task | None = None
217
+ self._poll_task: asyncio.Task[None] | None = None
214
218
  self._file_mtimes: dict[str, float] = {}
215
219
 
216
220
  self.progress_dir.mkdir(exist_ok=True)
@@ -252,7 +256,6 @@ class PollingProgressMonitor:
252
256
 
253
257
  for progress_file in self.progress_dir.glob("job-*.json"):
254
258
  try:
255
- # Validate file path is within our allowed directory
256
259
  validated_file = SecurePathValidator.validate_safe_path(
257
260
  progress_file, self.progress_dir
258
261
  )
@@ -267,7 +270,6 @@ class PollingProgressMonitor:
267
270
  job_id = validated_file.stem.replace("job-", "")
268
271
 
269
272
  try:
270
- # Validate file size before reading
271
273
  SecurePathValidator.validate_file_size(validated_file)
272
274
  with validated_file.open() as f:
273
275
  progress_data = json.load(f)
@@ -284,7 +286,7 @@ class PollingProgressMonitor:
284
286
 
285
287
  self._file_mtimes = current_files
286
288
 
287
- def _notify_subscribers(self, job_id: str, progress_data: dict) -> None:
289
+ def _notify_subscribers(self, job_id: str, progress_data: dict[str, t.Any]) -> None:
288
290
  if job_id in self.subscribers:
289
291
  for callback in self.subscribers[job_id].copy():
290
292
  try:
@@ -295,14 +297,18 @@ class PollingProgressMonitor:
295
297
  )
296
298
  self.subscribers[job_id].discard(callback)
297
299
 
298
- def subscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
300
+ def subscribe(
301
+ self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
302
+ ) -> None:
299
303
  if job_id not in self.subscribers:
300
304
  self.subscribers[job_id] = set()
301
305
 
302
306
  self.subscribers[job_id].add(callback)
303
307
  console.print(f"[cyan]📋 Subscribed to job updates: {job_id} (polling)[/ cyan]")
304
308
 
305
- def unsubscribe(self, job_id: str, callback: Callable[[dict], None]) -> None:
309
+ def unsubscribe(
310
+ self, job_id: str, callback: Callable[[dict[str, t.Any]], None]
311
+ ) -> None:
306
312
  if job_id in self.subscribers:
307
313
  self.subscribers[job_id].discard(callback)
308
314
 
@@ -313,7 +319,7 @@ class PollingProgressMonitor:
313
319
  f"[cyan]📋 Unsubscribed from job updates: {job_id} (polling)[/ cyan]",
314
320
  )
315
321
 
316
- async def get_current_progress(self, job_id: str) -> dict | None:
322
+ async def get_current_progress(self, job_id: str) -> dict[str, t.Any] | None:
317
323
  progress_file = self.progress_dir / f"job-{job_id}.json"
318
324
 
319
325
  if not progress_file.exists():
@@ -321,7 +327,8 @@ class PollingProgressMonitor:
321
327
 
322
328
  try:
323
329
  with progress_file.open() as f:
324
- return json.load(f)
330
+ json_result = json.load(f)
331
+ return t.cast(dict[str, t.Any] | None, json_result)
325
332
  except (json.JSONDecodeError, OSError):
326
333
  return None
327
334
 
@@ -109,7 +109,7 @@ class JobDataCollector:
109
109
  status = data.get("status", "unknown")
110
110
  stage = data.get("current_stage", "Unknown")
111
111
  iteration = data.get("iteration", 0)
112
- max_iterations = data.get("max_iterations", 10)
112
+ max_iterations = data.get("max_iterations", 5)
113
113
 
114
114
  status_emoji = {
115
115
  "running": "🚀 Running",
@@ -138,7 +138,7 @@ class JobDataCollector:
138
138
  )
139
139
 
140
140
  async def _discover_jobs_websocket(self) -> dict[str, Any]:
141
- jobs_data = {
141
+ jobs_data: dict[str, Any] = {
142
142
  "active": 0,
143
143
  "completed": 0,
144
144
  "failed": 0,
@@ -155,11 +155,13 @@ class JobDataCollector:
155
155
  with suppress(Exception):
156
156
  async with timeout_manager.timeout_context(
157
157
  "network_operations",
158
- timeout=5.0, # Short timeout for websocket discovery
158
+ timeout=5.0,
159
159
  ):
160
- websocket_base = self.websocket_url.replace("ws://", "http://").replace(
161
- "wss://",
162
- "https://",
160
+ websocket_base = self.websocket_url.replace(
161
+ "ws: //", "http: //"
162
+ ).replace(
163
+ "wss: //",
164
+ "https: //",
163
165
  )
164
166
 
165
167
  async with (
@@ -239,13 +241,13 @@ class ServiceHealthChecker:
239
241
  try:
240
242
  async with timeout_manager.timeout_context(
241
243
  "network_operations",
242
- timeout=3.0, # Quick health check timeout
244
+ timeout=3.0,
243
245
  ):
244
246
  async with (
245
247
  aiohttp.ClientSession(
246
248
  timeout=aiohttp.ClientTimeout(total=2),
247
249
  ) as session,
248
- session.get("http://localhost:8675/") as response,
250
+ session.get("http: //localhost: 8675/") as response,
249
251
  ):
250
252
  if response.status == 200:
251
253
  data = await response.json()
@@ -267,7 +269,7 @@ class ServiceHealthChecker:
267
269
  check=False,
268
270
  capture_output=True,
269
271
  text=True,
270
- timeout=5.0, # Add timeout protection
272
+ timeout=5.0,
271
273
  )
272
274
  if result.returncode == 0:
273
275
  return ("MCP Server", "🟢 Process Active", "0")
@@ -284,7 +286,7 @@ class ServiceHealthChecker:
284
286
  check=False,
285
287
  capture_output=True,
286
288
  text=True,
287
- timeout=5.0, # Add timeout protection
289
+ timeout=5.0,
288
290
  )
289
291
  if result.returncode == 0:
290
292
  return ("Service Watchdog", "🟢 Active", "0")
@@ -300,7 +302,7 @@ class ErrorCollector:
300
302
  self.console = Console()
301
303
 
302
304
  async def collect_recent_errors(self) -> list[tuple[str, str, str, str]]:
303
- errors = []
305
+ errors: list[tuple[str, str, str, str]] = []
304
306
 
305
307
  errors.extend(self._check_debug_logs())
306
308
 
@@ -320,7 +322,7 @@ class ErrorCollector:
320
322
  return errors[-5:]
321
323
 
322
324
  def _check_debug_logs(self) -> list[tuple[str, str, str, str]]:
323
- errors = []
325
+ errors: list[tuple[str, str, str, str]] = []
324
326
 
325
327
  with suppress(Exception):
326
328
  debug_log = Path(tempfile.gettempdir()) / "tui_debug.log"
@@ -333,7 +335,7 @@ class ErrorCollector:
333
335
  self,
334
336
  debug_log: Path,
335
337
  ) -> list[tuple[str, str, str, str]]:
336
- errors = []
338
+ errors: list[tuple[str, str, str, str]] = []
337
339
 
338
340
  with debug_log.open() as f:
339
341
  lines = f.readlines()[-10:]
@@ -362,7 +364,7 @@ class ErrorCollector:
362
364
  return message[:40] + "..." if len(message) > 40 else message
363
365
 
364
366
  def _check_crackerjack_logs(self) -> list[tuple[str, str, str, str]]:
365
- errors = []
367
+ errors: list[tuple[str, str, str, str]] = []
366
368
 
367
369
  with suppress(Exception):
368
370
  for log_file in Path(tempfile.gettempdir()).glob(
@@ -380,7 +382,7 @@ class ErrorCollector:
380
382
  self,
381
383
  log_file: Path,
382
384
  ) -> list[tuple[str, str, str, str]]:
383
- errors = []
385
+ errors: list[tuple[str, str, str, str]] = []
384
386
 
385
387
  with log_file.open() as f:
386
388
  lines = f.readlines()[-5:]
@@ -415,7 +417,7 @@ class ErrorCollector:
415
417
 
416
418
  class ServiceManager:
417
419
  def __init__(self) -> None:
418
- self.started_services: list[tuple[str, subprocess.Popen]] = []
420
+ self.started_services: list[tuple[str, subprocess.Popen[bytes]]] = []
419
421
  self.console = Console()
420
422
 
421
423
  async def ensure_services_running(self) -> None:
@@ -446,12 +448,12 @@ class ServiceManager:
446
448
  with suppress(Exception):
447
449
  async with timeout_manager.timeout_context(
448
450
  "network_operations",
449
- timeout=3.0, # Quick check timeout
451
+ timeout=3.0,
450
452
  ):
451
453
  async with aiohttp.ClientSession(
452
454
  timeout=aiohttp.ClientTimeout(total=2),
453
455
  ) as session:
454
- async with session.get("http://localhost:8675/") as response:
456
+ async with session.get("http: //localhost: 8675/") as response:
455
457
  return response.status == 200
456
458
  return False
457
459
 
@@ -462,11 +464,19 @@ class ServiceManager:
462
464
  check=False,
463
465
  capture_output=True,
464
466
  text=True,
465
- timeout=5.0, # Add timeout protection
467
+ timeout=5.0,
466
468
  )
467
469
  return result.returncode == 0
468
470
  return False
469
471
 
472
+ def collect_services_data(self) -> list[tuple[str, str, str]]:
473
+ """Check all services and return their status information."""
474
+ mcp_status = "running" if self._check_mcp_server() else "stopped"
475
+ return [
476
+ ("mcp_server", mcp_status, "localhost:8675"),
477
+ ("websocket_server", "unknown", "localhost:8676"),
478
+ ]
479
+
470
480
  async def _start_websocket_server(self) -> None:
471
481
  with suppress(Exception):
472
482
  process = subprocess.Popen(
@@ -496,7 +506,7 @@ class ServiceManager:
496
506
  check=False,
497
507
  capture_output=True,
498
508
  text=True,
499
- timeout=5.0, # Add timeout protection
509
+ timeout=5.0,
500
510
  )
501
511
  if result.returncode == 0:
502
512
  return
@@ -514,7 +524,7 @@ class ServiceManager:
514
524
  self._cleanup_single_service(process)
515
525
  self.started_services.clear()
516
526
 
517
- def _cleanup_single_service(self, process: subprocess.Popen) -> None:
527
+ def _cleanup_single_service(self, process: subprocess.Popen[bytes]) -> None:
518
528
  with suppress(Exception):
519
529
  if process.poll() is not None:
520
530
  return