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,649 @@
1
+ """Core error handling decorators."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import errno
7
+ import inspect
8
+ import sys
9
+ import time
10
+ import typing as t
11
+ from concurrent.futures import ThreadPoolExecutor
12
+ from concurrent.futures import TimeoutError as FutureTimeoutError
13
+ from contextlib import suppress
14
+ from functools import wraps
15
+
16
+ from acb.console import Console
17
+ from acb.depends import depends
18
+
19
+ from ..errors import (
20
+ CrackerjackError,
21
+ ValidationError,
22
+ )
23
+ from ..errors import (
24
+ TimeoutError as CrackerjackTimeoutError,
25
+ )
26
+ from .helpers import format_exception_chain, get_function_context, is_async_function
27
+
28
+
29
+ def _is_would_block_error(e: Exception) -> bool:
30
+ """Check if exception is a would-block error (EAGAIN/EWOULDBLOCK)."""
31
+ err_no = getattr(e, "errno", None)
32
+ if err_no is not None:
33
+ return err_no in {errno.EAGAIN, errno.EWOULDBLOCK}
34
+ return isinstance(e, BlockingIOError)
35
+
36
+
37
+ def _fallback_stderr_write(message: str, include_traceback: bool) -> None:
38
+ """Write to stderr as final fallback, suppressing all errors."""
39
+ with suppress(Exception):
40
+ err = sys.__stderr__
41
+ if err is not None:
42
+ err.write(message + "\n")
43
+ if include_traceback:
44
+ err.write("(traceback suppressed due to I/O constraints)\n")
45
+ err.flush()
46
+
47
+
48
+ def _safe_console_print(
49
+ console: Console,
50
+ message: str,
51
+ *,
52
+ include_traceback: bool = False,
53
+ retries: int = 3,
54
+ retry_delay: float = 0.05,
55
+ ) -> None:
56
+ """Safely print to a Rich Console, tolerating non-blocking streams.
57
+
58
+ Some environments set stdout/stderr to non-blocking (e.g., PTY pipes),
59
+ which can raise BlockingIOError (EWOULDBLOCK/EAGAIN). This helper retries
60
+ briefly and then degrades silently if output still cannot be written.
61
+ """
62
+ for attempt in range(retries + 1):
63
+ try:
64
+ console.print(message)
65
+ if include_traceback:
66
+ console.print_exception()
67
+ return
68
+ except (BlockingIOError, BrokenPipeError, OSError) as e: # pragma: no cover
69
+ if _is_would_block_error(e) and attempt < retries:
70
+ time.sleep(retry_delay)
71
+ continue
72
+ _fallback_stderr_write(message, include_traceback)
73
+ return
74
+
75
+
76
+ def _handle_exception(
77
+ e: Exception,
78
+ func: t.Callable[..., t.Any],
79
+ transform_to: type[CrackerjackError] | None,
80
+ fallback: t.Any,
81
+ suppress: bool,
82
+ console: Console,
83
+ ) -> t.Any:
84
+ """Helper to handle exception with transformation and fallback logic."""
85
+ context = get_function_context(func)
86
+
87
+ # Log error with context
88
+ _safe_console_print(
89
+ console,
90
+ f"[red]❌ Error in {context['function_name']}: {type(e).__name__}: {e}[/red]",
91
+ )
92
+
93
+ # Transform to CrackerjackError if requested
94
+ if transform_to:
95
+ transformed = transform_to( # type: ignore[call-arg]
96
+ message=str(e),
97
+ details={
98
+ "original_error": type(e).__name__,
99
+ "function": context["function_name"],
100
+ "module": context["module"],
101
+ },
102
+ )
103
+ if not suppress:
104
+ raise transformed from e
105
+
106
+ # Use fallback if provided
107
+ if fallback is not None:
108
+ return fallback() if callable(fallback) else fallback
109
+
110
+ # Re-raise if not suppressed and no transform
111
+ if not suppress:
112
+ raise
113
+
114
+ return None
115
+
116
+
117
+ def handle_errors(
118
+ func: t.Callable[..., t.Any] | None = None,
119
+ *,
120
+ error_types: list[type[Exception]] | None = None,
121
+ fallback: t.Any = None,
122
+ transform_to: type[CrackerjackError] | None = None,
123
+ console: Console | None = None,
124
+ suppress: bool = False,
125
+ ) -> (
126
+ t.Callable[..., t.Any]
127
+ | t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]
128
+ ):
129
+ """
130
+ Centralized error handling with transformation and fallback support.
131
+
132
+ Args:
133
+ error_types: List of exception types to handle (None = all exceptions)
134
+ fallback: Fallback value or callable to return on error
135
+ transform_to: Transform caught exceptions to this CrackerjackError type
136
+ console: Optional Rich Console for error output
137
+ suppress: If True, suppress errors and use fallback (no re-raise)
138
+
139
+ Returns:
140
+ Decorated function with error handling
141
+
142
+ Example:
143
+ >>> from crackerjack.errors import FileError, ExecutionError
144
+ >>>
145
+ >>> @handle_errors(
146
+ ... error_types=[FileNotFoundError, PermissionError],
147
+ ... transform_to=FileError,
148
+ ... fallback={}
149
+ ... )
150
+ >>> def load_config(path: str) -> dict:
151
+ ... with open(path) as f:
152
+ ... return json.load(f)
153
+
154
+ >>> @handle_errors(fallback=lambda: [], suppress=True)
155
+ >>> def get_optional_data() -> list[str]:
156
+ ... # Errors suppressed, returns []
157
+ ... return fetch_data()
158
+
159
+ Notes:
160
+ - If transform_to is set, exceptions are wrapped in CrackerjackError
161
+ - Fallback can be a value or callable
162
+ - With suppress=True, errors are logged but not raised
163
+ - Integrates with Rich console for beautiful output
164
+ """
165
+ _console = console or depends.get_sync(Console)
166
+ _error_types = tuple(error_types) if error_types else (Exception,)
167
+
168
+ def decorator(inner_func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
169
+ target = inner_func
170
+ if is_async_function(inner_func):
171
+
172
+ @wraps(inner_func)
173
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
174
+ try:
175
+ return await target(*args, **kwargs)
176
+ except _error_types as e:
177
+ return _handle_exception(
178
+ e, target, transform_to, fallback, suppress, _console
179
+ )
180
+
181
+ return async_wrapper
182
+
183
+ else:
184
+
185
+ @wraps(inner_func)
186
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
187
+ try:
188
+ return target(*args, **kwargs)
189
+ except _error_types as e:
190
+ return _handle_exception(
191
+ e, target, transform_to, fallback, suppress, _console
192
+ )
193
+
194
+ return sync_wrapper
195
+
196
+ if func is not None and callable(func):
197
+ return decorator(func)
198
+
199
+ return decorator
200
+
201
+
202
+ def log_errors(
203
+ logger: t.Any | None = None,
204
+ level: str = "error",
205
+ include_traceback: bool = True,
206
+ console: Console | None = None,
207
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
208
+ """
209
+ Log errors with context before re-raising.
210
+
211
+ Args:
212
+ logger: Logger instance (uses print if None)
213
+ level: Log level (error, warning, info, debug)
214
+ include_traceback: Include full exception traceback
215
+ console: Optional Rich Console
216
+
217
+ Returns:
218
+ Decorated function with error logging
219
+
220
+ Example:
221
+ >>> import logging
222
+ >>> logger = logging.getLogger(__name__)
223
+ >>>
224
+ >>> @log_errors(logger=logger, level="error")
225
+ >>> async def critical_operation() -> bool:
226
+ ... # Errors are logged before re-raising
227
+ ... return await perform_operation()
228
+
229
+ Notes:
230
+ - Does not suppress errors, only logs them
231
+ - Includes function context in logs
232
+ - Supports structured logging if logger supports it
233
+ """
234
+ _console = console or depends.get_sync(Console)
235
+
236
+ def _log_exception(
237
+ func: t.Callable[..., t.Any],
238
+ exception: Exception,
239
+ ) -> None:
240
+ """Log exception with function context."""
241
+ context = get_function_context(func)
242
+ error_chain = format_exception_chain(exception)
243
+
244
+ if logger:
245
+ log_method = getattr(logger, level, logger.error)
246
+ log_method(
247
+ f"Error in {context['function_name']}",
248
+ exc_info=include_traceback,
249
+ extra={
250
+ "function": context["function_name"],
251
+ "module": context["module"],
252
+ "error_type": type(exception).__name__,
253
+ "error_chain": error_chain,
254
+ },
255
+ )
256
+ else:
257
+ _safe_console_print(
258
+ _console,
259
+ f"[red]Error in {context['function_name']}: "
260
+ f"{type(exception).__name__}: {exception}[/red]",
261
+ include_traceback=include_traceback,
262
+ )
263
+
264
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
265
+ if is_async_function(func):
266
+
267
+ @wraps(func)
268
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
269
+ try:
270
+ return await func(*args, **kwargs)
271
+ except Exception as e:
272
+ _log_exception(func, e)
273
+ raise
274
+
275
+ return async_wrapper
276
+
277
+ else:
278
+
279
+ @wraps(func)
280
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
281
+ try:
282
+ return func(*args, **kwargs)
283
+ except Exception as e:
284
+ _log_exception(func, e)
285
+ raise
286
+
287
+ return sync_wrapper
288
+
289
+ return decorator
290
+
291
+
292
+ def _calculate_retry_delay(attempt: int, backoff: float) -> float:
293
+ """Calculate delay for retry attempt with linear backoff."""
294
+ return backoff * attempt if backoff > 0 else 0.0
295
+
296
+
297
+ def _create_async_retry_wrapper(
298
+ func: t.Callable[..., t.Any],
299
+ max_attempts: int,
300
+ retry_exceptions: tuple[type[Exception], ...],
301
+ backoff: float,
302
+ ) -> t.Callable[..., t.Any]:
303
+ """Create async wrapper with retry logic."""
304
+
305
+ @wraps(func)
306
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
307
+ attempt = 0
308
+ while True:
309
+ try:
310
+ return await func(*args, **kwargs)
311
+ except retry_exceptions:
312
+ attempt += 1
313
+ if attempt >= max_attempts:
314
+ raise
315
+ delay = _calculate_retry_delay(attempt, backoff)
316
+ if delay > 0:
317
+ await asyncio.sleep(delay)
318
+
319
+ return async_wrapper
320
+
321
+
322
+ def _create_sync_retry_wrapper(
323
+ func: t.Callable[..., t.Any],
324
+ max_attempts: int,
325
+ retry_exceptions: tuple[type[Exception], ...],
326
+ backoff: float,
327
+ ) -> t.Callable[..., t.Any]:
328
+ """Create sync wrapper with retry logic."""
329
+
330
+ @wraps(func)
331
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
332
+ attempt = 0
333
+ while True:
334
+ try:
335
+ return func(*args, **kwargs)
336
+ except retry_exceptions:
337
+ attempt += 1
338
+ if attempt >= max_attempts:
339
+ raise
340
+ delay = _calculate_retry_delay(attempt, backoff)
341
+ if delay > 0:
342
+ time.sleep(delay)
343
+
344
+ return sync_wrapper
345
+
346
+
347
+ def retry(
348
+ *,
349
+ max_attempts: int = 3,
350
+ exceptions: t.Iterable[type[Exception]] | None = None,
351
+ backoff: float = 0.0,
352
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
353
+ """
354
+ Retry decorator supporting sync and async callables.
355
+
356
+ Args:
357
+ max_attempts: Maximum number of attempts (>=1)
358
+ exceptions: Iterable of exception types to retry on (default: Exception)
359
+ backoff: Base delay in seconds applied after each failed attempt.
360
+ Delay grows linearly with attempt number.
361
+ """
362
+ if max_attempts < 1:
363
+ raise ValueError("max_attempts must be >= 1")
364
+
365
+ retry_exceptions = tuple(exceptions) if exceptions else (Exception,)
366
+
367
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
368
+ if is_async_function(func):
369
+ return _create_async_retry_wrapper(
370
+ func, max_attempts, retry_exceptions, backoff
371
+ )
372
+ return _create_sync_retry_wrapper(func, max_attempts, retry_exceptions, backoff)
373
+
374
+ return decorator
375
+
376
+
377
+ def with_timeout(
378
+ *,
379
+ seconds: float,
380
+ error_message: str | None = None,
381
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
382
+ """
383
+ Timeout enforcement decorator.
384
+
385
+ Args:
386
+ seconds: Maximum execution time in seconds.
387
+ error_message: Optional custom error message for timeout.
388
+ """
389
+
390
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
391
+ if is_async_function(func):
392
+
393
+ @wraps(func)
394
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
395
+ try:
396
+ return await asyncio.wait_for(
397
+ func(*args, **kwargs), timeout=seconds
398
+ )
399
+ except TimeoutError as exc:
400
+ message = error_message or f"Operation timed out after {seconds}s"
401
+ raise CrackerjackTimeoutError(message=message) from exc
402
+
403
+ return async_wrapper
404
+
405
+ @wraps(func)
406
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
407
+ with ThreadPoolExecutor(max_workers=1) as executor:
408
+ future = executor.submit(func, *args, **kwargs)
409
+ try:
410
+ return future.result(timeout=seconds)
411
+ except FutureTimeoutError as exc:
412
+ message = error_message or f"Operation timed out after {seconds}s"
413
+ raise CrackerjackTimeoutError(message=message) from exc
414
+
415
+ return sync_wrapper
416
+
417
+ return decorator
418
+
419
+
420
+ def _execute_single_validator(
421
+ validate: t.Callable[[t.Any], bool], param: str, value: t.Any
422
+ ) -> None:
423
+ """Execute a single validator and raise ValidationError if it fails."""
424
+ try:
425
+ result = validate(value)
426
+ except Exception as exc: # pragma: no cover - defensive
427
+ raise ValidationError(
428
+ message=f"Validator for '{param}' raised {type(exc).__name__}: {exc}",
429
+ ) from exc
430
+ if not result:
431
+ raise ValidationError(
432
+ message=f"Validation failed for parameter '{param}'",
433
+ )
434
+
435
+
436
+ def _check_type_annotation_against_signature(
437
+ name: str, value: t.Any, signature: inspect.Signature
438
+ ) -> None:
439
+ """Check if value matches parameter type annotation."""
440
+ parameter = signature.parameters.get(name)
441
+ if not parameter or parameter.annotation is inspect.Signature.empty:
442
+ return
443
+ if not isinstance(value, parameter.annotation): # type: ignore[arg-type]
444
+ raise ValidationError(
445
+ message=(
446
+ f"Parameter '{name}' expected "
447
+ f"{parameter.annotation!r}, got {type(value)!r}"
448
+ ),
449
+ )
450
+
451
+
452
+ def _create_async_validation_wrapper(
453
+ func: t.Callable[..., t.Any],
454
+ signature: inspect.Signature,
455
+ validate_fn: t.Callable[[inspect.BoundArguments], None],
456
+ ) -> t.Callable[..., t.Any]:
457
+ """Create async wrapper with validation."""
458
+
459
+ @wraps(func)
460
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
461
+ bound = signature.bind_partial(*args, **kwargs)
462
+ bound.apply_defaults()
463
+ validate_fn(bound)
464
+ return await func(*args, **kwargs)
465
+
466
+ return async_wrapper
467
+
468
+
469
+ def _create_sync_validation_wrapper(
470
+ func: t.Callable[..., t.Any],
471
+ signature: inspect.Signature,
472
+ validate_fn: t.Callable[[inspect.BoundArguments], None],
473
+ ) -> t.Callable[..., t.Any]:
474
+ """Create sync wrapper with validation."""
475
+
476
+ @wraps(func)
477
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
478
+ bound = signature.bind_partial(*args, **kwargs)
479
+ bound.apply_defaults()
480
+ validate_fn(bound)
481
+ return func(*args, **kwargs)
482
+
483
+ return sync_wrapper
484
+
485
+
486
+ def _normalize_validators(
487
+ param: str,
488
+ funcs: t.Callable[[t.Any], bool] | t.Iterable[t.Callable[[t.Any], bool]],
489
+ ) -> list[t.Callable[[t.Any], bool]]:
490
+ """Convert single validator or iterable to list of validators."""
491
+ if isinstance(funcs, (list, tuple, set)):
492
+ return list(funcs) # type: ignore[arg-type]
493
+ return [funcs] # type: ignore[list-item]
494
+
495
+
496
+ def _create_validator_runner(
497
+ validator_map: dict[
498
+ str, t.Callable[[t.Any], bool] | t.Iterable[t.Callable[[t.Any], bool]]
499
+ ],
500
+ ) -> t.Callable[[str, t.Any], None]:
501
+ """Create function to run validators for a parameter."""
502
+
503
+ def _run_validators(param: str, value: t.Any) -> None:
504
+ funcs = validator_map.get(param)
505
+ if not funcs:
506
+ return
507
+ normalized = _normalize_validators(param, funcs)
508
+ for validate in normalized:
509
+ _execute_single_validator(validate, param, value)
510
+
511
+ return _run_validators
512
+
513
+
514
+ def validate_args(
515
+ *,
516
+ validators: dict[
517
+ str, t.Callable[[t.Any], bool] | t.Iterable[t.Callable[[t.Any], bool]]
518
+ ]
519
+ | None = None,
520
+ type_check: bool = False,
521
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
522
+ """
523
+ Validate function arguments using provided callables and optional type checks.
524
+
525
+ Args:
526
+ validators: Mapping of argument names to validator callable(s).
527
+ type_check: If True, enforce annotations via isinstance checks.
528
+ """
529
+ validator_map = validators or {}
530
+ _run_validators = _create_validator_runner(validator_map)
531
+
532
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
533
+ signature = inspect.signature(func)
534
+
535
+ def _validate(bound: inspect.BoundArguments) -> None:
536
+ if type_check:
537
+ for name, value in bound.arguments.items():
538
+ _check_type_annotation_against_signature(name, value, signature)
539
+
540
+ for name, value in bound.arguments.items():
541
+ _run_validators(name, value)
542
+
543
+ if is_async_function(func):
544
+ return _create_async_validation_wrapper(func, signature, _validate)
545
+
546
+ return _create_sync_validation_wrapper(func, signature, _validate)
547
+
548
+ return decorator
549
+
550
+
551
+ def _handle_degradation_error(
552
+ func: t.Callable[..., t.Any],
553
+ e: Exception,
554
+ fallback_value: t.Any,
555
+ warn: bool,
556
+ console: Console,
557
+ ) -> t.Any:
558
+ """Handle error with warning and fallback resolution."""
559
+ if warn:
560
+ context = get_function_context(func)
561
+ _safe_console_print(
562
+ console,
563
+ f"[yellow]⚠️ {context['function_name']} failed, using fallback: "
564
+ f"{type(e).__name__}[/yellow]",
565
+ )
566
+
567
+ if callable(fallback_value):
568
+ return fallback_value()
569
+ return fallback_value
570
+
571
+
572
+ def _create_async_degradation_wrapper(
573
+ func: t.Callable[..., t.Any],
574
+ fallback_value: t.Any,
575
+ warn: bool,
576
+ console: Console,
577
+ ) -> t.Callable[..., t.Any]:
578
+ """Create async wrapper with graceful degradation."""
579
+
580
+ @wraps(func)
581
+ async def async_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
582
+ try:
583
+ return await func(*args, **kwargs)
584
+ except Exception as e:
585
+ return _handle_degradation_error(func, e, fallback_value, warn, console)
586
+
587
+ return async_wrapper
588
+
589
+
590
+ def _create_sync_degradation_wrapper(
591
+ func: t.Callable[..., t.Any],
592
+ fallback_value: t.Any,
593
+ warn: bool,
594
+ console: Console,
595
+ ) -> t.Callable[..., t.Any]:
596
+ """Create sync wrapper with graceful degradation."""
597
+
598
+ @wraps(func)
599
+ def sync_wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
600
+ try:
601
+ return func(*args, **kwargs)
602
+ except Exception as e:
603
+ return _handle_degradation_error(func, e, fallback_value, warn, console)
604
+
605
+ return sync_wrapper
606
+
607
+
608
+ def graceful_degradation(
609
+ fallback_value: t.Any = None,
610
+ warn: bool = True,
611
+ console: Console | None = None,
612
+ ) -> t.Callable[[t.Callable[..., t.Any]], t.Callable[..., t.Any]]:
613
+ """
614
+ Gracefully degrade on errors with optional warnings.
615
+
616
+ Args:
617
+ fallback_value: Value to return on error (can be callable)
618
+ warn: Show warning message when falling back
619
+ console: Optional Rich Console
620
+
621
+ Returns:
622
+ Decorated function with graceful degradation
623
+
624
+ Example:
625
+ >>> @graceful_degradation(fallback_value=[], warn=True)
626
+ >>> def get_optional_features() -> list[str]:
627
+ ... # Returns [] on error with warning
628
+ ... return fetch_features()
629
+
630
+ >>> @graceful_degradation(fallback_value=lambda: {})
631
+ >>> async def load_cache() -> dict:
632
+ ... # Returns {} on error
633
+ ... return await load_cache_file()
634
+
635
+ Notes:
636
+ - Suppresses all exceptions
637
+ - Logs/warns about failures if warn=True
638
+ - Useful for optional features that shouldn't break the app
639
+ """
640
+ _console = console or depends.get_sync(Console)
641
+
642
+ def decorator(func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
643
+ if is_async_function(func):
644
+ return _create_async_degradation_wrapper(
645
+ func, fallback_value, warn, _console
646
+ )
647
+ return _create_sync_degradation_wrapper(func, fallback_value, warn, _console)
648
+
649
+ return decorator