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
@@ -4,11 +4,12 @@ import logging
4
4
  import os
5
5
  import time
6
6
  import typing as t
7
+ import uuid
7
8
  from collections import defaultdict
8
- from contextlib import AbstractAsyncContextManager, asynccontextmanager, suppress
9
+ from contextlib import asynccontextmanager, suppress
9
10
  from pathlib import Path
10
11
 
11
- from ..config.global_lock_config import GlobalLockConfig
12
+ from ..config.global_lock_config import GlobalLockConfig, get_global_lock_config
12
13
 
13
14
 
14
15
  class HookLockManager:
@@ -28,9 +29,12 @@ class HookLockManager:
28
29
  "complexipy",
29
30
  }
30
31
 
31
- self._hook_locks: dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
32
+ # Create locks for all hooks that require them
33
+ self._hook_locks: dict[str, asyncio.Lock] = {
34
+ hook_name: asyncio.Lock() for hook_name in self._hooks_requiring_locks
35
+ }
32
36
 
33
- self._global_config = GlobalLockConfig()
37
+ self._global_config = get_global_lock_config()
34
38
  self._global_lock_enabled = self._global_config.enabled
35
39
  self._active_global_locks: set[str] = set()
36
40
  self._heartbeat_tasks: dict[str, asyncio.Task[None]] = {}
@@ -59,9 +63,7 @@ class HookLockManager:
59
63
  return hook_name in self._hooks_requiring_locks
60
64
 
61
65
  @asynccontextmanager
62
- async def acquire_hook_lock(
63
- self, hook_name: str
64
- ) -> AbstractAsyncContextManager[None]:
66
+ async def acquire_hook_lock(self, hook_name: str) -> t.AsyncIterator[None]:
65
67
  if not self.requires_lock(hook_name):
66
68
  yield
67
69
  return
@@ -220,7 +222,7 @@ class HookLockManager:
220
222
  )
221
223
 
222
224
  async def _attempt_lock_acquisition(self, hook_name: str, lock_path: Path) -> None:
223
- temp_path = lock_path.with_suffix(".tmp")
225
+ temp_path = lock_path.with_suffix(f".tmp.{uuid.uuid4().hex}")
224
226
 
225
227
  lock_data = {
226
228
  "session_id": self._global_config.session_id,
@@ -239,7 +241,9 @@ class HookLockManager:
239
241
  temp_path.chmod(0o600)
240
242
 
241
243
  try:
242
- temp_path.rename(lock_path)
244
+ # Use os.link() for atomic exclusive creation - fails if target exists
245
+ # (Path.rename() will replace existing file, which breaks lock semantics)
246
+ os.link(str(temp_path), str(lock_path))
243
247
  self.logger.debug(f"Successfully created global lock file: {lock_path}")
244
248
  except FileExistsError:
245
249
  with suppress(OSError):
@@ -473,6 +477,9 @@ class HookLockManager:
473
477
 
474
478
  def add_hook_to_lock_list(self, hook_name: str) -> None:
475
479
  self._hooks_requiring_locks.add(hook_name)
480
+ # Create lock for this hook if it doesn't already exist
481
+ if hook_name not in self._hook_locks:
482
+ self._hook_locks[hook_name] = asyncio.Lock()
476
483
  self.logger.info(f"Added {hook_name} to hooks requiring locks")
477
484
 
478
485
  def remove_hook_from_lock_list(self, hook_name: str) -> None:
@@ -497,7 +504,13 @@ class HookLockManager:
497
504
 
498
505
  def enable_global_lock(self, enabled: bool = True) -> None:
499
506
  self._global_lock_enabled = enabled
500
- self._global_config.enabled = enabled
507
+ # Update the settings model if supported
508
+ if hasattr(self._global_config._settings, "enabled"):
509
+ # Create a new settings object with updated enabled value
510
+ new_settings = self._global_config._settings.model_copy(
511
+ update={"enabled": enabled}
512
+ )
513
+ self._global_config._settings = new_settings
501
514
  self.logger.info(
502
515
  f"Global lock functionality {'enabled' if enabled else 'disabled'}"
503
516
  )
@@ -533,18 +546,9 @@ class HookLockManager:
533
546
  def _process_lock_file(
534
547
  self, lock_file: Path, max_age_hours: float, current_time: float
535
548
  ) -> int:
536
- try:
537
- file_age_hours = (current_time - lock_file.stat().st_mtime) / 3600
538
-
539
- if file_age_hours > max_age_hours:
540
- return self._cleanup_stale_lock_file(
541
- lock_file, max_age_hours, current_time
542
- )
543
- return 0
544
-
545
- except OSError as e:
546
- self.logger.warning(f"Could not process lock file {lock_file}: {e}")
547
- return 0
549
+ # Always attempt to check lock file data (file mtime is unreliable in tests)
550
+ # The JSON data's last_heartbeat is the source of truth for staleness
551
+ return self._cleanup_stale_lock_file(lock_file, max_age_hours, current_time)
548
552
 
549
553
  def _cleanup_stale_lock_file(
550
554
  self, lock_file: Path, max_age_hours: float, current_time: float
@@ -639,6 +643,11 @@ class HookLockManager:
639
643
  return stats
640
644
 
641
645
  def configure_from_options(self, options: t.Any) -> None:
646
+ """Configure lock manager from CLI options.
647
+
648
+ This is a synchronous method because it only performs configuration
649
+ updates without needing to await any async operations.
650
+ """
642
651
  self._global_config = GlobalLockConfig.from_options(options)
643
652
  self._global_lock_enabled = self._global_config.enabled
644
653
 
@@ -5,9 +5,12 @@ import typing as t
5
5
  from dataclasses import dataclass
6
6
  from pathlib import Path
7
7
 
8
- from rich.console import Console
8
+ from acb.console import Console
9
9
 
10
+ from crackerjack.cli.formatting import separator as make_separator
11
+ from crackerjack.config import get_console_width
10
12
  from crackerjack.config.hooks import HookDefinition, HookStrategy
13
+ from crackerjack.executors.hook_executor import HookExecutionResult
11
14
  from crackerjack.models.protocols import HookLockManagerProtocol
12
15
  from crackerjack.models.task import HookResult
13
16
  from crackerjack.services.regex_patterns import SAFE_PATTERNS
@@ -354,6 +357,7 @@ class IndividualHookExecutor:
354
357
  self,
355
358
  strategy: HookStrategy,
356
359
  ) -> IndividualExecutionResult:
360
+ """Execute hook strategy with individual (sequential) execution and progress tracking."""
357
361
  start_time = time.time()
358
362
  self._print_strategy_header(strategy)
359
363
 
@@ -364,6 +368,37 @@ class IndividualHookExecutor:
364
368
 
365
369
  return self._finalize_execution_result(strategy, execution_state, start_time)
366
370
 
371
+ async def execute_strategy(
372
+ self,
373
+ strategy: HookStrategy,
374
+ ) -> HookExecutionResult: # Changed return type to match base class
375
+ """Execute hook strategy - API-compatible method matching other executors."""
376
+ start_time = time.time()
377
+ self._print_strategy_header(strategy)
378
+
379
+ execution_state = self._initialize_execution_state()
380
+
381
+ for hook in strategy.hooks:
382
+ await self._execute_single_hook_in_strategy(hook, execution_state)
383
+
384
+ # Call finalize with original strategy name instead of modified one
385
+ total_duration = time.time() - start_time
386
+ success = all(r.status == "passed" for r in execution_state["hook_results"])
387
+
388
+ self._print_individual_summary(
389
+ strategy,
390
+ execution_state["hook_results"],
391
+ execution_state["hook_progress"],
392
+ )
393
+
394
+ # Return HookExecutionResult to maintain interface compatibility
395
+ return HookExecutionResult(
396
+ strategy_name=strategy.name, # Use original name, not with "_individual" suffix
397
+ results=execution_state["hook_results"],
398
+ total_duration=total_duration,
399
+ success=success,
400
+ )
401
+
367
402
  def _initialize_execution_state(self) -> dict[str, t.Any]:
368
403
  return {"hook_results": [], "hook_progress": [], "execution_order": []}
369
404
 
@@ -431,13 +466,7 @@ class IndividualHookExecutor:
431
466
  if self.progress_callback:
432
467
  self.progress_callback(progress)
433
468
 
434
- if self.hook_lock_manager.requires_lock(hook.name):
435
- self.console.print(
436
- f"\n[bold cyan]🔍 Running {hook.name} (with lock)[/ bold cyan]"
437
- )
438
- else:
439
- self.console.print(f"\n[bold cyan]🔍 Running {hook.name}[/ bold cyan]")
440
-
469
+ # Don't print verbose "Running..." messages - the dotted-line format shows status
441
470
  cmd = hook.get_command()
442
471
 
443
472
  try:
@@ -458,11 +487,16 @@ class IndividualHookExecutor:
458
487
  parsed_output["errors"] + parsed_output["warnings"]
459
488
  )
460
489
 
490
+ status = "passed" if result.returncode == 0 else "failed"
491
+ # Ensure failed hooks always have at least 1 issue count
492
+ issues_count = 1 if status == "failed" else 0
493
+
461
494
  hook_result = HookResult(
462
495
  id=hook.name,
463
496
  name=hook.name,
464
- status="passed" if result.returncode == 0 else "failed",
497
+ status=status,
465
498
  duration=progress.duration or 0,
499
+ issues_count=issues_count,
466
500
  )
467
501
 
468
502
  self._print_hook_summary(hook.name, hook_result, progress)
@@ -479,6 +513,7 @@ class IndividualHookExecutor:
479
513
  name=hook.name,
480
514
  status="failed",
481
515
  duration=hook.timeout,
516
+ issues_count=1, # Timeout counts as 1 issue
482
517
  )
483
518
  except Exception as e:
484
519
  progress.status = "failed"
@@ -490,6 +525,7 @@ class IndividualHookExecutor:
490
525
  name=hook.name,
491
526
  status="failed",
492
527
  duration=progress.duration or 0,
528
+ issues_count=1, # Error counts as 1 issue
493
529
  )
494
530
 
495
531
  async def _run_command_with_streaming(
@@ -519,14 +555,11 @@ class IndividualHookExecutor:
519
555
  return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
520
556
 
521
557
  async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
522
- repo_root = (
523
- self.pkg_path.parent
524
- if self.pkg_path.name == "crackerjack"
525
- else self.pkg_path
526
- )
558
+ # Use pkg_path directly as the working directory for hook execution
559
+ # This ensures hooks run in the correct project directory regardless of project name
527
560
  return await asyncio.create_subprocess_exec(
528
561
  *cmd,
529
- cwd=repo_root,
562
+ cwd=self.pkg_path,
530
563
  stdout=asyncio.subprocess.PIPE,
531
564
  stderr=asyncio.subprocess.PIPE,
532
565
  )
@@ -602,8 +635,8 @@ class IndividualHookExecutor:
602
635
  ):
603
636
  self.progress_callback(progress)
604
637
 
638
+ @staticmethod
605
639
  async def _wait_for_process_completion(
606
- self,
607
640
  process: asyncio.subprocess.Process,
608
641
  tasks: list[asyncio.Task[None]],
609
642
  timeout: int,
@@ -611,8 +644,8 @@ class IndividualHookExecutor:
611
644
  await asyncio.wait_for(process.wait(), timeout=timeout)
612
645
  await asyncio.gather(*tasks, return_exceptions=True)
613
646
 
647
+ @staticmethod
614
648
  def _handle_process_timeout(
615
- self,
616
649
  process: asyncio.subprocess.Process,
617
650
  tasks: list[asyncio.Task[None]],
618
651
  ) -> None:
@@ -620,8 +653,8 @@ class IndividualHookExecutor:
620
653
  for task in tasks:
621
654
  task.cancel()
622
655
 
656
+ @staticmethod
623
657
  def _create_completed_process(
624
- self,
625
658
  cmd: list[str],
626
659
  process: asyncio.subprocess.Process,
627
660
  stdout_lines: list[str],
@@ -651,22 +684,23 @@ class IndividualHookExecutor:
651
684
  result: HookResult,
652
685
  progress: HookProgress,
653
686
  ) -> None:
687
+ """Print hook result in dotted-line format matching pre-commit style.
688
+
689
+ Format: hook-name.......................................... ✅
690
+ """
654
691
  status_icon = "✅" if result.status == "passed" else "❌"
655
- duration_str = f"{progress.duration: .1f}s" if progress.duration else "0.0s"
656
692
 
657
- summary_parts: list[str] = []
658
- if progress.errors_found > 0:
659
- summary_parts.append(f"{progress.errors_found} errors")
660
- if progress.warnings_found > 0:
661
- summary_parts.append(f"{progress.warnings_found} warnings")
662
- if progress.files_processed > 0:
663
- summary_parts.append(f"{progress.files_processed} files")
693
+ # Calculate dotted line (same logic as base HookExecutor)
694
+ max_width = get_console_width()
695
+ content_width = max_width - 4 # Adjusted for icon and padding
664
696
 
665
- summary = ", ".join(summary_parts) if summary_parts else "clean"
697
+ if len(hook_name) > content_width:
698
+ line = hook_name[: content_width - 3] + "..."
699
+ else:
700
+ dots_needed = max(0, content_width - len(hook_name))
701
+ line = hook_name + ("." * dots_needed)
666
702
 
667
- self.console.print(
668
- f"[bold]{status_icon} {hook_name}[/ bold] - {duration_str}-{summary}",
669
- )
703
+ self.console.print(f"{line} {status_icon}")
670
704
 
671
705
  def _print_individual_summary(
672
706
  self,
@@ -680,7 +714,7 @@ class IndividualHookExecutor:
680
714
  total_warnings = sum(p.warnings_found for p in progress_list)
681
715
  total_duration = sum(p.duration or 0 for p in progress_list)
682
716
 
683
- self.console.print("\n" + "-" * 74)
717
+ self.console.print("\n" + make_separator("-", get_console_width()))
684
718
  self.console.print(
685
719
  f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
686
720
  )
@@ -702,4 +736,4 @@ class IndividualHookExecutor:
702
736
  )
703
737
  self.console.print(f" ❌ {progress.hook_name}-{error_summary}")
704
738
 
705
- self.console.print("-" * 74)
739
+ self.console.print(make_separator("-", get_console_width()))
@@ -1,8 +1,10 @@
1
1
  import time
2
2
  import typing as t
3
+ from contextlib import suppress
3
4
  from pathlib import Path
4
5
 
5
- from rich.console import Console
6
+ from acb.console import Console
7
+ from acb.depends import Inject, depends
6
8
 
7
9
  from crackerjack.config.hooks import HookDefinition, HookStrategy
8
10
  from crackerjack.executors.hook_executor import HookExecutionResult, HookExecutor
@@ -19,18 +21,25 @@ except ImportError:
19
21
  class LSPAwareHookExecutor(HookExecutor):
20
22
  """Hook executor that can leverage LSP server for enhanced performance."""
21
23
 
24
+ @depends.inject
22
25
  def __init__(
23
26
  self,
24
- console: Console,
27
+ console: Inject[Console],
25
28
  pkg_path: Path,
26
29
  verbose: bool = False,
27
30
  quiet: bool = False,
31
+ debug: bool = False,
28
32
  use_tool_proxy: bool = True,
33
+ use_incremental: bool = False,
34
+ git_service: t.Any | None = None,
29
35
  ) -> None:
30
- super().__init__(console, pkg_path, verbose, quiet)
31
- self.lsp_client = LSPClient(console)
36
+ super().__init__(
37
+ console, pkg_path, verbose, quiet, debug, use_incremental, git_service
38
+ )
39
+ self.lsp_client = LSPClient()
32
40
  self.use_tool_proxy = use_tool_proxy and ToolProxy is not None
33
- self.tool_proxy = ToolProxy(console) if self.use_tool_proxy else None
41
+ self.tool_proxy = ToolProxy() if self.use_tool_proxy else None
42
+ self.debug = debug
34
43
 
35
44
  def execute_strategy(self, strategy: HookStrategy) -> HookExecutionResult:
36
45
  """Execute hook strategy with LSP optimization where possible."""
@@ -38,24 +47,14 @@ class LSPAwareHookExecutor(HookExecutor):
38
47
  results = []
39
48
 
40
49
  # Check if LSP server is available
41
- lsp_available = self.lsp_client.is_server_running()
42
-
43
- if lsp_available and not self.quiet:
44
- server_info = self.lsp_client.get_server_info()
45
- if server_info:
46
- self.console.print(
47
- f"🔍 LSP server available (PID: {server_info['pid']}), using optimized execution"
48
- )
50
+ lsp_available = self._check_lsp_availability()
49
51
 
50
52
  # Execute hooks with LSP optimization and tool proxy resilience
51
53
  for hook in strategy.hooks:
52
- if self._should_use_lsp_for_hook(hook, lsp_available):
53
- result = self._execute_lsp_hook(hook)
54
- elif self._should_use_tool_proxy(hook):
55
- result = self._execute_hook_with_proxy(hook)
56
- else:
57
- result = self.execute_single_hook(hook)
54
+ self._handle_progress_start(len(strategy.hooks))
55
+ result = self._execute_single_hook_with_strategies(hook, lsp_available)
58
56
  results.append(result)
57
+ self._handle_progress_completion(len(strategy.hooks))
59
58
 
60
59
  duration = time.time() - start_time
61
60
  success = all(result.status in ("passed", "skipped") for result in results)
@@ -68,6 +67,51 @@ class LSPAwareHookExecutor(HookExecutor):
68
67
  concurrent_execution=False,
69
68
  )
70
69
 
70
+ def _check_lsp_availability(self) -> bool:
71
+ """Check if LSP server is available and print info message."""
72
+ lsp_available = self.lsp_client.is_server_running()
73
+
74
+ if lsp_available and not self.quiet:
75
+ server_info = self.lsp_client.get_server_info()
76
+ if server_info:
77
+ self.console.print(
78
+ f"🔍 LSP server available (PID: {server_info['pid']}), using optimized execution"
79
+ )
80
+
81
+ return lsp_available
82
+
83
+ def _execute_single_hook_with_strategies(
84
+ self, hook: HookDefinition, lsp_available: bool
85
+ ) -> HookResult:
86
+ """Execute a single hook using appropriate strategy."""
87
+ if self._should_use_lsp_for_hook(hook, lsp_available):
88
+ return self._execute_lsp_hook(hook)
89
+ elif self._should_use_tool_proxy(hook):
90
+ return self._execute_hook_with_proxy(hook)
91
+
92
+ return self.execute_single_hook(hook)
93
+
94
+ def _handle_progress_start(self, total_hooks: int | None = None) -> None:
95
+ """Handle progress start callback."""
96
+ with suppress(Exception):
97
+ callback = getattr(self, "_progress_start_callback", None)
98
+ if callback:
99
+ # _total_hooks/_started_hooks are initialized by set_progress_callbacks on base class
100
+ self._started_hooks += 1 # type: ignore[attr-defined]
101
+ total = self._total_hooks or total_hooks # type: ignore[attr-defined]
102
+ if total:
103
+ callback(self._started_hooks, total) # type: ignore[attr-defined]
104
+
105
+ def _handle_progress_completion(self, total_hooks: int | None = None) -> None:
106
+ """Handle progress completion callback."""
107
+ with suppress(Exception):
108
+ callback = getattr(self, "_progress_callback", None)
109
+ if callback:
110
+ self._completed_hooks += 1 # type: ignore[attr-defined]
111
+ total = self._total_hooks or total_hooks # type: ignore[attr-defined]
112
+ if total:
113
+ callback(self._completed_hooks, total) # type: ignore[attr-defined]
114
+
71
115
  def _should_use_lsp_for_hook(
72
116
  self, hook: HookDefinition, lsp_available: bool
73
117
  ) -> bool:
@@ -108,13 +152,32 @@ class LSPAwareHookExecutor(HookExecutor):
108
152
 
109
153
  self._display_lsp_results(hook, has_errors, output, summary)
110
154
 
155
+ # Create hook result
156
+ return self._create_lsp_hook_result(
157
+ hook, duration, has_errors, output, diagnostics
158
+ )
159
+
160
+ def _create_lsp_hook_result(
161
+ self,
162
+ hook: HookDefinition,
163
+ duration: float,
164
+ has_errors: bool,
165
+ output: str,
166
+ diagnostics: dict,
167
+ ) -> HookResult:
168
+ """Create the HookResult for LSP execution."""
169
+ # Ensure failed hooks always have at least 1 issue count
170
+ issues_found = [output] if has_errors else []
171
+ issues_count = max(len(issues_found), 1 if has_errors else 0)
172
+
111
173
  return HookResult(
112
174
  id=f"{hook.name}-lsp-{int(time.time())}",
113
175
  name=f"{hook.name}-lsp",
114
176
  status="failed" if has_errors else "passed",
115
177
  duration=duration,
116
178
  files_processed=len(diagnostics),
117
- issues_found=[output] if has_errors else [],
179
+ issues_found=issues_found,
180
+ issues_count=issues_count,
118
181
  )
119
182
 
120
183
  def _format_lsp_output(self, diagnostics: dict[str, t.Any], duration: float) -> str:
@@ -163,50 +226,66 @@ class LSPAwareHookExecutor(HookExecutor):
163
226
  start_time = time.time()
164
227
 
165
228
  try:
166
- if not self.quiet:
167
- self.console.print(
168
- f"🛡️ Using resilient execution for {hook.name}", style="blue"
169
- )
229
+ return self._perform_proxy_execution(hook, start_time)
230
+ except Exception as e:
231
+ return self._handle_proxy_execution_error(hook, start_time, e)
170
232
 
171
- # Parse hook entry to extract tool name and args
172
- tool_name, args = self._parse_hook_entry(hook)
233
+ def _perform_proxy_execution(
234
+ self, hook: HookDefinition, start_time: float
235
+ ) -> HookResult:
236
+ """Perform the actual proxy execution."""
237
+ if not self.quiet:
238
+ self.console.print(
239
+ f"🛡️ Using resilient execution for {hook.name}", style="blue"
240
+ )
173
241
 
174
- # Execute through tool proxy
175
- if self.tool_proxy is not None:
176
- exit_code = self.tool_proxy.execute_tool(tool_name, args)
177
- else:
178
- exit_code = -1 # Error code when tool proxy is not available
242
+ # Parse hook entry to extract tool name and args
243
+ tool_name, args = self._parse_hook_entry(hook)
179
244
 
180
- duration = time.time() - start_time
181
- status = "passed" if exit_code == 0 else "failed"
245
+ # Execute through tool proxy
246
+ if self.tool_proxy is not None:
247
+ exit_code = self.tool_proxy.execute_tool(tool_name, args)
248
+ else:
249
+ exit_code = -1 # Error code when tool proxy is not available
182
250
 
183
- # Get tool status for output
184
- tool_status = (
185
- self.tool_proxy.get_tool_status().get(tool_name, {})
186
- if self.tool_proxy is not None
187
- else {}
188
- )
189
- output = self._format_proxy_output(tool_name, tool_status, duration)
190
-
191
- return HookResult(
192
- id=f"{hook.name}-proxy-{int(time.time())}",
193
- name=f"{hook.name}-proxy",
194
- status=status,
195
- duration=duration,
196
- files_processed=1, # Placeholder value
197
- issues_found=[output] if status == "failed" else [],
198
- )
251
+ duration = time.time() - start_time
252
+ status = "passed" if exit_code == 0 else "failed"
199
253
 
200
- except Exception as e:
201
- duration = time.time() - start_time
202
- error_msg = f"Tool proxy execution failed: {e}"
254
+ # Get tool status for output
255
+ tool_status = (
256
+ self.tool_proxy.get_tool_status().get(tool_name, {})
257
+ if self.tool_proxy is not None
258
+ else {}
259
+ )
260
+ output = self._format_proxy_output(tool_name, tool_status, duration)
203
261
 
204
- if not self.quiet:
205
- self.console.print(f"❌ {hook.name} (proxy): {error_msg}", style="red")
206
- self.console.print(f"🔄 Falling back to regular {hook.name} execution")
262
+ # Ensure failed hooks always have at least 1 issue count
263
+ issues_found = [output] if status == "failed" else []
264
+ issues_count = max(len(issues_found), 1 if status == "failed" else 0)
265
+
266
+ return HookResult(
267
+ id=f"{hook.name}-proxy-{int(time.time())}",
268
+ name=f"{hook.name}-proxy",
269
+ status=status,
270
+ duration=duration,
271
+ files_processed=1, # Placeholder value
272
+ issues_found=issues_found,
273
+ issues_count=issues_count,
274
+ )
275
+
276
+ def _handle_proxy_execution_error(
277
+ self, hook: HookDefinition, start_time: float, error: Exception
278
+ ) -> HookResult:
279
+ """Handle proxy execution errors with fallback."""
280
+ time.time() - start_time
281
+ error_msg = f"Tool proxy execution failed: {error}"
207
282
 
208
- # Fallback to regular execution
209
- return self.execute_single_hook(hook)
283
+ if not self.quiet:
284
+ self.console.print(f"❌ {hook.name} (proxy): {error_msg}", style="red")
285
+ self.console.print(f"🔄 Falling back to regular {hook.name} execution")
286
+
287
+ # Fallback to regular execution
288
+ return self.execute_single_hook(hook)
210
289
 
211
290
  def _parse_hook_entry(self, hook: HookDefinition) -> tuple[str, list[str]]:
212
291
  """Parse hook entry to extract tool name and arguments."""