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
|
@@ -7,10 +7,18 @@ from dataclasses import dataclass, field
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
|
|
10
|
+
from acb.console import Console
|
|
11
|
+
from acb.depends import Inject, depends
|
|
12
|
+
from acb.logger import Logger
|
|
13
|
+
|
|
14
|
+
from crackerjack.models.protocols import (
|
|
15
|
+
PerformanceBenchmarkProtocol,
|
|
16
|
+
PerformanceBenchmarkServiceProtocol,
|
|
17
|
+
ServiceProtocol,
|
|
18
|
+
)
|
|
19
|
+
from crackerjack.services.memory_optimizer import LazyLoader, MemoryOptimizer
|
|
20
|
+
from crackerjack.services.monitoring.performance_cache import get_performance_cache
|
|
21
|
+
from crackerjack.services.monitoring.performance_monitor import get_performance_monitor
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
@dataclass
|
|
@@ -88,10 +96,11 @@ class BenchmarkSuite:
|
|
|
88
96
|
|
|
89
97
|
|
|
90
98
|
class PerformanceBenchmarker:
|
|
91
|
-
|
|
92
|
-
|
|
99
|
+
@depends.inject
|
|
100
|
+
def __init__(self, logger: Inject[Logger]) -> None:
|
|
101
|
+
self._logger = logger
|
|
93
102
|
self._monitor = get_performance_monitor()
|
|
94
|
-
self._memory_optimizer =
|
|
103
|
+
self._memory_optimizer = MemoryOptimizer.get_instance()
|
|
95
104
|
self._cache = get_performance_cache()
|
|
96
105
|
|
|
97
106
|
self._test_iterations = 3
|
|
@@ -144,8 +153,6 @@ class PerformanceBenchmarker:
|
|
|
144
153
|
"optimized_start"
|
|
145
154
|
)
|
|
146
155
|
|
|
147
|
-
from crackerjack.services.memory_optimizer import LazyLoader
|
|
148
|
-
|
|
149
156
|
lazy_objects = []
|
|
150
157
|
for i in range(50):
|
|
151
158
|
|
|
@@ -298,15 +305,94 @@ class PerformanceBenchmarker:
|
|
|
298
305
|
self._logger.info(f"Exported benchmark results to {output_path}")
|
|
299
306
|
|
|
300
307
|
|
|
301
|
-
class PerformanceBenchmarkService
|
|
308
|
+
class PerformanceBenchmarkService(
|
|
309
|
+
PerformanceBenchmarkProtocol, PerformanceBenchmarkServiceProtocol, ServiceProtocol
|
|
310
|
+
):
|
|
302
311
|
"""Service wrapper for performance benchmarking in workflow orchestration."""
|
|
303
312
|
|
|
304
|
-
|
|
313
|
+
@depends.inject
|
|
314
|
+
def __init__(
|
|
315
|
+
self, console: Console, logger: Inject[Logger], pkg_path: Path
|
|
316
|
+
) -> None:
|
|
305
317
|
self._console = console
|
|
306
318
|
self._pkg_path = pkg_path
|
|
307
|
-
self._benchmarker = PerformanceBenchmarker()
|
|
308
|
-
self._logger =
|
|
319
|
+
self._benchmarker = PerformanceBenchmarker(logger=logger)
|
|
320
|
+
self._logger = logger
|
|
321
|
+
|
|
322
|
+
def initialize(self) -> None:
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
def cleanup(self) -> None:
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
def health_check(self) -> bool:
|
|
329
|
+
return True
|
|
330
|
+
|
|
331
|
+
def shutdown(self) -> None:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def metrics(self) -> dict[str, t.Any]:
|
|
335
|
+
return {}
|
|
336
|
+
|
|
337
|
+
def is_healthy(self) -> bool:
|
|
338
|
+
return True
|
|
339
|
+
|
|
340
|
+
def register_resource(self, resource: t.Any) -> None:
|
|
341
|
+
pass
|
|
342
|
+
|
|
343
|
+
def cleanup_resource(self, resource: t.Any) -> None:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
def record_error(self, error: Exception) -> None:
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
def increment_requests(self) -> None:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
def get_custom_metric(self, name: str) -> t.Any:
|
|
353
|
+
return None
|
|
354
|
+
|
|
355
|
+
def set_custom_metric(self, name: str, value: t.Any) -> None:
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
# Methods for PerformanceBenchmarkProtocol
|
|
359
|
+
def run_benchmark(self, operation: str) -> dict[str, t.Any]:
|
|
360
|
+
"""Run a specific benchmark operation."""
|
|
361
|
+
start_time = time.time()
|
|
362
|
+
# Since we don't have operation-specific benchmark methods,
|
|
363
|
+
# we'll return basic timing information
|
|
364
|
+
return {
|
|
365
|
+
"operation": operation,
|
|
366
|
+
"start_time": start_time,
|
|
367
|
+
"end_time": time.time(),
|
|
368
|
+
"duration": time.time() - start_time,
|
|
369
|
+
"status": "completed",
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
def get_report(self) -> dict[str, t.Any]:
|
|
373
|
+
"""Get performance report."""
|
|
374
|
+
# Return a basic report based on workflow metrics
|
|
375
|
+
return {
|
|
376
|
+
"timestamp": datetime.now().isoformat(),
|
|
377
|
+
"report_type": "performance_summary",
|
|
378
|
+
"total_benchmarks": 0, # This would be updated with actual values from the benchmarker
|
|
379
|
+
"summary": "Performance benchmark service operational",
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
def compare_benchmarks(
|
|
383
|
+
self,
|
|
384
|
+
baseline: dict[str, t.Any],
|
|
385
|
+
current: dict[str, t.Any],
|
|
386
|
+
) -> dict[str, t.Any]:
|
|
387
|
+
"""Compare two benchmark results."""
|
|
388
|
+
return {
|
|
389
|
+
"comparison_type": "basic_comparison",
|
|
390
|
+
"baseline": baseline,
|
|
391
|
+
"current": current,
|
|
392
|
+
"differences": {},
|
|
393
|
+
}
|
|
309
394
|
|
|
395
|
+
# Methods for PerformanceBenchmarkServiceProtocol
|
|
310
396
|
async def run_benchmark_suite(self) -> BenchmarkSuite | None:
|
|
311
397
|
"""Run comprehensive benchmark suite and return results."""
|
|
312
398
|
try:
|
|
@@ -321,4 +407,4 @@ class PerformanceBenchmarkService:
|
|
|
321
407
|
|
|
322
408
|
|
|
323
409
|
def get_benchmarker() -> PerformanceBenchmarker:
|
|
324
|
-
return PerformanceBenchmarker()
|
|
410
|
+
return PerformanceBenchmarker(logger=depends.get_sync(Logger))
|
|
@@ -8,7 +8,8 @@ from pathlib import Path
|
|
|
8
8
|
from threading import Lock
|
|
9
9
|
from weakref import WeakValueDictionary
|
|
10
10
|
|
|
11
|
-
from
|
|
11
|
+
from acb.depends import Inject, depends
|
|
12
|
+
from acb.logger import Logger
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclass
|
|
@@ -45,8 +46,10 @@ class CacheStats:
|
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
class PerformanceCache:
|
|
49
|
+
@depends.inject
|
|
48
50
|
def __init__(
|
|
49
51
|
self,
|
|
52
|
+
logger: Inject[Logger],
|
|
50
53
|
max_memory_mb: int = 50,
|
|
51
54
|
default_ttl_seconds: int = 300,
|
|
52
55
|
cleanup_interval_seconds: int = 60,
|
|
@@ -58,7 +61,7 @@ class PerformanceCache:
|
|
|
58
61
|
self._cache: dict[str, CacheEntry] = {}
|
|
59
62
|
self._lock = Lock()
|
|
60
63
|
self._stats = CacheStats()
|
|
61
|
-
self._logger =
|
|
64
|
+
self._logger = logger
|
|
62
65
|
self._cleanup_task: asyncio.Task[None] | None = None
|
|
63
66
|
self._invalidation_map: dict[str, set[str]] = {}
|
|
64
67
|
|
|
@@ -241,9 +244,9 @@ class PerformanceCache:
|
|
|
241
244
|
|
|
242
245
|
|
|
243
246
|
class GitOperationCache:
|
|
244
|
-
def __init__(self, cache: PerformanceCache):
|
|
247
|
+
def __init__(self, cache: PerformanceCache, logger: Logger):
|
|
245
248
|
self.cache = cache
|
|
246
|
-
self._logger =
|
|
249
|
+
self._logger = logger
|
|
247
250
|
|
|
248
251
|
def _make_repo_key(self, repo_path: Path, operation: str, params: str = "") -> str:
|
|
249
252
|
repo_hash = hashlib.md5(
|
|
@@ -268,7 +271,7 @@ class GitOperationCache:
|
|
|
268
271
|
) -> None:
|
|
269
272
|
key = self._make_repo_key(repo_path, "branch_info")
|
|
270
273
|
invalidation_keys = {f"git_repo: {repo_path}"}
|
|
271
|
-
self.cache.set
|
|
274
|
+
self.cache.set(key, branch_info, ttl_seconds, invalidation_keys)
|
|
272
275
|
|
|
273
276
|
def get_file_status(self, repo_path: Path) -> t.Any:
|
|
274
277
|
key = self._make_repo_key(repo_path, "file_status")
|
|
@@ -282,7 +285,7 @@ class GitOperationCache:
|
|
|
282
285
|
) -> None:
|
|
283
286
|
key = self._make_repo_key(repo_path, "file_status")
|
|
284
287
|
invalidation_keys = {f"git_repo: {repo_path}", "git_files"}
|
|
285
|
-
self.cache.set
|
|
288
|
+
self.cache.set(key, file_status, ttl_seconds, invalidation_keys)
|
|
286
289
|
|
|
287
290
|
def invalidate_repo(self, repo_path: Path) -> None:
|
|
288
291
|
self.cache.invalidate(f"git_repo: {repo_path}")
|
|
@@ -290,9 +293,9 @@ class GitOperationCache:
|
|
|
290
293
|
|
|
291
294
|
|
|
292
295
|
class FileSystemCache:
|
|
293
|
-
def __init__(self, cache: PerformanceCache):
|
|
296
|
+
def __init__(self, cache: PerformanceCache, logger: Logger):
|
|
294
297
|
self.cache = cache
|
|
295
|
-
self._logger =
|
|
298
|
+
self._logger = logger
|
|
296
299
|
|
|
297
300
|
def _make_file_key(self, file_path: Path, operation: str) -> str:
|
|
298
301
|
file_hash = hashlib.md5(
|
|
@@ -312,16 +315,16 @@ class FileSystemCache:
|
|
|
312
315
|
) -> None:
|
|
313
316
|
key = self._make_file_key(file_path, "stats")
|
|
314
317
|
invalidation_keys = {f"file: {file_path}"}
|
|
315
|
-
self.cache.set
|
|
318
|
+
self.cache.set(key, stats, ttl_seconds, invalidation_keys)
|
|
316
319
|
|
|
317
320
|
def invalidate_file(self, file_path: Path) -> None:
|
|
318
321
|
self.cache.invalidate(f"file: {file_path}")
|
|
319
322
|
|
|
320
323
|
|
|
321
324
|
class CommandResultCache:
|
|
322
|
-
def __init__(self, cache: PerformanceCache):
|
|
325
|
+
def __init__(self, cache: PerformanceCache, logger: Logger):
|
|
323
326
|
self.cache = cache
|
|
324
|
-
self._logger =
|
|
327
|
+
self._logger = logger
|
|
325
328
|
|
|
326
329
|
def _make_command_key(self, command: list[str], cwd: Path | None = None) -> str:
|
|
327
330
|
cmd_str = " ".join(command)
|
|
@@ -352,7 +355,7 @@ class CommandResultCache:
|
|
|
352
355
|
if cwd:
|
|
353
356
|
invalidation_keys.add(f"cwd: {cwd}")
|
|
354
357
|
|
|
355
|
-
self.cache.set
|
|
358
|
+
self.cache.set(key, result, ttl_seconds, invalidation_keys)
|
|
356
359
|
|
|
357
360
|
def invalidate_commands(self) -> None:
|
|
358
361
|
self.cache.invalidate("commands")
|
|
@@ -371,12 +374,15 @@ def get_performance_cache() -> PerformanceCache:
|
|
|
371
374
|
|
|
372
375
|
|
|
373
376
|
def get_git_cache() -> GitOperationCache:
|
|
374
|
-
|
|
377
|
+
performance_cache = get_performance_cache()
|
|
378
|
+
return GitOperationCache(performance_cache, logger=performance_cache._logger)
|
|
375
379
|
|
|
376
380
|
|
|
377
381
|
def get_filesystem_cache() -> FileSystemCache:
|
|
378
|
-
|
|
382
|
+
performance_cache = get_performance_cache()
|
|
383
|
+
return FileSystemCache(performance_cache, logger=performance_cache._logger)
|
|
379
384
|
|
|
380
385
|
|
|
381
386
|
def get_command_cache() -> CommandResultCache:
|
|
382
|
-
|
|
387
|
+
performance_cache = get_performance_cache()
|
|
388
|
+
return CommandResultCache(performance_cache, logger=performance_cache._logger)
|
|
@@ -7,9 +7,11 @@ from pathlib import Path
|
|
|
7
7
|
from threading import Lock
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from acb.depends import Inject, depends
|
|
11
|
+
from acb.logger import Logger
|
|
12
|
+
|
|
11
13
|
from crackerjack.services.memory_optimizer import MemoryOptimizer
|
|
12
|
-
from crackerjack.services.performance_cache import get_performance_cache
|
|
14
|
+
from crackerjack.services.monitoring.performance_cache import get_performance_cache
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
@dataclass
|
|
@@ -119,15 +121,17 @@ class PerformanceBenchmark:
|
|
|
119
121
|
|
|
120
122
|
|
|
121
123
|
class PerformanceMonitor:
|
|
124
|
+
@depends.inject
|
|
122
125
|
def __init__(
|
|
123
126
|
self,
|
|
127
|
+
logger: Inject[Logger],
|
|
124
128
|
data_retention_days: int = 30,
|
|
125
129
|
benchmark_history_size: int = 100,
|
|
126
130
|
):
|
|
127
131
|
self.data_retention_days = data_retention_days
|
|
128
132
|
self.benchmark_history_size = benchmark_history_size
|
|
129
133
|
self._initialize_data_structures(benchmark_history_size)
|
|
130
|
-
self._initialize_services()
|
|
134
|
+
self._initialize_services(logger)
|
|
131
135
|
self._initialize_thresholds()
|
|
132
136
|
|
|
133
137
|
def _initialize_data_structures(self, history_size: int) -> None:
|
|
@@ -140,9 +144,9 @@ class PerformanceMonitor:
|
|
|
140
144
|
lambda: deque(maxlen=history_size) # type: ignore[arg-type]
|
|
141
145
|
)
|
|
142
146
|
|
|
143
|
-
def _initialize_services(self) -> None:
|
|
147
|
+
def _initialize_services(self, logger: Logger) -> None:
|
|
144
148
|
self._lock = Lock()
|
|
145
|
-
self._logger =
|
|
149
|
+
self._logger = logger
|
|
146
150
|
self._memory_optimizer = MemoryOptimizer.get_instance()
|
|
147
151
|
self._cache = get_performance_cache()
|
|
148
152
|
|
|
@@ -532,7 +536,7 @@ def get_performance_monitor() -> PerformanceMonitor:
|
|
|
532
536
|
global _global_monitor
|
|
533
537
|
with _monitor_lock:
|
|
534
538
|
if _global_monitor is None:
|
|
535
|
-
_global_monitor = PerformanceMonitor()
|
|
539
|
+
_global_monitor = PerformanceMonitor(logger=depends.get_sync(Logger))
|
|
536
540
|
return _global_monitor
|
|
537
541
|
|
|
538
542
|
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import subprocess
|
|
2
3
|
import time
|
|
3
4
|
import typing as t
|
|
4
|
-
from
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
10
|
+
from acb.depends import Inject, depends
|
|
11
|
+
from acb.logger import Logger
|
|
12
|
+
|
|
9
13
|
from crackerjack.config.hooks import HookDefinition, SecurityLevel
|
|
10
|
-
from crackerjack.
|
|
11
|
-
|
|
14
|
+
from crackerjack.models.protocols import (
|
|
15
|
+
AsyncCommandExecutorProtocol,
|
|
16
|
+
ParallelHookExecutorProtocol,
|
|
17
|
+
PerformanceCacheProtocol,
|
|
18
|
+
ServiceProtocol,
|
|
19
|
+
)
|
|
20
|
+
from crackerjack.models.results import ExecutionResult, ParallelExecutionResult
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
class ExecutionStrategy(Enum):
|
|
@@ -27,42 +36,19 @@ class ExecutionGroup:
|
|
|
27
36
|
security_level: SecurityLevel = SecurityLevel.MEDIUM
|
|
28
37
|
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
operation_id: str
|
|
33
|
-
success: bool
|
|
34
|
-
duration_seconds: float
|
|
35
|
-
output: str = ""
|
|
36
|
-
error: str = ""
|
|
37
|
-
exit_code: int = 0
|
|
38
|
-
metadata: dict[str, t.Any] = field(default_factory=dict[str, t.Any])
|
|
39
|
-
|
|
39
|
+
class ParallelHookExecutor(ParallelHookExecutorProtocol, ServiceProtocol):
|
|
40
|
+
"""Executes hooks in parallel or sequentially based on defined strategies.
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
successful_operations: int
|
|
46
|
-
failed_operations: int
|
|
47
|
-
total_duration_seconds: float
|
|
48
|
-
results: list[ExecutionResult]
|
|
49
|
-
|
|
50
|
-
@property
|
|
51
|
-
def success_rate(self) -> float:
|
|
52
|
-
return (
|
|
53
|
-
self.successful_operations / self.total_operations
|
|
54
|
-
if self.total_operations > 0
|
|
55
|
-
else 0.0
|
|
56
|
-
)
|
|
42
|
+
This service manages the concurrent execution of various hooks (e.g., linting, formatting)
|
|
43
|
+
to optimize performance and provide faster feedback loops. It supports different execution
|
|
44
|
+
strategies and handles dependencies between hooks.
|
|
45
|
+
"""
|
|
57
46
|
|
|
58
|
-
@
|
|
59
|
-
def overall_success(self) -> bool:
|
|
60
|
-
return self.failed_operations == 0
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class ParallelHookExecutor:
|
|
47
|
+
@depends.inject
|
|
64
48
|
def __init__(
|
|
65
49
|
self,
|
|
50
|
+
logger: Inject[Logger],
|
|
51
|
+
cache: Inject[PerformanceCacheProtocol],
|
|
66
52
|
max_workers: int = 3,
|
|
67
53
|
timeout_seconds: int = 300,
|
|
68
54
|
strategy: ExecutionStrategy = ExecutionStrategy.PARALLEL_SAFE,
|
|
@@ -70,8 +56,68 @@ class ParallelHookExecutor:
|
|
|
70
56
|
self.max_workers = max_workers
|
|
71
57
|
self.timeout_seconds = timeout_seconds
|
|
72
58
|
self.strategy = strategy
|
|
73
|
-
self._logger =
|
|
74
|
-
self._cache =
|
|
59
|
+
self._logger = logger
|
|
60
|
+
self._cache = cache
|
|
61
|
+
|
|
62
|
+
def initialize(self) -> None:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def cleanup(self) -> None:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
async def async_cleanup(self) -> None:
|
|
69
|
+
"""Async cleanup for any remaining tasks."""
|
|
70
|
+
with suppress(RuntimeError):
|
|
71
|
+
loop = asyncio.get_running_loop()
|
|
72
|
+
pending_tasks = (
|
|
73
|
+
task
|
|
74
|
+
for task in asyncio.all_tasks(loop)
|
|
75
|
+
if not task.done()
|
|
76
|
+
and ("hook" in str(task).lower() or "parallel" in str(task).lower())
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
for task in pending_tasks:
|
|
80
|
+
if not task.done():
|
|
81
|
+
try:
|
|
82
|
+
task.cancel()
|
|
83
|
+
await asyncio.wait_for(task, timeout=0.1)
|
|
84
|
+
except (TimeoutError, asyncio.CancelledError):
|
|
85
|
+
pass
|
|
86
|
+
except RuntimeError as e:
|
|
87
|
+
if "Event loop is closed" in str(e):
|
|
88
|
+
return
|
|
89
|
+
else:
|
|
90
|
+
raise
|
|
91
|
+
|
|
92
|
+
def health_check(self) -> bool:
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
def shutdown(self) -> None:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
def metrics(self) -> dict[str, t.Any]:
|
|
99
|
+
return {}
|
|
100
|
+
|
|
101
|
+
def is_healthy(self) -> bool:
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
def register_resource(self, resource: t.Any) -> None:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
def cleanup_resource(self, resource: t.Any) -> None:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
def record_error(self, error: Exception) -> None:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
def increment_requests(self) -> None:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
def get_custom_metric(self, name: str) -> t.Any:
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
def set_custom_metric(self, name: str, value: t.Any) -> None:
|
|
120
|
+
pass
|
|
75
121
|
|
|
76
122
|
def analyze_hook_dependencies(
|
|
77
123
|
self,
|
|
@@ -239,7 +285,7 @@ class ParallelHookExecutor:
|
|
|
239
285
|
f"Hook {hooks[i].name} failed with exception: {result}"
|
|
240
286
|
)
|
|
241
287
|
else:
|
|
242
|
-
processed_results.append(result)
|
|
288
|
+
processed_results.append(t.cast(ExecutionResult, result))
|
|
243
289
|
|
|
244
290
|
successful = sum(1 for r in processed_results if r.success)
|
|
245
291
|
self._logger.info(
|
|
@@ -249,18 +295,82 @@ class ParallelHookExecutor:
|
|
|
249
295
|
return processed_results
|
|
250
296
|
|
|
251
297
|
|
|
252
|
-
class AsyncCommandExecutor:
|
|
298
|
+
class AsyncCommandExecutor(AsyncCommandExecutorProtocol, ServiceProtocol):
|
|
299
|
+
"""Executes shell commands asynchronously, with caching capabilities.
|
|
300
|
+
|
|
301
|
+
This service provides a robust way to run external commands without blocking
|
|
302
|
+
the main event loop, supporting parallel execution and caching of results
|
|
303
|
+
to improve performance and responsiveness.
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
@depends.inject
|
|
253
307
|
def __init__(
|
|
254
308
|
self,
|
|
309
|
+
logger: Inject[Logger],
|
|
310
|
+
cache: Inject[PerformanceCacheProtocol],
|
|
255
311
|
max_workers: int = 4,
|
|
256
312
|
cache_results: bool = True,
|
|
257
313
|
):
|
|
258
314
|
self.max_workers = max_workers
|
|
259
315
|
self.cache_results = cache_results
|
|
260
|
-
self._logger =
|
|
261
|
-
self._cache =
|
|
316
|
+
self._logger = logger
|
|
317
|
+
self._cache = cache
|
|
318
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
319
|
+
|
|
262
320
|
self._thread_pool = ThreadPoolExecutor(max_workers=max_workers)
|
|
263
321
|
|
|
322
|
+
def shutdown(self) -> None:
|
|
323
|
+
if hasattr(self, "_thread_pool"):
|
|
324
|
+
self._thread_pool.shutdown(wait=True)
|
|
325
|
+
|
|
326
|
+
def metrics(self) -> dict[str, t.Any]:
|
|
327
|
+
return {}
|
|
328
|
+
|
|
329
|
+
def is_healthy(self) -> bool:
|
|
330
|
+
return True
|
|
331
|
+
|
|
332
|
+
def register_resource(self, resource: t.Any) -> None:
|
|
333
|
+
pass
|
|
334
|
+
|
|
335
|
+
def cleanup_resource(self, resource: t.Any) -> None:
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
def record_error(self, error: Exception) -> None:
|
|
339
|
+
pass
|
|
340
|
+
|
|
341
|
+
def increment_requests(self) -> None:
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
def get_custom_metric(self, name: str) -> t.Any:
|
|
345
|
+
return None
|
|
346
|
+
|
|
347
|
+
def set_custom_metric(self, name: str, value: t.Any) -> None:
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
async def async_cleanup(self) -> None:
|
|
351
|
+
"""Async cleanup for any remaining command executor tasks."""
|
|
352
|
+
with suppress(RuntimeError):
|
|
353
|
+
loop = asyncio.get_running_loop()
|
|
354
|
+
pending_tasks = (
|
|
355
|
+
task
|
|
356
|
+
for task in asyncio.all_tasks(loop)
|
|
357
|
+
if not task.done()
|
|
358
|
+
and ("command" in str(task).lower() or "async" in str(task).lower())
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
for task in pending_tasks:
|
|
362
|
+
if not task.done():
|
|
363
|
+
try:
|
|
364
|
+
task.cancel()
|
|
365
|
+
await asyncio.wait_for(task, timeout=0.1)
|
|
366
|
+
except (TimeoutError, asyncio.CancelledError):
|
|
367
|
+
pass
|
|
368
|
+
except RuntimeError as e:
|
|
369
|
+
if "Event loop is closed" in str(e):
|
|
370
|
+
return
|
|
371
|
+
else:
|
|
372
|
+
raise
|
|
373
|
+
|
|
264
374
|
async def execute_command(
|
|
265
375
|
self,
|
|
266
376
|
command: list[str],
|
|
@@ -307,7 +417,7 @@ class AsyncCommandExecutor:
|
|
|
307
417
|
)
|
|
308
418
|
processed_results.append(error_result)
|
|
309
419
|
else:
|
|
310
|
-
processed_results.append(result)
|
|
420
|
+
processed_results.append(t.cast(ExecutionResult, result))
|
|
311
421
|
|
|
312
422
|
successful = sum(1 for r in processed_results if r.success)
|
|
313
423
|
self._logger.info(
|
|
@@ -325,8 +435,6 @@ class AsyncCommandExecutor:
|
|
|
325
435
|
loop = asyncio.get_event_loop()
|
|
326
436
|
|
|
327
437
|
def run_sync_command() -> ExecutionResult:
|
|
328
|
-
import subprocess
|
|
329
|
-
|
|
330
438
|
try:
|
|
331
439
|
result = subprocess.run(
|
|
332
440
|
command,
|
|
@@ -370,9 +478,7 @@ class AsyncCommandExecutor:
|
|
|
370
478
|
command: list[str],
|
|
371
479
|
cwd: Path | None = None,
|
|
372
480
|
) -> ExecutionResult | None:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
cache_result = get_command_cache().get_command_result(command, cwd)
|
|
481
|
+
cache_result = self._cache.get(self._get_cache_key(command, cwd))
|
|
376
482
|
return t.cast(ExecutionResult | None, cache_result)
|
|
377
483
|
|
|
378
484
|
async def _cache_result(
|
|
@@ -382,14 +488,13 @@ class AsyncCommandExecutor:
|
|
|
382
488
|
ttl_seconds: int,
|
|
383
489
|
cwd: Path | None = None,
|
|
384
490
|
) -> None:
|
|
385
|
-
|
|
491
|
+
self._cache.set(self._get_cache_key(command, cwd), result, ttl_seconds)
|
|
386
492
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
self._thread_pool.shutdown(wait=False)
|
|
493
|
+
def _get_cache_key(self, command: list[str], cwd: Path | None) -> str:
|
|
494
|
+
key_parts = [" ".join(command)]
|
|
495
|
+
if cwd:
|
|
496
|
+
key_parts.append(str(cwd))
|
|
497
|
+
return ":".join(key_parts)
|
|
393
498
|
|
|
394
499
|
|
|
395
500
|
_parallel_executor: ParallelHookExecutor | None = None
|
|
@@ -403,6 +508,8 @@ def get_parallel_executor(
|
|
|
403
508
|
global _parallel_executor
|
|
404
509
|
if _parallel_executor is None:
|
|
405
510
|
_parallel_executor = ParallelHookExecutor(
|
|
511
|
+
logger=depends.get_sync(Logger),
|
|
512
|
+
cache=depends.get_sync(PerformanceCacheProtocol),
|
|
406
513
|
max_workers=max_workers,
|
|
407
514
|
strategy=strategy,
|
|
408
515
|
)
|
|
@@ -412,5 +519,9 @@ def get_parallel_executor(
|
|
|
412
519
|
def get_async_executor(max_workers: int = 4) -> AsyncCommandExecutor:
|
|
413
520
|
global _async_executor
|
|
414
521
|
if _async_executor is None:
|
|
415
|
-
_async_executor = AsyncCommandExecutor(
|
|
522
|
+
_async_executor = AsyncCommandExecutor(
|
|
523
|
+
logger=depends.get_sync(Logger),
|
|
524
|
+
cache=depends.get_sync(PerformanceCacheProtocol),
|
|
525
|
+
max_workers=max_workers,
|
|
526
|
+
)
|
|
416
527
|
return _async_executor
|