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
|
@@ -4,11 +4,12 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import time
|
|
6
6
|
import typing as t
|
|
7
|
+
import uuid
|
|
7
8
|
from collections import defaultdict
|
|
8
|
-
from contextlib import
|
|
9
|
+
from contextlib import asynccontextmanager, suppress
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
11
|
-
from ..config.global_lock_config import GlobalLockConfig
|
|
12
|
+
from ..config.global_lock_config import GlobalLockConfig, get_global_lock_config
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class HookLockManager:
|
|
@@ -28,9 +29,12 @@ class HookLockManager:
|
|
|
28
29
|
"complexipy",
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# Create locks for all hooks that require them
|
|
33
|
+
self._hook_locks: dict[str, asyncio.Lock] = {
|
|
34
|
+
hook_name: asyncio.Lock() for hook_name in self._hooks_requiring_locks
|
|
35
|
+
}
|
|
32
36
|
|
|
33
|
-
self._global_config =
|
|
37
|
+
self._global_config = get_global_lock_config()
|
|
34
38
|
self._global_lock_enabled = self._global_config.enabled
|
|
35
39
|
self._active_global_locks: set[str] = set()
|
|
36
40
|
self._heartbeat_tasks: dict[str, asyncio.Task[None]] = {}
|
|
@@ -59,9 +63,7 @@ class HookLockManager:
|
|
|
59
63
|
return hook_name in self._hooks_requiring_locks
|
|
60
64
|
|
|
61
65
|
@asynccontextmanager
|
|
62
|
-
async def acquire_hook_lock(
|
|
63
|
-
self, hook_name: str
|
|
64
|
-
) -> AbstractAsyncContextManager[None]:
|
|
66
|
+
async def acquire_hook_lock(self, hook_name: str) -> t.AsyncIterator[None]:
|
|
65
67
|
if not self.requires_lock(hook_name):
|
|
66
68
|
yield
|
|
67
69
|
return
|
|
@@ -220,7 +222,7 @@ class HookLockManager:
|
|
|
220
222
|
)
|
|
221
223
|
|
|
222
224
|
async def _attempt_lock_acquisition(self, hook_name: str, lock_path: Path) -> None:
|
|
223
|
-
temp_path = lock_path.with_suffix(".tmp")
|
|
225
|
+
temp_path = lock_path.with_suffix(f".tmp.{uuid.uuid4().hex}")
|
|
224
226
|
|
|
225
227
|
lock_data = {
|
|
226
228
|
"session_id": self._global_config.session_id,
|
|
@@ -239,7 +241,9 @@ class HookLockManager:
|
|
|
239
241
|
temp_path.chmod(0o600)
|
|
240
242
|
|
|
241
243
|
try:
|
|
242
|
-
|
|
244
|
+
# Use os.link() for atomic exclusive creation - fails if target exists
|
|
245
|
+
# (Path.rename() will replace existing file, which breaks lock semantics)
|
|
246
|
+
os.link(str(temp_path), str(lock_path))
|
|
243
247
|
self.logger.debug(f"Successfully created global lock file: {lock_path}")
|
|
244
248
|
except FileExistsError:
|
|
245
249
|
with suppress(OSError):
|
|
@@ -473,6 +477,9 @@ class HookLockManager:
|
|
|
473
477
|
|
|
474
478
|
def add_hook_to_lock_list(self, hook_name: str) -> None:
|
|
475
479
|
self._hooks_requiring_locks.add(hook_name)
|
|
480
|
+
# Create lock for this hook if it doesn't already exist
|
|
481
|
+
if hook_name not in self._hook_locks:
|
|
482
|
+
self._hook_locks[hook_name] = asyncio.Lock()
|
|
476
483
|
self.logger.info(f"Added {hook_name} to hooks requiring locks")
|
|
477
484
|
|
|
478
485
|
def remove_hook_from_lock_list(self, hook_name: str) -> None:
|
|
@@ -497,7 +504,13 @@ class HookLockManager:
|
|
|
497
504
|
|
|
498
505
|
def enable_global_lock(self, enabled: bool = True) -> None:
|
|
499
506
|
self._global_lock_enabled = enabled
|
|
500
|
-
|
|
507
|
+
# Update the settings model if supported
|
|
508
|
+
if hasattr(self._global_config._settings, "enabled"):
|
|
509
|
+
# Create a new settings object with updated enabled value
|
|
510
|
+
new_settings = self._global_config._settings.model_copy(
|
|
511
|
+
update={"enabled": enabled}
|
|
512
|
+
)
|
|
513
|
+
self._global_config._settings = new_settings
|
|
501
514
|
self.logger.info(
|
|
502
515
|
f"Global lock functionality {'enabled' if enabled else 'disabled'}"
|
|
503
516
|
)
|
|
@@ -533,18 +546,9 @@ class HookLockManager:
|
|
|
533
546
|
def _process_lock_file(
|
|
534
547
|
self, lock_file: Path, max_age_hours: float, current_time: float
|
|
535
548
|
) -> int:
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if file_age_hours > max_age_hours:
|
|
540
|
-
return self._cleanup_stale_lock_file(
|
|
541
|
-
lock_file, max_age_hours, current_time
|
|
542
|
-
)
|
|
543
|
-
return 0
|
|
544
|
-
|
|
545
|
-
except OSError as e:
|
|
546
|
-
self.logger.warning(f"Could not process lock file {lock_file}: {e}")
|
|
547
|
-
return 0
|
|
549
|
+
# Always attempt to check lock file data (file mtime is unreliable in tests)
|
|
550
|
+
# The JSON data's last_heartbeat is the source of truth for staleness
|
|
551
|
+
return self._cleanup_stale_lock_file(lock_file, max_age_hours, current_time)
|
|
548
552
|
|
|
549
553
|
def _cleanup_stale_lock_file(
|
|
550
554
|
self, lock_file: Path, max_age_hours: float, current_time: float
|
|
@@ -639,6 +643,11 @@ class HookLockManager:
|
|
|
639
643
|
return stats
|
|
640
644
|
|
|
641
645
|
def configure_from_options(self, options: t.Any) -> None:
|
|
646
|
+
"""Configure lock manager from CLI options.
|
|
647
|
+
|
|
648
|
+
This is a synchronous method because it only performs configuration
|
|
649
|
+
updates without needing to await any async operations.
|
|
650
|
+
"""
|
|
642
651
|
self._global_config = GlobalLockConfig.from_options(options)
|
|
643
652
|
self._global_lock_enabled = self._global_config.enabled
|
|
644
653
|
|
|
@@ -5,9 +5,12 @@ import typing as t
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from acb.console import Console
|
|
9
9
|
|
|
10
|
+
from crackerjack.cli.formatting import separator as make_separator
|
|
11
|
+
from crackerjack.config import get_console_width
|
|
10
12
|
from crackerjack.config.hooks import HookDefinition, HookStrategy
|
|
13
|
+
from crackerjack.executors.hook_executor import HookExecutionResult
|
|
11
14
|
from crackerjack.models.protocols import HookLockManagerProtocol
|
|
12
15
|
from crackerjack.models.task import HookResult
|
|
13
16
|
from crackerjack.services.regex_patterns import SAFE_PATTERNS
|
|
@@ -354,6 +357,7 @@ class IndividualHookExecutor:
|
|
|
354
357
|
self,
|
|
355
358
|
strategy: HookStrategy,
|
|
356
359
|
) -> IndividualExecutionResult:
|
|
360
|
+
"""Execute hook strategy with individual (sequential) execution and progress tracking."""
|
|
357
361
|
start_time = time.time()
|
|
358
362
|
self._print_strategy_header(strategy)
|
|
359
363
|
|
|
@@ -364,6 +368,37 @@ class IndividualHookExecutor:
|
|
|
364
368
|
|
|
365
369
|
return self._finalize_execution_result(strategy, execution_state, start_time)
|
|
366
370
|
|
|
371
|
+
async def execute_strategy(
|
|
372
|
+
self,
|
|
373
|
+
strategy: HookStrategy,
|
|
374
|
+
) -> HookExecutionResult: # Changed return type to match base class
|
|
375
|
+
"""Execute hook strategy - API-compatible method matching other executors."""
|
|
376
|
+
start_time = time.time()
|
|
377
|
+
self._print_strategy_header(strategy)
|
|
378
|
+
|
|
379
|
+
execution_state = self._initialize_execution_state()
|
|
380
|
+
|
|
381
|
+
for hook in strategy.hooks:
|
|
382
|
+
await self._execute_single_hook_in_strategy(hook, execution_state)
|
|
383
|
+
|
|
384
|
+
# Call finalize with original strategy name instead of modified one
|
|
385
|
+
total_duration = time.time() - start_time
|
|
386
|
+
success = all(r.status == "passed" for r in execution_state["hook_results"])
|
|
387
|
+
|
|
388
|
+
self._print_individual_summary(
|
|
389
|
+
strategy,
|
|
390
|
+
execution_state["hook_results"],
|
|
391
|
+
execution_state["hook_progress"],
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Return HookExecutionResult to maintain interface compatibility
|
|
395
|
+
return HookExecutionResult(
|
|
396
|
+
strategy_name=strategy.name, # Use original name, not with "_individual" suffix
|
|
397
|
+
results=execution_state["hook_results"],
|
|
398
|
+
total_duration=total_duration,
|
|
399
|
+
success=success,
|
|
400
|
+
)
|
|
401
|
+
|
|
367
402
|
def _initialize_execution_state(self) -> dict[str, t.Any]:
|
|
368
403
|
return {"hook_results": [], "hook_progress": [], "execution_order": []}
|
|
369
404
|
|
|
@@ -431,13 +466,7 @@ class IndividualHookExecutor:
|
|
|
431
466
|
if self.progress_callback:
|
|
432
467
|
self.progress_callback(progress)
|
|
433
468
|
|
|
434
|
-
|
|
435
|
-
self.console.print(
|
|
436
|
-
f"\n[bold cyan]🔍 Running {hook.name} (with lock)[/ bold cyan]"
|
|
437
|
-
)
|
|
438
|
-
else:
|
|
439
|
-
self.console.print(f"\n[bold cyan]🔍 Running {hook.name}[/ bold cyan]")
|
|
440
|
-
|
|
469
|
+
# Don't print verbose "Running..." messages - the dotted-line format shows status
|
|
441
470
|
cmd = hook.get_command()
|
|
442
471
|
|
|
443
472
|
try:
|
|
@@ -458,11 +487,16 @@ class IndividualHookExecutor:
|
|
|
458
487
|
parsed_output["errors"] + parsed_output["warnings"]
|
|
459
488
|
)
|
|
460
489
|
|
|
490
|
+
status = "passed" if result.returncode == 0 else "failed"
|
|
491
|
+
# Ensure failed hooks always have at least 1 issue count
|
|
492
|
+
issues_count = 1 if status == "failed" else 0
|
|
493
|
+
|
|
461
494
|
hook_result = HookResult(
|
|
462
495
|
id=hook.name,
|
|
463
496
|
name=hook.name,
|
|
464
|
-
status=
|
|
497
|
+
status=status,
|
|
465
498
|
duration=progress.duration or 0,
|
|
499
|
+
issues_count=issues_count,
|
|
466
500
|
)
|
|
467
501
|
|
|
468
502
|
self._print_hook_summary(hook.name, hook_result, progress)
|
|
@@ -479,6 +513,7 @@ class IndividualHookExecutor:
|
|
|
479
513
|
name=hook.name,
|
|
480
514
|
status="failed",
|
|
481
515
|
duration=hook.timeout,
|
|
516
|
+
issues_count=1, # Timeout counts as 1 issue
|
|
482
517
|
)
|
|
483
518
|
except Exception as e:
|
|
484
519
|
progress.status = "failed"
|
|
@@ -490,6 +525,7 @@ class IndividualHookExecutor:
|
|
|
490
525
|
name=hook.name,
|
|
491
526
|
status="failed",
|
|
492
527
|
duration=progress.duration or 0,
|
|
528
|
+
issues_count=1, # Error counts as 1 issue
|
|
493
529
|
)
|
|
494
530
|
|
|
495
531
|
async def _run_command_with_streaming(
|
|
@@ -519,14 +555,11 @@ class IndividualHookExecutor:
|
|
|
519
555
|
return self._create_completed_process(cmd, process, stdout_lines, stderr_lines)
|
|
520
556
|
|
|
521
557
|
async def _create_subprocess(self, cmd: list[str]) -> asyncio.subprocess.Process:
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
if self.pkg_path.name == "crackerjack"
|
|
525
|
-
else self.pkg_path
|
|
526
|
-
)
|
|
558
|
+
# Use pkg_path directly as the working directory for hook execution
|
|
559
|
+
# This ensures hooks run in the correct project directory regardless of project name
|
|
527
560
|
return await asyncio.create_subprocess_exec(
|
|
528
561
|
*cmd,
|
|
529
|
-
cwd=
|
|
562
|
+
cwd=self.pkg_path,
|
|
530
563
|
stdout=asyncio.subprocess.PIPE,
|
|
531
564
|
stderr=asyncio.subprocess.PIPE,
|
|
532
565
|
)
|
|
@@ -602,8 +635,8 @@ class IndividualHookExecutor:
|
|
|
602
635
|
):
|
|
603
636
|
self.progress_callback(progress)
|
|
604
637
|
|
|
638
|
+
@staticmethod
|
|
605
639
|
async def _wait_for_process_completion(
|
|
606
|
-
self,
|
|
607
640
|
process: asyncio.subprocess.Process,
|
|
608
641
|
tasks: list[asyncio.Task[None]],
|
|
609
642
|
timeout: int,
|
|
@@ -611,8 +644,8 @@ class IndividualHookExecutor:
|
|
|
611
644
|
await asyncio.wait_for(process.wait(), timeout=timeout)
|
|
612
645
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
613
646
|
|
|
647
|
+
@staticmethod
|
|
614
648
|
def _handle_process_timeout(
|
|
615
|
-
self,
|
|
616
649
|
process: asyncio.subprocess.Process,
|
|
617
650
|
tasks: list[asyncio.Task[None]],
|
|
618
651
|
) -> None:
|
|
@@ -620,8 +653,8 @@ class IndividualHookExecutor:
|
|
|
620
653
|
for task in tasks:
|
|
621
654
|
task.cancel()
|
|
622
655
|
|
|
656
|
+
@staticmethod
|
|
623
657
|
def _create_completed_process(
|
|
624
|
-
self,
|
|
625
658
|
cmd: list[str],
|
|
626
659
|
process: asyncio.subprocess.Process,
|
|
627
660
|
stdout_lines: list[str],
|
|
@@ -651,22 +684,23 @@ class IndividualHookExecutor:
|
|
|
651
684
|
result: HookResult,
|
|
652
685
|
progress: HookProgress,
|
|
653
686
|
) -> None:
|
|
687
|
+
"""Print hook result in dotted-line format matching pre-commit style.
|
|
688
|
+
|
|
689
|
+
Format: hook-name.......................................... ✅
|
|
690
|
+
"""
|
|
654
691
|
status_icon = "✅" if result.status == "passed" else "❌"
|
|
655
|
-
duration_str = f"{progress.duration: .1f}s" if progress.duration else "0.0s"
|
|
656
692
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if progress.warnings_found > 0:
|
|
661
|
-
summary_parts.append(f"{progress.warnings_found} warnings")
|
|
662
|
-
if progress.files_processed > 0:
|
|
663
|
-
summary_parts.append(f"{progress.files_processed} files")
|
|
693
|
+
# Calculate dotted line (same logic as base HookExecutor)
|
|
694
|
+
max_width = get_console_width()
|
|
695
|
+
content_width = max_width - 4 # Adjusted for icon and padding
|
|
664
696
|
|
|
665
|
-
|
|
697
|
+
if len(hook_name) > content_width:
|
|
698
|
+
line = hook_name[: content_width - 3] + "..."
|
|
699
|
+
else:
|
|
700
|
+
dots_needed = max(0, content_width - len(hook_name))
|
|
701
|
+
line = hook_name + ("." * dots_needed)
|
|
666
702
|
|
|
667
|
-
self.console.print(
|
|
668
|
-
f"[bold]{status_icon} {hook_name}[/ bold] - {duration_str}-{summary}",
|
|
669
|
-
)
|
|
703
|
+
self.console.print(f"{line} {status_icon}")
|
|
670
704
|
|
|
671
705
|
def _print_individual_summary(
|
|
672
706
|
self,
|
|
@@ -680,7 +714,7 @@ class IndividualHookExecutor:
|
|
|
680
714
|
total_warnings = sum(p.warnings_found for p in progress_list)
|
|
681
715
|
total_duration = sum(p.duration or 0 for p in progress_list)
|
|
682
716
|
|
|
683
|
-
self.console.print("\n" + "-"
|
|
717
|
+
self.console.print("\n" + make_separator("-", get_console_width()))
|
|
684
718
|
self.console.print(
|
|
685
719
|
f"[bold]📊 INDIVIDUAL EXECUTION SUMMARY[/ bold]-{strategy.name.upper()}",
|
|
686
720
|
)
|
|
@@ -702,4 +736,4 @@ class IndividualHookExecutor:
|
|
|
702
736
|
)
|
|
703
737
|
self.console.print(f" ❌ {progress.hook_name}-{error_summary}")
|
|
704
738
|
|
|
705
|
-
self.console.print("-"
|
|
739
|
+
self.console.print(make_separator("-", get_console_width()))
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import typing as t
|
|
3
|
+
from contextlib import suppress
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
|
-
from
|
|
6
|
+
from acb.console import Console
|
|
7
|
+
from acb.depends import Inject, depends
|
|
6
8
|
|
|
7
9
|
from crackerjack.config.hooks import HookDefinition, HookStrategy
|
|
8
10
|
from crackerjack.executors.hook_executor import HookExecutionResult, HookExecutor
|
|
@@ -19,18 +21,25 @@ except ImportError:
|
|
|
19
21
|
class LSPAwareHookExecutor(HookExecutor):
|
|
20
22
|
"""Hook executor that can leverage LSP server for enhanced performance."""
|
|
21
23
|
|
|
24
|
+
@depends.inject
|
|
22
25
|
def __init__(
|
|
23
26
|
self,
|
|
24
|
-
console: Console,
|
|
27
|
+
console: Inject[Console],
|
|
25
28
|
pkg_path: Path,
|
|
26
29
|
verbose: bool = False,
|
|
27
30
|
quiet: bool = False,
|
|
31
|
+
debug: bool = False,
|
|
28
32
|
use_tool_proxy: bool = True,
|
|
33
|
+
use_incremental: bool = False,
|
|
34
|
+
git_service: t.Any | None = None,
|
|
29
35
|
) -> None:
|
|
30
|
-
super().__init__(
|
|
31
|
-
|
|
36
|
+
super().__init__(
|
|
37
|
+
console, pkg_path, verbose, quiet, debug, use_incremental, git_service
|
|
38
|
+
)
|
|
39
|
+
self.lsp_client = LSPClient()
|
|
32
40
|
self.use_tool_proxy = use_tool_proxy and ToolProxy is not None
|
|
33
|
-
self.tool_proxy = ToolProxy(
|
|
41
|
+
self.tool_proxy = ToolProxy() if self.use_tool_proxy else None
|
|
42
|
+
self.debug = debug
|
|
34
43
|
|
|
35
44
|
def execute_strategy(self, strategy: HookStrategy) -> HookExecutionResult:
|
|
36
45
|
"""Execute hook strategy with LSP optimization where possible."""
|
|
@@ -38,24 +47,14 @@ class LSPAwareHookExecutor(HookExecutor):
|
|
|
38
47
|
results = []
|
|
39
48
|
|
|
40
49
|
# Check if LSP server is available
|
|
41
|
-
lsp_available = self.
|
|
42
|
-
|
|
43
|
-
if lsp_available and not self.quiet:
|
|
44
|
-
server_info = self.lsp_client.get_server_info()
|
|
45
|
-
if server_info:
|
|
46
|
-
self.console.print(
|
|
47
|
-
f"🔍 LSP server available (PID: {server_info['pid']}), using optimized execution"
|
|
48
|
-
)
|
|
50
|
+
lsp_available = self._check_lsp_availability()
|
|
49
51
|
|
|
50
52
|
# Execute hooks with LSP optimization and tool proxy resilience
|
|
51
53
|
for hook in strategy.hooks:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
elif self._should_use_tool_proxy(hook):
|
|
55
|
-
result = self._execute_hook_with_proxy(hook)
|
|
56
|
-
else:
|
|
57
|
-
result = self.execute_single_hook(hook)
|
|
54
|
+
self._handle_progress_start(len(strategy.hooks))
|
|
55
|
+
result = self._execute_single_hook_with_strategies(hook, lsp_available)
|
|
58
56
|
results.append(result)
|
|
57
|
+
self._handle_progress_completion(len(strategy.hooks))
|
|
59
58
|
|
|
60
59
|
duration = time.time() - start_time
|
|
61
60
|
success = all(result.status in ("passed", "skipped") for result in results)
|
|
@@ -68,6 +67,51 @@ class LSPAwareHookExecutor(HookExecutor):
|
|
|
68
67
|
concurrent_execution=False,
|
|
69
68
|
)
|
|
70
69
|
|
|
70
|
+
def _check_lsp_availability(self) -> bool:
|
|
71
|
+
"""Check if LSP server is available and print info message."""
|
|
72
|
+
lsp_available = self.lsp_client.is_server_running()
|
|
73
|
+
|
|
74
|
+
if lsp_available and not self.quiet:
|
|
75
|
+
server_info = self.lsp_client.get_server_info()
|
|
76
|
+
if server_info:
|
|
77
|
+
self.console.print(
|
|
78
|
+
f"🔍 LSP server available (PID: {server_info['pid']}), using optimized execution"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return lsp_available
|
|
82
|
+
|
|
83
|
+
def _execute_single_hook_with_strategies(
|
|
84
|
+
self, hook: HookDefinition, lsp_available: bool
|
|
85
|
+
) -> HookResult:
|
|
86
|
+
"""Execute a single hook using appropriate strategy."""
|
|
87
|
+
if self._should_use_lsp_for_hook(hook, lsp_available):
|
|
88
|
+
return self._execute_lsp_hook(hook)
|
|
89
|
+
elif self._should_use_tool_proxy(hook):
|
|
90
|
+
return self._execute_hook_with_proxy(hook)
|
|
91
|
+
|
|
92
|
+
return self.execute_single_hook(hook)
|
|
93
|
+
|
|
94
|
+
def _handle_progress_start(self, total_hooks: int | None = None) -> None:
|
|
95
|
+
"""Handle progress start callback."""
|
|
96
|
+
with suppress(Exception):
|
|
97
|
+
callback = getattr(self, "_progress_start_callback", None)
|
|
98
|
+
if callback:
|
|
99
|
+
# _total_hooks/_started_hooks are initialized by set_progress_callbacks on base class
|
|
100
|
+
self._started_hooks += 1 # type: ignore[attr-defined]
|
|
101
|
+
total = self._total_hooks or total_hooks # type: ignore[attr-defined]
|
|
102
|
+
if total:
|
|
103
|
+
callback(self._started_hooks, total) # type: ignore[attr-defined]
|
|
104
|
+
|
|
105
|
+
def _handle_progress_completion(self, total_hooks: int | None = None) -> None:
|
|
106
|
+
"""Handle progress completion callback."""
|
|
107
|
+
with suppress(Exception):
|
|
108
|
+
callback = getattr(self, "_progress_callback", None)
|
|
109
|
+
if callback:
|
|
110
|
+
self._completed_hooks += 1 # type: ignore[attr-defined]
|
|
111
|
+
total = self._total_hooks or total_hooks # type: ignore[attr-defined]
|
|
112
|
+
if total:
|
|
113
|
+
callback(self._completed_hooks, total) # type: ignore[attr-defined]
|
|
114
|
+
|
|
71
115
|
def _should_use_lsp_for_hook(
|
|
72
116
|
self, hook: HookDefinition, lsp_available: bool
|
|
73
117
|
) -> bool:
|
|
@@ -108,13 +152,32 @@ class LSPAwareHookExecutor(HookExecutor):
|
|
|
108
152
|
|
|
109
153
|
self._display_lsp_results(hook, has_errors, output, summary)
|
|
110
154
|
|
|
155
|
+
# Create hook result
|
|
156
|
+
return self._create_lsp_hook_result(
|
|
157
|
+
hook, duration, has_errors, output, diagnostics
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def _create_lsp_hook_result(
|
|
161
|
+
self,
|
|
162
|
+
hook: HookDefinition,
|
|
163
|
+
duration: float,
|
|
164
|
+
has_errors: bool,
|
|
165
|
+
output: str,
|
|
166
|
+
diagnostics: dict,
|
|
167
|
+
) -> HookResult:
|
|
168
|
+
"""Create the HookResult for LSP execution."""
|
|
169
|
+
# Ensure failed hooks always have at least 1 issue count
|
|
170
|
+
issues_found = [output] if has_errors else []
|
|
171
|
+
issues_count = max(len(issues_found), 1 if has_errors else 0)
|
|
172
|
+
|
|
111
173
|
return HookResult(
|
|
112
174
|
id=f"{hook.name}-lsp-{int(time.time())}",
|
|
113
175
|
name=f"{hook.name}-lsp",
|
|
114
176
|
status="failed" if has_errors else "passed",
|
|
115
177
|
duration=duration,
|
|
116
178
|
files_processed=len(diagnostics),
|
|
117
|
-
issues_found=
|
|
179
|
+
issues_found=issues_found,
|
|
180
|
+
issues_count=issues_count,
|
|
118
181
|
)
|
|
119
182
|
|
|
120
183
|
def _format_lsp_output(self, diagnostics: dict[str, t.Any], duration: float) -> str:
|
|
@@ -163,50 +226,66 @@ class LSPAwareHookExecutor(HookExecutor):
|
|
|
163
226
|
start_time = time.time()
|
|
164
227
|
|
|
165
228
|
try:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
)
|
|
229
|
+
return self._perform_proxy_execution(hook, start_time)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
return self._handle_proxy_execution_error(hook, start_time, e)
|
|
170
232
|
|
|
171
|
-
|
|
172
|
-
|
|
233
|
+
def _perform_proxy_execution(
|
|
234
|
+
self, hook: HookDefinition, start_time: float
|
|
235
|
+
) -> HookResult:
|
|
236
|
+
"""Perform the actual proxy execution."""
|
|
237
|
+
if not self.quiet:
|
|
238
|
+
self.console.print(
|
|
239
|
+
f"🛡️ Using resilient execution for {hook.name}", style="blue"
|
|
240
|
+
)
|
|
173
241
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
exit_code = self.tool_proxy.execute_tool(tool_name, args)
|
|
177
|
-
else:
|
|
178
|
-
exit_code = -1 # Error code when tool proxy is not available
|
|
242
|
+
# Parse hook entry to extract tool name and args
|
|
243
|
+
tool_name, args = self._parse_hook_entry(hook)
|
|
179
244
|
|
|
180
|
-
|
|
181
|
-
|
|
245
|
+
# Execute through tool proxy
|
|
246
|
+
if self.tool_proxy is not None:
|
|
247
|
+
exit_code = self.tool_proxy.execute_tool(tool_name, args)
|
|
248
|
+
else:
|
|
249
|
+
exit_code = -1 # Error code when tool proxy is not available
|
|
182
250
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
self.tool_proxy.get_tool_status().get(tool_name, {})
|
|
186
|
-
if self.tool_proxy is not None
|
|
187
|
-
else {}
|
|
188
|
-
)
|
|
189
|
-
output = self._format_proxy_output(tool_name, tool_status, duration)
|
|
190
|
-
|
|
191
|
-
return HookResult(
|
|
192
|
-
id=f"{hook.name}-proxy-{int(time.time())}",
|
|
193
|
-
name=f"{hook.name}-proxy",
|
|
194
|
-
status=status,
|
|
195
|
-
duration=duration,
|
|
196
|
-
files_processed=1, # Placeholder value
|
|
197
|
-
issues_found=[output] if status == "failed" else [],
|
|
198
|
-
)
|
|
251
|
+
duration = time.time() - start_time
|
|
252
|
+
status = "passed" if exit_code == 0 else "failed"
|
|
199
253
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
254
|
+
# Get tool status for output
|
|
255
|
+
tool_status = (
|
|
256
|
+
self.tool_proxy.get_tool_status().get(tool_name, {})
|
|
257
|
+
if self.tool_proxy is not None
|
|
258
|
+
else {}
|
|
259
|
+
)
|
|
260
|
+
output = self._format_proxy_output(tool_name, tool_status, duration)
|
|
203
261
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
262
|
+
# Ensure failed hooks always have at least 1 issue count
|
|
263
|
+
issues_found = [output] if status == "failed" else []
|
|
264
|
+
issues_count = max(len(issues_found), 1 if status == "failed" else 0)
|
|
265
|
+
|
|
266
|
+
return HookResult(
|
|
267
|
+
id=f"{hook.name}-proxy-{int(time.time())}",
|
|
268
|
+
name=f"{hook.name}-proxy",
|
|
269
|
+
status=status,
|
|
270
|
+
duration=duration,
|
|
271
|
+
files_processed=1, # Placeholder value
|
|
272
|
+
issues_found=issues_found,
|
|
273
|
+
issues_count=issues_count,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def _handle_proxy_execution_error(
|
|
277
|
+
self, hook: HookDefinition, start_time: float, error: Exception
|
|
278
|
+
) -> HookResult:
|
|
279
|
+
"""Handle proxy execution errors with fallback."""
|
|
280
|
+
time.time() - start_time
|
|
281
|
+
error_msg = f"Tool proxy execution failed: {error}"
|
|
207
282
|
|
|
208
|
-
|
|
209
|
-
|
|
283
|
+
if not self.quiet:
|
|
284
|
+
self.console.print(f"❌ {hook.name} (proxy): {error_msg}", style="red")
|
|
285
|
+
self.console.print(f"🔄 Falling back to regular {hook.name} execution")
|
|
286
|
+
|
|
287
|
+
# Fallback to regular execution
|
|
288
|
+
return self.execute_single_hook(hook)
|
|
210
289
|
|
|
211
290
|
def _parse_hook_entry(self, hook: HookDefinition) -> tuple[str, list[str]]:
|
|
212
291
|
"""Parse hook entry to extract tool name and arguments."""
|