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
@@ -1,276 +1,193 @@
1
+ import asyncio
1
2
  import hashlib
2
- import json
3
- import time
4
- import typing as t
5
- from dataclasses import asdict, dataclass, field
3
+ import logging
4
+ from collections.abc import Awaitable
5
+ from dataclasses import dataclass
6
6
  from pathlib import Path
7
+ from typing import Any
7
8
 
8
- from crackerjack.models.task import HookResult
9
-
10
-
11
- @dataclass
12
- class CacheEntry:
13
- key: str
14
- value: t.Any
15
- created_at: float = field(default_factory=time.time)
16
- accessed_at: float = field(default_factory=time.time)
17
- ttl_seconds: int = 3600
18
- access_count: int = 0
9
+ from acb.adapters import AdapterNotInstalled, import_adapter
10
+ from acb.depends import depends
19
11
 
20
- @property
21
- def is_expired(self) -> bool:
22
- return (time.time() - self.created_at) > self.ttl_seconds
23
-
24
- @property
25
- def age_seconds(self) -> int:
26
- return int(time.time() - self.created_at)
12
+ from crackerjack.models.task import HookResult
27
13
 
28
- def touch(self) -> None:
29
- self.accessed_at = time.time()
30
- self.access_count += 1
14
+ logger = logging.getLogger(__name__)
31
15
 
32
- def to_dict(self) -> dict[str, t.Any]:
33
- return asdict(self)
16
+ Cache: Any | None = None
17
+ _cache_import_error: Exception | None = None
34
18
 
35
- @classmethod
36
- def from_dict(cls, data: dict[str, t.Any]) -> "CacheEntry":
37
- return cls(**data)
19
+ if import_adapter is not None:
20
+ try:
21
+ Cache = import_adapter("cache")
22
+ except AdapterNotInstalled as e: # pragma: no cover - depends on env
23
+ _cache_import_error = e
24
+ Cache = None
25
+ except Exception as e: # pragma: no cover - defensive
26
+ _cache_import_error = e
27
+ Cache = None
38
28
 
39
29
 
40
30
  @dataclass
41
31
  class CacheStats:
32
+ """Cache statistics compatible with legacy cache implementation."""
33
+
42
34
  hits: int = 0
43
35
  misses: int = 0
44
36
  evictions: int = 0
45
37
  total_entries: int = 0
46
- total_size_bytes: int = 0
47
38
 
48
39
  @property
49
40
  def hit_rate(self) -> float:
50
- total = self.hits + self.misses
51
- return (self.hits / total * 100) if total > 0 else 0.0
41
+ total_requests = self.hits + self.misses
42
+ return (self.hits / total_requests * 100) if total_requests else 0.0
52
43
 
53
- def to_dict(self) -> dict[str, t.Any]:
44
+ def to_dict(self) -> dict[str, Any]:
54
45
  return {
55
46
  "hits": self.hits,
56
47
  "misses": self.misses,
57
48
  "evictions": self.evictions,
58
49
  "total_entries": self.total_entries,
59
50
  "hit_rate_percent": round(self.hit_rate, 2),
60
- "total_size_mb": round(self.total_size_bytes / 1024 / 1024, 2),
61
51
  }
62
52
 
63
53
 
64
- class InMemoryCache:
65
- def __init__(self, max_entries: int = 1000, default_ttl: int = 3600) -> None:
66
- self.max_entries = max_entries
67
- self.default_ttl = default_ttl
68
- self._cache: dict[str, CacheEntry] = {}
69
- self.stats = CacheStats()
70
-
71
- def get(self, key: str) -> t.Any | None:
72
- entry = self._cache.get(key)
73
-
74
- if entry is None:
75
- self.stats.misses += 1
76
- return None
77
-
78
- if entry.is_expired:
79
- del self._cache[key]
80
- self.stats.misses += 1
81
- self.stats.evictions += 1
82
- return None
83
-
84
- entry.touch()
85
- self.stats.hits += 1
86
- return entry.value
87
-
88
- def set(self, key: str, value: t.Any, ttl_seconds: int | None = None) -> None:
89
- if ttl_seconds is None:
90
- ttl_seconds = self.default_ttl
91
-
92
- if len(self._cache) >= self.max_entries:
93
- self._evict_lru()
94
-
95
- self._cache[key] = CacheEntry(
96
- key=key,
97
- value=value,
98
- ttl_seconds=ttl_seconds,
54
+ def get_cache() -> Any:
55
+ """Return the configured cache backend from ACB."""
56
+ if Cache is None or depends is None:
57
+ reason = (
58
+ f"{type(_cache_import_error).__name__}: {_cache_import_error}"
59
+ if _cache_import_error is not None
60
+ else "cache adapter import failed"
99
61
  )
100
-
101
- self.stats.total_entries = len(self._cache)
102
-
103
- def invalidate(self, key: str) -> bool:
104
- if key in self._cache:
105
- del self._cache[key]
106
- self.stats.total_entries = len(self._cache)
107
- return True
108
- return False
109
-
110
- def clear(self) -> None:
111
- evicted = len(self._cache)
112
- self._cache.clear()
113
- self.stats.evictions += evicted
114
- self.stats.total_entries = 0
115
-
116
- def cleanup_expired(self) -> int:
117
- expired_keys = [key for key, entry in self._cache.items() if entry.is_expired]
118
-
119
- for key in expired_keys:
120
- del self._cache[key]
121
-
122
- self.stats.evictions += len(expired_keys)
123
- self.stats.total_entries = len(self._cache)
124
- return len(expired_keys)
125
-
126
- def _evict_lru(self) -> None:
127
- if not self._cache:
128
- return
129
-
130
- lru_key = min(self._cache.keys(), key=lambda k: self._cache[k].accessed_at)
131
-
132
- del self._cache[lru_key]
133
- self.stats.evictions += 1
134
-
135
-
136
- class FileCache:
137
- def __init__(self, cache_dir: Path, namespace: str = "crackerjack") -> None:
138
- self.cache_dir = cache_dir / namespace
139
- self.cache_dir.mkdir(parents=True, exist_ok=True)
140
- self.stats = CacheStats()
141
-
142
- def get(self, key: str) -> t.Any | None:
143
- cache_file = self._get_cache_file(key)
144
-
145
- if not cache_file.exists():
146
- self.stats.misses += 1
147
- return None
148
-
149
- try:
150
- with cache_file.open(encoding="utf-8") as f:
151
- data = json.load(f)
152
- entry = CacheEntry.from_dict(data)
153
-
154
- if entry.is_expired:
155
- cache_file.unlink(missing_ok=True)
156
- self.stats.misses += 1
157
- self.stats.evictions += 1
158
- return None
159
-
160
- entry.touch()
161
-
162
- with cache_file.open("w", encoding="utf-8") as f:
163
- json.dump(entry.to_dict(), f)
164
-
165
- self.stats.hits += 1
166
- return entry.value
167
-
168
- except (json.JSONDecodeError, FileNotFoundError, OSError, KeyError):
169
- self.stats.misses += 1
170
- cache_file.unlink(missing_ok=True)
171
- return None
172
-
173
- def set(self, key: str, value: t.Any, ttl_seconds: int = 3600) -> None:
174
- cache_file = self._get_cache_file(key)
175
-
176
- entry = CacheEntry(
177
- key=key,
178
- value=value,
179
- ttl_seconds=ttl_seconds,
62
+ msg = (
63
+ "ACB cache adapter is unavailable. "
64
+ f"Resolve adapter configuration before continuing ({reason})."
180
65
  )
181
-
182
- try:
183
- with cache_file.open("w", encoding="utf-8") as f:
184
- json.dump(entry.to_dict(), f)
185
- except (json.JSONDecodeError, OSError, KeyError):
186
- pass
187
-
188
- def invalidate(self, key: str) -> bool:
189
- cache_file = self._get_cache_file(key)
190
- if cache_file.exists():
191
- cache_file.unlink()
192
- return True
193
- return False
194
-
195
- def clear(self) -> None:
196
- for cache_file in self.cache_dir.glob("*.cache"):
197
- cache_file.unlink(missing_ok=True)
198
-
199
- def cleanup_expired(self) -> int:
200
- removed = 0
201
- for cache_file in self.cache_dir.glob("*.cache"):
202
- try:
203
- with cache_file.open(encoding="utf-8") as f:
204
- data = json.load(f)
205
- entry = CacheEntry.from_dict(data)
206
-
207
- if entry.is_expired:
208
- cache_file.unlink()
209
- removed += 1
210
- except (json.JSONDecodeError, FileNotFoundError, OSError, KeyError):
211
- cache_file.unlink(missing_ok=True)
212
- removed += 1
213
-
214
- self.stats.evictions += removed
215
- return removed
216
-
217
- def _get_cache_file(self, key: str) -> Path:
218
- safe_key = hashlib.md5(key.encode(), usedforsecurity=False).hexdigest()
219
- return self.cache_dir / f"{safe_key}.cache"
66
+ raise RuntimeError(msg)
67
+ try:
68
+ return depends.get_sync(Cache)
69
+ except Exception as exception: # pragma: no cover - runtime safety
70
+ # Check if the error is specifically about the adapter not being found
71
+ error_msg = str(exception).lower()
72
+ if "adapter" in error_msg and "not found" in error_msg and "cache" in error_msg:
73
+ # Instead of raising an error, return None to trigger fallback behavior
74
+ import logging
75
+
76
+ logging.getLogger(__name__).warning(
77
+ "ACB cache adapter not found or not installed, using fallback behavior"
78
+ )
79
+ return None
80
+ else:
81
+ msg = (
82
+ "Failed to resolve ACB cache adapter via dependency injection. "
83
+ "Ensure adapters.yml specifies a valid cache adapter."
84
+ )
85
+ raise RuntimeError(msg) from exception
220
86
 
221
87
 
222
88
  class CrackerjackCache:
223
- # Expensive hooks that benefit from disk caching across sessions
89
+ """ACB-backed cache adapter with in-memory fallback when adapter missing."""
90
+
224
91
  EXPENSIVE_HOOKS = {
225
- "pyright",
226
- "bandit",
227
- "vulture",
228
- "complexipy",
229
- "refurb",
230
- "gitleaks",
231
- "detect-secrets",
92
+ # Type checking and analysis
93
+ "pyright", # Legacy, keep for backward compatibility
94
+ "zuban", # Fast Rust-based type checking
95
+ "skylos", # Rust-based dead code detection
96
+ # Security and vulnerability scanning
97
+ "bandit", # Python security linter
98
+ "gitleaks", # Secret scanning
99
+ "semgrep", # SAST scanning
100
+ "pyscn", # Security scanning
101
+ "pip-audit", # Dependency vulnerability scanning
102
+ # Code quality and complexity
103
+ "vulture", # Dead code detection (Python)
104
+ "complexipy", # Complexity analysis
105
+ "refurb", # Python code modernization
106
+ # Schema and data validation
107
+ "check-jsonschema", # JSON schema validation (8466+ files)
108
+ # Text and formatting
109
+ "codespell", # Spell checking across entire codebase
110
+ "ruff-check", # Comprehensive Python linting
111
+ "mdformat", # Markdown formatting
232
112
  }
233
113
 
234
- # TTL configuration for different cache types (in seconds)
235
114
  HOOK_DISK_TTLS = {
236
- "pyright": 86400, # 24 hours - type checking is stable
237
- "bandit": 86400 * 3, # 3 days - security patterns change slowly
238
- "vulture": 86400 * 2, # 2 days - dead code detection is stable
239
- "complexipy": 86400, # 24 hours - complexity analysis
240
- "refurb": 86400, # 24 hours - code improvements
241
- "gitleaks": 86400 * 7, # 7 days - secret detection is very stable
242
- "detect-secrets": 86400 * 7, # 7 days - secret detection
115
+ # Type checking - daily updates as code changes
116
+ "pyright": 86400, # 1 day
117
+ "zuban": 86400, # 1 day
118
+ "skylos": 86400 * 2, # 2 days - dead code analysis less volatile
119
+ # Security - longer TTLs, check periodically for new vulnerabilities
120
+ "bandit": 86400 * 3, # 3 days
121
+ "gitleaks": 86400 * 7, # 7 days - secrets rarely change once clean
122
+ "semgrep": 86400 * 3, # 3 days - security rules change occasionally
123
+ "pyscn": 86400 * 3, # 3 days
124
+ "pip-audit": 86400, # 1 day - check daily for new CVEs
125
+ # Code quality
126
+ "vulture": 86400 * 2, # 2 days
127
+ "complexipy": 86400, # 1 day
128
+ "refurb": 86400, # 1 day
129
+ # Validation and formatting - longest TTLs
130
+ "check-jsonschema": 86400 * 7, # 7 days - schemas rarely change
131
+ "codespell": 86400 * 7, # 7 days - spelling errors are rare
132
+ "ruff-check": 86400, # 1 day - linting rules can change
133
+ "mdformat": 86400 * 7, # 7 days - markdown style rarely changes
243
134
  }
244
135
 
245
- # Agent version for cache invalidation when agent logic changes
246
136
  AGENT_VERSION = "1.0.0"
247
137
 
248
138
  def __init__(
249
139
  self,
250
140
  cache_dir: Path | None = None,
251
141
  enable_disk_cache: bool = True,
142
+ backend: Any | None = None,
252
143
  ) -> None:
253
- if cache_dir:
254
- self.cache_dir = cache_dir
255
- else:
256
- self.cache_dir = Path.cwd() / ".crackerjack" / "cache"
257
-
144
+ self.cache_dir = cache_dir or Path.cwd() / ".crackerjack" / "cache"
258
145
  self.enable_disk_cache = enable_disk_cache
146
+ self.stats = CacheStats()
147
+
148
+ if backend is not None:
149
+ self._backend = backend
150
+ else:
151
+ # Try to get ACB cache adapter, fallback to None if unavailable
152
+ try:
153
+ self._backend = get_cache()
154
+ except RuntimeError:
155
+ # ACB cache adapter not available - use in-memory fallback
156
+ logger.info("ACB cache adapter unavailable, using in-memory cache")
157
+ self._backend = None
259
158
 
260
- self.hook_results_cache = InMemoryCache(max_entries=500, default_ttl=1800)
261
- self.file_hash_cache = InMemoryCache(max_entries=2000)
262
- self.config_cache = InMemoryCache(max_entries=100, default_ttl=7200)
159
+ @staticmethod
160
+ def _run_async(coro: Awaitable[Any]) -> Any:
161
+ async def _await(value: Awaitable[Any]) -> Any:
162
+ return await value
263
163
 
264
- if enable_disk_cache:
265
- self.disk_cache = FileCache(self.cache_dir)
164
+ try:
165
+ return asyncio.run(_await(coro))
166
+ except RuntimeError as exception:
167
+ message = str(exception)
168
+ if "asyncio.run()" not in message:
169
+ raise
170
+ loop = asyncio.new_event_loop()
171
+ try:
172
+ return loop.run_until_complete(_await(coro))
173
+ finally:
174
+ loop.close()
266
175
 
267
176
  def get_hook_result(
268
177
  self,
269
178
  hook_name: str,
270
179
  file_hashes: list[str],
271
180
  ) -> HookResult | None:
181
+ if self._backend is None:
182
+ self.stats.misses += 1
183
+ return None
272
184
  cache_key = self._get_hook_cache_key(hook_name, file_hashes)
273
- return self.hook_results_cache.get(cache_key)
185
+ result = self._run_async(self._backend.get(cache_key))
186
+ if result is None:
187
+ self.stats.misses += 1
188
+ else:
189
+ self.stats.hits += 1
190
+ return result
274
191
 
275
192
  def set_hook_result(
276
193
  self,
@@ -278,8 +195,11 @@ class CrackerjackCache:
278
195
  file_hashes: list[str],
279
196
  result: HookResult,
280
197
  ) -> None:
198
+ if self._backend is None:
199
+ return
281
200
  cache_key = self._get_hook_cache_key(hook_name, file_hashes)
282
- self.hook_results_cache.set(cache_key, result, ttl_seconds=1800)
201
+ self._run_async(self._backend.set(cache_key, result, ttl=1800))
202
+ self.stats.total_entries += 1
283
203
 
284
204
  def get_expensive_hook_result(
285
205
  self,
@@ -287,20 +207,25 @@ class CrackerjackCache:
287
207
  file_hashes: list[str],
288
208
  tool_version: str | None = None,
289
209
  ) -> HookResult | None:
290
- """Get hook result with disk cache fallback for expensive hooks."""
291
- # Always check memory first for speed
210
+ if self._backend is None:
211
+ self.stats.misses += 1
212
+ return None
292
213
  result = self.get_hook_result(hook_name, file_hashes)
293
- if result:
214
+ if result is not None:
294
215
  return result
295
-
296
- # Fall back to disk cache for expensive hooks
297
- if self.enable_disk_cache and hook_name in self.EXPENSIVE_HOOKS:
298
- cache_key = self._get_versioned_hook_cache_key(
299
- hook_name, file_hashes, tool_version
300
- )
301
- return self.disk_cache.get(cache_key)
302
-
303
- return None
216
+ if not self.enable_disk_cache or hook_name not in self.EXPENSIVE_HOOKS:
217
+ return None
218
+ cache_key = self._get_versioned_hook_cache_key(
219
+ hook_name,
220
+ file_hashes,
221
+ tool_version,
222
+ )
223
+ result = self._run_async(self._backend.get(cache_key))
224
+ if result is None:
225
+ self.stats.misses += 1
226
+ else:
227
+ self.stats.hits += 1
228
+ return result
304
229
 
305
230
  def set_expensive_hook_result(
306
231
  self,
@@ -309,119 +234,126 @@ class CrackerjackCache:
309
234
  result: HookResult,
310
235
  tool_version: str | None = None,
311
236
  ) -> None:
312
- """Set hook result in both memory and disk cache for expensive hooks."""
313
- # Always set[t.Any] in memory for current session
237
+ if self._backend is None:
238
+ return
314
239
  self.set_hook_result(hook_name, file_hashes, result)
315
-
316
- # Also persist to disk for expensive hooks
317
- if self.enable_disk_cache and hook_name in self.EXPENSIVE_HOOKS:
318
- cache_key = self._get_versioned_hook_cache_key(
319
- hook_name, file_hashes, tool_version
320
- )
321
- ttl = self.HOOK_DISK_TTLS.get(hook_name, 86400) # Default 24 hours
322
- self.disk_cache.set(cache_key, result, ttl_seconds=ttl)
240
+ if not self.enable_disk_cache or hook_name not in self.EXPENSIVE_HOOKS:
241
+ return
242
+ cache_key = self._get_versioned_hook_cache_key(
243
+ hook_name,
244
+ file_hashes,
245
+ tool_version,
246
+ )
247
+ ttl = self.HOOK_DISK_TTLS.get(hook_name, 86400)
248
+ self._run_async(self._backend.set(cache_key, result, ttl=ttl))
323
249
 
324
250
  def get_file_hash(self, file_path: Path) -> str | None:
251
+ if self._backend is None:
252
+ self.stats.misses += 1
253
+ return None
325
254
  stat = file_path.stat()
326
- cache_key = f"file_hash: {file_path}: {stat.st_mtime}: {stat.st_size}"
327
- return self.file_hash_cache.get(cache_key)
255
+ cache_key = f"file_hash:{file_path}:{stat.st_mtime}:{stat.st_size}"
256
+ result = self._run_async(self._backend.get(cache_key))
257
+ if result is None:
258
+ self.stats.misses += 1
259
+ else:
260
+ self.stats.hits += 1
261
+ return result
328
262
 
329
263
  def set_file_hash(self, file_path: Path, file_hash: str) -> None:
264
+ if self._backend is None:
265
+ return
330
266
  stat = file_path.stat()
331
- cache_key = f"file_hash: {file_path}: {stat.st_mtime}: {stat.st_size}"
332
- self.file_hash_cache.set(cache_key, file_hash, ttl_seconds=3600)
267
+ cache_key = f"file_hash:{file_path}:{stat.st_mtime}:{stat.st_size}"
268
+ self._run_async(self._backend.set(cache_key, file_hash, ttl=3600))
269
+ self.stats.total_entries += 1
333
270
 
334
- def get_config_data(self, config_key: str) -> t.Any | None:
335
- return self.config_cache.get(f"config: {config_key}")
271
+ def get_config_data(self, config_key: str) -> Any | None:
272
+ if self._backend is None:
273
+ self.stats.misses += 1
274
+ return None
275
+ result = self._run_async(self._backend.get(f"config:{config_key}"))
276
+ if result is None:
277
+ self.stats.misses += 1
278
+ else:
279
+ self.stats.hits += 1
280
+ return result
336
281
 
337
- def set_config_data(self, config_key: str, data: t.Any) -> None:
338
- self.config_cache.set(f"config: {config_key}", data, ttl_seconds=7200)
282
+ def set_config_data(self, config_key: str, data: Any) -> None:
283
+ if self._backend is None:
284
+ return
285
+ self._run_async(self._backend.set(f"config:{config_key}", data, ttl=7200))
286
+ self.stats.total_entries += 1
339
287
 
340
- def get(self, key: str, default: t.Any = None) -> t.Any:
341
- """General purpose get method for metrics and other data."""
342
- return self.config_cache.get(key) or default
288
+ def get(self, key: str, default: Any = None) -> Any:
289
+ if self._backend is None:
290
+ return default
291
+ result = self._run_async(self._backend.get(key))
292
+ return result if result is not None else default
343
293
 
344
- def set(self, key: str, value: t.Any, ttl_seconds: int | None = None) -> None:
345
- """General purpose set method for metrics and other data."""
346
- ttl = ttl_seconds or 3600 # Default 1 hour
347
- self.config_cache.set(key, value, ttl_seconds=ttl)
294
+ def set(self, key: str, value: Any, ttl_seconds: int | None = None) -> None:
295
+ if self._backend is None:
296
+ return
297
+ ttl = ttl_seconds if ttl_seconds is not None else 3600
298
+ self._run_async(self._backend.set(key, value, ttl=ttl))
348
299
 
349
- def get_agent_decision(self, agent_name: str, issue_hash: str) -> t.Any | None:
350
- """Get cached AI agent decision based on issue content."""
351
- if not self.enable_disk_cache:
300
+ def get_agent_decision(self, agent_name: str, issue_hash: str) -> Any | None:
301
+ if self._backend is None or not self.enable_disk_cache:
352
302
  return None
353
-
354
303
  cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
355
- return self.disk_cache.get(cache_key)
304
+ return self._run_async(self._backend.get(cache_key))
356
305
 
357
306
  def set_agent_decision(
358
- self, agent_name: str, issue_hash: str, decision: t.Any
307
+ self,
308
+ agent_name: str,
309
+ issue_hash: str,
310
+ decision: Any,
359
311
  ) -> None:
360
- """Cache AI agent decision for future use."""
361
- if not self.enable_disk_cache:
312
+ if self._backend is None or not self.enable_disk_cache:
362
313
  return
363
-
364
314
  cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
365
- self.disk_cache.set(cache_key, decision, ttl_seconds=604800) # 7 days
315
+ self._run_async(self._backend.set(cache_key, decision, ttl=604800))
366
316
 
367
- def get_quality_baseline(self, git_hash: str) -> dict[str, t.Any] | None:
368
- """Get quality baseline metrics for a specific git commit."""
369
- if not self.enable_disk_cache:
317
+ def get_quality_baseline(self, git_hash: str) -> dict[str, Any] | None:
318
+ if self._backend is None or not self.enable_disk_cache:
370
319
  return None
320
+ return self._run_async(self._backend.get(f"baseline:{git_hash}"))
371
321
 
372
- return self.disk_cache.get(f"baseline:{git_hash}")
373
-
374
- def set_quality_baseline(self, git_hash: str, metrics: dict[str, t.Any]) -> None:
375
- """Store quality baseline metrics for a git commit."""
376
- if not self.enable_disk_cache:
322
+ def set_quality_baseline(
323
+ self,
324
+ git_hash: str,
325
+ metrics: dict[str, Any],
326
+ ) -> None:
327
+ if self._backend is None or not self.enable_disk_cache:
377
328
  return
329
+ self._run_async(self._backend.set(f"baseline:{git_hash}", metrics, ttl=2592000))
378
330
 
379
- self.disk_cache.set(
380
- f"baseline:{git_hash}", metrics, ttl_seconds=2592000
381
- ) # 30 days
382
-
383
- def invalidate_hook_cache(self, hook_name: str | None = None) -> None:
384
- if hook_name:
385
- keys_to_remove = [
386
- key
387
- for key in self.hook_results_cache._cache
388
- if key.startswith(f"hook_result: {hook_name}: ")
389
- ]
390
- for key in keys_to_remove:
391
- self.hook_results_cache.invalidate(key)
392
- else:
393
- self.hook_results_cache.clear()
394
-
395
- def cleanup_all(self) -> dict[str, int]:
396
- results = {
397
- "hook_results": self.hook_results_cache.cleanup_expired(),
398
- "file_hashes": self.file_hash_cache.cleanup_expired(),
399
- "config": self.config_cache.cleanup_expired(),
400
- }
401
-
402
- if self.enable_disk_cache:
403
- results["disk_cache"] = self.disk_cache.cleanup_expired()
404
-
405
- return results
331
+ @staticmethod
332
+ def invalidate_hook_cache(hook_name: str | None = None) -> None:
333
+ logger.warning(
334
+ "ACB cache fallback does not support selective invalidation (hook=%s).",
335
+ hook_name,
336
+ )
406
337
 
407
- def get_cache_stats(self) -> dict[str, t.Any]:
408
- stats = {
409
- "hook_results": self.hook_results_cache.stats.to_dict(),
410
- "file_hashes": self.file_hash_cache.stats.to_dict(),
411
- "config": self.config_cache.stats.to_dict(),
338
+ @staticmethod
339
+ def cleanup_all() -> dict[str, int]:
340
+ return {
341
+ "hook_results": 0,
342
+ "file_hashes": 0,
343
+ "config": 0,
344
+ "disk_cache": 0,
412
345
  }
413
346
 
414
- if self.enable_disk_cache:
415
- stats["disk_cache"] = self.disk_cache.stats.to_dict()
416
-
417
- return stats
347
+ def get_cache_stats(self) -> dict[str, Any]:
348
+ return {"acb_cache": self.stats.to_dict()}
418
349
 
419
- def _get_hook_cache_key(self, hook_name: str, file_hashes: list[str]) -> str:
350
+ @staticmethod
351
+ def _get_hook_cache_key(hook_name: str, file_hashes: list[str]) -> str:
420
352
  hash_signature = hashlib.md5(
421
- ", ".join(sorted(file_hashes)).encode(),
353
+ ",".join(sorted(file_hashes)).encode(),
422
354
  usedforsecurity=False,
423
355
  ).hexdigest()
424
- return f"hook_result: {hook_name}: {hash_signature}"
356
+ return f"hook_result:{hook_name}:{hash_signature}"
425
357
 
426
358
  def _get_versioned_hook_cache_key(
427
359
  self,
@@ -429,10 +361,9 @@ class CrackerjackCache:
429
361
  file_hashes: list[str],
430
362
  tool_version: str | None = None,
431
363
  ) -> str:
432
- """Get cache key with tool version for disk cache invalidation."""
433
- hash_signature = hashlib.md5(
434
- ", ".join(sorted(file_hashes)).encode(),
435
- usedforsecurity=False,
436
- ).hexdigest()
437
364
  version_part = f":{tool_version}" if tool_version else ""
438
- return f"hook_result: {hook_name}: {hash_signature}{version_part}"
365
+ base_key = self._get_hook_cache_key(hook_name, file_hashes)
366
+ return f"{base_key}{version_part}"
367
+
368
+
369
+ __all__ = ["CrackerjackCache", "CacheStats", "Cache", "get_cache"]