crackerjack 0.18.2__py3-none-any.whl → 0.45.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. crackerjack/README.md +19 -0
  2. crackerjack/__init__.py +96 -2
  3. crackerjack/__main__.py +637 -138
  4. crackerjack/adapters/README.md +18 -0
  5. crackerjack/adapters/__init__.py +39 -0
  6. crackerjack/adapters/_output_paths.py +167 -0
  7. crackerjack/adapters/_qa_adapter_base.py +309 -0
  8. crackerjack/adapters/_tool_adapter_base.py +706 -0
  9. crackerjack/adapters/ai/README.md +65 -0
  10. crackerjack/adapters/ai/__init__.py +5 -0
  11. crackerjack/adapters/ai/claude.py +853 -0
  12. crackerjack/adapters/complexity/README.md +53 -0
  13. crackerjack/adapters/complexity/__init__.py +10 -0
  14. crackerjack/adapters/complexity/complexipy.py +641 -0
  15. crackerjack/adapters/dependency/__init__.py +22 -0
  16. crackerjack/adapters/dependency/pip_audit.py +418 -0
  17. crackerjack/adapters/format/README.md +72 -0
  18. crackerjack/adapters/format/__init__.py +11 -0
  19. crackerjack/adapters/format/mdformat.py +313 -0
  20. crackerjack/adapters/format/ruff.py +516 -0
  21. crackerjack/adapters/lint/README.md +47 -0
  22. crackerjack/adapters/lint/__init__.py +11 -0
  23. crackerjack/adapters/lint/codespell.py +273 -0
  24. crackerjack/adapters/lsp/README.md +49 -0
  25. crackerjack/adapters/lsp/__init__.py +27 -0
  26. crackerjack/adapters/lsp/_base.py +194 -0
  27. crackerjack/adapters/lsp/_client.py +358 -0
  28. crackerjack/adapters/lsp/_manager.py +193 -0
  29. crackerjack/adapters/lsp/skylos.py +283 -0
  30. crackerjack/adapters/lsp/zuban.py +557 -0
  31. crackerjack/adapters/refactor/README.md +59 -0
  32. crackerjack/adapters/refactor/__init__.py +12 -0
  33. crackerjack/adapters/refactor/creosote.py +318 -0
  34. crackerjack/adapters/refactor/refurb.py +406 -0
  35. crackerjack/adapters/refactor/skylos.py +494 -0
  36. crackerjack/adapters/sast/README.md +132 -0
  37. crackerjack/adapters/sast/__init__.py +32 -0
  38. crackerjack/adapters/sast/_base.py +201 -0
  39. crackerjack/adapters/sast/bandit.py +423 -0
  40. crackerjack/adapters/sast/pyscn.py +405 -0
  41. crackerjack/adapters/sast/semgrep.py +241 -0
  42. crackerjack/adapters/security/README.md +111 -0
  43. crackerjack/adapters/security/__init__.py +17 -0
  44. crackerjack/adapters/security/gitleaks.py +339 -0
  45. crackerjack/adapters/type/README.md +52 -0
  46. crackerjack/adapters/type/__init__.py +12 -0
  47. crackerjack/adapters/type/pyrefly.py +402 -0
  48. crackerjack/adapters/type/ty.py +402 -0
  49. crackerjack/adapters/type/zuban.py +522 -0
  50. crackerjack/adapters/utility/README.md +51 -0
  51. crackerjack/adapters/utility/__init__.py +10 -0
  52. crackerjack/adapters/utility/checks.py +884 -0
  53. crackerjack/agents/README.md +264 -0
  54. crackerjack/agents/__init__.py +66 -0
  55. crackerjack/agents/architect_agent.py +238 -0
  56. crackerjack/agents/base.py +167 -0
  57. crackerjack/agents/claude_code_bridge.py +641 -0
  58. crackerjack/agents/coordinator.py +600 -0
  59. crackerjack/agents/documentation_agent.py +520 -0
  60. crackerjack/agents/dry_agent.py +585 -0
  61. crackerjack/agents/enhanced_coordinator.py +279 -0
  62. crackerjack/agents/enhanced_proactive_agent.py +185 -0
  63. crackerjack/agents/error_middleware.py +53 -0
  64. crackerjack/agents/formatting_agent.py +230 -0
  65. crackerjack/agents/helpers/__init__.py +9 -0
  66. crackerjack/agents/helpers/performance/__init__.py +22 -0
  67. crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
  68. crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
  69. crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
  70. crackerjack/agents/helpers/refactoring/__init__.py +22 -0
  71. crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
  72. crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
  73. crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
  74. crackerjack/agents/helpers/test_creation/__init__.py +19 -0
  75. crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
  76. crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
  77. crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
  78. crackerjack/agents/import_optimization_agent.py +1181 -0
  79. crackerjack/agents/performance_agent.py +325 -0
  80. crackerjack/agents/performance_helpers.py +205 -0
  81. crackerjack/agents/proactive_agent.py +55 -0
  82. crackerjack/agents/refactoring_agent.py +511 -0
  83. crackerjack/agents/refactoring_helpers.py +247 -0
  84. crackerjack/agents/security_agent.py +793 -0
  85. crackerjack/agents/semantic_agent.py +479 -0
  86. crackerjack/agents/semantic_helpers.py +356 -0
  87. crackerjack/agents/test_creation_agent.py +570 -0
  88. crackerjack/agents/test_specialist_agent.py +526 -0
  89. crackerjack/agents/tracker.py +110 -0
  90. crackerjack/api.py +647 -0
  91. crackerjack/cli/README.md +394 -0
  92. crackerjack/cli/__init__.py +24 -0
  93. crackerjack/cli/cache_handlers.py +209 -0
  94. crackerjack/cli/cache_handlers_enhanced.py +680 -0
  95. crackerjack/cli/facade.py +162 -0
  96. crackerjack/cli/formatting.py +13 -0
  97. crackerjack/cli/handlers/__init__.py +85 -0
  98. crackerjack/cli/handlers/advanced.py +103 -0
  99. crackerjack/cli/handlers/ai_features.py +62 -0
  100. crackerjack/cli/handlers/analytics.py +479 -0
  101. crackerjack/cli/handlers/changelog.py +271 -0
  102. crackerjack/cli/handlers/config_handlers.py +16 -0
  103. crackerjack/cli/handlers/coverage.py +84 -0
  104. crackerjack/cli/handlers/documentation.py +280 -0
  105. crackerjack/cli/handlers/main_handlers.py +497 -0
  106. crackerjack/cli/handlers/monitoring.py +371 -0
  107. crackerjack/cli/handlers.py +700 -0
  108. crackerjack/cli/interactive.py +488 -0
  109. crackerjack/cli/options.py +1216 -0
  110. crackerjack/cli/semantic_handlers.py +292 -0
  111. crackerjack/cli/utils.py +19 -0
  112. crackerjack/cli/version.py +19 -0
  113. crackerjack/code_cleaner.py +1307 -0
  114. crackerjack/config/README.md +472 -0
  115. crackerjack/config/__init__.py +275 -0
  116. crackerjack/config/global_lock_config.py +207 -0
  117. crackerjack/config/hooks.py +390 -0
  118. crackerjack/config/loader.py +239 -0
  119. crackerjack/config/settings.py +141 -0
  120. crackerjack/config/tool_commands.py +331 -0
  121. crackerjack/core/README.md +393 -0
  122. crackerjack/core/__init__.py +0 -0
  123. crackerjack/core/async_workflow_orchestrator.py +738 -0
  124. crackerjack/core/autofix_coordinator.py +282 -0
  125. crackerjack/core/container.py +105 -0
  126. crackerjack/core/enhanced_container.py +583 -0
  127. crackerjack/core/file_lifecycle.py +472 -0
  128. crackerjack/core/performance.py +244 -0
  129. crackerjack/core/performance_monitor.py +357 -0
  130. crackerjack/core/phase_coordinator.py +1227 -0
  131. crackerjack/core/proactive_workflow.py +267 -0
  132. crackerjack/core/resource_manager.py +425 -0
  133. crackerjack/core/retry.py +275 -0
  134. crackerjack/core/service_watchdog.py +601 -0
  135. crackerjack/core/session_coordinator.py +239 -0
  136. crackerjack/core/timeout_manager.py +563 -0
  137. crackerjack/core/websocket_lifecycle.py +410 -0
  138. crackerjack/core/workflow/__init__.py +21 -0
  139. crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
  140. crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
  141. crackerjack/core/workflow/workflow_issue_parser.py +714 -0
  142. crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
  143. crackerjack/core/workflow/workflow_security_gates.py +400 -0
  144. crackerjack/core/workflow_orchestrator.py +2243 -0
  145. crackerjack/data/README.md +11 -0
  146. crackerjack/data/__init__.py +8 -0
  147. crackerjack/data/models.py +79 -0
  148. crackerjack/data/repository.py +210 -0
  149. crackerjack/decorators/README.md +180 -0
  150. crackerjack/decorators/__init__.py +35 -0
  151. crackerjack/decorators/error_handling.py +649 -0
  152. crackerjack/decorators/error_handling_decorators.py +334 -0
  153. crackerjack/decorators/helpers.py +58 -0
  154. crackerjack/decorators/patterns.py +281 -0
  155. crackerjack/decorators/utils.py +58 -0
  156. crackerjack/docs/INDEX.md +11 -0
  157. crackerjack/docs/README.md +11 -0
  158. crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
  159. crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
  160. crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
  161. crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
  162. crackerjack/docs/generated/api/SERVICES.md +1252 -0
  163. crackerjack/documentation/README.md +11 -0
  164. crackerjack/documentation/__init__.py +31 -0
  165. crackerjack/documentation/ai_templates.py +756 -0
  166. crackerjack/documentation/dual_output_generator.py +767 -0
  167. crackerjack/documentation/mkdocs_integration.py +518 -0
  168. crackerjack/documentation/reference_generator.py +1065 -0
  169. crackerjack/dynamic_config.py +678 -0
  170. crackerjack/errors.py +378 -0
  171. crackerjack/events/README.md +11 -0
  172. crackerjack/events/__init__.py +16 -0
  173. crackerjack/events/telemetry.py +175 -0
  174. crackerjack/events/workflow_bus.py +346 -0
  175. crackerjack/exceptions/README.md +301 -0
  176. crackerjack/exceptions/__init__.py +5 -0
  177. crackerjack/exceptions/config.py +4 -0
  178. crackerjack/exceptions/tool_execution_error.py +245 -0
  179. crackerjack/executors/README.md +591 -0
  180. crackerjack/executors/__init__.py +13 -0
  181. crackerjack/executors/async_hook_executor.py +938 -0
  182. crackerjack/executors/cached_hook_executor.py +316 -0
  183. crackerjack/executors/hook_executor.py +1295 -0
  184. crackerjack/executors/hook_lock_manager.py +708 -0
  185. crackerjack/executors/individual_hook_executor.py +739 -0
  186. crackerjack/executors/lsp_aware_hook_executor.py +349 -0
  187. crackerjack/executors/progress_hook_executor.py +282 -0
  188. crackerjack/executors/tool_proxy.py +433 -0
  189. crackerjack/hooks/README.md +485 -0
  190. crackerjack/hooks/lsp_hook.py +93 -0
  191. crackerjack/intelligence/README.md +557 -0
  192. crackerjack/intelligence/__init__.py +37 -0
  193. crackerjack/intelligence/adaptive_learning.py +693 -0
  194. crackerjack/intelligence/agent_orchestrator.py +485 -0
  195. crackerjack/intelligence/agent_registry.py +377 -0
  196. crackerjack/intelligence/agent_selector.py +439 -0
  197. crackerjack/intelligence/integration.py +250 -0
  198. crackerjack/interactive.py +719 -0
  199. crackerjack/managers/README.md +369 -0
  200. crackerjack/managers/__init__.py +11 -0
  201. crackerjack/managers/async_hook_manager.py +135 -0
  202. crackerjack/managers/hook_manager.py +585 -0
  203. crackerjack/managers/publish_manager.py +631 -0
  204. crackerjack/managers/test_command_builder.py +391 -0
  205. crackerjack/managers/test_executor.py +474 -0
  206. crackerjack/managers/test_manager.py +1357 -0
  207. crackerjack/managers/test_progress.py +187 -0
  208. crackerjack/mcp/README.md +374 -0
  209. crackerjack/mcp/__init__.py +0 -0
  210. crackerjack/mcp/cache.py +352 -0
  211. crackerjack/mcp/client_runner.py +121 -0
  212. crackerjack/mcp/context.py +802 -0
  213. crackerjack/mcp/dashboard.py +657 -0
  214. crackerjack/mcp/enhanced_progress_monitor.py +493 -0
  215. crackerjack/mcp/file_monitor.py +394 -0
  216. crackerjack/mcp/progress_components.py +607 -0
  217. crackerjack/mcp/progress_monitor.py +1016 -0
  218. crackerjack/mcp/rate_limiter.py +336 -0
  219. crackerjack/mcp/server.py +24 -0
  220. crackerjack/mcp/server_core.py +526 -0
  221. crackerjack/mcp/service_watchdog.py +505 -0
  222. crackerjack/mcp/state.py +407 -0
  223. crackerjack/mcp/task_manager.py +259 -0
  224. crackerjack/mcp/tools/README.md +27 -0
  225. crackerjack/mcp/tools/__init__.py +19 -0
  226. crackerjack/mcp/tools/core_tools.py +469 -0
  227. crackerjack/mcp/tools/error_analyzer.py +283 -0
  228. crackerjack/mcp/tools/execution_tools.py +384 -0
  229. crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
  230. crackerjack/mcp/tools/intelligence_tools.py +264 -0
  231. crackerjack/mcp/tools/monitoring_tools.py +628 -0
  232. crackerjack/mcp/tools/proactive_tools.py +367 -0
  233. crackerjack/mcp/tools/progress_tools.py +222 -0
  234. crackerjack/mcp/tools/semantic_tools.py +584 -0
  235. crackerjack/mcp/tools/utility_tools.py +358 -0
  236. crackerjack/mcp/tools/workflow_executor.py +699 -0
  237. crackerjack/mcp/websocket/README.md +31 -0
  238. crackerjack/mcp/websocket/__init__.py +14 -0
  239. crackerjack/mcp/websocket/app.py +54 -0
  240. crackerjack/mcp/websocket/endpoints.py +492 -0
  241. crackerjack/mcp/websocket/event_bridge.py +188 -0
  242. crackerjack/mcp/websocket/jobs.py +406 -0
  243. crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
  244. crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
  245. crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
  246. crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
  247. crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
  248. crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
  249. crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
  250. crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
  251. crackerjack/mcp/websocket/monitoring/factory.py +109 -0
  252. crackerjack/mcp/websocket/monitoring/filters.py +10 -0
  253. crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
  254. crackerjack/mcp/websocket/monitoring/models.py +90 -0
  255. crackerjack/mcp/websocket/monitoring/utils.py +171 -0
  256. crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
  257. crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
  258. crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
  259. crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
  260. crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
  261. crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
  262. crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
  263. crackerjack/mcp/websocket/server.py +174 -0
  264. crackerjack/mcp/websocket/websocket_handler.py +276 -0
  265. crackerjack/mcp/websocket_server.py +10 -0
  266. crackerjack/models/README.md +308 -0
  267. crackerjack/models/__init__.py +40 -0
  268. crackerjack/models/config.py +730 -0
  269. crackerjack/models/config_adapter.py +265 -0
  270. crackerjack/models/protocols.py +1535 -0
  271. crackerjack/models/pydantic_models.py +320 -0
  272. crackerjack/models/qa_config.py +145 -0
  273. crackerjack/models/qa_results.py +134 -0
  274. crackerjack/models/resource_protocols.py +299 -0
  275. crackerjack/models/results.py +35 -0
  276. crackerjack/models/semantic_models.py +258 -0
  277. crackerjack/models/task.py +173 -0
  278. crackerjack/models/test_models.py +60 -0
  279. crackerjack/monitoring/README.md +11 -0
  280. crackerjack/monitoring/__init__.py +0 -0
  281. crackerjack/monitoring/ai_agent_watchdog.py +405 -0
  282. crackerjack/monitoring/metrics_collector.py +427 -0
  283. crackerjack/monitoring/regression_prevention.py +580 -0
  284. crackerjack/monitoring/websocket_server.py +406 -0
  285. crackerjack/orchestration/README.md +340 -0
  286. crackerjack/orchestration/__init__.py +43 -0
  287. crackerjack/orchestration/advanced_orchestrator.py +894 -0
  288. crackerjack/orchestration/cache/README.md +312 -0
  289. crackerjack/orchestration/cache/__init__.py +37 -0
  290. crackerjack/orchestration/cache/memory_cache.py +338 -0
  291. crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
  292. crackerjack/orchestration/config.py +297 -0
  293. crackerjack/orchestration/coverage_improvement.py +180 -0
  294. crackerjack/orchestration/execution_strategies.py +361 -0
  295. crackerjack/orchestration/hook_orchestrator.py +1398 -0
  296. crackerjack/orchestration/strategies/README.md +401 -0
  297. crackerjack/orchestration/strategies/__init__.py +39 -0
  298. crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
  299. crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
  300. crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
  301. crackerjack/orchestration/test_progress_streamer.py +647 -0
  302. crackerjack/plugins/README.md +11 -0
  303. crackerjack/plugins/__init__.py +15 -0
  304. crackerjack/plugins/base.py +200 -0
  305. crackerjack/plugins/hooks.py +254 -0
  306. crackerjack/plugins/loader.py +335 -0
  307. crackerjack/plugins/managers.py +264 -0
  308. crackerjack/py313.py +191 -0
  309. crackerjack/security/README.md +11 -0
  310. crackerjack/security/__init__.py +0 -0
  311. crackerjack/security/audit.py +197 -0
  312. crackerjack/services/README.md +374 -0
  313. crackerjack/services/__init__.py +9 -0
  314. crackerjack/services/ai/README.md +295 -0
  315. crackerjack/services/ai/__init__.py +7 -0
  316. crackerjack/services/ai/advanced_optimizer.py +878 -0
  317. crackerjack/services/ai/contextual_ai_assistant.py +542 -0
  318. crackerjack/services/ai/embeddings.py +444 -0
  319. crackerjack/services/ai/intelligent_commit.py +328 -0
  320. crackerjack/services/ai/predictive_analytics.py +510 -0
  321. crackerjack/services/anomaly_detector.py +392 -0
  322. crackerjack/services/api_extractor.py +617 -0
  323. crackerjack/services/backup_service.py +467 -0
  324. crackerjack/services/bounded_status_operations.py +530 -0
  325. crackerjack/services/cache.py +369 -0
  326. crackerjack/services/changelog_automation.py +399 -0
  327. crackerjack/services/command_execution_service.py +305 -0
  328. crackerjack/services/config_integrity.py +132 -0
  329. crackerjack/services/config_merge.py +546 -0
  330. crackerjack/services/config_service.py +198 -0
  331. crackerjack/services/config_template.py +493 -0
  332. crackerjack/services/coverage_badge_service.py +173 -0
  333. crackerjack/services/coverage_ratchet.py +381 -0
  334. crackerjack/services/debug.py +733 -0
  335. crackerjack/services/dependency_analyzer.py +460 -0
  336. crackerjack/services/dependency_monitor.py +622 -0
  337. crackerjack/services/documentation_generator.py +493 -0
  338. crackerjack/services/documentation_service.py +704 -0
  339. crackerjack/services/enhanced_filesystem.py +497 -0
  340. crackerjack/services/enterprise_optimizer.py +865 -0
  341. crackerjack/services/error_pattern_analyzer.py +676 -0
  342. crackerjack/services/file_filter.py +221 -0
  343. crackerjack/services/file_hasher.py +149 -0
  344. crackerjack/services/file_io_service.py +361 -0
  345. crackerjack/services/file_modifier.py +615 -0
  346. crackerjack/services/filesystem.py +381 -0
  347. crackerjack/services/git.py +422 -0
  348. crackerjack/services/health_metrics.py +615 -0
  349. crackerjack/services/heatmap_generator.py +744 -0
  350. crackerjack/services/incremental_executor.py +380 -0
  351. crackerjack/services/initialization.py +823 -0
  352. crackerjack/services/input_validator.py +668 -0
  353. crackerjack/services/intelligent_commit.py +327 -0
  354. crackerjack/services/log_manager.py +289 -0
  355. crackerjack/services/logging.py +228 -0
  356. crackerjack/services/lsp_client.py +628 -0
  357. crackerjack/services/memory_optimizer.py +414 -0
  358. crackerjack/services/metrics.py +587 -0
  359. crackerjack/services/monitoring/README.md +30 -0
  360. crackerjack/services/monitoring/__init__.py +9 -0
  361. crackerjack/services/monitoring/dependency_monitor.py +678 -0
  362. crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
  363. crackerjack/services/monitoring/health_metrics.py +716 -0
  364. crackerjack/services/monitoring/metrics.py +587 -0
  365. crackerjack/services/monitoring/performance_benchmarks.py +410 -0
  366. crackerjack/services/monitoring/performance_cache.py +388 -0
  367. crackerjack/services/monitoring/performance_monitor.py +569 -0
  368. crackerjack/services/parallel_executor.py +527 -0
  369. crackerjack/services/pattern_cache.py +333 -0
  370. crackerjack/services/pattern_detector.py +478 -0
  371. crackerjack/services/patterns/__init__.py +142 -0
  372. crackerjack/services/patterns/agents.py +107 -0
  373. crackerjack/services/patterns/code/__init__.py +15 -0
  374. crackerjack/services/patterns/code/detection.py +118 -0
  375. crackerjack/services/patterns/code/imports.py +107 -0
  376. crackerjack/services/patterns/code/paths.py +159 -0
  377. crackerjack/services/patterns/code/performance.py +119 -0
  378. crackerjack/services/patterns/code/replacement.py +36 -0
  379. crackerjack/services/patterns/core.py +212 -0
  380. crackerjack/services/patterns/documentation/__init__.py +14 -0
  381. crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
  382. crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
  383. crackerjack/services/patterns/documentation/docstrings.py +89 -0
  384. crackerjack/services/patterns/formatting.py +226 -0
  385. crackerjack/services/patterns/operations.py +339 -0
  386. crackerjack/services/patterns/security/__init__.py +23 -0
  387. crackerjack/services/patterns/security/code_injection.py +122 -0
  388. crackerjack/services/patterns/security/credentials.py +190 -0
  389. crackerjack/services/patterns/security/path_traversal.py +221 -0
  390. crackerjack/services/patterns/security/unsafe_operations.py +216 -0
  391. crackerjack/services/patterns/templates.py +62 -0
  392. crackerjack/services/patterns/testing/__init__.py +18 -0
  393. crackerjack/services/patterns/testing/error_patterns.py +107 -0
  394. crackerjack/services/patterns/testing/pytest_output.py +126 -0
  395. crackerjack/services/patterns/tool_output/__init__.py +16 -0
  396. crackerjack/services/patterns/tool_output/bandit.py +72 -0
  397. crackerjack/services/patterns/tool_output/other.py +97 -0
  398. crackerjack/services/patterns/tool_output/pyright.py +67 -0
  399. crackerjack/services/patterns/tool_output/ruff.py +44 -0
  400. crackerjack/services/patterns/url_sanitization.py +114 -0
  401. crackerjack/services/patterns/utilities.py +42 -0
  402. crackerjack/services/patterns/utils.py +339 -0
  403. crackerjack/services/patterns/validation.py +46 -0
  404. crackerjack/services/patterns/versioning.py +62 -0
  405. crackerjack/services/predictive_analytics.py +523 -0
  406. crackerjack/services/profiler.py +280 -0
  407. crackerjack/services/quality/README.md +415 -0
  408. crackerjack/services/quality/__init__.py +11 -0
  409. crackerjack/services/quality/anomaly_detector.py +392 -0
  410. crackerjack/services/quality/pattern_cache.py +333 -0
  411. crackerjack/services/quality/pattern_detector.py +479 -0
  412. crackerjack/services/quality/qa_orchestrator.py +491 -0
  413. crackerjack/services/quality/quality_baseline.py +395 -0
  414. crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
  415. crackerjack/services/quality/quality_intelligence.py +949 -0
  416. crackerjack/services/regex_patterns.py +58 -0
  417. crackerjack/services/regex_utils.py +483 -0
  418. crackerjack/services/secure_path_utils.py +524 -0
  419. crackerjack/services/secure_status_formatter.py +450 -0
  420. crackerjack/services/secure_subprocess.py +635 -0
  421. crackerjack/services/security.py +239 -0
  422. crackerjack/services/security_logger.py +495 -0
  423. crackerjack/services/server_manager.py +411 -0
  424. crackerjack/services/smart_scheduling.py +167 -0
  425. crackerjack/services/status_authentication.py +460 -0
  426. crackerjack/services/status_security_manager.py +315 -0
  427. crackerjack/services/terminal_utils.py +0 -0
  428. crackerjack/services/thread_safe_status_collector.py +441 -0
  429. crackerjack/services/tool_filter.py +368 -0
  430. crackerjack/services/tool_version_service.py +43 -0
  431. crackerjack/services/unified_config.py +115 -0
  432. crackerjack/services/validation_rate_limiter.py +220 -0
  433. crackerjack/services/vector_store.py +689 -0
  434. crackerjack/services/version_analyzer.py +461 -0
  435. crackerjack/services/version_checker.py +223 -0
  436. crackerjack/services/websocket_resource_limiter.py +438 -0
  437. crackerjack/services/zuban_lsp_service.py +391 -0
  438. crackerjack/slash_commands/README.md +11 -0
  439. crackerjack/slash_commands/__init__.py +59 -0
  440. crackerjack/slash_commands/init.md +112 -0
  441. crackerjack/slash_commands/run.md +197 -0
  442. crackerjack/slash_commands/status.md +127 -0
  443. crackerjack/tools/README.md +11 -0
  444. crackerjack/tools/__init__.py +30 -0
  445. crackerjack/tools/_git_utils.py +105 -0
  446. crackerjack/tools/check_added_large_files.py +139 -0
  447. crackerjack/tools/check_ast.py +105 -0
  448. crackerjack/tools/check_json.py +103 -0
  449. crackerjack/tools/check_jsonschema.py +297 -0
  450. crackerjack/tools/check_toml.py +103 -0
  451. crackerjack/tools/check_yaml.py +110 -0
  452. crackerjack/tools/codespell_wrapper.py +72 -0
  453. crackerjack/tools/end_of_file_fixer.py +202 -0
  454. crackerjack/tools/format_json.py +128 -0
  455. crackerjack/tools/mdformat_wrapper.py +114 -0
  456. crackerjack/tools/trailing_whitespace.py +198 -0
  457. crackerjack/tools/validate_input_validator_patterns.py +236 -0
  458. crackerjack/tools/validate_regex_patterns.py +188 -0
  459. crackerjack/ui/README.md +11 -0
  460. crackerjack/ui/__init__.py +1 -0
  461. crackerjack/ui/dashboard_renderer.py +28 -0
  462. crackerjack/ui/templates/README.md +11 -0
  463. crackerjack/utils/console_utils.py +13 -0
  464. crackerjack/utils/dependency_guard.py +230 -0
  465. crackerjack/utils/retry_utils.py +275 -0
  466. crackerjack/workflows/README.md +590 -0
  467. crackerjack/workflows/__init__.py +46 -0
  468. crackerjack/workflows/actions.py +811 -0
  469. crackerjack/workflows/auto_fix.py +444 -0
  470. crackerjack/workflows/container_builder.py +499 -0
  471. crackerjack/workflows/definitions.py +443 -0
  472. crackerjack/workflows/engine.py +177 -0
  473. crackerjack/workflows/event_bridge.py +242 -0
  474. crackerjack-0.45.2.dist-info/METADATA +1678 -0
  475. crackerjack-0.45.2.dist-info/RECORD +478 -0
  476. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
  477. crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
  478. crackerjack/.gitignore +0 -14
  479. crackerjack/.libcst.codemod.yaml +0 -18
  480. crackerjack/.pdm.toml +0 -1
  481. crackerjack/.pre-commit-config.yaml +0 -91
  482. crackerjack/.pytest_cache/.gitignore +0 -2
  483. crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
  484. crackerjack/.pytest_cache/README.md +0 -8
  485. crackerjack/.pytest_cache/v/cache/nodeids +0 -1
  486. crackerjack/.pytest_cache/v/cache/stepwise +0 -1
  487. crackerjack/.ruff_cache/.gitignore +0 -1
  488. crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  489. crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  490. crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  491. crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  492. crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  493. crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  494. crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  495. crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  496. crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  497. crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  498. crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  499. crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  500. crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  501. crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  502. crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  503. crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  504. crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  505. crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  506. crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  507. crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  508. crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  509. crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  510. crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  511. crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  512. crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  513. crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  514. crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  515. crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  516. crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  517. crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  518. crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  519. crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  520. crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  521. crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  522. crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  523. crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  524. crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  525. crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  526. crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  527. crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
  528. crackerjack/crackerjack.py +0 -855
  529. crackerjack/pyproject.toml +0 -214
  530. crackerjack-0.18.2.dist-info/METADATA +0 -420
  531. crackerjack-0.18.2.dist-info/RECORD +0 -59
  532. crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
  533. {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,739 @@
1
+ import asyncio
2
+ import subprocess
3
+ import time
4
+ import typing as t
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from acb.console import Console
9
+
10
+ from crackerjack.cli.formatting import separator as make_separator
11
+ from crackerjack.config import get_console_width
12
+ from crackerjack.config.hooks import HookDefinition, HookStrategy
13
+ from crackerjack.executors.hook_executor import HookExecutionResult
14
+ from crackerjack.models.protocols import HookLockManagerProtocol
15
+ from crackerjack.models.task import HookResult
16
+ from crackerjack.services.regex_patterns import SAFE_PATTERNS
17
+
18
+
19
+ @dataclass
20
+ class HookProgress:
21
+ hook_name: str
22
+ status: str
23
+ start_time: float
24
+ end_time: float | None = None
25
+ duration: float | None = None
26
+ errors_found: int = 0
27
+ warnings_found: int = 0
28
+ files_processed: int = 0
29
+ lines_processed: int = 0
30
+ output_lines: list[str] | None = None
31
+ error_details: list[dict[str, t.Any]] | None = None
32
+
33
+ def __post_init__(self) -> None:
34
+ if self.output_lines is None:
35
+ self.output_lines = []
36
+ if self.error_details is None:
37
+ self.error_details = []
38
+ if self.end_time and self.start_time:
39
+ self.duration = self.end_time - self.start_time
40
+
41
+ def to_dict(self) -> dict[str, t.Any]:
42
+ return {
43
+ "hook_name": self.hook_name,
44
+ "status": self.status,
45
+ "start_time": self.start_time,
46
+ "end_time": self.end_time,
47
+ "duration": self.duration,
48
+ "errors_found": self.errors_found,
49
+ "warnings_found": self.warnings_found,
50
+ "files_processed": self.files_processed,
51
+ "lines_processed": self.lines_processed,
52
+ "output_lines": self.output_lines[-10:] if self.output_lines else [],
53
+ "error_details": self.error_details,
54
+ }
55
+
56
+
57
+ @dataclass
58
+ class IndividualExecutionResult:
59
+ strategy_name: str
60
+ hook_results: list[HookResult]
61
+ hook_progress: list[HookProgress]
62
+ total_duration: float
63
+ success: bool
64
+ execution_order: list[str]
65
+
66
+ @property
67
+ def failed_hooks(self) -> list[str]:
68
+ return [p.hook_name for p in self.hook_progress if p.status == "failed"]
69
+
70
+ @property
71
+ def total_errors(self) -> int:
72
+ return sum(p.errors_found for p in self.hook_progress)
73
+
74
+ @property
75
+ def total_warnings(self) -> int:
76
+ return sum(p.warnings_found for p in self.hook_progress)
77
+
78
+
79
+ class HookOutputParser:
80
+ HOOK_PATTERNS: dict[str, dict[str, str]] = {
81
+ "ruff-check": {
82
+ "error": "ruff_check_error",
83
+ "summary": "ruff_check_summary",
84
+ },
85
+ "pyright": {
86
+ "error": "pyright_error",
87
+ "warning": "pyright_warning",
88
+ "summary": "pyright_summary",
89
+ },
90
+ "bandit": {
91
+ "issue": "bandit_issue",
92
+ "location": "bandit_location",
93
+ "confidence": "bandit_confidence",
94
+ "severity": "bandit_severity",
95
+ },
96
+ "mypy": {
97
+ "error": "mypy_error",
98
+ "note": "mypy_note",
99
+ },
100
+ "vulture": {
101
+ "unused": "vulture_unused",
102
+ },
103
+ "complexipy": {
104
+ "complex": "complexipy_complex",
105
+ },
106
+ }
107
+
108
+ def parse_hook_output(
109
+ self,
110
+ hook_name: str,
111
+ output_lines: list[str],
112
+ ) -> dict[str, t.Any]:
113
+ if hook_name not in self.HOOK_PATTERNS:
114
+ return self._parse_generic_output(output_lines)
115
+
116
+ result: dict[str, t.Any] = {
117
+ "errors": [],
118
+ "warnings": [],
119
+ "files_processed": set(),
120
+ }
121
+ patterns = self.HOOK_PATTERNS[hook_name]
122
+
123
+ parser_map = {
124
+ "ruff-check": self._parse_ruff_check,
125
+ "pyright": self._parse_pyright,
126
+ "bandit": self._parse_bandit,
127
+ "vulture": self._parse_vulture,
128
+ "complexipy": self._parse_complexipy,
129
+ }
130
+
131
+ parser_map.get(hook_name, self._parse_default_hook)(
132
+ output_lines, patterns, result
133
+ )
134
+
135
+ result["files_processed"] = list[t.Any](result["files_processed"])
136
+ return result
137
+
138
+ def _parse_ruff_check(
139
+ self,
140
+ output_lines: list[str],
141
+ patterns: dict[str, str],
142
+ result: dict[str, t.Any],
143
+ ) -> None:
144
+ error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
145
+
146
+ for line in output_lines:
147
+ line = line.strip()
148
+ if not line:
149
+ continue
150
+ if match := error_pattern.match(line):
151
+ assert match is not None # Type checker: match cannot be None here
152
+ file_path, line_num, col_num, code, message = match.groups()
153
+ result["files_processed"].add(file_path)
154
+ result["errors"].append(
155
+ {
156
+ "file": file_path,
157
+ "line": int(line_num),
158
+ "column": int(col_num),
159
+ "code": code,
160
+ "message": message,
161
+ "type": "error",
162
+ },
163
+ )
164
+
165
+ def _parse_pyright(
166
+ self,
167
+ output_lines: list[str],
168
+ patterns: dict[str, str],
169
+ result: dict[str, t.Any],
170
+ ) -> None:
171
+ error_pattern = SAFE_PATTERNS[patterns["error"]]._get_compiled_pattern()
172
+ warning_pattern = SAFE_PATTERNS[patterns["warning"]]._get_compiled_pattern()
173
+
174
+ for line in output_lines:
175
+ line = line.strip()
176
+ if not line:
177
+ continue
178
+ if match := error_pattern.match(line):
179
+ assert match is not None # Type checker: match cannot be None here
180
+ file_path, line_num, col_num, message = match.groups()
181
+ result["files_processed"].add(file_path)
182
+ result["errors"].append(
183
+ {
184
+ "file": file_path,
185
+ "line": int(line_num),
186
+ "column": int(col_num),
187
+ "message": message,
188
+ "type": "error",
189
+ },
190
+ )
191
+ elif match := warning_pattern.match(line):
192
+ assert match is not None # Type checker: match cannot be None here
193
+ file_path, line_num, col_num, message = match.groups()
194
+ result["files_processed"].add(file_path)
195
+ result["warnings"].append(
196
+ {
197
+ "file": file_path,
198
+ "line": int(line_num),
199
+ "column": int(col_num),
200
+ "message": message,
201
+ "type": "warning",
202
+ },
203
+ )
204
+
205
+ def _parse_bandit(
206
+ self,
207
+ output_lines: list[str],
208
+ patterns: dict[str, str],
209
+ result: dict[str, t.Any],
210
+ ) -> None:
211
+ issue_pattern = SAFE_PATTERNS[patterns["issue"]]._get_compiled_pattern()
212
+
213
+ for line in output_lines:
214
+ line = line.strip()
215
+ if not line:
216
+ continue
217
+ if match := issue_pattern.match(line):
218
+ assert match is not None # Type checker: match cannot be None here
219
+ code, message = match.groups()
220
+ result["errors"].append(
221
+ {"code": code, "message": message, "type": "security"},
222
+ )
223
+
224
+ def _parse_vulture(
225
+ self,
226
+ output_lines: list[str],
227
+ patterns: dict[str, str],
228
+ result: dict[str, t.Any],
229
+ ) -> None:
230
+ unused_pattern = SAFE_PATTERNS[patterns["unused"]]._get_compiled_pattern()
231
+
232
+ for line in output_lines:
233
+ line = line.strip()
234
+ if not line:
235
+ continue
236
+ if match := unused_pattern.match(line):
237
+ assert match is not None # Type checker: match cannot be None here
238
+ file_path, line_num, item_type, item_name = match.groups()
239
+ result["files_processed"].add(file_path)
240
+ result["warnings"].append(
241
+ {
242
+ "file": file_path,
243
+ "line": int(line_num),
244
+ "message": f"unused {item_type} '{item_name}'",
245
+ "type": "unused_code",
246
+ },
247
+ )
248
+
249
+ def _parse_complexipy(
250
+ self,
251
+ output_lines: list[str],
252
+ patterns: dict[str, str],
253
+ result: dict[str, t.Any],
254
+ ) -> None:
255
+ complex_pattern = SAFE_PATTERNS[patterns["complex"]]._get_compiled_pattern()
256
+
257
+ for line in output_lines:
258
+ line = line.strip()
259
+ if not line:
260
+ continue
261
+ if match := complex_pattern.match(line):
262
+ assert match is not None # Type checker: match cannot be None here
263
+ file_path, line_num, col_num, function_name, complexity = match.groups()
264
+ result["files_processed"].add(file_path)
265
+ result["errors"].append(
266
+ {
267
+ "file": file_path,
268
+ "line": int(line_num),
269
+ "column": int(col_num),
270
+ "message": f"{function_name} is too complex ({complexity})",
271
+ "type": "complexity",
272
+ },
273
+ )
274
+
275
+ def _parse_default_hook(
276
+ self,
277
+ output_lines: list[str],
278
+ patterns: dict[str, str],
279
+ result: dict[str, t.Any],
280
+ ) -> None:
281
+ for line in output_lines:
282
+ line = line.strip()
283
+ if not line:
284
+ continue
285
+
286
+ if "error" in line.lower() or "fail" in line.lower():
287
+ result["errors"].append(
288
+ {
289
+ "message": line,
290
+ "type": "generic_error",
291
+ },
292
+ )
293
+ elif "warning" in line.lower():
294
+ result["warnings"].append(
295
+ {
296
+ "message": line,
297
+ "type": "generic_warning",
298
+ },
299
+ )
300
+
301
+ def _parse_generic_output(self, output_lines: list[str]) -> dict[str, t.Any]:
302
+ errors: list[dict[str, str]] = []
303
+ warnings: list[dict[str, str]] = []
304
+
305
+ error_keywords = ["error", "failed", "violation", "issue"]
306
+ warning_keywords = ["warning", "caution", "note"]
307
+
308
+ for line in output_lines:
309
+ line_lower = line.lower()
310
+ if any(keyword in line_lower for keyword in error_keywords):
311
+ errors.append({"message": line.strip(), "type": "generic_error"})
312
+ elif any(keyword in line_lower for keyword in warning_keywords):
313
+ warnings.append({"message": line.strip(), "type": "generic_warning"})
314
+
315
+ return {
316
+ "errors": errors,
317
+ "warnings": warnings,
318
+ "files_processed": 0,
319
+ "total_lines": len(output_lines),
320
+ }
321
+
322
+
323
+ class IndividualHookExecutor:
324
+ def __init__(
325
+ self,
326
+ console: Console,
327
+ pkg_path: Path,
328
+ hook_lock_manager: HookLockManagerProtocol | None = None,
329
+ ) -> None:
330
+ self.console = console
331
+ self.pkg_path = pkg_path
332
+ self.parser = HookOutputParser()
333
+ self.progress_callback: t.Callable[[HookProgress], None] | None = None
334
+ self.suppress_realtime_output = False
335
+ self.progress_callback_interval = 1
336
+ self.hook_lock_manager: HookLockManagerProtocol
337
+
338
+ if hook_lock_manager is None:
339
+ from crackerjack.executors.hook_lock_manager import (
340
+ hook_lock_manager as default_manager,
341
+ )
342
+
343
+ # Type cast: default_manager implements the protocol interface
344
+ self.hook_lock_manager = t.cast(HookLockManagerProtocol, default_manager)
345
+ else:
346
+ self.hook_lock_manager = hook_lock_manager
347
+
348
+ def set_progress_callback(self, callback: t.Callable[[HookProgress], None]) -> None:
349
+ self.progress_callback = callback
350
+
351
+ def set_mcp_mode(self, enable: bool = True) -> None:
352
+ self.suppress_realtime_output = enable
353
+ if enable:
354
+ self.progress_callback_interval = 10
355
+
356
+ async def execute_strategy_individual(
357
+ self,
358
+ strategy: HookStrategy,
359
+ ) -> IndividualExecutionResult:
360
+ """Execute hook strategy with individual (sequential) execution and progress tracking."""
361
+ start_time = time.time()
362
+ self._print_strategy_header(strategy)
363
+
364
+ execution_state = self._initialize_execution_state()
365
+
366
+ for hook in strategy.hooks:
367
+ await self._execute_single_hook_in_strategy(hook, execution_state)
368
+
369
+ return self._finalize_execution_result(strategy, execution_state, start_time)
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
+
402
+ def _initialize_execution_state(self) -> dict[str, t.Any]:
403
+ return {"hook_results": [], "hook_progress": [], "execution_order": []}
404
+
405
+ async def _execute_single_hook_in_strategy(
406
+ self,
407
+ hook: HookDefinition,
408
+ execution_state: dict[str, t.Any],
409
+ ) -> None:
410
+ execution_state["execution_order"].append(hook.name)
411
+
412
+ progress = HookProgress(
413
+ hook_name=hook.name,
414
+ status="pending",
415
+ start_time=time.time(),
416
+ )
417
+ execution_state["hook_progress"].append(progress)
418
+
419
+ result = await self._execute_individual_hook(hook, progress)
420
+ execution_state["hook_results"].append(result)
421
+
422
+ self._update_hook_progress_status(progress, result)
423
+
424
+ def _update_hook_progress_status(
425
+ self,
426
+ progress: HookProgress,
427
+ result: HookResult,
428
+ ) -> None:
429
+ progress.status = "completed" if result.status == "passed" else "failed"
430
+ progress.end_time = time.time()
431
+ progress.duration = progress.end_time - progress.start_time
432
+
433
+ if self.progress_callback:
434
+ self.progress_callback(progress)
435
+
436
+ def _finalize_execution_result(
437
+ self,
438
+ strategy: HookStrategy,
439
+ execution_state: dict[str, t.Any],
440
+ start_time: float,
441
+ ) -> IndividualExecutionResult:
442
+ total_duration = time.time() - start_time
443
+ success = all(r.status == "passed" for r in execution_state["hook_results"])
444
+
445
+ self._print_individual_summary(
446
+ strategy,
447
+ execution_state["hook_results"],
448
+ execution_state["hook_progress"],
449
+ )
450
+
451
+ return IndividualExecutionResult(
452
+ strategy_name=f"{strategy.name}_individual",
453
+ hook_results=execution_state["hook_results"],
454
+ hook_progress=execution_state["hook_progress"],
455
+ total_duration=total_duration,
456
+ success=success,
457
+ execution_order=execution_state["execution_order"],
458
+ )
459
+
460
+ async def _execute_individual_hook(
461
+ self,
462
+ hook: HookDefinition,
463
+ progress: HookProgress,
464
+ ) -> HookResult:
465
+ progress.status = "running"
466
+ if self.progress_callback:
467
+ self.progress_callback(progress)
468
+
469
+ # Don't print verbose "Running..." messages - the dotted-line format shows status
470
+ cmd = hook.get_command()
471
+
472
+ try:
473
+ async with self.hook_lock_manager.acquire_hook_lock(hook.name): # type: ignore[attr-defined]
474
+ result = await self._run_command_with_streaming(
475
+ cmd, hook.timeout, progress
476
+ )
477
+
478
+ parsed_output = self.parser.parse_hook_output(
479
+ hook.name,
480
+ progress.output_lines or [],
481
+ )
482
+ progress.errors_found = len(parsed_output["errors"])
483
+ progress.warnings_found = len(parsed_output["warnings"])
484
+ progress.files_processed = parsed_output["files_processed"]
485
+ progress.lines_processed = parsed_output["total_lines"]
486
+ progress.error_details = (
487
+ parsed_output["errors"] + parsed_output["warnings"]
488
+ )
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
+
494
+ hook_result = HookResult(
495
+ id=hook.name,
496
+ name=hook.name,
497
+ status=status,
498
+ duration=progress.duration or 0,
499
+ issues_count=issues_count,
500
+ )
501
+
502
+ self._print_hook_summary(hook.name, hook_result, progress)
503
+
504
+ return hook_result
505
+
506
+ except TimeoutError:
507
+ progress.status = "failed"
508
+ error_msg = f"Hook {hook.name} timed out after {hook.timeout}s"
509
+ self.console.print(f"[red]⏰ {error_msg}[/ red]")
510
+
511
+ return HookResult(
512
+ id=hook.name,
513
+ name=hook.name,
514
+ status="failed",
515
+ duration=hook.timeout,
516
+ issues_count=1, # Timeout counts as 1 issue
517
+ )
518
+ except Exception as e:
519
+ progress.status = "failed"
520
+ error_msg = f"Hook {hook.name} failed with error: {e}"
521
+ self.console.print(f"[red]❌ {error_msg}[/ red]")
522
+
523
+ return HookResult(
524
+ id=hook.name,
525
+ name=hook.name,
526
+ status="failed",
527
+ duration=progress.duration or 0,
528
+ issues_count=1, # Error counts as 1 issue
529
+ )
530
+
531
+ async def _run_command_with_streaming(
532
+ self,
533
+ cmd: list[str],
534
+ timeout: int,
535
+ progress: HookProgress,
536
+ ) -> subprocess.CompletedProcess[str]:
537
+ process = await self._create_subprocess(cmd)
538
+
539
+ stdout_lines: list[str] = []
540
+ stderr_lines: list[str] = []
541
+
542
+ tasks = self._create_stream_reader_tasks(
543
+ process,
544
+ stdout_lines,
545
+ stderr_lines,
546
+ progress,
547
+ )
548
+
549
+ try:
550
+ await self._wait_for_process_completion(process, tasks, timeout)
551
+ except TimeoutError:
552
+ self._handle_process_timeout(process, tasks)
553
+ raise
554
+
555
+ return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
556
+
557
+ async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
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
560
+ return await asyncio.create_subprocess_exec(
561
+ *cmd,
562
+ cwd=self.pkg_path,
563
+ stdout=asyncio.subprocess.PIPE,
564
+ stderr=asyncio.subprocess.PIPE,
565
+ )
566
+
567
+ def _create_stream_reader_tasks(
568
+ self,
569
+ process: asyncio.subprocess.Process,
570
+ stdout_lines: list[str],
571
+ stderr_lines: list[str],
572
+ progress: HookProgress,
573
+ ) -> list[asyncio.Task[None]]:
574
+ return [
575
+ asyncio.create_task(
576
+ self._read_stream(process.stdout, stdout_lines, progress),
577
+ ),
578
+ asyncio.create_task(
579
+ self._read_stream(process.stderr, stderr_lines, progress),
580
+ ),
581
+ ]
582
+
583
+ async def _read_stream(
584
+ self,
585
+ stream: asyncio.StreamReader | None,
586
+ output_list: list[str],
587
+ progress: HookProgress,
588
+ ) -> None:
589
+ if not stream:
590
+ return
591
+
592
+ line_count = 0
593
+ while True:
594
+ try:
595
+ line = await stream.readline()
596
+ if not line:
597
+ break
598
+
599
+ line_str = self._process_stream_line(line)
600
+ self._update_progress_with_line(
601
+ line_str,
602
+ output_list,
603
+ progress,
604
+ line_count,
605
+ )
606
+ line_count += 1
607
+
608
+ except Exception:
609
+ break
610
+
611
+ def _process_stream_line(self, line: bytes | str) -> str:
612
+ return (line.decode() if isinstance(line, bytes) else line).rstrip()
613
+
614
+ def _update_progress_with_line(
615
+ self,
616
+ line_str: str,
617
+ output_list: list[str],
618
+ progress: HookProgress,
619
+ line_count: int,
620
+ ) -> None:
621
+ output_list.append(line_str)
622
+ progress.output_lines = progress.output_lines or []
623
+ progress.output_lines.append(line_str)
624
+
625
+ self._maybe_print_line(line_str)
626
+ self._maybe_callback_progress(progress, line_count)
627
+
628
+ def _maybe_print_line(self, line_str: str) -> None:
629
+ if not self.suppress_realtime_output and line_str.strip():
630
+ self.console.print(f"[dim] {line_str}[/ dim]")
631
+
632
+ def _maybe_callback_progress(self, progress: HookProgress, line_count: int) -> None:
633
+ if self.progress_callback and (
634
+ line_count % self.progress_callback_interval == 0
635
+ ):
636
+ self.progress_callback(progress)
637
+
638
+ @staticmethod
639
+ async def _wait_for_process_completion(
640
+ process: asyncio.subprocess.Process,
641
+ tasks: list[asyncio.Task[None]],
642
+ timeout: int,
643
+ ) -> None:
644
+ await asyncio.wait_for(process.wait(), timeout=timeout)
645
+ await asyncio.gather(*tasks, return_exceptions=True)
646
+
647
+ @staticmethod
648
+ def _handle_process_timeout(
649
+ process: asyncio.subprocess.Process,
650
+ tasks: list[asyncio.Task[None]],
651
+ ) -> None:
652
+ process.kill()
653
+ for task in tasks:
654
+ task.cancel()
655
+
656
+ @staticmethod
657
+ def _create_completed_process(
658
+ cmd: list[str],
659
+ process: asyncio.subprocess.Process,
660
+ stdout_lines: list[str],
661
+ stderr_lines: list[str],
662
+ ) -> subprocess.CompletedProcess[str]:
663
+ return subprocess.CompletedProcess(
664
+ args=cmd,
665
+ returncode=process.returncode or 0,
666
+ stdout="\n".join(stdout_lines),
667
+ stderr="\n".join(stderr_lines),
668
+ )
669
+
670
+ def _print_strategy_header(self, strategy: HookStrategy) -> None:
671
+ self.console.print("\n" + "=" * 80)
672
+ self.console.print(
673
+ f"[bold bright_cyan]🔍 INDIVIDUAL HOOK EXECUTION[/ bold bright_cyan] "
674
+ f"[bold bright_white]{strategy.name.upper()} HOOKS[/ bold bright_white]",
675
+ )
676
+ self.console.print(
677
+ f"[dim]Running {len(strategy.hooks)} hooks individually with real-time streaming[/ dim]",
678
+ )
679
+ self.console.print("=" * 80)
680
+
681
+ def _print_hook_summary(
682
+ self,
683
+ hook_name: str,
684
+ result: HookResult,
685
+ progress: HookProgress,
686
+ ) -> None:
687
+ """Print hook result in dotted-line format matching pre-commit style.
688
+
689
+ Format: hook-name.......................................... ✅
690
+ """
691
+ status_icon = "✅" if result.status == "passed" else "❌"
692
+
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
696
+
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)
702
+
703
+ self.console.print(f"{line} {status_icon}")
704
+
705
+ def _print_individual_summary(
706
+ self,
707
+ strategy: HookStrategy,
708
+ results: list[HookResult],
709
+ progress_list: list[HookProgress],
710
+ ) -> None:
711
+ passed = sum(1 for r in results if r.status == "passed")
712
+ failed = sum(1 for r in results if r.status == "failed")
713
+ total_errors = sum(p.errors_found for p in progress_list)
714
+ total_warnings = sum(p.warnings_found for p in progress_list)
715
+ total_duration = sum(p.duration or 0 for p in progress_list)
716
+
717
+ self.console.print("\n" + make_separator("-", get_console_width()))
718
+ self.console.print(
719
+ f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
720
+ )
721
+ self.console.print(f"✅ Passed: {passed} | ❌ Failed: {failed}")
722
+ if total_errors > 0:
723
+ self.console.print(f"🚨 Total Errors: {total_errors}")
724
+ if total_warnings > 0:
725
+ self.console.print(f"⚠️ Total Warnings: {total_warnings}")
726
+ self.console.print(f"⏱️ Total Duration: {total_duration: .1f}s")
727
+
728
+ if failed > 0:
729
+ self.console.print("\n[bold red]Failed Hooks: [/ bold red]")
730
+ for progress in progress_list:
731
+ if progress.status == "failed":
732
+ error_summary = (
733
+ f"{progress.errors_found} errors"
734
+ if progress.errors_found > 0
735
+ else "failed"
736
+ )
737
+ self.console.print(f" ❌ {progress.hook_name}-{error_summary}")
738
+
739
+ self.console.print(make_separator("-", get_console_width()))