crackerjack 0.37.9__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 (425) hide show
  1. crackerjack/README.md +19 -0
  2. crackerjack/__init__.py +30 -1
  3. crackerjack/__main__.py +342 -1263
  4. crackerjack/adapters/README.md +18 -0
  5. crackerjack/adapters/__init__.py +27 -5
  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/{rust_tool_manager.py → lsp/_manager.py} +3 -3
  27. crackerjack/adapters/{skylos_adapter.py → lsp/skylos.py} +59 -7
  28. crackerjack/adapters/{zuban_adapter.py → lsp/zuban.py} +3 -6
  29. crackerjack/adapters/refactor/README.md +59 -0
  30. crackerjack/adapters/refactor/__init__.py +12 -0
  31. crackerjack/adapters/refactor/creosote.py +318 -0
  32. crackerjack/adapters/refactor/refurb.py +406 -0
  33. crackerjack/adapters/refactor/skylos.py +494 -0
  34. crackerjack/adapters/sast/README.md +132 -0
  35. crackerjack/adapters/sast/__init__.py +32 -0
  36. crackerjack/adapters/sast/_base.py +201 -0
  37. crackerjack/adapters/sast/bandit.py +423 -0
  38. crackerjack/adapters/sast/pyscn.py +405 -0
  39. crackerjack/adapters/sast/semgrep.py +241 -0
  40. crackerjack/adapters/security/README.md +111 -0
  41. crackerjack/adapters/security/__init__.py +17 -0
  42. crackerjack/adapters/security/gitleaks.py +339 -0
  43. crackerjack/adapters/type/README.md +52 -0
  44. crackerjack/adapters/type/__init__.py +12 -0
  45. crackerjack/adapters/type/pyrefly.py +402 -0
  46. crackerjack/adapters/type/ty.py +402 -0
  47. crackerjack/adapters/type/zuban.py +522 -0
  48. crackerjack/adapters/utility/README.md +51 -0
  49. crackerjack/adapters/utility/__init__.py +10 -0
  50. crackerjack/adapters/utility/checks.py +884 -0
  51. crackerjack/agents/README.md +264 -0
  52. crackerjack/agents/__init__.py +40 -12
  53. crackerjack/agents/base.py +1 -0
  54. crackerjack/agents/claude_code_bridge.py +641 -0
  55. crackerjack/agents/coordinator.py +49 -53
  56. crackerjack/agents/dry_agent.py +187 -3
  57. crackerjack/agents/enhanced_coordinator.py +279 -0
  58. crackerjack/agents/enhanced_proactive_agent.py +185 -0
  59. crackerjack/agents/error_middleware.py +53 -0
  60. crackerjack/agents/formatting_agent.py +6 -8
  61. crackerjack/agents/helpers/__init__.py +9 -0
  62. crackerjack/agents/helpers/performance/__init__.py +22 -0
  63. crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
  64. crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
  65. crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
  66. crackerjack/agents/helpers/refactoring/__init__.py +22 -0
  67. crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
  68. crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
  69. crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
  70. crackerjack/agents/helpers/test_creation/__init__.py +19 -0
  71. crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
  72. crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
  73. crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
  74. crackerjack/agents/performance_agent.py +121 -1152
  75. crackerjack/agents/refactoring_agent.py +156 -655
  76. crackerjack/agents/semantic_agent.py +479 -0
  77. crackerjack/agents/semantic_helpers.py +356 -0
  78. crackerjack/agents/test_creation_agent.py +19 -1605
  79. crackerjack/api.py +5 -7
  80. crackerjack/cli/README.md +394 -0
  81. crackerjack/cli/__init__.py +1 -1
  82. crackerjack/cli/cache_handlers.py +23 -18
  83. crackerjack/cli/cache_handlers_enhanced.py +1 -4
  84. crackerjack/cli/facade.py +70 -8
  85. crackerjack/cli/formatting.py +13 -0
  86. crackerjack/cli/handlers/__init__.py +85 -0
  87. crackerjack/cli/handlers/advanced.py +103 -0
  88. crackerjack/cli/handlers/ai_features.py +62 -0
  89. crackerjack/cli/handlers/analytics.py +479 -0
  90. crackerjack/cli/handlers/changelog.py +271 -0
  91. crackerjack/cli/handlers/config_handlers.py +16 -0
  92. crackerjack/cli/handlers/coverage.py +84 -0
  93. crackerjack/cli/handlers/documentation.py +280 -0
  94. crackerjack/cli/handlers/main_handlers.py +497 -0
  95. crackerjack/cli/handlers/monitoring.py +371 -0
  96. crackerjack/cli/handlers.py +249 -49
  97. crackerjack/cli/interactive.py +8 -5
  98. crackerjack/cli/options.py +203 -110
  99. crackerjack/cli/semantic_handlers.py +292 -0
  100. crackerjack/cli/version.py +19 -0
  101. crackerjack/code_cleaner.py +60 -24
  102. crackerjack/config/README.md +472 -0
  103. crackerjack/config/__init__.py +256 -0
  104. crackerjack/config/global_lock_config.py +191 -54
  105. crackerjack/config/hooks.py +188 -16
  106. crackerjack/config/loader.py +239 -0
  107. crackerjack/config/settings.py +141 -0
  108. crackerjack/config/tool_commands.py +331 -0
  109. crackerjack/core/README.md +393 -0
  110. crackerjack/core/async_workflow_orchestrator.py +79 -53
  111. crackerjack/core/autofix_coordinator.py +22 -9
  112. crackerjack/core/container.py +10 -9
  113. crackerjack/core/enhanced_container.py +9 -9
  114. crackerjack/core/performance.py +1 -1
  115. crackerjack/core/performance_monitor.py +5 -3
  116. crackerjack/core/phase_coordinator.py +1018 -634
  117. crackerjack/core/proactive_workflow.py +3 -3
  118. crackerjack/core/retry.py +275 -0
  119. crackerjack/core/service_watchdog.py +167 -23
  120. crackerjack/core/session_coordinator.py +187 -382
  121. crackerjack/core/timeout_manager.py +161 -44
  122. crackerjack/core/workflow/__init__.py +21 -0
  123. crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
  124. crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
  125. crackerjack/core/workflow/workflow_issue_parser.py +714 -0
  126. crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
  127. crackerjack/core/workflow/workflow_security_gates.py +400 -0
  128. crackerjack/core/workflow_orchestrator.py +1247 -953
  129. crackerjack/data/README.md +11 -0
  130. crackerjack/data/__init__.py +8 -0
  131. crackerjack/data/models.py +79 -0
  132. crackerjack/data/repository.py +210 -0
  133. crackerjack/decorators/README.md +180 -0
  134. crackerjack/decorators/__init__.py +35 -0
  135. crackerjack/decorators/error_handling.py +649 -0
  136. crackerjack/decorators/error_handling_decorators.py +334 -0
  137. crackerjack/decorators/helpers.py +58 -0
  138. crackerjack/decorators/patterns.py +281 -0
  139. crackerjack/decorators/utils.py +58 -0
  140. crackerjack/docs/README.md +11 -0
  141. crackerjack/docs/generated/api/CLI_REFERENCE.md +1 -1
  142. crackerjack/documentation/README.md +11 -0
  143. crackerjack/documentation/ai_templates.py +1 -1
  144. crackerjack/documentation/dual_output_generator.py +11 -9
  145. crackerjack/documentation/reference_generator.py +104 -59
  146. crackerjack/dynamic_config.py +52 -61
  147. crackerjack/errors.py +1 -1
  148. crackerjack/events/README.md +11 -0
  149. crackerjack/events/__init__.py +16 -0
  150. crackerjack/events/telemetry.py +175 -0
  151. crackerjack/events/workflow_bus.py +346 -0
  152. crackerjack/exceptions/README.md +301 -0
  153. crackerjack/exceptions/__init__.py +5 -0
  154. crackerjack/exceptions/config.py +4 -0
  155. crackerjack/exceptions/tool_execution_error.py +245 -0
  156. crackerjack/executors/README.md +591 -0
  157. crackerjack/executors/__init__.py +2 -0
  158. crackerjack/executors/async_hook_executor.py +539 -77
  159. crackerjack/executors/cached_hook_executor.py +3 -3
  160. crackerjack/executors/hook_executor.py +967 -102
  161. crackerjack/executors/hook_lock_manager.py +31 -22
  162. crackerjack/executors/individual_hook_executor.py +66 -32
  163. crackerjack/executors/lsp_aware_hook_executor.py +136 -57
  164. crackerjack/executors/progress_hook_executor.py +282 -0
  165. crackerjack/executors/tool_proxy.py +23 -7
  166. crackerjack/hooks/README.md +485 -0
  167. crackerjack/hooks/lsp_hook.py +8 -9
  168. crackerjack/intelligence/README.md +557 -0
  169. crackerjack/interactive.py +37 -10
  170. crackerjack/managers/README.md +369 -0
  171. crackerjack/managers/async_hook_manager.py +41 -57
  172. crackerjack/managers/hook_manager.py +449 -79
  173. crackerjack/managers/publish_manager.py +81 -36
  174. crackerjack/managers/test_command_builder.py +290 -12
  175. crackerjack/managers/test_executor.py +93 -8
  176. crackerjack/managers/test_manager.py +1082 -75
  177. crackerjack/managers/test_progress.py +118 -26
  178. crackerjack/mcp/README.md +374 -0
  179. crackerjack/mcp/cache.py +25 -2
  180. crackerjack/mcp/client_runner.py +35 -18
  181. crackerjack/mcp/context.py +9 -9
  182. crackerjack/mcp/dashboard.py +24 -8
  183. crackerjack/mcp/enhanced_progress_monitor.py +34 -23
  184. crackerjack/mcp/file_monitor.py +27 -6
  185. crackerjack/mcp/progress_components.py +45 -34
  186. crackerjack/mcp/progress_monitor.py +6 -9
  187. crackerjack/mcp/rate_limiter.py +11 -7
  188. crackerjack/mcp/server.py +2 -0
  189. crackerjack/mcp/server_core.py +187 -55
  190. crackerjack/mcp/service_watchdog.py +12 -9
  191. crackerjack/mcp/task_manager.py +2 -2
  192. crackerjack/mcp/tools/README.md +27 -0
  193. crackerjack/mcp/tools/__init__.py +2 -0
  194. crackerjack/mcp/tools/core_tools.py +75 -52
  195. crackerjack/mcp/tools/execution_tools.py +87 -31
  196. crackerjack/mcp/tools/intelligence_tools.py +2 -2
  197. crackerjack/mcp/tools/proactive_tools.py +1 -1
  198. crackerjack/mcp/tools/semantic_tools.py +584 -0
  199. crackerjack/mcp/tools/utility_tools.py +180 -132
  200. crackerjack/mcp/tools/workflow_executor.py +87 -46
  201. crackerjack/mcp/websocket/README.md +31 -0
  202. crackerjack/mcp/websocket/app.py +11 -1
  203. crackerjack/mcp/websocket/event_bridge.py +188 -0
  204. crackerjack/mcp/websocket/jobs.py +27 -4
  205. crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
  206. crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
  207. crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
  208. crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
  209. crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
  210. crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
  211. crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
  212. crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
  213. crackerjack/mcp/websocket/monitoring/factory.py +109 -0
  214. crackerjack/mcp/websocket/monitoring/filters.py +10 -0
  215. crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
  216. crackerjack/mcp/websocket/monitoring/models.py +90 -0
  217. crackerjack/mcp/websocket/monitoring/utils.py +171 -0
  218. crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
  219. crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
  220. crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
  221. crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
  222. crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
  223. crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
  224. crackerjack/mcp/websocket/monitoring_endpoints.py +16 -2930
  225. crackerjack/mcp/websocket/server.py +1 -3
  226. crackerjack/mcp/websocket/websocket_handler.py +107 -6
  227. crackerjack/models/README.md +308 -0
  228. crackerjack/models/__init__.py +10 -1
  229. crackerjack/models/config.py +639 -22
  230. crackerjack/models/config_adapter.py +6 -6
  231. crackerjack/models/protocols.py +1167 -23
  232. crackerjack/models/pydantic_models.py +320 -0
  233. crackerjack/models/qa_config.py +145 -0
  234. crackerjack/models/qa_results.py +134 -0
  235. crackerjack/models/results.py +35 -0
  236. crackerjack/models/semantic_models.py +258 -0
  237. crackerjack/models/task.py +19 -3
  238. crackerjack/models/test_models.py +60 -0
  239. crackerjack/monitoring/README.md +11 -0
  240. crackerjack/monitoring/ai_agent_watchdog.py +5 -4
  241. crackerjack/monitoring/metrics_collector.py +4 -3
  242. crackerjack/monitoring/regression_prevention.py +4 -3
  243. crackerjack/monitoring/websocket_server.py +4 -241
  244. crackerjack/orchestration/README.md +340 -0
  245. crackerjack/orchestration/__init__.py +43 -0
  246. crackerjack/orchestration/advanced_orchestrator.py +20 -67
  247. crackerjack/orchestration/cache/README.md +312 -0
  248. crackerjack/orchestration/cache/__init__.py +37 -0
  249. crackerjack/orchestration/cache/memory_cache.py +338 -0
  250. crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
  251. crackerjack/orchestration/config.py +297 -0
  252. crackerjack/orchestration/coverage_improvement.py +13 -6
  253. crackerjack/orchestration/execution_strategies.py +6 -6
  254. crackerjack/orchestration/hook_orchestrator.py +1398 -0
  255. crackerjack/orchestration/strategies/README.md +401 -0
  256. crackerjack/orchestration/strategies/__init__.py +39 -0
  257. crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
  258. crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
  259. crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
  260. crackerjack/orchestration/test_progress_streamer.py +1 -1
  261. crackerjack/plugins/README.md +11 -0
  262. crackerjack/plugins/hooks.py +3 -2
  263. crackerjack/plugins/loader.py +3 -3
  264. crackerjack/plugins/managers.py +1 -1
  265. crackerjack/py313.py +191 -0
  266. crackerjack/security/README.md +11 -0
  267. crackerjack/services/README.md +374 -0
  268. crackerjack/services/__init__.py +8 -21
  269. crackerjack/services/ai/README.md +295 -0
  270. crackerjack/services/ai/__init__.py +7 -0
  271. crackerjack/services/ai/advanced_optimizer.py +878 -0
  272. crackerjack/services/{contextual_ai_assistant.py → ai/contextual_ai_assistant.py} +5 -3
  273. crackerjack/services/ai/embeddings.py +444 -0
  274. crackerjack/services/ai/intelligent_commit.py +328 -0
  275. crackerjack/services/ai/predictive_analytics.py +510 -0
  276. crackerjack/services/api_extractor.py +5 -3
  277. crackerjack/services/bounded_status_operations.py +45 -5
  278. crackerjack/services/cache.py +249 -318
  279. crackerjack/services/changelog_automation.py +7 -3
  280. crackerjack/services/command_execution_service.py +305 -0
  281. crackerjack/services/config_integrity.py +83 -39
  282. crackerjack/services/config_merge.py +9 -6
  283. crackerjack/services/config_service.py +198 -0
  284. crackerjack/services/config_template.py +13 -26
  285. crackerjack/services/coverage_badge_service.py +6 -4
  286. crackerjack/services/coverage_ratchet.py +53 -27
  287. crackerjack/services/debug.py +18 -7
  288. crackerjack/services/dependency_analyzer.py +4 -4
  289. crackerjack/services/dependency_monitor.py +13 -13
  290. crackerjack/services/documentation_generator.py +4 -2
  291. crackerjack/services/documentation_service.py +62 -33
  292. crackerjack/services/enhanced_filesystem.py +81 -27
  293. crackerjack/services/enterprise_optimizer.py +1 -1
  294. crackerjack/services/error_pattern_analyzer.py +10 -10
  295. crackerjack/services/file_filter.py +221 -0
  296. crackerjack/services/file_hasher.py +5 -7
  297. crackerjack/services/file_io_service.py +361 -0
  298. crackerjack/services/file_modifier.py +615 -0
  299. crackerjack/services/filesystem.py +80 -109
  300. crackerjack/services/git.py +99 -5
  301. crackerjack/services/health_metrics.py +4 -6
  302. crackerjack/services/heatmap_generator.py +12 -3
  303. crackerjack/services/incremental_executor.py +380 -0
  304. crackerjack/services/initialization.py +101 -49
  305. crackerjack/services/log_manager.py +2 -2
  306. crackerjack/services/logging.py +120 -68
  307. crackerjack/services/lsp_client.py +12 -12
  308. crackerjack/services/memory_optimizer.py +27 -22
  309. crackerjack/services/monitoring/README.md +30 -0
  310. crackerjack/services/monitoring/__init__.py +9 -0
  311. crackerjack/services/monitoring/dependency_monitor.py +678 -0
  312. crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
  313. crackerjack/services/monitoring/health_metrics.py +716 -0
  314. crackerjack/services/monitoring/metrics.py +587 -0
  315. crackerjack/services/{performance_benchmarks.py → monitoring/performance_benchmarks.py} +100 -14
  316. crackerjack/services/{performance_cache.py → monitoring/performance_cache.py} +21 -15
  317. crackerjack/services/{performance_monitor.py → monitoring/performance_monitor.py} +10 -6
  318. crackerjack/services/parallel_executor.py +166 -55
  319. crackerjack/services/patterns/__init__.py +142 -0
  320. crackerjack/services/patterns/agents.py +107 -0
  321. crackerjack/services/patterns/code/__init__.py +15 -0
  322. crackerjack/services/patterns/code/detection.py +118 -0
  323. crackerjack/services/patterns/code/imports.py +107 -0
  324. crackerjack/services/patterns/code/paths.py +159 -0
  325. crackerjack/services/patterns/code/performance.py +119 -0
  326. crackerjack/services/patterns/code/replacement.py +36 -0
  327. crackerjack/services/patterns/core.py +212 -0
  328. crackerjack/services/patterns/documentation/__init__.py +14 -0
  329. crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
  330. crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
  331. crackerjack/services/patterns/documentation/docstrings.py +89 -0
  332. crackerjack/services/patterns/formatting.py +226 -0
  333. crackerjack/services/patterns/operations.py +339 -0
  334. crackerjack/services/patterns/security/__init__.py +23 -0
  335. crackerjack/services/patterns/security/code_injection.py +122 -0
  336. crackerjack/services/patterns/security/credentials.py +190 -0
  337. crackerjack/services/patterns/security/path_traversal.py +221 -0
  338. crackerjack/services/patterns/security/unsafe_operations.py +216 -0
  339. crackerjack/services/patterns/templates.py +62 -0
  340. crackerjack/services/patterns/testing/__init__.py +18 -0
  341. crackerjack/services/patterns/testing/error_patterns.py +107 -0
  342. crackerjack/services/patterns/testing/pytest_output.py +126 -0
  343. crackerjack/services/patterns/tool_output/__init__.py +16 -0
  344. crackerjack/services/patterns/tool_output/bandit.py +72 -0
  345. crackerjack/services/patterns/tool_output/other.py +97 -0
  346. crackerjack/services/patterns/tool_output/pyright.py +67 -0
  347. crackerjack/services/patterns/tool_output/ruff.py +44 -0
  348. crackerjack/services/patterns/url_sanitization.py +114 -0
  349. crackerjack/services/patterns/utilities.py +42 -0
  350. crackerjack/services/patterns/utils.py +339 -0
  351. crackerjack/services/patterns/validation.py +46 -0
  352. crackerjack/services/patterns/versioning.py +62 -0
  353. crackerjack/services/predictive_analytics.py +21 -8
  354. crackerjack/services/profiler.py +280 -0
  355. crackerjack/services/quality/README.md +415 -0
  356. crackerjack/services/quality/__init__.py +11 -0
  357. crackerjack/services/quality/anomaly_detector.py +392 -0
  358. crackerjack/services/quality/pattern_cache.py +333 -0
  359. crackerjack/services/quality/pattern_detector.py +479 -0
  360. crackerjack/services/quality/qa_orchestrator.py +491 -0
  361. crackerjack/services/{quality_baseline.py → quality/quality_baseline.py} +163 -2
  362. crackerjack/services/{quality_baseline_enhanced.py → quality/quality_baseline_enhanced.py} +4 -1
  363. crackerjack/services/{quality_intelligence.py → quality/quality_intelligence.py} +180 -16
  364. crackerjack/services/regex_patterns.py +58 -2987
  365. crackerjack/services/regex_utils.py +55 -29
  366. crackerjack/services/secure_status_formatter.py +42 -15
  367. crackerjack/services/secure_subprocess.py +35 -2
  368. crackerjack/services/security.py +16 -8
  369. crackerjack/services/server_manager.py +40 -51
  370. crackerjack/services/smart_scheduling.py +46 -6
  371. crackerjack/services/status_authentication.py +3 -3
  372. crackerjack/services/thread_safe_status_collector.py +1 -0
  373. crackerjack/services/tool_filter.py +368 -0
  374. crackerjack/services/tool_version_service.py +9 -5
  375. crackerjack/services/unified_config.py +43 -351
  376. crackerjack/services/vector_store.py +689 -0
  377. crackerjack/services/version_analyzer.py +6 -4
  378. crackerjack/services/version_checker.py +14 -8
  379. crackerjack/services/zuban_lsp_service.py +5 -4
  380. crackerjack/slash_commands/README.md +11 -0
  381. crackerjack/slash_commands/init.md +2 -12
  382. crackerjack/slash_commands/run.md +84 -50
  383. crackerjack/tools/README.md +11 -0
  384. crackerjack/tools/__init__.py +30 -0
  385. crackerjack/tools/_git_utils.py +105 -0
  386. crackerjack/tools/check_added_large_files.py +139 -0
  387. crackerjack/tools/check_ast.py +105 -0
  388. crackerjack/tools/check_json.py +103 -0
  389. crackerjack/tools/check_jsonschema.py +297 -0
  390. crackerjack/tools/check_toml.py +103 -0
  391. crackerjack/tools/check_yaml.py +110 -0
  392. crackerjack/tools/codespell_wrapper.py +72 -0
  393. crackerjack/tools/end_of_file_fixer.py +202 -0
  394. crackerjack/tools/format_json.py +128 -0
  395. crackerjack/tools/mdformat_wrapper.py +114 -0
  396. crackerjack/tools/trailing_whitespace.py +198 -0
  397. crackerjack/tools/validate_regex_patterns.py +7 -3
  398. crackerjack/ui/README.md +11 -0
  399. crackerjack/ui/dashboard_renderer.py +28 -0
  400. crackerjack/ui/templates/README.md +11 -0
  401. crackerjack/utils/console_utils.py +13 -0
  402. crackerjack/utils/dependency_guard.py +230 -0
  403. crackerjack/utils/retry_utils.py +275 -0
  404. crackerjack/workflows/README.md +590 -0
  405. crackerjack/workflows/__init__.py +46 -0
  406. crackerjack/workflows/actions.py +811 -0
  407. crackerjack/workflows/auto_fix.py +444 -0
  408. crackerjack/workflows/container_builder.py +499 -0
  409. crackerjack/workflows/definitions.py +443 -0
  410. crackerjack/workflows/engine.py +177 -0
  411. crackerjack/workflows/event_bridge.py +242 -0
  412. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/METADATA +678 -98
  413. crackerjack-0.45.2.dist-info/RECORD +478 -0
  414. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
  415. crackerjack/managers/test_manager_backup.py +0 -1075
  416. crackerjack/mcp/tools/execution_tools_backup.py +0 -1011
  417. crackerjack/mixins/__init__.py +0 -3
  418. crackerjack/mixins/error_handling.py +0 -145
  419. crackerjack/services/config.py +0 -358
  420. crackerjack/ui/server_panels.py +0 -125
  421. crackerjack-0.37.9.dist-info/RECORD +0 -231
  422. /crackerjack/adapters/{rust_tool_adapter.py → lsp/_base.py} +0 -0
  423. /crackerjack/adapters/{lsp_client.py → lsp/_client.py} +0 -0
  424. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/entry_points.txt +0 -0
  425. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,491 @@
1
+ """QA Orchestrator service for coordinating quality assurance checks.
2
+
3
+ This service coordinates multiple QA adapters, handles parallel execution,
4
+ caching, and result aggregation. It replaces the pre-commit hook orchestration
5
+ with native ACB-based quality checks.
6
+
7
+ ACB Patterns:
8
+ - Service implements QAOrchestratorProtocol from models.protocols
9
+ - Async execution throughout
10
+ - Proper error handling and logging
11
+ - Graceful degradation on adapter failures
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import contextlib
18
+ import hashlib
19
+ import typing as t
20
+ from datetime import datetime, timedelta
21
+ from pathlib import Path
22
+
23
+ import yaml
24
+ from acb.depends import depends
25
+
26
+ from crackerjack.models.protocols import QAAdapterProtocol
27
+ from crackerjack.models.qa_config import QACheckConfig, QAOrchestratorConfig
28
+ from crackerjack.models.qa_results import QAResult, QAResultStatus
29
+
30
+ if t.TYPE_CHECKING:
31
+ pass
32
+
33
+
34
+ class QAOrchestrator:
35
+ """Orchestrates multiple QA adapters for comprehensive quality checking.
36
+
37
+ Coordinates execution of all registered QA adapters:
38
+ - Parallel execution with configurable concurrency
39
+ - Stage-based execution (fast vs comprehensive)
40
+ - Result caching for performance
41
+ - Formatter-first execution order
42
+ - Incremental checking support
43
+
44
+ Example:
45
+ ```python
46
+ # Initialize orchestrator
47
+ config = QAOrchestratorConfig(
48
+ project_root=Path.cwd(),
49
+ max_parallel_checks=4,
50
+ enable_caching=True,
51
+ fail_fast=False,
52
+ )
53
+ orchestrator = QAOrchestrator(config)
54
+
55
+ # Register adapters
56
+ await orchestrator.register_adapter(ruff_adapter)
57
+ await orchestrator.register_adapter(bandit_adapter)
58
+ await orchestrator.register_adapter(zuban_adapter)
59
+
60
+ # Run fast stage checks
61
+ results = await orchestrator.run_checks(stage="fast")
62
+
63
+ # Run comprehensive checks
64
+ results = await orchestrator.run_checks(stage="comprehensive")
65
+
66
+ # Run all checks
67
+ all_results = await orchestrator.run_all_checks()
68
+ ```
69
+ """
70
+
71
+ def __init__(self, config: QAOrchestratorConfig) -> None:
72
+ """Initialize QA orchestrator.
73
+
74
+ Args:
75
+ config: Orchestrator configuration
76
+ """
77
+ self.config = config
78
+ self._adapters: dict[str, QAAdapterProtocol] = {}
79
+ self._cache: dict[str, tuple[QAResult, datetime]] = {}
80
+ self._semaphore = asyncio.Semaphore(config.max_parallel_checks)
81
+
82
+ async def register_adapter(self, adapter: QAAdapterProtocol) -> None:
83
+ """Register a QA adapter.
84
+
85
+ Args:
86
+ adapter: QA adapter to register
87
+ """
88
+ adapter_name = adapter.adapter_name
89
+ self._adapters[adapter_name] = adapter
90
+
91
+ # Initialize adapter if not already initialized
92
+ await adapter.init()
93
+
94
+ def get_adapter(self, name: str) -> QAAdapterProtocol | None:
95
+ """Get registered adapter by name.
96
+
97
+ Args:
98
+ name: Adapter name
99
+
100
+ Returns:
101
+ Adapter if found, None otherwise
102
+ """
103
+ return self._adapters.get(name)
104
+
105
+ async def run_checks(
106
+ self,
107
+ stage: str = "fast",
108
+ files: list[Path] | None = None,
109
+ ) -> list[QAResult]:
110
+ """Run QA checks for specified stage.
111
+
112
+ Args:
113
+ stage: Execution stage ('fast' or 'comprehensive')
114
+ files: Optional list of files to check
115
+
116
+ Returns:
117
+ List of QAResult objects
118
+ """
119
+ # Get checks for this stage
120
+ if stage == "fast":
121
+ checks = self.config.fast_checks
122
+ elif stage == "comprehensive":
123
+ checks = self.config.comprehensive_checks
124
+ else:
125
+ raise ValueError(f"Invalid stage: {stage}")
126
+
127
+ # Filter enabled checks
128
+ checks = [c for c in checks if c.enabled]
129
+
130
+ if not checks:
131
+ return []
132
+
133
+ # Sort checks: formatters first if configured
134
+ if self.config.run_formatters_first:
135
+ checks.sort(key=lambda c: (not c.is_formatter, c.check_name))
136
+
137
+ # Execute checks
138
+ results = await self._execute_checks(checks, files)
139
+
140
+ return results
141
+
142
+ async def run_all_checks(
143
+ self,
144
+ files: list[Path] | None = None,
145
+ ) -> dict[str, t.Any]:
146
+ """Run all registered QA checks.
147
+
148
+ Args:
149
+ files: Optional list of files to check
150
+
151
+ Returns:
152
+ Dictionary mapping adapter names to results
153
+ """
154
+ # Run fast stage
155
+ fast_results = await self.run_checks(stage="fast", files=files)
156
+
157
+ # Run comprehensive stage
158
+ comprehensive_results = await self.run_checks(
159
+ stage="comprehensive", files=files
160
+ )
161
+
162
+ # Aggregate results
163
+ all_results = fast_results + comprehensive_results
164
+
165
+ return {
166
+ "fast_stage": fast_results,
167
+ "comprehensive_stage": comprehensive_results,
168
+ "all_results": all_results,
169
+ "summary": self._create_summary(all_results),
170
+ }
171
+
172
+ def _create_check_tasks(
173
+ self,
174
+ checks: list[QACheckConfig],
175
+ files: list[Path] | None,
176
+ ) -> list[asyncio.Task]:
177
+ """Create asyncio tasks for check execution."""
178
+ tasks = []
179
+
180
+ for check_config in checks:
181
+ adapter = self.get_adapter(check_config.check_name)
182
+ if not adapter:
183
+ continue
184
+
185
+ # Create task for this check
186
+ task = self._execute_single_check(adapter, check_config, files)
187
+ tasks.append(task)
188
+
189
+ return tasks
190
+
191
+ async def _handle_fail_fast(
192
+ self, tasks: list[asyncio.Task]
193
+ ) -> list[QAResult] | None:
194
+ """Handle fail-fast logic if configured."""
195
+ if not self.config.fail_fast or not tasks:
196
+ return None
197
+
198
+ # Wait for first task to complete
199
+ done, pending = await asyncio.wait(
200
+ tasks[-1:], return_when=asyncio.FIRST_COMPLETED
201
+ )
202
+ result = done.pop().result()
203
+ if not result.is_success:
204
+ # Cancel remaining tasks
205
+ for pending_task in pending:
206
+ pending_task.cancel()
207
+ return [result]
208
+
209
+ return None
210
+
211
+ def _filter_valid_results(self, results: list[t.Any]) -> list[QAResult]:
212
+ """Filter out exceptions and convert to valid QAResult objects."""
213
+ valid_results = []
214
+ for result in results:
215
+ if isinstance(result, QAResult):
216
+ valid_results.append(result)
217
+ elif isinstance(result, Exception):
218
+ # Log error but continue
219
+ continue
220
+ return valid_results
221
+
222
+ async def _execute_checks(
223
+ self,
224
+ checks: list[QACheckConfig],
225
+ files: list[Path] | None,
226
+ ) -> list[QAResult]:
227
+ """Execute multiple checks in parallel.
228
+
229
+ Args:
230
+ checks: List of check configurations
231
+ files: Optional files to check
232
+
233
+ Returns:
234
+ List of QAResult objects
235
+ """
236
+ tasks = self._create_check_tasks(checks, files)
237
+
238
+ # Handle fail fast logic if needed
239
+ fail_result = await self._handle_fail_fast(tasks)
240
+ if fail_result is not None:
241
+ return fail_result
242
+
243
+ # Execute all tasks
244
+ results = await asyncio.gather(*tasks, return_exceptions=True)
245
+
246
+ # Filter out exceptions and convert to QAResult
247
+ return self._filter_valid_results(results)
248
+
249
+ async def _execute_single_check(
250
+ self,
251
+ adapter: QAAdapterProtocol,
252
+ config: QACheckConfig,
253
+ files: list[Path] | None,
254
+ ) -> QAResult:
255
+ """Execute a single check with caching and semaphore control.
256
+
257
+ Args:
258
+ adapter: QA adapter to execute
259
+ config: Check configuration
260
+ files: Optional files to check
261
+
262
+ Returns:
263
+ QAResult
264
+ """
265
+ # Generate cache key
266
+ cache_key = self._generate_cache_key(adapter, config, files)
267
+
268
+ # Check cache if enabled
269
+ if self.config.enable_caching:
270
+ cached_result = self._get_cached_result(cache_key)
271
+ if cached_result:
272
+ return cached_result
273
+
274
+ # Acquire semaphore for parallel execution control
275
+ async with self._semaphore:
276
+ try:
277
+ # Execute check
278
+ result = await adapter.check(files=files, config=config)
279
+
280
+ # Cache result if enabled
281
+ if self.config.enable_caching:
282
+ self._cache_result(cache_key, result)
283
+
284
+ # Retry on failure if configured
285
+ if not result.is_success and config.retry_on_failure:
286
+ result = await adapter.check(files=files, config=config)
287
+
288
+ return result
289
+
290
+ except Exception as e:
291
+ # Return error result
292
+ return QAResult(
293
+ check_id=config.check_id,
294
+ check_name=adapter.adapter_name,
295
+ check_type=config.check_type,
296
+ status=QAResultStatus.ERROR,
297
+ message=f"Check failed: {e}",
298
+ details=str(e),
299
+ )
300
+
301
+ def _generate_cache_key(
302
+ self,
303
+ adapter: QAAdapterProtocol,
304
+ config: QACheckConfig,
305
+ files: list[Path] | None,
306
+ ) -> str:
307
+ """Generate cache key for check execution.
308
+
309
+ Args:
310
+ adapter: QA adapter
311
+ config: Check configuration
312
+ files: Files to check
313
+
314
+ Returns:
315
+ Cache key string
316
+ """
317
+ key_parts = [
318
+ adapter.adapter_name,
319
+ str(config.check_id),
320
+ str(sorted([str(f) for f in files]) if files else "all"),
321
+ ]
322
+
323
+ key_string = "|".join(key_parts)
324
+ return hashlib.sha256(key_string.encode()).hexdigest()[:16]
325
+
326
+ def _get_cached_result(self, cache_key: str) -> QAResult | None:
327
+ """Get cached result if valid.
328
+
329
+ Args:
330
+ cache_key: Cache key
331
+
332
+ Returns:
333
+ Cached QAResult or None
334
+ """
335
+ if cache_key not in self._cache:
336
+ return None
337
+
338
+ result, timestamp = self._cache[cache_key]
339
+
340
+ # Check if cache is still valid (1 hour TTL)
341
+ if datetime.now() - timestamp > timedelta(hours=1):
342
+ del self._cache[cache_key]
343
+ return None
344
+
345
+ return result
346
+
347
+ def _cache_result(self, cache_key: str, result: QAResult) -> None:
348
+ """Cache check result.
349
+
350
+ Args:
351
+ cache_key: Cache key
352
+ result: QAResult to cache
353
+ """
354
+ self._cache[cache_key] = (result, datetime.now())
355
+
356
+ def _create_summary(self, results: list[QAResult]) -> dict[str, t.Any]:
357
+ """Create summary statistics from results.
358
+
359
+ Args:
360
+ results: List of QAResult objects
361
+
362
+ Returns:
363
+ Summary dictionary
364
+ """
365
+ total_checks = len(results)
366
+ success_count = sum(1 for r in results if r.is_success)
367
+ failure_count = sum(1 for r in results if r.status == QAResultStatus.FAILURE)
368
+ error_count = sum(1 for r in results if r.status == QAResultStatus.ERROR)
369
+ warning_count = sum(1 for r in results if r.status == QAResultStatus.WARNING)
370
+
371
+ total_issues = sum(r.issues_found for r in results)
372
+ total_fixed = sum(r.issues_fixed for r in results)
373
+ total_execution_time = sum(r.execution_time_ms for r in results)
374
+
375
+ return {
376
+ "total_checks": total_checks,
377
+ "success": success_count,
378
+ "failures": failure_count,
379
+ "errors": error_count,
380
+ "warnings": warning_count,
381
+ "total_issues_found": total_issues,
382
+ "total_issues_fixed": total_fixed,
383
+ "total_execution_time_ms": total_execution_time,
384
+ "pass_rate": success_count / total_checks if total_checks > 0 else 0.0,
385
+ }
386
+
387
+ @classmethod
388
+ async def from_yaml_config(
389
+ cls,
390
+ config_path: Path,
391
+ project_root: Path | None = None,
392
+ ) -> QAOrchestrator:
393
+ """Create orchestrator from YAML configuration.
394
+
395
+ Args:
396
+ config_path: Path to YAML configuration file
397
+ project_root: Optional project root override
398
+
399
+ Returns:
400
+ Configured QAOrchestrator instance
401
+
402
+ Example YAML:
403
+ ```yaml
404
+ project_root: .
405
+ max_parallel_checks: 4
406
+ enable_caching: true
407
+ fail_fast: false
408
+ run_formatters_first: true
409
+
410
+ checks:
411
+ - check_name: ruff-lint
412
+ check_type: lint
413
+ enabled: true
414
+ stage: fast
415
+ settings:
416
+ mode: check
417
+ fix_enabled: false
418
+
419
+ - check_name: ruff-format
420
+ check_type: format
421
+ enabled: true
422
+ stage: fast
423
+ settings:
424
+ mode: format
425
+ fix_enabled: false
426
+
427
+ - check_name: bandit
428
+ check_type: security
429
+ enabled: true
430
+ stage: comprehensive
431
+ ```
432
+ """
433
+ if not config_path.exists():
434
+ raise FileNotFoundError(f"Config file not found: {config_path}")
435
+
436
+ # Load YAML configuration
437
+ with config_path.open() as f:
438
+ config_data = yaml.safe_load(f)
439
+
440
+ # Create orchestrator config
441
+ if project_root is None:
442
+ project_root = Path(config_data.get("project_root", "."))
443
+
444
+ config = QAOrchestratorConfig(
445
+ project_root=project_root,
446
+ max_parallel_checks=config_data.get("max_parallel_checks", 4),
447
+ enable_caching=config_data.get("enable_caching", True),
448
+ fail_fast=config_data.get("fail_fast", False),
449
+ run_formatters_first=config_data.get("run_formatters_first", True),
450
+ enable_incremental=config_data.get("enable_incremental", True),
451
+ verbose=config_data.get("verbose", False),
452
+ )
453
+
454
+ # Create orchestrator
455
+ orchestrator = cls(config)
456
+
457
+ # Load and register adapters based on configuration
458
+ # This would be implemented to dynamically load adapters
459
+ # based on the YAML configuration
460
+
461
+ return orchestrator
462
+
463
+ async def health_check(self) -> dict[str, t.Any]:
464
+ """Check health of orchestrator and all adapters.
465
+
466
+ Returns:
467
+ Health status dictionary
468
+ """
469
+ adapter_health = {}
470
+
471
+ for name, adapter in self._adapters.items():
472
+ try:
473
+ health = await adapter.health_check()
474
+ adapter_health[name] = health
475
+ except Exception as e:
476
+ adapter_health[name] = {
477
+ "status": "error",
478
+ "error": str(e),
479
+ }
480
+
481
+ return {
482
+ "orchestrator_status": "healthy",
483
+ "registered_adapters": len(self._adapters),
484
+ "cache_entries": len(self._cache),
485
+ "adapters": adapter_health,
486
+ }
487
+
488
+
489
+ # Register orchestrator with ACB dependency injection
490
+ with contextlib.suppress(Exception):
491
+ depends.set(QAOrchestrator)
@@ -1,8 +1,15 @@
1
+ import asyncio
2
+ import logging
1
3
  import subprocess
2
4
  import typing as t
3
5
  from dataclasses import asdict, dataclass
4
6
  from datetime import datetime
5
7
 
8
+ from acb.depends import depends
9
+
10
+ from crackerjack.data.models import QualityBaselineRecord
11
+ from crackerjack.data.repository import QualityBaselineRepository
12
+ from crackerjack.models.protocols import QualityBaselineProtocol
6
13
  from crackerjack.services.cache import CrackerjackCache
7
14
 
8
15
 
@@ -35,11 +42,23 @@ class QualityMetrics:
35
42
  return cls(**data)
36
43
 
37
44
 
38
- class QualityBaselineService:
45
+ class QualityBaselineService(QualityBaselineProtocol):
39
46
  """Service for tracking and persisting quality baselines across sessions."""
40
47
 
41
- def __init__(self, cache: CrackerjackCache | None = None) -> None:
48
+ def __init__(
49
+ self,
50
+ cache: CrackerjackCache | None = None,
51
+ repository: QualityBaselineRepository | None = None,
52
+ ) -> None:
42
53
  self.cache = cache or CrackerjackCache()
54
+ self._logger = logging.getLogger(__name__)
55
+ if repository is not None:
56
+ self._repository: QualityBaselineRepository | None = repository
57
+ else:
58
+ try:
59
+ self._repository = depends.get_sync(QualityBaselineRepository)
60
+ except Exception:
61
+ self._repository = None
43
62
 
44
63
  def get_current_git_hash(self) -> str | None:
45
64
  """Get current git commit hash."""
@@ -92,6 +111,31 @@ class QualityBaselineService:
92
111
  security_issues: int = 0,
93
112
  type_errors: int = 0,
94
113
  linting_issues: int = 0,
114
+ ) -> QualityMetrics | None:
115
+ """Synchronous wrapper for asynchronous baseline recording."""
116
+ return self._run_async(
117
+ self.arecord_baseline(
118
+ coverage_percent=coverage_percent,
119
+ test_count=test_count,
120
+ test_pass_rate=test_pass_rate,
121
+ hook_failures=hook_failures,
122
+ complexity_violations=complexity_violations,
123
+ security_issues=security_issues,
124
+ type_errors=type_errors,
125
+ linting_issues=linting_issues,
126
+ )
127
+ )
128
+
129
+ async def arecord_baseline(
130
+ self,
131
+ coverage_percent: float,
132
+ test_count: int,
133
+ test_pass_rate: float,
134
+ hook_failures: int = 0,
135
+ complexity_violations: int = 0,
136
+ security_issues: int = 0,
137
+ type_errors: int = 0,
138
+ linting_issues: int = 0,
95
139
  ) -> QualityMetrics | None:
96
140
  """Record quality baseline for current commit."""
97
141
  git_hash = self.get_current_git_hash()
@@ -124,9 +168,17 @@ class QualityBaselineService:
124
168
 
125
169
  # Store in cache for persistence across sessions
126
170
  self.cache.set_quality_baseline(git_hash, metrics.to_dict())
171
+ await self._persist_metrics(metrics)
127
172
  return metrics
128
173
 
129
174
  def get_baseline(self, git_hash: str | None = None) -> QualityMetrics | None:
175
+ """Synchronous wrapper around asynchronous baseline retrieval."""
176
+ return self._run_async(self.aget_baseline(git_hash=git_hash))
177
+
178
+ async def aget_baseline(
179
+ self,
180
+ git_hash: str | None = None,
181
+ ) -> QualityMetrics | None:
130
182
  """Get quality baseline for specific commit (or current commit)."""
131
183
  if not git_hash:
132
184
  git_hash = self.get_current_git_hash()
@@ -134,6 +186,13 @@ class QualityBaselineService:
134
186
  if not git_hash:
135
187
  return None
136
188
 
189
+ if self._repository:
190
+ record = await self._repository.get_by_git_hash(git_hash)
191
+ if record:
192
+ metrics = self._record_to_metrics(record)
193
+ self.cache.set_quality_baseline(git_hash, metrics.to_dict())
194
+ return metrics
195
+
137
196
  baseline_data = self.cache.get_quality_baseline(git_hash)
138
197
  if baseline_data:
139
198
  return QualityMetrics.from_dict(baseline_data)
@@ -212,7 +271,15 @@ class QualityBaselineService:
212
271
  return regressions
213
272
 
214
273
  def get_recent_baselines(self, limit: int = 10) -> list[QualityMetrics]:
274
+ """Synchronous wrapper around asynchronous baseline listing."""
275
+ return self._run_async(self.aget_recent_baselines(limit=limit))
276
+
277
+ async def aget_recent_baselines(self, limit: int = 10) -> list[QualityMetrics]:
215
278
  """Get recent baselines (requires git log parsing since cache is keyed by hash)."""
279
+ if self._repository:
280
+ records = await self._repository.list_recent(limit=limit)
281
+ return [self._record_to_metrics(record) for record in records]
282
+
216
283
  try:
217
284
  result = subprocess.run(
218
285
  ["git", "log", "--oneline", "-n", str(limit), "--format=%H"],
@@ -232,3 +299,97 @@ class QualityBaselineService:
232
299
 
233
300
  except (subprocess.CalledProcessError, FileNotFoundError):
234
301
  return []
302
+
303
+ # ------------------------------------------------------------------ #
304
+ # Internal helpers
305
+ # ------------------------------------------------------------------ #
306
+ async def _persist_metrics(self, metrics: QualityMetrics) -> None:
307
+ if not self._repository:
308
+ return
309
+
310
+ try:
311
+ await self._repository.upsert(
312
+ {
313
+ "git_hash": metrics.git_hash,
314
+ "recorded_at": metrics.timestamp,
315
+ "coverage_percent": metrics.coverage_percent,
316
+ "test_count": metrics.test_count,
317
+ "test_pass_rate": metrics.test_pass_rate,
318
+ "hook_failures": metrics.hook_failures,
319
+ "complexity_violations": metrics.complexity_violations,
320
+ "security_issues": metrics.security_issues,
321
+ "type_errors": metrics.type_errors,
322
+ "linting_issues": metrics.linting_issues,
323
+ "quality_score": metrics.quality_score,
324
+ }
325
+ )
326
+ except Exception as exc: # pragma: no cover - defensive
327
+ self._logger.debug(
328
+ "Failed to persist quality baseline record",
329
+ exc_info=exc,
330
+ )
331
+
332
+ def _record_to_metrics(self, record: QualityBaselineRecord) -> QualityMetrics:
333
+ return QualityMetrics(
334
+ git_hash=record.git_hash,
335
+ timestamp=record.recorded_at,
336
+ coverage_percent=record.coverage_percent,
337
+ test_count=record.test_count,
338
+ test_pass_rate=record.test_pass_rate,
339
+ hook_failures=record.hook_failures,
340
+ complexity_violations=record.complexity_violations,
341
+ security_issues=record.security_issues,
342
+ type_errors=record.type_errors,
343
+ linting_issues=record.linting_issues,
344
+ quality_score=record.quality_score,
345
+ )
346
+
347
+ def _run_async(self, coro: t.Awaitable[t.Any]) -> t.Any:
348
+ try:
349
+ asyncio.get_running_loop()
350
+ except RuntimeError:
351
+ return asyncio.run(coro)
352
+ msg = (
353
+ "QualityBaselineService synchronous method called while an event loop is "
354
+ "running. Use the corresponding async method instead."
355
+ )
356
+ raise RuntimeError(msg)
357
+
358
+ # Protocol methods
359
+ def get_current_baseline(self) -> dict[str, t.Any]:
360
+ """Protocol method for getting baseline metrics."""
361
+ baseline = self.get_baseline() # Call the existing method
362
+ if baseline:
363
+ return baseline.to_dict()
364
+ return {}
365
+
366
+ def update_baseline(self, metrics: dict[str, t.Any]) -> bool:
367
+ """Protocol method for updating baseline metrics."""
368
+ try:
369
+ # Extract required values from metrics dict
370
+ coverage_percent = metrics.get("coverage_percent", 0.0)
371
+ test_count = metrics.get("test_count", 0)
372
+ test_pass_rate = metrics.get("test_pass_rate", 0.0)
373
+ hook_failures = metrics.get("hook_failures", 0)
374
+ complexity_violations = metrics.get("complexity_violations", 0)
375
+ security_issues = metrics.get("security_issues", 0)
376
+ type_errors = metrics.get("type_errors", 0)
377
+ linting_issues = metrics.get("linting_issues", 0)
378
+
379
+ result = self.record_baseline(
380
+ coverage_percent=coverage_percent,
381
+ test_count=test_count,
382
+ test_pass_rate=test_pass_rate,
383
+ hook_failures=hook_failures,
384
+ complexity_violations=complexity_violations,
385
+ security_issues=security_issues,
386
+ type_errors=type_errors,
387
+ linting_issues=linting_issues,
388
+ )
389
+ return result is not None
390
+ except Exception:
391
+ return False
392
+
393
+ def compare(self, current: dict[str, t.Any]) -> dict[str, t.Any]:
394
+ """Protocol method for comparing current metrics against baseline."""
395
+ return self.compare_with_baseline(current)