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,399 @@
1
+ """Automatic changelog generation and updates service."""
2
+
3
+ import re
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+
7
+ from acb.console import Console
8
+ from acb.depends import Inject, depends
9
+
10
+ from crackerjack.models.protocols import GitServiceProtocol
11
+
12
+
13
+ class ChangelogEntry:
14
+ """Represents a single changelog entry."""
15
+
16
+ def __init__(
17
+ self,
18
+ entry_type: str,
19
+ description: str,
20
+ commit_hash: str = "",
21
+ breaking_change: bool = False,
22
+ ) -> None:
23
+ self.type = entry_type
24
+ self.description = description
25
+ self.commit_hash = commit_hash
26
+ self.breaking_change = breaking_change
27
+
28
+ def to_markdown(self) -> str:
29
+ """Convert entry to markdown format."""
30
+ prefix = "**BREAKING:** " if self.breaking_change else ""
31
+ return f"- {prefix}{self.description}"
32
+
33
+
34
+ class ChangelogGenerator:
35
+ """Generate and update changelogs based on git commits."""
36
+
37
+ @depends.inject
38
+ def __init__(
39
+ self, console: Inject[Console], git_service: Inject[GitServiceProtocol]
40
+ ) -> None:
41
+ self.console = console
42
+ self.git = git_service
43
+
44
+ # Conventional commit type mappings to changelog sections
45
+ self.type_mappings = {
46
+ "feat": "Added",
47
+ "fix": "Fixed",
48
+ "docs": "Documentation",
49
+ "style": "Changed",
50
+ "refactor": "Changed",
51
+ "test": "Testing",
52
+ "chore": "Internal",
53
+ "perf": "Performance",
54
+ "build": "Build",
55
+ "ci": "CI/CD",
56
+ "revert": "Reverted",
57
+ }
58
+
59
+ # Regex patterns for parsing commit messages
60
+ self.conventional_commit_pattern = re.compile( # REGEX OK: conventional commit parsing
61
+ r"^(?P<type>\w+)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s*(?P<description>.+)$"
62
+ )
63
+
64
+ self.breaking_change_pattern = (
65
+ re.compile( # REGEX OK: breaking change detection
66
+ r"BREAKING\s*CHANGE[:]\s*(.+)", re.IGNORECASE | re.MULTILINE
67
+ )
68
+ )
69
+
70
+ def parse_commit_message(
71
+ self, commit_message: str, commit_hash: str = ""
72
+ ) -> ChangelogEntry | None:
73
+ """Parse a commit message into a changelog entry."""
74
+ # Split commit message into header and body
75
+ lines = commit_message.strip().split("\n")
76
+ header = lines[0].strip()
77
+ body = "\n".join(lines[1:]).strip() if len(lines) > 1 else ""
78
+
79
+ # Try to match conventional commit format
80
+ match = self.conventional_commit_pattern.match(header)
81
+ if not match:
82
+ # Fallback for non-conventional commits
83
+ return self._parse_non_conventional_commit(header, body, commit_hash)
84
+
85
+ commit_type = match.group("type").lower()
86
+ scope = match.group("scope") or ""
87
+ breaking_marker = match.group("breaking") == "!"
88
+ description = match.group("description").strip()
89
+
90
+ # Check for breaking changes in body
91
+ breaking_in_body = bool(self.breaking_change_pattern.search(body))
92
+ breaking_change = breaking_marker or breaking_in_body
93
+
94
+ # Map commit type to changelog section
95
+ changelog_section = self.type_mappings.get(commit_type, "Changed")
96
+
97
+ # Format description
98
+ formatted_description = self._format_description(
99
+ description, scope, commit_type
100
+ )
101
+
102
+ return ChangelogEntry(
103
+ entry_type=changelog_section,
104
+ description=formatted_description,
105
+ commit_hash=commit_hash,
106
+ breaking_change=breaking_change,
107
+ )
108
+
109
+ def _parse_non_conventional_commit(
110
+ self, header: str, body: str, commit_hash: str
111
+ ) -> ChangelogEntry | None:
112
+ """Parse non-conventional commit messages."""
113
+ # Simple heuristics for non-conventional commits
114
+ header_lower = header.lower()
115
+
116
+ if any(
117
+ keyword in header_lower for keyword in ("add", "new", "create", "implement")
118
+ ):
119
+ entry_type = "Added"
120
+ elif any(
121
+ keyword in header_lower for keyword in ("fix", "bug", "resolve", "correct")
122
+ ):
123
+ entry_type = "Fixed"
124
+ elif any(
125
+ keyword in header_lower
126
+ for keyword in ("update", "change", "modify", "improve")
127
+ ):
128
+ entry_type = "Changed"
129
+ elif any(keyword in header_lower for keyword in ("remove", "delete", "drop")):
130
+ entry_type = "Removed"
131
+ elif any(keyword in header_lower for keyword in ("doc", "readme", "comment")):
132
+ entry_type = "Documentation"
133
+ else:
134
+ entry_type = "Changed"
135
+
136
+ # Check for breaking changes
137
+ breaking_change = bool(self.breaking_change_pattern.search(body))
138
+
139
+ return ChangelogEntry(
140
+ entry_type=entry_type,
141
+ description=header,
142
+ commit_hash=commit_hash,
143
+ breaking_change=breaking_change,
144
+ )
145
+
146
+ def _format_description(
147
+ self, description: str, scope: str, commit_type: str
148
+ ) -> str:
149
+ """Format the changelog description."""
150
+ # Capitalize first letter
151
+ description = description[0].upper() + description[1:] if description else ""
152
+
153
+ # Add scope context if present
154
+ if scope:
155
+ # Only add scope if it's not already mentioned in description
156
+ if scope.lower() not in description.lower():
157
+ description = f"{scope}: {description}"
158
+
159
+ return description
160
+
161
+ def generate_changelog_entries(
162
+ self, since_version: str | None = None, target_file: Path | None = None
163
+ ) -> dict[str, list[ChangelogEntry]]:
164
+ """Generate changelog entries from git commits."""
165
+ try:
166
+ # Get git commits
167
+ git_result = self._get_git_commits(since_version)
168
+ if not git_result:
169
+ return {}
170
+
171
+ # Parse commits into entries
172
+ return self._parse_commits_to_entries(git_result)
173
+
174
+ except Exception as e:
175
+ self.console.print(f"[red]❌[/red] Error generating changelog entries: {e}")
176
+ return {}
177
+
178
+ def _get_git_commits(self, since_version: str | None = None) -> str | None:
179
+ """Get git commit log output."""
180
+ # Build git command
181
+ git_command = self._build_git_log_command(since_version)
182
+
183
+ # Execute git command
184
+ result = self.git._run_git_command(git_command)
185
+ if result.returncode != 0:
186
+ self.console.print(
187
+ f"[yellow]⚠️[/yellow] Failed to get git log: {result.stderr}"
188
+ )
189
+ return None
190
+
191
+ return result.stdout
192
+
193
+ def _build_git_log_command(self, since_version: str | None = None) -> list[str]:
194
+ """Build the git log command based on parameters."""
195
+ if since_version:
196
+ return [
197
+ "log",
198
+ f"{since_version}..HEAD",
199
+ "--oneline",
200
+ "--no-merges",
201
+ ]
202
+ # Get commits since last release tag or last 50 commits
203
+ return ["log", "-50", "--oneline", "--no-merges"]
204
+
205
+ def _parse_commits_to_entries(
206
+ self, git_output: str
207
+ ) -> dict[str, list[ChangelogEntry]]:
208
+ """Parse git commit output into changelog entries."""
209
+ entries_by_type: dict[str, list[ChangelogEntry]] = {}
210
+
211
+ for line in git_output.strip().split("\n"):
212
+ if not line.strip():
213
+ continue
214
+
215
+ entry = self._process_commit_line(line)
216
+ if entry:
217
+ self._add_entry_to_collection(entry, entries_by_type)
218
+
219
+ return entries_by_type
220
+
221
+ def _process_commit_line(self, line: str) -> ChangelogEntry | None:
222
+ """Process a single commit line into a changelog entry."""
223
+ # Parse commit hash and message
224
+ parts = line.strip().split(" ", 1)
225
+ if len(parts) < 2:
226
+ return None
227
+
228
+ commit_hash = parts[0]
229
+ commit_message = parts[1]
230
+
231
+ # Get full commit message
232
+ full_message = self._get_full_commit_message(commit_hash, commit_message)
233
+
234
+ # Parse into changelog entry
235
+ return self.parse_commit_message(full_message, commit_hash)
236
+
237
+ def _get_full_commit_message(self, commit_hash: str, fallback_message: str) -> str:
238
+ """Get the full commit message for detailed parsing."""
239
+ full_commit_result = self.git._run_git_command(
240
+ ["show", "--format=%B", "--no-patch", commit_hash]
241
+ )
242
+
243
+ return (
244
+ full_commit_result.stdout
245
+ if full_commit_result.returncode == 0
246
+ else fallback_message
247
+ )
248
+
249
+ def _add_entry_to_collection(
250
+ self, entry: ChangelogEntry, entries_by_type: dict[str, list[ChangelogEntry]]
251
+ ) -> None:
252
+ """Add a changelog entry to the appropriate type collection."""
253
+ if entry.type not in entries_by_type:
254
+ entries_by_type[entry.type] = []
255
+ entries_by_type[entry.type].append(entry)
256
+
257
+ def update_changelog(
258
+ self,
259
+ changelog_path: Path,
260
+ new_version: str,
261
+ entries_by_type: dict[str, list[ChangelogEntry]] | None = None,
262
+ ) -> bool:
263
+ """Update the changelog file with new entries."""
264
+ try:
265
+ if entries_by_type is None:
266
+ entries_by_type = self.generate_changelog_entries()
267
+
268
+ if not entries_by_type:
269
+ self.console.print("[yellow]ℹ️[/yellow] No new changelog entries to add")
270
+ return True
271
+
272
+ # Read existing changelog
273
+ existing_content = ""
274
+ if changelog_path.exists():
275
+ existing_content = changelog_path.read_text(encoding="utf-8")
276
+
277
+ # Generate new section
278
+ new_section = self._generate_changelog_section(new_version, entries_by_type)
279
+
280
+ # Insert new section
281
+ updated_content = self._insert_new_section(existing_content, new_section)
282
+
283
+ # Write updated changelog
284
+ changelog_path.write_text(updated_content, encoding="utf-8")
285
+
286
+ self.console.print(
287
+ f"[green]✅[/green] Updated {changelog_path.name} with {len(entries_by_type)} sections"
288
+ )
289
+ return True
290
+
291
+ except Exception as e:
292
+ self.console.print(f"[red]❌[/red] Failed to update changelog: {e}")
293
+ return False
294
+
295
+ def _generate_changelog_section(
296
+ self, version: str, entries_by_type: dict[str, list[ChangelogEntry]]
297
+ ) -> str:
298
+ """Generate a new changelog section."""
299
+ today = datetime.now().strftime("%Y-%m-%d")
300
+ section_lines = [f"## [{version}] - {today}", ""]
301
+
302
+ # Order sections by importance
303
+ section_order = [
304
+ "Added",
305
+ "Changed",
306
+ "Fixed",
307
+ "Removed",
308
+ "Performance",
309
+ "Security",
310
+ "Deprecated",
311
+ "Documentation",
312
+ "Testing",
313
+ "Build",
314
+ "CI/CD",
315
+ "Internal",
316
+ ]
317
+
318
+ for section_name in section_order:
319
+ if section_name in entries_by_type:
320
+ entries = entries_by_type[section_name]
321
+ if entries:
322
+ section_lines.extend((f"### {section_name}", ""))
323
+
324
+ # Sort entries: breaking changes first, then alphabetically
325
+ entries.sort(
326
+ key=lambda e: (not e.breaking_change, e.description.lower())
327
+ )
328
+
329
+ for entry in entries:
330
+ section_lines.append(entry.to_markdown())
331
+ section_lines.append("")
332
+
333
+ return "\n".join(section_lines)
334
+
335
+ def _insert_new_section(self, existing_content: str, new_section: str) -> str:
336
+ """Insert new section into existing changelog content."""
337
+ if not existing_content.strip():
338
+ # Create new changelog
339
+ header = """# Changelog
340
+
341
+ All notable changes to this project will be documented in this file.
342
+
343
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
344
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
345
+
346
+ """
347
+ return header + new_section
348
+
349
+ # Find where to insert (after header, before first existing version)
350
+ lines = existing_content.split("\n")
351
+ insert_index = 0
352
+
353
+ # Find the insertion point (after the header, before first version)
354
+ for i, line in enumerate(lines):
355
+ if line.strip().startswith("## ["):
356
+ insert_index = i
357
+ break
358
+ else:
359
+ # No existing version sections found, insert at end
360
+ insert_index = len(lines)
361
+
362
+ # Insert new section
363
+ new_lines = (
364
+ lines[:insert_index] + new_section.split("\n") + lines[insert_index:]
365
+ )
366
+ return "\n".join(new_lines)
367
+
368
+ def generate_changelog_from_commits(
369
+ self, changelog_path: Path, version: str, since_version: str | None = None
370
+ ) -> bool:
371
+ """Generate and update changelog from git commits."""
372
+ self.console.print(
373
+ f"[cyan]📝[/cyan] Generating changelog entries for version {version}..."
374
+ )
375
+
376
+ entries = self.generate_changelog_entries(since_version)
377
+ if not entries:
378
+ self.console.print("[yellow]ℹ️[/yellow] No changelog entries generated")
379
+ return True
380
+
381
+ # Display preview
382
+ self._display_changelog_preview(entries)
383
+
384
+ return self.update_changelog(changelog_path, version, entries)
385
+
386
+ def _display_changelog_preview(
387
+ self, entries_by_type: dict[str, list[ChangelogEntry]]
388
+ ) -> None:
389
+ """Display a preview of generated changelog entries."""
390
+ self.console.print("[cyan]📋[/cyan] Changelog preview:")
391
+
392
+ for section_name, entries in entries_by_type.items():
393
+ if entries:
394
+ self.console.print(f"[bold]{section_name}:[/bold]")
395
+ for entry in entries[:3]: # Show first 3 entries
396
+ self.console.print(f" {entry.to_markdown()}")
397
+ if len(entries) > 3:
398
+ self.console.print(f" [dim]... and {len(entries) - 3} more[/dim]")
399
+ self.console.print()
@@ -0,0 +1,305 @@
1
+ """Unified command execution service with consistent error handling and timeouts."""
2
+
3
+ import asyncio
4
+ import subprocess
5
+ from pathlib import Path
6
+
7
+ from loguru import logger
8
+
9
+
10
+ class CommandExecutionService:
11
+ """Unified command execution with consistent error handling, timeouts, and caching."""
12
+
13
+ def __init__(self, default_timeout: int = 30):
14
+ """
15
+ Initialize the command execution service.
16
+
17
+ Args:
18
+ default_timeout: Default timeout in seconds for commands
19
+ """
20
+ self.default_timeout = default_timeout
21
+
22
+ async def run_command(
23
+ self,
24
+ cmd: str | list[str],
25
+ cwd: str | Path | None = None,
26
+ env: dict[str, str] | None = None,
27
+ timeout: int | None = None,
28
+ capture_output: bool = True,
29
+ check: bool = True,
30
+ ) -> subprocess.CompletedProcess:
31
+ """
32
+ Run a command with timeout and error handling.
33
+
34
+ Args:
35
+ cmd: Command to run as a string or list of strings
36
+ cwd: Working directory to run the command in
37
+ env: Environment variables to use
38
+ timeout: Timeout in seconds (uses default if not specified)
39
+ capture_output: Whether to capture stdout/stderr
40
+ check: If True, raises exception on non-zero exit code
41
+
42
+ Returns:
43
+ CompletedProcess instance with results
44
+
45
+ Raises:
46
+ subprocess.TimeoutExpired: If command times out
47
+ subprocess.CalledProcessError: If command fails and check=True
48
+ """
49
+ timeout = timeout or self.default_timeout
50
+ str_cmd = " ".join(cmd) if isinstance(cmd, list) else cmd
51
+ logger.debug(f"Executing command: {str_cmd}")
52
+
53
+ try:
54
+ self._get_executable(cmd)
55
+ process = await self._create_subprocess(cmd, capture_output, cwd, env)
56
+ return await self._execute_process(
57
+ process, cmd, str_cmd, timeout, check, capture_output
58
+ )
59
+ except FileNotFoundError:
60
+ logger.error(f"Command not found: {self._get_executable(cmd)}")
61
+ raise
62
+ except Exception as e:
63
+ logger.error(f"Command execution failed: {str_cmd}, Error: {e}")
64
+ raise
65
+
66
+ def _get_executable(self, cmd: str | list[str]) -> str:
67
+ """Extract executable name from command."""
68
+ if isinstance(cmd, list):
69
+ return cmd[0] if cmd else ""
70
+ else:
71
+ parts = cmd.split()
72
+ return parts[0] if parts else ""
73
+
74
+ async def _create_subprocess(
75
+ self,
76
+ cmd: str | list[str],
77
+ capture_output: bool,
78
+ cwd: str | Path | None,
79
+ env: dict[str, str] | None,
80
+ ) -> asyncio.subprocess.Process:
81
+ """Create subprocess with proper configuration."""
82
+ return await asyncio.create_subprocess_exec(
83
+ *(cmd if isinstance(cmd, list) else cmd.split()),
84
+ stdout=asyncio.subprocess.PIPE if capture_output else None,
85
+ stderr=asyncio.subprocess.PIPE if capture_output else None,
86
+ cwd=cwd,
87
+ env=env,
88
+ )
89
+
90
+ async def _execute_process(
91
+ self,
92
+ process: asyncio.subprocess.Process,
93
+ cmd: str | list[str],
94
+ str_cmd: str,
95
+ timeout: int,
96
+ check: bool,
97
+ capture_output: bool,
98
+ ) -> subprocess.CompletedProcess:
99
+ """Execute the process and handle the result."""
100
+ try:
101
+ stdout, stderr = await asyncio.wait_for(
102
+ process.communicate(), timeout=timeout
103
+ )
104
+
105
+ return_code = process.returncode if process.returncode is not None else 0
106
+ completed_process = subprocess.CompletedProcess(
107
+ cmd,
108
+ return_code,
109
+ stdout.decode() if stdout else None,
110
+ stderr.decode() if stderr else None,
111
+ )
112
+
113
+ if check and process.returncode != 0:
114
+ logger.error(
115
+ f"Command failed with exit code {process.returncode}: {str_cmd}"
116
+ )
117
+ raise subprocess.CalledProcessError(
118
+ return_code,
119
+ cmd,
120
+ output=completed_process.stdout,
121
+ stderr=completed_process.stderr,
122
+ )
123
+
124
+ logger.debug(f"Command completed successfully: {str_cmd}")
125
+ return completed_process
126
+
127
+ except TimeoutError:
128
+ # Handle timeout
129
+ process.kill()
130
+ await process.wait() # Ensure process is cleaned up
131
+ logger.error(f"Command timed out after {timeout}s: {str_cmd}")
132
+ raise subprocess.TimeoutExpired(cmd, timeout)
133
+
134
+ def run_command_sync(
135
+ self,
136
+ cmd: str | list[str],
137
+ cwd: str | Path | None = None,
138
+ env: dict[str, str] | None = None,
139
+ timeout: int | None = None,
140
+ capture_output: bool = True,
141
+ check: bool = True,
142
+ ) -> subprocess.CompletedProcess:
143
+ """
144
+ Synchronous version of run_command.
145
+
146
+ Args:
147
+ cmd: Command to run as a string or list of strings
148
+ cwd: Working directory to run the command in
149
+ env: Environment variables to use
150
+ timeout: Timeout in seconds (uses default if not specified)
151
+ capture_output: Whether to capture stdout/stderr
152
+ check: If True, raises exception on non-zero exit code
153
+
154
+ Returns:
155
+ CompletedProcess instance with results
156
+ """
157
+ timeout = timeout or self.default_timeout
158
+ str_cmd = " ".join(cmd) if isinstance(cmd, list) else cmd
159
+
160
+ try:
161
+ logger.debug(f"Executing sync command: {str_cmd}")
162
+
163
+ result = subprocess.run(
164
+ cmd,
165
+ cwd=cwd,
166
+ env=env,
167
+ timeout=timeout,
168
+ capture_output=capture_output,
169
+ text=True, # Return strings instead of bytes
170
+ check=check,
171
+ )
172
+
173
+ logger.debug(f"Sync command completed: {str_cmd}")
174
+ return result
175
+
176
+ except subprocess.TimeoutExpired:
177
+ logger.error(f"Sync command timed out after {timeout}s: {str_cmd}")
178
+ raise
179
+ except Exception as e:
180
+ logger.error(f"Sync command execution failed: {str_cmd}, Error: {e}")
181
+ raise
182
+
183
+ async def run_multiple_commands(
184
+ self,
185
+ commands: list[str | list[str]],
186
+ cwd: str | Path | None = None,
187
+ env: dict[str, str] | None = None,
188
+ timeout: int | None = None,
189
+ parallel: bool = False,
190
+ ) -> list[subprocess.CompletedProcess]:
191
+ """
192
+ Run multiple commands sequentially or in parallel.
193
+
194
+ Args:
195
+ commands: List of commands to run
196
+ cwd: Working directory to run the commands in
197
+ env: Environment variables to use
198
+ timeout: Timeout in seconds for each command
199
+ parallel: If True, run commands in parallel; otherwise sequentially
200
+
201
+ Returns:
202
+ List of CompletedProcess instances with results
203
+ """
204
+ results = []
205
+
206
+ if parallel:
207
+ # Run commands in parallel
208
+ tasks = [
209
+ self.run_command(cmd, cwd=cwd, env=env, timeout=timeout)
210
+ for cmd in commands
211
+ ]
212
+ results = await asyncio.gather(*tasks)
213
+ else:
214
+ # Run commands sequentially
215
+ for cmd in commands:
216
+ result = await self.run_command(cmd, cwd=cwd, env=env, timeout=timeout)
217
+ results.append(result)
218
+
219
+ return results
220
+
221
+ async def command_exists(self, command: str) -> bool:
222
+ """
223
+ Check if a command exists in the system.
224
+
225
+ Args:
226
+ command: Command to check
227
+
228
+ Returns:
229
+ True if command exists, False otherwise
230
+ """
231
+ try:
232
+ # Try running 'which' on Unix-like systems or 'where' on Windows
233
+ import platform
234
+
235
+ if platform.system() == "Windows":
236
+ check_cmd = ["where", command]
237
+ else:
238
+ check_cmd = ["which", command]
239
+
240
+ result = await self.run_command(
241
+ check_cmd,
242
+ capture_output=True,
243
+ check=False, # Don't raise on non-zero exit (which returns 1 if not found)
244
+ )
245
+
246
+ return result.returncode == 0
247
+ except (subprocess.CalledProcessError, FileNotFoundError):
248
+ # If the check command itself fails, command likely doesn't exist
249
+ return False
250
+
251
+ async def run_command_with_retries(
252
+ self,
253
+ cmd: str | list[str],
254
+ max_retries: int = 3,
255
+ cwd: str | Path | None = None,
256
+ env: dict[str, str] | None = None,
257
+ timeout: int | None = None,
258
+ capture_output: bool = True,
259
+ check: bool = True,
260
+ backoff_factor: float = 1.0,
261
+ ) -> subprocess.CompletedProcess:
262
+ """
263
+ Run a command with retry logic.
264
+
265
+ Args:
266
+ cmd: Command to run
267
+ max_retries: Maximum number of retry attempts
268
+ cwd: Working directory
269
+ env: Environment variables
270
+ timeout: Timeout for each attempt
271
+ capture_output: Whether to capture output
272
+ check: Whether to check return code
273
+ backoff_factor: Factor by which to multiply wait time between retries
274
+
275
+ Returns:
276
+ CompletedProcess instance with results
277
+ """
278
+ for attempt in range(
279
+ max_retries + 1
280
+ ): # +1 because first attempt doesn't count as retry
281
+ try:
282
+ return await self.run_command(
283
+ cmd,
284
+ cwd=cwd,
285
+ env=env,
286
+ timeout=timeout,
287
+ capture_output=capture_output,
288
+ check=check,
289
+ )
290
+ except (subprocess.TimeoutExpired, subprocess.CalledProcessError) as e:
291
+ if attempt == max_retries:
292
+ # Last attempt, re-raise the exception
293
+ logger.error(f"Command failed after {max_retries} retries: {cmd}")
294
+ raise
295
+ else:
296
+ # Wait before retrying with exponential backoff
297
+ wait_time = backoff_factor * (2**attempt)
298
+ logger.warning(
299
+ f"Command failed on attempt {attempt + 1}, "
300
+ f"retrying in {wait_time}s: {cmd}. Error: {e}"
301
+ )
302
+ await asyncio.sleep(wait_time)
303
+
304
+ # This line should never be reached due to the loop logic
305
+ raise RuntimeError("Unexpected error in run_command_with_retries")