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.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +30 -1
- crackerjack/__main__.py +342 -1263
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +27 -5
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/{rust_tool_manager.py → lsp/_manager.py} +3 -3
- crackerjack/adapters/{skylos_adapter.py → lsp/skylos.py} +59 -7
- crackerjack/adapters/{zuban_adapter.py → lsp/zuban.py} +3 -6
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +40 -12
- crackerjack/agents/base.py +1 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +49 -53
- crackerjack/agents/dry_agent.py +187 -3
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +6 -8
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/performance_agent.py +121 -1152
- crackerjack/agents/refactoring_agent.py +156 -655
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +19 -1605
- crackerjack/api.py +5 -7
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +1 -1
- crackerjack/cli/cache_handlers.py +23 -18
- crackerjack/cli/cache_handlers_enhanced.py +1 -4
- crackerjack/cli/facade.py +70 -8
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +249 -49
- crackerjack/cli/interactive.py +8 -5
- crackerjack/cli/options.py +203 -110
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +60 -24
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +256 -0
- crackerjack/config/global_lock_config.py +191 -54
- crackerjack/config/hooks.py +188 -16
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/async_workflow_orchestrator.py +79 -53
- crackerjack/core/autofix_coordinator.py +22 -9
- crackerjack/core/container.py +10 -9
- crackerjack/core/enhanced_container.py +9 -9
- crackerjack/core/performance.py +1 -1
- crackerjack/core/performance_monitor.py +5 -3
- crackerjack/core/phase_coordinator.py +1018 -634
- crackerjack/core/proactive_workflow.py +3 -3
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +167 -23
- crackerjack/core/session_coordinator.py +187 -382
- crackerjack/core/timeout_manager.py +161 -44
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +1247 -953
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +1 -1
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/ai_templates.py +1 -1
- crackerjack/documentation/dual_output_generator.py +11 -9
- crackerjack/documentation/reference_generator.py +104 -59
- crackerjack/dynamic_config.py +52 -61
- crackerjack/errors.py +1 -1
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +2 -0
- crackerjack/executors/async_hook_executor.py +539 -77
- crackerjack/executors/cached_hook_executor.py +3 -3
- crackerjack/executors/hook_executor.py +967 -102
- crackerjack/executors/hook_lock_manager.py +31 -22
- crackerjack/executors/individual_hook_executor.py +66 -32
- crackerjack/executors/lsp_aware_hook_executor.py +136 -57
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +23 -7
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +8 -9
- crackerjack/intelligence/README.md +557 -0
- crackerjack/interactive.py +37 -10
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/async_hook_manager.py +41 -57
- crackerjack/managers/hook_manager.py +449 -79
- crackerjack/managers/publish_manager.py +81 -36
- crackerjack/managers/test_command_builder.py +290 -12
- crackerjack/managers/test_executor.py +93 -8
- crackerjack/managers/test_manager.py +1082 -75
- crackerjack/managers/test_progress.py +118 -26
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/cache.py +25 -2
- crackerjack/mcp/client_runner.py +35 -18
- crackerjack/mcp/context.py +9 -9
- crackerjack/mcp/dashboard.py +24 -8
- crackerjack/mcp/enhanced_progress_monitor.py +34 -23
- crackerjack/mcp/file_monitor.py +27 -6
- crackerjack/mcp/progress_components.py +45 -34
- crackerjack/mcp/progress_monitor.py +6 -9
- crackerjack/mcp/rate_limiter.py +11 -7
- crackerjack/mcp/server.py +2 -0
- crackerjack/mcp/server_core.py +187 -55
- crackerjack/mcp/service_watchdog.py +12 -9
- crackerjack/mcp/task_manager.py +2 -2
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +2 -0
- crackerjack/mcp/tools/core_tools.py +75 -52
- crackerjack/mcp/tools/execution_tools.py +87 -31
- crackerjack/mcp/tools/intelligence_tools.py +2 -2
- crackerjack/mcp/tools/proactive_tools.py +1 -1
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +180 -132
- crackerjack/mcp/tools/workflow_executor.py +87 -46
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/app.py +11 -1
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +27 -4
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +16 -2930
- crackerjack/mcp/websocket/server.py +1 -3
- crackerjack/mcp/websocket/websocket_handler.py +107 -6
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +10 -1
- crackerjack/models/config.py +639 -22
- crackerjack/models/config_adapter.py +6 -6
- crackerjack/models/protocols.py +1167 -23
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +19 -3
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/ai_agent_watchdog.py +5 -4
- crackerjack/monitoring/metrics_collector.py +4 -3
- crackerjack/monitoring/regression_prevention.py +4 -3
- crackerjack/monitoring/websocket_server.py +4 -241
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +20 -67
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +13 -6
- crackerjack/orchestration/execution_strategies.py +6 -6
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +1 -1
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/hooks.py +3 -2
- crackerjack/plugins/loader.py +3 -3
- crackerjack/plugins/managers.py +1 -1
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +8 -21
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/{contextual_ai_assistant.py → ai/contextual_ai_assistant.py} +5 -3
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/api_extractor.py +5 -3
- crackerjack/services/bounded_status_operations.py +45 -5
- crackerjack/services/cache.py +249 -318
- crackerjack/services/changelog_automation.py +7 -3
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +83 -39
- crackerjack/services/config_merge.py +9 -6
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +13 -26
- crackerjack/services/coverage_badge_service.py +6 -4
- crackerjack/services/coverage_ratchet.py +53 -27
- crackerjack/services/debug.py +18 -7
- crackerjack/services/dependency_analyzer.py +4 -4
- crackerjack/services/dependency_monitor.py +13 -13
- crackerjack/services/documentation_generator.py +4 -2
- crackerjack/services/documentation_service.py +62 -33
- crackerjack/services/enhanced_filesystem.py +81 -27
- crackerjack/services/enterprise_optimizer.py +1 -1
- crackerjack/services/error_pattern_analyzer.py +10 -10
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +5 -7
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +80 -109
- crackerjack/services/git.py +99 -5
- crackerjack/services/health_metrics.py +4 -6
- crackerjack/services/heatmap_generator.py +12 -3
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +101 -49
- crackerjack/services/log_manager.py +2 -2
- crackerjack/services/logging.py +120 -68
- crackerjack/services/lsp_client.py +12 -12
- crackerjack/services/memory_optimizer.py +27 -22
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/{performance_benchmarks.py → monitoring/performance_benchmarks.py} +100 -14
- crackerjack/services/{performance_cache.py → monitoring/performance_cache.py} +21 -15
- crackerjack/services/{performance_monitor.py → monitoring/performance_monitor.py} +10 -6
- crackerjack/services/parallel_executor.py +166 -55
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +21 -8
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/{quality_baseline.py → quality/quality_baseline.py} +163 -2
- crackerjack/services/{quality_baseline_enhanced.py → quality/quality_baseline_enhanced.py} +4 -1
- crackerjack/services/{quality_intelligence.py → quality/quality_intelligence.py} +180 -16
- crackerjack/services/regex_patterns.py +58 -2987
- crackerjack/services/regex_utils.py +55 -29
- crackerjack/services/secure_status_formatter.py +42 -15
- crackerjack/services/secure_subprocess.py +35 -2
- crackerjack/services/security.py +16 -8
- crackerjack/services/server_manager.py +40 -51
- crackerjack/services/smart_scheduling.py +46 -6
- crackerjack/services/status_authentication.py +3 -3
- crackerjack/services/thread_safe_status_collector.py +1 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +9 -5
- crackerjack/services/unified_config.py +43 -351
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +6 -4
- crackerjack/services/version_checker.py +14 -8
- crackerjack/services/zuban_lsp_service.py +5 -4
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/init.md +2 -12
- crackerjack/slash_commands/run.md +84 -50
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_regex_patterns.py +7 -3
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/METADATA +678 -98
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack/managers/test_manager_backup.py +0 -1075
- crackerjack/mcp/tools/execution_tools_backup.py +0 -1011
- crackerjack/mixins/__init__.py +0 -3
- crackerjack/mixins/error_handling.py +0 -145
- crackerjack/services/config.py +0 -358
- crackerjack/ui/server_panels.py +0 -125
- crackerjack-0.37.9.dist-info/RECORD +0 -231
- /crackerjack/adapters/{rust_tool_adapter.py → lsp/_base.py} +0 -0
- /crackerjack/adapters/{lsp_client.py → lsp/_client.py} +0 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/entry_points.txt +0 -0
- {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
|