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,188 @@
|
|
|
1
|
+
"""WebSocket bridge for routing WorkflowEventBus events to connected clients.
|
|
2
|
+
|
|
3
|
+
This module provides the EventBusWebSocketBridge class that connects the
|
|
4
|
+
WorkflowEventBus to WebSocket clients, enabling real-time workflow progress
|
|
5
|
+
updates without polling.
|
|
6
|
+
|
|
7
|
+
Phase 7.3: WebSocket Streaming for Real-Time Updates
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import typing as t
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
|
|
15
|
+
from acb.depends import Inject, depends
|
|
16
|
+
|
|
17
|
+
from crackerjack.events.workflow_bus import WorkflowEvent, WorkflowEventBus
|
|
18
|
+
|
|
19
|
+
if t.TYPE_CHECKING:
|
|
20
|
+
from acb.actions.events import Event
|
|
21
|
+
from fastapi import WebSocket
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@depends.inject
|
|
25
|
+
class EventBusWebSocketBridge:
|
|
26
|
+
"""Bridges workflow events to WebSocket clients for real-time updates.
|
|
27
|
+
|
|
28
|
+
This class subscribes to all WorkflowEvent types from the WorkflowEventBus
|
|
29
|
+
and routes them to appropriate WebSocket clients based on job_id.
|
|
30
|
+
|
|
31
|
+
Architecture:
|
|
32
|
+
WorkflowActions → WorkflowEventBus → EventBusWebSocketBridge → WebSocket Clients
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
bridge = EventBusWebSocketBridge(event_bus)
|
|
36
|
+
await bridge.register_client(job_id, websocket)
|
|
37
|
+
# Events automatically routed to client
|
|
38
|
+
await bridge.unregister_client(job_id, websocket)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
event_bus: Inject[WorkflowEventBus] = None,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Initialize event bridge with WorkflowEventBus.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
event_bus: WorkflowEventBus instance (injected via DI)
|
|
49
|
+
"""
|
|
50
|
+
self._event_bus = event_bus
|
|
51
|
+
self._clients: dict[str, list[WebSocket]] = defaultdict(list)
|
|
52
|
+
self._subscription_ids: list[str] = []
|
|
53
|
+
|
|
54
|
+
# Subscribe to all workflow events
|
|
55
|
+
if self._event_bus:
|
|
56
|
+
self._subscribe_to_events()
|
|
57
|
+
|
|
58
|
+
def _subscribe_to_events(self) -> None:
|
|
59
|
+
"""Subscribe to all WorkflowEvent types for event routing."""
|
|
60
|
+
for event_type in WorkflowEvent:
|
|
61
|
+
subscription_id = self._event_bus.subscribe(
|
|
62
|
+
event_type,
|
|
63
|
+
self._handle_workflow_event,
|
|
64
|
+
)
|
|
65
|
+
self._subscription_ids.append(subscription_id)
|
|
66
|
+
|
|
67
|
+
async def _handle_workflow_event(self, event: Event) -> None:
|
|
68
|
+
"""Handle workflow event and route to appropriate clients.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
event: ACB Event object with workflow event data
|
|
72
|
+
"""
|
|
73
|
+
# Extract job_id from event payload
|
|
74
|
+
# Events include step_id in format "action_name_timestamp" or "workflow_id"
|
|
75
|
+
payload = event.payload
|
|
76
|
+
step_id = payload.get("step_id", "")
|
|
77
|
+
|
|
78
|
+
if not step_id:
|
|
79
|
+
# No step_id in payload, skip routing
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# For now, broadcast to all connected clients
|
|
83
|
+
# In future, could parse step_id to extract workflow_id/job_id for filtering
|
|
84
|
+
all_clients: list[WebSocket] = []
|
|
85
|
+
for clients in self._clients.values():
|
|
86
|
+
all_clients.extend(clients)
|
|
87
|
+
|
|
88
|
+
if not all_clients:
|
|
89
|
+
# No clients connected
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Transform event to WebSocket message format
|
|
93
|
+
message = self._transform_event_to_message(event)
|
|
94
|
+
|
|
95
|
+
# Send to all clients
|
|
96
|
+
await self._broadcast_to_clients(all_clients, message)
|
|
97
|
+
|
|
98
|
+
def _transform_event_to_message(self, event: Event) -> dict[str, t.Any]:
|
|
99
|
+
"""Transform ACB Event to WebSocket message format.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
event: ACB Event object
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict with event_type, data, and timestamp
|
|
106
|
+
"""
|
|
107
|
+
return {
|
|
108
|
+
"event_type": event.event_type.value
|
|
109
|
+
if hasattr(event.event_type, "value")
|
|
110
|
+
else str(event.event_type),
|
|
111
|
+
"data": event.payload,
|
|
112
|
+
"timestamp": event.payload.get("timestamp"),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async def _broadcast_to_clients(
|
|
116
|
+
self,
|
|
117
|
+
clients: list[WebSocket],
|
|
118
|
+
message: dict[str, t.Any],
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Broadcast message to all clients, removing disconnected ones.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
clients: List of WebSocket clients to send to
|
|
124
|
+
message: Message data to send
|
|
125
|
+
"""
|
|
126
|
+
disconnected: list[WebSocket] = []
|
|
127
|
+
|
|
128
|
+
for websocket in clients:
|
|
129
|
+
try:
|
|
130
|
+
await websocket.send_json(message)
|
|
131
|
+
except Exception:
|
|
132
|
+
# Client disconnected, mark for removal
|
|
133
|
+
disconnected.append(websocket)
|
|
134
|
+
|
|
135
|
+
# Remove disconnected clients
|
|
136
|
+
for websocket in disconnected:
|
|
137
|
+
clients.remove(websocket)
|
|
138
|
+
|
|
139
|
+
async def register_client(self, job_id: str, websocket: WebSocket) -> None:
|
|
140
|
+
"""Register WebSocket client for job-specific event updates.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
job_id: Job identifier to subscribe to
|
|
144
|
+
websocket: WebSocket connection to send events to
|
|
145
|
+
"""
|
|
146
|
+
self._clients[job_id].append(websocket)
|
|
147
|
+
|
|
148
|
+
async def unregister_client(self, job_id: str, websocket: WebSocket) -> None:
|
|
149
|
+
"""Unregister WebSocket client from event updates.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
job_id: Job identifier to unsubscribe from
|
|
153
|
+
websocket: WebSocket connection to remove
|
|
154
|
+
"""
|
|
155
|
+
if job_id in self._clients:
|
|
156
|
+
with suppress(ValueError):
|
|
157
|
+
self._clients[job_id].remove(websocket)
|
|
158
|
+
|
|
159
|
+
# Clean up empty job client lists
|
|
160
|
+
if not self._clients[job_id]:
|
|
161
|
+
del self._clients[job_id]
|
|
162
|
+
|
|
163
|
+
def get_active_connections(self) -> int:
|
|
164
|
+
"""Get count of active WebSocket connections.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Total number of active WebSocket connections across all jobs
|
|
168
|
+
"""
|
|
169
|
+
return sum(len(clients) for clients in self._clients.values())
|
|
170
|
+
|
|
171
|
+
def get_jobs_with_clients(self) -> list[str]:
|
|
172
|
+
"""Get list of job IDs with active clients.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of job_id strings that have connected clients
|
|
176
|
+
"""
|
|
177
|
+
return [job_id for job_id, clients in self._clients.items() if clients]
|
|
178
|
+
|
|
179
|
+
def cleanup(self) -> None:
|
|
180
|
+
"""Cleanup event subscriptions and client connections."""
|
|
181
|
+
# Unsubscribe from all events
|
|
182
|
+
if self._event_bus:
|
|
183
|
+
for subscription_id in self._subscription_ids:
|
|
184
|
+
self._event_bus.unsubscribe(subscription_id)
|
|
185
|
+
|
|
186
|
+
# Clear client lists
|
|
187
|
+
self._clients.clear()
|
|
188
|
+
self._subscription_ids.clear()
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import typing as t
|
|
5
|
+
import uuid
|
|
6
|
+
from contextlib import suppress
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Final
|
|
9
|
+
from uuid import UUID, uuid4
|
|
10
|
+
|
|
11
|
+
from acb import console
|
|
12
|
+
from acb.depends import depends
|
|
13
|
+
|
|
14
|
+
from crackerjack.core.timeout_manager import TimeoutStrategy, get_timeout_manager
|
|
15
|
+
from crackerjack.services.input_validator import get_input_validator
|
|
16
|
+
from crackerjack.services.secure_path_utils import SecurePathValidator
|
|
17
|
+
|
|
18
|
+
# Phase 9.3: ACB Integration - Module registration for dependency injection
|
|
19
|
+
# Note: Currently using file-based JSON storage for job tracking
|
|
20
|
+
# Future enhancement: Consider ACB SQL adapter for scalability if needed
|
|
21
|
+
MODULE_ID: Final[UUID] = uuid4()
|
|
22
|
+
MODULE_STATUS: Final[str] = "stable"
|
|
23
|
+
|
|
24
|
+
# console imported from acb
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class JobManager:
|
|
28
|
+
def __init__(self, progress_dir: Path) -> None:
|
|
29
|
+
self.progress_dir = SecurePathValidator.validate_safe_path(progress_dir)
|
|
30
|
+
self.active_connections: dict[str, set[Any]] = {}
|
|
31
|
+
self.known_jobs: set[str] = set()
|
|
32
|
+
self.is_running = True
|
|
33
|
+
|
|
34
|
+
self.progress_dir.mkdir(exist_ok=True)
|
|
35
|
+
|
|
36
|
+
def validate_job_id(self, job_id: str) -> bool:
|
|
37
|
+
if not job_id:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
with suppress(ValueError):
|
|
41
|
+
uuid.UUID(job_id)
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
result = get_input_validator().validate_job_id(job_id)
|
|
45
|
+
return result.valid
|
|
46
|
+
|
|
47
|
+
def add_connection(self, job_id: str, websocket: Any) -> None:
|
|
48
|
+
if job_id not in self.active_connections:
|
|
49
|
+
self.active_connections[job_id] = set()
|
|
50
|
+
self.active_connections[job_id].add(websocket)
|
|
51
|
+
|
|
52
|
+
def remove_connection(self, job_id: str, websocket: Any) -> None:
|
|
53
|
+
if job_id in self.active_connections:
|
|
54
|
+
self.active_connections[job_id].discard(websocket)
|
|
55
|
+
if not self.active_connections[job_id]:
|
|
56
|
+
del self.active_connections[job_id]
|
|
57
|
+
|
|
58
|
+
async def broadcast_to_job(self, job_id: str, data: dict[str, t.Any]) -> None:
|
|
59
|
+
if job_id not in self.active_connections:
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
timeout_manager = get_timeout_manager()
|
|
63
|
+
connections = self.active_connections[job_id].copy()
|
|
64
|
+
|
|
65
|
+
send_tasks = self._create_broadcast_tasks(connections, timeout_manager, data)
|
|
66
|
+
|
|
67
|
+
if send_tasks:
|
|
68
|
+
await self._execute_broadcast_tasks(job_id, send_tasks)
|
|
69
|
+
|
|
70
|
+
def _create_broadcast_tasks(
|
|
71
|
+
self, connections: set[t.Any], timeout_manager: t.Any, data: dict[str, t.Any]
|
|
72
|
+
) -> list[tuple[t.Any, asyncio.Task[t.Any]]]:
|
|
73
|
+
send_tasks = []
|
|
74
|
+
for websocket in connections:
|
|
75
|
+
task = asyncio.create_task(
|
|
76
|
+
timeout_manager.with_timeout(
|
|
77
|
+
"websocket_broadcast",
|
|
78
|
+
websocket.send_json(data),
|
|
79
|
+
timeout=2.0,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
send_tasks.append((websocket, task))
|
|
83
|
+
return send_tasks
|
|
84
|
+
|
|
85
|
+
async def _execute_broadcast_tasks(
|
|
86
|
+
self, job_id: str, send_tasks: list[t.Any]
|
|
87
|
+
) -> None:
|
|
88
|
+
try:
|
|
89
|
+
done, pending = await asyncio.wait(
|
|
90
|
+
[task for _, task in send_tasks],
|
|
91
|
+
timeout=5.0,
|
|
92
|
+
return_when=asyncio.ALL_COMPLETED,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
await self._handle_broadcast_results(job_id, send_tasks, done, pending)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
console.print(f"[red]Broadcast error: {e}[/red]")
|
|
99
|
+
await self._cleanup_failed_broadcast(job_id, send_tasks)
|
|
100
|
+
|
|
101
|
+
async def _handle_broadcast_results(
|
|
102
|
+
self,
|
|
103
|
+
job_id: str,
|
|
104
|
+
send_tasks: list[t.Any],
|
|
105
|
+
done: set[t.Any],
|
|
106
|
+
pending: set[t.Any],
|
|
107
|
+
) -> None:
|
|
108
|
+
for websocket, task in send_tasks:
|
|
109
|
+
if task in pending:
|
|
110
|
+
task.cancel()
|
|
111
|
+
self.remove_connection(job_id, websocket)
|
|
112
|
+
elif task in done:
|
|
113
|
+
try:
|
|
114
|
+
await task
|
|
115
|
+
except Exception:
|
|
116
|
+
self.remove_connection(job_id, websocket)
|
|
117
|
+
|
|
118
|
+
if pending:
|
|
119
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
120
|
+
|
|
121
|
+
async def _cleanup_failed_broadcast(
|
|
122
|
+
self, job_id: str, send_tasks: list[t.Any]
|
|
123
|
+
) -> None:
|
|
124
|
+
for websocket, task in send_tasks:
|
|
125
|
+
if not task.done():
|
|
126
|
+
task.cancel()
|
|
127
|
+
self.remove_connection(job_id, websocket)
|
|
128
|
+
|
|
129
|
+
def get_latest_job_id(self) -> str | None:
|
|
130
|
+
if not self.progress_dir.exists():
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
progress_files = list[t.Any](self.progress_dir.glob("job-*.json"))
|
|
134
|
+
if not progress_files:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
latest_file = max(progress_files, key=lambda f: f.stat().st_mtime)
|
|
138
|
+
return self.extract_job_id_from_file(latest_file)
|
|
139
|
+
|
|
140
|
+
def extract_job_id_from_file(self, progress_file: Path) -> str | None:
|
|
141
|
+
return (
|
|
142
|
+
progress_file.stem[4:] if progress_file.stem.startswith("job -") else None
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def get_job_progress(self, job_id: str) -> dict[str, t.Any] | None:
|
|
146
|
+
if not self.validate_job_id(job_id):
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
progress_file = SecurePathValidator.secure_path_join(
|
|
151
|
+
self.progress_dir, f"job-{job_id}.json"
|
|
152
|
+
)
|
|
153
|
+
if not progress_file.exists():
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
SecurePathValidator.validate_file_size(progress_file)
|
|
157
|
+
|
|
158
|
+
return json.loads(progress_file.read_text()) # type: ignore[no-any-return]
|
|
159
|
+
except (json.JSONDecodeError, OSError):
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
async def _process_progress_file(self, progress_file: Path) -> None:
|
|
163
|
+
try:
|
|
164
|
+
validated_file = SecurePathValidator.validate_safe_path(
|
|
165
|
+
progress_file, self.progress_dir
|
|
166
|
+
)
|
|
167
|
+
except Exception:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
job_id = self.extract_job_id_from_file(validated_file)
|
|
171
|
+
if not (job_id and self.validate_job_id(job_id)):
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
progress_data = self.get_job_progress(job_id)
|
|
175
|
+
if progress_data and job_id not in self.known_jobs:
|
|
176
|
+
self.known_jobs.add(job_id)
|
|
177
|
+
console.print(f"[green]New job detected: {job_id}[/ green]")
|
|
178
|
+
await self.broadcast_to_job(job_id, progress_data)
|
|
179
|
+
|
|
180
|
+
async def _monitor_directory_changes(self) -> None:
|
|
181
|
+
timeout_manager = get_timeout_manager()
|
|
182
|
+
consecutive_errors = 0
|
|
183
|
+
max_consecutive_errors = 5
|
|
184
|
+
|
|
185
|
+
while self.is_running:
|
|
186
|
+
try:
|
|
187
|
+
async with timeout_manager.timeout_context(
|
|
188
|
+
"file_operations",
|
|
189
|
+
timeout=10.0,
|
|
190
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
191
|
+
):
|
|
192
|
+
if self.progress_dir.exists():
|
|
193
|
+
for progress_file in self.progress_dir.glob("job-*.json"):
|
|
194
|
+
try:
|
|
195
|
+
await timeout_manager.with_timeout(
|
|
196
|
+
"file_operations",
|
|
197
|
+
self._process_progress_file(progress_file),
|
|
198
|
+
timeout=5.0,
|
|
199
|
+
)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
console.print(
|
|
202
|
+
f"[yellow]File processing error: {e}[/yellow]"
|
|
203
|
+
)
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
consecutive_errors = 0
|
|
207
|
+
await asyncio.sleep(1)
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
consecutive_errors += 1
|
|
211
|
+
console.print(f"[red]Progress monitoring error: {e}[/red]")
|
|
212
|
+
|
|
213
|
+
if consecutive_errors >= max_consecutive_errors:
|
|
214
|
+
console.print(
|
|
215
|
+
f"[red]Too many consecutive errors ({consecutive_errors}), stopping monitor[/red]"
|
|
216
|
+
)
|
|
217
|
+
break
|
|
218
|
+
|
|
219
|
+
delay = min(5 * (2 ** (consecutive_errors - 1)), 60)
|
|
220
|
+
await asyncio.sleep(delay)
|
|
221
|
+
|
|
222
|
+
async def monitor_progress_files(self) -> None:
|
|
223
|
+
from crackerjack.mcp.file_monitor import create_progress_monitor
|
|
224
|
+
|
|
225
|
+
console.print("[blue]Starting progress file monitoring...[/blue]")
|
|
226
|
+
timeout_manager = get_timeout_manager()
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
async with timeout_manager.timeout_context(
|
|
230
|
+
"file_operations",
|
|
231
|
+
timeout=30.0,
|
|
232
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
233
|
+
):
|
|
234
|
+
monitor = create_progress_monitor(self.progress_dir)
|
|
235
|
+
await monitor.start()
|
|
236
|
+
|
|
237
|
+
def on_progress_update(
|
|
238
|
+
job_id: str, progress_data: dict[str, t.Any]
|
|
239
|
+
) -> None:
|
|
240
|
+
if job_id and self.validate_job_id(job_id):
|
|
241
|
+
|
|
242
|
+
async def safe_broadcast() -> None:
|
|
243
|
+
try:
|
|
244
|
+
await timeout_manager.with_timeout(
|
|
245
|
+
"websocket_broadcast",
|
|
246
|
+
self.broadcast_to_job(job_id, progress_data),
|
|
247
|
+
timeout=5.0,
|
|
248
|
+
)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
console.print(
|
|
251
|
+
f"[yellow]Broadcast failed for job {job_id}: {e}[/yellow]"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
asyncio.create_task(safe_broadcast()) # type: ignore[no-untyped-call]
|
|
255
|
+
|
|
256
|
+
if job_id not in self.known_jobs:
|
|
257
|
+
self.known_jobs.add(job_id)
|
|
258
|
+
console.print(f"[green]New job detected: {job_id}[/green]")
|
|
259
|
+
|
|
260
|
+
await self._monitor_directory_changes()
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
console.print(f"[red]Progress monitoring setup error: {e}[/red]")
|
|
264
|
+
|
|
265
|
+
async def cleanup_old_jobs(self) -> None:
|
|
266
|
+
timeout_manager = get_timeout_manager()
|
|
267
|
+
|
|
268
|
+
while self.is_running:
|
|
269
|
+
try:
|
|
270
|
+
await timeout_manager.with_timeout(
|
|
271
|
+
"file_operations",
|
|
272
|
+
self._perform_cleanup_cycle(),
|
|
273
|
+
timeout=30.0,
|
|
274
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
275
|
+
)
|
|
276
|
+
await asyncio.sleep(3600)
|
|
277
|
+
except Exception as e:
|
|
278
|
+
console.print(f"[red]Cleanup error: {e}[/red]")
|
|
279
|
+
|
|
280
|
+
await asyncio.sleep(1800)
|
|
281
|
+
|
|
282
|
+
async def _perform_cleanup_cycle(self) -> None:
|
|
283
|
+
if not self.progress_dir.exists():
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
cutoff_time = self._calculate_cleanup_cutoff_time()
|
|
287
|
+
old_job_files = self._find_old_job_files(cutoff_time)
|
|
288
|
+
|
|
289
|
+
for progress_file in old_job_files:
|
|
290
|
+
self._cleanup_old_job_file(progress_file)
|
|
291
|
+
|
|
292
|
+
def _calculate_cleanup_cutoff_time(self) -> float:
|
|
293
|
+
return time.time() - (24 * 60 * 60)
|
|
294
|
+
|
|
295
|
+
def _find_old_job_files(self, cutoff_time: float) -> list[Path]:
|
|
296
|
+
return [
|
|
297
|
+
progress_file
|
|
298
|
+
for progress_file in self.progress_dir.glob("job - *.json")
|
|
299
|
+
if progress_file.stat().st_mtime < cutoff_time
|
|
300
|
+
]
|
|
301
|
+
|
|
302
|
+
def _cleanup_old_job_file(self, progress_file: Path) -> None:
|
|
303
|
+
job_id = self.extract_job_id_from_file(progress_file)
|
|
304
|
+
|
|
305
|
+
if job_id not in self.active_connections:
|
|
306
|
+
progress_file.unlink(missing_ok=True)
|
|
307
|
+
console.print(f"[yellow]Cleaned up old job: {job_id}[/ yellow]")
|
|
308
|
+
|
|
309
|
+
async def timeout_stuck_jobs(self) -> None:
|
|
310
|
+
timeout_manager = get_timeout_manager()
|
|
311
|
+
|
|
312
|
+
while self.is_running:
|
|
313
|
+
try:
|
|
314
|
+
await timeout_manager.with_timeout(
|
|
315
|
+
"file_operations",
|
|
316
|
+
self._check_and_timeout_stuck_jobs(),
|
|
317
|
+
timeout=60.0,
|
|
318
|
+
strategy=TimeoutStrategy.GRACEFUL_DEGRADATION,
|
|
319
|
+
)
|
|
320
|
+
await asyncio.sleep(300)
|
|
321
|
+
except Exception as e:
|
|
322
|
+
console.print(f"[red]Timeout check error: {e}[/red]")
|
|
323
|
+
|
|
324
|
+
await asyncio.sleep(300)
|
|
325
|
+
|
|
326
|
+
async def _check_and_timeout_stuck_jobs(self) -> None:
|
|
327
|
+
if not self.progress_dir.exists():
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
current_time = time.time()
|
|
331
|
+
timeout_seconds = 30 * 60
|
|
332
|
+
|
|
333
|
+
for progress_file in self.progress_dir.glob("job-* .json"):
|
|
334
|
+
await self._process_job_timeout_check(
|
|
335
|
+
progress_file,
|
|
336
|
+
current_time,
|
|
337
|
+
timeout_seconds,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
async def _process_job_timeout_check(
|
|
341
|
+
self,
|
|
342
|
+
progress_file: Path,
|
|
343
|
+
current_time: float,
|
|
344
|
+
timeout_seconds: int,
|
|
345
|
+
) -> None:
|
|
346
|
+
try:
|
|
347
|
+
validated_file = SecurePathValidator.validate_safe_path(
|
|
348
|
+
progress_file, self.progress_dir
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
SecurePathValidator.validate_file_size(validated_file)
|
|
352
|
+
|
|
353
|
+
progress_data = json.loads(validated_file.read_text())
|
|
354
|
+
|
|
355
|
+
if self._should_timeout_job(
|
|
356
|
+
progress_data,
|
|
357
|
+
validated_file,
|
|
358
|
+
current_time,
|
|
359
|
+
timeout_seconds,
|
|
360
|
+
):
|
|
361
|
+
self._timeout_job(progress_data, validated_file)
|
|
362
|
+
|
|
363
|
+
except (json.JSONDecodeError, OSError, Exception):
|
|
364
|
+
pass
|
|
365
|
+
|
|
366
|
+
def _should_timeout_job(
|
|
367
|
+
self,
|
|
368
|
+
progress_data: dict[str, t.Any],
|
|
369
|
+
progress_file: Path,
|
|
370
|
+
current_time: float,
|
|
371
|
+
timeout_seconds: int,
|
|
372
|
+
) -> bool:
|
|
373
|
+
return (
|
|
374
|
+
progress_data.get("status") == "running"
|
|
375
|
+
and current_time - progress_file.stat().st_mtime > timeout_seconds
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def _timeout_job(
|
|
379
|
+
self, progress_data: dict[str, t.Any], progress_file: Path
|
|
380
|
+
) -> None:
|
|
381
|
+
progress_data["status"] = "failed"
|
|
382
|
+
progress_data["message"] = "Job timed out (no updates for 30 minutes)"
|
|
383
|
+
|
|
384
|
+
progress_file.write_text(json.dumps(progress_data, indent=2))
|
|
385
|
+
|
|
386
|
+
job_id = progress_data.get("job_id", "unknown")
|
|
387
|
+
console.print(f"[red]Job {job_id} timed out and marked as failed[/ red]")
|
|
388
|
+
|
|
389
|
+
def cleanup(self) -> None:
|
|
390
|
+
self.is_running = False
|
|
391
|
+
console.print("[blue]Job manager cleanup completed[/blue]")
|
|
392
|
+
|
|
393
|
+
@property
|
|
394
|
+
def module_id(self) -> UUID:
|
|
395
|
+
"""Reference to module-level MODULE_ID for ACB integration."""
|
|
396
|
+
return MODULE_ID
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def module_status(self) -> str:
|
|
400
|
+
"""Module status for ACB integration."""
|
|
401
|
+
return MODULE_STATUS
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
# Phase 9.3: ACB Integration - Register JobManager with dependency injection system
|
|
405
|
+
with suppress(Exception):
|
|
406
|
+
depends.set(JobManager)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Monitoring endpoints module.
|
|
2
|
+
|
|
3
|
+
This module provides WebSocket and REST API endpoints for real-time
|
|
4
|
+
monitoring, metrics streaming, intelligence features, and error analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .factory import create_monitoring_endpoints
|
|
8
|
+
from .models import (
|
|
9
|
+
HealthResponseModel,
|
|
10
|
+
TelemetryEventModel,
|
|
11
|
+
TelemetryResponseModel,
|
|
12
|
+
TelemetrySnapshotModel,
|
|
13
|
+
UnifiedMetricsModel,
|
|
14
|
+
)
|
|
15
|
+
from .websocket_manager import MonitoringWebSocketManager
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"create_monitoring_endpoints",
|
|
19
|
+
"MonitoringWebSocketManager",
|
|
20
|
+
"TelemetryEventModel",
|
|
21
|
+
"TelemetrySnapshotModel",
|
|
22
|
+
"TelemetryResponseModel",
|
|
23
|
+
"UnifiedMetricsModel",
|
|
24
|
+
"HealthResponseModel",
|
|
25
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""REST API endpoint registration.
|
|
2
|
+
|
|
3
|
+
This module provides registration functions for all REST API endpoints
|
|
4
|
+
including telemetry, metrics, intelligence, dependencies, and heatmap analysis.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .dependencies import register_dependency_api_endpoints
|
|
8
|
+
from .heatmap import register_heatmap_api_endpoints
|
|
9
|
+
from .intelligence import register_intelligence_api_endpoints
|
|
10
|
+
from .metrics import register_metrics_api_endpoints
|
|
11
|
+
from .telemetry import register_telemetry_api_endpoints
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"register_telemetry_api_endpoints",
|
|
15
|
+
"register_metrics_api_endpoints",
|
|
16
|
+
"register_intelligence_api_endpoints",
|
|
17
|
+
"register_dependency_api_endpoints",
|
|
18
|
+
"register_heatmap_api_endpoints",
|
|
19
|
+
]
|