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,706 @@
|
|
|
1
|
+
"""Base adapter for external tool integration in ACB QA framework.
|
|
2
|
+
|
|
3
|
+
Provides shared functionality for adapters that wrap external CLI tools
|
|
4
|
+
like Ruff, Zuban, Bandit, Gitleaks, etc.
|
|
5
|
+
|
|
6
|
+
ACB Patterns:
|
|
7
|
+
- Extends QAAdapterBase for ACB compliance
|
|
8
|
+
- Async subprocess execution with timeout/cancellation
|
|
9
|
+
- Standardized output parsing and error handling
|
|
10
|
+
- Tool availability validation
|
|
11
|
+
- Version detection and caching
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import shutil
|
|
18
|
+
import typing as t
|
|
19
|
+
from abc import abstractmethod
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from crackerjack.adapters._qa_adapter_base import QAAdapterBase, QABaseSettings
|
|
24
|
+
from crackerjack.models.qa_results import QACheckType, QAResult, QAResultStatus
|
|
25
|
+
|
|
26
|
+
if t.TYPE_CHECKING:
|
|
27
|
+
from crackerjack.models.qa_config import QACheckConfig
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ToolIssue:
|
|
32
|
+
"""Represents a single issue found by a tool.
|
|
33
|
+
|
|
34
|
+
Standardized format for all tool outputs.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
file_path: Path
|
|
38
|
+
line_number: int | None = None
|
|
39
|
+
column_number: int | None = None
|
|
40
|
+
message: str = ""
|
|
41
|
+
code: str | None = None
|
|
42
|
+
severity: str = "error"
|
|
43
|
+
suggestion: str | None = None
|
|
44
|
+
|
|
45
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
46
|
+
"""Convert to dictionary for serialization."""
|
|
47
|
+
return {
|
|
48
|
+
"file_path": str(self.file_path),
|
|
49
|
+
"line_number": self.line_number,
|
|
50
|
+
"column_number": self.column_number,
|
|
51
|
+
"message": self.message,
|
|
52
|
+
"code": self.code,
|
|
53
|
+
"severity": self.severity,
|
|
54
|
+
"suggestion": self.suggestion,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class ToolExecutionResult:
|
|
60
|
+
"""Result of tool execution with standardized format."""
|
|
61
|
+
|
|
62
|
+
success: bool
|
|
63
|
+
issues: list[ToolIssue] = field(default_factory=list)
|
|
64
|
+
error_message: str | None = None
|
|
65
|
+
raw_output: str = ""
|
|
66
|
+
raw_stderr: str = ""
|
|
67
|
+
execution_time_ms: float = 0.0
|
|
68
|
+
exit_code: int = 0
|
|
69
|
+
tool_version: str | None = None
|
|
70
|
+
files_processed: list[Path] = field(default_factory=list)
|
|
71
|
+
files_modified: list[Path] = field(default_factory=list)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def has_errors(self) -> bool:
|
|
75
|
+
"""Check if result contains error-level issues."""
|
|
76
|
+
return any(issue.severity == "error" for issue in self.issues)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def error_count(self) -> int:
|
|
80
|
+
"""Count of error-level issues."""
|
|
81
|
+
return len([i for i in self.issues if i.severity == "error"])
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def warning_count(self) -> int:
|
|
85
|
+
"""Count of warning-level issues."""
|
|
86
|
+
return len([i for i in self.issues if i.severity == "warning"])
|
|
87
|
+
|
|
88
|
+
def to_dict(self) -> dict[str, t.Any]:
|
|
89
|
+
"""Convert to dictionary for serialization."""
|
|
90
|
+
return {
|
|
91
|
+
"success": self.success,
|
|
92
|
+
"issues": [issue.to_dict() for issue in self.issues],
|
|
93
|
+
"error_message": self.error_message,
|
|
94
|
+
"raw_output": self.raw_output[:500], # Truncate for logs
|
|
95
|
+
"execution_time_ms": self.execution_time_ms,
|
|
96
|
+
"exit_code": self.exit_code,
|
|
97
|
+
"tool_version": self.tool_version,
|
|
98
|
+
"error_count": self.error_count,
|
|
99
|
+
"warning_count": self.warning_count,
|
|
100
|
+
"files_processed": [str(f) for f in self.files_processed],
|
|
101
|
+
"files_modified": [str(f) for f in self.files_modified],
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ToolAdapterSettings(QABaseSettings):
|
|
106
|
+
"""Settings for tool-based adapters.
|
|
107
|
+
|
|
108
|
+
Extends QABaseSettings with tool-specific configuration.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
tool_name: str = ""
|
|
112
|
+
tool_args: list[str] = field(default_factory=list)
|
|
113
|
+
use_json_output: bool = False
|
|
114
|
+
fix_enabled: bool = False
|
|
115
|
+
include_warnings: bool = True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
from acb.adapters import AdapterMetadata
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BaseToolAdapter(QAAdapterBase):
|
|
122
|
+
"""Base adapter for external CLI tools in QA framework.
|
|
123
|
+
|
|
124
|
+
Provides shared implementation for tools like Ruff, Zuban, Bandit, etc.
|
|
125
|
+
Subclasses implement tool-specific command building and output parsing.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
```python
|
|
129
|
+
import uuid
|
|
130
|
+
from contextlib import suppress
|
|
131
|
+
from acb.depends import depends
|
|
132
|
+
|
|
133
|
+
MODULE_ID = uuid.uuid7()
|
|
134
|
+
MODULE_STATUS = "stable"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class RuffAdapter(BaseToolAdapter):
|
|
138
|
+
settings: RuffSettings | None = None
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def module_id(self) -> uuid.UUID:
|
|
142
|
+
return MODULE_ID
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def tool_name(self) -> str:
|
|
146
|
+
return "ruff"
|
|
147
|
+
|
|
148
|
+
def build_command(self, files: list[Path]) -> list[str]:
|
|
149
|
+
cmd = [self.tool_name, "check"]
|
|
150
|
+
if self.settings and self.settings.fix_enabled:
|
|
151
|
+
cmd.append("--fix")
|
|
152
|
+
cmd.extend([str(f) for f in files])
|
|
153
|
+
return cmd
|
|
154
|
+
|
|
155
|
+
async def parse_output(
|
|
156
|
+
self, result: ToolExecutionResult
|
|
157
|
+
) -> list[ToolIssue]:
|
|
158
|
+
# Parse ruff JSON output
|
|
159
|
+
if not result.raw_output:
|
|
160
|
+
return []
|
|
161
|
+
data = json.loads(result.raw_output)
|
|
162
|
+
return [self._parse_ruff_issue(issue) for issue in data]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
with suppress(Exception):
|
|
166
|
+
depends.set(RuffAdapter)
|
|
167
|
+
```
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
settings: ToolAdapterSettings | None = None
|
|
171
|
+
metadata: AdapterMetadata | None = None
|
|
172
|
+
|
|
173
|
+
def __init__(self, settings: ToolAdapterSettings | None = None) -> None:
|
|
174
|
+
"""Initialize tool adapter.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
settings: Optional settings override
|
|
178
|
+
"""
|
|
179
|
+
super().__init__()
|
|
180
|
+
if settings:
|
|
181
|
+
self.settings = settings
|
|
182
|
+
self._tool_version: str | None = None
|
|
183
|
+
self._tool_available: bool | None = None
|
|
184
|
+
|
|
185
|
+
async def init(self) -> None:
|
|
186
|
+
"""Initialize adapter with tool validation."""
|
|
187
|
+
if not self.settings:
|
|
188
|
+
self.settings = ToolAdapterSettings(tool_name=self.tool_name)
|
|
189
|
+
|
|
190
|
+
# Validate tool is available
|
|
191
|
+
available = await self.validate_tool_available()
|
|
192
|
+
if not available:
|
|
193
|
+
raise RuntimeError(
|
|
194
|
+
f"Tool '{self.tool_name}' not found in PATH. "
|
|
195
|
+
f"Please install it before using this adapter."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Cache tool version
|
|
199
|
+
self._tool_version = await self.get_tool_version()
|
|
200
|
+
|
|
201
|
+
await super().init()
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
@abstractmethod
|
|
205
|
+
def tool_name(self) -> str:
|
|
206
|
+
"""Get the CLI tool name (e.g., 'ruff', 'bandit', 'zuban').
|
|
207
|
+
|
|
208
|
+
Must be implemented by subclasses.
|
|
209
|
+
"""
|
|
210
|
+
...
|
|
211
|
+
|
|
212
|
+
@abstractmethod
|
|
213
|
+
def build_command(
|
|
214
|
+
self,
|
|
215
|
+
files: list[Path],
|
|
216
|
+
config: QACheckConfig | None = None,
|
|
217
|
+
) -> list[str]:
|
|
218
|
+
"""Build the command to execute for this tool.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
files: Files to check
|
|
222
|
+
config: Optional configuration override
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Command as list of strings (for subprocess)
|
|
226
|
+
|
|
227
|
+
Must be implemented by subclasses.
|
|
228
|
+
"""
|
|
229
|
+
...
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
async def parse_output(
|
|
233
|
+
self,
|
|
234
|
+
result: ToolExecutionResult,
|
|
235
|
+
) -> list[ToolIssue]:
|
|
236
|
+
"""Parse tool output into standardized issues.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
result: Raw execution result from tool
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
List of parsed issues
|
|
243
|
+
|
|
244
|
+
Must be implemented by subclasses.
|
|
245
|
+
"""
|
|
246
|
+
...
|
|
247
|
+
|
|
248
|
+
async def check(
|
|
249
|
+
self,
|
|
250
|
+
files: list[Path] | None = None,
|
|
251
|
+
config: QACheckConfig | None = None,
|
|
252
|
+
) -> QAResult:
|
|
253
|
+
"""Execute tool check on files.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
files: List of files to check (None = check all matching patterns)
|
|
257
|
+
config: Optional configuration override
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
QAResult with check execution results
|
|
261
|
+
"""
|
|
262
|
+
if not self._initialized:
|
|
263
|
+
await self.init()
|
|
264
|
+
|
|
265
|
+
start_time = asyncio.get_event_loop().time()
|
|
266
|
+
|
|
267
|
+
# Determine files to check
|
|
268
|
+
target_files = await self._get_target_files(files, config)
|
|
269
|
+
|
|
270
|
+
if not target_files:
|
|
271
|
+
return self._create_result(
|
|
272
|
+
status=QAResultStatus.SKIPPED,
|
|
273
|
+
message="No files to check",
|
|
274
|
+
start_time=start_time,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Build and execute command
|
|
278
|
+
command = self.build_command(target_files, config)
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
exec_result = await self._execute_tool(command, target_files, start_time)
|
|
282
|
+
except TimeoutError:
|
|
283
|
+
# At this point, settings should be initialized by the init() method
|
|
284
|
+
assert self.settings is not None, "Settings should be initialized"
|
|
285
|
+
timeout_msg = (
|
|
286
|
+
f"Tool execution timed out after {self.settings.timeout_seconds}s"
|
|
287
|
+
)
|
|
288
|
+
return self._create_result(
|
|
289
|
+
status=QAResultStatus.ERROR,
|
|
290
|
+
message=timeout_msg,
|
|
291
|
+
details=timeout_msg,
|
|
292
|
+
start_time=start_time,
|
|
293
|
+
)
|
|
294
|
+
except Exception as e:
|
|
295
|
+
error_msg = f"Tool execution failed: {e}"
|
|
296
|
+
# Include full traceback in details for better debugging
|
|
297
|
+
import traceback
|
|
298
|
+
|
|
299
|
+
error_details = f"{error_msg}\n\nFull traceback:\n{traceback.format_exc()}"
|
|
300
|
+
return self._create_result(
|
|
301
|
+
status=QAResultStatus.ERROR,
|
|
302
|
+
message=error_msg,
|
|
303
|
+
details=error_details,
|
|
304
|
+
start_time=start_time,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
# Parse output into issues
|
|
308
|
+
issues = await self.parse_output(exec_result)
|
|
309
|
+
|
|
310
|
+
# Convert to QAResult
|
|
311
|
+
return self._convert_to_qa_result(
|
|
312
|
+
exec_result=exec_result,
|
|
313
|
+
issues=issues,
|
|
314
|
+
target_files=target_files,
|
|
315
|
+
start_time=start_time,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
async def _get_target_files(
|
|
319
|
+
self, files: list[Path] | None, config: QACheckConfig | None
|
|
320
|
+
) -> list[Path]:
|
|
321
|
+
"""Collect target files based on provided list or config patterns.
|
|
322
|
+
|
|
323
|
+
If explicit files are provided, return them. Otherwise, scan the project
|
|
324
|
+
root for files matching include patterns and not matching exclude patterns.
|
|
325
|
+
"""
|
|
326
|
+
if files:
|
|
327
|
+
return files
|
|
328
|
+
|
|
329
|
+
# Fallback to default configuration if none provided
|
|
330
|
+
cfg = config or self.get_default_config()
|
|
331
|
+
|
|
332
|
+
root = Path.cwd() / "crackerjack"
|
|
333
|
+
if not root.exists():
|
|
334
|
+
root = Path.cwd()
|
|
335
|
+
|
|
336
|
+
# Standard directories to always exclude (even if not in config)
|
|
337
|
+
# These are directories that should never be scanned
|
|
338
|
+
standard_excludes = {
|
|
339
|
+
".venv",
|
|
340
|
+
"venv",
|
|
341
|
+
".env",
|
|
342
|
+
"env",
|
|
343
|
+
".tox",
|
|
344
|
+
".nox",
|
|
345
|
+
"__pycache__",
|
|
346
|
+
".pytest_cache",
|
|
347
|
+
".mypy_cache",
|
|
348
|
+
".ruff_cache",
|
|
349
|
+
".git",
|
|
350
|
+
".hg",
|
|
351
|
+
".svn",
|
|
352
|
+
"node_modules",
|
|
353
|
+
".uv",
|
|
354
|
+
"dist",
|
|
355
|
+
"build",
|
|
356
|
+
"*.egg-info",
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# Conditionally exclude tests directory only for comprehensive hooks
|
|
360
|
+
# Fast hooks (formatters, linters) should check test files
|
|
361
|
+
# Comprehensive hooks (type checking, security) can skip tests
|
|
362
|
+
if (
|
|
363
|
+
cfg
|
|
364
|
+
and hasattr(cfg, "is_comprehensive_stage")
|
|
365
|
+
and cfg.is_comprehensive_stage
|
|
366
|
+
):
|
|
367
|
+
standard_excludes.add("tests")
|
|
368
|
+
|
|
369
|
+
candidates = [p for p in root.rglob("*.py")]
|
|
370
|
+
result: list[Path] = []
|
|
371
|
+
for path in candidates:
|
|
372
|
+
# Skip if path contains any standard exclude directory
|
|
373
|
+
if any(excluded in path.parts for excluded in standard_excludes):
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
# Include if matches include patterns
|
|
377
|
+
include = any(path.match(pattern) for pattern in cfg.file_patterns)
|
|
378
|
+
if not include:
|
|
379
|
+
continue
|
|
380
|
+
# Exclude if matches any exclude pattern
|
|
381
|
+
if any(path.match(pattern) for pattern in cfg.exclude_patterns):
|
|
382
|
+
continue
|
|
383
|
+
result.append(path)
|
|
384
|
+
|
|
385
|
+
return result
|
|
386
|
+
|
|
387
|
+
async def _execute_tool(
|
|
388
|
+
self,
|
|
389
|
+
command: list[str],
|
|
390
|
+
target_files: list[Path],
|
|
391
|
+
start_time: float,
|
|
392
|
+
) -> ToolExecutionResult:
|
|
393
|
+
"""Execute tool command asynchronously.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
command: Command to execute
|
|
397
|
+
target_files: Files being processed
|
|
398
|
+
start_time: Start time for duration calculation
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
ToolExecutionResult with execution details
|
|
402
|
+
|
|
403
|
+
Raises:
|
|
404
|
+
asyncio.TimeoutError: If execution exceeds timeout
|
|
405
|
+
Exception: For other execution failures
|
|
406
|
+
"""
|
|
407
|
+
if not self.settings:
|
|
408
|
+
raise RuntimeError("Settings not initialized")
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
process = await asyncio.create_subprocess_exec(
|
|
412
|
+
*command,
|
|
413
|
+
stdout=asyncio.subprocess.PIPE,
|
|
414
|
+
stderr=asyncio.subprocess.PIPE,
|
|
415
|
+
cwd=Path.cwd(),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# At this point, settings should be initialized by the init() method
|
|
419
|
+
assert self.settings is not None, "Settings should be initialized"
|
|
420
|
+
stdout_bytes, stderr_bytes = await asyncio.wait_for(
|
|
421
|
+
process.communicate(),
|
|
422
|
+
timeout=self.settings.timeout_seconds,
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
stdout = stdout_bytes.decode("utf-8", errors="replace")
|
|
426
|
+
stderr = stderr_bytes.decode("utf-8", errors="replace")
|
|
427
|
+
|
|
428
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
429
|
+
|
|
430
|
+
# Non-zero exit code doesn't always mean failure
|
|
431
|
+
# Some tools return 1 when they find issues
|
|
432
|
+
success = process.returncode == 0 or (
|
|
433
|
+
process.returncode == 1 and bool(stdout)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return ToolExecutionResult(
|
|
437
|
+
success=success,
|
|
438
|
+
raw_output=stdout,
|
|
439
|
+
raw_stderr=stderr,
|
|
440
|
+
exit_code=process.returncode or 0,
|
|
441
|
+
execution_time_ms=elapsed_ms,
|
|
442
|
+
tool_version=self._tool_version,
|
|
443
|
+
files_processed=target_files,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
except TimeoutError:
|
|
447
|
+
# Kill the process if it times out
|
|
448
|
+
if process:
|
|
449
|
+
from contextlib import suppress
|
|
450
|
+
|
|
451
|
+
with suppress(Exception):
|
|
452
|
+
process.kill()
|
|
453
|
+
await process.wait()
|
|
454
|
+
raise
|
|
455
|
+
|
|
456
|
+
async def validate_tool_available(self) -> bool:
|
|
457
|
+
"""Check if tool is available in PATH.
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
True if tool is available, False otherwise
|
|
461
|
+
"""
|
|
462
|
+
if self._tool_available is not None:
|
|
463
|
+
return self._tool_available
|
|
464
|
+
|
|
465
|
+
tool_path = shutil.which(self.tool_name)
|
|
466
|
+
self._tool_available = tool_path is not None
|
|
467
|
+
return self._tool_available
|
|
468
|
+
|
|
469
|
+
async def get_tool_version(self) -> str | None:
|
|
470
|
+
"""Get tool version asynchronously.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
Version string or None if unavailable
|
|
474
|
+
"""
|
|
475
|
+
if self._tool_version is not None:
|
|
476
|
+
return self._tool_version
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
process = await asyncio.create_subprocess_exec(
|
|
480
|
+
self.tool_name,
|
|
481
|
+
"--version",
|
|
482
|
+
stdout=asyncio.subprocess.PIPE,
|
|
483
|
+
stderr=asyncio.subprocess.PIPE,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
stdout_bytes, _ = await asyncio.wait_for(
|
|
487
|
+
process.communicate(),
|
|
488
|
+
timeout=10,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
version_output = stdout_bytes.decode("utf-8", errors="replace")
|
|
492
|
+
# Return first line of version output
|
|
493
|
+
return version_output.strip().split("\n")[0]
|
|
494
|
+
|
|
495
|
+
except (TimeoutError, FileNotFoundError, Exception):
|
|
496
|
+
return None
|
|
497
|
+
|
|
498
|
+
async def health_check(self) -> dict[str, t.Any]:
|
|
499
|
+
"""Check adapter and tool health.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Dictionary with health status and metadata
|
|
503
|
+
"""
|
|
504
|
+
base_health = await super().health_check()
|
|
505
|
+
|
|
506
|
+
tool_available = await self.validate_tool_available()
|
|
507
|
+
tool_version = await self.get_tool_version() if tool_available else None
|
|
508
|
+
|
|
509
|
+
return base_health | {
|
|
510
|
+
"tool_name": self.tool_name,
|
|
511
|
+
"tool_available": tool_available,
|
|
512
|
+
"tool_version": tool_version,
|
|
513
|
+
"metadata": self.metadata.dict() if self.metadata else None,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
def _count_issues_by_severity(self, issues: list[ToolIssue]) -> tuple[int, int]:
|
|
517
|
+
"""Count errors and warnings in issues list.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
issues: List of tool issues
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Tuple of (error_count, warning_count)
|
|
524
|
+
"""
|
|
525
|
+
error_count = sum(1 for i in issues if i.severity == "error")
|
|
526
|
+
warning_count = sum(1 for i in issues if i.severity == "warning")
|
|
527
|
+
return error_count, warning_count
|
|
528
|
+
|
|
529
|
+
def _determine_qa_status_and_message(
|
|
530
|
+
self, exec_result: ToolExecutionResult, issues: list[ToolIssue]
|
|
531
|
+
) -> tuple[QAResultStatus, str]:
|
|
532
|
+
"""Determine QA status and message based on execution result and issues.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
exec_result: Tool execution result
|
|
536
|
+
issues: Parsed issues
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Tuple of (status, message)
|
|
540
|
+
"""
|
|
541
|
+
if exec_result.error_message:
|
|
542
|
+
return QAResultStatus.ERROR, exec_result.error_message
|
|
543
|
+
|
|
544
|
+
if not exec_result.success and exec_result.exit_code != 1:
|
|
545
|
+
return (
|
|
546
|
+
QAResultStatus.ERROR,
|
|
547
|
+
f"Tool exited with code {exec_result.exit_code}",
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
if not issues:
|
|
551
|
+
return QAResultStatus.SUCCESS, "No issues found"
|
|
552
|
+
|
|
553
|
+
error_count, warning_count = self._count_issues_by_severity(issues)
|
|
554
|
+
|
|
555
|
+
if error_count > 0:
|
|
556
|
+
message = f"Found {error_count} errors"
|
|
557
|
+
if warning_count > 0:
|
|
558
|
+
message += f" and {warning_count} warnings"
|
|
559
|
+
return QAResultStatus.FAILURE, message
|
|
560
|
+
|
|
561
|
+
return QAResultStatus.WARNING, f"Found {warning_count} warnings"
|
|
562
|
+
|
|
563
|
+
def _build_details_from_issues(self, issues: list[ToolIssue]) -> str:
|
|
564
|
+
"""Build details string from list of issues.
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
issues: List of tool issues
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
Formatted details string
|
|
571
|
+
"""
|
|
572
|
+
details_lines = []
|
|
573
|
+
for issue in issues[:10]: # Limit to first 10 for readability
|
|
574
|
+
loc = str(issue.file_path)
|
|
575
|
+
if issue.line_number:
|
|
576
|
+
loc += f":{issue.line_number}"
|
|
577
|
+
if issue.column_number:
|
|
578
|
+
loc += f":{issue.column_number}"
|
|
579
|
+
details_lines.append(f"{loc}: {issue.message}")
|
|
580
|
+
|
|
581
|
+
if len(issues) > 10:
|
|
582
|
+
details_lines.append(f"... and {len(issues) - 10} more issues")
|
|
583
|
+
|
|
584
|
+
return "\n".join(details_lines)
|
|
585
|
+
|
|
586
|
+
def _convert_to_qa_result(
|
|
587
|
+
self,
|
|
588
|
+
exec_result: ToolExecutionResult,
|
|
589
|
+
issues: list[ToolIssue],
|
|
590
|
+
target_files: list[Path],
|
|
591
|
+
start_time: float,
|
|
592
|
+
) -> QAResult:
|
|
593
|
+
"""Convert tool execution result to QAResult.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
exec_result: Raw execution result
|
|
597
|
+
issues: Parsed issues
|
|
598
|
+
target_files: Files that were checked
|
|
599
|
+
start_time: Start time for duration
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
QAResult
|
|
603
|
+
"""
|
|
604
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
605
|
+
status, message = self._determine_qa_status_and_message(exec_result, issues)
|
|
606
|
+
details = self._build_details_from_issues(issues)
|
|
607
|
+
|
|
608
|
+
return QAResult(
|
|
609
|
+
check_id=self.module_id,
|
|
610
|
+
check_name=self.adapter_name,
|
|
611
|
+
check_type=self._get_check_type(),
|
|
612
|
+
status=status,
|
|
613
|
+
message=message,
|
|
614
|
+
details=details,
|
|
615
|
+
files_checked=target_files,
|
|
616
|
+
files_modified=exec_result.files_modified,
|
|
617
|
+
issues_found=len(issues),
|
|
618
|
+
issues_fixed=len(exec_result.files_modified),
|
|
619
|
+
execution_time_ms=elapsed_ms,
|
|
620
|
+
metadata={
|
|
621
|
+
"tool_version": exec_result.tool_version,
|
|
622
|
+
"exit_code": exec_result.exit_code,
|
|
623
|
+
"error_count": exec_result.error_count,
|
|
624
|
+
"warning_count": exec_result.warning_count,
|
|
625
|
+
},
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
def _create_result(
|
|
629
|
+
self,
|
|
630
|
+
status: QAResultStatus,
|
|
631
|
+
message: str,
|
|
632
|
+
start_time: float,
|
|
633
|
+
files: list[Path] | None = None,
|
|
634
|
+
details: str | None = None,
|
|
635
|
+
) -> QAResult:
|
|
636
|
+
"""Create a QAResult with standard fields.
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
status: Result status
|
|
640
|
+
message: Result message
|
|
641
|
+
start_time: Start time for duration
|
|
642
|
+
files: Optional files list
|
|
643
|
+
details: Optional detailed error output
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
QAResult
|
|
647
|
+
"""
|
|
648
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
649
|
+
|
|
650
|
+
return QAResult(
|
|
651
|
+
check_id=self.module_id,
|
|
652
|
+
check_name=self.adapter_name,
|
|
653
|
+
check_type=self._get_check_type(),
|
|
654
|
+
status=status,
|
|
655
|
+
message=message,
|
|
656
|
+
details=details or "",
|
|
657
|
+
files_checked=files or [],
|
|
658
|
+
execution_time_ms=elapsed_ms,
|
|
659
|
+
metadata={"tool_version": self._tool_version},
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
def _get_check_type(self) -> QACheckType:
|
|
663
|
+
"""Determine QACheckType based on tool name.
|
|
664
|
+
|
|
665
|
+
Subclasses can override for more specific typing.
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
QACheckType
|
|
669
|
+
"""
|
|
670
|
+
# Default mapping based on tool name patterns
|
|
671
|
+
tool_lower = self.tool_name.lower()
|
|
672
|
+
|
|
673
|
+
if "format" in tool_lower or "fmt" in tool_lower:
|
|
674
|
+
return QACheckType.FORMAT
|
|
675
|
+
if any(x in tool_lower for x in ("type", "pyright", "mypy", "zuban")):
|
|
676
|
+
return QACheckType.TYPE
|
|
677
|
+
if any(x in tool_lower for x in ("bandit", "safety", "gitleaks", "semgrep")):
|
|
678
|
+
return QACheckType.SECURITY
|
|
679
|
+
if any(x in tool_lower for x in ("test", "pytest", "unittest")):
|
|
680
|
+
return QACheckType.TEST
|
|
681
|
+
if any(x in tool_lower for x in ("refactor", "refurb", "complex")):
|
|
682
|
+
return QACheckType.REFACTOR
|
|
683
|
+
return QACheckType.LINT
|
|
684
|
+
|
|
685
|
+
def get_default_config(self) -> QACheckConfig:
|
|
686
|
+
"""Get default configuration for tool adapter.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
QACheckConfig with sensible defaults
|
|
690
|
+
"""
|
|
691
|
+
from crackerjack.models.qa_config import QACheckConfig
|
|
692
|
+
|
|
693
|
+
return QACheckConfig(
|
|
694
|
+
check_id=self.module_id,
|
|
695
|
+
check_name=self.adapter_name,
|
|
696
|
+
check_type=self._get_check_type(),
|
|
697
|
+
enabled=True,
|
|
698
|
+
file_patterns=["**/*.py"],
|
|
699
|
+
timeout_seconds=60,
|
|
700
|
+
parallel_safe=True,
|
|
701
|
+
stage="fast",
|
|
702
|
+
settings={
|
|
703
|
+
"tool_name": self.tool_name,
|
|
704
|
+
"fix_enabled": False,
|
|
705
|
+
},
|
|
706
|
+
)
|