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
@@ -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))
@@ -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