crackerjack 0.18.2__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 +96 -2
- crackerjack/__main__.py +637 -138
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +39 -0
- 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/lsp/_base.py +194 -0
- crackerjack/adapters/lsp/_client.py +358 -0
- crackerjack/adapters/lsp/_manager.py +193 -0
- crackerjack/adapters/lsp/skylos.py +283 -0
- crackerjack/adapters/lsp/zuban.py +557 -0
- 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 +66 -0
- crackerjack/agents/architect_agent.py +238 -0
- crackerjack/agents/base.py +167 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +600 -0
- crackerjack/agents/documentation_agent.py +520 -0
- crackerjack/agents/dry_agent.py +585 -0
- 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 +230 -0
- 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/import_optimization_agent.py +1181 -0
- crackerjack/agents/performance_agent.py +325 -0
- crackerjack/agents/performance_helpers.py +205 -0
- crackerjack/agents/proactive_agent.py +55 -0
- crackerjack/agents/refactoring_agent.py +511 -0
- crackerjack/agents/refactoring_helpers.py +247 -0
- crackerjack/agents/security_agent.py +793 -0
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +570 -0
- crackerjack/agents/test_specialist_agent.py +526 -0
- crackerjack/agents/tracker.py +110 -0
- crackerjack/api.py +647 -0
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/cache_handlers.py +209 -0
- crackerjack/cli/cache_handlers_enhanced.py +680 -0
- crackerjack/cli/facade.py +162 -0
- 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 +700 -0
- crackerjack/cli/interactive.py +488 -0
- crackerjack/cli/options.py +1216 -0
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/utils.py +19 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +1307 -0
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +275 -0
- crackerjack/config/global_lock_config.py +207 -0
- crackerjack/config/hooks.py +390 -0
- 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/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +738 -0
- crackerjack/core/autofix_coordinator.py +282 -0
- crackerjack/core/container.py +105 -0
- crackerjack/core/enhanced_container.py +583 -0
- crackerjack/core/file_lifecycle.py +472 -0
- crackerjack/core/performance.py +244 -0
- crackerjack/core/performance_monitor.py +357 -0
- crackerjack/core/phase_coordinator.py +1227 -0
- crackerjack/core/proactive_workflow.py +267 -0
- crackerjack/core/resource_manager.py +425 -0
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +601 -0
- crackerjack/core/session_coordinator.py +239 -0
- crackerjack/core/timeout_manager.py +563 -0
- crackerjack/core/websocket_lifecycle.py +410 -0
- 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 +2243 -0
- 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/INDEX.md +11 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +767 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +1065 -0
- crackerjack/dynamic_config.py +678 -0
- crackerjack/errors.py +378 -0
- 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 +13 -0
- crackerjack/executors/async_hook_executor.py +938 -0
- crackerjack/executors/cached_hook_executor.py +316 -0
- crackerjack/executors/hook_executor.py +1295 -0
- crackerjack/executors/hook_lock_manager.py +708 -0
- crackerjack/executors/individual_hook_executor.py +739 -0
- crackerjack/executors/lsp_aware_hook_executor.py +349 -0
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +433 -0
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +93 -0
- crackerjack/intelligence/README.md +557 -0
- crackerjack/intelligence/__init__.py +37 -0
- crackerjack/intelligence/adaptive_learning.py +693 -0
- crackerjack/intelligence/agent_orchestrator.py +485 -0
- crackerjack/intelligence/agent_registry.py +377 -0
- crackerjack/intelligence/agent_selector.py +439 -0
- crackerjack/intelligence/integration.py +250 -0
- crackerjack/interactive.py +719 -0
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +585 -0
- crackerjack/managers/publish_manager.py +631 -0
- crackerjack/managers/test_command_builder.py +391 -0
- crackerjack/managers/test_executor.py +474 -0
- crackerjack/managers/test_manager.py +1357 -0
- crackerjack/managers/test_progress.py +187 -0
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +352 -0
- crackerjack/mcp/client_runner.py +121 -0
- crackerjack/mcp/context.py +802 -0
- crackerjack/mcp/dashboard.py +657 -0
- crackerjack/mcp/enhanced_progress_monitor.py +493 -0
- crackerjack/mcp/file_monitor.py +394 -0
- crackerjack/mcp/progress_components.py +607 -0
- crackerjack/mcp/progress_monitor.py +1016 -0
- crackerjack/mcp/rate_limiter.py +336 -0
- crackerjack/mcp/server.py +24 -0
- crackerjack/mcp/server_core.py +526 -0
- crackerjack/mcp/service_watchdog.py +505 -0
- crackerjack/mcp/state.py +407 -0
- crackerjack/mcp/task_manager.py +259 -0
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +19 -0
- crackerjack/mcp/tools/core_tools.py +469 -0
- crackerjack/mcp/tools/error_analyzer.py +283 -0
- crackerjack/mcp/tools/execution_tools.py +384 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
- crackerjack/mcp/tools/intelligence_tools.py +264 -0
- crackerjack/mcp/tools/monitoring_tools.py +628 -0
- crackerjack/mcp/tools/proactive_tools.py +367 -0
- crackerjack/mcp/tools/progress_tools.py +222 -0
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +358 -0
- crackerjack/mcp/tools/workflow_executor.py +699 -0
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +54 -0
- crackerjack/mcp/websocket/endpoints.py +492 -0
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +406 -0
- 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 +21 -0
- crackerjack/mcp/websocket/server.py +174 -0
- crackerjack/mcp/websocket/websocket_handler.py +276 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +40 -0
- crackerjack/models/config.py +730 -0
- crackerjack/models/config_adapter.py +265 -0
- crackerjack/models/protocols.py +1535 -0
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/resource_protocols.py +299 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +173 -0
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +405 -0
- crackerjack/monitoring/metrics_collector.py +427 -0
- crackerjack/monitoring/regression_prevention.py +580 -0
- crackerjack/monitoring/websocket_server.py +406 -0
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +894 -0
- 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 +180 -0
- crackerjack/orchestration/execution_strategies.py +361 -0
- 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 +647 -0
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +254 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +264 -0
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/security/__init__.py +0 -0
- crackerjack/security/audit.py +197 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +9 -0
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/ai/contextual_ai_assistant.py +542 -0
- 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/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +617 -0
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +530 -0
- crackerjack/services/cache.py +369 -0
- crackerjack/services/changelog_automation.py +399 -0
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +132 -0
- crackerjack/services/config_merge.py +546 -0
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +493 -0
- crackerjack/services/coverage_badge_service.py +173 -0
- crackerjack/services/coverage_ratchet.py +381 -0
- crackerjack/services/debug.py +733 -0
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +622 -0
- crackerjack/services/documentation_generator.py +493 -0
- crackerjack/services/documentation_service.py +704 -0
- crackerjack/services/enhanced_filesystem.py +497 -0
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +149 -0
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +381 -0
- crackerjack/services/git.py +422 -0
- crackerjack/services/health_metrics.py +615 -0
- crackerjack/services/heatmap_generator.py +744 -0
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +823 -0
- crackerjack/services/input_validator.py +668 -0
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +289 -0
- crackerjack/services/logging.py +228 -0
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +414 -0
- crackerjack/services/metrics.py +587 -0
- 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/monitoring/performance_benchmarks.py +410 -0
- crackerjack/services/monitoring/performance_cache.py +388 -0
- crackerjack/services/monitoring/performance_monitor.py +569 -0
- crackerjack/services/parallel_executor.py +527 -0
- crackerjack/services/pattern_cache.py +333 -0
- crackerjack/services/pattern_detector.py +478 -0
- 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 +523 -0
- 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/quality_baseline.py +395 -0
- crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
- crackerjack/services/quality/quality_intelligence.py +949 -0
- crackerjack/services/regex_patterns.py +58 -0
- crackerjack/services/regex_utils.py +483 -0
- crackerjack/services/secure_path_utils.py +524 -0
- crackerjack/services/secure_status_formatter.py +450 -0
- crackerjack/services/secure_subprocess.py +635 -0
- crackerjack/services/security.py +239 -0
- crackerjack/services/security_logger.py +495 -0
- crackerjack/services/server_manager.py +411 -0
- crackerjack/services/smart_scheduling.py +167 -0
- crackerjack/services/status_authentication.py +460 -0
- crackerjack/services/status_security_manager.py +315 -0
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +441 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +43 -0
- crackerjack/services/unified_config.py +115 -0
- crackerjack/services/validation_rate_limiter.py +220 -0
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +461 -0
- crackerjack/services/version_checker.py +223 -0
- crackerjack/services/websocket_resource_limiter.py +438 -0
- crackerjack/services/zuban_lsp_service.py +391 -0
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/__init__.py +59 -0
- crackerjack/slash_commands/init.md +112 -0
- crackerjack/slash_commands/run.md +197 -0
- crackerjack/slash_commands/status.md +127 -0
- 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_input_validator_patterns.py +236 -0
- crackerjack/tools/validate_regex_patterns.py +188 -0
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/__init__.py +1 -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.45.2.dist-info/METADATA +1678 -0
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -14
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config.yaml +0 -91
- crackerjack/.pytest_cache/.gitignore +0 -2
- crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
- crackerjack/.pytest_cache/README.md +0 -8
- crackerjack/.pytest_cache/v/cache/nodeids +0 -1
- crackerjack/.pytest_cache/v/cache/stepwise +0 -1
- crackerjack/.ruff_cache/.gitignore +0 -1
- crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
- crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
- crackerjack/crackerjack.py +0 -855
- crackerjack/pyproject.toml +0 -214
- crackerjack-0.18.2.dist-info/METADATA +0 -420
- crackerjack-0.18.2.dist-info/RECORD +0 -59
- crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"""Adaptive execution strategy with dependency-aware batching.
|
|
2
|
+
|
|
3
|
+
Implements intelligent parallel execution that:
|
|
4
|
+
- Analyzes dependency graph to identify independent groups
|
|
5
|
+
- Executes hooks in waves (parallel within wave, sequential between waves)
|
|
6
|
+
- Maximizes parallelism while respecting dependencies
|
|
7
|
+
- Handles critical failures with early exit
|
|
8
|
+
|
|
9
|
+
This is the optimal strategy for mixed workloads with some dependencies.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
import typing as t
|
|
17
|
+
from contextlib import suppress
|
|
18
|
+
|
|
19
|
+
from crackerjack.config.hooks import HookDefinition, SecurityLevel
|
|
20
|
+
from crackerjack.models.task import HookResult
|
|
21
|
+
|
|
22
|
+
# Module-level logger
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AdaptiveExecutionStrategy:
|
|
27
|
+
"""Adaptive execution with dependency-aware batching.
|
|
28
|
+
|
|
29
|
+
Features:
|
|
30
|
+
- Topological sort for dependency-aware wave computation
|
|
31
|
+
- Parallel execution within each wave (independent hooks)
|
|
32
|
+
- Sequential execution between waves (respects dependencies)
|
|
33
|
+
- Early exit on critical security failures
|
|
34
|
+
- Resource limiting via asyncio.Semaphore
|
|
35
|
+
|
|
36
|
+
Algorithm:
|
|
37
|
+
1. Compute execution waves using topological sort
|
|
38
|
+
2. Wave 1: All hooks with zero dependencies (parallel)
|
|
39
|
+
3. Wave 2: Hooks whose dependencies completed (parallel within wave)
|
|
40
|
+
4. Wave N: Repeat until all hooks executed
|
|
41
|
+
5. Stop early if critical hook fails
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
```python
|
|
45
|
+
# Dependency graph:
|
|
46
|
+
# gitleaks → bandit
|
|
47
|
+
# zuban → refurb
|
|
48
|
+
# ruff-format (independent)
|
|
49
|
+
|
|
50
|
+
strategy = AdaptiveExecutionStrategy(
|
|
51
|
+
dependency_graph={"bandit": ["gitleaks"], "refurb": ["zuban"]}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Execution waves:
|
|
55
|
+
# Wave 1 (parallel): gitleaks, zuban, ruff-format
|
|
56
|
+
# Wave 2 (parallel): bandit, refurb
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
dependency_graph: dict[str, list[str]],
|
|
63
|
+
max_parallel: int = 4,
|
|
64
|
+
default_timeout: int = 300,
|
|
65
|
+
stop_on_critical_failure: bool = True,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Initialize adaptive execution strategy.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
dependency_graph: Hook dependencies (dependent → list of prerequisites)
|
|
71
|
+
max_parallel: Maximum concurrent executions per wave
|
|
72
|
+
default_timeout: Default timeout per hook in seconds
|
|
73
|
+
stop_on_critical_failure: Stop execution if critical hook fails
|
|
74
|
+
"""
|
|
75
|
+
self.dependency_graph = dependency_graph
|
|
76
|
+
self.max_parallel = max_parallel
|
|
77
|
+
self.default_timeout = default_timeout
|
|
78
|
+
self.stop_on_critical_failure = stop_on_critical_failure
|
|
79
|
+
|
|
80
|
+
logger.debug(
|
|
81
|
+
"AdaptiveExecutionStrategy initialized",
|
|
82
|
+
extra={
|
|
83
|
+
"dependency_count": len(dependency_graph),
|
|
84
|
+
"max_parallel": max_parallel,
|
|
85
|
+
"default_timeout": default_timeout,
|
|
86
|
+
"stop_on_critical_failure": stop_on_critical_failure,
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def execute(
|
|
91
|
+
self,
|
|
92
|
+
hooks: list[HookDefinition],
|
|
93
|
+
max_parallel: int | None = None,
|
|
94
|
+
timeout: int | None = None,
|
|
95
|
+
executor_callable: t.Callable[[HookDefinition], t.Awaitable[HookResult]]
|
|
96
|
+
| None = None,
|
|
97
|
+
progress_callback: t.Callable[[int, int], None] | None = None,
|
|
98
|
+
progress_start_callback: t.Callable[[int, int], None] | None = None,
|
|
99
|
+
) -> list[HookResult]:
|
|
100
|
+
"""Execute hooks in dependency-aware waves.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
hooks: List of hook definitions to execute
|
|
104
|
+
max_parallel: Optional override for max concurrent executions per wave
|
|
105
|
+
timeout: Optional override for default timeout
|
|
106
|
+
executor_callable: Async function that executes a single hook
|
|
107
|
+
progress_callback: Optional callback(completed, total) for progress updates
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
List of HookResult objects (combined from all waves)
|
|
111
|
+
"""
|
|
112
|
+
if not hooks:
|
|
113
|
+
logger.debug("No hooks to execute")
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
max_par = max_parallel or self.max_parallel
|
|
117
|
+
timeout_sec = timeout or self.default_timeout
|
|
118
|
+
|
|
119
|
+
logger.info(
|
|
120
|
+
"Starting adaptive execution",
|
|
121
|
+
extra={
|
|
122
|
+
"hook_count": len(hooks),
|
|
123
|
+
"max_parallel": max_par,
|
|
124
|
+
"timeout": timeout_sec,
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Compute execution waves using topological sort
|
|
129
|
+
waves = self._compute_execution_waves(hooks)
|
|
130
|
+
|
|
131
|
+
logger.info(
|
|
132
|
+
f"Computed {len(waves)} execution waves",
|
|
133
|
+
extra={
|
|
134
|
+
"wave_count": len(waves),
|
|
135
|
+
"waves": [
|
|
136
|
+
{
|
|
137
|
+
"wave_idx": idx,
|
|
138
|
+
"hook_count": len(wave),
|
|
139
|
+
"hooks": [h.name for h in wave],
|
|
140
|
+
}
|
|
141
|
+
for idx, wave in enumerate(waves, 1)
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Execute each wave in parallel, waves sequentially
|
|
147
|
+
all_results = []
|
|
148
|
+
total_hooks = len(hooks)
|
|
149
|
+
completed_hooks = 0
|
|
150
|
+
|
|
151
|
+
# Initial progress report
|
|
152
|
+
if progress_callback:
|
|
153
|
+
progress_callback(0, total_hooks)
|
|
154
|
+
|
|
155
|
+
for wave_idx, wave_hooks in enumerate(waves, 1):
|
|
156
|
+
logger.info(
|
|
157
|
+
f"Executing wave {wave_idx}/{len(waves)}",
|
|
158
|
+
extra={
|
|
159
|
+
"wave_idx": wave_idx,
|
|
160
|
+
"total_waves": len(waves),
|
|
161
|
+
"hooks_in_wave": len(wave_hooks),
|
|
162
|
+
"hook_names": [h.name for h in wave_hooks],
|
|
163
|
+
},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Execute this wave in parallel
|
|
167
|
+
# Define per-hook completion notifier to update progress incrementally
|
|
168
|
+
def on_hook_start() -> None:
|
|
169
|
+
nonlocal completed_hooks
|
|
170
|
+
# Use started count as the metric to tick the progress bar
|
|
171
|
+
started = completed_hooks + 1
|
|
172
|
+
if progress_start_callback:
|
|
173
|
+
progress_start_callback(started, total_hooks)
|
|
174
|
+
|
|
175
|
+
def on_hook_complete() -> None:
|
|
176
|
+
nonlocal completed_hooks
|
|
177
|
+
completed_hooks += 1
|
|
178
|
+
if progress_callback:
|
|
179
|
+
progress_callback(completed_hooks, total_hooks)
|
|
180
|
+
|
|
181
|
+
wave_results = await self._execute_wave(
|
|
182
|
+
wave_hooks,
|
|
183
|
+
max_parallel=max_par,
|
|
184
|
+
timeout=timeout_sec,
|
|
185
|
+
executor_callable=executor_callable,
|
|
186
|
+
on_hook_complete=on_hook_complete,
|
|
187
|
+
on_hook_start=on_hook_start,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
all_results.extend(wave_results)
|
|
191
|
+
|
|
192
|
+
logger.info(
|
|
193
|
+
f"Wave {wave_idx} complete",
|
|
194
|
+
extra={
|
|
195
|
+
"wave_idx": wave_idx,
|
|
196
|
+
"passed": sum(1 for r in wave_results if r.status == "passed"),
|
|
197
|
+
"failed": sum(1 for r in wave_results if r.status == "failed"),
|
|
198
|
+
"errors": sum(
|
|
199
|
+
1 for r in wave_results if r.status in ("timeout", "error")
|
|
200
|
+
),
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Check for critical failures that should stop execution
|
|
205
|
+
if self.stop_on_critical_failure and self._has_critical_failure(
|
|
206
|
+
wave_hooks, wave_results
|
|
207
|
+
):
|
|
208
|
+
remaining_hooks = sum(len(w) for w in waves[wave_idx:])
|
|
209
|
+
logger.warning(
|
|
210
|
+
"Critical failure detected, stopping execution",
|
|
211
|
+
extra={
|
|
212
|
+
"completed_waves": wave_idx,
|
|
213
|
+
"remaining_waves": len(waves) - wave_idx,
|
|
214
|
+
"remaining_hooks": remaining_hooks,
|
|
215
|
+
},
|
|
216
|
+
)
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
logger.info(
|
|
220
|
+
"Adaptive execution complete",
|
|
221
|
+
extra={
|
|
222
|
+
"total_hooks": len(all_results),
|
|
223
|
+
"executed_waves": min(wave_idx, len(waves)),
|
|
224
|
+
"total_waves": len(waves),
|
|
225
|
+
"passed": sum(1 for r in all_results if r.status == "passed"),
|
|
226
|
+
"failed": sum(1 for r in all_results if r.status == "failed"),
|
|
227
|
+
"errors": sum(
|
|
228
|
+
1 for r in all_results if r.status in ("timeout", "error")
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return all_results
|
|
234
|
+
|
|
235
|
+
def _compute_execution_waves(
|
|
236
|
+
self,
|
|
237
|
+
hooks: list[HookDefinition],
|
|
238
|
+
) -> list[list[HookDefinition]]:
|
|
239
|
+
"""Compute execution waves using topological sort.
|
|
240
|
+
|
|
241
|
+
Algorithm:
|
|
242
|
+
1. Build in-degree map (count of unsatisfied dependencies per hook)
|
|
243
|
+
2. Wave 1: All hooks with zero dependencies
|
|
244
|
+
3. After wave completes, decrement in-degree for dependent hooks
|
|
245
|
+
4. Next wave: Hooks that now have zero dependencies
|
|
246
|
+
5. Repeat until all hooks assigned to waves
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of waves, each wave contains hooks that can execute in parallel
|
|
250
|
+
|
|
251
|
+
Raises:
|
|
252
|
+
ValueError: If circular dependency detected
|
|
253
|
+
"""
|
|
254
|
+
hook_map = {hook.name: hook for hook in hooks}
|
|
255
|
+
in_degree = self._build_in_degree_map(hooks, hook_map)
|
|
256
|
+
|
|
257
|
+
logger.debug(
|
|
258
|
+
"Computed in-degree map",
|
|
259
|
+
extra={
|
|
260
|
+
"in_degree": {name: degree for name, degree in in_degree.items()},
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
waves = self._compute_waves_from_dependencies(hooks, hook_map, in_degree)
|
|
265
|
+
|
|
266
|
+
logger.debug(
|
|
267
|
+
f"Computed {len(waves)} execution waves",
|
|
268
|
+
extra={
|
|
269
|
+
"wave_count": len(waves),
|
|
270
|
+
"total_hooks": sum(len(wave) for wave in waves),
|
|
271
|
+
},
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
return waves
|
|
275
|
+
|
|
276
|
+
def _build_in_degree_map(
|
|
277
|
+
self,
|
|
278
|
+
hooks: list[HookDefinition],
|
|
279
|
+
hook_map: dict[str, HookDefinition],
|
|
280
|
+
) -> dict[str, int]:
|
|
281
|
+
"""Build in-degree map counting dependencies per hook."""
|
|
282
|
+
in_degree = {hook.name: 0 for hook in hooks}
|
|
283
|
+
for hook_name in hook_map:
|
|
284
|
+
if hook_name in self.dependency_graph:
|
|
285
|
+
deps = self.dependency_graph[hook_name]
|
|
286
|
+
in_degree[hook_name] = sum(1 for dep in deps if dep in hook_map)
|
|
287
|
+
return in_degree
|
|
288
|
+
|
|
289
|
+
def _compute_waves_from_dependencies(
|
|
290
|
+
self,
|
|
291
|
+
hooks: list[HookDefinition],
|
|
292
|
+
hook_map: dict[str, HookDefinition],
|
|
293
|
+
in_degree: dict[str, int],
|
|
294
|
+
) -> list[list[HookDefinition]]:
|
|
295
|
+
"""Compute waves by iteratively processing hooks with satisfied dependencies."""
|
|
296
|
+
waves: list[list[HookDefinition]] = []
|
|
297
|
+
remaining_hooks = set(hook_map.keys())
|
|
298
|
+
iteration = 0
|
|
299
|
+
max_iterations = len(hooks) + 1
|
|
300
|
+
|
|
301
|
+
while remaining_hooks:
|
|
302
|
+
iteration += 1
|
|
303
|
+
if self._check_max_iterations_exceeded(
|
|
304
|
+
iteration, max_iterations, remaining_hooks, hook_map, waves
|
|
305
|
+
):
|
|
306
|
+
break
|
|
307
|
+
|
|
308
|
+
ready_hooks = self._find_ready_hooks(remaining_hooks, in_degree, hook_map)
|
|
309
|
+
self._validate_wave_progress(ready_hooks, remaining_hooks, in_degree)
|
|
310
|
+
|
|
311
|
+
waves.append(ready_hooks)
|
|
312
|
+
self._log_wave_ready(waves, ready_hooks, remaining_hooks)
|
|
313
|
+
|
|
314
|
+
self._update_dependencies_after_wave(
|
|
315
|
+
ready_hooks, remaining_hooks, in_degree
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
return waves
|
|
319
|
+
|
|
320
|
+
def _check_max_iterations_exceeded(
|
|
321
|
+
self,
|
|
322
|
+
iteration: int,
|
|
323
|
+
max_iterations: int,
|
|
324
|
+
remaining_hooks: set[str],
|
|
325
|
+
hook_map: dict[str, HookDefinition],
|
|
326
|
+
waves: list[list[HookDefinition]],
|
|
327
|
+
) -> bool:
|
|
328
|
+
"""Check if max iterations exceeded and handle fallback."""
|
|
329
|
+
if iteration > max_iterations:
|
|
330
|
+
logger.error(
|
|
331
|
+
"Max iterations exceeded, circular dependency suspected",
|
|
332
|
+
extra={
|
|
333
|
+
"remaining_hooks": list(remaining_hooks),
|
|
334
|
+
"iteration": iteration,
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
waves.append([hook_map[name] for name in remaining_hooks])
|
|
338
|
+
return True
|
|
339
|
+
return False
|
|
340
|
+
|
|
341
|
+
def _find_ready_hooks(
|
|
342
|
+
self,
|
|
343
|
+
remaining_hooks: set[str],
|
|
344
|
+
in_degree: dict[str, int],
|
|
345
|
+
hook_map: dict[str, HookDefinition],
|
|
346
|
+
) -> list[HookDefinition]:
|
|
347
|
+
"""Find all hooks with zero dependencies in current wave."""
|
|
348
|
+
return [hook_map[name] for name in remaining_hooks if in_degree[name] == 0]
|
|
349
|
+
|
|
350
|
+
def _validate_wave_progress(
|
|
351
|
+
self,
|
|
352
|
+
ready_hooks: list[HookDefinition],
|
|
353
|
+
remaining_hooks: set[str],
|
|
354
|
+
in_degree: dict[str, int],
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Validate that wave has ready hooks or raise circular dependency error."""
|
|
357
|
+
if not ready_hooks:
|
|
358
|
+
logger.error(
|
|
359
|
+
"Circular dependency detected",
|
|
360
|
+
extra={
|
|
361
|
+
"remaining_hooks": list(remaining_hooks),
|
|
362
|
+
"in_degrees": {name: in_degree[name] for name in remaining_hooks},
|
|
363
|
+
},
|
|
364
|
+
)
|
|
365
|
+
raise ValueError(
|
|
366
|
+
f"Circular dependency detected in hooks: {list(remaining_hooks)}"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
def _log_wave_ready(
|
|
370
|
+
self,
|
|
371
|
+
waves: list[list[HookDefinition]],
|
|
372
|
+
ready_hooks: list[HookDefinition],
|
|
373
|
+
remaining_hooks: set[str],
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Log wave readiness information."""
|
|
376
|
+
logger.debug(
|
|
377
|
+
f"Wave {len(waves)} ready",
|
|
378
|
+
extra={
|
|
379
|
+
"wave_idx": len(waves),
|
|
380
|
+
"hooks": [h.name for h in ready_hooks],
|
|
381
|
+
"remaining_count": len(remaining_hooks) - len(ready_hooks),
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
def _update_dependencies_after_wave(
|
|
386
|
+
self,
|
|
387
|
+
ready_hooks: list[HookDefinition],
|
|
388
|
+
remaining_hooks: set[str],
|
|
389
|
+
in_degree: dict[str, int],
|
|
390
|
+
) -> None:
|
|
391
|
+
"""Update in-degrees after wave completion."""
|
|
392
|
+
for hook in ready_hooks:
|
|
393
|
+
remaining_hooks.remove(hook.name)
|
|
394
|
+
|
|
395
|
+
for hook in ready_hooks:
|
|
396
|
+
for dependent_name, deps in self.dependency_graph.items():
|
|
397
|
+
if hook.name in deps and dependent_name in remaining_hooks:
|
|
398
|
+
in_degree[dependent_name] -= 1
|
|
399
|
+
logger.debug(
|
|
400
|
+
f"Decremented in-degree for {dependent_name}",
|
|
401
|
+
extra={
|
|
402
|
+
"dependent": dependent_name,
|
|
403
|
+
"completed_dependency": hook.name,
|
|
404
|
+
"new_in_degree": in_degree[dependent_name],
|
|
405
|
+
},
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
async def _execute_wave(
|
|
409
|
+
self,
|
|
410
|
+
hooks: list[HookDefinition],
|
|
411
|
+
max_parallel: int,
|
|
412
|
+
timeout: int,
|
|
413
|
+
executor_callable: t.Callable[[HookDefinition], t.Awaitable[HookResult]] | None,
|
|
414
|
+
on_hook_complete: t.Callable[[], None] | None = None,
|
|
415
|
+
on_hook_start: t.Callable[[], None] | None = None,
|
|
416
|
+
) -> list[HookResult]:
|
|
417
|
+
"""Execute a single wave of hooks in parallel.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
hooks: Hooks to execute in this wave
|
|
421
|
+
max_parallel: Maximum concurrent executions
|
|
422
|
+
timeout: Timeout per hook in seconds
|
|
423
|
+
executor_callable: Async function that executes a single hook
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
List of HookResult objects for this wave
|
|
427
|
+
"""
|
|
428
|
+
if not hooks:
|
|
429
|
+
return []
|
|
430
|
+
|
|
431
|
+
semaphore = asyncio.Semaphore(max_parallel)
|
|
432
|
+
|
|
433
|
+
async def execute_with_limit(hook: HookDefinition) -> HookResult:
|
|
434
|
+
"""Execute hook with semaphore and timeout."""
|
|
435
|
+
async with semaphore:
|
|
436
|
+
self._notify_hook_start(on_hook_start)
|
|
437
|
+
result = await self._execute_single_hook_in_wave(
|
|
438
|
+
hook, timeout, executor_callable
|
|
439
|
+
)
|
|
440
|
+
self._notify_hook_complete(on_hook_complete)
|
|
441
|
+
return result
|
|
442
|
+
|
|
443
|
+
tasks = [execute_with_limit(hook) for hook in hooks]
|
|
444
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
445
|
+
return self._process_wave_results(hooks, results)
|
|
446
|
+
|
|
447
|
+
def _notify_hook_start(self, on_hook_start: t.Callable[[], None] | None) -> None:
|
|
448
|
+
"""Notify that hook execution has started."""
|
|
449
|
+
if on_hook_start:
|
|
450
|
+
with suppress(Exception):
|
|
451
|
+
on_hook_start()
|
|
452
|
+
|
|
453
|
+
def _notify_hook_complete(
|
|
454
|
+
self, on_hook_complete: t.Callable[[], None] | None
|
|
455
|
+
) -> None:
|
|
456
|
+
"""Notify that hook execution has completed."""
|
|
457
|
+
if on_hook_complete:
|
|
458
|
+
with suppress(Exception):
|
|
459
|
+
on_hook_complete()
|
|
460
|
+
|
|
461
|
+
async def _execute_single_hook_in_wave(
|
|
462
|
+
self,
|
|
463
|
+
hook: HookDefinition,
|
|
464
|
+
timeout: int,
|
|
465
|
+
executor_callable: t.Callable[[HookDefinition], t.Awaitable[HookResult]] | None,
|
|
466
|
+
) -> HookResult:
|
|
467
|
+
"""Execute a single hook with timeout and error handling."""
|
|
468
|
+
try:
|
|
469
|
+
hook_timeout = hook.timeout or timeout
|
|
470
|
+
logger.debug(
|
|
471
|
+
f"Executing hook {hook.name}",
|
|
472
|
+
extra={
|
|
473
|
+
"hook": hook.name,
|
|
474
|
+
"timeout": hook_timeout,
|
|
475
|
+
},
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
result = await self._run_hook_with_executor(
|
|
479
|
+
hook, hook_timeout, executor_callable
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
logger.debug(
|
|
483
|
+
f"Hook {hook.name} completed",
|
|
484
|
+
extra={
|
|
485
|
+
"hook": hook.name,
|
|
486
|
+
"status": result.status,
|
|
487
|
+
"duration": result.duration,
|
|
488
|
+
},
|
|
489
|
+
)
|
|
490
|
+
return result
|
|
491
|
+
|
|
492
|
+
except TimeoutError:
|
|
493
|
+
return self._create_timeout_result(hook, hook.timeout or timeout)
|
|
494
|
+
except Exception as e:
|
|
495
|
+
return self._create_error_result(hook, e)
|
|
496
|
+
|
|
497
|
+
async def _run_hook_with_executor(
|
|
498
|
+
self,
|
|
499
|
+
hook: HookDefinition,
|
|
500
|
+
hook_timeout: int,
|
|
501
|
+
executor_callable: t.Callable[[HookDefinition], t.Awaitable[HookResult]] | None,
|
|
502
|
+
) -> HookResult:
|
|
503
|
+
"""Run hook with executor or return placeholder."""
|
|
504
|
+
if executor_callable:
|
|
505
|
+
return await asyncio.wait_for(executor_callable(hook), timeout=hook_timeout)
|
|
506
|
+
return HookResult(
|
|
507
|
+
id=hook.name,
|
|
508
|
+
name=hook.name,
|
|
509
|
+
status="passed",
|
|
510
|
+
duration=0.0,
|
|
511
|
+
issues_found=[], # Initialize with empty list to match expected format
|
|
512
|
+
files_processed=0,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
def _create_timeout_result(
|
|
516
|
+
self, hook: HookDefinition, hook_timeout: int
|
|
517
|
+
) -> HookResult:
|
|
518
|
+
"""Create HookResult for timeout condition."""
|
|
519
|
+
logger.warning(
|
|
520
|
+
f"Hook {hook.name} timed out",
|
|
521
|
+
extra={
|
|
522
|
+
"hook": hook.name,
|
|
523
|
+
"timeout": hook_timeout,
|
|
524
|
+
},
|
|
525
|
+
)
|
|
526
|
+
return HookResult(
|
|
527
|
+
id=hook.name,
|
|
528
|
+
name=hook.name,
|
|
529
|
+
status="timeout",
|
|
530
|
+
duration=hook_timeout,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
def _create_error_result(
|
|
534
|
+
self, hook: HookDefinition, exception: Exception
|
|
535
|
+
) -> HookResult:
|
|
536
|
+
"""Create HookResult for exception condition."""
|
|
537
|
+
logger.error(
|
|
538
|
+
f"Hook {hook.name} raised exception",
|
|
539
|
+
extra={
|
|
540
|
+
"hook": hook.name,
|
|
541
|
+
"exception": str(exception),
|
|
542
|
+
"exception_type": type(exception).__name__,
|
|
543
|
+
},
|
|
544
|
+
)
|
|
545
|
+
return HookResult(
|
|
546
|
+
id=hook.name,
|
|
547
|
+
name=hook.name,
|
|
548
|
+
status="error",
|
|
549
|
+
duration=0.0,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def _process_wave_results(
|
|
553
|
+
self,
|
|
554
|
+
hooks: list[HookDefinition],
|
|
555
|
+
results: list[HookResult | BaseException],
|
|
556
|
+
) -> list[HookResult]:
|
|
557
|
+
"""Process results from asyncio.gather, converting exceptions to HookResults."""
|
|
558
|
+
final_results = []
|
|
559
|
+
for hook, result in zip(hooks, results):
|
|
560
|
+
if isinstance(result, HookResult):
|
|
561
|
+
final_results.append(result)
|
|
562
|
+
else:
|
|
563
|
+
logger.error(
|
|
564
|
+
"Unexpected exception from gather",
|
|
565
|
+
extra={
|
|
566
|
+
"hook": hook.name,
|
|
567
|
+
"exception": str(result),
|
|
568
|
+
"exception_type": type(result).__name__,
|
|
569
|
+
},
|
|
570
|
+
)
|
|
571
|
+
final_results.append(
|
|
572
|
+
HookResult(
|
|
573
|
+
id=hook.name,
|
|
574
|
+
name=hook.name,
|
|
575
|
+
status="error",
|
|
576
|
+
duration=0.0,
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
return final_results
|
|
580
|
+
|
|
581
|
+
def _has_critical_failure(
|
|
582
|
+
self,
|
|
583
|
+
hooks: list[HookDefinition],
|
|
584
|
+
results: list[HookResult],
|
|
585
|
+
) -> bool:
|
|
586
|
+
"""Check if any critical failure occurred in wave results.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
hooks: Hooks that were executed
|
|
590
|
+
results: Results from execution
|
|
591
|
+
|
|
592
|
+
Returns:
|
|
593
|
+
True if a critical-level hook failed, False otherwise
|
|
594
|
+
"""
|
|
595
|
+
for hook, result in zip(hooks, results):
|
|
596
|
+
if hook.security_level == SecurityLevel.CRITICAL:
|
|
597
|
+
if result.status in ("failed", "timeout", "error"):
|
|
598
|
+
logger.warning(
|
|
599
|
+
f"Critical hook {hook.name} failed",
|
|
600
|
+
extra={
|
|
601
|
+
"hook": hook.name,
|
|
602
|
+
"status": result.status,
|
|
603
|
+
"security_level": "critical",
|
|
604
|
+
},
|
|
605
|
+
)
|
|
606
|
+
return True
|
|
607
|
+
|
|
608
|
+
return False
|
|
609
|
+
|
|
610
|
+
def get_execution_order(
|
|
611
|
+
self,
|
|
612
|
+
hooks: list[HookDefinition],
|
|
613
|
+
) -> list[list[HookDefinition]]:
|
|
614
|
+
"""Return batches of hooks for execution (waves).
|
|
615
|
+
|
|
616
|
+
Args:
|
|
617
|
+
hooks: List of hook definitions
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
List of batches (waves), each containing hooks that can execute in parallel
|
|
621
|
+
"""
|
|
622
|
+
try:
|
|
623
|
+
return self._compute_execution_waves(hooks)
|
|
624
|
+
except ValueError as e:
|
|
625
|
+
# Circular dependency detected, fall back to sequential
|
|
626
|
+
logger.error(
|
|
627
|
+
f"Failed to compute waves: {e}, falling back to sequential execution",
|
|
628
|
+
extra={"error": str(e)},
|
|
629
|
+
)
|
|
630
|
+
return [[hook] for hook in hooks] # Sequential fallback
|