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,802 @@
1
+ import asyncio
2
+ import contextlib
3
+ import io
4
+ import os
5
+ import subprocess
6
+ import tempfile
7
+ import time
8
+ import typing as t
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from types import TracebackType
12
+
13
+ from acb.console import Console
14
+ from acb.depends import depends
15
+
16
+ from crackerjack.core.resource_manager import (
17
+ ResourceManager,
18
+ register_global_resource_manager,
19
+ )
20
+ from crackerjack.core.websocket_lifecycle import NetworkResourceManager
21
+ from crackerjack.core.workflow_orchestrator import WorkflowOrchestrator
22
+ from crackerjack.services.secure_path_utils import SecurePathValidator
23
+
24
+ from .cache import ErrorCache
25
+ from .rate_limiter import RateLimitConfig, RateLimitMiddleware
26
+ from .state import StateManager
27
+
28
+
29
+ class BatchedStateSaver:
30
+ def __init__(self, debounce_delay: float = 1.0, max_batch_size: int = 10) -> None:
31
+ self.debounce_delay = debounce_delay
32
+ self.max_batch_size = max_batch_size
33
+
34
+ self._pending_saves: dict[str, t.Callable[[], None]] = {}
35
+ self._last_save_time: dict[str, float] = {}
36
+
37
+ self._save_task: asyncio.Task[None] | None = None
38
+ self._running = False
39
+ self._lock = asyncio.Lock()
40
+
41
+ async def start(self) -> None:
42
+ if self._running:
43
+ return
44
+
45
+ self._running = True
46
+ self._save_task = asyncio.create_task(self._save_loop())
47
+
48
+ async def stop(self) -> None:
49
+ self._running = False
50
+
51
+ if self._save_task:
52
+ self._save_task.cancel()
53
+ with contextlib.suppress(asyncio.CancelledError):
54
+ await self._save_task
55
+
56
+ await self._flush_saves()
57
+
58
+ async def schedule_save(
59
+ self,
60
+ save_id: str,
61
+ save_func: t.Callable[[], None],
62
+ ) -> None:
63
+ async with self._lock:
64
+ self._pending_saves[save_id] = save_func
65
+ self._last_save_time[save_id] = time.time()
66
+
67
+ if len(self._pending_saves) >= self.max_batch_size:
68
+ await self._flush_saves()
69
+
70
+ async def _save_loop(self) -> None:
71
+ while self._running:
72
+ try:
73
+ await asyncio.sleep(self.debounce_delay)
74
+ ready_saves = await self._get_ready_saves()
75
+
76
+ if ready_saves:
77
+ await self._execute_saves(ready_saves)
78
+
79
+ except asyncio.CancelledError:
80
+ break
81
+ except Exception:
82
+ await asyncio.sleep(1)
83
+
84
+ async def _get_ready_saves(self) -> list[str]:
85
+ now = time.time()
86
+ ready_saves = []
87
+
88
+ async with self._lock:
89
+ for save_id, last_time in list[t.Any](self._last_save_time.items()):
90
+ if now - last_time >= self.debounce_delay:
91
+ ready_saves.append(save_id)
92
+
93
+ return ready_saves
94
+
95
+ async def _execute_saves(self, save_ids: list[str]) -> None:
96
+ async with self._lock:
97
+ saves_to_execute = []
98
+
99
+ for save_id in save_ids:
100
+ if save_id in self._pending_saves:
101
+ saves_to_execute.append((save_id, self._pending_saves.pop(save_id)))
102
+ self._last_save_time.pop(save_id, None)
103
+
104
+ for save_id, save_func in saves_to_execute:
105
+ with contextlib.suppress(Exception):
106
+ save_func()
107
+
108
+ async def _flush_saves(self) -> None:
109
+ async with self._lock:
110
+ save_ids = list[t.Any](self._pending_saves.keys())
111
+
112
+ if save_ids:
113
+ await self._execute_saves(save_ids)
114
+
115
+ def get_stats(self) -> dict[str, t.Any]:
116
+ return {
117
+ "running": self._running,
118
+ "pending_saves": len(self._pending_saves),
119
+ "debounce_delay": self.debounce_delay,
120
+ "max_batch_size": self.max_batch_size,
121
+ }
122
+
123
+
124
+ @dataclass
125
+ class MCPServerConfig:
126
+ project_path: Path
127
+ progress_dir: Path | None = None
128
+ rate_limit_config: RateLimitConfig | None = None
129
+ stdio_mode: bool = True
130
+ state_dir: Path | None = None
131
+ cache_dir: Path | None = None
132
+
133
+ def __post_init__(self) -> None:
134
+ self.project_path = SecurePathValidator.validate_safe_path(self.project_path)
135
+
136
+ if self.progress_dir:
137
+ self.progress_dir = SecurePathValidator.validate_safe_path(
138
+ self.progress_dir
139
+ )
140
+
141
+ if self.state_dir:
142
+ self.state_dir = SecurePathValidator.validate_safe_path(self.state_dir)
143
+
144
+ if self.cache_dir:
145
+ self.cache_dir = SecurePathValidator.validate_safe_path(self.cache_dir)
146
+
147
+
148
+ class MCPServerContext:
149
+ def __init__(self, config: MCPServerConfig) -> None:
150
+ self.config = config
151
+
152
+ self.resource_manager = ResourceManager()
153
+ self.network_manager = NetworkResourceManager()
154
+ register_global_resource_manager(self.resource_manager)
155
+
156
+ self.console: Console | None = None
157
+ self.cli_runner: WorkflowOrchestrator | None = None
158
+ self.state_manager: StateManager | None = None
159
+ self.error_cache: ErrorCache | None = None
160
+ self.rate_limiter: RateLimitMiddleware | None = None
161
+ self.batched_saver: BatchedStateSaver = BatchedStateSaver()
162
+
163
+ self.progress_dir = config.progress_dir or (
164
+ Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
165
+ )
166
+ self.progress_queue: asyncio.Queue[dict[str, t.Any]] = asyncio.Queue(
167
+ maxsize=1000,
168
+ )
169
+
170
+ self.websocket_server_process: subprocess.Popen[bytes] | None = None
171
+ self.websocket_server_port: int = int(
172
+ os.environ.get("CRACKERJACK_WEBSOCKET_PORT", "8675"),
173
+ )
174
+ self._websocket_process_lock = asyncio.Lock()
175
+ self._websocket_cleanup_registered = False
176
+ self._websocket_health_check_task: asyncio.Task[None] | None = None
177
+
178
+ self._initialized = False
179
+ self._startup_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
180
+ self._shutdown_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
181
+
182
+ async def _auto_setup_git_working_directory(self) -> None:
183
+ """Auto-detect and setup git working directory for enhanced DX."""
184
+ try:
185
+ git_root = await self._detect_git_repository()
186
+ if git_root:
187
+ await self._log_git_detection(git_root)
188
+
189
+ except Exception as e:
190
+ self._handle_git_setup_failure(e)
191
+
192
+ async def _detect_git_repository(self) -> Path | None:
193
+ """Detect if we're in a git repository and return the root path."""
194
+
195
+ current_dir = Path.cwd()
196
+
197
+ # Check if we're in a git repository
198
+ if not self._is_git_repository(current_dir):
199
+ return None
200
+
201
+ return self._get_git_root_directory(current_dir)
202
+
203
+ def _is_git_repository(self, current_dir: Path) -> bool:
204
+ """Check if the current directory is within a git repository."""
205
+ import subprocess
206
+
207
+ git_check = subprocess.run(
208
+ ["git", "rev-parse", "--is-inside-work-tree"],
209
+ capture_output=True,
210
+ text=True,
211
+ cwd=current_dir,
212
+ )
213
+ return git_check.returncode == 0
214
+
215
+ def _get_git_root_directory(self, current_dir: Path) -> Path | None:
216
+ """Get the git repository root directory."""
217
+ import subprocess
218
+
219
+ git_root_result = subprocess.run(
220
+ ["git", "rev-parse", "--show-toplevel"],
221
+ capture_output=True,
222
+ text=True,
223
+ cwd=current_dir,
224
+ )
225
+
226
+ if git_root_result.returncode == 0:
227
+ git_root = Path(git_root_result.stdout.strip())
228
+ return git_root if git_root.exists() else None
229
+ return None
230
+
231
+ async def _log_git_detection(self, git_root: Path) -> None:
232
+ """Log git repository detection to stderr and console."""
233
+
234
+ # Log to stderr for Claude to see
235
+ self._log_to_stderr(git_root)
236
+
237
+ # Log to console if available
238
+ self._log_to_console(git_root)
239
+
240
+ def _log_to_stderr(self, git_root: Path) -> None:
241
+ """Log git detection messages to stderr."""
242
+ import sys
243
+
244
+ print(
245
+ f"📍 Crackerjack MCP: Git repository detected at {git_root}",
246
+ file=sys.stderr,
247
+ )
248
+ print(
249
+ f"💡 Tip: Auto-setup git working directory with: git_set_working_dir('{git_root}')",
250
+ file=sys.stderr,
251
+ )
252
+
253
+ def _log_to_console(self, git_root: Path) -> None:
254
+ """Log git detection messages to console if available."""
255
+ if self.console:
256
+ self.console.print(f"🔧 Auto-detected git repository: {git_root}")
257
+ self.console.print(
258
+ f"💡 Recommend: Use `mcp__git__git_set_working_dir` with path='{git_root}'"
259
+ )
260
+
261
+ def _handle_git_setup_failure(self, error: Exception) -> None:
262
+ """Handle git setup failure with graceful fallback."""
263
+ if self.console:
264
+ self.console.print(
265
+ f"[dim]Git auto-setup failed (non-critical): {error}[/dim]"
266
+ )
267
+
268
+ async def initialize(self) -> None:
269
+ if self._initialized:
270
+ return
271
+
272
+ try:
273
+ await self._perform_initialization_sequence()
274
+ self._initialized = True
275
+
276
+ except Exception as e:
277
+ self._handle_initialization_failure(e)
278
+
279
+ async def _perform_initialization_sequence(self) -> None:
280
+ """Perform the complete initialization sequence."""
281
+ self._setup_console()
282
+ self._setup_directories()
283
+ await self._initialize_components()
284
+ await self._finalize_initialization()
285
+
286
+ def _handle_initialization_failure(self, error: Exception) -> None:
287
+ """Handle initialization failure with cleanup and error propagation."""
288
+ self._cleanup_failed_initialization()
289
+ msg = f"Failed to initialize MCP server context: {error}"
290
+ raise RuntimeError(msg) from error
291
+
292
+ def _setup_console(self) -> None:
293
+ """Setup console based on configuration mode."""
294
+ if self.config.stdio_mode:
295
+ io.StringIO()
296
+ self.console = depends.get_sync(Console)
297
+ else:
298
+ self.console = depends.get_sync(Console)
299
+
300
+ def _setup_directories(self) -> None:
301
+ """Setup required directories."""
302
+ self.progress_dir.mkdir(exist_ok=True)
303
+
304
+ async def _initialize_components(self) -> None:
305
+ """Initialize all service components."""
306
+ self.cli_runner = WorkflowOrchestrator(
307
+ pkg_path=self.config.project_path,
308
+ )
309
+
310
+ self.state_manager = StateManager(
311
+ self.config.state_dir or Path.home() / ".cache" / "crackerjack-mcp",
312
+ self.batched_saver,
313
+ )
314
+
315
+ self.error_cache = ErrorCache(
316
+ self.config.cache_dir or Path.home() / ".cache" / "crackerjack-mcp",
317
+ )
318
+
319
+ self.rate_limiter = RateLimitMiddleware(self.config.rate_limit_config)
320
+ await self.batched_saver.start()
321
+
322
+ async def _finalize_initialization(self) -> None:
323
+ """Complete initialization with optional setup and startup tasks."""
324
+ # Auto-setup git working directory for enhanced DX
325
+ await self._auto_setup_git_working_directory()
326
+
327
+ for task in self._startup_tasks:
328
+ await task()
329
+
330
+ def _cleanup_failed_initialization(self) -> None:
331
+ """Cleanup components after failed initialization."""
332
+ self.cli_runner = None
333
+ self.state_manager = None
334
+ self.error_cache = None
335
+ self.rate_limiter = None
336
+
337
+ async def shutdown(self) -> None:
338
+ if not self._initialized:
339
+ return
340
+
341
+ for task in reversed(self._shutdown_tasks):
342
+ try:
343
+ await task()
344
+ except Exception as e:
345
+ if self.console:
346
+ self.console.print(f"[red]Error during shutdown: {e}[/red]")
347
+
348
+ if self._websocket_health_check_task:
349
+ self._websocket_health_check_task.cancel()
350
+ with contextlib.suppress(asyncio.CancelledError):
351
+ await self._websocket_health_check_task
352
+ self._websocket_health_check_task = None
353
+
354
+ await self._stop_websocket_server()
355
+
356
+ if self.rate_limiter:
357
+ await self.rate_limiter.stop()
358
+
359
+ await self.batched_saver.stop()
360
+
361
+ try:
362
+ await self.network_manager.cleanup_all()
363
+ except Exception as e:
364
+ if self.console:
365
+ self.console.print(
366
+ f"[yellow]Warning: Network resource cleanup error: {e}[/yellow]"
367
+ )
368
+
369
+ try:
370
+ await self.resource_manager.cleanup_all()
371
+ except Exception as e:
372
+ if self.console:
373
+ self.console.print(
374
+ f"[yellow]Warning: Resource cleanup error: {e}[/yellow]"
375
+ )
376
+
377
+ self._initialized = False
378
+
379
+ def add_startup_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
380
+ self._startup_tasks.append(task)
381
+
382
+ def add_shutdown_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
383
+ self._shutdown_tasks.append(task)
384
+
385
+ def validate_job_id(self, job_id: str) -> bool:
386
+ if not job_id:
387
+ return False
388
+
389
+ import uuid
390
+ from contextlib import suppress
391
+
392
+ with suppress(ValueError):
393
+ uuid.UUID(job_id)
394
+ return True
395
+
396
+ from crackerjack.services.regex_patterns import is_valid_job_id
397
+
398
+ if not is_valid_job_id(job_id):
399
+ return False
400
+
401
+ if ".." in job_id or "/" in job_id or "\\" in job_id:
402
+ return False
403
+
404
+ import os
405
+
406
+ return os.path.basename(job_id) == job_id
407
+
408
+ async def check_websocket_server_running(self) -> bool:
409
+ import socket
410
+
411
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
412
+ sock.settimeout(1.0)
413
+ result = sock.connect_ex(("localhost", self.websocket_server_port))
414
+ return result == 0
415
+
416
+ async def start_websocket_server(self) -> bool:
417
+ async with self._websocket_process_lock:
418
+ if await self._check_existing_websocket_server():
419
+ return True
420
+
421
+ self._print_websocket_startup_message()
422
+ return await self._attempt_websocket_startup()
423
+
424
+ def _print_websocket_startup_message(self) -> None:
425
+ """Print websocket server startup message."""
426
+ if self.console:
427
+ self.console.print(
428
+ f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}",
429
+ )
430
+
431
+ async def _attempt_websocket_startup(self) -> bool:
432
+ """Attempt to start the websocket server with error handling."""
433
+ try:
434
+ await self._spawn_websocket_process()
435
+ await self._register_websocket_cleanup()
436
+ return await self._wait_for_websocket_startup()
437
+ except Exception as e:
438
+ await self._handle_websocket_startup_failure(e)
439
+ return False
440
+
441
+ async def _handle_websocket_startup_failure(self, error: Exception) -> None:
442
+ """Handle websocket server startup failure."""
443
+ if self.console:
444
+ self.console.print(f"❌ Failed to start WebSocket server: {error}")
445
+ await self._cleanup_dead_websocket_process()
446
+
447
+ async def _check_existing_websocket_server(self) -> bool:
448
+ if (
449
+ self.websocket_server_process
450
+ and self.websocket_server_process.poll() is None
451
+ ):
452
+ if await self.check_websocket_server_running():
453
+ if self.console:
454
+ self.console.print(
455
+ f"✅ WebSocket server already running on port {self.websocket_server_port}",
456
+ )
457
+ return True
458
+ await self._cleanup_dead_websocket_process()
459
+
460
+ if await self.check_websocket_server_running():
461
+ if self.console:
462
+ self.console.print(
463
+ f"⚠️ Port {self.websocket_server_port} already in use by another process",
464
+ )
465
+ return True
466
+
467
+ return False
468
+
469
+ async def _spawn_websocket_process(self) -> None:
470
+ import sys
471
+
472
+ self.websocket_server_process = subprocess.Popen(
473
+ [
474
+ sys.executable,
475
+ "-m",
476
+ "crackerjack",
477
+ "--start-websocket-server",
478
+ "--websocket-port",
479
+ str(self.websocket_server_port),
480
+ ],
481
+ stdout=subprocess.DEVNULL,
482
+ stderr=subprocess.DEVNULL,
483
+ start_new_session=True,
484
+ )
485
+
486
+ if self.websocket_server_process:
487
+ managed_process = self.network_manager.create_subprocess(
488
+ self.websocket_server_process, timeout=30.0
489
+ )
490
+ await managed_process.start_monitoring()
491
+
492
+ async def _register_websocket_cleanup(self) -> None:
493
+ if not self._websocket_cleanup_registered:
494
+ self.add_shutdown_task(self._stop_websocket_server)
495
+ self._websocket_cleanup_registered = True
496
+
497
+ if not self._websocket_health_check_task:
498
+ self._websocket_health_check_task = asyncio.create_task(
499
+ self._websocket_health_monitor(),
500
+ )
501
+
502
+ async def _wait_for_websocket_startup(self) -> bool:
503
+ max_attempts = 10
504
+ for _attempt in range(max_attempts):
505
+ await asyncio.sleep(0.5)
506
+
507
+ if (
508
+ self.websocket_server_process is not None
509
+ and self.websocket_server_process.poll() is not None
510
+ ):
511
+ return_code = self.websocket_server_process.returncode
512
+ if self.console:
513
+ self.console.print(
514
+ f"❌ WebSocket server process died during startup (exit code: {return_code})",
515
+ )
516
+ self.websocket_server_process = None
517
+ return False
518
+
519
+ if await self.check_websocket_server_running():
520
+ if self.console:
521
+ self.console.print(
522
+ f"✅ WebSocket server started successfully on port {self.websocket_server_port}",
523
+ )
524
+ self.console.print(
525
+ f"📊 Progress available at: ws: / / localhost: {self.websocket_server_port}/ ws / progress /{{job_id}}",
526
+ )
527
+ return True
528
+
529
+ if self.console:
530
+ self.console.print(
531
+ f"❌ WebSocket server failed to start within {max_attempts * 0.5}s",
532
+ )
533
+ await self._cleanup_dead_websocket_process()
534
+ return False
535
+
536
+ async def _cleanup_dead_websocket_process(self) -> None:
537
+ if self.websocket_server_process:
538
+ try:
539
+ if (
540
+ self.websocket_server_process is not None
541
+ and self.websocket_server_process.poll() is None
542
+ ):
543
+ self.websocket_server_process.terminate()
544
+ try:
545
+ self.websocket_server_process.wait(timeout=2)
546
+ except subprocess.TimeoutExpired:
547
+ self.websocket_server_process.kill()
548
+ self.websocket_server_process.wait(timeout=1)
549
+
550
+ if self.console:
551
+ self.console.print("🧹 Cleaned up dead WebSocket server process")
552
+ except Exception as e:
553
+ if self.console:
554
+ self.console.print(f"⚠️ Error cleaning up WebSocket process: {e}")
555
+ finally:
556
+ self.websocket_server_process = None
557
+
558
+ async def _stop_websocket_server(self) -> None:
559
+ async with self._websocket_process_lock:
560
+ if not self.websocket_server_process:
561
+ return
562
+
563
+ try:
564
+ if self.websocket_server_process.poll() is None:
565
+ await self._terminate_live_websocket_process()
566
+ else:
567
+ await self._handle_dead_websocket_process_cleanup()
568
+
569
+ except Exception as e:
570
+ if self.console:
571
+ self.console.print(f"⚠️ Error stopping WebSocket server: {e}")
572
+ finally:
573
+ self.websocket_server_process = None
574
+
575
+ async def _terminate_live_websocket_process(self) -> None:
576
+ if self.console:
577
+ self.console.print("🛑 Stopping WebSocket server")
578
+
579
+ if self.websocket_server_process is not None:
580
+ self.websocket_server_process.terminate()
581
+
582
+ if await self._wait_for_graceful_termination():
583
+ return
584
+
585
+ await self._force_kill_websocket_process()
586
+
587
+ async def _wait_for_graceful_termination(self) -> bool:
588
+ try:
589
+ if self.websocket_server_process is not None:
590
+ self.websocket_server_process.wait(timeout=5)
591
+ if self.console:
592
+ self.console.print("✅ WebSocket server stopped gracefully")
593
+ return True
594
+ except subprocess.TimeoutExpired:
595
+ return False
596
+
597
+ async def _force_kill_websocket_process(self) -> None:
598
+ if self.console:
599
+ self.console.print("⚡ Force killing unresponsive WebSocket server")
600
+
601
+ if self.websocket_server_process is not None:
602
+ self.websocket_server_process.kill()
603
+
604
+ try:
605
+ if self.websocket_server_process is not None:
606
+ self.websocket_server_process.wait(timeout=2)
607
+ if self.console:
608
+ self.console.print("💀 WebSocket server force killed")
609
+ except subprocess.TimeoutExpired:
610
+ if self.console:
611
+ self.console.print("⚠️ WebSocket server process may be zombified")
612
+
613
+ async def _handle_dead_websocket_process_cleanup(self) -> None:
614
+ if self.console:
615
+ self.console.print("💀 WebSocket server process was already dead")
616
+
617
+ async def get_websocket_server_status(self) -> dict[str, t.Any]:
618
+ async with self._websocket_process_lock:
619
+ status = {
620
+ "port": self.websocket_server_port,
621
+ "process_exists": self.websocket_server_process is not None,
622
+ "process_alive": False,
623
+ "server_responding": False,
624
+ "process_id": None,
625
+ "return_code": None,
626
+ }
627
+
628
+ if self.websocket_server_process:
629
+ status["process_id"] = self.websocket_server_process.pid
630
+ poll_result = self.websocket_server_process.poll()
631
+ status["process_alive"] = poll_result is None
632
+ if poll_result is not None:
633
+ status["return_code"] = poll_result
634
+
635
+ status["server_responding"] = await self.check_websocket_server_running()
636
+
637
+ return status
638
+
639
+ async def _websocket_health_monitor(self) -> None:
640
+ while True:
641
+ try:
642
+ await asyncio.sleep(30)
643
+ await self._check_and_restart_websocket()
644
+ except asyncio.CancelledError:
645
+ break
646
+ except Exception as e:
647
+ if self.console:
648
+ self.console.print(f"⚠️ Error in WebSocket health monitor: {e}")
649
+ await asyncio.sleep(60)
650
+
651
+ async def _check_and_restart_websocket(self) -> None:
652
+ async with self._websocket_process_lock:
653
+ if not self.websocket_server_process:
654
+ return
655
+
656
+ if self.websocket_server_process.poll() is not None:
657
+ await self._handle_dead_websocket_process()
658
+ return
659
+
660
+ if not await self.check_websocket_server_running():
661
+ await self._handle_unresponsive_websocket_server()
662
+
663
+ async def _handle_dead_websocket_process(self) -> None:
664
+ if self.websocket_server_process is not None:
665
+ return_code = self.websocket_server_process.returncode
666
+ if self.console:
667
+ self.console.print(
668
+ f"⚠️ WebSocket server process died (exit code: {return_code}), attempting restart...",
669
+ )
670
+ self.websocket_server_process = None
671
+ await self._restart_websocket_server()
672
+
673
+ async def _handle_unresponsive_websocket_server(self) -> None:
674
+ if self.console:
675
+ self.console.print("⚠️ WebSocket server not responding, restarting...")
676
+ await self._cleanup_dead_websocket_process()
677
+ await self._restart_websocket_server()
678
+
679
+ async def _restart_websocket_server(self) -> None:
680
+ if await self.start_websocket_server():
681
+ if self.console:
682
+ self.console.print("✅ WebSocket server restarted successfully")
683
+ elif self.console:
684
+ self.console.print("❌ Failed to restart WebSocket server")
685
+
686
+ def safe_print(self, *args: t.Any, **kwargs: t.Any) -> None:
687
+ if not self.config.stdio_mode and self.console:
688
+ self.console.print(*args, **kwargs)
689
+
690
+ def create_progress_file_path(self, job_id: str) -> Path:
691
+ if not self.validate_job_id(job_id):
692
+ msg = f"Invalid job_id: {job_id}"
693
+ raise ValueError(msg)
694
+
695
+ return SecurePathValidator.secure_path_join(
696
+ self.progress_dir, f"job-{job_id}.json"
697
+ )
698
+
699
+ async def schedule_state_save(
700
+ self,
701
+ save_id: str,
702
+ save_func: t.Callable[[], None],
703
+ ) -> None:
704
+ await self.batched_saver.schedule_save(save_id, save_func)
705
+
706
+ def get_current_time(self) -> str:
707
+ import datetime
708
+
709
+ return datetime.datetime.now().isoformat()
710
+
711
+ def get_context_stats(self) -> dict[str, t.Any]:
712
+ return {
713
+ "initialized": self._initialized,
714
+ "stdio_mode": self.config.stdio_mode,
715
+ "project_path": str(self.config.project_path),
716
+ "progress_dir": str(self.progress_dir),
717
+ "components": {
718
+ "cli_runner": self.cli_runner is not None,
719
+ "state_manager": self.state_manager is not None,
720
+ "error_cache": self.error_cache is not None,
721
+ "rate_limiter": self.rate_limiter is not None,
722
+ "batched_saver": self.batched_saver is not None,
723
+ },
724
+ "websocket_server": {
725
+ "port": self.websocket_server_port,
726
+ "process_exists": self.websocket_server_process is not None,
727
+ "health_monitor_running": self._websocket_health_check_task is not None
728
+ and not self._websocket_health_check_task.done(),
729
+ "cleanup_registered": self._websocket_cleanup_registered,
730
+ },
731
+ "progress_queue": {
732
+ "maxsize": self.progress_queue.maxsize,
733
+ "current_size": self.progress_queue.qsize(),
734
+ "full": self.progress_queue.full(),
735
+ },
736
+ "startup_tasks": len(self._startup_tasks),
737
+ "shutdown_tasks": len(self._shutdown_tasks),
738
+ "batched_saving": self.batched_saver.get_stats(),
739
+ }
740
+
741
+
742
+ class MCPContextManager:
743
+ def __init__(self, config: MCPServerConfig) -> None:
744
+ self.context = MCPServerContext(config)
745
+
746
+ async def __aenter__(self) -> MCPServerContext:
747
+ await self.context.initialize()
748
+ return self.context
749
+
750
+ async def __aexit__(
751
+ self,
752
+ exc_type: type[BaseException] | None,
753
+ exc_val: BaseException | None,
754
+ _exc_tb: TracebackType | None,
755
+ ) -> None:
756
+ await self.context.shutdown()
757
+
758
+
759
+ _global_context: MCPServerContext | None = None
760
+
761
+
762
+ def get_context() -> MCPServerContext:
763
+ if _global_context is None:
764
+ msg = "MCP server context not initialized. Call set_context() first."
765
+ raise RuntimeError(
766
+ msg,
767
+ )
768
+ return _global_context
769
+
770
+
771
+ def set_context(context: MCPServerContext) -> None:
772
+ global _global_context
773
+ _global_context = context
774
+
775
+
776
+ def clear_context() -> None:
777
+ global _global_context
778
+ _global_context = None
779
+
780
+
781
+ def get_console() -> Console:
782
+ return get_context().console or depends.get_sync(Console)
783
+
784
+
785
+ def get_state_manager() -> StateManager | None:
786
+ return get_context().state_manager
787
+
788
+
789
+ def get_error_cache() -> ErrorCache | None:
790
+ return get_context().error_cache
791
+
792
+
793
+ def get_rate_limiter() -> RateLimitMiddleware | None:
794
+ return get_context().rate_limiter
795
+
796
+
797
+ def safe_print(*args: t.Any, **kwargs: t.Any) -> None:
798
+ get_context().safe_print(*args, **kwargs)
799
+
800
+
801
+ def validate_job_id(job_id: str) -> bool:
802
+ return get_context().validate_job_id(job_id)