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
@@ -1,855 +0,0 @@
1
- import io
2
- import os
3
- import platform
4
- import re
5
- import subprocess
6
- import time
7
- import tokenize
8
- import typing as t
9
- from contextlib import suppress
10
- from dataclasses import dataclass, field
11
- from pathlib import Path
12
- from subprocess import CompletedProcess
13
- from subprocess import run as execute
14
- from token import STRING
15
- from tomllib import loads
16
-
17
- from rich.console import Console
18
- from tomli_w import dumps
19
-
20
- config_files = (".gitignore", ".pre-commit-config.yaml", ".libcst.codemod.yaml")
21
- interactive_hooks = ("refurb", "bandit", "pyright")
22
- default_python_version = "3.13"
23
-
24
-
25
- @t.runtime_checkable
26
- class CommandRunner(t.Protocol):
27
- def execute_command(
28
- self, cmd: list[str], **kwargs: t.Any
29
- ) -> subprocess.CompletedProcess[str]: ...
30
-
31
-
32
- @t.runtime_checkable
33
- class OptionsProtocol(t.Protocol):
34
- commit: bool
35
- interactive: bool
36
- doc: bool
37
- no_config_updates: bool
38
- verbose: bool
39
- update_precommit: bool
40
- clean: bool
41
- test: bool
42
- publish: t.Any | None
43
- bump: t.Any | None
44
- all: t.Any | None
45
- ai_agent: bool = False
46
- create_pr: bool = False
47
- skip_hooks: bool = False
48
-
49
-
50
- @dataclass
51
- class CodeCleaner:
52
- console: Console
53
-
54
- def clean_files(self, pkg_dir: Path | None) -> None:
55
- if pkg_dir is None:
56
- return
57
- for file_path in pkg_dir.rglob("*.py"):
58
- if not str(file_path.parent).startswith("__"):
59
- self.clean_file(file_path)
60
-
61
- def clean_file(self, file_path: Path) -> None:
62
- try:
63
- if file_path.resolve() == Path(__file__).resolve():
64
- self.console.print(f"Skipping cleaning of {file_path} (self file).")
65
- return
66
- except Exception as e:
67
- self.console.print(f"Error comparing file paths: {e}")
68
- try:
69
- code = file_path.read_text()
70
- code = self.remove_docstrings(code)
71
- code = self.remove_line_comments(code)
72
- code = self.remove_extra_whitespace(code)
73
- code = self.reformat_code(code)
74
- file_path.write_text(code) # type: ignore
75
- self.console.print(f"Cleaned: {file_path}")
76
- except Exception as e:
77
- self.console.print(f"Error cleaning {file_path}: {e}")
78
-
79
- def remove_line_comments(self, code: str) -> str:
80
- new_lines = []
81
- for line in code.splitlines():
82
- if "#" not in line or line.endswith("# skip"):
83
- new_lines.append(line)
84
- continue
85
- if re.search(r"#\s", line):
86
- idx = line.find("#")
87
- code_part = line[:idx].rstrip()
88
- comment_part = line[idx:]
89
- if (
90
- " type: ignore" in comment_part
91
- or " noqa" in comment_part
92
- or " nosec" in comment_part
93
- ):
94
- new_lines.append(line)
95
- else:
96
- if code_part:
97
- new_lines.append(code_part)
98
- else:
99
- new_lines.append(line)
100
- return "\n".join(new_lines)
101
-
102
- def _is_triple_quoted(self, token_string: str) -> bool:
103
- triple_quote_patterns = [
104
- ('"""', '"""'),
105
- ("'''", "'''"),
106
- ('r"""', '"""'),
107
- ("r'''", "'''"),
108
- ]
109
- return any(
110
- token_string.startswith(start) and token_string.endswith(end)
111
- for start, end in triple_quote_patterns
112
- )
113
-
114
- def _is_module_docstring(
115
- self, tokens: list[tokenize.TokenInfo], i: int, indent_level: int
116
- ) -> bool:
117
- if i <= 0 or indent_level != 0:
118
- return False
119
- preceding_tokens = tokens[:i]
120
- return not preceding_tokens
121
-
122
- def _is_function_or_class_docstring(
123
- self,
124
- tokens: list[tokenize.TokenInfo],
125
- i: int,
126
- last_token_type: t.Any,
127
- last_token_string: str,
128
- ) -> bool:
129
- if last_token_type != tokenize.OP or last_token_string != ":": # nosec B105
130
- return False
131
- for prev_idx in range(i - 1, max(0, i - 20), -1):
132
- prev_token = tokens[prev_idx]
133
- if prev_token[1] in ("def", "class") and prev_token[0] == tokenize.NAME:
134
- return True
135
- elif prev_token[0] == tokenize.DEDENT:
136
- break
137
- return False
138
-
139
- def _is_variable_docstring(
140
- self, tokens: list[tokenize.TokenInfo], i: int, indent_level: int
141
- ) -> bool:
142
- if indent_level <= 0:
143
- return False
144
- for prev_idx in range(i - 1, max(0, i - 10), -1):
145
- if tokens[prev_idx][0]:
146
- return True
147
- return False
148
-
149
- def remove_docstrings(self, source: str) -> str:
150
- try:
151
- io_obj = io.StringIO(source)
152
- tokens = list(tokenize.generate_tokens(io_obj.readline))
153
- result_tokens = []
154
- indent_level = 0
155
- last_non_ws_token_type = None
156
- last_non_ws_token_string = "" # nosec B105
157
- for i, token in enumerate(tokens):
158
- token_type, token_string, _, _, _ = token
159
- if token_type == tokenize.INDENT:
160
- indent_level += 1
161
- elif token_type == tokenize.DEDENT:
162
- indent_level -= 1
163
- if token_type == STRING and self._is_triple_quoted(token_string):
164
- is_docstring = (
165
- self._is_module_docstring(tokens, i, indent_level)
166
- or self._is_function_or_class_docstring(
167
- tokens, i, last_non_ws_token_type, last_non_ws_token_string
168
- )
169
- or self._is_variable_docstring(tokens, i, indent_level)
170
- )
171
- if is_docstring:
172
- continue
173
- if token_type not in (
174
- tokenize.NL,
175
- tokenize.NEWLINE,
176
- tokenize.INDENT,
177
- tokenize.DEDENT,
178
- ):
179
- last_non_ws_token_type = token_type
180
- last_non_ws_token_string = token_string
181
- result_tokens.append(token)
182
- return tokenize.untokenize(result_tokens)
183
- except Exception as e:
184
- self.console.print(f"Error removing docstrings: {e}")
185
- return source
186
-
187
- def remove_extra_whitespace(self, code: str) -> str:
188
- lines = code.split("\n")
189
- cleaned_lines = []
190
- for i, line in enumerate(lines):
191
- line = line.rstrip()
192
- if i > 0 and (not line) and (not cleaned_lines[-1]):
193
- continue
194
- cleaned_lines.append(line)
195
- return "\n".join(cleaned_lines)
196
-
197
- def reformat_code(self, code: str) -> str | None:
198
- try:
199
- import tempfile
200
-
201
- with tempfile.NamedTemporaryFile(
202
- suffix=".py", mode="w+", delete=False
203
- ) as temp:
204
- temp_path = Path(temp.name)
205
- temp_path.write_text(code)
206
- try:
207
- result = subprocess.run(
208
- ["ruff", "format", str(temp_path)],
209
- check=False,
210
- capture_output=True,
211
- text=True,
212
- )
213
- if result.returncode == 0:
214
- formatted_code = temp_path.read_text()
215
- else:
216
- self.console.print(f"Ruff formatting failed: {result.stderr}")
217
- formatted_code = code
218
- except Exception as e:
219
- self.console.print(f"Error running Ruff: {e}")
220
- formatted_code = code
221
- finally:
222
- with suppress(FileNotFoundError):
223
- temp_path.unlink()
224
- return formatted_code
225
- except Exception as e:
226
- self.console.print(f"Error during reformatting: {e}")
227
- return code
228
-
229
-
230
- @dataclass
231
- class ConfigManager:
232
- our_path: Path
233
- pkg_path: Path
234
- pkg_name: str
235
- console: Console
236
- our_toml_path: Path | None = None
237
- pkg_toml_path: Path | None = None
238
- python_version: str = default_python_version
239
- dry_run: bool = False
240
-
241
- def swap_package_name(self, value: list[str] | str) -> list[str] | str:
242
- if isinstance(value, list):
243
- value.remove("crackerjack")
244
- value.append(self.pkg_name)
245
- else:
246
- value = value.replace("crackerjack", self.pkg_name)
247
- return value
248
-
249
- def update_pyproject_configs(self) -> None:
250
- self._setup_toml_paths()
251
- if self._is_crackerjack_project():
252
- self._handle_crackerjack_project()
253
- return
254
- our_toml_config = self._load_our_toml()
255
- pkg_toml_config = self._load_pkg_toml()
256
- self._ensure_required_sections(pkg_toml_config)
257
- self._update_tool_settings(our_toml_config, pkg_toml_config)
258
- self._update_python_version(our_toml_config, pkg_toml_config)
259
- self._save_pkg_toml(pkg_toml_config)
260
-
261
- def _setup_toml_paths(self) -> None:
262
- toml_file = "pyproject.toml"
263
- self.our_toml_path = self.our_path / toml_file
264
- self.pkg_toml_path = self.pkg_path / toml_file
265
-
266
- def _is_crackerjack_project(self) -> bool:
267
- return self.pkg_path.stem == "crackerjack"
268
-
269
- def _handle_crackerjack_project(self) -> None:
270
- if self.our_toml_path and self.pkg_toml_path:
271
- self.our_toml_path.write_text(self.pkg_toml_path.read_text())
272
-
273
- def _load_our_toml(self) -> dict[str, t.Any]:
274
- if self.our_toml_path:
275
- return loads(self.our_toml_path.read_text())
276
- return {}
277
-
278
- def _load_pkg_toml(self) -> dict[str, t.Any]:
279
- if self.pkg_toml_path:
280
- return loads(self.pkg_toml_path.read_text())
281
- return {}
282
-
283
- def _ensure_required_sections(self, pkg_toml_config: dict[str, t.Any]) -> None:
284
- pkg_toml_config.setdefault("tool", {})
285
- pkg_toml_config.setdefault("project", {})
286
-
287
- def _update_tool_settings(
288
- self, our_toml_config: dict[str, t.Any], pkg_toml_config: dict[str, t.Any]
289
- ) -> None:
290
- for tool, settings in our_toml_config.get("tool", {}).items():
291
- for setting, value in settings.items():
292
- if isinstance(value, dict):
293
- for k, v in {
294
- x: self.swap_package_name(y)
295
- for x, y in value.items()
296
- if isinstance(y, (str, list)) and "crackerjack" in str(y)
297
- }.items():
298
- settings[setting][k] = v
299
- elif isinstance(value, (str, list)) and "crackerjack" in str(value):
300
- value = self.swap_package_name(value)
301
- settings[setting] = value
302
- if setting in (
303
- "exclude-deps",
304
- "exclude",
305
- "excluded",
306
- "skips",
307
- "ignore",
308
- ) and isinstance(value, list):
309
- conf = pkg_toml_config["tool"].get(tool, {}).get(setting, [])
310
- settings[setting] = list(set(conf + value))
311
- pkg_toml_config["tool"][tool] = settings
312
-
313
- def _update_python_version(
314
- self, our_toml_config: dict[str, t.Any], pkg_toml_config: dict[str, t.Any]
315
- ) -> None:
316
- python_version_pattern = "\\s*W*(\\d\\.\\d*)"
317
- requires_python = our_toml_config.get("project", {}).get("requires-python", "")
318
- classifiers = []
319
- for classifier in pkg_toml_config.get("project", {}).get("classifiers", []):
320
- classifier = re.sub(
321
- python_version_pattern, f" {self.python_version}", classifier
322
- )
323
- classifiers.append(classifier)
324
- pkg_toml_config["project"]["classifiers"] = classifiers
325
- if requires_python:
326
- pkg_toml_config["project"]["requires-python"] = requires_python
327
-
328
- def _save_pkg_toml(self, pkg_toml_config: dict[str, t.Any]) -> None:
329
- if self.pkg_toml_path:
330
- self.pkg_toml_path.write_text(dumps(pkg_toml_config))
331
-
332
- def copy_configs(self) -> None:
333
- for config in config_files:
334
- config_path = self.our_path / config
335
- pkg_config_path = self.pkg_path / config
336
- pkg_config_path.touch()
337
- if self.pkg_path.stem == "crackerjack":
338
- config_path.write_text(pkg_config_path.read_text())
339
- continue
340
- if config != ".gitignore":
341
- pkg_config_path.write_text(
342
- config_path.read_text().replace("crackerjack", self.pkg_name)
343
- )
344
- self.execute_command(["git", "add", config])
345
-
346
- def execute_command(
347
- self, cmd: list[str], **kwargs: t.Any
348
- ) -> subprocess.CompletedProcess[str]:
349
- if self.dry_run:
350
- self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
351
- return CompletedProcess(cmd, 0, "", "")
352
- return execute(cmd, **kwargs)
353
-
354
-
355
- @dataclass
356
- class ProjectManager:
357
- our_path: Path
358
- pkg_path: Path
359
- console: Console
360
- code_cleaner: CodeCleaner
361
- config_manager: ConfigManager
362
- pkg_dir: Path | None = None
363
- pkg_name: str = "crackerjack"
364
- dry_run: bool = False
365
-
366
- def run_interactive(self, hook: str) -> None:
367
- success: bool = False
368
- while not success:
369
- fail = self.execute_command(
370
- ["pre-commit", "run", hook.lower(), "--all-files"]
371
- )
372
- if fail.returncode > 0:
373
- retry = input(f"\n\n{hook.title()} failed. Retry? (y/N): ")
374
- self.console.print()
375
- if retry.strip().lower() == "y":
376
- continue
377
- raise SystemExit(1)
378
- success = True
379
-
380
- def update_pkg_configs(self) -> None:
381
- self.config_manager.copy_configs()
382
- installed_pkgs = self.execute_command(
383
- ["pdm", "list", "--freeze"], capture_output=True, text=True
384
- ).stdout.splitlines()
385
- if not len([pkg for pkg in installed_pkgs if "pre-commit" in pkg]):
386
- self.console.print("Initializing project...")
387
- self.execute_command(["pdm", "self", "add", "keyring"])
388
- self.execute_command(["pdm", "config", "python.use_uv", "true"])
389
- self.execute_command(["git", "init"])
390
- self.execute_command(["git", "branch", "-m", "main"])
391
- self.execute_command(["git", "add", "pyproject.toml"])
392
- self.execute_command(["git", "add", "pdm.lock"])
393
- self.execute_command(["pre-commit", "install"])
394
- self.execute_command(["git", "config", "advice.addIgnoredFile", "false"])
395
- self.config_manager.update_pyproject_configs()
396
-
397
- def run_pre_commit(self) -> None:
398
- self.console.print("\nRunning pre-commit hooks...\n")
399
- check_all = self.execute_command(["pre-commit", "run", "--all-files"])
400
- if check_all.returncode > 0:
401
- check_all = self.execute_command(["pre-commit", "run", "--all-files"])
402
- if check_all.returncode > 0:
403
- self.console.print("\n\nPre-commit failed. Please fix errors.\n")
404
- raise SystemExit(1)
405
-
406
- def execute_command(
407
- self, cmd: list[str], **kwargs: t.Any
408
- ) -> subprocess.CompletedProcess[str]:
409
- if self.dry_run:
410
- self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
411
- return CompletedProcess(cmd, 0, "", "")
412
- return execute(cmd, **kwargs)
413
-
414
-
415
- @dataclass
416
- class Crackerjack:
417
- our_path: Path = field(default_factory=lambda: Path(__file__).parent)
418
- pkg_path: Path = field(default_factory=lambda: Path(Path.cwd()))
419
- pkg_dir: Path | None = None
420
- pkg_name: str = "crackerjack"
421
- python_version: str = default_python_version
422
- console: Console = field(default_factory=lambda: Console(force_terminal=True))
423
- dry_run: bool = False
424
- code_cleaner: CodeCleaner | None = None
425
- config_manager: ConfigManager | None = None
426
- project_manager: ProjectManager | None = None
427
-
428
- def __post_init__(self) -> None:
429
- if self.code_cleaner is None:
430
- self.code_cleaner = CodeCleaner(console=self.console)
431
- if self.config_manager is None:
432
- self.config_manager = ConfigManager(
433
- our_path=self.our_path,
434
- pkg_path=self.pkg_path,
435
- pkg_name=self.pkg_name,
436
- console=self.console,
437
- python_version=self.python_version,
438
- dry_run=self.dry_run,
439
- )
440
- if self.project_manager is None:
441
- self.project_manager = ProjectManager(
442
- our_path=self.our_path,
443
- pkg_path=self.pkg_path,
444
- pkg_dir=self.pkg_dir,
445
- pkg_name=self.pkg_name,
446
- console=self.console,
447
- code_cleaner=self.code_cleaner,
448
- config_manager=self.config_manager,
449
- dry_run=self.dry_run,
450
- )
451
-
452
- def _setup_package(self) -> None:
453
- self.pkg_name = self.pkg_path.stem.lower().replace("-", "_")
454
- self.pkg_dir = self.pkg_path / self.pkg_name
455
- self.pkg_dir.mkdir(exist_ok=True)
456
- self.console.print("\nCrackerjacking...\n")
457
- self.config_manager.pkg_name = self.pkg_name
458
- self.project_manager.pkg_name = self.pkg_name
459
- self.project_manager.pkg_dir = self.pkg_dir
460
-
461
- def _update_project(self, options: OptionsProtocol) -> None:
462
- if not options.no_config_updates:
463
- self.project_manager.update_pkg_configs()
464
- result: CompletedProcess[str] = self.execute_command(
465
- ["pdm", "install"], capture_output=True, text=True
466
- )
467
- if result.returncode == 0:
468
- self.console.print("PDM installed: āœ…\n")
469
- else:
470
- self.console.print(
471
- "\n\nāŒ PDM installation failed. Is PDM is installed? Run `pipx install pdm` and try again.\n\n"
472
- )
473
-
474
- def _update_precommit(self, options: OptionsProtocol) -> None:
475
- if self.pkg_path.stem == "crackerjack" and options.update_precommit:
476
- self.execute_command(["pre-commit", "autoupdate"])
477
-
478
- def _run_interactive_hooks(self, options: OptionsProtocol) -> None:
479
- if options.interactive:
480
- for hook in interactive_hooks:
481
- self.project_manager.run_interactive(hook)
482
-
483
- def _clean_project(self, options: OptionsProtocol) -> None:
484
- if options.clean:
485
- if self.pkg_dir:
486
- self.code_cleaner.clean_files(self.pkg_dir)
487
- tests_dir = self.pkg_path / "tests"
488
- if tests_dir.exists() and tests_dir.is_dir():
489
- self.console.print("\nCleaning tests directory...\n")
490
- self.code_cleaner.clean_files(tests_dir)
491
-
492
- def _prepare_pytest_command(self, options: OptionsProtocol) -> list[str]:
493
- test = ["pytest"]
494
- if options.verbose:
495
- test.append("-v")
496
- test.extend(
497
- [
498
- "--capture=fd", # Capture stdout/stderr at file descriptor level
499
- "--tb=short", # Shorter traceback format
500
- "--no-header", # Reduce output noise
501
- "--disable-warnings", # Disable warning capture
502
- "--durations=0", # Show slowest tests to identify potential hanging tests
503
- "--timeout=60", # 1-minute timeout for tests
504
- ]
505
- )
506
- return test
507
-
508
- def _setup_test_environment(self) -> None:
509
- os.environ["PYTHONASYNCIO_DEBUG"] = "0" # Disable asyncio debug mode
510
- os.environ["RUNNING_UNDER_CRACKERJACK"] = "1" # Signal to conftest.py
511
- if "PYTEST_ASYNCIO_MODE" not in os.environ:
512
- os.environ["PYTEST_ASYNCIO_MODE"] = "strict"
513
-
514
- def _run_pytest_process(
515
- self, test_command: list[str]
516
- ) -> subprocess.CompletedProcess[str]:
517
- try:
518
- process = subprocess.Popen(
519
- test_command,
520
- stdout=subprocess.PIPE,
521
- stderr=subprocess.PIPE,
522
- text=True,
523
- bufsize=1,
524
- universal_newlines=True,
525
- )
526
- timeout = 300
527
- start_time = time.time()
528
- stdout_data = []
529
- stderr_data = []
530
- while process.poll() is None:
531
- if time.time() - start_time > timeout:
532
- self.console.print(
533
- "[red]Test execution timed out after 5 minutes. Terminating...[/red]"
534
- )
535
- process.terminate()
536
- try:
537
- process.wait(timeout=5)
538
- except subprocess.TimeoutExpired:
539
- process.kill()
540
- break
541
- if process.stdout:
542
- line = process.stdout.readline()
543
- if line:
544
- stdout_data.append(line)
545
- self.console.print(line, end="")
546
- if process.stderr:
547
- line = process.stderr.readline()
548
- if line:
549
- stderr_data.append(line)
550
- self.console.print(f"[red]{line}[/red]", end="")
551
- time.sleep(0.1)
552
- if process.stdout:
553
- for line in process.stdout:
554
- stdout_data.append(line)
555
- self.console.print(line, end="")
556
- if process.stderr:
557
- for line in process.stderr:
558
- stderr_data.append(line)
559
- self.console.print(f"[red]{line}[/red]", end="")
560
- returncode = process.returncode or 0
561
- stdout = "".join(stdout_data)
562
- stderr = "".join(stderr_data)
563
- return subprocess.CompletedProcess(
564
- args=test_command, returncode=returncode, stdout=stdout, stderr=stderr
565
- )
566
-
567
- except Exception as e:
568
- self.console.print(f"[red]Error running tests: {e}[/red]")
569
- return subprocess.CompletedProcess(test_command, 1, "", str(e))
570
-
571
- def _report_test_results(
572
- self, result: subprocess.CompletedProcess[str], ai_agent: str
573
- ) -> None:
574
- if result.returncode > 0:
575
- if result.stderr:
576
- self.console.print(result.stderr)
577
- if ai_agent:
578
- self.console.print(
579
- '[json]{"status": "failed", "action": "tests", "returncode": '
580
- + str(result.returncode)
581
- + "}[/json]"
582
- )
583
- else:
584
- self.console.print("\n\nāŒ Tests failed. Please fix errors.\n")
585
- raise SystemExit(1)
586
-
587
- if ai_agent:
588
- self.console.print('[json]{"status": "success", "action": "tests"}[/json]')
589
- else:
590
- self.console.print("\n\nāœ… Tests passed successfully!\n")
591
-
592
- def _run_tests(self, options: OptionsProtocol) -> None:
593
- if options.test:
594
- ai_agent = os.environ.get("AI_AGENT", "")
595
- if ai_agent:
596
- self.console.print(
597
- '[json]{"status": "running", "action": "tests"}[/json]'
598
- )
599
- else:
600
- self.console.print("\n\nRunning tests...\n")
601
- test_command = self._prepare_pytest_command(options)
602
- self._setup_test_environment()
603
- result = self._run_pytest_process(test_command)
604
- self._report_test_results(result, ai_agent)
605
-
606
- def _bump_version(self, options: OptionsProtocol) -> None:
607
- for option in (options.publish, options.bump):
608
- if option:
609
- self.execute_command(["pdm", "bump", option])
610
- break
611
-
612
- def _publish_project(self, options: OptionsProtocol) -> None:
613
- if options.publish:
614
- if platform.system() == "Darwin":
615
- authorize = self.execute_command(
616
- ["pdm", "self", "add", "keyring"], capture_output=True, text=True
617
- )
618
- if authorize.returncode > 0:
619
- self.console.print(
620
- "\n\nAuthorization failed. Please add your keyring credentials to PDM. Run `pdm self add keyring` and try again.\n\n"
621
- )
622
- raise SystemExit(1)
623
- build = self.execute_command(
624
- ["pdm", "build"], capture_output=True, text=True
625
- )
626
- self.console.print(build.stdout)
627
- if build.returncode > 0:
628
- self.console.print(build.stderr)
629
- self.console.print("\n\nBuild failed. Please fix errors.\n")
630
- raise SystemExit(1)
631
- self.execute_command(["pdm", "publish", "--no-build"])
632
-
633
- def _commit_and_push(self, options: OptionsProtocol) -> None:
634
- if options.commit:
635
- commit_msg = input("\nCommit message: ")
636
- self.execute_command(
637
- ["git", "commit", "-m", commit_msg, "--no-verify", "--", "."]
638
- )
639
- self.execute_command(["git", "push", "origin", "main"])
640
-
641
- def _create_pull_request(self, options: OptionsProtocol) -> None:
642
- if options.create_pr:
643
- self.console.print("\nCreating pull request...")
644
- current_branch = self.execute_command(
645
- ["git", "branch", "--show-current"], capture_output=True, text=True
646
- ).stdout.strip()
647
- remote_url = self.execute_command(
648
- ["git", "remote", "get-url", "origin"], capture_output=True, text=True
649
- ).stdout.strip()
650
- is_github = "github.com" in remote_url
651
- is_gitlab = "gitlab.com" in remote_url
652
- if is_github:
653
- gh_installed = (
654
- self.execute_command(
655
- ["which", "gh"], capture_output=True, text=True
656
- ).returncode
657
- == 0
658
- )
659
- if not gh_installed:
660
- self.console.print(
661
- "\n[red]GitHub CLI (gh) is not installed. Please install it first:[/red]\n"
662
- " brew install gh # for macOS\n"
663
- " or visit https://cli.github.com/ for other installation methods"
664
- )
665
- return
666
- auth_status = self.execute_command(
667
- ["gh", "auth", "status"], capture_output=True, text=True
668
- ).returncode
669
- if auth_status != 0:
670
- self.console.print(
671
- "\n[red]You need to authenticate with GitHub first. Run:[/red]\n"
672
- " gh auth login"
673
- )
674
- return
675
- pr_title = input("\nEnter a title for your pull request: ")
676
- self.console.print(
677
- "Enter a description for your pull request (press Ctrl+D when done):"
678
- )
679
- pr_description = ""
680
- with suppress(EOFError):
681
- pr_description = "".join(iter(input, ""))
682
- self.console.print("Creating pull request to GitHub repository...")
683
- result = self.execute_command(
684
- [
685
- "gh",
686
- "pr",
687
- "create",
688
- "--title",
689
- pr_title,
690
- "--body",
691
- pr_description,
692
- ],
693
- capture_output=True,
694
- text=True,
695
- )
696
- if result.returncode == 0:
697
- self.console.print(
698
- f"\n[green]Pull request created successfully![/green]\n{result.stdout}"
699
- )
700
- else:
701
- self.console.print(
702
- f"\n[red]Failed to create pull request:[/red]\n{result.stderr}"
703
- )
704
- elif is_gitlab:
705
- glab_installed = (
706
- self.execute_command(
707
- ["which", "glab"], capture_output=True, text=True
708
- ).returncode
709
- == 0
710
- )
711
- if not glab_installed:
712
- self.console.print(
713
- "\n[red]GitLab CLI (glab) is not installed. Please install it first:[/red]\n"
714
- " brew install glab # for macOS\n"
715
- " or visit https://gitlab.com/gitlab-org/cli for other installation methods"
716
- )
717
- return
718
- auth_status = self.execute_command(
719
- ["glab", "auth", "status"], capture_output=True, text=True
720
- ).returncode
721
- if auth_status != 0:
722
- self.console.print(
723
- "\n[red]You need to authenticate with GitLab first. Run:[/red]\n"
724
- " glab auth login"
725
- )
726
- return
727
- mr_title = input("\nEnter a title for your merge request: ")
728
- self.console.print(
729
- "Enter a description for your merge request (press Ctrl+D when done):"
730
- )
731
- mr_description = ""
732
- with suppress(EOFError):
733
- mr_description = "".join(iter(input, ""))
734
- self.console.print("Creating merge request to GitLab repository...")
735
- result = self.execute_command(
736
- [
737
- "glab",
738
- "mr",
739
- "create",
740
- "--title",
741
- mr_title,
742
- "--description",
743
- mr_description,
744
- "--source-branch",
745
- current_branch,
746
- "--target-branch",
747
- "main",
748
- ],
749
- capture_output=True,
750
- text=True,
751
- )
752
- if result.returncode == 0:
753
- self.console.print(
754
- f"\n[green]Merge request created successfully![/green]\n{result.stdout}"
755
- )
756
- else:
757
- self.console.print(
758
- f"\n[red]Failed to create merge request:[/red]\n{result.stderr}"
759
- )
760
- else:
761
- self.console.print(
762
- f"\n[red]Unsupported git hosting service: {remote_url}[/red]\n"
763
- "This command currently supports GitHub and GitLab."
764
- )
765
-
766
- def execute_command(
767
- self, cmd: list[str], **kwargs: t.Any
768
- ) -> subprocess.CompletedProcess[str]:
769
- if self.dry_run:
770
- self.console.print(f"[yellow]Would run: {' '.join(cmd)}[/yellow]")
771
- return CompletedProcess(cmd, 0, "", "")
772
- return execute(cmd, **kwargs)
773
-
774
- def process(self, options: OptionsProtocol) -> None:
775
- actions_performed = []
776
- if options.all:
777
- options.clean = True
778
- options.test = True
779
- options.publish = options.all
780
- options.commit = True
781
-
782
- self._setup_package()
783
- actions_performed.append("setup_package")
784
-
785
- self._update_project(options)
786
- actions_performed.append("update_project")
787
-
788
- self._update_precommit(options)
789
- if options.update_precommit:
790
- actions_performed.append("update_precommit")
791
-
792
- self._run_interactive_hooks(options)
793
- if options.interactive:
794
- actions_performed.append("run_interactive_hooks")
795
-
796
- self._clean_project(options)
797
- if options.clean:
798
- actions_performed.append("clean_project")
799
-
800
- if not options.skip_hooks:
801
- self.project_manager.run_pre_commit()
802
- actions_performed.append("run_pre_commit")
803
- else:
804
- self.console.print(
805
- "\n[yellow]Skipping pre-commit hooks as requested[/yellow]\n"
806
- )
807
- actions_performed.append("skip_pre_commit")
808
-
809
- self._run_tests(options)
810
- if options.test:
811
- actions_performed.append("run_tests")
812
-
813
- self._bump_version(options)
814
- if options.bump or options.publish:
815
- actions_performed.append("bump_version")
816
-
817
- self._publish_project(options)
818
- if options.publish:
819
- actions_performed.append("publish_project")
820
-
821
- self._commit_and_push(options)
822
- if options.commit:
823
- actions_performed.append("commit_and_push")
824
-
825
- self._create_pull_request(options)
826
- if options.create_pr:
827
- actions_performed.append("create_pull_request")
828
-
829
- if getattr(options, "ai_agent", False):
830
- import json
831
-
832
- result = {
833
- "status": "complete",
834
- "package": self.pkg_name,
835
- "actions": actions_performed,
836
- }
837
- self.console.print(f"[json]{json.dumps(result)}[/json]")
838
- else:
839
- self.console.print("\nšŸŗ Crackerjack complete!\n")
840
-
841
-
842
- def create_crackerjack_runner(
843
- console: Console | None = None,
844
- our_path: Path | None = None,
845
- pkg_path: Path | None = None,
846
- python_version: str = default_python_version,
847
- dry_run: bool = False,
848
- ) -> Crackerjack:
849
- return Crackerjack(
850
- console=console or Console(force_terminal=True),
851
- our_path=our_path or Path(__file__).parent,
852
- pkg_path=pkg_path or Path.cwd(),
853
- python_version=python_version,
854
- dry_run=dry_run,
855
- )