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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. crackerjack/README.md +19 -0
  2. crackerjack/__init__.py +30 -1
  3. crackerjack/__main__.py +342 -1263
  4. crackerjack/adapters/README.md +18 -0
  5. crackerjack/adapters/__init__.py +27 -5
  6. crackerjack/adapters/_output_paths.py +167 -0
  7. crackerjack/adapters/_qa_adapter_base.py +309 -0
  8. crackerjack/adapters/_tool_adapter_base.py +706 -0
  9. crackerjack/adapters/ai/README.md +65 -0
  10. crackerjack/adapters/ai/__init__.py +5 -0
  11. crackerjack/adapters/ai/claude.py +853 -0
  12. crackerjack/adapters/complexity/README.md +53 -0
  13. crackerjack/adapters/complexity/__init__.py +10 -0
  14. crackerjack/adapters/complexity/complexipy.py +641 -0
  15. crackerjack/adapters/dependency/__init__.py +22 -0
  16. crackerjack/adapters/dependency/pip_audit.py +418 -0
  17. crackerjack/adapters/format/README.md +72 -0
  18. crackerjack/adapters/format/__init__.py +11 -0
  19. crackerjack/adapters/format/mdformat.py +313 -0
  20. crackerjack/adapters/format/ruff.py +516 -0
  21. crackerjack/adapters/lint/README.md +47 -0
  22. crackerjack/adapters/lint/__init__.py +11 -0
  23. crackerjack/adapters/lint/codespell.py +273 -0
  24. crackerjack/adapters/lsp/README.md +49 -0
  25. crackerjack/adapters/lsp/__init__.py +27 -0
  26. crackerjack/adapters/{rust_tool_manager.py → lsp/_manager.py} +3 -3
  27. crackerjack/adapters/{skylos_adapter.py → lsp/skylos.py} +59 -7
  28. crackerjack/adapters/{zuban_adapter.py → lsp/zuban.py} +3 -6
  29. crackerjack/adapters/refactor/README.md +59 -0
  30. crackerjack/adapters/refactor/__init__.py +12 -0
  31. crackerjack/adapters/refactor/creosote.py +318 -0
  32. crackerjack/adapters/refactor/refurb.py +406 -0
  33. crackerjack/adapters/refactor/skylos.py +494 -0
  34. crackerjack/adapters/sast/README.md +132 -0
  35. crackerjack/adapters/sast/__init__.py +32 -0
  36. crackerjack/adapters/sast/_base.py +201 -0
  37. crackerjack/adapters/sast/bandit.py +423 -0
  38. crackerjack/adapters/sast/pyscn.py +405 -0
  39. crackerjack/adapters/sast/semgrep.py +241 -0
  40. crackerjack/adapters/security/README.md +111 -0
  41. crackerjack/adapters/security/__init__.py +17 -0
  42. crackerjack/adapters/security/gitleaks.py +339 -0
  43. crackerjack/adapters/type/README.md +52 -0
  44. crackerjack/adapters/type/__init__.py +12 -0
  45. crackerjack/adapters/type/pyrefly.py +402 -0
  46. crackerjack/adapters/type/ty.py +402 -0
  47. crackerjack/adapters/type/zuban.py +522 -0
  48. crackerjack/adapters/utility/README.md +51 -0
  49. crackerjack/adapters/utility/__init__.py +10 -0
  50. crackerjack/adapters/utility/checks.py +884 -0
  51. crackerjack/agents/README.md +264 -0
  52. crackerjack/agents/__init__.py +40 -12
  53. crackerjack/agents/base.py +1 -0
  54. crackerjack/agents/claude_code_bridge.py +641 -0
  55. crackerjack/agents/coordinator.py +49 -53
  56. crackerjack/agents/dry_agent.py +187 -3
  57. crackerjack/agents/enhanced_coordinator.py +279 -0
  58. crackerjack/agents/enhanced_proactive_agent.py +185 -0
  59. crackerjack/agents/error_middleware.py +53 -0
  60. crackerjack/agents/formatting_agent.py +6 -8
  61. crackerjack/agents/helpers/__init__.py +9 -0
  62. crackerjack/agents/helpers/performance/__init__.py +22 -0
  63. crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
  64. crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
  65. crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
  66. crackerjack/agents/helpers/refactoring/__init__.py +22 -0
  67. crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
  68. crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
  69. crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
  70. crackerjack/agents/helpers/test_creation/__init__.py +19 -0
  71. crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
  72. crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
  73. crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
  74. crackerjack/agents/performance_agent.py +121 -1152
  75. crackerjack/agents/refactoring_agent.py +156 -655
  76. crackerjack/agents/semantic_agent.py +479 -0
  77. crackerjack/agents/semantic_helpers.py +356 -0
  78. crackerjack/agents/test_creation_agent.py +19 -1605
  79. crackerjack/api.py +5 -7
  80. crackerjack/cli/README.md +394 -0
  81. crackerjack/cli/__init__.py +1 -1
  82. crackerjack/cli/cache_handlers.py +23 -18
  83. crackerjack/cli/cache_handlers_enhanced.py +1 -4
  84. crackerjack/cli/facade.py +70 -8
  85. crackerjack/cli/formatting.py +13 -0
  86. crackerjack/cli/handlers/__init__.py +85 -0
  87. crackerjack/cli/handlers/advanced.py +103 -0
  88. crackerjack/cli/handlers/ai_features.py +62 -0
  89. crackerjack/cli/handlers/analytics.py +479 -0
  90. crackerjack/cli/handlers/changelog.py +271 -0
  91. crackerjack/cli/handlers/config_handlers.py +16 -0
  92. crackerjack/cli/handlers/coverage.py +84 -0
  93. crackerjack/cli/handlers/documentation.py +280 -0
  94. crackerjack/cli/handlers/main_handlers.py +497 -0
  95. crackerjack/cli/handlers/monitoring.py +371 -0
  96. crackerjack/cli/handlers.py +249 -49
  97. crackerjack/cli/interactive.py +8 -5
  98. crackerjack/cli/options.py +203 -110
  99. crackerjack/cli/semantic_handlers.py +292 -0
  100. crackerjack/cli/version.py +19 -0
  101. crackerjack/code_cleaner.py +60 -24
  102. crackerjack/config/README.md +472 -0
  103. crackerjack/config/__init__.py +256 -0
  104. crackerjack/config/global_lock_config.py +191 -54
  105. crackerjack/config/hooks.py +188 -16
  106. crackerjack/config/loader.py +239 -0
  107. crackerjack/config/settings.py +141 -0
  108. crackerjack/config/tool_commands.py +331 -0
  109. crackerjack/core/README.md +393 -0
  110. crackerjack/core/async_workflow_orchestrator.py +79 -53
  111. crackerjack/core/autofix_coordinator.py +22 -9
  112. crackerjack/core/container.py +10 -9
  113. crackerjack/core/enhanced_container.py +9 -9
  114. crackerjack/core/performance.py +1 -1
  115. crackerjack/core/performance_monitor.py +5 -3
  116. crackerjack/core/phase_coordinator.py +1018 -634
  117. crackerjack/core/proactive_workflow.py +3 -3
  118. crackerjack/core/retry.py +275 -0
  119. crackerjack/core/service_watchdog.py +167 -23
  120. crackerjack/core/session_coordinator.py +187 -382
  121. crackerjack/core/timeout_manager.py +161 -44
  122. crackerjack/core/workflow/__init__.py +21 -0
  123. crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
  124. crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
  125. crackerjack/core/workflow/workflow_issue_parser.py +714 -0
  126. crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
  127. crackerjack/core/workflow/workflow_security_gates.py +400 -0
  128. crackerjack/core/workflow_orchestrator.py +1247 -953
  129. crackerjack/data/README.md +11 -0
  130. crackerjack/data/__init__.py +8 -0
  131. crackerjack/data/models.py +79 -0
  132. crackerjack/data/repository.py +210 -0
  133. crackerjack/decorators/README.md +180 -0
  134. crackerjack/decorators/__init__.py +35 -0
  135. crackerjack/decorators/error_handling.py +649 -0
  136. crackerjack/decorators/error_handling_decorators.py +334 -0
  137. crackerjack/decorators/helpers.py +58 -0
  138. crackerjack/decorators/patterns.py +281 -0
  139. crackerjack/decorators/utils.py +58 -0
  140. crackerjack/docs/README.md +11 -0
  141. crackerjack/docs/generated/api/CLI_REFERENCE.md +1 -1
  142. crackerjack/documentation/README.md +11 -0
  143. crackerjack/documentation/ai_templates.py +1 -1
  144. crackerjack/documentation/dual_output_generator.py +11 -9
  145. crackerjack/documentation/reference_generator.py +104 -59
  146. crackerjack/dynamic_config.py +52 -61
  147. crackerjack/errors.py +1 -1
  148. crackerjack/events/README.md +11 -0
  149. crackerjack/events/__init__.py +16 -0
  150. crackerjack/events/telemetry.py +175 -0
  151. crackerjack/events/workflow_bus.py +346 -0
  152. crackerjack/exceptions/README.md +301 -0
  153. crackerjack/exceptions/__init__.py +5 -0
  154. crackerjack/exceptions/config.py +4 -0
  155. crackerjack/exceptions/tool_execution_error.py +245 -0
  156. crackerjack/executors/README.md +591 -0
  157. crackerjack/executors/__init__.py +2 -0
  158. crackerjack/executors/async_hook_executor.py +539 -77
  159. crackerjack/executors/cached_hook_executor.py +3 -3
  160. crackerjack/executors/hook_executor.py +967 -102
  161. crackerjack/executors/hook_lock_manager.py +31 -22
  162. crackerjack/executors/individual_hook_executor.py +66 -32
  163. crackerjack/executors/lsp_aware_hook_executor.py +136 -57
  164. crackerjack/executors/progress_hook_executor.py +282 -0
  165. crackerjack/executors/tool_proxy.py +23 -7
  166. crackerjack/hooks/README.md +485 -0
  167. crackerjack/hooks/lsp_hook.py +8 -9
  168. crackerjack/intelligence/README.md +557 -0
  169. crackerjack/interactive.py +37 -10
  170. crackerjack/managers/README.md +369 -0
  171. crackerjack/managers/async_hook_manager.py +41 -57
  172. crackerjack/managers/hook_manager.py +449 -79
  173. crackerjack/managers/publish_manager.py +81 -36
  174. crackerjack/managers/test_command_builder.py +290 -12
  175. crackerjack/managers/test_executor.py +93 -8
  176. crackerjack/managers/test_manager.py +1082 -75
  177. crackerjack/managers/test_progress.py +118 -26
  178. crackerjack/mcp/README.md +374 -0
  179. crackerjack/mcp/cache.py +25 -2
  180. crackerjack/mcp/client_runner.py +35 -18
  181. crackerjack/mcp/context.py +9 -9
  182. crackerjack/mcp/dashboard.py +24 -8
  183. crackerjack/mcp/enhanced_progress_monitor.py +34 -23
  184. crackerjack/mcp/file_monitor.py +27 -6
  185. crackerjack/mcp/progress_components.py +45 -34
  186. crackerjack/mcp/progress_monitor.py +6 -9
  187. crackerjack/mcp/rate_limiter.py +11 -7
  188. crackerjack/mcp/server.py +2 -0
  189. crackerjack/mcp/server_core.py +187 -55
  190. crackerjack/mcp/service_watchdog.py +12 -9
  191. crackerjack/mcp/task_manager.py +2 -2
  192. crackerjack/mcp/tools/README.md +27 -0
  193. crackerjack/mcp/tools/__init__.py +2 -0
  194. crackerjack/mcp/tools/core_tools.py +75 -52
  195. crackerjack/mcp/tools/execution_tools.py +87 -31
  196. crackerjack/mcp/tools/intelligence_tools.py +2 -2
  197. crackerjack/mcp/tools/proactive_tools.py +1 -1
  198. crackerjack/mcp/tools/semantic_tools.py +584 -0
  199. crackerjack/mcp/tools/utility_tools.py +180 -132
  200. crackerjack/mcp/tools/workflow_executor.py +87 -46
  201. crackerjack/mcp/websocket/README.md +31 -0
  202. crackerjack/mcp/websocket/app.py +11 -1
  203. crackerjack/mcp/websocket/event_bridge.py +188 -0
  204. crackerjack/mcp/websocket/jobs.py +27 -4
  205. crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
  206. crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
  207. crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
  208. crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
  209. crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
  210. crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
  211. crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
  212. crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
  213. crackerjack/mcp/websocket/monitoring/factory.py +109 -0
  214. crackerjack/mcp/websocket/monitoring/filters.py +10 -0
  215. crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
  216. crackerjack/mcp/websocket/monitoring/models.py +90 -0
  217. crackerjack/mcp/websocket/monitoring/utils.py +171 -0
  218. crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
  219. crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
  220. crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
  221. crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
  222. crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
  223. crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
  224. crackerjack/mcp/websocket/monitoring_endpoints.py +16 -2930
  225. crackerjack/mcp/websocket/server.py +1 -3
  226. crackerjack/mcp/websocket/websocket_handler.py +107 -6
  227. crackerjack/models/README.md +308 -0
  228. crackerjack/models/__init__.py +10 -1
  229. crackerjack/models/config.py +639 -22
  230. crackerjack/models/config_adapter.py +6 -6
  231. crackerjack/models/protocols.py +1167 -23
  232. crackerjack/models/pydantic_models.py +320 -0
  233. crackerjack/models/qa_config.py +145 -0
  234. crackerjack/models/qa_results.py +134 -0
  235. crackerjack/models/results.py +35 -0
  236. crackerjack/models/semantic_models.py +258 -0
  237. crackerjack/models/task.py +19 -3
  238. crackerjack/models/test_models.py +60 -0
  239. crackerjack/monitoring/README.md +11 -0
  240. crackerjack/monitoring/ai_agent_watchdog.py +5 -4
  241. crackerjack/monitoring/metrics_collector.py +4 -3
  242. crackerjack/monitoring/regression_prevention.py +4 -3
  243. crackerjack/monitoring/websocket_server.py +4 -241
  244. crackerjack/orchestration/README.md +340 -0
  245. crackerjack/orchestration/__init__.py +43 -0
  246. crackerjack/orchestration/advanced_orchestrator.py +20 -67
  247. crackerjack/orchestration/cache/README.md +312 -0
  248. crackerjack/orchestration/cache/__init__.py +37 -0
  249. crackerjack/orchestration/cache/memory_cache.py +338 -0
  250. crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
  251. crackerjack/orchestration/config.py +297 -0
  252. crackerjack/orchestration/coverage_improvement.py +13 -6
  253. crackerjack/orchestration/execution_strategies.py +6 -6
  254. crackerjack/orchestration/hook_orchestrator.py +1398 -0
  255. crackerjack/orchestration/strategies/README.md +401 -0
  256. crackerjack/orchestration/strategies/__init__.py +39 -0
  257. crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
  258. crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
  259. crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
  260. crackerjack/orchestration/test_progress_streamer.py +1 -1
  261. crackerjack/plugins/README.md +11 -0
  262. crackerjack/plugins/hooks.py +3 -2
  263. crackerjack/plugins/loader.py +3 -3
  264. crackerjack/plugins/managers.py +1 -1
  265. crackerjack/py313.py +191 -0
  266. crackerjack/security/README.md +11 -0
  267. crackerjack/services/README.md +374 -0
  268. crackerjack/services/__init__.py +8 -21
  269. crackerjack/services/ai/README.md +295 -0
  270. crackerjack/services/ai/__init__.py +7 -0
  271. crackerjack/services/ai/advanced_optimizer.py +878 -0
  272. crackerjack/services/{contextual_ai_assistant.py → ai/contextual_ai_assistant.py} +5 -3
  273. crackerjack/services/ai/embeddings.py +444 -0
  274. crackerjack/services/ai/intelligent_commit.py +328 -0
  275. crackerjack/services/ai/predictive_analytics.py +510 -0
  276. crackerjack/services/api_extractor.py +5 -3
  277. crackerjack/services/bounded_status_operations.py +45 -5
  278. crackerjack/services/cache.py +249 -318
  279. crackerjack/services/changelog_automation.py +7 -3
  280. crackerjack/services/command_execution_service.py +305 -0
  281. crackerjack/services/config_integrity.py +83 -39
  282. crackerjack/services/config_merge.py +9 -6
  283. crackerjack/services/config_service.py +198 -0
  284. crackerjack/services/config_template.py +13 -26
  285. crackerjack/services/coverage_badge_service.py +6 -4
  286. crackerjack/services/coverage_ratchet.py +53 -27
  287. crackerjack/services/debug.py +18 -7
  288. crackerjack/services/dependency_analyzer.py +4 -4
  289. crackerjack/services/dependency_monitor.py +13 -13
  290. crackerjack/services/documentation_generator.py +4 -2
  291. crackerjack/services/documentation_service.py +62 -33
  292. crackerjack/services/enhanced_filesystem.py +81 -27
  293. crackerjack/services/enterprise_optimizer.py +1 -1
  294. crackerjack/services/error_pattern_analyzer.py +10 -10
  295. crackerjack/services/file_filter.py +221 -0
  296. crackerjack/services/file_hasher.py +5 -7
  297. crackerjack/services/file_io_service.py +361 -0
  298. crackerjack/services/file_modifier.py +615 -0
  299. crackerjack/services/filesystem.py +80 -109
  300. crackerjack/services/git.py +99 -5
  301. crackerjack/services/health_metrics.py +4 -6
  302. crackerjack/services/heatmap_generator.py +12 -3
  303. crackerjack/services/incremental_executor.py +380 -0
  304. crackerjack/services/initialization.py +101 -49
  305. crackerjack/services/log_manager.py +2 -2
  306. crackerjack/services/logging.py +120 -68
  307. crackerjack/services/lsp_client.py +12 -12
  308. crackerjack/services/memory_optimizer.py +27 -22
  309. crackerjack/services/monitoring/README.md +30 -0
  310. crackerjack/services/monitoring/__init__.py +9 -0
  311. crackerjack/services/monitoring/dependency_monitor.py +678 -0
  312. crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
  313. crackerjack/services/monitoring/health_metrics.py +716 -0
  314. crackerjack/services/monitoring/metrics.py +587 -0
  315. crackerjack/services/{performance_benchmarks.py → monitoring/performance_benchmarks.py} +100 -14
  316. crackerjack/services/{performance_cache.py → monitoring/performance_cache.py} +21 -15
  317. crackerjack/services/{performance_monitor.py → monitoring/performance_monitor.py} +10 -6
  318. crackerjack/services/parallel_executor.py +166 -55
  319. crackerjack/services/patterns/__init__.py +142 -0
  320. crackerjack/services/patterns/agents.py +107 -0
  321. crackerjack/services/patterns/code/__init__.py +15 -0
  322. crackerjack/services/patterns/code/detection.py +118 -0
  323. crackerjack/services/patterns/code/imports.py +107 -0
  324. crackerjack/services/patterns/code/paths.py +159 -0
  325. crackerjack/services/patterns/code/performance.py +119 -0
  326. crackerjack/services/patterns/code/replacement.py +36 -0
  327. crackerjack/services/patterns/core.py +212 -0
  328. crackerjack/services/patterns/documentation/__init__.py +14 -0
  329. crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
  330. crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
  331. crackerjack/services/patterns/documentation/docstrings.py +89 -0
  332. crackerjack/services/patterns/formatting.py +226 -0
  333. crackerjack/services/patterns/operations.py +339 -0
  334. crackerjack/services/patterns/security/__init__.py +23 -0
  335. crackerjack/services/patterns/security/code_injection.py +122 -0
  336. crackerjack/services/patterns/security/credentials.py +190 -0
  337. crackerjack/services/patterns/security/path_traversal.py +221 -0
  338. crackerjack/services/patterns/security/unsafe_operations.py +216 -0
  339. crackerjack/services/patterns/templates.py +62 -0
  340. crackerjack/services/patterns/testing/__init__.py +18 -0
  341. crackerjack/services/patterns/testing/error_patterns.py +107 -0
  342. crackerjack/services/patterns/testing/pytest_output.py +126 -0
  343. crackerjack/services/patterns/tool_output/__init__.py +16 -0
  344. crackerjack/services/patterns/tool_output/bandit.py +72 -0
  345. crackerjack/services/patterns/tool_output/other.py +97 -0
  346. crackerjack/services/patterns/tool_output/pyright.py +67 -0
  347. crackerjack/services/patterns/tool_output/ruff.py +44 -0
  348. crackerjack/services/patterns/url_sanitization.py +114 -0
  349. crackerjack/services/patterns/utilities.py +42 -0
  350. crackerjack/services/patterns/utils.py +339 -0
  351. crackerjack/services/patterns/validation.py +46 -0
  352. crackerjack/services/patterns/versioning.py +62 -0
  353. crackerjack/services/predictive_analytics.py +21 -8
  354. crackerjack/services/profiler.py +280 -0
  355. crackerjack/services/quality/README.md +415 -0
  356. crackerjack/services/quality/__init__.py +11 -0
  357. crackerjack/services/quality/anomaly_detector.py +392 -0
  358. crackerjack/services/quality/pattern_cache.py +333 -0
  359. crackerjack/services/quality/pattern_detector.py +479 -0
  360. crackerjack/services/quality/qa_orchestrator.py +491 -0
  361. crackerjack/services/{quality_baseline.py → quality/quality_baseline.py} +163 -2
  362. crackerjack/services/{quality_baseline_enhanced.py → quality/quality_baseline_enhanced.py} +4 -1
  363. crackerjack/services/{quality_intelligence.py → quality/quality_intelligence.py} +180 -16
  364. crackerjack/services/regex_patterns.py +58 -2987
  365. crackerjack/services/regex_utils.py +55 -29
  366. crackerjack/services/secure_status_formatter.py +42 -15
  367. crackerjack/services/secure_subprocess.py +35 -2
  368. crackerjack/services/security.py +16 -8
  369. crackerjack/services/server_manager.py +40 -51
  370. crackerjack/services/smart_scheduling.py +46 -6
  371. crackerjack/services/status_authentication.py +3 -3
  372. crackerjack/services/thread_safe_status_collector.py +1 -0
  373. crackerjack/services/tool_filter.py +368 -0
  374. crackerjack/services/tool_version_service.py +9 -5
  375. crackerjack/services/unified_config.py +43 -351
  376. crackerjack/services/vector_store.py +689 -0
  377. crackerjack/services/version_analyzer.py +6 -4
  378. crackerjack/services/version_checker.py +14 -8
  379. crackerjack/services/zuban_lsp_service.py +5 -4
  380. crackerjack/slash_commands/README.md +11 -0
  381. crackerjack/slash_commands/init.md +2 -12
  382. crackerjack/slash_commands/run.md +84 -50
  383. crackerjack/tools/README.md +11 -0
  384. crackerjack/tools/__init__.py +30 -0
  385. crackerjack/tools/_git_utils.py +105 -0
  386. crackerjack/tools/check_added_large_files.py +139 -0
  387. crackerjack/tools/check_ast.py +105 -0
  388. crackerjack/tools/check_json.py +103 -0
  389. crackerjack/tools/check_jsonschema.py +297 -0
  390. crackerjack/tools/check_toml.py +103 -0
  391. crackerjack/tools/check_yaml.py +110 -0
  392. crackerjack/tools/codespell_wrapper.py +72 -0
  393. crackerjack/tools/end_of_file_fixer.py +202 -0
  394. crackerjack/tools/format_json.py +128 -0
  395. crackerjack/tools/mdformat_wrapper.py +114 -0
  396. crackerjack/tools/trailing_whitespace.py +198 -0
  397. crackerjack/tools/validate_regex_patterns.py +7 -3
  398. crackerjack/ui/README.md +11 -0
  399. crackerjack/ui/dashboard_renderer.py +28 -0
  400. crackerjack/ui/templates/README.md +11 -0
  401. crackerjack/utils/console_utils.py +13 -0
  402. crackerjack/utils/dependency_guard.py +230 -0
  403. crackerjack/utils/retry_utils.py +275 -0
  404. crackerjack/workflows/README.md +590 -0
  405. crackerjack/workflows/__init__.py +46 -0
  406. crackerjack/workflows/actions.py +811 -0
  407. crackerjack/workflows/auto_fix.py +444 -0
  408. crackerjack/workflows/container_builder.py +499 -0
  409. crackerjack/workflows/definitions.py +443 -0
  410. crackerjack/workflows/engine.py +177 -0
  411. crackerjack/workflows/event_bridge.py +242 -0
  412. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/METADATA +678 -98
  413. crackerjack-0.45.2.dist-info/RECORD +478 -0
  414. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
  415. crackerjack/managers/test_manager_backup.py +0 -1075
  416. crackerjack/mcp/tools/execution_tools_backup.py +0 -1011
  417. crackerjack/mixins/__init__.py +0 -3
  418. crackerjack/mixins/error_handling.py +0 -145
  419. crackerjack/services/config.py +0 -358
  420. crackerjack/ui/server_panels.py +0 -125
  421. crackerjack-0.37.9.dist-info/RECORD +0 -231
  422. /crackerjack/adapters/{rust_tool_adapter.py → lsp/_base.py} +0 -0
  423. /crackerjack/adapters/{lsp_client.py → lsp/_client.py} +0 -0
  424. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/entry_points.txt +0 -0
  425. {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,678 @@
1
+ import asyncio
2
+ import json
3
+ import subprocess
4
+ import tempfile
5
+ import time
6
+ import tomllib
7
+ import typing as t
8
+ from contextlib import suppress
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from urllib.parse import urlparse
12
+
13
+ import requests
14
+ from acb.console import Console
15
+ from acb.depends import Inject, depends
16
+
17
+ from crackerjack.data.repository import DependencyMonitorRepository
18
+ from crackerjack.models.protocols import FileSystemInterface
19
+
20
+
21
+ @dataclass
22
+ class DependencyVulnerability:
23
+ package: str
24
+ installed_version: str
25
+ vulnerability_id: str
26
+ severity: str
27
+ advisory_url: str
28
+ vulnerable_versions: str
29
+ patched_version: str
30
+
31
+
32
+ @dataclass
33
+ class MajorUpdate:
34
+ package: str
35
+ current_version: str
36
+ latest_version: str
37
+ release_date: str
38
+ breaking_changes: bool
39
+
40
+
41
+ class DependencyMonitorService:
42
+ @depends.inject
43
+ def __init__(
44
+ self,
45
+ filesystem: FileSystemInterface,
46
+ console: Inject[Console],
47
+ ) -> None:
48
+ self.filesystem = filesystem
49
+ self.console = console
50
+ self.project_root = Path.cwd()
51
+ self.pyproject_path = self.project_root / "pyproject.toml"
52
+ self._legacy_cache_file = (
53
+ self.project_root / ".crackerjack" / "dependency_cache.json"
54
+ )
55
+ try:
56
+ self._repository: DependencyMonitorRepository | None = depends.get_sync(
57
+ DependencyMonitorRepository
58
+ )
59
+ except Exception:
60
+ self._repository = None
61
+
62
+ def _run_async(self, coro: t.Awaitable[t.Any]) -> t.Any:
63
+ try:
64
+ asyncio.get_running_loop()
65
+ except RuntimeError:
66
+ return asyncio.run(coro)
67
+ msg = (
68
+ "DependencyMonitorService synchronous method called while an event loop is running."
69
+ " Use async variants instead."
70
+ )
71
+ raise RuntimeError(msg)
72
+
73
+ def check_dependency_updates(self) -> bool:
74
+ if not self.pyproject_path.exists():
75
+ return False
76
+
77
+ dependencies = self._parse_dependencies()
78
+ if not dependencies:
79
+ return False
80
+
81
+ vulnerabilities = self._check_security_vulnerabilities(dependencies)
82
+ major_updates = self._check_major_updates(dependencies)
83
+
84
+ if vulnerabilities:
85
+ self._report_vulnerabilities(vulnerabilities)
86
+ return True
87
+
88
+ if major_updates and self._should_notify_major_updates():
89
+ self._report_major_updates(major_updates)
90
+ return True
91
+
92
+ return False
93
+
94
+ def _parse_dependencies(self) -> dict[str, str]:
95
+ try:
96
+ with self.pyproject_path.open("rb") as f:
97
+ data = tomllib.load(f)
98
+
99
+ dependencies: dict[str, str] = {}
100
+ project_data = data.get("project", {})
101
+
102
+ self._extract_main_dependencies(project_data, dependencies)
103
+ self._extract_optional_dependencies(project_data, dependencies)
104
+
105
+ return dependencies
106
+
107
+ except Exception as e:
108
+ self.console.print(
109
+ f"[yellow]Warning: Failed to parse pyproject.toml: {e}[/ yellow]",
110
+ )
111
+ return {}
112
+
113
+ def _extract_main_dependencies(
114
+ self,
115
+ project_data: dict[str, t.Any],
116
+ dependencies: dict[str, str],
117
+ ) -> None:
118
+ if "dependencies" not in project_data:
119
+ return
120
+
121
+ for dep in project_data["dependencies"]:
122
+ name, version = self._parse_dependency_spec(dep)
123
+ if name and version:
124
+ dependencies[name] = version
125
+
126
+ def _extract_optional_dependencies(
127
+ self,
128
+ project_data: dict[str, t.Any],
129
+ dependencies: dict[str, str],
130
+ ) -> None:
131
+ if "optional-dependencies" not in project_data:
132
+ return
133
+
134
+ for group_deps in project_data["optional-dependencies"].values():
135
+ for dep in group_deps:
136
+ name, version = self._parse_dependency_spec(dep)
137
+ if name and version:
138
+ dependencies[name] = version
139
+
140
+ def _parse_dependency_spec(self, spec: str) -> tuple[str | None, str | None]:
141
+ if not spec or spec.startswith("-"):
142
+ return None, None
143
+
144
+ for operator in ("> =", "< =", "= =", "~=", "! =", ">", "<"):
145
+ if operator in spec:
146
+ parts = spec.split(operator, 1)
147
+ if len(parts) == 2:
148
+ package = parts[0].strip()
149
+ version = parts[1].strip()
150
+ return package, version
151
+
152
+ return spec.strip(), "latest"
153
+
154
+ def _check_security_vulnerabilities(
155
+ self,
156
+ dependencies: dict[str, str],
157
+ ) -> list[DependencyVulnerability]:
158
+ vulnerabilities: list[DependencyVulnerability] = []
159
+
160
+ safety_vulns = self._check_with_safety(dependencies)
161
+ vulnerabilities.extend(safety_vulns)
162
+
163
+ if not vulnerabilities:
164
+ pip_audit_vulns = self._check_with_pip_audit(dependencies)
165
+ vulnerabilities.extend(pip_audit_vulns)
166
+
167
+ return vulnerabilities
168
+
169
+ def _check_with_safety(
170
+ self,
171
+ dependencies: dict[str, str],
172
+ ) -> list[DependencyVulnerability]:
173
+ cmd = ["uv", "run", "safety", "check", "--file", "__TEMP_FILE__", "--json"]
174
+ return self._run_vulnerability_tool(
175
+ dependencies,
176
+ cmd,
177
+ self._parse_safety_output,
178
+ )
179
+
180
+ def _check_with_pip_audit(
181
+ self,
182
+ dependencies: dict[str, str],
183
+ ) -> list[DependencyVulnerability]:
184
+ cmd = [
185
+ "uv",
186
+ "run",
187
+ "pip-audit",
188
+ "--requirement",
189
+ "__TEMP_FILE__",
190
+ "--format",
191
+ "json",
192
+ ]
193
+ return self._run_vulnerability_tool(
194
+ dependencies,
195
+ cmd,
196
+ self._parse_pip_audit_output,
197
+ )
198
+
199
+ def _run_vulnerability_tool(
200
+ self,
201
+ dependencies: dict[str, str],
202
+ command_template: list[str],
203
+ parser_func: t.Callable[[t.Any], list[DependencyVulnerability]],
204
+ ) -> list[DependencyVulnerability]:
205
+ try:
206
+ temp_file = self._create_requirements_file(dependencies)
207
+ try:
208
+ result = self._execute_vulnerability_command(
209
+ command_template,
210
+ temp_file,
211
+ )
212
+ return self._process_vulnerability_result(result, parser_func)
213
+ finally:
214
+ Path(temp_file).unlink(missing_ok=True)
215
+ except (
216
+ subprocess.CalledProcessError,
217
+ subprocess.TimeoutExpired,
218
+ json.JSONDecodeError,
219
+ Exception,
220
+ ):
221
+ return []
222
+
223
+ def _create_requirements_file(self, dependencies: dict[str, str]) -> str:
224
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
225
+ for package, version in dependencies.items():
226
+ if version != "latest":
227
+ f.write(f"{package}= ={version}\n")
228
+ else:
229
+ f.write(f"{package}\n")
230
+ return f.name
231
+
232
+ def _execute_vulnerability_command(
233
+ self,
234
+ command_template: list[str],
235
+ temp_file: str,
236
+ ) -> subprocess.CompletedProcess[str]:
237
+ cmd = [part.replace("__TEMP_FILE__", temp_file) for part in command_template]
238
+ return subprocess.run(
239
+ cmd,
240
+ check=False,
241
+ capture_output=True,
242
+ text=True,
243
+ timeout=30,
244
+ )
245
+
246
+ def _process_vulnerability_result(
247
+ self,
248
+ result: subprocess.CompletedProcess[str],
249
+ parser_func: t.Callable[[t.Any], list[DependencyVulnerability]],
250
+ ) -> list[DependencyVulnerability]:
251
+ if result.returncode == 0:
252
+ return []
253
+
254
+ if result.stdout:
255
+ data = json.loads(result.stdout)
256
+ return parser_func(data)
257
+
258
+ return []
259
+
260
+ def _parse_safety_output(self, safety_data: t.Any) -> list[DependencyVulnerability]:
261
+ vulnerabilities: list[DependencyVulnerability] = []
262
+
263
+ with suppress(Exception):
264
+ for vuln in safety_data:
265
+ vulnerabilities.append(
266
+ DependencyVulnerability(
267
+ package=vuln.get("package", ""),
268
+ installed_version=vuln.get("installed_version", ""),
269
+ vulnerability_id=vuln.get("vulnerability_id", ""),
270
+ severity=vuln.get("severity", "unknown"),
271
+ advisory_url=vuln.get("more_info_url", ""),
272
+ vulnerable_versions=vuln.get("vulnerable_spec", ""),
273
+ patched_version=vuln.get("analyzed_version", ""),
274
+ ),
275
+ )
276
+
277
+ return vulnerabilities
278
+
279
+ def _parse_pip_audit_output(
280
+ self,
281
+ audit_data: t.Any,
282
+ ) -> list[DependencyVulnerability]:
283
+ vulnerabilities: list[DependencyVulnerability] = []
284
+
285
+ with suppress(Exception):
286
+ for vuln in audit_data.get("vulnerabilities", []):
287
+ package = vuln.get("package", {})
288
+ vulnerabilities.append(
289
+ DependencyVulnerability(
290
+ package=package.get("name", ""),
291
+ installed_version=package.get("version", ""),
292
+ vulnerability_id=vuln.get("id", ""),
293
+ severity=vuln.get("severity", "unknown"),
294
+ advisory_url=vuln.get("link", ""),
295
+ vulnerable_versions=vuln.get("vulnerable_ranges", ""),
296
+ patched_version=vuln.get("fix_versions", [""])[0],
297
+ ),
298
+ )
299
+
300
+ return vulnerabilities
301
+
302
+ def _check_major_updates(self, dependencies: dict[str, str]) -> list[MajorUpdate]:
303
+ major_updates: list[MajorUpdate] = []
304
+ cache = self._load_update_cache()
305
+ current_time = time.time()
306
+
307
+ for package, current_version in dependencies.items():
308
+ if current_version == "latest":
309
+ continue
310
+
311
+ update = self._check_package_major_update(
312
+ package,
313
+ current_version,
314
+ cache,
315
+ current_time,
316
+ )
317
+ if update:
318
+ major_updates.append(update)
319
+
320
+ self._save_update_cache(cache)
321
+ return major_updates
322
+
323
+ def _check_package_major_update(
324
+ self,
325
+ package: str,
326
+ current_version: str,
327
+ cache: dict[str, t.Any],
328
+ current_time: float,
329
+ ) -> MajorUpdate | None:
330
+ cache_key = self._build_cache_key(package, current_version)
331
+
332
+ cached_update = self._get_cached_major_update(
333
+ cache_key,
334
+ cache,
335
+ current_time,
336
+ package,
337
+ current_version,
338
+ )
339
+ if cached_update is not None:
340
+ return cached_update
341
+
342
+ return self._fetch_and_cache_update_info(
343
+ package,
344
+ current_version,
345
+ cache_key,
346
+ cache,
347
+ current_time,
348
+ )
349
+
350
+ def _build_cache_key(self, package: str, current_version: str) -> str:
351
+ return f"{package}_{current_version}"
352
+
353
+ def _get_cached_major_update(
354
+ self,
355
+ cache_key: str,
356
+ cache: dict[str, t.Any],
357
+ current_time: float,
358
+ package: str,
359
+ current_version: str,
360
+ ) -> MajorUpdate | None:
361
+ if not self._is_cache_entry_valid(cache_key, cache, current_time):
362
+ return None
363
+
364
+ cached_data = cache[cache_key]
365
+ if not cached_data["has_major_update"]:
366
+ return None
367
+
368
+ return self._create_major_update_from_cache(
369
+ package,
370
+ current_version,
371
+ cached_data,
372
+ )
373
+
374
+ def _is_cache_entry_valid(
375
+ self,
376
+ cache_key: str,
377
+ cache: dict[str, t.Any],
378
+ current_time: float,
379
+ ) -> bool:
380
+ if cache_key not in cache:
381
+ return False
382
+
383
+ cached_data = cache[cache_key]
384
+ cache_age = current_time - cached_data["timestamp"]
385
+ is_fresh: bool = cache_age < 86400
386
+ return is_fresh
387
+
388
+ def _create_major_update_from_cache(
389
+ self,
390
+ package: str,
391
+ current_version: str,
392
+ cached_data: dict[str, t.Any],
393
+ ) -> MajorUpdate:
394
+ return MajorUpdate(
395
+ package=package,
396
+ current_version=current_version,
397
+ latest_version=cached_data["latest_version"],
398
+ release_date=cached_data["release_date"],
399
+ breaking_changes=cached_data["breaking_changes"],
400
+ )
401
+
402
+ def _fetch_and_cache_update_info(
403
+ self,
404
+ package: str,
405
+ current_version: str,
406
+ cache_key: str,
407
+ cache: dict[str, t.Any],
408
+ current_time: float,
409
+ ) -> MajorUpdate | None:
410
+ latest_info = self._get_latest_version_info(package)
411
+ if not latest_info:
412
+ return None
413
+
414
+ has_major_update = self._is_major_version_update(
415
+ current_version,
416
+ latest_info["version"],
417
+ )
418
+
419
+ self._update_cache_entry(
420
+ cache,
421
+ cache_key,
422
+ current_time,
423
+ has_major_update,
424
+ latest_info,
425
+ )
426
+
427
+ return self._create_major_update_if_needed(
428
+ package,
429
+ current_version,
430
+ latest_info,
431
+ has_major_update,
432
+ )
433
+
434
+ def _create_major_update_if_needed(
435
+ self,
436
+ package: str,
437
+ current_version: str,
438
+ latest_info: dict[str, t.Any],
439
+ has_major_update: bool,
440
+ ) -> MajorUpdate | None:
441
+ if not has_major_update:
442
+ return None
443
+
444
+ return MajorUpdate(
445
+ package=package,
446
+ current_version=current_version,
447
+ latest_version=latest_info["version"],
448
+ release_date=latest_info["release_date"],
449
+ breaking_changes=latest_info["breaking_changes"],
450
+ )
451
+
452
+ def _update_cache_entry(
453
+ self,
454
+ cache: dict[str, t.Any],
455
+ cache_key: str,
456
+ current_time: float,
457
+ has_major_update: bool,
458
+ latest_info: dict[str, t.Any],
459
+ ) -> None:
460
+ cache[cache_key] = {
461
+ "timestamp": current_time,
462
+ "has_major_update": has_major_update,
463
+ "latest_version": latest_info["version"],
464
+ "release_date": latest_info["release_date"],
465
+ "breaking_changes": latest_info["breaking_changes"],
466
+ }
467
+
468
+ def _get_latest_version_info(self, package: str) -> dict[str, t.Any] | None:
469
+ try:
470
+ data = self._fetch_pypi_data(package)
471
+ return self._extract_version_info(data)
472
+ except Exception:
473
+ return None
474
+
475
+ def _fetch_pypi_data(self, package: str) -> dict[str, t.Any]:
476
+ url = f"https://pypi.org/pypi/{package}/json"
477
+ self._validate_pypi_url(url)
478
+
479
+ parsed = urlparse(url)
480
+
481
+ if parsed.scheme != "https" or parsed.netloc != "pypi.org":
482
+ msg = f"Invalid URL: only https://pypi.org URLs are allowed, got {url}"
483
+ raise ValueError(msg)
484
+
485
+ response = requests.get(url, timeout=10, verify=True)
486
+ response.raise_for_status()
487
+ return t.cast(dict[str, t.Any], response.json())
488
+
489
+ def _validate_pypi_url(self, url: str) -> None:
490
+ parsed = urlparse(url)
491
+
492
+ if parsed.scheme != "https":
493
+ msg = f"Invalid URL scheme '{parsed.scheme}': only HTTPS is allowed"
494
+ raise ValueError(msg)
495
+
496
+ if parsed.netloc != "pypi.org":
497
+ msg = f"Invalid hostname '{parsed.netloc}': only pypi.org is allowed"
498
+ raise ValueError(msg)
499
+
500
+ if not parsed.path.startswith("/pypi/") or not parsed.path.endswith("/json"):
501
+ msg = f"Invalid PyPI API path: {parsed.path}"
502
+ raise ValueError(msg)
503
+
504
+ def _extract_version_info(self, data: dict[str, t.Any]) -> dict[str, t.Any] | None:
505
+ info = data.get("info", {})
506
+ releases = data.get("releases", {})
507
+
508
+ latest_version = info.get("version", "")
509
+ if not latest_version:
510
+ return None
511
+
512
+ release_date = self._get_release_date(releases, latest_version)
513
+ breaking_changes = self._has_breaking_changes(latest_version)
514
+
515
+ return {
516
+ "version": latest_version,
517
+ "release_date": release_date,
518
+ "breaking_changes": breaking_changes,
519
+ }
520
+
521
+ def _get_release_date(self, releases: dict[str, t.Any], version: str) -> str:
522
+ release_info = releases.get(version, [])
523
+ if release_info:
524
+ upload_time: str = release_info[0].get("upload_time", "")
525
+ return upload_time
526
+ return ""
527
+
528
+ def _has_breaking_changes(self, version: str) -> bool:
529
+ return version.split(".")[0] != "0" if "." in version else False
530
+
531
+ def _is_major_version_update(self, current: str, latest: str) -> bool:
532
+ with suppress(ValueError, IndexError):
533
+ current_parts = current.split(".")
534
+ latest_parts = latest.split(".")
535
+
536
+ if current_parts and latest_parts:
537
+ current_major = int(current_parts[0])
538
+ latest_major = int(latest_parts[0])
539
+ return latest_major > current_major
540
+
541
+ return False
542
+
543
+ def _should_notify_major_updates(self) -> bool:
544
+ cache = self._load_update_cache()
545
+ last_major_notification = cache.get("last_major_notification", 0)
546
+ current_time = time.time()
547
+
548
+ if current_time - last_major_notification > 604800:
549
+ cache["last_major_notification"] = current_time
550
+ self._save_update_cache(cache)
551
+ return True
552
+
553
+ return False
554
+
555
+ def _load_update_cache(self) -> dict[str, t.Any]:
556
+ if self._repository:
557
+ try:
558
+ return self._run_async(self._aload_update_cache())
559
+ except RuntimeError:
560
+ raise
561
+ return self._load_legacy_cache()
562
+
563
+ async def _aload_update_cache(self) -> dict[str, t.Any]:
564
+ if not self._repository:
565
+ return self._load_legacy_cache()
566
+
567
+ record = await self._repository.get(str(self.project_root))
568
+ if record:
569
+ return dict(record.cache_data or {})
570
+ legacy = self._load_legacy_cache()
571
+ if legacy:
572
+ await self._repository.upsert(str(self.project_root), legacy)
573
+ return legacy
574
+
575
+ def _save_update_cache(self, cache: dict[str, t.Any]) -> None:
576
+ if self._repository:
577
+ try:
578
+ self._run_async(self._asave_update_cache(cache))
579
+ return
580
+ except RuntimeError:
581
+ raise
582
+ except Exception:
583
+ pass
584
+ self._save_legacy_cache(cache)
585
+
586
+ async def _asave_update_cache(self, cache: dict[str, t.Any]) -> None:
587
+ if not self._repository:
588
+ self._save_legacy_cache(cache)
589
+ return
590
+ await self._repository.upsert(str(self.project_root), cache)
591
+
592
+ def _load_legacy_cache(self) -> dict[str, t.Any]:
593
+ with suppress(Exception):
594
+ if self._legacy_cache_file.exists():
595
+ with self._legacy_cache_file.open() as f:
596
+ return t.cast(dict[str, t.Any], json.load(f))
597
+ return {}
598
+
599
+ def _save_legacy_cache(self, cache: dict[str, t.Any]) -> None:
600
+ with suppress(Exception):
601
+ self._legacy_cache_file.parent.mkdir(exist_ok=True)
602
+ with self._legacy_cache_file.open("w") as f:
603
+ json.dump(cache, f, indent=2)
604
+
605
+ def _report_vulnerabilities(
606
+ self,
607
+ vulnerabilities: list[DependencyVulnerability],
608
+ ) -> None:
609
+ self.console.print(
610
+ "\n[bold red]🚨 Security Vulnerabilities Found ![/ bold red]"
611
+ )
612
+ self.console.print(
613
+ "[red]Please update the following packages immediately: [/ red]\n",
614
+ )
615
+
616
+ for vuln in vulnerabilities:
617
+ self.console.print(f"[red]• {vuln.package} {vuln.installed_version}[/ red]")
618
+ self.console.print(
619
+ f" [dim]Vulnerability ID: {vuln.vulnerability_id}[/ dim]"
620
+ )
621
+ self.console.print(f" [dim]Severity: {vuln.severity.upper()}[/ dim]")
622
+ if vuln.patched_version:
623
+ self.console.print(
624
+ f" [green]Fix available: {vuln.patched_version}[/ green]",
625
+ )
626
+ if vuln.advisory_url:
627
+ self.console.print(f" [dim]More info: {vuln.advisory_url}[/ dim]")
628
+ self.console.print()
629
+
630
+ def _report_major_updates(self, major_updates: list[MajorUpdate]) -> None:
631
+ self.console.print(
632
+ "\n[bold yellow]📦 Major Version Updates Available[/ bold yellow]",
633
+ )
634
+ self.console.print(
635
+ "[yellow]The following packages have major updates: [/ yellow]\n",
636
+ )
637
+
638
+ for update in major_updates:
639
+ self.console.print(f"[yellow]• {update.package}[/ yellow]")
640
+ self.console.print(f" [dim]Current: {update.current_version}[/ dim]")
641
+ self.console.print(f" [dim]Latest: {update.latest_version}[/ dim]")
642
+ if update.release_date:
643
+ release_date = update.release_date[:10]
644
+ self.console.print(f" [dim]Released: {release_date}[/ dim]")
645
+ if update.breaking_changes:
646
+ self.console.print(" [red]⚠️ May contain breaking changes[/ red]")
647
+ self.console.print()
648
+
649
+ self.console.print(
650
+ "[dim]Review changelogs before updating to major versions.[/ dim]",
651
+ )
652
+
653
+ def force_check_updates(
654
+ self,
655
+ ) -> tuple[list[DependencyVulnerability], list[MajorUpdate]]:
656
+ if not self.pyproject_path.exists():
657
+ self.console.print("[yellow]⚠️ No pyproject.toml found[/ yellow]")
658
+ return [], []
659
+
660
+ self.console.print("[dim]Parsing dependencies from pyproject.toml...[/ dim]")
661
+ dependencies = self._parse_dependencies()
662
+ if not dependencies:
663
+ self.console.print(
664
+ "[yellow]⚠️ No dependencies found in pyproject.toml[/ yellow]",
665
+ )
666
+ return [], []
667
+
668
+ self.console.print(
669
+ f"[dim]Found {len(dependencies)} dependencies to check[/ dim]",
670
+ )
671
+
672
+ self.console.print("[dim]Checking for security vulnerabilities...[/ dim]")
673
+ vulnerabilities = self._check_security_vulnerabilities(dependencies)
674
+
675
+ self.console.print("[dim]Checking for major version updates...[/ dim]")
676
+ major_updates = self._check_major_updates(dependencies)
677
+
678
+ return vulnerabilities, major_updates