crackerjack 0.18.2__py3-none-any.whl → 0.45.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.
Files changed (533) hide show
  1. crackerjack/README.md +19 -0
  2. crackerjack/__init__.py +96 -2
  3. crackerjack/__main__.py +637 -138
  4. crackerjack/adapters/README.md +18 -0
  5. crackerjack/adapters/__init__.py +39 -0
  6. crackerjack/adapters/_output_paths.py +167 -0
  7. crackerjack/adapters/_qa_adapter_base.py +309 -0
  8. crackerjack/adapters/_tool_adapter_base.py +706 -0
  9. crackerjack/adapters/ai/README.md +65 -0
  10. crackerjack/adapters/ai/__init__.py +5 -0
  11. crackerjack/adapters/ai/claude.py +853 -0
  12. crackerjack/adapters/complexity/README.md +53 -0
  13. crackerjack/adapters/complexity/__init__.py +10 -0
  14. crackerjack/adapters/complexity/complexipy.py +641 -0
  15. crackerjack/adapters/dependency/__init__.py +22 -0
  16. crackerjack/adapters/dependency/pip_audit.py +418 -0
  17. crackerjack/adapters/format/README.md +72 -0
  18. crackerjack/adapters/format/__init__.py +11 -0
  19. crackerjack/adapters/format/mdformat.py +313 -0
  20. crackerjack/adapters/format/ruff.py +516 -0
  21. crackerjack/adapters/lint/README.md +47 -0
  22. crackerjack/adapters/lint/__init__.py +11 -0
  23. crackerjack/adapters/lint/codespell.py +273 -0
  24. crackerjack/adapters/lsp/README.md +49 -0
  25. crackerjack/adapters/lsp/__init__.py +27 -0
  26. crackerjack/adapters/lsp/_base.py +194 -0
  27. crackerjack/adapters/lsp/_client.py +358 -0
  28. crackerjack/adapters/lsp/_manager.py +193 -0
  29. crackerjack/adapters/lsp/skylos.py +283 -0
  30. crackerjack/adapters/lsp/zuban.py +557 -0
  31. crackerjack/adapters/refactor/README.md +59 -0
  32. crackerjack/adapters/refactor/__init__.py +12 -0
  33. crackerjack/adapters/refactor/creosote.py +318 -0
  34. crackerjack/adapters/refactor/refurb.py +406 -0
  35. crackerjack/adapters/refactor/skylos.py +494 -0
  36. crackerjack/adapters/sast/README.md +132 -0
  37. crackerjack/adapters/sast/__init__.py +32 -0
  38. crackerjack/adapters/sast/_base.py +201 -0
  39. crackerjack/adapters/sast/bandit.py +423 -0
  40. crackerjack/adapters/sast/pyscn.py +405 -0
  41. crackerjack/adapters/sast/semgrep.py +241 -0
  42. crackerjack/adapters/security/README.md +111 -0
  43. crackerjack/adapters/security/__init__.py +17 -0
  44. crackerjack/adapters/security/gitleaks.py +339 -0
  45. crackerjack/adapters/type/README.md +52 -0
  46. crackerjack/adapters/type/__init__.py +12 -0
  47. crackerjack/adapters/type/pyrefly.py +402 -0
  48. crackerjack/adapters/type/ty.py +402 -0
  49. crackerjack/adapters/type/zuban.py +522 -0
  50. crackerjack/adapters/utility/README.md +51 -0
  51. crackerjack/adapters/utility/__init__.py +10 -0
  52. crackerjack/adapters/utility/checks.py +884 -0
  53. crackerjack/agents/README.md +264 -0
  54. crackerjack/agents/__init__.py +66 -0
  55. crackerjack/agents/architect_agent.py +238 -0
  56. crackerjack/agents/base.py +167 -0
  57. crackerjack/agents/claude_code_bridge.py +641 -0
  58. crackerjack/agents/coordinator.py +600 -0
  59. crackerjack/agents/documentation_agent.py +520 -0
  60. crackerjack/agents/dry_agent.py +585 -0
  61. crackerjack/agents/enhanced_coordinator.py +279 -0
  62. crackerjack/agents/enhanced_proactive_agent.py +185 -0
  63. crackerjack/agents/error_middleware.py +53 -0
  64. crackerjack/agents/formatting_agent.py +230 -0
  65. crackerjack/agents/helpers/__init__.py +9 -0
  66. crackerjack/agents/helpers/performance/__init__.py +22 -0
  67. crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
  68. crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
  69. crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
  70. crackerjack/agents/helpers/refactoring/__init__.py +22 -0
  71. crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
  72. crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
  73. crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
  74. crackerjack/agents/helpers/test_creation/__init__.py +19 -0
  75. crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
  76. crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
  77. crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
  78. crackerjack/agents/import_optimization_agent.py +1181 -0
  79. crackerjack/agents/performance_agent.py +325 -0
  80. crackerjack/agents/performance_helpers.py +205 -0
  81. crackerjack/agents/proactive_agent.py +55 -0
  82. crackerjack/agents/refactoring_agent.py +511 -0
  83. crackerjack/agents/refactoring_helpers.py +247 -0
  84. crackerjack/agents/security_agent.py +793 -0
  85. crackerjack/agents/semantic_agent.py +479 -0
  86. crackerjack/agents/semantic_helpers.py +356 -0
  87. crackerjack/agents/test_creation_agent.py +570 -0
  88. crackerjack/agents/test_specialist_agent.py +526 -0
  89. crackerjack/agents/tracker.py +110 -0
  90. crackerjack/api.py +647 -0
  91. crackerjack/cli/README.md +394 -0
  92. crackerjack/cli/__init__.py +24 -0
  93. crackerjack/cli/cache_handlers.py +209 -0
  94. crackerjack/cli/cache_handlers_enhanced.py +680 -0
  95. crackerjack/cli/facade.py +162 -0
  96. crackerjack/cli/formatting.py +13 -0
  97. crackerjack/cli/handlers/__init__.py +85 -0
  98. crackerjack/cli/handlers/advanced.py +103 -0
  99. crackerjack/cli/handlers/ai_features.py +62 -0
  100. crackerjack/cli/handlers/analytics.py +479 -0
  101. crackerjack/cli/handlers/changelog.py +271 -0
  102. crackerjack/cli/handlers/config_handlers.py +16 -0
  103. crackerjack/cli/handlers/coverage.py +84 -0
  104. crackerjack/cli/handlers/documentation.py +280 -0
  105. crackerjack/cli/handlers/main_handlers.py +497 -0
  106. crackerjack/cli/handlers/monitoring.py +371 -0
  107. crackerjack/cli/handlers.py +700 -0
  108. crackerjack/cli/interactive.py +488 -0
  109. crackerjack/cli/options.py +1216 -0
  110. crackerjack/cli/semantic_handlers.py +292 -0
  111. crackerjack/cli/utils.py +19 -0
  112. crackerjack/cli/version.py +19 -0
  113. crackerjack/code_cleaner.py +1307 -0
  114. crackerjack/config/README.md +472 -0
  115. crackerjack/config/__init__.py +275 -0
  116. crackerjack/config/global_lock_config.py +207 -0
  117. crackerjack/config/hooks.py +390 -0
  118. crackerjack/config/loader.py +239 -0
  119. crackerjack/config/settings.py +141 -0
  120. crackerjack/config/tool_commands.py +331 -0
  121. crackerjack/core/README.md +393 -0
  122. crackerjack/core/__init__.py +0 -0
  123. crackerjack/core/async_workflow_orchestrator.py +738 -0
  124. crackerjack/core/autofix_coordinator.py +282 -0
  125. crackerjack/core/container.py +105 -0
  126. crackerjack/core/enhanced_container.py +583 -0
  127. crackerjack/core/file_lifecycle.py +472 -0
  128. crackerjack/core/performance.py +244 -0
  129. crackerjack/core/performance_monitor.py +357 -0
  130. crackerjack/core/phase_coordinator.py +1227 -0
  131. crackerjack/core/proactive_workflow.py +267 -0
  132. crackerjack/core/resource_manager.py +425 -0
  133. crackerjack/core/retry.py +275 -0
  134. crackerjack/core/service_watchdog.py +601 -0
  135. crackerjack/core/session_coordinator.py +239 -0
  136. crackerjack/core/timeout_manager.py +563 -0
  137. crackerjack/core/websocket_lifecycle.py +410 -0
  138. crackerjack/core/workflow/__init__.py +21 -0
  139. crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
  140. crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
  141. crackerjack/core/workflow/workflow_issue_parser.py +714 -0
  142. crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
  143. crackerjack/core/workflow/workflow_security_gates.py +400 -0
  144. crackerjack/core/workflow_orchestrator.py +2243 -0
  145. crackerjack/data/README.md +11 -0
  146. crackerjack/data/__init__.py +8 -0
  147. crackerjack/data/models.py +79 -0
  148. crackerjack/data/repository.py +210 -0
  149. crackerjack/decorators/README.md +180 -0
  150. crackerjack/decorators/__init__.py +35 -0
  151. crackerjack/decorators/error_handling.py +649 -0
  152. crackerjack/decorators/error_handling_decorators.py +334 -0
  153. crackerjack/decorators/helpers.py +58 -0
  154. crackerjack/decorators/patterns.py +281 -0
  155. crackerjack/decorators/utils.py +58 -0
  156. crackerjack/docs/INDEX.md +11 -0
  157. crackerjack/docs/README.md +11 -0
  158. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  159. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  160. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  161. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  162. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  163. crackerjack/documentation/README.md +11 -0
  164. crackerjack/documentation/__init__.py +31 -0
  165. crackerjack/documentation/ai_templates.py +756 -0
  166. crackerjack/documentation/dual_output_generator.py +767 -0
  167. crackerjack/documentation/mkdocs_integration.py +518 -0
  168. crackerjack/documentation/reference_generator.py +1065 -0
  169. crackerjack/dynamic_config.py +678 -0
  170. crackerjack/errors.py +378 -0
  171. crackerjack/events/README.md +11 -0
  172. crackerjack/events/__init__.py +16 -0
  173. crackerjack/events/telemetry.py +175 -0
  174. crackerjack/events/workflow_bus.py +346 -0
  175. crackerjack/exceptions/README.md +301 -0
  176. crackerjack/exceptions/__init__.py +5 -0
  177. crackerjack/exceptions/config.py +4 -0
  178. crackerjack/exceptions/tool_execution_error.py +245 -0
  179. crackerjack/executors/README.md +591 -0
  180. crackerjack/executors/__init__.py +13 -0
  181. crackerjack/executors/async_hook_executor.py +938 -0
  182. crackerjack/executors/cached_hook_executor.py +316 -0
  183. crackerjack/executors/hook_executor.py +1295 -0
  184. crackerjack/executors/hook_lock_manager.py +708 -0
  185. crackerjack/executors/individual_hook_executor.py +739 -0
  186. crackerjack/executors/lsp_aware_hook_executor.py +349 -0
  187. crackerjack/executors/progress_hook_executor.py +282 -0
  188. crackerjack/executors/tool_proxy.py +433 -0
  189. crackerjack/hooks/README.md +485 -0
  190. crackerjack/hooks/lsp_hook.py +93 -0
  191. crackerjack/intelligence/README.md +557 -0
  192. crackerjack/intelligence/__init__.py +37 -0
  193. crackerjack/intelligence/adaptive_learning.py +693 -0
  194. crackerjack/intelligence/agent_orchestrator.py +485 -0
  195. crackerjack/intelligence/agent_registry.py +377 -0
  196. crackerjack/intelligence/agent_selector.py +439 -0
  197. crackerjack/intelligence/integration.py +250 -0
  198. crackerjack/interactive.py +719 -0
  199. crackerjack/managers/README.md +369 -0
  200. crackerjack/managers/__init__.py +11 -0
  201. crackerjack/managers/async_hook_manager.py +135 -0
  202. crackerjack/managers/hook_manager.py +585 -0
  203. crackerjack/managers/publish_manager.py +631 -0
  204. crackerjack/managers/test_command_builder.py +391 -0
  205. crackerjack/managers/test_executor.py +474 -0
  206. crackerjack/managers/test_manager.py +1357 -0
  207. crackerjack/managers/test_progress.py +187 -0
  208. crackerjack/mcp/README.md +374 -0
  209. crackerjack/mcp/__init__.py +0 -0
  210. crackerjack/mcp/cache.py +352 -0
  211. crackerjack/mcp/client_runner.py +121 -0
  212. crackerjack/mcp/context.py +802 -0
  213. crackerjack/mcp/dashboard.py +657 -0
  214. crackerjack/mcp/enhanced_progress_monitor.py +493 -0
  215. crackerjack/mcp/file_monitor.py +394 -0
  216. crackerjack/mcp/progress_components.py +607 -0
  217. crackerjack/mcp/progress_monitor.py +1016 -0
  218. crackerjack/mcp/rate_limiter.py +336 -0
  219. crackerjack/mcp/server.py +24 -0
  220. crackerjack/mcp/server_core.py +526 -0
  221. crackerjack/mcp/service_watchdog.py +505 -0
  222. crackerjack/mcp/state.py +407 -0
  223. crackerjack/mcp/task_manager.py +259 -0
  224. crackerjack/mcp/tools/README.md +27 -0
  225. crackerjack/mcp/tools/__init__.py +19 -0
  226. crackerjack/mcp/tools/core_tools.py +469 -0
  227. crackerjack/mcp/tools/error_analyzer.py +283 -0
  228. crackerjack/mcp/tools/execution_tools.py +384 -0
  229. crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
  230. crackerjack/mcp/tools/intelligence_tools.py +264 -0
  231. crackerjack/mcp/tools/monitoring_tools.py +628 -0
  232. crackerjack/mcp/tools/proactive_tools.py +367 -0
  233. crackerjack/mcp/tools/progress_tools.py +222 -0
  234. crackerjack/mcp/tools/semantic_tools.py +584 -0
  235. crackerjack/mcp/tools/utility_tools.py +358 -0
  236. crackerjack/mcp/tools/workflow_executor.py +699 -0
  237. crackerjack/mcp/websocket/README.md +31 -0
  238. crackerjack/mcp/websocket/__init__.py +14 -0
  239. crackerjack/mcp/websocket/app.py +54 -0
  240. crackerjack/mcp/websocket/endpoints.py +492 -0
  241. crackerjack/mcp/websocket/event_bridge.py +188 -0
  242. crackerjack/mcp/websocket/jobs.py +406 -0
  243. crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
  244. crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
  245. crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
  246. crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
  247. crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
  248. crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
  249. crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
  250. crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
  251. crackerjack/mcp/websocket/monitoring/factory.py +109 -0
  252. crackerjack/mcp/websocket/monitoring/filters.py +10 -0
  253. crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
  254. crackerjack/mcp/websocket/monitoring/models.py +90 -0
  255. crackerjack/mcp/websocket/monitoring/utils.py +171 -0
  256. crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
  257. crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
  258. crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
  259. crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
  260. crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
  261. crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
  262. crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
  263. crackerjack/mcp/websocket/server.py +174 -0
  264. crackerjack/mcp/websocket/websocket_handler.py +276 -0
  265. crackerjack/mcp/websocket_server.py +10 -0
  266. crackerjack/models/README.md +308 -0
  267. crackerjack/models/__init__.py +40 -0
  268. crackerjack/models/config.py +730 -0
  269. crackerjack/models/config_adapter.py +265 -0
  270. crackerjack/models/protocols.py +1535 -0
  271. crackerjack/models/pydantic_models.py +320 -0
  272. crackerjack/models/qa_config.py +145 -0
  273. crackerjack/models/qa_results.py +134 -0
  274. crackerjack/models/resource_protocols.py +299 -0
  275. crackerjack/models/results.py +35 -0
  276. crackerjack/models/semantic_models.py +258 -0
  277. crackerjack/models/task.py +173 -0
  278. crackerjack/models/test_models.py +60 -0
  279. crackerjack/monitoring/README.md +11 -0
  280. crackerjack/monitoring/__init__.py +0 -0
  281. crackerjack/monitoring/ai_agent_watchdog.py +405 -0
  282. crackerjack/monitoring/metrics_collector.py +427 -0
  283. crackerjack/monitoring/regression_prevention.py +580 -0
  284. crackerjack/monitoring/websocket_server.py +406 -0
  285. crackerjack/orchestration/README.md +340 -0
  286. crackerjack/orchestration/__init__.py +43 -0
  287. crackerjack/orchestration/advanced_orchestrator.py +894 -0
  288. crackerjack/orchestration/cache/README.md +312 -0
  289. crackerjack/orchestration/cache/__init__.py +37 -0
  290. crackerjack/orchestration/cache/memory_cache.py +338 -0
  291. crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
  292. crackerjack/orchestration/config.py +297 -0
  293. crackerjack/orchestration/coverage_improvement.py +180 -0
  294. crackerjack/orchestration/execution_strategies.py +361 -0
  295. crackerjack/orchestration/hook_orchestrator.py +1398 -0
  296. crackerjack/orchestration/strategies/README.md +401 -0
  297. crackerjack/orchestration/strategies/__init__.py +39 -0
  298. crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
  299. crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
  300. crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
  301. crackerjack/orchestration/test_progress_streamer.py +647 -0
  302. crackerjack/plugins/README.md +11 -0
  303. crackerjack/plugins/__init__.py +15 -0
  304. crackerjack/plugins/base.py +200 -0
  305. crackerjack/plugins/hooks.py +254 -0
  306. crackerjack/plugins/loader.py +335 -0
  307. crackerjack/plugins/managers.py +264 -0
  308. crackerjack/py313.py +191 -0
  309. crackerjack/security/README.md +11 -0
  310. crackerjack/security/__init__.py +0 -0
  311. crackerjack/security/audit.py +197 -0
  312. crackerjack/services/README.md +374 -0
  313. crackerjack/services/__init__.py +9 -0
  314. crackerjack/services/ai/README.md +295 -0
  315. crackerjack/services/ai/__init__.py +7 -0
  316. crackerjack/services/ai/advanced_optimizer.py +878 -0
  317. crackerjack/services/ai/contextual_ai_assistant.py +542 -0
  318. crackerjack/services/ai/embeddings.py +444 -0
  319. crackerjack/services/ai/intelligent_commit.py +328 -0
  320. crackerjack/services/ai/predictive_analytics.py +510 -0
  321. crackerjack/services/anomaly_detector.py +392 -0
  322. crackerjack/services/api_extractor.py +617 -0
  323. crackerjack/services/backup_service.py +467 -0
  324. crackerjack/services/bounded_status_operations.py +530 -0
  325. crackerjack/services/cache.py +369 -0
  326. crackerjack/services/changelog_automation.py +399 -0
  327. crackerjack/services/command_execution_service.py +305 -0
  328. crackerjack/services/config_integrity.py +132 -0
  329. crackerjack/services/config_merge.py +546 -0
  330. crackerjack/services/config_service.py +198 -0
  331. crackerjack/services/config_template.py +493 -0
  332. crackerjack/services/coverage_badge_service.py +173 -0
  333. crackerjack/services/coverage_ratchet.py +381 -0
  334. crackerjack/services/debug.py +733 -0
  335. crackerjack/services/dependency_analyzer.py +460 -0
  336. crackerjack/services/dependency_monitor.py +622 -0
  337. crackerjack/services/documentation_generator.py +493 -0
  338. crackerjack/services/documentation_service.py +704 -0
  339. crackerjack/services/enhanced_filesystem.py +497 -0
  340. crackerjack/services/enterprise_optimizer.py +865 -0
  341. crackerjack/services/error_pattern_analyzer.py +676 -0
  342. crackerjack/services/file_filter.py +221 -0
  343. crackerjack/services/file_hasher.py +149 -0
  344. crackerjack/services/file_io_service.py +361 -0
  345. crackerjack/services/file_modifier.py +615 -0
  346. crackerjack/services/filesystem.py +381 -0
  347. crackerjack/services/git.py +422 -0
  348. crackerjack/services/health_metrics.py +615 -0
  349. crackerjack/services/heatmap_generator.py +744 -0
  350. crackerjack/services/incremental_executor.py +380 -0
  351. crackerjack/services/initialization.py +823 -0
  352. crackerjack/services/input_validator.py +668 -0
  353. crackerjack/services/intelligent_commit.py +327 -0
  354. crackerjack/services/log_manager.py +289 -0
  355. crackerjack/services/logging.py +228 -0
  356. crackerjack/services/lsp_client.py +628 -0
  357. crackerjack/services/memory_optimizer.py +414 -0
  358. crackerjack/services/metrics.py +587 -0
  359. crackerjack/services/monitoring/README.md +30 -0
  360. crackerjack/services/monitoring/__init__.py +9 -0
  361. crackerjack/services/monitoring/dependency_monitor.py +678 -0
  362. crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
  363. crackerjack/services/monitoring/health_metrics.py +716 -0
  364. crackerjack/services/monitoring/metrics.py +587 -0
  365. crackerjack/services/monitoring/performance_benchmarks.py +410 -0
  366. crackerjack/services/monitoring/performance_cache.py +388 -0
  367. crackerjack/services/monitoring/performance_monitor.py +569 -0
  368. crackerjack/services/parallel_executor.py +527 -0
  369. crackerjack/services/pattern_cache.py +333 -0
  370. crackerjack/services/pattern_detector.py +478 -0
  371. crackerjack/services/patterns/__init__.py +142 -0
  372. crackerjack/services/patterns/agents.py +107 -0
  373. crackerjack/services/patterns/code/__init__.py +15 -0
  374. crackerjack/services/patterns/code/detection.py +118 -0
  375. crackerjack/services/patterns/code/imports.py +107 -0
  376. crackerjack/services/patterns/code/paths.py +159 -0
  377. crackerjack/services/patterns/code/performance.py +119 -0
  378. crackerjack/services/patterns/code/replacement.py +36 -0
  379. crackerjack/services/patterns/core.py +212 -0
  380. crackerjack/services/patterns/documentation/__init__.py +14 -0
  381. crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
  382. crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
  383. crackerjack/services/patterns/documentation/docstrings.py +89 -0
  384. crackerjack/services/patterns/formatting.py +226 -0
  385. crackerjack/services/patterns/operations.py +339 -0
  386. crackerjack/services/patterns/security/__init__.py +23 -0
  387. crackerjack/services/patterns/security/code_injection.py +122 -0
  388. crackerjack/services/patterns/security/credentials.py +190 -0
  389. crackerjack/services/patterns/security/path_traversal.py +221 -0
  390. crackerjack/services/patterns/security/unsafe_operations.py +216 -0
  391. crackerjack/services/patterns/templates.py +62 -0
  392. crackerjack/services/patterns/testing/__init__.py +18 -0
  393. crackerjack/services/patterns/testing/error_patterns.py +107 -0
  394. crackerjack/services/patterns/testing/pytest_output.py +126 -0
  395. crackerjack/services/patterns/tool_output/__init__.py +16 -0
  396. crackerjack/services/patterns/tool_output/bandit.py +72 -0
  397. crackerjack/services/patterns/tool_output/other.py +97 -0
  398. crackerjack/services/patterns/tool_output/pyright.py +67 -0
  399. crackerjack/services/patterns/tool_output/ruff.py +44 -0
  400. crackerjack/services/patterns/url_sanitization.py +114 -0
  401. crackerjack/services/patterns/utilities.py +42 -0
  402. crackerjack/services/patterns/utils.py +339 -0
  403. crackerjack/services/patterns/validation.py +46 -0
  404. crackerjack/services/patterns/versioning.py +62 -0
  405. crackerjack/services/predictive_analytics.py +523 -0
  406. crackerjack/services/profiler.py +280 -0
  407. crackerjack/services/quality/README.md +415 -0
  408. crackerjack/services/quality/__init__.py +11 -0
  409. crackerjack/services/quality/anomaly_detector.py +392 -0
  410. crackerjack/services/quality/pattern_cache.py +333 -0
  411. crackerjack/services/quality/pattern_detector.py +479 -0
  412. crackerjack/services/quality/qa_orchestrator.py +491 -0
  413. crackerjack/services/quality/quality_baseline.py +395 -0
  414. crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
  415. crackerjack/services/quality/quality_intelligence.py +949 -0
  416. crackerjack/services/regex_patterns.py +58 -0
  417. crackerjack/services/regex_utils.py +483 -0
  418. crackerjack/services/secure_path_utils.py +524 -0
  419. crackerjack/services/secure_status_formatter.py +450 -0
  420. crackerjack/services/secure_subprocess.py +635 -0
  421. crackerjack/services/security.py +239 -0
  422. crackerjack/services/security_logger.py +495 -0
  423. crackerjack/services/server_manager.py +411 -0
  424. crackerjack/services/smart_scheduling.py +167 -0
  425. crackerjack/services/status_authentication.py +460 -0
  426. crackerjack/services/status_security_manager.py +315 -0
  427. crackerjack/services/terminal_utils.py +0 -0
  428. crackerjack/services/thread_safe_status_collector.py +441 -0
  429. crackerjack/services/tool_filter.py +368 -0
  430. crackerjack/services/tool_version_service.py +43 -0
  431. crackerjack/services/unified_config.py +115 -0
  432. crackerjack/services/validation_rate_limiter.py +220 -0
  433. crackerjack/services/vector_store.py +689 -0
  434. crackerjack/services/version_analyzer.py +461 -0
  435. crackerjack/services/version_checker.py +223 -0
  436. crackerjack/services/websocket_resource_limiter.py +438 -0
  437. crackerjack/services/zuban_lsp_service.py +391 -0
  438. crackerjack/slash_commands/README.md +11 -0
  439. crackerjack/slash_commands/__init__.py +59 -0
  440. crackerjack/slash_commands/init.md +112 -0
  441. crackerjack/slash_commands/run.md +197 -0
  442. crackerjack/slash_commands/status.md +127 -0
  443. crackerjack/tools/README.md +11 -0
  444. crackerjack/tools/__init__.py +30 -0
  445. crackerjack/tools/_git_utils.py +105 -0
  446. crackerjack/tools/check_added_large_files.py +139 -0
  447. crackerjack/tools/check_ast.py +105 -0
  448. crackerjack/tools/check_json.py +103 -0
  449. crackerjack/tools/check_jsonschema.py +297 -0
  450. crackerjack/tools/check_toml.py +103 -0
  451. crackerjack/tools/check_yaml.py +110 -0
  452. crackerjack/tools/codespell_wrapper.py +72 -0
  453. crackerjack/tools/end_of_file_fixer.py +202 -0
  454. crackerjack/tools/format_json.py +128 -0
  455. crackerjack/tools/mdformat_wrapper.py +114 -0
  456. crackerjack/tools/trailing_whitespace.py +198 -0
  457. crackerjack/tools/validate_input_validator_patterns.py +236 -0
  458. crackerjack/tools/validate_regex_patterns.py +188 -0
  459. crackerjack/ui/README.md +11 -0
  460. crackerjack/ui/__init__.py +1 -0
  461. crackerjack/ui/dashboard_renderer.py +28 -0
  462. crackerjack/ui/templates/README.md +11 -0
  463. crackerjack/utils/console_utils.py +13 -0
  464. crackerjack/utils/dependency_guard.py +230 -0
  465. crackerjack/utils/retry_utils.py +275 -0
  466. crackerjack/workflows/README.md +590 -0
  467. crackerjack/workflows/__init__.py +46 -0
  468. crackerjack/workflows/actions.py +811 -0
  469. crackerjack/workflows/auto_fix.py +444 -0
  470. crackerjack/workflows/container_builder.py +499 -0
  471. crackerjack/workflows/definitions.py +443 -0
  472. crackerjack/workflows/engine.py +177 -0
  473. crackerjack/workflows/event_bridge.py +242 -0
  474. crackerjack-0.45.2.dist-info/METADATA +1678 -0
  475. crackerjack-0.45.2.dist-info/RECORD +478 -0
  476. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
  477. crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
  478. crackerjack/.gitignore +0 -14
  479. crackerjack/.libcst.codemod.yaml +0 -18
  480. crackerjack/.pdm.toml +0 -1
  481. crackerjack/.pre-commit-config.yaml +0 -91
  482. crackerjack/.pytest_cache/.gitignore +0 -2
  483. crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
  484. crackerjack/.pytest_cache/README.md +0 -8
  485. crackerjack/.pytest_cache/v/cache/nodeids +0 -1
  486. crackerjack/.pytest_cache/v/cache/stepwise +0 -1
  487. crackerjack/.ruff_cache/.gitignore +0 -1
  488. crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  489. crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  490. crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  491. crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  492. crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  493. crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  494. crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  495. crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  496. crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  497. crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  498. crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  499. crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  500. crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  501. crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  502. crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  503. crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  504. crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  505. crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  506. crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  507. crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  508. crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  509. crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  510. crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  511. crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  512. crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  513. crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  514. crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  515. crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  516. crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  517. crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  518. crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  519. crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  520. crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  521. crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  522. crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  523. crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  524. crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  525. crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  526. crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  527. crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
  528. crackerjack/crackerjack.py +0 -855
  529. crackerjack/pyproject.toml +0 -214
  530. crackerjack-0.18.2.dist-info/METADATA +0 -420
  531. crackerjack-0.18.2.dist-info/RECORD +0 -59
  532. crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
  533. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1016 @@
1
+ import asyncio
2
+ import atexit
3
+ import signal
4
+ import subprocess
5
+ import sys
6
+ import tempfile
7
+ import time
8
+ import typing as t
9
+ from contextlib import suppress
10
+ from pathlib import Path
11
+
12
+ from acb import console
13
+ from textual.app import App, ComposeResult
14
+ from textual.containers import Container
15
+ from textual.widget import Widget
16
+ from textual.widgets import DataTable, Footer, Label, ProgressBar
17
+
18
+ from crackerjack.core.timeout_manager import TimeoutStrategy, get_timeout_manager
19
+
20
+ from .progress_components import (
21
+ ErrorCollector,
22
+ JobDataCollector,
23
+ ServiceHealthChecker,
24
+ ServiceManager,
25
+ TerminalRestorer,
26
+ )
27
+
28
+
29
+ class AgentStatusPanel(Widget):
30
+ def __init__(self, **kwargs: t.Any) -> None:
31
+ super().__init__(**kwargs)
32
+ self.border_title = "🤖 AI Agents"
33
+ self.border_title_align = "left"
34
+
35
+ def compose(self) -> ComposeResult:
36
+ yield DataTable(id="agents-table")
37
+ yield Label("Coordinator: Loading...", id="coordinator-status")
38
+ yield Label("Stats: Loading...", id="agent-stats")
39
+
40
+ def on_mount(self) -> None:
41
+ with suppress(Exception):
42
+ agents_table = self.query_one("#agents-table", DataTable)
43
+ agents_table.add_columns(
44
+ "Agent",
45
+ "Status",
46
+ "Issue Type",
47
+ "Confidence",
48
+ "Time",
49
+ )
50
+
51
+ agents_table.styles.max_height = "8"
52
+
53
+ def update_agent_data(self, agent_data: dict[str, t.Any]) -> None:
54
+ with suppress(Exception):
55
+ self._update_coordinator_status(agent_data)
56
+ self._update_agents_table(agent_data)
57
+ self._update_stats(agent_data)
58
+
59
+ def _update_coordinator_status(self, data: dict[str, t.Any]) -> None:
60
+ with suppress(Exception):
61
+ activity = data.get("agent_activity", {})
62
+ registry = activity.get("agent_registry", {})
63
+ coordinator_status = activity.get("coordinator_status", "idle")
64
+ total_agents = registry.get("total_agents", 6)
65
+
66
+ status_emoji = "✅" if coordinator_status == "active" else "⏸️"
67
+ status_label = self.query_one("#coordinator-status", Label)
68
+ status_label.update(
69
+ f"Coordinator: {status_emoji} {coordinator_status.title()} ({total_agents} agents)",
70
+ )
71
+
72
+ def _update_agents_table(self, data: dict[str, t.Any]) -> None:
73
+ with suppress(Exception):
74
+ agents_table = self.query_one("#agents-table", DataTable)
75
+ agents_table.clear()
76
+
77
+ activity = data.get("agent_activity", {})
78
+ active_agents = activity.get("active_agents", [])
79
+
80
+ if not active_agents:
81
+ agents_table.add_row("No active agents", "-", "-", "-", "-")
82
+ return
83
+
84
+ for agent in active_agents:
85
+ agent_type = agent.get("agent_type", "Unknown")
86
+ status = agent.get("status", "unknown")
87
+ confidence = agent.get("confidence", 0)
88
+ processing_time = agent.get("processing_time", 0)
89
+
90
+ emoji = self._get_agent_emoji(agent_type)
91
+
92
+ current_issue = agent.get("current_issue", {})
93
+ issue_type = (
94
+ current_issue.get("type", "-")
95
+ if current_issue
96
+ else agent.get("issue_type", "-")
97
+ )
98
+
99
+ status_display = f"{self._get_status_emoji(status)} {status.title()}"
100
+
101
+ agents_table.add_row(
102
+ f"{emoji} {agent_type}",
103
+ status_display,
104
+ issue_type,
105
+ f"{confidence: .0 % }" if confidence > 0 else "-",
106
+ f"{processing_time: .1f}s" if processing_time > 0 else "-",
107
+ )
108
+
109
+ def _update_stats(self, data: dict[str, t.Any]) -> None:
110
+ with suppress(Exception):
111
+ performance = data.get("agent_performance", {})
112
+ total_issues = performance.get("total_issues_processed", 0)
113
+ success_rate = performance.get("success_rate", 0)
114
+ avg_time = performance.get("average_processing_time", 0)
115
+ cache_hits = performance.get("cache_hits", 0)
116
+
117
+ stats_label = self.query_one("#agent-stats", Label)
118
+ stats_text = f"Stats: {total_issues} issues | {success_rate: .0 % } success"
119
+ if avg_time > 0:
120
+ stats_text += f" | {avg_time: .1f}s avg"
121
+ if cache_hits > 0:
122
+ stats_text += f" | {cache_hits} cached"
123
+
124
+ if success_rate >= 80:
125
+ stats_text += " ↑🟢"
126
+ elif success_rate >= 60:
127
+ stats_text += " 🟡"
128
+ elif total_issues > 0:
129
+ stats_text += " ↓🔴"
130
+
131
+ stats_label.update(stats_text)
132
+
133
+ def _get_agent_emoji(self, agent_type: str) -> str:
134
+ emojis = {
135
+ "FormattingAgent": "🎨",
136
+ "SecurityAgent": "🔒",
137
+ "TestSpecialistAgent": "🧪",
138
+ "TestCreationAgent": "➕",
139
+ "RefactoringAgent": "🔧",
140
+ "ImportOptimizationAgent": "📦",
141
+ }
142
+ return emojis.get(agent_type) or "🤖"
143
+
144
+ def _get_status_emoji(self, status: str) -> str:
145
+ emojis = {
146
+ "evaluating": "🔍",
147
+ "processing": "⏳",
148
+ "completed": "✅",
149
+ "failed": "❌",
150
+ "idle": "⏸️",
151
+ }
152
+ return emojis.get(status.lower()) or "❓"
153
+
154
+
155
+ class JobPanel(Widget):
156
+ def __init__(self, job_data: dict[str, t.Any], **kwargs: t.Any) -> None:
157
+ super().__init__(**kwargs)
158
+ self.job_data = job_data
159
+ self.completion_time: float | None = None
160
+ self.iteration_count = job_data.get("iteration", 0)
161
+ self.max_iterations = job_data.get("max_iterations", 5)
162
+ self.fade_timer: t.Any | None = None # Timer object or None
163
+ self.remove_timer: t.Any | None = None # Timer object or None
164
+ self.fade_level = 0
165
+ self.border_style = self._calculate_border_style()
166
+
167
+ def _calculate_border_style(self) -> str:
168
+ status = self.job_data.get("status", "unknown").lower()
169
+
170
+ if status == "completed":
171
+ errors = self.job_data.get("errors", [])
172
+ hook_failures = self.job_data.get("hook_failures", [])
173
+ test_failures = self.job_data.get("test_failures", [])
174
+ total_failures = (
175
+ len(hook_failures)
176
+ + len(test_failures)
177
+ + len([e for e in errors if "failed" in str(e).lower()])
178
+ )
179
+
180
+ if total_failures == 0:
181
+ return "round green"
182
+ return "round red"
183
+ if status == "failed" or self.iteration_count >= 10:
184
+ return "round red"
185
+ if status == "running":
186
+ return "round blue"
187
+ return "round white"
188
+
189
+ def on_mount(self) -> None:
190
+ project_name = self.job_data.get("project", "crackerjack")
191
+ status = self.job_data.get("status", "").lower()
192
+ if status == "running":
193
+ self.border_title = f"📁 {project_name}"
194
+ self.border_subtitle = "💓"
195
+ self.border_subtitle_align = "right"
196
+ else:
197
+ self.border_title = f"📁 {project_name}"
198
+ self.border_title_align = "left"
199
+
200
+ self._setup_errors_table()
201
+
202
+ self._update_progress_bar()
203
+
204
+ status = self.job_data.get("status", "").lower()
205
+ if status in ("completed", "failed") and self.completion_time is None:
206
+ self.completion_time = time.time()
207
+
208
+ self.fade_timer = self.set_timer(300.0, self._start_fade)
209
+
210
+ self.remove_timer = self.set_timer(1200.0, self._remove_panel)
211
+
212
+ def _setup_errors_table(self) -> None:
213
+ with suppress(Exception):
214
+ errors_container = self.query_one(".job-errors")
215
+ errors_container.border_title = "❌ Errors"
216
+
217
+ errors_table = self.query_one(
218
+ f"#job-errors -{self.job_data.get('job_id', 'unknown')}",
219
+ DataTable,
220
+ )
221
+ errors_table.add_columns("", "", "", "")
222
+
223
+ self._update_errors_table()
224
+
225
+ def _update_errors_table(self) -> None:
226
+ with suppress(Exception):
227
+ errors_table = self.query_one(
228
+ f"#job-errors -{self.job_data.get('job_id', 'unknown')}",
229
+ DataTable,
230
+ )
231
+ errors_table.clear()
232
+
233
+ total_errors = self.job_data.get("total_issues", 0)
234
+ fixed_errors = self.job_data.get("errors_fixed", 0)
235
+
236
+ remaining_errors = max(0, total_errors - fixed_errors)
237
+
238
+ progress_pct = 0
239
+ if total_errors > 0:
240
+ progress_pct = int((fixed_errors / total_errors) * 100)
241
+
242
+ if total_errors == 0 and "errors" in self.job_data:
243
+ errors = self.job_data.get("errors", [])
244
+ hook_failures = self.job_data.get("hook_failures", [])
245
+ test_failures = self.job_data.get("test_failures", [])
246
+ total_errors = len(errors) + len(hook_failures) + len(test_failures)
247
+ failed_errors = (
248
+ len(hook_failures)
249
+ + len(test_failures)
250
+ + len([e for e in errors if "failed" in str(e).lower()])
251
+ )
252
+ fixed_errors = max(0, total_errors - failed_errors)
253
+ remaining_errors = failed_errors
254
+ if total_errors > 0:
255
+ progress_pct = int((fixed_errors / total_errors) * 100)
256
+
257
+ discovered_label = "🔍 Found"
258
+ discovered_value = f"{total_errors: > 15}"
259
+ resolved_label = "✅ Fixed"
260
+ resolved_value = f"{fixed_errors: > 15}"
261
+
262
+ remaining_label = "❌ Left"
263
+ remaining_value = f"{remaining_errors: > 15}"
264
+ progress_label = "📈 Done"
265
+ progress_value = f"{progress_pct} % ".rjust(15)
266
+
267
+ errors_table.add_rows(
268
+ [
269
+ (
270
+ discovered_label,
271
+ discovered_value,
272
+ resolved_label,
273
+ resolved_value,
274
+ ),
275
+ (remaining_label, remaining_value, progress_label, progress_value),
276
+ ],
277
+ )
278
+
279
+ def _update_progress_bar(self) -> None:
280
+ with suppress(Exception):
281
+ progress_bar = self.query_one(
282
+ f"#job-progress -{self.job_data.get('job_id', 'unknown')}",
283
+ ProgressBar,
284
+ )
285
+ progress_value = self.iteration_count / max(self.max_iterations, 1) * 100
286
+ progress_bar.update(progress=progress_value)
287
+
288
+ def _start_fade(self) -> None:
289
+ self.fade_level += 1
290
+
291
+ if self.fade_level == 1:
292
+ self.add_class("fade-1")
293
+ elif self.fade_level == 2:
294
+ self.add_class("fade-2")
295
+ elif self.fade_level == 3:
296
+ self.add_class("fade-3")
297
+ elif self.fade_level >= 4:
298
+ self.add_class("fade-4")
299
+
300
+ if self.fade_level < 4:
301
+ self.fade_timer = self.set_timer(300.0, self._start_fade)
302
+
303
+ def _remove_panel(self) -> None:
304
+ if hasattr(self.app, "completed_jobs_stats"):
305
+ job_id = self.job_data.get("job_id")
306
+
307
+ total_errors = self.job_data.get("total_issues", 0)
308
+ fixed_errors = self.job_data.get("errors_fixed", 0)
309
+ remaining_errors = max(0, total_errors - fixed_errors)
310
+
311
+ if total_errors == 0 and "errors" in self.job_data:
312
+ errors = self.job_data.get("errors", [])
313
+ hook_failures = self.job_data.get("hook_failures", [])
314
+ test_failures = self.job_data.get("test_failures", [])
315
+
316
+ total_errors = len(errors) + len(hook_failures) + len(test_failures)
317
+ failed_errors = (
318
+ len(hook_failures)
319
+ + len(test_failures)
320
+ + len([e for e in errors if "failed" in str(e).lower()])
321
+ )
322
+ fixed_errors = max(0, total_errors - failed_errors)
323
+ remaining_errors = failed_errors
324
+
325
+ self.app.completed_jobs_stats[job_id] = {
326
+ "status": self.job_data.get("status", "unknown"),
327
+ "total_errors": total_errors,
328
+ "fixed_errors": fixed_errors,
329
+ "remaining_errors": remaining_errors,
330
+ "completion_time": self.completion_time,
331
+ }
332
+
333
+ if hasattr(self.app, "active_jobs"):
334
+ job_id = self.job_data.get("job_id")
335
+ if job_id in self.app.active_jobs:
336
+ del self.app.active_jobs[job_id]
337
+ self.remove()
338
+
339
+ def compose(self) -> ComposeResult:
340
+ with Container(classes="job-panel"):
341
+ yield from self._compose_status_column()
342
+ yield from self._compose_errors_column()
343
+ yield from self._compose_mcp_message()
344
+
345
+ def _compose_mcp_message(self) -> ComposeResult:
346
+ mcp_message = self.job_data.get("message", "Processing...")
347
+ yield Label(f"💬 {mcp_message}", classes="mcp-message")
348
+
349
+ def _compose_status_column(self) -> ComposeResult:
350
+ with Container(classes="job-status"):
351
+ yield from self._compose_job_identifiers()
352
+ yield from self._compose_progress_info()
353
+ yield from self._compose_stage_and_status()
354
+ yield from self._compose_agent_info()
355
+ yield from self._compose_warning_messages()
356
+
357
+ def _compose_job_identifiers(self) -> ComposeResult:
358
+ job_id = self.job_data.get(
359
+ "full_job_id",
360
+ self.job_data.get("job_id", "Unknown"),
361
+ )
362
+ yield Label(f"🆔 UUID: {job_id}")
363
+
364
+ def _compose_progress_info(self) -> ComposeResult:
365
+ progress_stage = f"{self.iteration_count} / {self.max_iterations}"
366
+ yield Label(f"📊 Progress: {progress_stage}")
367
+ yield ProgressBar(
368
+ total=100,
369
+ show_eta=False,
370
+ show_percentage=False,
371
+ id=f"job-progress -{self.job_data.get('job_id', 'unknown')}",
372
+ )
373
+
374
+ def _compose_stage_and_status(self) -> ComposeResult:
375
+ yield Label(f"🎯 Stage: {self.job_data.get('stage', 'Unknown')}")
376
+ yield Label(f"📝 Status: {self.job_data.get('status', 'Unknown')}")
377
+
378
+ def _compose_agent_info(self) -> ComposeResult:
379
+ agent_summary = self.job_data.get("agent_summary", {})
380
+ if not agent_summary:
381
+ return
382
+
383
+ active_count = agent_summary.get("active_count", 0)
384
+ cached_fixes = agent_summary.get("cached_fixes", 0)
385
+
386
+ if active_count > 0 or cached_fixes > 0:
387
+ agent_text = f"🤖 Agents: {active_count} active"
388
+ if cached_fixes > 0:
389
+ agent_text += f", {cached_fixes} cached"
390
+
391
+ agents_data = agent_summary.get("agents", [])
392
+ if agents_data:
393
+ avg_confidence = sum(
394
+ agent.get("confidence", 0) for agent in agents_data
395
+ ) / max(len(agents_data), 1)
396
+ if avg_confidence > 0:
397
+ agent_text += f", {avg_confidence: .0 % } conf"
398
+ yield Label(agent_text)
399
+
400
+ def _compose_warning_messages(self) -> ComposeResult:
401
+ if self.iteration_count >= 10:
402
+ yield Label("⚠️ Max iterations reached")
403
+
404
+ def _compose_errors_column(self) -> ComposeResult:
405
+ with Container(classes="job-errors"):
406
+ yield DataTable(
407
+ id=f"job-errors -{self.job_data.get('job_id', 'unknown')}",
408
+ )
409
+
410
+
411
+ class CrackerjackDashboard(App):
412
+ ENABLE_COMMAND_PALETTE = False
413
+ CSS_PATH = Path(__file__).parent / "progress_monitor.tcss"
414
+
415
+ BINDINGS = [
416
+ ("q", "quit", "Quit"),
417
+ ]
418
+
419
+ def __init__(self) -> None:
420
+ super().__init__()
421
+ self.progress_dir = Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
422
+ self.websocket_url = "ws://localhost:8675"
423
+ self.refresh_timer: t.Any | None = None # Timer object or None
424
+ self._refresh_counter = 0
425
+ self.dev = False
426
+ self.active_jobs: dict[str, t.Any] = {}
427
+ self.completed_jobs_stats: dict[str, t.Any] = {}
428
+ self.current_polling_method = "File"
429
+ self.timeout_manager = get_timeout_manager()
430
+
431
+ self.job_collector = JobDataCollector(self.progress_dir, self.websocket_url)
432
+ self.service_checker = ServiceHealthChecker()
433
+ self.error_collector = ErrorCollector()
434
+ self.service_manager = ServiceManager()
435
+ self.terminal_restorer = TerminalRestorer()
436
+
437
+ def compose(self) -> ComposeResult:
438
+ with Container(id="main-container"):
439
+ yield from self._compose_top_section()
440
+ yield from self._compose_discovery_section()
441
+ yield Footer()
442
+
443
+ def _compose_top_section(self) -> ComposeResult:
444
+ with Container(id="top-section"):
445
+ yield from self._compose_left_column()
446
+ yield from self._compose_right_column()
447
+
448
+ def _compose_left_column(self) -> ComposeResult:
449
+ with Container(id="left-column"):
450
+ yield from self._compose_jobs_panel()
451
+ yield AgentStatusPanel(id="agent - status-panel")
452
+
453
+ def _compose_right_column(self) -> ComposeResult:
454
+ with Container(id="right-column"):
455
+ yield from self._compose_errors_panel()
456
+ yield from self._compose_services_panel()
457
+
458
+ def _compose_jobs_panel(self) -> ComposeResult:
459
+ with Container(id="jobs-panel"):
460
+ yield DataTable(
461
+ id="jobs-table",
462
+ )
463
+
464
+ def _compose_errors_panel(self) -> ComposeResult:
465
+ with Container(id="errors-panel"):
466
+ yield DataTable(
467
+ id="errors-table",
468
+ )
469
+
470
+ def _compose_services_panel(self) -> ComposeResult:
471
+ with Container(id="services-panel"):
472
+ yield DataTable(
473
+ id="services-table",
474
+ zebra_stripes=True,
475
+ )
476
+
477
+ def _compose_discovery_section(self) -> ComposeResult:
478
+ with Container(id="discovery-section"):
479
+ yield Container(id="job - discovery-container")
480
+
481
+ def on_mount(self) -> None:
482
+ self._setup_border_titles()
483
+ self._setup_datatables()
484
+
485
+ asyncio.create_task(self._ensure_services_running())
486
+ self._start_refresh_timer()
487
+
488
+ atexit.register(self._restore_terminal_fallback)
489
+
490
+ signal.signal(signal.SIGINT, self._signal_handler)
491
+ signal.signal(signal.SIGTERM, self._signal_handler)
492
+
493
+ def on_unmount(self) -> None:
494
+ with suppress(Exception):
495
+ self._cleanup_started_services()
496
+
497
+ def _setup_border_titles(self) -> None:
498
+ self.query_one("#top-section").border_title = "🚀 Crackerjack Dashboard"
499
+ self.query_one("#jobs-panel").border_title = "📊 Issue Metrics"
500
+ self.query_one("#errors-panel").border_title = "❌ Error Tracking"
501
+ self.query_one("#services-panel").border_title = "🔧 Service Health"
502
+ self.query_one("#discovery-section").border_title = "🔍 Active Jobs"
503
+
504
+ def _setup_datatables(self) -> None:
505
+ jobs_table = self.query_one("#jobs-table", DataTable)
506
+ jobs_table.add_columns("", "", "", "")
507
+
508
+ services_table = self.query_one("#services-table", DataTable)
509
+ services_table.add_columns("Service", "Status", "Restarts")
510
+
511
+ errors_table = self.query_one("#errors-table", DataTable)
512
+ errors_table.add_columns("", "", "", "")
513
+
514
+ self._show_default_values()
515
+
516
+ asyncio.create_task(self._refresh_data())
517
+
518
+ def _show_default_values(self) -> None:
519
+ default_jobs_data = {
520
+ "active": 0,
521
+ "completed": 0,
522
+ "failed": 0,
523
+ "total": 0,
524
+ "individual_jobs": [],
525
+ }
526
+ self._update_jobs_table(default_jobs_data)
527
+
528
+ self._update_errors_table([])
529
+
530
+ async def _ensure_services_running(self) -> None:
531
+ await self.service_manager.ensure_services_running()
532
+
533
+ def _start_refresh_timer(self) -> None:
534
+ self.refresh_timer = self.set_interval(0.5, self._refresh_data)
535
+
536
+ async def _refresh_data(self) -> None:
537
+ try:
538
+ async with self.timeout_manager.timeout_context(
539
+ "network_operations",
540
+ timeout=10.0,
541
+ strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
542
+ ):
543
+ self._refresh_counter += 1
544
+
545
+ if self._refresh_counter % 20 == 0:
546
+ with suppress(Exception):
547
+ await self.timeout_manager.with_timeout(
548
+ "network_operations",
549
+ self._ensure_services_running(),
550
+ timeout=5.0,
551
+ strategy=TimeoutStrategy.FAIL_FAST,
552
+ )
553
+
554
+ jobs_data = await self.timeout_manager.with_timeout(
555
+ "network_operations",
556
+ self._discover_jobs(),
557
+ timeout=3.0,
558
+ strategy=TimeoutStrategy.FAIL_FAST,
559
+ )
560
+
561
+ services_data = await self.timeout_manager.with_timeout(
562
+ "network_operations",
563
+ self._collect_services_data(),
564
+ timeout=2.0,
565
+ strategy=TimeoutStrategy.FAIL_FAST,
566
+ )
567
+
568
+ errors_data = await self.timeout_manager.with_timeout(
569
+ "file_operations",
570
+ self._collect_recent_errors(),
571
+ timeout=2.0,
572
+ strategy=TimeoutStrategy.FAIL_FAST,
573
+ )
574
+
575
+ self.query_one("#services-panel").border_title = "🔧 Services"
576
+
577
+ self._update_jobs_table(jobs_data)
578
+ self._update_services_table(services_data)
579
+ self._update_errors_table(errors_data)
580
+ self._update_job_panels(jobs_data)
581
+ self._update_agent_panel(jobs_data)
582
+ self._update_status_bars(jobs_data)
583
+
584
+ except Exception as e:
585
+ with suppress(Exception):
586
+ console.print(f"[red]Dashboard refresh error: {e}[/red]")
587
+
588
+ async def _discover_jobs(self) -> dict[str, t.Any]:
589
+ try:
590
+ result = await self.timeout_manager.with_timeout(
591
+ "network_operations",
592
+ self.job_collector.discover_jobs(),
593
+ timeout=5.0,
594
+ strategy=TimeoutStrategy.FAIL_FAST,
595
+ )
596
+ self.current_polling_method = result["method"]
597
+ data_result = result["data"]
598
+ return t.cast(dict[str, t.Any], data_result)
599
+ except Exception:
600
+ return {
601
+ "active": 0,
602
+ "completed": 0,
603
+ "failed": 0,
604
+ "total": 0,
605
+ "individual_jobs": [],
606
+ "total_issues": 0,
607
+ "errors_fixed": 0,
608
+ "errors_failed": 0,
609
+ "current_errors": 0,
610
+ }
611
+
612
+ async def _collect_services_data(self) -> list[t.Any]:
613
+ try:
614
+ services_data = await self.timeout_manager.with_timeout(
615
+ "network_operations",
616
+ self.service_checker.collect_services_data(),
617
+ timeout=3.0,
618
+ strategy=TimeoutStrategy.FAIL_FAST,
619
+ )
620
+ return t.cast(list[t.Any], services_data)
621
+ except Exception:
622
+ return [("Services", "🔴 Timeout", "0")]
623
+
624
+ async def _collect_recent_errors(self) -> list[t.Any]:
625
+ try:
626
+ errors_data = await self.timeout_manager.with_timeout(
627
+ "file_operations",
628
+ self.error_collector.collect_recent_errors(),
629
+ timeout=2.0,
630
+ strategy=TimeoutStrategy.FAIL_FAST,
631
+ )
632
+ return t.cast(list[t.Any], errors_data)
633
+ except Exception:
634
+ return []
635
+
636
+ def _update_jobs_table(self, jobs_data: dict[str, t.Any]) -> None:
637
+ with suppress(Exception):
638
+ jobs_table = self.query_one("#jobs-table", DataTable)
639
+ jobs_table.clear()
640
+
641
+ total_issues = jobs_data.get("total_issues", 0)
642
+ errors_fixed = jobs_data.get("errors_fixed", 0)
643
+ errors_failed = jobs_data.get("errors_failed", 0)
644
+ jobs_data.get("current_errors", 0)
645
+
646
+ for job_stats in self.completed_jobs_stats.values():
647
+ total_issues += job_stats.get("total_issues", 0)
648
+ errors_fixed += job_stats.get("errors_fixed", 0)
649
+ errors_failed += job_stats.get("errors_failed", 0)
650
+
651
+ remaining_errors = max(0, total_issues - errors_fixed)
652
+
653
+ discovered_label = "🔍 Found"
654
+ discovered_value = f"{total_issues: > 12}"
655
+ resolved_label = "✅ Fixed"
656
+ resolved_value = f"{errors_fixed: > 12}"
657
+
658
+ remaining_label = "❌ Left"
659
+ remaining_value = f"{remaining_errors: > 12}"
660
+ progress_label = "📈 Done"
661
+ progress_pct = (
662
+ int(errors_fixed / total_issues * 100) if total_issues > 0 else 0
663
+ )
664
+ progress_value = f"{progress_pct} % ".rjust(12)
665
+
666
+ jobs_table.add_rows(
667
+ [
668
+ (
669
+ discovered_label,
670
+ discovered_value,
671
+ resolved_label,
672
+ resolved_value,
673
+ ),
674
+ (remaining_label, remaining_value, progress_label, progress_value),
675
+ ],
676
+ )
677
+
678
+ def _update_services_table(self, services_data: list[t.Any]) -> None:
679
+ with suppress(Exception):
680
+ services_table = self.query_one("#services-table", DataTable)
681
+ services_table.clear()
682
+
683
+ for service in services_data:
684
+ service_name = (
685
+ service[0]
686
+ .replace("WebSocket Server", "WebSocket")
687
+ .replace(" Server", "")
688
+ )
689
+
690
+ status_text = service[1] or "❓ Unknown"
691
+ restart_count = service[2] if len(service) > 2 else "0"
692
+ restart_value = f"{restart_count: ^ 8}"
693
+ services_table.add_row(service_name, status_text, restart_value)
694
+
695
+ method_emoji = "🌐" if self.current_polling_method == "WebSocket" else "📁"
696
+ polling_status = f"{method_emoji} {self.current_polling_method}"
697
+
698
+ if self.current_polling_method == "WebSocket":
699
+ polling_status += " 🟢"
700
+ services_table.add_row("Polling", polling_status, "")
701
+
702
+ def _update_errors_table(self, errors_data: list[t.Any]) -> None:
703
+ with suppress(Exception):
704
+ errors_table = self.query_one("#errors-table", DataTable)
705
+ errors_table.clear()
706
+
707
+ job_errors = (
708
+ [
709
+ e
710
+ for e in errors_data
711
+ if "crackerjack" in str(e).lower() and "job" in str(e).lower()
712
+ ]
713
+ if errors_data
714
+ else []
715
+ )
716
+
717
+ active_errors = 0
718
+ fixed_errors = 0
719
+ total_errors = 0
720
+
721
+ if job_errors:
722
+ total_errors = len(job_errors)
723
+ active_errors = sum(
724
+ 1
725
+ for e in job_errors
726
+ if "running" in str(e).lower() or "active" in str(e).lower()
727
+ )
728
+ sum(
729
+ 1
730
+ for e in job_errors
731
+ if "failed" in str(e).lower() or "error" in str(e).lower()
732
+ )
733
+ fixed_errors = sum(
734
+ 1
735
+ for e in job_errors
736
+ if "fixed" in str(e).lower() or "resolved" in str(e).lower()
737
+ )
738
+
739
+ discovered_label = "🔍 Found"
740
+ discovered_value = f"{total_errors: > 12}"
741
+ resolved_label = "✅ Fixed"
742
+ resolved_value = f"{fixed_errors: > 12}"
743
+
744
+ remaining_label = "❌ Left"
745
+ remaining_value = f"{active_errors: > 12}"
746
+ progress_label = "📈 Done"
747
+ progress_pct = (
748
+ int(fixed_errors / total_errors * 100) if total_errors > 0 else 0
749
+ )
750
+ progress_value = f"{progress_pct} % ".rjust(12)
751
+
752
+ errors_table.add_rows(
753
+ [
754
+ (
755
+ discovered_label,
756
+ discovered_value,
757
+ resolved_label,
758
+ resolved_value,
759
+ ),
760
+ (remaining_label, remaining_value, progress_label, progress_value),
761
+ ],
762
+ )
763
+
764
+ def _update_agent_panel(self, jobs_data: dict[str, t.Any]) -> None:
765
+ with suppress(Exception):
766
+ agent_panel = self.query_one("#agent - status-panel", AgentStatusPanel)
767
+
768
+ agent_data = {}
769
+ for job in jobs_data.get("individual_jobs", []):
770
+ if "agent_activity" in job or "agent_performance" in job:
771
+ agent_data = job
772
+ break
773
+
774
+ if agent_data:
775
+ agent_panel.update_agent_data(agent_data)
776
+
777
+ def _update_job_panels(self, jobs_data: dict[str, t.Any]) -> None:
778
+ with suppress(Exception):
779
+ container = self.query_one("#job - discovery-container")
780
+ current_job_ids = self._get_current_job_ids(jobs_data)
781
+
782
+ self._remove_obsolete_panels(current_job_ids)
783
+ self._update_or_create_panels(jobs_data, container)
784
+ self._handle_placeholder_visibility(container)
785
+
786
+ def _get_current_job_ids(self, jobs_data: dict[str, t.Any]) -> set[t.Any]:
787
+ return (
788
+ {job["job_id"] for job in jobs_data["individual_jobs"]}
789
+ if jobs_data["individual_jobs"]
790
+ else set()
791
+ )
792
+
793
+ def _remove_obsolete_panels(self, current_job_ids: set[t.Any]) -> None:
794
+ jobs_to_remove = []
795
+ for job_id, panel in self.active_jobs.items():
796
+ panel_status = panel.job_data.get("status", "").lower()
797
+ if (
798
+ job_id not in current_job_ids
799
+ and panel_status not in ("completed", "failed")
800
+ and panel.completion_time is None
801
+ ):
802
+ jobs_to_remove.append(job_id)
803
+
804
+ for job_id in jobs_to_remove:
805
+ panel = self.active_jobs.pop(job_id)
806
+ panel.remove()
807
+
808
+ def _update_or_create_panels(
809
+ self, jobs_data: dict[str, t.Any], container: t.Any
810
+ ) -> None:
811
+ if not jobs_data["individual_jobs"]:
812
+ return
813
+
814
+ for job in jobs_data["individual_jobs"]:
815
+ job_id = job["job_id"]
816
+ if job_id in self.active_jobs:
817
+ self._update_existing_panel(job)
818
+ else:
819
+ self._create_new_panel(job, container)
820
+
821
+ def _update_existing_panel(self, job: dict[str, t.Any]) -> None:
822
+ existing_panel = self.active_jobs[job["job_id"]]
823
+ existing_panel.job_data = job
824
+ existing_panel.iteration_count = job.get("iteration", 0)
825
+
826
+ self._update_panel_title(existing_panel, job)
827
+ existing_panel._update_errors_table()
828
+ existing_panel._update_progress_bar()
829
+ self._handle_job_completion(existing_panel, job)
830
+ self._update_panel_border(existing_panel)
831
+
832
+ def _update_panel_title(self, panel: t.Any, job: dict[str, t.Any]) -> None:
833
+ project_name = job.get("project", "crackerjack")
834
+ status = job.get("status", "").lower()
835
+
836
+ panel.border_title = f"📁 {project_name}"
837
+ panel.border_title_align = "left"
838
+
839
+ if status == "running":
840
+ panel.border_subtitle = "💓"
841
+ panel.border_subtitle_align = "right"
842
+ else:
843
+ panel.border_subtitle = ""
844
+
845
+ def _handle_job_completion(self, panel: t.Any, job: dict[str, t.Any]) -> None:
846
+ job_status = job.get("status", "").lower()
847
+ if job_status in ("completed", "failed") and panel.completion_time is None:
848
+ panel.completion_time = time.time()
849
+ panel.fade_timer = panel.set_timer(300.0, panel._start_fade)
850
+ panel.remove_timer = panel.set_timer(1200.0, panel._remove_panel)
851
+
852
+ def _update_panel_border(self, panel: t.Any) -> None:
853
+ new_border = panel._calculate_border_style()
854
+ if new_border != panel.border_style:
855
+ panel.border_style = new_border
856
+ panel.refresh()
857
+
858
+ def _create_new_panel(self, job: dict[str, t.Any], container: t.Any) -> None:
859
+ job_panel = JobPanel(job)
860
+ self.active_jobs[job["job_id"]] = job_panel
861
+ container.mount(job_panel)
862
+
863
+ def _handle_placeholder_visibility(self, container: t.Any) -> None:
864
+ has_placeholder = bool(container.query("#no - jobs-label"))
865
+
866
+ if not self.active_jobs and not has_placeholder:
867
+ container.mount(
868
+ Label(
869
+ "No active jobs detected. Start a Crackerjack job to see progress here.",
870
+ id="no - jobs-label",
871
+ ),
872
+ )
873
+ elif self.active_jobs and has_placeholder:
874
+ container.query("#no - jobs-label").remove()
875
+
876
+ def _update_status_bars(self, jobs_data: dict[str, t.Any]) -> None:
877
+ pass
878
+
879
+ def action_refresh(self) -> None:
880
+ asyncio.create_task(self._refresh_data())
881
+
882
+ def action_clear(self) -> None:
883
+ with suppress(Exception):
884
+ for table_id in ("#jobs-table", "#services-table", "#errors-table"):
885
+ table = self.query_one(table_id, DataTable)
886
+ table.clear()
887
+
888
+ container = self.query_one("#job - discovery-container")
889
+ container.query("JobPanel").remove()
890
+ container.query("Label").remove()
891
+ self.active_jobs.clear()
892
+
893
+ async def action_quit(self) -> None:
894
+ with suppress(Exception):
895
+ if self.refresh_timer:
896
+ self.refresh_timer.stop()
897
+ self._cleanup_started_services()
898
+ self._restore_terminal()
899
+ self.exit()
900
+
901
+ def _restore_terminal(self) -> None:
902
+ self.terminal_restorer.restore_terminal()
903
+
904
+ def _restore_terminal_fallback(self) -> None:
905
+ self.terminal_restorer.restore_terminal()
906
+
907
+ def _signal_handler(self, _signum: t.Any, _frame: t.Any) -> None:
908
+ with suppress(Exception):
909
+ self._restore_terminal()
910
+ self._cleanup_started_services()
911
+ sys.exit(0)
912
+
913
+ def _cleanup_started_services(self) -> None:
914
+ self.service_manager.cleanup_services()
915
+
916
+ def _format_time_metric(self, seconds: float) -> str:
917
+ if seconds < 60:
918
+ return f"{seconds: .0f}s"
919
+ if seconds < 3600:
920
+ return f"{seconds / 60: .0f}m {seconds % 60: .0f}s"
921
+ return f"{seconds / 3600: .0f}h {(seconds % 3600) / 60: .0f}m"
922
+
923
+ def _format_metric_with_trend(self, value: int, trend: str = "") -> str:
924
+ formatted = f"{value: , }"
925
+ if trend:
926
+ formatted += f" {trend}"
927
+ return formatted
928
+
929
+
930
+ class JobMetrics:
931
+ def __init__(self, job_id: str, project_path: str = "") -> None:
932
+ self.job_id = job_id
933
+ self.project_path = project_path
934
+ self.project_name = Path(project_path).name if project_path else "crackerjack"
935
+ self.start_time = time.time()
936
+ self.last_update = time.time()
937
+ self.completion_time: float | None = None
938
+
939
+ self.iteration = 0
940
+ self.max_iterations = 5
941
+ self.current_stage = "Initializing"
942
+ self.status = "running"
943
+ self.message = ""
944
+
945
+ self.stages_completed: set[str] = set()
946
+ self.stages_failed: set[str] = set()
947
+
948
+ self.errors: list[str] = []
949
+ self.warnings: list[str] = []
950
+ self.hook_failures: list[str] = []
951
+ self.test_failures: list[str] = []
952
+
953
+
954
+ async def run_progress_monitor(
955
+ enable_watchdog: bool = True,
956
+ dev_mode: bool = False,
957
+ ) -> None:
958
+ with suppress(Exception):
959
+ console.print(
960
+ "[bold green]🚀 Starting Crackerjack Progress Monitor[/ bold green]",
961
+ )
962
+
963
+ if enable_watchdog:
964
+ console.print("[bold yellow]🐕 Service Watchdog: Enabled[/ bold yellow]")
965
+
966
+ if dev_mode:
967
+ console.print("[bold cyan]🛠️ Development Mode: Enabled[/ bold cyan]")
968
+
969
+ app = CrackerjackDashboard()
970
+
971
+ if dev_mode:
972
+ app.dev = True
973
+
974
+ await app.run_async()
975
+
976
+
977
+ async def run_crackerjack_with_progress(
978
+ command: str = " / crackerjack: run",
979
+ ) -> None:
980
+ with suppress(Exception):
981
+ console.print(
982
+ "[bold green]🚀 Starting Crackerjack Progress Monitor[/ bold green]",
983
+ )
984
+
985
+ app = CrackerjackDashboard()
986
+ await app.run_async()
987
+
988
+
989
+ def main() -> None:
990
+ try:
991
+ app = CrackerjackDashboard()
992
+ app.run()
993
+ except KeyboardInterrupt:
994
+ with suppress(Exception):
995
+ sys.stdout.write("\033[?25h\033[0m")
996
+ sys.stdout.flush()
997
+ subprocess.run(
998
+ ["stty", "sane"],
999
+ check=False,
1000
+ capture_output=True,
1001
+ timeout=1,
1002
+ )
1003
+ except Exception:
1004
+ with suppress(Exception):
1005
+ sys.stdout.write("\033[?25h\033[0m")
1006
+ sys.stdout.flush()
1007
+ subprocess.run(
1008
+ ["stty", "sane"],
1009
+ check=False,
1010
+ capture_output=True,
1011
+ timeout=1,
1012
+ )
1013
+
1014
+
1015
+ if __name__ == "__main__":
1016
+ main()