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,643 @@
1
+ """Coverage analysis helper for test creation.
2
+
3
+ This module provides coverage analysis and gap detection capabilities
4
+ for test creation. Uses AgentContext pattern (legacy, intentional).
5
+ """
6
+
7
+ import json
8
+ import operator
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from crackerjack.agents.base import AgentContext
13
+
14
+
15
+ class TestCoverageAnalyzer:
16
+ """Coverage analyzer helper for test creation.
17
+
18
+ Uses AgentContext pattern (legacy, intentional).
19
+ May use TestASTAnalyzer for code structure analysis.
20
+ """
21
+
22
+ def __init__(self, context: AgentContext) -> None:
23
+ self.context = context
24
+
25
+ async def analyze_coverage(self) -> dict[str, Any]:
26
+ """Analyze test coverage and identify gaps."""
27
+ try:
28
+ coverage_data = await self._get_existing_coverage_data()
29
+ if coverage_data:
30
+ return coverage_data
31
+
32
+ returncode, _, stderr = await self._run_coverage_command()
33
+
34
+ if returncode != 0:
35
+ return self._handle_coverage_command_failure(stderr)
36
+
37
+ return await self._process_coverage_results_enhanced()
38
+
39
+ except Exception as e:
40
+ self._log(f"Coverage analysis error: {e}", "WARN")
41
+ return self._create_default_coverage_result()
42
+
43
+ async def _get_existing_coverage_data(self) -> dict[str, Any] | None:
44
+ """Try to get existing coverage data from files."""
45
+ try:
46
+ project_path = Path(str(self.context.project_path))
47
+ json_report = project_path / "coverage.json"
48
+ if json_report.exists():
49
+ content = self.context.get_file_content(json_report)
50
+ if content:
51
+ coverage_json = json.loads(content)
52
+ return self._parse_coverage_json(coverage_json)
53
+
54
+ coverage_file = project_path / ".coverage"
55
+ if coverage_file.exists():
56
+ return await self._process_coverage_results_enhanced()
57
+
58
+ except Exception as e:
59
+ self._log(f"Error reading existing coverage: {e}", "WARN")
60
+
61
+ return None
62
+
63
+ def _parse_coverage_json(self, coverage_json: dict[str, Any]) -> dict[str, Any]:
64
+ """Parse coverage JSON report."""
65
+ try:
66
+ totals = coverage_json.get("totals", {})
67
+ current_coverage = totals.get("percent_covered", 0) / 100.0
68
+
69
+ uncovered_modules = []
70
+ files = coverage_json.get("files", {})
71
+
72
+ for file_path, file_data in files.items():
73
+ if file_data.get("summary", {}).get("percent_covered", 100) < 80:
74
+ rel_path = str(
75
+ Path(file_path).relative_to(self.context.project_path)
76
+ )
77
+ uncovered_modules.append(rel_path)
78
+
79
+ return {
80
+ "below_threshold": current_coverage < 0.8,
81
+ "current_coverage": current_coverage,
82
+ "uncovered_modules": uncovered_modules[:15],
83
+ "missing_lines": totals.get("num_statements", 0)
84
+ - totals.get("covered_lines", 0),
85
+ "total_lines": totals.get("num_statements", 0),
86
+ }
87
+
88
+ except Exception as e:
89
+ self._log(f"Error parsing coverage JSON: {e}", "WARN")
90
+ return self._create_default_coverage_result()
91
+
92
+ async def _run_coverage_command(self) -> tuple[int, str, str]:
93
+ """Run coverage command via context."""
94
+ # This would call through the context's run_command method
95
+ # For now, return a placeholder
96
+ return 1, "", "Coverage command not available in helper"
97
+
98
+ def _handle_coverage_command_failure(self, stderr: str) -> dict[str, Any]:
99
+ """Handle coverage command failure."""
100
+ self._log(f"Coverage analysis failed: {stderr}", "WARN")
101
+ return self._create_default_coverage_result()
102
+
103
+ async def _process_coverage_results_enhanced(self) -> dict[str, Any]:
104
+ """Process coverage results with enhanced analysis."""
105
+ coverage_file = self.context.project_path / ".coverage"
106
+ if not coverage_file.exists():
107
+ return self._create_default_coverage_result()
108
+
109
+ uncovered_modules = await self._find_uncovered_modules_enhanced()
110
+ untested_functions = await self._find_untested_functions_enhanced()
111
+
112
+ current_coverage = await self._estimate_current_coverage()
113
+
114
+ return {
115
+ "below_threshold": current_coverage < 0.8,
116
+ "current_coverage": current_coverage,
117
+ "uncovered_modules": uncovered_modules[:15],
118
+ "untested_functions": untested_functions[:20],
119
+ "coverage_gaps": await self._identify_coverage_gaps(),
120
+ "improvement_potential": self._calculate_improvement_potential(
121
+ len(uncovered_modules), len(untested_functions)
122
+ ),
123
+ }
124
+
125
+ async def _estimate_current_coverage(self) -> float:
126
+ """Estimate current test coverage."""
127
+ try:
128
+ source_files: list[Path] = list(
129
+ (self.context.project_path / "crackerjack").rglob("*.py")
130
+ )
131
+ source_files = [f for f in source_files if not f.name.startswith("test_")]
132
+
133
+ test_files: list[Path] = list(
134
+ (self.context.project_path / "tests").rglob("test_*.py")
135
+ )
136
+
137
+ if not source_files:
138
+ return 0.0
139
+
140
+ coverage_ratio = len(test_files) / len(source_files)
141
+
142
+ estimated_coverage = min(coverage_ratio * 0.6, 0.9)
143
+
144
+ return estimated_coverage
145
+
146
+ except Exception:
147
+ return 0.1
148
+
149
+ def _calculate_improvement_potential(
150
+ self, uncovered_modules: int, untested_functions: int
151
+ ) -> dict[str, Any]:
152
+ """Calculate coverage improvement potential."""
153
+ if uncovered_modules == untested_functions == 0:
154
+ return {"percentage_points": 0, "priority": "low"}
155
+
156
+ module_improvement = uncovered_modules * 2.5
157
+ function_improvement = untested_functions * 0.8
158
+
159
+ total_potential = min(module_improvement + function_improvement, 40)
160
+
161
+ priority = (
162
+ "high"
163
+ if total_potential > 15
164
+ else "medium"
165
+ if total_potential > 5
166
+ else "low"
167
+ )
168
+
169
+ return {
170
+ "percentage_points": round(total_potential, 1),
171
+ "priority": priority,
172
+ "module_contribution": round(module_improvement, 1),
173
+ "function_contribution": round(function_improvement, 1),
174
+ }
175
+
176
+ def _create_default_coverage_result(self) -> dict[str, Any]:
177
+ """Create default coverage result."""
178
+ return {
179
+ "below_threshold": True,
180
+ "current_coverage": 0.0,
181
+ "uncovered_modules": [],
182
+ }
183
+
184
+ async def _find_uncovered_modules_enhanced(self) -> list[dict[str, Any]]:
185
+ """Find uncovered modules with priority scoring."""
186
+ from .test_ast_analyzer import TestASTAnalyzer
187
+
188
+ uncovered: list[dict[str, Any]] = []
189
+
190
+ project_path = Path(str(self.context.project_path))
191
+ package_dir = project_path / "crackerjack"
192
+ if not package_dir.exists():
193
+ return uncovered[:15]
194
+
195
+ ast_analyzer = TestASTAnalyzer(self.context)
196
+
197
+ for py_file in package_dir.rglob("*.py"):
198
+ if ast_analyzer.should_skip_module_for_coverage(py_file):
199
+ continue
200
+
201
+ if not ast_analyzer.has_corresponding_test(str(py_file)):
202
+ module_info = await self._analyze_module_priority(py_file, ast_analyzer)
203
+ uncovered.append(module_info)
204
+
205
+ uncovered.sort(key=operator.itemgetter("priority_score"), reverse=True)
206
+ return uncovered[:15]
207
+
208
+ async def _analyze_module_priority(
209
+ self, py_file: Path, ast_analyzer: "TestASTAnalyzer"
210
+ ) -> dict[str, Any]:
211
+ """Analyze module priority for testing."""
212
+ try:
213
+ content = self.context.get_file_content(py_file) or ""
214
+ import ast
215
+
216
+ ast.parse(content)
217
+
218
+ functions = await ast_analyzer.extract_functions_from_file(py_file)
219
+ classes = await ast_analyzer.extract_classes_from_file(py_file)
220
+
221
+ priority_score = 0
222
+
223
+ rel_path = str(py_file.relative_to(self.context.project_path))
224
+ if any(
225
+ core_path in rel_path
226
+ for core_path in ("managers/", "services/", "core/", "agents/")
227
+ ):
228
+ priority_score += 10
229
+
230
+ priority_score += len(functions) * 2
231
+ priority_score += len(classes) * 3
232
+
233
+ public_functions = [f for f in functions if not f["name"].startswith("_")]
234
+ priority_score += len(public_functions) * 2
235
+
236
+ lines_count = len(content.split("\n"))
237
+ if lines_count > 100:
238
+ priority_score += 5
239
+ elif lines_count > 50:
240
+ priority_score += 2
241
+
242
+ return {
243
+ "path": rel_path,
244
+ "absolute_path": str(py_file),
245
+ "priority_score": priority_score,
246
+ "function_count": len(functions),
247
+ "class_count": len(classes),
248
+ "public_function_count": len(public_functions),
249
+ "lines_count": lines_count,
250
+ "category": self._categorize_module(rel_path),
251
+ }
252
+
253
+ except Exception as e:
254
+ self._log(f"Error analyzing module priority for {py_file}: {e}", "WARN")
255
+ return {
256
+ "path": str(py_file.relative_to(self.context.project_path)),
257
+ "absolute_path": str(py_file),
258
+ "priority_score": 1,
259
+ "function_count": 0,
260
+ "class_count": 0,
261
+ "public_function_count": 0,
262
+ "lines_count": 0,
263
+ "category": "unknown",
264
+ }
265
+
266
+ def _categorize_module(self, relative_path: str) -> str:
267
+ """Categorize module by path."""
268
+ if "managers/" in relative_path:
269
+ return "manager"
270
+ elif "services/" in relative_path:
271
+ return "service"
272
+ elif "core/" in relative_path:
273
+ return "core"
274
+ elif "agents/" in relative_path:
275
+ return "agent"
276
+ elif "models/" in relative_path:
277
+ return "model"
278
+ elif "executors/" in relative_path:
279
+ return "executor"
280
+ return "utility"
281
+
282
+ async def _find_untested_functions_enhanced(self) -> list[dict[str, Any]]:
283
+ """Find untested functions with priority scoring."""
284
+ from .test_ast_analyzer import TestASTAnalyzer
285
+
286
+ untested: list[dict[str, Any]] = []
287
+
288
+ package_dir = self.context.project_path / "crackerjack"
289
+ if not package_dir.exists():
290
+ return untested[:20]
291
+
292
+ ast_analyzer = TestASTAnalyzer(self.context)
293
+
294
+ for py_file in package_dir.rglob("*.py"):
295
+ if ast_analyzer.should_skip_file_for_testing(py_file):
296
+ continue
297
+
298
+ file_untested = await self._find_untested_functions_in_file_enhanced(
299
+ py_file, ast_analyzer
300
+ )
301
+ untested.extend(file_untested)
302
+
303
+ untested.sort(key=operator.itemgetter("testing_priority"), reverse=True)
304
+ return untested[:20]
305
+
306
+ async def _find_untested_functions_in_file_enhanced(
307
+ self, py_file: Path, ast_analyzer: "TestASTAnalyzer"
308
+ ) -> list[dict[str, Any]]:
309
+ """Find untested functions in file with enhanced analysis."""
310
+ untested: list[dict[str, Any]] = []
311
+
312
+ try:
313
+ functions = await ast_analyzer.extract_functions_from_file(py_file)
314
+ for func in functions:
315
+ if not await ast_analyzer.function_has_test(func, py_file):
316
+ func_info = await self._analyze_function_testability(func, py_file)
317
+ untested.append(func_info)
318
+
319
+ except Exception as e:
320
+ self._log(f"Error finding untested functions in {py_file}: {e}", "WARN")
321
+
322
+ return untested
323
+
324
+ async def _analyze_function_testability(
325
+ self, func: dict[str, Any], py_file: Path
326
+ ) -> dict[str, Any]:
327
+ """Analyze function testability and priority."""
328
+ try:
329
+ func_info = {
330
+ "name": func["name"],
331
+ "file": str(py_file),
332
+ "relative_file": str(py_file.relative_to(self.context.project_path)),
333
+ "line": func.get("line", 1),
334
+ "signature": func.get("signature", ""),
335
+ "args": func.get("args", []),
336
+ "returns": func.get("returns", "Any"),
337
+ "testing_priority": 0,
338
+ "complexity": "simple",
339
+ "test_strategy": "basic",
340
+ }
341
+
342
+ priority = 0
343
+
344
+ if not func["name"].startswith("_"):
345
+ priority += 10
346
+
347
+ arg_count = len(func.get("args", []))
348
+ if arg_count > 3:
349
+ priority += 5
350
+ func_info["complexity"] = "complex"
351
+ func_info["test_strategy"] = "parametrized"
352
+ elif arg_count > 1:
353
+ priority += 2
354
+ func_info["complexity"] = "moderate"
355
+
356
+ if any(
357
+ core_path in str(func_info["relative_file"])
358
+ for core_path in ("managers/", "services/", "core/")
359
+ ):
360
+ priority += 8
361
+
362
+ if func.get("is_async", False):
363
+ priority += 3
364
+ func_info["test_strategy"] = "async"
365
+
366
+ func_info["testing_priority"] = priority
367
+
368
+ return func_info
369
+
370
+ except Exception as e:
371
+ self._log(f"Error analyzing function testability: {e}", "WARN")
372
+ return {
373
+ "name": func.get("name", "unknown"),
374
+ "file": str(py_file),
375
+ "relative_file": str(py_file.relative_to(self.context.project_path)),
376
+ "line": func.get("line", 1),
377
+ "testing_priority": 1,
378
+ "complexity": "unknown",
379
+ "test_strategy": "basic",
380
+ }
381
+
382
+ async def _identify_coverage_gaps(self) -> list[dict[str, Any]]:
383
+ """Identify coverage gaps in existing tests."""
384
+ from .test_ast_analyzer import TestASTAnalyzer
385
+
386
+ gaps: list[dict[str, Any]] = []
387
+
388
+ try:
389
+ package_dir = self.context.project_path / "crackerjack"
390
+ tests_dir = self.context.project_path / "tests"
391
+
392
+ if not package_dir.exists() or not tests_dir.exists():
393
+ return gaps
394
+
395
+ ast_analyzer = TestASTAnalyzer(self.context)
396
+
397
+ for py_file in package_dir.rglob("*.py"):
398
+ if ast_analyzer.should_skip_module_for_coverage(py_file):
399
+ continue
400
+
401
+ test_coverage_info = await self._analyze_existing_test_coverage(
402
+ py_file, ast_analyzer
403
+ )
404
+ if test_coverage_info["has_gaps"]:
405
+ gaps.append(test_coverage_info)
406
+
407
+ except Exception as e:
408
+ self._log(f"Error identifying coverage gaps: {e}", "WARN")
409
+
410
+ return gaps[:10]
411
+
412
+ async def _analyze_existing_test_coverage(
413
+ self, py_file: Path, ast_analyzer: "TestASTAnalyzer"
414
+ ) -> dict[str, Any]:
415
+ """Analyze existing test coverage for file."""
416
+ try:
417
+ test_file_path = await ast_analyzer.generate_test_file_path(py_file)
418
+
419
+ coverage_info: dict[str, Any] = {
420
+ "source_file": str(py_file.relative_to(self.context.project_path)),
421
+ "test_file": str(test_file_path) if test_file_path.exists() else None,
422
+ "has_gaps": True,
423
+ "missing_test_types": [],
424
+ "coverage_score": 0,
425
+ }
426
+
427
+ if not test_file_path.exists():
428
+ coverage_info["missing_test_types"] = [
429
+ "basic",
430
+ "edge_cases",
431
+ "error_handling",
432
+ ]
433
+ return coverage_info
434
+
435
+ test_content = self.context.get_file_content(test_file_path) or ""
436
+
437
+ missing_types = []
438
+ if "def test_" not in test_content:
439
+ missing_types.append("basic")
440
+ if "@pytest.mark.parametrize" not in test_content:
441
+ missing_types.append("parametrized")
442
+ if "with pytest.raises" not in test_content:
443
+ missing_types.append("error_handling")
444
+ if "mock" not in test_content.lower():
445
+ missing_types.append("mocking")
446
+
447
+ coverage_info["missing_test_types"] = missing_types
448
+ coverage_info["has_gaps"] = len(missing_types) > 0
449
+ coverage_info["coverage_score"] = max(0, 100 - len(missing_types) * 25)
450
+
451
+ return coverage_info
452
+
453
+ except Exception as e:
454
+ self._log(f"Error analyzing test coverage for {py_file}: {e}", "WARN")
455
+ return {
456
+ "source_file": str(py_file.relative_to(self.context.project_path)),
457
+ "test_file": None,
458
+ "has_gaps": True,
459
+ "missing_test_types": ["basic"],
460
+ "coverage_score": 0,
461
+ }
462
+
463
+ async def create_tests_for_module(self, module_path: str) -> dict[str, list[str]]:
464
+ """Create tests for a module."""
465
+ from .test_ast_analyzer import TestASTAnalyzer
466
+ from .test_template_generator import TestTemplateGenerator
467
+
468
+ fixes: list[str] = []
469
+ files: list[str] = []
470
+
471
+ try:
472
+ test_results = await self._generate_module_tests(
473
+ module_path,
474
+ TestASTAnalyzer(self.context),
475
+ TestTemplateGenerator(self.context),
476
+ )
477
+ fixes.extend(test_results["fixes"])
478
+ files.extend(test_results["files"])
479
+
480
+ except Exception as e:
481
+ self._handle_test_creation_error(module_path, e)
482
+
483
+ return {"fixes": fixes, "files": files}
484
+
485
+ async def _generate_module_tests(
486
+ self,
487
+ module_path: str,
488
+ ast_analyzer: "TestASTAnalyzer",
489
+ template_gen: "TestTemplateGenerator",
490
+ ) -> dict[str, list[str]]:
491
+ """Generate tests for module."""
492
+ module_file = Path(module_path)
493
+ if not await self._is_module_valid(module_file):
494
+ return {"fixes": [], "files": []}
495
+
496
+ functions = await ast_analyzer.extract_functions_from_file(module_file)
497
+ classes = await ast_analyzer.extract_classes_from_file(module_file)
498
+
499
+ if not functions and not classes:
500
+ return {"fixes": [], "files": []}
501
+
502
+ return await self._create_test_artifacts(
503
+ module_file, functions, classes, ast_analyzer, template_gen
504
+ )
505
+
506
+ async def _is_module_valid(self, module_file: Path) -> bool:
507
+ """Check if module file is valid."""
508
+ return module_file.exists()
509
+
510
+ async def _create_test_artifacts(
511
+ self,
512
+ module_file: Path,
513
+ functions: list[dict[str, Any]],
514
+ classes: list[dict[str, Any]],
515
+ ast_analyzer: "TestASTAnalyzer",
516
+ template_gen: "TestTemplateGenerator",
517
+ ) -> dict[str, list[str]]:
518
+ """Create test artifacts for module."""
519
+ test_file_path = await ast_analyzer.generate_test_file_path(module_file)
520
+ test_content = await template_gen.generate_test_content(
521
+ module_file,
522
+ functions,
523
+ classes,
524
+ )
525
+
526
+ if self.context.write_file_content(test_file_path, test_content):
527
+ self._log(f"Created test file: {test_file_path}")
528
+ return {
529
+ "fixes": [f"Created test file for {module_file}"],
530
+ "files": [str(test_file_path)],
531
+ }
532
+
533
+ return {"fixes": [], "files": []}
534
+
535
+ def _handle_test_creation_error(self, module_path: str, e: Exception) -> None:
536
+ """Handle test creation error."""
537
+ self._log(f"Error creating tests for module {module_path}: {e}", "ERROR")
538
+
539
+ async def create_tests_for_file(self, file_path: str) -> dict[str, list[str]]:
540
+ """Create tests for file."""
541
+ from .test_ast_analyzer import TestASTAnalyzer
542
+
543
+ ast_analyzer = TestASTAnalyzer(self.context)
544
+ if ast_analyzer.has_corresponding_test(file_path):
545
+ return {"fixes": [], "files": []}
546
+
547
+ return await self.create_tests_for_module(file_path)
548
+
549
+ async def find_untested_functions(self) -> list[dict[str, Any]]:
550
+ """Find untested functions (basic version)."""
551
+ from .test_ast_analyzer import TestASTAnalyzer
552
+
553
+ untested: list[dict[str, Any]] = []
554
+
555
+ package_dir = self.context.project_path / "crackerjack"
556
+ if not package_dir.exists():
557
+ return untested[:10]
558
+
559
+ ast_analyzer = TestASTAnalyzer(self.context)
560
+
561
+ for py_file in package_dir.rglob("*.py"):
562
+ if ast_analyzer.should_skip_file_for_testing(py_file):
563
+ continue
564
+
565
+ file_untested = await self._find_untested_functions_in_file(
566
+ py_file, ast_analyzer
567
+ )
568
+ untested.extend(file_untested)
569
+
570
+ return untested[:10]
571
+
572
+ async def _find_untested_functions_in_file(
573
+ self,
574
+ py_file: Path,
575
+ ast_analyzer: "TestASTAnalyzer",
576
+ ) -> list[dict[str, Any]]:
577
+ """Find untested functions in file (basic version)."""
578
+ untested: list[dict[str, Any]] = []
579
+
580
+ functions = await ast_analyzer.extract_functions_from_file(py_file)
581
+ for func in functions:
582
+ if not await ast_analyzer.function_has_test(func, py_file):
583
+ untested.append(self._create_untested_function_info(func, py_file))
584
+
585
+ return untested
586
+
587
+ def _create_untested_function_info(
588
+ self,
589
+ func: dict[str, Any],
590
+ py_file: Path,
591
+ ) -> dict[str, Any]:
592
+ """Create untested function info."""
593
+ return {
594
+ "name": func["name"],
595
+ "file": str(py_file),
596
+ "line": func.get("line", 1),
597
+ "signature": func.get("signature", ""),
598
+ }
599
+
600
+ async def create_test_for_function(
601
+ self,
602
+ func_info: dict[str, Any],
603
+ ) -> dict[str, list[str]]:
604
+ """Create test for function."""
605
+ from .test_ast_analyzer import TestASTAnalyzer
606
+ from .test_template_generator import TestTemplateGenerator
607
+
608
+ fixes: list[str] = []
609
+ files: list[str] = []
610
+
611
+ try:
612
+ func_file = Path(func_info["file"])
613
+ ast_analyzer = TestASTAnalyzer(self.context)
614
+ template_gen = TestTemplateGenerator(self.context)
615
+
616
+ test_file_path = await ast_analyzer.generate_test_file_path(func_file)
617
+
618
+ if test_file_path.exists():
619
+ existing_content = self.context.get_file_content(test_file_path) or ""
620
+ new_test = await template_gen.generate_function_test(func_info)
621
+
622
+ updated_content = existing_content.rstrip() + "\n\n" + new_test
623
+ if self.context.write_file_content(test_file_path, updated_content):
624
+ fixes.append(f"Added test for function {func_info['name']}")
625
+ files.append(str(test_file_path))
626
+ else:
627
+ test_content = await template_gen.generate_function_test(func_info)
628
+ if self.context.write_file_content(test_file_path, test_content):
629
+ fixes.append(f"Created test file with test for {func_info['name']}")
630
+ files.append(str(test_file_path))
631
+
632
+ except Exception as e:
633
+ self._log(
634
+ f"Error creating test for function {func_info['name']}: {e}",
635
+ "ERROR",
636
+ )
637
+
638
+ return {"fixes": fixes, "files": files}
639
+
640
+ def _log(self, message: str, level: str = "INFO") -> None:
641
+ """Log message through context."""
642
+ # This is a helper - logging would typically go through the agent
643
+ pass