crackerjack 0.18.2__py3-none-any.whl → 0.45.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +96 -2
- crackerjack/__main__.py +637 -138
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +39 -0
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/lsp/_base.py +194 -0
- crackerjack/adapters/lsp/_client.py +358 -0
- crackerjack/adapters/lsp/_manager.py +193 -0
- crackerjack/adapters/lsp/skylos.py +283 -0
- crackerjack/adapters/lsp/zuban.py +557 -0
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +66 -0
- crackerjack/agents/architect_agent.py +238 -0
- crackerjack/agents/base.py +167 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +600 -0
- crackerjack/agents/documentation_agent.py +520 -0
- crackerjack/agents/dry_agent.py +585 -0
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +230 -0
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/import_optimization_agent.py +1181 -0
- crackerjack/agents/performance_agent.py +325 -0
- crackerjack/agents/performance_helpers.py +205 -0
- crackerjack/agents/proactive_agent.py +55 -0
- crackerjack/agents/refactoring_agent.py +511 -0
- crackerjack/agents/refactoring_helpers.py +247 -0
- crackerjack/agents/security_agent.py +793 -0
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +570 -0
- crackerjack/agents/test_specialist_agent.py +526 -0
- crackerjack/agents/tracker.py +110 -0
- crackerjack/api.py +647 -0
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +24 -0
- crackerjack/cli/cache_handlers.py +209 -0
- crackerjack/cli/cache_handlers_enhanced.py +680 -0
- crackerjack/cli/facade.py +162 -0
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +700 -0
- crackerjack/cli/interactive.py +488 -0
- crackerjack/cli/options.py +1216 -0
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/utils.py +19 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +1307 -0
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +275 -0
- crackerjack/config/global_lock_config.py +207 -0
- crackerjack/config/hooks.py +390 -0
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/__init__.py +0 -0
- crackerjack/core/async_workflow_orchestrator.py +738 -0
- crackerjack/core/autofix_coordinator.py +282 -0
- crackerjack/core/container.py +105 -0
- crackerjack/core/enhanced_container.py +583 -0
- crackerjack/core/file_lifecycle.py +472 -0
- crackerjack/core/performance.py +244 -0
- crackerjack/core/performance_monitor.py +357 -0
- crackerjack/core/phase_coordinator.py +1227 -0
- crackerjack/core/proactive_workflow.py +267 -0
- crackerjack/core/resource_manager.py +425 -0
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +601 -0
- crackerjack/core/session_coordinator.py +239 -0
- crackerjack/core/timeout_manager.py +563 -0
- crackerjack/core/websocket_lifecycle.py +410 -0
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +2243 -0
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +767 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +1065 -0
- crackerjack/dynamic_config.py +678 -0
- crackerjack/errors.py +378 -0
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +13 -0
- crackerjack/executors/async_hook_executor.py +938 -0
- crackerjack/executors/cached_hook_executor.py +316 -0
- crackerjack/executors/hook_executor.py +1295 -0
- crackerjack/executors/hook_lock_manager.py +708 -0
- crackerjack/executors/individual_hook_executor.py +739 -0
- crackerjack/executors/lsp_aware_hook_executor.py +349 -0
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +433 -0
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +93 -0
- crackerjack/intelligence/README.md +557 -0
- crackerjack/intelligence/__init__.py +37 -0
- crackerjack/intelligence/adaptive_learning.py +693 -0
- crackerjack/intelligence/agent_orchestrator.py +485 -0
- crackerjack/intelligence/agent_registry.py +377 -0
- crackerjack/intelligence/agent_selector.py +439 -0
- crackerjack/intelligence/integration.py +250 -0
- crackerjack/interactive.py +719 -0
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/__init__.py +11 -0
- crackerjack/managers/async_hook_manager.py +135 -0
- crackerjack/managers/hook_manager.py +585 -0
- crackerjack/managers/publish_manager.py +631 -0
- crackerjack/managers/test_command_builder.py +391 -0
- crackerjack/managers/test_executor.py +474 -0
- crackerjack/managers/test_manager.py +1357 -0
- crackerjack/managers/test_progress.py +187 -0
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/__init__.py +0 -0
- crackerjack/mcp/cache.py +352 -0
- crackerjack/mcp/client_runner.py +121 -0
- crackerjack/mcp/context.py +802 -0
- crackerjack/mcp/dashboard.py +657 -0
- crackerjack/mcp/enhanced_progress_monitor.py +493 -0
- crackerjack/mcp/file_monitor.py +394 -0
- crackerjack/mcp/progress_components.py +607 -0
- crackerjack/mcp/progress_monitor.py +1016 -0
- crackerjack/mcp/rate_limiter.py +336 -0
- crackerjack/mcp/server.py +24 -0
- crackerjack/mcp/server_core.py +526 -0
- crackerjack/mcp/service_watchdog.py +505 -0
- crackerjack/mcp/state.py +407 -0
- crackerjack/mcp/task_manager.py +259 -0
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +19 -0
- crackerjack/mcp/tools/core_tools.py +469 -0
- crackerjack/mcp/tools/error_analyzer.py +283 -0
- crackerjack/mcp/tools/execution_tools.py +384 -0
- crackerjack/mcp/tools/intelligence_tool_registry.py +46 -0
- crackerjack/mcp/tools/intelligence_tools.py +264 -0
- crackerjack/mcp/tools/monitoring_tools.py +628 -0
- crackerjack/mcp/tools/proactive_tools.py +367 -0
- crackerjack/mcp/tools/progress_tools.py +222 -0
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +358 -0
- crackerjack/mcp/tools/workflow_executor.py +699 -0
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/__init__.py +14 -0
- crackerjack/mcp/websocket/app.py +54 -0
- crackerjack/mcp/websocket/endpoints.py +492 -0
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +406 -0
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +21 -0
- crackerjack/mcp/websocket/server.py +174 -0
- crackerjack/mcp/websocket/websocket_handler.py +276 -0
- crackerjack/mcp/websocket_server.py +10 -0
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +40 -0
- crackerjack/models/config.py +730 -0
- crackerjack/models/config_adapter.py +265 -0
- crackerjack/models/protocols.py +1535 -0
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/resource_protocols.py +299 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +173 -0
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/__init__.py +0 -0
- crackerjack/monitoring/ai_agent_watchdog.py +405 -0
- crackerjack/monitoring/metrics_collector.py +427 -0
- crackerjack/monitoring/regression_prevention.py +580 -0
- crackerjack/monitoring/websocket_server.py +406 -0
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +894 -0
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +180 -0
- crackerjack/orchestration/execution_strategies.py +361 -0
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +647 -0
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/__init__.py +15 -0
- crackerjack/plugins/base.py +200 -0
- crackerjack/plugins/hooks.py +254 -0
- crackerjack/plugins/loader.py +335 -0
- crackerjack/plugins/managers.py +264 -0
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/security/__init__.py +0 -0
- crackerjack/security/audit.py +197 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +9 -0
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/ai/contextual_ai_assistant.py +542 -0
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +617 -0
- crackerjack/services/backup_service.py +467 -0
- crackerjack/services/bounded_status_operations.py +530 -0
- crackerjack/services/cache.py +369 -0
- crackerjack/services/changelog_automation.py +399 -0
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +132 -0
- crackerjack/services/config_merge.py +546 -0
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +493 -0
- crackerjack/services/coverage_badge_service.py +173 -0
- crackerjack/services/coverage_ratchet.py +381 -0
- crackerjack/services/debug.py +733 -0
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +622 -0
- crackerjack/services/documentation_generator.py +493 -0
- crackerjack/services/documentation_service.py +704 -0
- crackerjack/services/enhanced_filesystem.py +497 -0
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +149 -0
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +381 -0
- crackerjack/services/git.py +422 -0
- crackerjack/services/health_metrics.py +615 -0
- crackerjack/services/heatmap_generator.py +744 -0
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +823 -0
- crackerjack/services/input_validator.py +668 -0
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +289 -0
- crackerjack/services/logging.py +228 -0
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +414 -0
- crackerjack/services/metrics.py +587 -0
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/monitoring/performance_benchmarks.py +410 -0
- crackerjack/services/monitoring/performance_cache.py +388 -0
- crackerjack/services/monitoring/performance_monitor.py +569 -0
- crackerjack/services/parallel_executor.py +527 -0
- crackerjack/services/pattern_cache.py +333 -0
- crackerjack/services/pattern_detector.py +478 -0
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +523 -0
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/quality/quality_baseline.py +395 -0
- crackerjack/services/quality/quality_baseline_enhanced.py +649 -0
- crackerjack/services/quality/quality_intelligence.py +949 -0
- crackerjack/services/regex_patterns.py +58 -0
- crackerjack/services/regex_utils.py +483 -0
- crackerjack/services/secure_path_utils.py +524 -0
- crackerjack/services/secure_status_formatter.py +450 -0
- crackerjack/services/secure_subprocess.py +635 -0
- crackerjack/services/security.py +239 -0
- crackerjack/services/security_logger.py +495 -0
- crackerjack/services/server_manager.py +411 -0
- crackerjack/services/smart_scheduling.py +167 -0
- crackerjack/services/status_authentication.py +460 -0
- crackerjack/services/status_security_manager.py +315 -0
- crackerjack/services/terminal_utils.py +0 -0
- crackerjack/services/thread_safe_status_collector.py +441 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +43 -0
- crackerjack/services/unified_config.py +115 -0
- crackerjack/services/validation_rate_limiter.py +220 -0
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +461 -0
- crackerjack/services/version_checker.py +223 -0
- crackerjack/services/websocket_resource_limiter.py +438 -0
- crackerjack/services/zuban_lsp_service.py +391 -0
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/__init__.py +59 -0
- crackerjack/slash_commands/init.md +112 -0
- crackerjack/slash_commands/run.md +197 -0
- crackerjack/slash_commands/status.md +127 -0
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_input_validator_patterns.py +236 -0
- crackerjack/tools/validate_regex_patterns.py +188 -0
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/__init__.py +1 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- crackerjack-0.45.2.dist-info/METADATA +1678 -0
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack-0.45.2.dist-info/entry_points.txt +2 -0
- crackerjack/.gitignore +0 -14
- crackerjack/.libcst.codemod.yaml +0 -18
- crackerjack/.pdm.toml +0 -1
- crackerjack/.pre-commit-config.yaml +0 -91
- crackerjack/.pytest_cache/.gitignore +0 -2
- crackerjack/.pytest_cache/CACHEDIR.TAG +0 -4
- crackerjack/.pytest_cache/README.md +0 -8
- crackerjack/.pytest_cache/v/cache/nodeids +0 -1
- crackerjack/.pytest_cache/v/cache/stepwise +0 -1
- crackerjack/.ruff_cache/.gitignore +0 -1
- crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
- crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
- crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
- crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
- crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
- crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
- crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
- crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
- crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
- crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
- crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
- crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
- crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
- crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
- crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
- crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
- crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
- crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
- crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
- crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
- crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
- crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
- crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
- crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
- crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
- crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
- crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
- crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
- crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
- crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
- crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
- crackerjack/.ruff_cache/CACHEDIR.TAG +0 -1
- crackerjack/crackerjack.py +0 -855
- crackerjack/pyproject.toml +0 -214
- crackerjack-0.18.2.dist-info/METADATA +0 -420
- crackerjack-0.18.2.dist-info/RECORD +0 -59
- crackerjack-0.18.2.dist-info/entry_points.txt +0 -4
- {crackerjack-0.18.2.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
import typing as t
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
|
|
13
|
+
from acb.console import Console
|
|
14
|
+
from acb.depends import depends
|
|
15
|
+
|
|
16
|
+
from crackerjack.core.resource_manager import (
|
|
17
|
+
ResourceManager,
|
|
18
|
+
register_global_resource_manager,
|
|
19
|
+
)
|
|
20
|
+
from crackerjack.core.websocket_lifecycle import NetworkResourceManager
|
|
21
|
+
from crackerjack.core.workflow_orchestrator import WorkflowOrchestrator
|
|
22
|
+
from crackerjack.services.secure_path_utils import SecurePathValidator
|
|
23
|
+
|
|
24
|
+
from .cache import ErrorCache
|
|
25
|
+
from .rate_limiter import RateLimitConfig, RateLimitMiddleware
|
|
26
|
+
from .state import StateManager
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BatchedStateSaver:
|
|
30
|
+
def __init__(self, debounce_delay: float = 1.0, max_batch_size: int = 10) -> None:
|
|
31
|
+
self.debounce_delay = debounce_delay
|
|
32
|
+
self.max_batch_size = max_batch_size
|
|
33
|
+
|
|
34
|
+
self._pending_saves: dict[str, t.Callable[[], None]] = {}
|
|
35
|
+
self._last_save_time: dict[str, float] = {}
|
|
36
|
+
|
|
37
|
+
self._save_task: asyncio.Task[None] | None = None
|
|
38
|
+
self._running = False
|
|
39
|
+
self._lock = asyncio.Lock()
|
|
40
|
+
|
|
41
|
+
async def start(self) -> None:
|
|
42
|
+
if self._running:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
self._running = True
|
|
46
|
+
self._save_task = asyncio.create_task(self._save_loop())
|
|
47
|
+
|
|
48
|
+
async def stop(self) -> None:
|
|
49
|
+
self._running = False
|
|
50
|
+
|
|
51
|
+
if self._save_task:
|
|
52
|
+
self._save_task.cancel()
|
|
53
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
54
|
+
await self._save_task
|
|
55
|
+
|
|
56
|
+
await self._flush_saves()
|
|
57
|
+
|
|
58
|
+
async def schedule_save(
|
|
59
|
+
self,
|
|
60
|
+
save_id: str,
|
|
61
|
+
save_func: t.Callable[[], None],
|
|
62
|
+
) -> None:
|
|
63
|
+
async with self._lock:
|
|
64
|
+
self._pending_saves[save_id] = save_func
|
|
65
|
+
self._last_save_time[save_id] = time.time()
|
|
66
|
+
|
|
67
|
+
if len(self._pending_saves) >= self.max_batch_size:
|
|
68
|
+
await self._flush_saves()
|
|
69
|
+
|
|
70
|
+
async def _save_loop(self) -> None:
|
|
71
|
+
while self._running:
|
|
72
|
+
try:
|
|
73
|
+
await asyncio.sleep(self.debounce_delay)
|
|
74
|
+
ready_saves = await self._get_ready_saves()
|
|
75
|
+
|
|
76
|
+
if ready_saves:
|
|
77
|
+
await self._execute_saves(ready_saves)
|
|
78
|
+
|
|
79
|
+
except asyncio.CancelledError:
|
|
80
|
+
break
|
|
81
|
+
except Exception:
|
|
82
|
+
await asyncio.sleep(1)
|
|
83
|
+
|
|
84
|
+
async def _get_ready_saves(self) -> list[str]:
|
|
85
|
+
now = time.time()
|
|
86
|
+
ready_saves = []
|
|
87
|
+
|
|
88
|
+
async with self._lock:
|
|
89
|
+
for save_id, last_time in list[t.Any](self._last_save_time.items()):
|
|
90
|
+
if now - last_time >= self.debounce_delay:
|
|
91
|
+
ready_saves.append(save_id)
|
|
92
|
+
|
|
93
|
+
return ready_saves
|
|
94
|
+
|
|
95
|
+
async def _execute_saves(self, save_ids: list[str]) -> None:
|
|
96
|
+
async with self._lock:
|
|
97
|
+
saves_to_execute = []
|
|
98
|
+
|
|
99
|
+
for save_id in save_ids:
|
|
100
|
+
if save_id in self._pending_saves:
|
|
101
|
+
saves_to_execute.append((save_id, self._pending_saves.pop(save_id)))
|
|
102
|
+
self._last_save_time.pop(save_id, None)
|
|
103
|
+
|
|
104
|
+
for save_id, save_func in saves_to_execute:
|
|
105
|
+
with contextlib.suppress(Exception):
|
|
106
|
+
save_func()
|
|
107
|
+
|
|
108
|
+
async def _flush_saves(self) -> None:
|
|
109
|
+
async with self._lock:
|
|
110
|
+
save_ids = list[t.Any](self._pending_saves.keys())
|
|
111
|
+
|
|
112
|
+
if save_ids:
|
|
113
|
+
await self._execute_saves(save_ids)
|
|
114
|
+
|
|
115
|
+
def get_stats(self) -> dict[str, t.Any]:
|
|
116
|
+
return {
|
|
117
|
+
"running": self._running,
|
|
118
|
+
"pending_saves": len(self._pending_saves),
|
|
119
|
+
"debounce_delay": self.debounce_delay,
|
|
120
|
+
"max_batch_size": self.max_batch_size,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class MCPServerConfig:
|
|
126
|
+
project_path: Path
|
|
127
|
+
progress_dir: Path | None = None
|
|
128
|
+
rate_limit_config: RateLimitConfig | None = None
|
|
129
|
+
stdio_mode: bool = True
|
|
130
|
+
state_dir: Path | None = None
|
|
131
|
+
cache_dir: Path | None = None
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
self.project_path = SecurePathValidator.validate_safe_path(self.project_path)
|
|
135
|
+
|
|
136
|
+
if self.progress_dir:
|
|
137
|
+
self.progress_dir = SecurePathValidator.validate_safe_path(
|
|
138
|
+
self.progress_dir
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if self.state_dir:
|
|
142
|
+
self.state_dir = SecurePathValidator.validate_safe_path(self.state_dir)
|
|
143
|
+
|
|
144
|
+
if self.cache_dir:
|
|
145
|
+
self.cache_dir = SecurePathValidator.validate_safe_path(self.cache_dir)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class MCPServerContext:
|
|
149
|
+
def __init__(self, config: MCPServerConfig) -> None:
|
|
150
|
+
self.config = config
|
|
151
|
+
|
|
152
|
+
self.resource_manager = ResourceManager()
|
|
153
|
+
self.network_manager = NetworkResourceManager()
|
|
154
|
+
register_global_resource_manager(self.resource_manager)
|
|
155
|
+
|
|
156
|
+
self.console: Console | None = None
|
|
157
|
+
self.cli_runner: WorkflowOrchestrator | None = None
|
|
158
|
+
self.state_manager: StateManager | None = None
|
|
159
|
+
self.error_cache: ErrorCache | None = None
|
|
160
|
+
self.rate_limiter: RateLimitMiddleware | None = None
|
|
161
|
+
self.batched_saver: BatchedStateSaver = BatchedStateSaver()
|
|
162
|
+
|
|
163
|
+
self.progress_dir = config.progress_dir or (
|
|
164
|
+
Path(tempfile.gettempdir()) / "crackerjack-mcp-progress"
|
|
165
|
+
)
|
|
166
|
+
self.progress_queue: asyncio.Queue[dict[str, t.Any]] = asyncio.Queue(
|
|
167
|
+
maxsize=1000,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self.websocket_server_process: subprocess.Popen[bytes] | None = None
|
|
171
|
+
self.websocket_server_port: int = int(
|
|
172
|
+
os.environ.get("CRACKERJACK_WEBSOCKET_PORT", "8675"),
|
|
173
|
+
)
|
|
174
|
+
self._websocket_process_lock = asyncio.Lock()
|
|
175
|
+
self._websocket_cleanup_registered = False
|
|
176
|
+
self._websocket_health_check_task: asyncio.Task[None] | None = None
|
|
177
|
+
|
|
178
|
+
self._initialized = False
|
|
179
|
+
self._startup_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
|
|
180
|
+
self._shutdown_tasks: list[t.Callable[[], t.Awaitable[None]]] = []
|
|
181
|
+
|
|
182
|
+
async def _auto_setup_git_working_directory(self) -> None:
|
|
183
|
+
"""Auto-detect and setup git working directory for enhanced DX."""
|
|
184
|
+
try:
|
|
185
|
+
git_root = await self._detect_git_repository()
|
|
186
|
+
if git_root:
|
|
187
|
+
await self._log_git_detection(git_root)
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
self._handle_git_setup_failure(e)
|
|
191
|
+
|
|
192
|
+
async def _detect_git_repository(self) -> Path | None:
|
|
193
|
+
"""Detect if we're in a git repository and return the root path."""
|
|
194
|
+
|
|
195
|
+
current_dir = Path.cwd()
|
|
196
|
+
|
|
197
|
+
# Check if we're in a git repository
|
|
198
|
+
if not self._is_git_repository(current_dir):
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
return self._get_git_root_directory(current_dir)
|
|
202
|
+
|
|
203
|
+
def _is_git_repository(self, current_dir: Path) -> bool:
|
|
204
|
+
"""Check if the current directory is within a git repository."""
|
|
205
|
+
import subprocess
|
|
206
|
+
|
|
207
|
+
git_check = subprocess.run(
|
|
208
|
+
["git", "rev-parse", "--is-inside-work-tree"],
|
|
209
|
+
capture_output=True,
|
|
210
|
+
text=True,
|
|
211
|
+
cwd=current_dir,
|
|
212
|
+
)
|
|
213
|
+
return git_check.returncode == 0
|
|
214
|
+
|
|
215
|
+
def _get_git_root_directory(self, current_dir: Path) -> Path | None:
|
|
216
|
+
"""Get the git repository root directory."""
|
|
217
|
+
import subprocess
|
|
218
|
+
|
|
219
|
+
git_root_result = subprocess.run(
|
|
220
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
221
|
+
capture_output=True,
|
|
222
|
+
text=True,
|
|
223
|
+
cwd=current_dir,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if git_root_result.returncode == 0:
|
|
227
|
+
git_root = Path(git_root_result.stdout.strip())
|
|
228
|
+
return git_root if git_root.exists() else None
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
async def _log_git_detection(self, git_root: Path) -> None:
|
|
232
|
+
"""Log git repository detection to stderr and console."""
|
|
233
|
+
|
|
234
|
+
# Log to stderr for Claude to see
|
|
235
|
+
self._log_to_stderr(git_root)
|
|
236
|
+
|
|
237
|
+
# Log to console if available
|
|
238
|
+
self._log_to_console(git_root)
|
|
239
|
+
|
|
240
|
+
def _log_to_stderr(self, git_root: Path) -> None:
|
|
241
|
+
"""Log git detection messages to stderr."""
|
|
242
|
+
import sys
|
|
243
|
+
|
|
244
|
+
print(
|
|
245
|
+
f"📍 Crackerjack MCP: Git repository detected at {git_root}",
|
|
246
|
+
file=sys.stderr,
|
|
247
|
+
)
|
|
248
|
+
print(
|
|
249
|
+
f"💡 Tip: Auto-setup git working directory with: git_set_working_dir('{git_root}')",
|
|
250
|
+
file=sys.stderr,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def _log_to_console(self, git_root: Path) -> None:
|
|
254
|
+
"""Log git detection messages to console if available."""
|
|
255
|
+
if self.console:
|
|
256
|
+
self.console.print(f"🔧 Auto-detected git repository: {git_root}")
|
|
257
|
+
self.console.print(
|
|
258
|
+
f"💡 Recommend: Use `mcp__git__git_set_working_dir` with path='{git_root}'"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def _handle_git_setup_failure(self, error: Exception) -> None:
|
|
262
|
+
"""Handle git setup failure with graceful fallback."""
|
|
263
|
+
if self.console:
|
|
264
|
+
self.console.print(
|
|
265
|
+
f"[dim]Git auto-setup failed (non-critical): {error}[/dim]"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
async def initialize(self) -> None:
|
|
269
|
+
if self._initialized:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
await self._perform_initialization_sequence()
|
|
274
|
+
self._initialized = True
|
|
275
|
+
|
|
276
|
+
except Exception as e:
|
|
277
|
+
self._handle_initialization_failure(e)
|
|
278
|
+
|
|
279
|
+
async def _perform_initialization_sequence(self) -> None:
|
|
280
|
+
"""Perform the complete initialization sequence."""
|
|
281
|
+
self._setup_console()
|
|
282
|
+
self._setup_directories()
|
|
283
|
+
await self._initialize_components()
|
|
284
|
+
await self._finalize_initialization()
|
|
285
|
+
|
|
286
|
+
def _handle_initialization_failure(self, error: Exception) -> None:
|
|
287
|
+
"""Handle initialization failure with cleanup and error propagation."""
|
|
288
|
+
self._cleanup_failed_initialization()
|
|
289
|
+
msg = f"Failed to initialize MCP server context: {error}"
|
|
290
|
+
raise RuntimeError(msg) from error
|
|
291
|
+
|
|
292
|
+
def _setup_console(self) -> None:
|
|
293
|
+
"""Setup console based on configuration mode."""
|
|
294
|
+
if self.config.stdio_mode:
|
|
295
|
+
io.StringIO()
|
|
296
|
+
self.console = depends.get_sync(Console)
|
|
297
|
+
else:
|
|
298
|
+
self.console = depends.get_sync(Console)
|
|
299
|
+
|
|
300
|
+
def _setup_directories(self) -> None:
|
|
301
|
+
"""Setup required directories."""
|
|
302
|
+
self.progress_dir.mkdir(exist_ok=True)
|
|
303
|
+
|
|
304
|
+
async def _initialize_components(self) -> None:
|
|
305
|
+
"""Initialize all service components."""
|
|
306
|
+
self.cli_runner = WorkflowOrchestrator(
|
|
307
|
+
pkg_path=self.config.project_path,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
self.state_manager = StateManager(
|
|
311
|
+
self.config.state_dir or Path.home() / ".cache" / "crackerjack-mcp",
|
|
312
|
+
self.batched_saver,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
self.error_cache = ErrorCache(
|
|
316
|
+
self.config.cache_dir or Path.home() / ".cache" / "crackerjack-mcp",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
self.rate_limiter = RateLimitMiddleware(self.config.rate_limit_config)
|
|
320
|
+
await self.batched_saver.start()
|
|
321
|
+
|
|
322
|
+
async def _finalize_initialization(self) -> None:
|
|
323
|
+
"""Complete initialization with optional setup and startup tasks."""
|
|
324
|
+
# Auto-setup git working directory for enhanced DX
|
|
325
|
+
await self._auto_setup_git_working_directory()
|
|
326
|
+
|
|
327
|
+
for task in self._startup_tasks:
|
|
328
|
+
await task()
|
|
329
|
+
|
|
330
|
+
def _cleanup_failed_initialization(self) -> None:
|
|
331
|
+
"""Cleanup components after failed initialization."""
|
|
332
|
+
self.cli_runner = None
|
|
333
|
+
self.state_manager = None
|
|
334
|
+
self.error_cache = None
|
|
335
|
+
self.rate_limiter = None
|
|
336
|
+
|
|
337
|
+
async def shutdown(self) -> None:
|
|
338
|
+
if not self._initialized:
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
for task in reversed(self._shutdown_tasks):
|
|
342
|
+
try:
|
|
343
|
+
await task()
|
|
344
|
+
except Exception as e:
|
|
345
|
+
if self.console:
|
|
346
|
+
self.console.print(f"[red]Error during shutdown: {e}[/red]")
|
|
347
|
+
|
|
348
|
+
if self._websocket_health_check_task:
|
|
349
|
+
self._websocket_health_check_task.cancel()
|
|
350
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
351
|
+
await self._websocket_health_check_task
|
|
352
|
+
self._websocket_health_check_task = None
|
|
353
|
+
|
|
354
|
+
await self._stop_websocket_server()
|
|
355
|
+
|
|
356
|
+
if self.rate_limiter:
|
|
357
|
+
await self.rate_limiter.stop()
|
|
358
|
+
|
|
359
|
+
await self.batched_saver.stop()
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
await self.network_manager.cleanup_all()
|
|
363
|
+
except Exception as e:
|
|
364
|
+
if self.console:
|
|
365
|
+
self.console.print(
|
|
366
|
+
f"[yellow]Warning: Network resource cleanup error: {e}[/yellow]"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
await self.resource_manager.cleanup_all()
|
|
371
|
+
except Exception as e:
|
|
372
|
+
if self.console:
|
|
373
|
+
self.console.print(
|
|
374
|
+
f"[yellow]Warning: Resource cleanup error: {e}[/yellow]"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
self._initialized = False
|
|
378
|
+
|
|
379
|
+
def add_startup_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
|
|
380
|
+
self._startup_tasks.append(task)
|
|
381
|
+
|
|
382
|
+
def add_shutdown_task(self, task: t.Callable[[], t.Awaitable[None]]) -> None:
|
|
383
|
+
self._shutdown_tasks.append(task)
|
|
384
|
+
|
|
385
|
+
def validate_job_id(self, job_id: str) -> bool:
|
|
386
|
+
if not job_id:
|
|
387
|
+
return False
|
|
388
|
+
|
|
389
|
+
import uuid
|
|
390
|
+
from contextlib import suppress
|
|
391
|
+
|
|
392
|
+
with suppress(ValueError):
|
|
393
|
+
uuid.UUID(job_id)
|
|
394
|
+
return True
|
|
395
|
+
|
|
396
|
+
from crackerjack.services.regex_patterns import is_valid_job_id
|
|
397
|
+
|
|
398
|
+
if not is_valid_job_id(job_id):
|
|
399
|
+
return False
|
|
400
|
+
|
|
401
|
+
if ".." in job_id or "/" in job_id or "\\" in job_id:
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
import os
|
|
405
|
+
|
|
406
|
+
return os.path.basename(job_id) == job_id
|
|
407
|
+
|
|
408
|
+
async def check_websocket_server_running(self) -> bool:
|
|
409
|
+
import socket
|
|
410
|
+
|
|
411
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
412
|
+
sock.settimeout(1.0)
|
|
413
|
+
result = sock.connect_ex(("localhost", self.websocket_server_port))
|
|
414
|
+
return result == 0
|
|
415
|
+
|
|
416
|
+
async def start_websocket_server(self) -> bool:
|
|
417
|
+
async with self._websocket_process_lock:
|
|
418
|
+
if await self._check_existing_websocket_server():
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
self._print_websocket_startup_message()
|
|
422
|
+
return await self._attempt_websocket_startup()
|
|
423
|
+
|
|
424
|
+
def _print_websocket_startup_message(self) -> None:
|
|
425
|
+
"""Print websocket server startup message."""
|
|
426
|
+
if self.console:
|
|
427
|
+
self.console.print(
|
|
428
|
+
f"🚀 Starting WebSocket server on localhost: {self.websocket_server_port}",
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
async def _attempt_websocket_startup(self) -> bool:
|
|
432
|
+
"""Attempt to start the websocket server with error handling."""
|
|
433
|
+
try:
|
|
434
|
+
await self._spawn_websocket_process()
|
|
435
|
+
await self._register_websocket_cleanup()
|
|
436
|
+
return await self._wait_for_websocket_startup()
|
|
437
|
+
except Exception as e:
|
|
438
|
+
await self._handle_websocket_startup_failure(e)
|
|
439
|
+
return False
|
|
440
|
+
|
|
441
|
+
async def _handle_websocket_startup_failure(self, error: Exception) -> None:
|
|
442
|
+
"""Handle websocket server startup failure."""
|
|
443
|
+
if self.console:
|
|
444
|
+
self.console.print(f"❌ Failed to start WebSocket server: {error}")
|
|
445
|
+
await self._cleanup_dead_websocket_process()
|
|
446
|
+
|
|
447
|
+
async def _check_existing_websocket_server(self) -> bool:
|
|
448
|
+
if (
|
|
449
|
+
self.websocket_server_process
|
|
450
|
+
and self.websocket_server_process.poll() is None
|
|
451
|
+
):
|
|
452
|
+
if await self.check_websocket_server_running():
|
|
453
|
+
if self.console:
|
|
454
|
+
self.console.print(
|
|
455
|
+
f"✅ WebSocket server already running on port {self.websocket_server_port}",
|
|
456
|
+
)
|
|
457
|
+
return True
|
|
458
|
+
await self._cleanup_dead_websocket_process()
|
|
459
|
+
|
|
460
|
+
if await self.check_websocket_server_running():
|
|
461
|
+
if self.console:
|
|
462
|
+
self.console.print(
|
|
463
|
+
f"⚠️ Port {self.websocket_server_port} already in use by another process",
|
|
464
|
+
)
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
async def _spawn_websocket_process(self) -> None:
|
|
470
|
+
import sys
|
|
471
|
+
|
|
472
|
+
self.websocket_server_process = subprocess.Popen(
|
|
473
|
+
[
|
|
474
|
+
sys.executable,
|
|
475
|
+
"-m",
|
|
476
|
+
"crackerjack",
|
|
477
|
+
"--start-websocket-server",
|
|
478
|
+
"--websocket-port",
|
|
479
|
+
str(self.websocket_server_port),
|
|
480
|
+
],
|
|
481
|
+
stdout=subprocess.DEVNULL,
|
|
482
|
+
stderr=subprocess.DEVNULL,
|
|
483
|
+
start_new_session=True,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
if self.websocket_server_process:
|
|
487
|
+
managed_process = self.network_manager.create_subprocess(
|
|
488
|
+
self.websocket_server_process, timeout=30.0
|
|
489
|
+
)
|
|
490
|
+
await managed_process.start_monitoring()
|
|
491
|
+
|
|
492
|
+
async def _register_websocket_cleanup(self) -> None:
|
|
493
|
+
if not self._websocket_cleanup_registered:
|
|
494
|
+
self.add_shutdown_task(self._stop_websocket_server)
|
|
495
|
+
self._websocket_cleanup_registered = True
|
|
496
|
+
|
|
497
|
+
if not self._websocket_health_check_task:
|
|
498
|
+
self._websocket_health_check_task = asyncio.create_task(
|
|
499
|
+
self._websocket_health_monitor(),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
async def _wait_for_websocket_startup(self) -> bool:
|
|
503
|
+
max_attempts = 10
|
|
504
|
+
for _attempt in range(max_attempts):
|
|
505
|
+
await asyncio.sleep(0.5)
|
|
506
|
+
|
|
507
|
+
if (
|
|
508
|
+
self.websocket_server_process is not None
|
|
509
|
+
and self.websocket_server_process.poll() is not None
|
|
510
|
+
):
|
|
511
|
+
return_code = self.websocket_server_process.returncode
|
|
512
|
+
if self.console:
|
|
513
|
+
self.console.print(
|
|
514
|
+
f"❌ WebSocket server process died during startup (exit code: {return_code})",
|
|
515
|
+
)
|
|
516
|
+
self.websocket_server_process = None
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
if await self.check_websocket_server_running():
|
|
520
|
+
if self.console:
|
|
521
|
+
self.console.print(
|
|
522
|
+
f"✅ WebSocket server started successfully on port {self.websocket_server_port}",
|
|
523
|
+
)
|
|
524
|
+
self.console.print(
|
|
525
|
+
f"📊 Progress available at: ws: / / localhost: {self.websocket_server_port}/ ws / progress /{{job_id}}",
|
|
526
|
+
)
|
|
527
|
+
return True
|
|
528
|
+
|
|
529
|
+
if self.console:
|
|
530
|
+
self.console.print(
|
|
531
|
+
f"❌ WebSocket server failed to start within {max_attempts * 0.5}s",
|
|
532
|
+
)
|
|
533
|
+
await self._cleanup_dead_websocket_process()
|
|
534
|
+
return False
|
|
535
|
+
|
|
536
|
+
async def _cleanup_dead_websocket_process(self) -> None:
|
|
537
|
+
if self.websocket_server_process:
|
|
538
|
+
try:
|
|
539
|
+
if (
|
|
540
|
+
self.websocket_server_process is not None
|
|
541
|
+
and self.websocket_server_process.poll() is None
|
|
542
|
+
):
|
|
543
|
+
self.websocket_server_process.terminate()
|
|
544
|
+
try:
|
|
545
|
+
self.websocket_server_process.wait(timeout=2)
|
|
546
|
+
except subprocess.TimeoutExpired:
|
|
547
|
+
self.websocket_server_process.kill()
|
|
548
|
+
self.websocket_server_process.wait(timeout=1)
|
|
549
|
+
|
|
550
|
+
if self.console:
|
|
551
|
+
self.console.print("🧹 Cleaned up dead WebSocket server process")
|
|
552
|
+
except Exception as e:
|
|
553
|
+
if self.console:
|
|
554
|
+
self.console.print(f"⚠️ Error cleaning up WebSocket process: {e}")
|
|
555
|
+
finally:
|
|
556
|
+
self.websocket_server_process = None
|
|
557
|
+
|
|
558
|
+
async def _stop_websocket_server(self) -> None:
|
|
559
|
+
async with self._websocket_process_lock:
|
|
560
|
+
if not self.websocket_server_process:
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
if self.websocket_server_process.poll() is None:
|
|
565
|
+
await self._terminate_live_websocket_process()
|
|
566
|
+
else:
|
|
567
|
+
await self._handle_dead_websocket_process_cleanup()
|
|
568
|
+
|
|
569
|
+
except Exception as e:
|
|
570
|
+
if self.console:
|
|
571
|
+
self.console.print(f"⚠️ Error stopping WebSocket server: {e}")
|
|
572
|
+
finally:
|
|
573
|
+
self.websocket_server_process = None
|
|
574
|
+
|
|
575
|
+
async def _terminate_live_websocket_process(self) -> None:
|
|
576
|
+
if self.console:
|
|
577
|
+
self.console.print("🛑 Stopping WebSocket server")
|
|
578
|
+
|
|
579
|
+
if self.websocket_server_process is not None:
|
|
580
|
+
self.websocket_server_process.terminate()
|
|
581
|
+
|
|
582
|
+
if await self._wait_for_graceful_termination():
|
|
583
|
+
return
|
|
584
|
+
|
|
585
|
+
await self._force_kill_websocket_process()
|
|
586
|
+
|
|
587
|
+
async def _wait_for_graceful_termination(self) -> bool:
|
|
588
|
+
try:
|
|
589
|
+
if self.websocket_server_process is not None:
|
|
590
|
+
self.websocket_server_process.wait(timeout=5)
|
|
591
|
+
if self.console:
|
|
592
|
+
self.console.print("✅ WebSocket server stopped gracefully")
|
|
593
|
+
return True
|
|
594
|
+
except subprocess.TimeoutExpired:
|
|
595
|
+
return False
|
|
596
|
+
|
|
597
|
+
async def _force_kill_websocket_process(self) -> None:
|
|
598
|
+
if self.console:
|
|
599
|
+
self.console.print("⚡ Force killing unresponsive WebSocket server")
|
|
600
|
+
|
|
601
|
+
if self.websocket_server_process is not None:
|
|
602
|
+
self.websocket_server_process.kill()
|
|
603
|
+
|
|
604
|
+
try:
|
|
605
|
+
if self.websocket_server_process is not None:
|
|
606
|
+
self.websocket_server_process.wait(timeout=2)
|
|
607
|
+
if self.console:
|
|
608
|
+
self.console.print("💀 WebSocket server force killed")
|
|
609
|
+
except subprocess.TimeoutExpired:
|
|
610
|
+
if self.console:
|
|
611
|
+
self.console.print("⚠️ WebSocket server process may be zombified")
|
|
612
|
+
|
|
613
|
+
async def _handle_dead_websocket_process_cleanup(self) -> None:
|
|
614
|
+
if self.console:
|
|
615
|
+
self.console.print("💀 WebSocket server process was already dead")
|
|
616
|
+
|
|
617
|
+
async def get_websocket_server_status(self) -> dict[str, t.Any]:
|
|
618
|
+
async with self._websocket_process_lock:
|
|
619
|
+
status = {
|
|
620
|
+
"port": self.websocket_server_port,
|
|
621
|
+
"process_exists": self.websocket_server_process is not None,
|
|
622
|
+
"process_alive": False,
|
|
623
|
+
"server_responding": False,
|
|
624
|
+
"process_id": None,
|
|
625
|
+
"return_code": None,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if self.websocket_server_process:
|
|
629
|
+
status["process_id"] = self.websocket_server_process.pid
|
|
630
|
+
poll_result = self.websocket_server_process.poll()
|
|
631
|
+
status["process_alive"] = poll_result is None
|
|
632
|
+
if poll_result is not None:
|
|
633
|
+
status["return_code"] = poll_result
|
|
634
|
+
|
|
635
|
+
status["server_responding"] = await self.check_websocket_server_running()
|
|
636
|
+
|
|
637
|
+
return status
|
|
638
|
+
|
|
639
|
+
async def _websocket_health_monitor(self) -> None:
|
|
640
|
+
while True:
|
|
641
|
+
try:
|
|
642
|
+
await asyncio.sleep(30)
|
|
643
|
+
await self._check_and_restart_websocket()
|
|
644
|
+
except asyncio.CancelledError:
|
|
645
|
+
break
|
|
646
|
+
except Exception as e:
|
|
647
|
+
if self.console:
|
|
648
|
+
self.console.print(f"⚠️ Error in WebSocket health monitor: {e}")
|
|
649
|
+
await asyncio.sleep(60)
|
|
650
|
+
|
|
651
|
+
async def _check_and_restart_websocket(self) -> None:
|
|
652
|
+
async with self._websocket_process_lock:
|
|
653
|
+
if not self.websocket_server_process:
|
|
654
|
+
return
|
|
655
|
+
|
|
656
|
+
if self.websocket_server_process.poll() is not None:
|
|
657
|
+
await self._handle_dead_websocket_process()
|
|
658
|
+
return
|
|
659
|
+
|
|
660
|
+
if not await self.check_websocket_server_running():
|
|
661
|
+
await self._handle_unresponsive_websocket_server()
|
|
662
|
+
|
|
663
|
+
async def _handle_dead_websocket_process(self) -> None:
|
|
664
|
+
if self.websocket_server_process is not None:
|
|
665
|
+
return_code = self.websocket_server_process.returncode
|
|
666
|
+
if self.console:
|
|
667
|
+
self.console.print(
|
|
668
|
+
f"⚠️ WebSocket server process died (exit code: {return_code}), attempting restart...",
|
|
669
|
+
)
|
|
670
|
+
self.websocket_server_process = None
|
|
671
|
+
await self._restart_websocket_server()
|
|
672
|
+
|
|
673
|
+
async def _handle_unresponsive_websocket_server(self) -> None:
|
|
674
|
+
if self.console:
|
|
675
|
+
self.console.print("⚠️ WebSocket server not responding, restarting...")
|
|
676
|
+
await self._cleanup_dead_websocket_process()
|
|
677
|
+
await self._restart_websocket_server()
|
|
678
|
+
|
|
679
|
+
async def _restart_websocket_server(self) -> None:
|
|
680
|
+
if await self.start_websocket_server():
|
|
681
|
+
if self.console:
|
|
682
|
+
self.console.print("✅ WebSocket server restarted successfully")
|
|
683
|
+
elif self.console:
|
|
684
|
+
self.console.print("❌ Failed to restart WebSocket server")
|
|
685
|
+
|
|
686
|
+
def safe_print(self, *args: t.Any, **kwargs: t.Any) -> None:
|
|
687
|
+
if not self.config.stdio_mode and self.console:
|
|
688
|
+
self.console.print(*args, **kwargs)
|
|
689
|
+
|
|
690
|
+
def create_progress_file_path(self, job_id: str) -> Path:
|
|
691
|
+
if not self.validate_job_id(job_id):
|
|
692
|
+
msg = f"Invalid job_id: {job_id}"
|
|
693
|
+
raise ValueError(msg)
|
|
694
|
+
|
|
695
|
+
return SecurePathValidator.secure_path_join(
|
|
696
|
+
self.progress_dir, f"job-{job_id}.json"
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
async def schedule_state_save(
|
|
700
|
+
self,
|
|
701
|
+
save_id: str,
|
|
702
|
+
save_func: t.Callable[[], None],
|
|
703
|
+
) -> None:
|
|
704
|
+
await self.batched_saver.schedule_save(save_id, save_func)
|
|
705
|
+
|
|
706
|
+
def get_current_time(self) -> str:
|
|
707
|
+
import datetime
|
|
708
|
+
|
|
709
|
+
return datetime.datetime.now().isoformat()
|
|
710
|
+
|
|
711
|
+
def get_context_stats(self) -> dict[str, t.Any]:
|
|
712
|
+
return {
|
|
713
|
+
"initialized": self._initialized,
|
|
714
|
+
"stdio_mode": self.config.stdio_mode,
|
|
715
|
+
"project_path": str(self.config.project_path),
|
|
716
|
+
"progress_dir": str(self.progress_dir),
|
|
717
|
+
"components": {
|
|
718
|
+
"cli_runner": self.cli_runner is not None,
|
|
719
|
+
"state_manager": self.state_manager is not None,
|
|
720
|
+
"error_cache": self.error_cache is not None,
|
|
721
|
+
"rate_limiter": self.rate_limiter is not None,
|
|
722
|
+
"batched_saver": self.batched_saver is not None,
|
|
723
|
+
},
|
|
724
|
+
"websocket_server": {
|
|
725
|
+
"port": self.websocket_server_port,
|
|
726
|
+
"process_exists": self.websocket_server_process is not None,
|
|
727
|
+
"health_monitor_running": self._websocket_health_check_task is not None
|
|
728
|
+
and not self._websocket_health_check_task.done(),
|
|
729
|
+
"cleanup_registered": self._websocket_cleanup_registered,
|
|
730
|
+
},
|
|
731
|
+
"progress_queue": {
|
|
732
|
+
"maxsize": self.progress_queue.maxsize,
|
|
733
|
+
"current_size": self.progress_queue.qsize(),
|
|
734
|
+
"full": self.progress_queue.full(),
|
|
735
|
+
},
|
|
736
|
+
"startup_tasks": len(self._startup_tasks),
|
|
737
|
+
"shutdown_tasks": len(self._shutdown_tasks),
|
|
738
|
+
"batched_saving": self.batched_saver.get_stats(),
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
class MCPContextManager:
|
|
743
|
+
def __init__(self, config: MCPServerConfig) -> None:
|
|
744
|
+
self.context = MCPServerContext(config)
|
|
745
|
+
|
|
746
|
+
async def __aenter__(self) -> MCPServerContext:
|
|
747
|
+
await self.context.initialize()
|
|
748
|
+
return self.context
|
|
749
|
+
|
|
750
|
+
async def __aexit__(
|
|
751
|
+
self,
|
|
752
|
+
exc_type: type[BaseException] | None,
|
|
753
|
+
exc_val: BaseException | None,
|
|
754
|
+
_exc_tb: TracebackType | None,
|
|
755
|
+
) -> None:
|
|
756
|
+
await self.context.shutdown()
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
_global_context: MCPServerContext | None = None
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def get_context() -> MCPServerContext:
|
|
763
|
+
if _global_context is None:
|
|
764
|
+
msg = "MCP server context not initialized. Call set_context() first."
|
|
765
|
+
raise RuntimeError(
|
|
766
|
+
msg,
|
|
767
|
+
)
|
|
768
|
+
return _global_context
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def set_context(context: MCPServerContext) -> None:
|
|
772
|
+
global _global_context
|
|
773
|
+
_global_context = context
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def clear_context() -> None:
|
|
777
|
+
global _global_context
|
|
778
|
+
_global_context = None
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
def get_console() -> Console:
|
|
782
|
+
return get_context().console or depends.get_sync(Console)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def get_state_manager() -> StateManager | None:
|
|
786
|
+
return get_context().state_manager
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def get_error_cache() -> ErrorCache | None:
|
|
790
|
+
return get_context().error_cache
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def get_rate_limiter() -> RateLimitMiddleware | None:
|
|
794
|
+
return get_context().rate_limiter
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def safe_print(*args: t.Any, **kwargs: t.Any) -> None:
|
|
798
|
+
get_context().safe_print(*args, **kwargs)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def validate_job_id(job_id: str) -> bool:
|
|
802
|
+
return get_context().validate_job_id(job_id)
|