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,884 @@
|
|
|
1
|
+
"""Generic utility check adapter for simple ACB Quality Assurance checks.
|
|
2
|
+
|
|
3
|
+
This adapter handles simple, configuration-driven checks like whitespace,
|
|
4
|
+
EOF newlines, YAML/TOML validation, file size limits, and dependency locks.
|
|
5
|
+
|
|
6
|
+
ACB Patterns:
|
|
7
|
+
- MODULE_ID and MODULE_STATUS at module level
|
|
8
|
+
- depends.set() registration after class definition
|
|
9
|
+
- Configuration-driven validators and fixers
|
|
10
|
+
- Async execution with semaphore control
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import asyncio
|
|
16
|
+
import logging
|
|
17
|
+
import subprocess
|
|
18
|
+
import typing as t
|
|
19
|
+
from contextlib import suppress
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from uuid import UUID, uuid4
|
|
23
|
+
|
|
24
|
+
import tomli
|
|
25
|
+
import yaml
|
|
26
|
+
from acb.depends import depends
|
|
27
|
+
from pydantic import Field, field_validator
|
|
28
|
+
|
|
29
|
+
from crackerjack.adapters._qa_adapter_base import QAAdapterBase, QABaseSettings
|
|
30
|
+
from crackerjack.models.qa_results import QACheckType, QAResult, QAResultStatus
|
|
31
|
+
from crackerjack.services.regex_patterns import CompiledPatternCache
|
|
32
|
+
|
|
33
|
+
if t.TYPE_CHECKING:
|
|
34
|
+
from crackerjack.models.qa_config import QACheckConfig
|
|
35
|
+
|
|
36
|
+
# ACB Module Registration (REQUIRED)
|
|
37
|
+
MODULE_ID = uuid4()
|
|
38
|
+
MODULE_STATUS = "stable"
|
|
39
|
+
|
|
40
|
+
# Module-level logger for structured logging
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UtilityCheckType(str, Enum):
|
|
45
|
+
"""Types of utility checks supported by this adapter."""
|
|
46
|
+
|
|
47
|
+
TEXT_PATTERN = "text_pattern"
|
|
48
|
+
SYNTAX_VALIDATION = "syntax_validation"
|
|
49
|
+
SIZE_CHECK = "size_check"
|
|
50
|
+
EOF_NEWLINE = "eof_newline"
|
|
51
|
+
DEPENDENCY_LOCK = "dependency_lock"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class UtilityCheckSettings(QABaseSettings):
|
|
55
|
+
"""Settings for utility check adapter.
|
|
56
|
+
|
|
57
|
+
Configuration-driven checks are defined here or loaded from YAML.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
check_type: UtilityCheckType = Field(
|
|
61
|
+
...,
|
|
62
|
+
description="Type of utility check to perform",
|
|
63
|
+
)
|
|
64
|
+
pattern: str | None = Field(
|
|
65
|
+
None,
|
|
66
|
+
description="Regex pattern for TEXT_PATTERN checks",
|
|
67
|
+
)
|
|
68
|
+
parser_type: str | None = Field(
|
|
69
|
+
None,
|
|
70
|
+
description="Parser type for SYNTAX_VALIDATION (yaml, toml, json)",
|
|
71
|
+
)
|
|
72
|
+
max_size_bytes: int | None = Field(
|
|
73
|
+
None,
|
|
74
|
+
ge=0,
|
|
75
|
+
description="Maximum file size in bytes for SIZE_CHECK",
|
|
76
|
+
)
|
|
77
|
+
auto_fix: bool = Field(
|
|
78
|
+
False,
|
|
79
|
+
description="Whether to automatically fix issues when possible",
|
|
80
|
+
)
|
|
81
|
+
lock_command: list[str] | None = Field(
|
|
82
|
+
None,
|
|
83
|
+
description="Command to run for DEPENDENCY_LOCK checks",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@field_validator("pattern")
|
|
87
|
+
def validate_pattern(cls, v: str | None, values: dict[str, t.Any]) -> str | None:
|
|
88
|
+
"""Validate regex pattern using safe compilation.
|
|
89
|
+
|
|
90
|
+
Uses CompiledPatternCache for security and performance.
|
|
91
|
+
Pattern validation only occurs if pattern is provided.
|
|
92
|
+
Runtime check() will enforce required patterns.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
v: Pattern string to validate
|
|
96
|
+
values: Other field values (unused)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Validated pattern string or None
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
ValueError: If pattern compilation fails
|
|
103
|
+
"""
|
|
104
|
+
if v is not None:
|
|
105
|
+
try:
|
|
106
|
+
# Use cached, safe pattern compilation
|
|
107
|
+
CompiledPatternCache.get_compiled_pattern(v)
|
|
108
|
+
except ValueError as e:
|
|
109
|
+
raise ValueError(f"Invalid regex pattern: {e}")
|
|
110
|
+
return v
|
|
111
|
+
|
|
112
|
+
@field_validator("parser_type")
|
|
113
|
+
def validate_parser_type(
|
|
114
|
+
cls, v: str | None, values: dict[str, t.Any]
|
|
115
|
+
) -> str | None:
|
|
116
|
+
"""Validate parser type for SYNTAX_VALIDATION checks.
|
|
117
|
+
|
|
118
|
+
Parser type validation only occurs if parser_type is provided.
|
|
119
|
+
Runtime check() will enforce required parser_type.
|
|
120
|
+
"""
|
|
121
|
+
if v is not None and v not in ("yaml", "toml", "json"):
|
|
122
|
+
raise ValueError(f"Unsupported parser type: {v}")
|
|
123
|
+
return v
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class UtilityCheckAdapter(QAAdapterBase):
|
|
127
|
+
"""Generic adapter for configuration-driven utility checks.
|
|
128
|
+
|
|
129
|
+
Handles simple checks like:
|
|
130
|
+
- Trailing whitespace detection/fixing
|
|
131
|
+
- EOF newline enforcement
|
|
132
|
+
- YAML/TOML/JSON syntax validation
|
|
133
|
+
- File size limits
|
|
134
|
+
- Dependency lock verification (uv.lock)
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
```python
|
|
138
|
+
from uuid import uuid7
|
|
139
|
+
from crackerjack.adapters.qa.utility_check import (
|
|
140
|
+
UtilityCheckAdapter,
|
|
141
|
+
UtilityCheckSettings,
|
|
142
|
+
UtilityCheckType,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
settings = UtilityCheckSettings(
|
|
146
|
+
check_type=UtilityCheckType.TEXT_PATTERN,
|
|
147
|
+
pattern=r"\\s+$",
|
|
148
|
+
auto_fix=True,
|
|
149
|
+
)
|
|
150
|
+
adapter = UtilityCheckAdapter(settings=settings)
|
|
151
|
+
await adapter.init()
|
|
152
|
+
result = await adapter.check(files=[Path("src/file.py")])
|
|
153
|
+
```
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
settings: UtilityCheckSettings | None = None
|
|
157
|
+
|
|
158
|
+
def __init__(self, settings: UtilityCheckSettings | None = None) -> None:
|
|
159
|
+
"""Initialize utility check adapter.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
settings: Optional settings override
|
|
163
|
+
"""
|
|
164
|
+
super().__init__()
|
|
165
|
+
if settings:
|
|
166
|
+
self.settings = settings
|
|
167
|
+
|
|
168
|
+
async def init(self) -> None:
|
|
169
|
+
"""Initialize adapter with settings validation."""
|
|
170
|
+
if not self.settings:
|
|
171
|
+
# Default to trailing whitespace check if no settings provided
|
|
172
|
+
self.settings = UtilityCheckSettings(
|
|
173
|
+
check_type=UtilityCheckType.TEXT_PATTERN,
|
|
174
|
+
pattern=r"\s+$",
|
|
175
|
+
auto_fix=True,
|
|
176
|
+
)
|
|
177
|
+
await super().init()
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def adapter_name(self) -> str:
|
|
181
|
+
"""Human-readable adapter name."""
|
|
182
|
+
if self.settings:
|
|
183
|
+
return f"UtilityCheck ({self.settings.check_type.value})"
|
|
184
|
+
return "UtilityCheck"
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def module_id(self) -> UUID:
|
|
188
|
+
"""Reference to module-level MODULE_ID."""
|
|
189
|
+
return MODULE_ID
|
|
190
|
+
|
|
191
|
+
async def check(
|
|
192
|
+
self,
|
|
193
|
+
files: list[Path] | None = None,
|
|
194
|
+
config: QACheckConfig | None = None,
|
|
195
|
+
) -> QAResult:
|
|
196
|
+
"""Execute utility check on files.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
files: List of files to check (None = check all matching patterns)
|
|
200
|
+
config: Optional configuration override
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
QAResult with check execution results
|
|
204
|
+
"""
|
|
205
|
+
if not self._initialized:
|
|
206
|
+
await self.init()
|
|
207
|
+
|
|
208
|
+
if not self.settings:
|
|
209
|
+
raise RuntimeError("Settings not initialized")
|
|
210
|
+
|
|
211
|
+
start_time = asyncio.get_event_loop().time()
|
|
212
|
+
|
|
213
|
+
# Determine files to check
|
|
214
|
+
target_files = await self._get_target_files(files, config)
|
|
215
|
+
|
|
216
|
+
if not target_files:
|
|
217
|
+
return self._create_skipped_result("No files to check", start_time)
|
|
218
|
+
|
|
219
|
+
# Execute appropriate check based on type
|
|
220
|
+
check_type = self.settings.check_type
|
|
221
|
+
|
|
222
|
+
if check_type == UtilityCheckType.TEXT_PATTERN:
|
|
223
|
+
result = await self._check_text_pattern(target_files, start_time)
|
|
224
|
+
elif check_type == UtilityCheckType.EOF_NEWLINE:
|
|
225
|
+
result = await self._check_eof_newline(target_files, start_time)
|
|
226
|
+
elif check_type == UtilityCheckType.SYNTAX_VALIDATION:
|
|
227
|
+
result = await self._check_syntax_validation(target_files, start_time)
|
|
228
|
+
elif check_type == UtilityCheckType.SIZE_CHECK:
|
|
229
|
+
result = await self._check_size_limit(target_files, start_time)
|
|
230
|
+
elif check_type == UtilityCheckType.DEPENDENCY_LOCK:
|
|
231
|
+
result = await self._check_dependency_lock(target_files, start_time)
|
|
232
|
+
else:
|
|
233
|
+
raise ValueError(f"Unsupported check type: {check_type}")
|
|
234
|
+
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _is_file_excluded(
|
|
239
|
+
file_path: Path,
|
|
240
|
+
exclude_patterns: list[str],
|
|
241
|
+
) -> bool:
|
|
242
|
+
"""Check if file matches any exclude pattern.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
file_path: File to check
|
|
246
|
+
exclude_patterns: List of glob patterns to exclude
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
True if file should be excluded
|
|
250
|
+
"""
|
|
251
|
+
for exclude_pattern in exclude_patterns:
|
|
252
|
+
if file_path.match(exclude_pattern):
|
|
253
|
+
return True
|
|
254
|
+
return False
|
|
255
|
+
|
|
256
|
+
def _apply_exclude_filters(
|
|
257
|
+
self,
|
|
258
|
+
files: list[Path],
|
|
259
|
+
exclude_patterns: list[str],
|
|
260
|
+
) -> list[Path]:
|
|
261
|
+
"""Filter files by exclude patterns.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
files: Files to filter
|
|
265
|
+
exclude_patterns: List of glob patterns to exclude
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Filtered file list
|
|
269
|
+
"""
|
|
270
|
+
return [
|
|
271
|
+
file_path
|
|
272
|
+
for file_path in files
|
|
273
|
+
if not self._is_file_excluded(file_path, exclude_patterns)
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
async def _get_target_files(
|
|
277
|
+
self,
|
|
278
|
+
files: list[Path] | None,
|
|
279
|
+
config: QACheckConfig | None,
|
|
280
|
+
) -> list[Path]:
|
|
281
|
+
"""Get list of files to check based on patterns.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
files: Explicit file list or None for pattern matching
|
|
285
|
+
config: Configuration with file patterns
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
List of Path objects to check
|
|
289
|
+
"""
|
|
290
|
+
if files:
|
|
291
|
+
return files
|
|
292
|
+
|
|
293
|
+
if not self.settings:
|
|
294
|
+
return []
|
|
295
|
+
|
|
296
|
+
# Use config patterns if available, otherwise use settings patterns
|
|
297
|
+
patterns = config.file_patterns if config else self.settings.file_patterns
|
|
298
|
+
exclude_patterns = (
|
|
299
|
+
config.exclude_patterns if config else self.settings.exclude_patterns
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Simple glob-based file discovery
|
|
303
|
+
# In production, this would integrate with git/project structure
|
|
304
|
+
target_files: list[Path] = []
|
|
305
|
+
for pattern in patterns:
|
|
306
|
+
target_files.extend(Path.cwd().glob(pattern))
|
|
307
|
+
|
|
308
|
+
# Filter out excluded files
|
|
309
|
+
if exclude_patterns:
|
|
310
|
+
target_files = self._apply_exclude_filters(target_files, exclude_patterns)
|
|
311
|
+
|
|
312
|
+
return [f for f in target_files if f.is_file()]
|
|
313
|
+
|
|
314
|
+
def _process_file_lines(
|
|
315
|
+
self,
|
|
316
|
+
lines: list[str],
|
|
317
|
+
pattern: t.Pattern[str],
|
|
318
|
+
) -> tuple[list[str], int]:
|
|
319
|
+
"""Process file lines for pattern violations.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
lines: File lines to process
|
|
323
|
+
pattern: Compiled regex pattern
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Tuple of (fixed_lines, issues_count)
|
|
327
|
+
"""
|
|
328
|
+
fixed_lines = []
|
|
329
|
+
issues_count = 0
|
|
330
|
+
|
|
331
|
+
for line in lines:
|
|
332
|
+
if pattern.search(line):
|
|
333
|
+
issues_count += 1
|
|
334
|
+
if self.settings and self.settings.auto_fix:
|
|
335
|
+
# Remove pattern matches (e.g., trailing whitespace)
|
|
336
|
+
fixed_lines.append(pattern.sub("", line))
|
|
337
|
+
else:
|
|
338
|
+
fixed_lines.append(line)
|
|
339
|
+
else:
|
|
340
|
+
fixed_lines.append(line)
|
|
341
|
+
|
|
342
|
+
return fixed_lines, issues_count
|
|
343
|
+
|
|
344
|
+
def _create_pattern_result(
|
|
345
|
+
self,
|
|
346
|
+
files: list[Path],
|
|
347
|
+
issues_found: int,
|
|
348
|
+
issues_fixed: int,
|
|
349
|
+
files_modified: list[Path],
|
|
350
|
+
elapsed_ms: float,
|
|
351
|
+
) -> QAResult:
|
|
352
|
+
"""Create QAResult for pattern check.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
files: Files checked
|
|
356
|
+
issues_found: Number of issues found
|
|
357
|
+
issues_fixed: Number of issues fixed
|
|
358
|
+
files_modified: List of modified files
|
|
359
|
+
elapsed_ms: Execution time in milliseconds
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
QAResult with appropriate status
|
|
363
|
+
"""
|
|
364
|
+
if issues_found == 0:
|
|
365
|
+
return QAResult(
|
|
366
|
+
check_id=MODULE_ID,
|
|
367
|
+
check_name=self.adapter_name,
|
|
368
|
+
check_type=QACheckType.FORMAT,
|
|
369
|
+
status=QAResultStatus.SUCCESS,
|
|
370
|
+
message="No pattern violations found",
|
|
371
|
+
files_checked=files,
|
|
372
|
+
execution_time_ms=elapsed_ms,
|
|
373
|
+
)
|
|
374
|
+
elif issues_fixed == issues_found:
|
|
375
|
+
return QAResult(
|
|
376
|
+
check_id=MODULE_ID,
|
|
377
|
+
check_name=self.adapter_name,
|
|
378
|
+
check_type=QACheckType.FORMAT,
|
|
379
|
+
status=QAResultStatus.SUCCESS,
|
|
380
|
+
message=f"Fixed {issues_fixed} pattern violations",
|
|
381
|
+
files_checked=files,
|
|
382
|
+
files_modified=files_modified,
|
|
383
|
+
issues_found=issues_found,
|
|
384
|
+
issues_fixed=issues_fixed,
|
|
385
|
+
execution_time_ms=elapsed_ms,
|
|
386
|
+
)
|
|
387
|
+
return QAResult(
|
|
388
|
+
check_id=MODULE_ID,
|
|
389
|
+
check_name=self.adapter_name,
|
|
390
|
+
check_type=QACheckType.FORMAT,
|
|
391
|
+
status=QAResultStatus.FAILURE,
|
|
392
|
+
message=f"Found {issues_found} pattern violations",
|
|
393
|
+
files_checked=files,
|
|
394
|
+
files_modified=files_modified,
|
|
395
|
+
issues_found=issues_found,
|
|
396
|
+
issues_fixed=issues_fixed,
|
|
397
|
+
execution_time_ms=elapsed_ms,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
async def _check_text_pattern(
|
|
401
|
+
self,
|
|
402
|
+
files: list[Path],
|
|
403
|
+
start_time: float,
|
|
404
|
+
) -> QAResult:
|
|
405
|
+
"""Check files for text pattern violations.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
files: Files to check
|
|
409
|
+
start_time: Check start time
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
QAResult with pattern check results
|
|
413
|
+
"""
|
|
414
|
+
if not self.settings or not self.settings.pattern:
|
|
415
|
+
raise ValueError("Pattern not configured")
|
|
416
|
+
|
|
417
|
+
# Use safe cached pattern compilation
|
|
418
|
+
pattern = CompiledPatternCache.get_compiled_pattern(self.settings.pattern)
|
|
419
|
+
issues_found = 0
|
|
420
|
+
issues_fixed = 0
|
|
421
|
+
files_modified: list[Path] = []
|
|
422
|
+
|
|
423
|
+
for file_path in files:
|
|
424
|
+
try:
|
|
425
|
+
content = file_path.read_text()
|
|
426
|
+
lines = content.splitlines(keepends=True)
|
|
427
|
+
|
|
428
|
+
fixed_lines, file_issues = self._process_file_lines(lines, pattern)
|
|
429
|
+
issues_found += file_issues
|
|
430
|
+
|
|
431
|
+
if file_issues > 0 and self.settings.auto_fix:
|
|
432
|
+
file_path.write_text("".join(fixed_lines))
|
|
433
|
+
files_modified.append(file_path)
|
|
434
|
+
issues_fixed += file_issues
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
# Add structured logging for failures
|
|
438
|
+
logger.warning(
|
|
439
|
+
"Failed to check file for pattern violations",
|
|
440
|
+
extra={
|
|
441
|
+
"file_path": str(file_path),
|
|
442
|
+
"check_type": self.settings.check_type.value,
|
|
443
|
+
"pattern": self.settings.pattern,
|
|
444
|
+
"error": str(e),
|
|
445
|
+
"error_type": type(e).__name__,
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
continue
|
|
449
|
+
|
|
450
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
451
|
+
|
|
452
|
+
return self._create_pattern_result(
|
|
453
|
+
files, issues_found, issues_fixed, files_modified, elapsed_ms
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
async def _check_eof_newline(
|
|
457
|
+
self,
|
|
458
|
+
files: list[Path],
|
|
459
|
+
start_time: float,
|
|
460
|
+
) -> QAResult:
|
|
461
|
+
"""Check files for missing EOF newlines.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
files: Files to check
|
|
465
|
+
start_time: Check start time
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
QAResult with EOF check results
|
|
469
|
+
"""
|
|
470
|
+
issues_found = 0
|
|
471
|
+
issues_fixed = 0
|
|
472
|
+
files_modified: list[Path] = []
|
|
473
|
+
|
|
474
|
+
for file_path in files:
|
|
475
|
+
try:
|
|
476
|
+
content = file_path.read_text()
|
|
477
|
+
if content and not content.endswith("\n"):
|
|
478
|
+
issues_found += 1
|
|
479
|
+
if self.settings and self.settings.auto_fix:
|
|
480
|
+
file_path.write_text(content + "\n")
|
|
481
|
+
issues_fixed += 1
|
|
482
|
+
files_modified.append(file_path)
|
|
483
|
+
except Exception as e:
|
|
484
|
+
# Add structured logging for failures
|
|
485
|
+
logger.warning(
|
|
486
|
+
"Failed to check file for EOF newline",
|
|
487
|
+
extra={
|
|
488
|
+
"file_path": str(file_path),
|
|
489
|
+
"check_type": self.settings.check_type.value
|
|
490
|
+
if self.settings
|
|
491
|
+
else "unknown",
|
|
492
|
+
"auto_fix": self.settings.auto_fix if self.settings else False,
|
|
493
|
+
"error": str(e),
|
|
494
|
+
"error_type": type(e).__name__,
|
|
495
|
+
},
|
|
496
|
+
)
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
500
|
+
|
|
501
|
+
if issues_found == 0:
|
|
502
|
+
return QAResult(
|
|
503
|
+
check_id=MODULE_ID,
|
|
504
|
+
check_name=self.adapter_name,
|
|
505
|
+
check_type=QACheckType.FORMAT,
|
|
506
|
+
status=QAResultStatus.SUCCESS,
|
|
507
|
+
message="All files have proper EOF newlines",
|
|
508
|
+
files_checked=files,
|
|
509
|
+
execution_time_ms=elapsed_ms,
|
|
510
|
+
)
|
|
511
|
+
elif issues_fixed == issues_found:
|
|
512
|
+
return QAResult(
|
|
513
|
+
check_id=MODULE_ID,
|
|
514
|
+
check_name=self.adapter_name,
|
|
515
|
+
check_type=QACheckType.FORMAT,
|
|
516
|
+
status=QAResultStatus.SUCCESS,
|
|
517
|
+
message=f"Fixed {issues_fixed} EOF newline issues",
|
|
518
|
+
files_checked=files,
|
|
519
|
+
files_modified=files_modified,
|
|
520
|
+
issues_found=issues_found,
|
|
521
|
+
issues_fixed=issues_fixed,
|
|
522
|
+
execution_time_ms=elapsed_ms,
|
|
523
|
+
)
|
|
524
|
+
return QAResult(
|
|
525
|
+
check_id=MODULE_ID,
|
|
526
|
+
check_name=self.adapter_name,
|
|
527
|
+
check_type=QACheckType.FORMAT,
|
|
528
|
+
status=QAResultStatus.FAILURE,
|
|
529
|
+
message=f"Found {issues_found} files missing EOF newlines",
|
|
530
|
+
files_checked=files,
|
|
531
|
+
issues_found=issues_found,
|
|
532
|
+
execution_time_ms=elapsed_ms,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
async def _check_syntax_validation(
|
|
536
|
+
self,
|
|
537
|
+
files: list[Path],
|
|
538
|
+
start_time: float,
|
|
539
|
+
) -> QAResult:
|
|
540
|
+
"""Validate file syntax (YAML/TOML/JSON).
|
|
541
|
+
|
|
542
|
+
Args:
|
|
543
|
+
files: Files to check
|
|
544
|
+
start_time: Check start time
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
QAResult with syntax validation results
|
|
548
|
+
"""
|
|
549
|
+
if not self.settings or not self.settings.parser_type:
|
|
550
|
+
raise ValueError("Parser type not configured")
|
|
551
|
+
|
|
552
|
+
parser_type = self.settings.parser_type
|
|
553
|
+
issues_found = 0
|
|
554
|
+
error_details: list[str] = []
|
|
555
|
+
|
|
556
|
+
for file_path in files:
|
|
557
|
+
try:
|
|
558
|
+
content = file_path.read_text()
|
|
559
|
+
|
|
560
|
+
if parser_type == "yaml":
|
|
561
|
+
yaml.safe_load(content)
|
|
562
|
+
elif parser_type == "toml":
|
|
563
|
+
tomli.loads(content)
|
|
564
|
+
elif parser_type == "json":
|
|
565
|
+
import json
|
|
566
|
+
|
|
567
|
+
json.loads(content)
|
|
568
|
+
|
|
569
|
+
except Exception as e:
|
|
570
|
+
issues_found += 1
|
|
571
|
+
error_details.append(f"{file_path}: {e}")
|
|
572
|
+
# Add structured logging for failures
|
|
573
|
+
logger.warning(
|
|
574
|
+
"Failed to validate file syntax",
|
|
575
|
+
extra={
|
|
576
|
+
"file_path": str(file_path),
|
|
577
|
+
"check_type": self.settings.check_type.value
|
|
578
|
+
if self.settings
|
|
579
|
+
else "unknown",
|
|
580
|
+
"parser_type": parser_type,
|
|
581
|
+
"error": str(e),
|
|
582
|
+
"error_type": type(e).__name__,
|
|
583
|
+
},
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
587
|
+
|
|
588
|
+
if issues_found == 0:
|
|
589
|
+
return QAResult(
|
|
590
|
+
check_id=MODULE_ID,
|
|
591
|
+
check_name=self.adapter_name,
|
|
592
|
+
check_type=QACheckType.FORMAT,
|
|
593
|
+
status=QAResultStatus.SUCCESS,
|
|
594
|
+
message=f"All {parser_type.upper()} files are valid",
|
|
595
|
+
files_checked=files,
|
|
596
|
+
execution_time_ms=elapsed_ms,
|
|
597
|
+
)
|
|
598
|
+
return QAResult(
|
|
599
|
+
check_id=MODULE_ID,
|
|
600
|
+
check_name=self.adapter_name,
|
|
601
|
+
check_type=QACheckType.FORMAT,
|
|
602
|
+
status=QAResultStatus.FAILURE,
|
|
603
|
+
message=f"Found {issues_found} {parser_type.upper()} syntax errors",
|
|
604
|
+
details="\n".join(error_details),
|
|
605
|
+
files_checked=files,
|
|
606
|
+
issues_found=issues_found,
|
|
607
|
+
execution_time_ms=elapsed_ms,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
async def _check_size_limit(
|
|
611
|
+
self,
|
|
612
|
+
files: list[Path],
|
|
613
|
+
start_time: float,
|
|
614
|
+
) -> QAResult:
|
|
615
|
+
"""Check files against size limits.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
files: Files to check
|
|
619
|
+
start_time: Check start time
|
|
620
|
+
|
|
621
|
+
Returns:
|
|
622
|
+
QAResult with size check results
|
|
623
|
+
"""
|
|
624
|
+
if not self.settings or self.settings.max_size_bytes is None:
|
|
625
|
+
raise ValueError("Max size not configured")
|
|
626
|
+
|
|
627
|
+
max_size = self.settings.max_size_bytes
|
|
628
|
+
issues_found = 0
|
|
629
|
+
large_files: list[str] = []
|
|
630
|
+
|
|
631
|
+
for file_path in files:
|
|
632
|
+
try:
|
|
633
|
+
file_size = file_path.stat().st_size
|
|
634
|
+
if file_size > max_size:
|
|
635
|
+
issues_found += 1
|
|
636
|
+
size_mb = file_size / (1024 * 1024)
|
|
637
|
+
max_mb = max_size / (1024 * 1024)
|
|
638
|
+
large_files.append(
|
|
639
|
+
f"{file_path} ({size_mb:.2f} MB > {max_mb:.2f} MB)"
|
|
640
|
+
)
|
|
641
|
+
except Exception as e:
|
|
642
|
+
# Add structured logging for failures
|
|
643
|
+
logger.warning(
|
|
644
|
+
"Failed to check file size",
|
|
645
|
+
extra={
|
|
646
|
+
"file_path": str(file_path),
|
|
647
|
+
"check_type": self.settings.check_type.value
|
|
648
|
+
if self.settings
|
|
649
|
+
else "unknown",
|
|
650
|
+
"max_size_bytes": max_size,
|
|
651
|
+
"error": str(e),
|
|
652
|
+
"error_type": type(e).__name__,
|
|
653
|
+
},
|
|
654
|
+
)
|
|
655
|
+
continue
|
|
656
|
+
|
|
657
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
658
|
+
|
|
659
|
+
if issues_found == 0:
|
|
660
|
+
return QAResult(
|
|
661
|
+
check_id=MODULE_ID,
|
|
662
|
+
check_name=self.adapter_name,
|
|
663
|
+
check_type=QACheckType.FORMAT,
|
|
664
|
+
status=QAResultStatus.SUCCESS,
|
|
665
|
+
message="All files within size limits",
|
|
666
|
+
files_checked=files,
|
|
667
|
+
execution_time_ms=elapsed_ms,
|
|
668
|
+
)
|
|
669
|
+
return QAResult(
|
|
670
|
+
check_id=MODULE_ID,
|
|
671
|
+
check_name=self.adapter_name,
|
|
672
|
+
check_type=QACheckType.FORMAT,
|
|
673
|
+
status=QAResultStatus.FAILURE,
|
|
674
|
+
message=f"Found {issues_found} files exceeding size limit",
|
|
675
|
+
details="\n".join(large_files),
|
|
676
|
+
files_checked=files,
|
|
677
|
+
issues_found=issues_found,
|
|
678
|
+
execution_time_ms=elapsed_ms,
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
async def _check_dependency_lock(
|
|
682
|
+
self,
|
|
683
|
+
files: list[Path],
|
|
684
|
+
start_time: float,
|
|
685
|
+
) -> QAResult:
|
|
686
|
+
"""Check dependency lock file integrity.
|
|
687
|
+
|
|
688
|
+
Args:
|
|
689
|
+
files: Files to check (typically uv.lock)
|
|
690
|
+
start_time: Check start time
|
|
691
|
+
|
|
692
|
+
Returns:
|
|
693
|
+
QAResult with dependency lock check results
|
|
694
|
+
"""
|
|
695
|
+
if not self.settings or not self.settings.lock_command:
|
|
696
|
+
raise ValueError("Lock command not configured")
|
|
697
|
+
|
|
698
|
+
try:
|
|
699
|
+
result = subprocess.run(
|
|
700
|
+
self.settings.lock_command,
|
|
701
|
+
capture_output=True,
|
|
702
|
+
text=True,
|
|
703
|
+
timeout=self.settings.timeout_seconds,
|
|
704
|
+
check=False,
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
708
|
+
|
|
709
|
+
if result.returncode == 0:
|
|
710
|
+
return QAResult(
|
|
711
|
+
check_id=MODULE_ID,
|
|
712
|
+
check_name=self.adapter_name,
|
|
713
|
+
check_type=QACheckType.FORMAT,
|
|
714
|
+
status=QAResultStatus.SUCCESS,
|
|
715
|
+
message="Dependency lock file is up to date",
|
|
716
|
+
files_checked=files,
|
|
717
|
+
execution_time_ms=elapsed_ms,
|
|
718
|
+
)
|
|
719
|
+
else:
|
|
720
|
+
return QAResult(
|
|
721
|
+
check_id=MODULE_ID,
|
|
722
|
+
check_name=self.adapter_name,
|
|
723
|
+
check_type=QACheckType.FORMAT,
|
|
724
|
+
status=QAResultStatus.FAILURE,
|
|
725
|
+
message="Dependency lock file is out of sync",
|
|
726
|
+
details=result.stderr or result.stdout,
|
|
727
|
+
files_checked=files,
|
|
728
|
+
issues_found=1,
|
|
729
|
+
execution_time_ms=elapsed_ms,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
except subprocess.TimeoutExpired:
|
|
733
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
734
|
+
return QAResult(
|
|
735
|
+
check_id=MODULE_ID,
|
|
736
|
+
check_name=self.adapter_name,
|
|
737
|
+
check_type=QACheckType.FORMAT,
|
|
738
|
+
status=QAResultStatus.ERROR,
|
|
739
|
+
message="Dependency lock check timed out",
|
|
740
|
+
files_checked=files,
|
|
741
|
+
execution_time_ms=elapsed_ms,
|
|
742
|
+
)
|
|
743
|
+
except Exception as e:
|
|
744
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
745
|
+
return QAResult(
|
|
746
|
+
check_id=MODULE_ID,
|
|
747
|
+
check_name=self.adapter_name,
|
|
748
|
+
check_type=QACheckType.FORMAT,
|
|
749
|
+
status=QAResultStatus.ERROR,
|
|
750
|
+
message=f"Dependency lock check failed: {e}",
|
|
751
|
+
files_checked=files,
|
|
752
|
+
execution_time_ms=elapsed_ms,
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
def _create_skipped_result(self, reason: str, start_time: float) -> QAResult:
|
|
756
|
+
"""Create a skipped result.
|
|
757
|
+
|
|
758
|
+
Args:
|
|
759
|
+
reason: Reason for skipping
|
|
760
|
+
start_time: Check start time
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
QAResult with skipped status
|
|
764
|
+
"""
|
|
765
|
+
elapsed_ms = (asyncio.get_event_loop().time() - start_time) * 1000
|
|
766
|
+
return QAResult(
|
|
767
|
+
check_id=MODULE_ID,
|
|
768
|
+
check_name=self.adapter_name,
|
|
769
|
+
check_type=QACheckType.FORMAT,
|
|
770
|
+
status=QAResultStatus.SKIPPED,
|
|
771
|
+
message=reason,
|
|
772
|
+
execution_time_ms=elapsed_ms,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
@staticmethod
|
|
776
|
+
def _get_check_type() -> QACheckType:
|
|
777
|
+
"""Determine QA check type based on utility check type.
|
|
778
|
+
|
|
779
|
+
Utility checks map to FORMAT type since they can format/fix files.
|
|
780
|
+
|
|
781
|
+
Returns:
|
|
782
|
+
QACheckType.FORMAT for all utility checks
|
|
783
|
+
"""
|
|
784
|
+
return QACheckType.FORMAT
|
|
785
|
+
|
|
786
|
+
def _get_syntax_validation_patterns(self) -> list[str]:
|
|
787
|
+
"""Get file patterns for syntax validation based on parser type."""
|
|
788
|
+
if not self.settings:
|
|
789
|
+
return ["**/*.yaml", "**/*.toml", "**/*.json"]
|
|
790
|
+
|
|
791
|
+
parser_patterns = {
|
|
792
|
+
"yaml": ["**/*.yaml", "**/*.yml"],
|
|
793
|
+
"toml": ["**/*.toml"],
|
|
794
|
+
"json": ["**/*.json"],
|
|
795
|
+
}
|
|
796
|
+
return parser_patterns.get(
|
|
797
|
+
self.settings.parser_type,
|
|
798
|
+
["**/*.yaml", "**/*.toml", "**/*.json"],
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
def _get_config_for_check_type(
|
|
802
|
+
self, check_type: UtilityCheckType
|
|
803
|
+
) -> tuple[list[str], str, bool]:
|
|
804
|
+
"""Get configuration tuple for a specific check type.
|
|
805
|
+
|
|
806
|
+
Args:
|
|
807
|
+
check_type: The utility check type
|
|
808
|
+
|
|
809
|
+
Returns:
|
|
810
|
+
Tuple of (file_patterns, stage, is_formatter)
|
|
811
|
+
"""
|
|
812
|
+
if check_type == UtilityCheckType.TEXT_PATTERN:
|
|
813
|
+
return (
|
|
814
|
+
["**/*.py", "**/*.yaml", "**/*.toml", "**/*.json", "**/*.md"],
|
|
815
|
+
"fast",
|
|
816
|
+
self.settings.auto_fix if self.settings else False,
|
|
817
|
+
)
|
|
818
|
+
if check_type == UtilityCheckType.EOF_NEWLINE:
|
|
819
|
+
return (["**/*"], "fast", True)
|
|
820
|
+
if check_type == UtilityCheckType.SYNTAX_VALIDATION:
|
|
821
|
+
return (self._get_syntax_validation_patterns(), "comprehensive", False)
|
|
822
|
+
if check_type == UtilityCheckType.SIZE_CHECK:
|
|
823
|
+
return (["**/*"], "fast", False)
|
|
824
|
+
if check_type == UtilityCheckType.DEPENDENCY_LOCK:
|
|
825
|
+
return (["uv.lock", "requirements.lock"], "fast", False)
|
|
826
|
+
|
|
827
|
+
# Fallback defaults
|
|
828
|
+
return (["**/*.py"], "fast", False)
|
|
829
|
+
|
|
830
|
+
def get_default_config(self) -> QACheckConfig:
|
|
831
|
+
"""Get default configuration for utility checks.
|
|
832
|
+
|
|
833
|
+
Configuration varies based on the check_type setting:
|
|
834
|
+
- TEXT_PATTERN: fast stage, common text files
|
|
835
|
+
- EOF_NEWLINE: fast stage, all files, is_formatter=True
|
|
836
|
+
- SYNTAX_VALIDATION: comprehensive stage, parser-specific patterns
|
|
837
|
+
- SIZE_CHECK: fast stage, all files
|
|
838
|
+
- DEPENDENCY_LOCK: fast stage, lock files only
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
QACheckConfig with check-type-specific defaults
|
|
842
|
+
"""
|
|
843
|
+
from crackerjack.models.qa_config import QACheckConfig
|
|
844
|
+
|
|
845
|
+
# Get configuration based on check type
|
|
846
|
+
if self.settings and self.settings.check_type:
|
|
847
|
+
file_patterns, stage, is_formatter = self._get_config_for_check_type(
|
|
848
|
+
self.settings.check_type
|
|
849
|
+
)
|
|
850
|
+
else:
|
|
851
|
+
# No settings, use generic defaults
|
|
852
|
+
file_patterns = ["**/*.py", "**/*.yaml", "**/*.toml", "**/*.json"]
|
|
853
|
+
stage = "fast"
|
|
854
|
+
is_formatter = False
|
|
855
|
+
|
|
856
|
+
return QACheckConfig(
|
|
857
|
+
check_id=MODULE_ID,
|
|
858
|
+
check_name=self.adapter_name,
|
|
859
|
+
check_type=self._get_check_type(),
|
|
860
|
+
enabled=True,
|
|
861
|
+
file_patterns=file_patterns,
|
|
862
|
+
exclude_patterns=[
|
|
863
|
+
"**/.*",
|
|
864
|
+
"**/.git/**",
|
|
865
|
+
"**/.venv/**",
|
|
866
|
+
"**/node_modules/**",
|
|
867
|
+
"**/__pycache__/**",
|
|
868
|
+
],
|
|
869
|
+
timeout_seconds=60, # Utility checks should be fast
|
|
870
|
+
parallel_safe=True,
|
|
871
|
+
is_formatter=is_formatter,
|
|
872
|
+
stage=stage,
|
|
873
|
+
settings={
|
|
874
|
+
"check_type": self.settings.check_type.value
|
|
875
|
+
if self.settings
|
|
876
|
+
else "text_pattern",
|
|
877
|
+
"auto_fix": self.settings.auto_fix if self.settings else False,
|
|
878
|
+
},
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
# ACB Registration (REQUIRED at module level)
|
|
883
|
+
with suppress(Exception):
|
|
884
|
+
depends.set(UtilityCheckAdapter)
|