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,267 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import typing as t
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..agents.base import AgentContext, Issue, IssueType, Priority
|
|
7
|
+
from ..agents.coordinator import AgentCoordinator
|
|
8
|
+
from ..models.protocols import OptionsProtocol
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProactiveWorkflowPipeline:
|
|
12
|
+
def __init__(self, project_path: Path) -> None:
|
|
13
|
+
self.project_path = project_path
|
|
14
|
+
self.logger = logging.getLogger(__name__)
|
|
15
|
+
self._architect_agent_coordinator: AgentCoordinator | None = None
|
|
16
|
+
|
|
17
|
+
async def run_complete_workflow_with_planning(
|
|
18
|
+
self, options: OptionsProtocol
|
|
19
|
+
) -> bool:
|
|
20
|
+
self.logger.info("Starting proactive workflow with architectural planning")
|
|
21
|
+
|
|
22
|
+
start_time = time.time()
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
assessment = await self._assess_codebase_architecture()
|
|
26
|
+
|
|
27
|
+
if assessment.needs_planning:
|
|
28
|
+
self.logger.info("Codebase requires architectural planning")
|
|
29
|
+
|
|
30
|
+
architectural_plan = await self._create_comprehensive_plan(assessment)
|
|
31
|
+
|
|
32
|
+
result = await self._execute_planned_workflow(
|
|
33
|
+
options, architectural_plan
|
|
34
|
+
)
|
|
35
|
+
else:
|
|
36
|
+
self.logger.info(
|
|
37
|
+
"Codebase is architecturally sound, using standard workflow"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
result = await self._execute_standard_workflow(options)
|
|
41
|
+
|
|
42
|
+
execution_time = time.time() - start_time
|
|
43
|
+
self.logger.info(f"Proactive workflow completed in {execution_time: .2f}s")
|
|
44
|
+
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
self.logger.exception(f"Proactive workflow failed: {e}")
|
|
49
|
+
|
|
50
|
+
return await self._execute_standard_workflow(options)
|
|
51
|
+
|
|
52
|
+
async def _assess_codebase_architecture(self) -> "ArchitecturalAssessment":
|
|
53
|
+
self.logger.info("Assessing codebase architecture")
|
|
54
|
+
|
|
55
|
+
if not self._architect_agent_coordinator:
|
|
56
|
+
agent_context = AgentContext(project_path=self.project_path)
|
|
57
|
+
self._architect_agent_coordinator = AgentCoordinator(agent_context)
|
|
58
|
+
self._architect_agent_coordinator.initialize_agents()
|
|
59
|
+
|
|
60
|
+
test_issues = await self._identify_potential_issues()
|
|
61
|
+
|
|
62
|
+
needs_planning = self._evaluate_planning_need(test_issues)
|
|
63
|
+
|
|
64
|
+
return ArchitecturalAssessment(
|
|
65
|
+
needs_planning=needs_planning,
|
|
66
|
+
complexity_score=len(test_issues),
|
|
67
|
+
potential_issues=test_issues,
|
|
68
|
+
recommended_strategy="proactive" if needs_planning else "standard",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def _identify_potential_issues(self) -> list[Issue]:
|
|
72
|
+
potential_issues: list[Issue] = []
|
|
73
|
+
|
|
74
|
+
potential_issues.extend(
|
|
75
|
+
(
|
|
76
|
+
Issue(
|
|
77
|
+
id="arch_assessment_complexity",
|
|
78
|
+
type=IssueType.COMPLEXITY,
|
|
79
|
+
severity=Priority.HIGH,
|
|
80
|
+
message="Potential complexity hotspots detected",
|
|
81
|
+
file_path=str(self.project_path),
|
|
82
|
+
details=["Multiple functions may exceed complexity threshold"],
|
|
83
|
+
),
|
|
84
|
+
Issue(
|
|
85
|
+
id="arch_assessment_dry",
|
|
86
|
+
type=IssueType.DRY_VIOLATION,
|
|
87
|
+
severity=Priority.MEDIUM,
|
|
88
|
+
message="Potential code duplication patterns",
|
|
89
|
+
file_path=str(self.project_path),
|
|
90
|
+
details=["Similar patterns may exist across modules"],
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return potential_issues
|
|
96
|
+
|
|
97
|
+
def _evaluate_planning_need(self, issues: list[Issue]) -> bool:
|
|
98
|
+
complex_issues = [
|
|
99
|
+
issue
|
|
100
|
+
for issue in issues
|
|
101
|
+
if issue.type
|
|
102
|
+
in {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION, IssueType.PERFORMANCE}
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
return len(complex_issues) >= 2
|
|
106
|
+
|
|
107
|
+
async def _create_comprehensive_plan(
|
|
108
|
+
self, assessment: "ArchitecturalAssessment"
|
|
109
|
+
) -> dict[str, t.Any]:
|
|
110
|
+
self.logger.info("Creating comprehensive architectural plan")
|
|
111
|
+
|
|
112
|
+
if self._architect_agent_coordinator is None:
|
|
113
|
+
raise RuntimeError("ArchitectAgentCoordinator is not initialized")
|
|
114
|
+
|
|
115
|
+
architect = self._architect_agent_coordinator._get_architect_agent()
|
|
116
|
+
|
|
117
|
+
if not architect:
|
|
118
|
+
self.logger.warning("No ArchitectAgent available, creating basic plan")
|
|
119
|
+
return {
|
|
120
|
+
"strategy": "basic_reactive",
|
|
121
|
+
"phases": ["standard_workflow"],
|
|
122
|
+
"patterns": ["default"],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
complex_issues = [
|
|
126
|
+
issue
|
|
127
|
+
for issue in assessment.potential_issues
|
|
128
|
+
if issue.type in {IssueType.COMPLEXITY, IssueType.DRY_VIOLATION}
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if complex_issues:
|
|
132
|
+
primary_issue = complex_issues[0]
|
|
133
|
+
base_plan = await architect.plan_before_action(primary_issue)
|
|
134
|
+
|
|
135
|
+
comprehensive_plan = base_plan | {
|
|
136
|
+
"phases": [
|
|
137
|
+
"configuration_setup",
|
|
138
|
+
"fast_hooks_with_architecture",
|
|
139
|
+
"architectural_refactoring",
|
|
140
|
+
"comprehensive_validation",
|
|
141
|
+
"pattern_learning",
|
|
142
|
+
],
|
|
143
|
+
"integration_points": [
|
|
144
|
+
"architect_guided_fixing",
|
|
145
|
+
"pattern_caching",
|
|
146
|
+
"validation_against_plan",
|
|
147
|
+
],
|
|
148
|
+
}
|
|
149
|
+
else:
|
|
150
|
+
comprehensive_plan = {
|
|
151
|
+
"strategy": "lightweight_proactive",
|
|
152
|
+
"phases": ["standard_workflow_enhanced"],
|
|
153
|
+
"patterns": ["cached_patterns"],
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
self.logger.info(
|
|
157
|
+
f"Created plan with strategy: {comprehensive_plan.get('strategy')}"
|
|
158
|
+
)
|
|
159
|
+
return comprehensive_plan
|
|
160
|
+
|
|
161
|
+
async def _execute_planned_workflow(
|
|
162
|
+
self, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
163
|
+
) -> bool:
|
|
164
|
+
strategy = plan.get("strategy", "basic_reactive")
|
|
165
|
+
phases = plan.get("phases", ["standard_workflow"])
|
|
166
|
+
|
|
167
|
+
self.logger.info(f"Executing {strategy} workflow with {len(phases)} phases")
|
|
168
|
+
|
|
169
|
+
for phase in phases:
|
|
170
|
+
success = await self._execute_workflow_phase(phase, options, plan)
|
|
171
|
+
if not success and phase in (
|
|
172
|
+
"configuration_setup",
|
|
173
|
+
"architectural_refactoring",
|
|
174
|
+
):
|
|
175
|
+
self.logger.error(f"Critical phase {phase} failed")
|
|
176
|
+
return False
|
|
177
|
+
elif not success:
|
|
178
|
+
self.logger.warning(f"Phase {phase} had issues but continuing")
|
|
179
|
+
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
async def _execute_workflow_phase(
|
|
183
|
+
self, phase: str, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
184
|
+
) -> bool:
|
|
185
|
+
self.logger.info(f"Executing phase: {phase}")
|
|
186
|
+
|
|
187
|
+
if phase == "configuration_setup":
|
|
188
|
+
return await self._setup_with_architecture(options, plan)
|
|
189
|
+
elif phase == "fast_hooks_with_architecture":
|
|
190
|
+
return await self._run_fast_hooks_with_planning(options, plan)
|
|
191
|
+
elif phase == "architectural_refactoring":
|
|
192
|
+
return await self._perform_architectural_refactoring(options, plan)
|
|
193
|
+
elif phase == "comprehensive_validation":
|
|
194
|
+
return await self._comprehensive_validation(options, plan)
|
|
195
|
+
elif phase == "pattern_learning":
|
|
196
|
+
return await self._learn_and_cache_patterns(plan)
|
|
197
|
+
|
|
198
|
+
return await self._execute_standard_workflow(options)
|
|
199
|
+
|
|
200
|
+
async def _setup_with_architecture(
|
|
201
|
+
self, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
202
|
+
) -> bool:
|
|
203
|
+
self.logger.info("Setting up project with architectural planning")
|
|
204
|
+
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
async def _run_fast_hooks_with_planning(
|
|
208
|
+
self, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
209
|
+
) -> bool:
|
|
210
|
+
self.logger.info("Running fast hooks with architectural planning")
|
|
211
|
+
|
|
212
|
+
return True
|
|
213
|
+
|
|
214
|
+
async def _perform_architectural_refactoring(
|
|
215
|
+
self, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
216
|
+
) -> bool:
|
|
217
|
+
self.logger.info("Performing architectural refactoring")
|
|
218
|
+
|
|
219
|
+
if self._architect_agent_coordinator:
|
|
220
|
+
architect = self._architect_agent_coordinator._get_architect_agent()
|
|
221
|
+
if architect:
|
|
222
|
+
patterns = plan.get("patterns", [])
|
|
223
|
+
self.logger.info(f"Applying architectural patterns: {patterns}")
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
async def _comprehensive_validation(
|
|
229
|
+
self, options: OptionsProtocol, plan: dict[str, t.Any]
|
|
230
|
+
) -> bool:
|
|
231
|
+
self.logger.info("Performing comprehensive validation")
|
|
232
|
+
validation_steps = plan.get("validation", [])
|
|
233
|
+
|
|
234
|
+
for step in validation_steps:
|
|
235
|
+
self.logger.info(f"Validating: {step}")
|
|
236
|
+
|
|
237
|
+
return True
|
|
238
|
+
|
|
239
|
+
async def _learn_and_cache_patterns(self, plan: dict[str, t.Any]) -> bool:
|
|
240
|
+
self.logger.info("Learning and caching successful patterns")
|
|
241
|
+
|
|
242
|
+
if self._architect_agent_coordinator:
|
|
243
|
+
architect = self._architect_agent_coordinator._get_architect_agent()
|
|
244
|
+
if architect and hasattr(architect, "get_cached_patterns"):
|
|
245
|
+
cached_patterns = architect.get_cached_patterns()
|
|
246
|
+
self.logger.info(f"Cached {len(cached_patterns)} patterns")
|
|
247
|
+
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
async def _execute_standard_workflow(self, options: OptionsProtocol) -> bool:
|
|
251
|
+
self.logger.info("Executing standard workflow (fallback)")
|
|
252
|
+
|
|
253
|
+
return True
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class ArchitecturalAssessment:
|
|
257
|
+
def __init__(
|
|
258
|
+
self,
|
|
259
|
+
needs_planning: bool,
|
|
260
|
+
complexity_score: int,
|
|
261
|
+
potential_issues: list[Issue],
|
|
262
|
+
recommended_strategy: str,
|
|
263
|
+
) -> None:
|
|
264
|
+
self.needs_planning = needs_planning
|
|
265
|
+
self.complexity_score = complexity_score
|
|
266
|
+
self.potential_issues = potential_issues
|
|
267
|
+
self.recommended_strategy = recommended_strategy
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
import tempfile
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import typing as t
|
|
8
|
+
import weakref
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ResourceProtocol(t.Protocol):
|
|
15
|
+
async def cleanup(self) -> None: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ResourceManager:
|
|
19
|
+
def __init__(self, logger: logging.Logger | None = None) -> None:
|
|
20
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
21
|
+
self._resources: list[ResourceProtocol] = []
|
|
22
|
+
self._cleanup_callbacks: list[t.Callable[[], t.Awaitable[None]]] = []
|
|
23
|
+
self._lock = threading.RLock()
|
|
24
|
+
self._closed = False
|
|
25
|
+
|
|
26
|
+
def register_resource(self, resource: ResourceProtocol) -> None:
|
|
27
|
+
with self._lock:
|
|
28
|
+
if self._closed:
|
|
29
|
+
asyncio.create_task(resource.cleanup())
|
|
30
|
+
return
|
|
31
|
+
self._resources.append(resource)
|
|
32
|
+
|
|
33
|
+
def register_cleanup_callback(
|
|
34
|
+
self, callback: t.Callable[[], t.Awaitable[None]]
|
|
35
|
+
) -> None:
|
|
36
|
+
with self._lock:
|
|
37
|
+
if self._closed:
|
|
38
|
+
coro = callback()
|
|
39
|
+
if asyncio.iscoroutine(coro):
|
|
40
|
+
asyncio.ensure_future(coro)
|
|
41
|
+
return
|
|
42
|
+
self._cleanup_callbacks.append(callback)
|
|
43
|
+
|
|
44
|
+
async def cleanup_all(self) -> None:
|
|
45
|
+
with self._lock:
|
|
46
|
+
if self._closed:
|
|
47
|
+
return
|
|
48
|
+
self._closed = True
|
|
49
|
+
|
|
50
|
+
resources = self._resources.copy()
|
|
51
|
+
callbacks = self._cleanup_callbacks.copy()
|
|
52
|
+
|
|
53
|
+
for resource in resources:
|
|
54
|
+
try:
|
|
55
|
+
await resource.cleanup()
|
|
56
|
+
except Exception as e:
|
|
57
|
+
self.logger.warning(f"Error cleaning up resource {resource}: {e}")
|
|
58
|
+
|
|
59
|
+
for callback in callbacks:
|
|
60
|
+
try:
|
|
61
|
+
await callback()
|
|
62
|
+
except Exception as e:
|
|
63
|
+
self.logger.warning(f"Error in cleanup callback: {e}")
|
|
64
|
+
|
|
65
|
+
with self._lock:
|
|
66
|
+
self._resources.clear()
|
|
67
|
+
self._cleanup_callbacks.clear()
|
|
68
|
+
|
|
69
|
+
async def __aenter__(self) -> "ResourceManager":
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
async def __aexit__(
|
|
73
|
+
self,
|
|
74
|
+
exc_type: type[BaseException] | None,
|
|
75
|
+
exc_val: BaseException | None,
|
|
76
|
+
exc_tb: TracebackType | None,
|
|
77
|
+
) -> None:
|
|
78
|
+
await self.cleanup_all()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ManagedResource(ABC):
|
|
82
|
+
def __init__(self, manager: ResourceManager | None = None) -> None:
|
|
83
|
+
self.manager = manager
|
|
84
|
+
self._closed = False
|
|
85
|
+
|
|
86
|
+
if manager:
|
|
87
|
+
manager.register_resource(self)
|
|
88
|
+
|
|
89
|
+
@abstractmethod
|
|
90
|
+
async def cleanup(self) -> None:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
async def close(self) -> None:
|
|
94
|
+
if not self._closed:
|
|
95
|
+
self._closed = True
|
|
96
|
+
await self.cleanup()
|
|
97
|
+
|
|
98
|
+
def __del__(self) -> None:
|
|
99
|
+
if not self._closed:
|
|
100
|
+
with contextlib.suppress(RuntimeError):
|
|
101
|
+
loop = asyncio.get_event_loop()
|
|
102
|
+
if loop.is_running():
|
|
103
|
+
asyncio.create_task(self.cleanup())
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ManagedTemporaryFile(ManagedResource):
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
suffix: str = "",
|
|
110
|
+
prefix: str = "crackerjack-",
|
|
111
|
+
manager: ResourceManager | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
super().__init__(manager)
|
|
114
|
+
self.temp_file = tempfile.NamedTemporaryFile(
|
|
115
|
+
suffix=suffix,
|
|
116
|
+
prefix=prefix,
|
|
117
|
+
delete=False,
|
|
118
|
+
)
|
|
119
|
+
self.path = Path(self.temp_file.name)
|
|
120
|
+
|
|
121
|
+
async def cleanup(self) -> None:
|
|
122
|
+
if not self._closed:
|
|
123
|
+
self._closed = True
|
|
124
|
+
|
|
125
|
+
if not self.temp_file.closed:
|
|
126
|
+
self.temp_file.close()
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
if self.path.exists():
|
|
130
|
+
self.path.unlink()
|
|
131
|
+
except OSError as e:
|
|
132
|
+
logging.getLogger(__name__).warning(
|
|
133
|
+
f"Failed to remove temporary file {self.path}: {e}"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def write_text(self, content: str, encoding: str = "utf-8") -> None:
|
|
137
|
+
if self._closed:
|
|
138
|
+
raise RuntimeError("Cannot write to closed temporary file")
|
|
139
|
+
self.path.write_text(content, encoding=encoding)
|
|
140
|
+
|
|
141
|
+
def read_text(self, encoding: str = "utf-8") -> str:
|
|
142
|
+
return self.path.read_text(encoding=encoding)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class ManagedTemporaryDirectory(ManagedResource):
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
suffix: str = "",
|
|
149
|
+
prefix: str = "crackerjack-",
|
|
150
|
+
manager: ResourceManager | None = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
super().__init__(manager)
|
|
153
|
+
self.temp_dir = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
|
|
154
|
+
self.path = Path(self.temp_dir)
|
|
155
|
+
|
|
156
|
+
async def cleanup(self) -> None:
|
|
157
|
+
if not self._closed:
|
|
158
|
+
self._closed = True
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
import shutil
|
|
162
|
+
|
|
163
|
+
if self.path.exists():
|
|
164
|
+
shutil.rmtree(self.path)
|
|
165
|
+
except OSError as e:
|
|
166
|
+
logging.getLogger(__name__).warning(
|
|
167
|
+
f"Failed to remove temporary directory {self.path}: {e}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ManagedProcess(ManagedResource):
|
|
172
|
+
def __init__(
|
|
173
|
+
self,
|
|
174
|
+
process: asyncio.subprocess.Process,
|
|
175
|
+
timeout: float = 30.0,
|
|
176
|
+
manager: ResourceManager | None = None,
|
|
177
|
+
) -> None:
|
|
178
|
+
super().__init__(manager)
|
|
179
|
+
self.process = process
|
|
180
|
+
self.timeout = timeout
|
|
181
|
+
|
|
182
|
+
async def cleanup(self) -> None:
|
|
183
|
+
if not self._closed and self.process.returncode is None:
|
|
184
|
+
self._closed = True
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
self.process.terminate()
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
await asyncio.wait_for(self.process.wait(), timeout=5.0)
|
|
191
|
+
except TimeoutError:
|
|
192
|
+
self.process.kill()
|
|
193
|
+
try:
|
|
194
|
+
await asyncio.wait_for(self.process.wait(), timeout=2.0)
|
|
195
|
+
except TimeoutError:
|
|
196
|
+
logging.getLogger(__name__).warning(
|
|
197
|
+
f"Process {self.process.pid} did not terminate after force kill"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
except ProcessLookupError:
|
|
201
|
+
pass
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logging.getLogger(__name__).warning(
|
|
204
|
+
f"Error cleaning up process {self.process.pid}: {e}"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class ManagedTask(ManagedResource):
|
|
209
|
+
def __init__(
|
|
210
|
+
self,
|
|
211
|
+
task: asyncio.Task[t.Any],
|
|
212
|
+
timeout: float = 30.0,
|
|
213
|
+
manager: ResourceManager | None = None,
|
|
214
|
+
) -> None:
|
|
215
|
+
super().__init__(manager)
|
|
216
|
+
self.task = task
|
|
217
|
+
self.timeout = timeout
|
|
218
|
+
|
|
219
|
+
async def cleanup(self) -> None:
|
|
220
|
+
if not self._closed and not self.task.done():
|
|
221
|
+
self._closed = True
|
|
222
|
+
|
|
223
|
+
self.task.cancel()
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
await asyncio.wait_for(self.task, timeout=self.timeout)
|
|
227
|
+
except (TimeoutError, asyncio.CancelledError):
|
|
228
|
+
pass
|
|
229
|
+
except Exception as e:
|
|
230
|
+
logging.getLogger(__name__).warning(f"Error cleaning up task: {e}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class ManagedFileHandle(ManagedResource):
|
|
234
|
+
def __init__(
|
|
235
|
+
self,
|
|
236
|
+
file_handle: t.IO[t.Any],
|
|
237
|
+
manager: ResourceManager | None = None,
|
|
238
|
+
) -> None:
|
|
239
|
+
super().__init__(manager)
|
|
240
|
+
self.file_handle = file_handle
|
|
241
|
+
|
|
242
|
+
async def cleanup(self) -> None:
|
|
243
|
+
if not self._closed and not self.file_handle.closed:
|
|
244
|
+
self._closed = True
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
self.file_handle.close()
|
|
248
|
+
except Exception as e:
|
|
249
|
+
logging.getLogger(__name__).warning(f"Error closing file handle: {e}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class ResourceContext:
|
|
253
|
+
def __init__(self) -> None:
|
|
254
|
+
self.resource_manager = ResourceManager()
|
|
255
|
+
|
|
256
|
+
def managed_temp_file(
|
|
257
|
+
self,
|
|
258
|
+
suffix: str = "",
|
|
259
|
+
prefix: str = "crackerjack-",
|
|
260
|
+
) -> ManagedTemporaryFile:
|
|
261
|
+
return ManagedTemporaryFile(suffix, prefix, self.resource_manager)
|
|
262
|
+
|
|
263
|
+
def managed_temp_dir(
|
|
264
|
+
self,
|
|
265
|
+
suffix: str = "",
|
|
266
|
+
prefix: str = "crackerjack-",
|
|
267
|
+
) -> ManagedTemporaryDirectory:
|
|
268
|
+
return ManagedTemporaryDirectory(suffix, prefix, self.resource_manager)
|
|
269
|
+
|
|
270
|
+
def managed_process(
|
|
271
|
+
self,
|
|
272
|
+
process: asyncio.subprocess.Process,
|
|
273
|
+
timeout: float = 30.0,
|
|
274
|
+
) -> ManagedProcess:
|
|
275
|
+
return ManagedProcess(process, timeout, self.resource_manager)
|
|
276
|
+
|
|
277
|
+
def managed_task(
|
|
278
|
+
self,
|
|
279
|
+
task: asyncio.Task[t.Any],
|
|
280
|
+
timeout: float = 30.0,
|
|
281
|
+
) -> ManagedTask:
|
|
282
|
+
return ManagedTask(task, timeout, self.resource_manager)
|
|
283
|
+
|
|
284
|
+
def managed_file(
|
|
285
|
+
self,
|
|
286
|
+
file_handle: t.IO[t.Any],
|
|
287
|
+
) -> ManagedFileHandle:
|
|
288
|
+
return ManagedFileHandle(file_handle, self.resource_manager)
|
|
289
|
+
|
|
290
|
+
async def __aenter__(self) -> "ResourceContext":
|
|
291
|
+
await self.resource_manager.__aenter__()
|
|
292
|
+
return self
|
|
293
|
+
|
|
294
|
+
async def __aexit__(
|
|
295
|
+
self,
|
|
296
|
+
exc_type: type[BaseException] | None,
|
|
297
|
+
exc_val: BaseException | None,
|
|
298
|
+
exc_tb: TracebackType | None,
|
|
299
|
+
) -> None:
|
|
300
|
+
await self.resource_manager.__aexit__(exc_type, exc_val, exc_tb)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
_global_managers: weakref.WeakSet[ResourceManager] = weakref.WeakSet()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def register_global_resource_manager(manager: ResourceManager) -> None:
|
|
307
|
+
_global_managers.add(manager)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
async def cleanup_all_global_resources() -> None:
|
|
311
|
+
managers = list[t.Any](_global_managers)
|
|
312
|
+
|
|
313
|
+
cleanup_tasks = [asyncio.create_task(manager.cleanup_all()) for manager in managers]
|
|
314
|
+
|
|
315
|
+
if cleanup_tasks:
|
|
316
|
+
await asyncio.gather(*cleanup_tasks, return_exceptions=True)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@contextlib.asynccontextmanager
|
|
320
|
+
async def with_resource_cleanup() -> t.AsyncIterator[ResourceContext]:
|
|
321
|
+
async with ResourceContext() as ctx:
|
|
322
|
+
yield ctx
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@contextlib.asynccontextmanager
|
|
326
|
+
async def with_temp_file(
|
|
327
|
+
suffix: str = "", prefix: str = "crackerjack-"
|
|
328
|
+
) -> t.AsyncIterator[ManagedTemporaryFile]:
|
|
329
|
+
async with ResourceContext() as ctx:
|
|
330
|
+
temp_file = ctx.managed_temp_file(suffix, prefix)
|
|
331
|
+
try:
|
|
332
|
+
yield temp_file
|
|
333
|
+
finally:
|
|
334
|
+
await temp_file.cleanup()
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@contextlib.asynccontextmanager
|
|
338
|
+
async def with_temp_dir(
|
|
339
|
+
suffix: str = "", prefix: str = "crackerjack-"
|
|
340
|
+
) -> t.AsyncIterator[ManagedTemporaryDirectory]:
|
|
341
|
+
async with ResourceContext() as ctx:
|
|
342
|
+
temp_dir = ctx.managed_temp_dir(suffix, prefix)
|
|
343
|
+
try:
|
|
344
|
+
yield temp_dir
|
|
345
|
+
finally:
|
|
346
|
+
await temp_dir.cleanup()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@contextlib.asynccontextmanager
|
|
350
|
+
async def with_managed_process(
|
|
351
|
+
process: asyncio.subprocess.Process,
|
|
352
|
+
timeout: float = 30.0,
|
|
353
|
+
) -> t.AsyncIterator[asyncio.subprocess.Process]:
|
|
354
|
+
async with ResourceContext() as ctx:
|
|
355
|
+
managed_proc = ctx.managed_process(process, timeout)
|
|
356
|
+
try:
|
|
357
|
+
yield managed_proc.process
|
|
358
|
+
finally:
|
|
359
|
+
await managed_proc.cleanup()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class ResourceLeakDetector:
|
|
363
|
+
def __init__(self) -> None:
|
|
364
|
+
self.open_files: set[str] = set()
|
|
365
|
+
self.active_processes: set[int] = set()
|
|
366
|
+
self.active_tasks: set[asyncio.Task[t.Any]] = set()
|
|
367
|
+
self._start_time = time.time()
|
|
368
|
+
|
|
369
|
+
def track_file(self, file_path: str) -> None:
|
|
370
|
+
self.open_files.add(file_path)
|
|
371
|
+
|
|
372
|
+
def untrack_file(self, file_path: str) -> None:
|
|
373
|
+
self.open_files.discard(file_path)
|
|
374
|
+
|
|
375
|
+
def track_process(self, pid: int) -> None:
|
|
376
|
+
self.active_processes.add(pid)
|
|
377
|
+
|
|
378
|
+
def untrack_process(self, pid: int) -> None:
|
|
379
|
+
self.active_processes.discard(pid)
|
|
380
|
+
|
|
381
|
+
def track_task(self, task: asyncio.Task[t.Any]) -> None:
|
|
382
|
+
self.active_tasks.add(task)
|
|
383
|
+
|
|
384
|
+
def untrack_task(self, task: asyncio.Task[t.Any]) -> None:
|
|
385
|
+
self.active_tasks.discard(task)
|
|
386
|
+
|
|
387
|
+
def get_leak_report(self) -> dict[str, t.Any]:
|
|
388
|
+
return {
|
|
389
|
+
"duration_seconds": time.time() - self._start_time,
|
|
390
|
+
"open_files": list[t.Any](self.open_files),
|
|
391
|
+
"active_processes": list[t.Any](self.active_processes),
|
|
392
|
+
"active_tasks": len([t for t in self.active_tasks if not t.done()]),
|
|
393
|
+
"total_tracked_files": len(self.open_files),
|
|
394
|
+
"total_tracked_processes": len(self.active_processes),
|
|
395
|
+
"total_tracked_tasks": len(self.active_tasks),
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
def has_potential_leaks(self) -> bool:
|
|
399
|
+
return bool(
|
|
400
|
+
self.open_files
|
|
401
|
+
or self.active_processes
|
|
402
|
+
or any(not t.done() for t in self.active_tasks)
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
_leak_detector: ResourceLeakDetector | None = None
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def enable_leak_detection() -> ResourceLeakDetector:
|
|
410
|
+
global _leak_detector
|
|
411
|
+
_leak_detector = ResourceLeakDetector()
|
|
412
|
+
return _leak_detector
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def get_leak_detector() -> ResourceLeakDetector | None:
|
|
416
|
+
return _leak_detector
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def disable_leak_detection() -> dict[str, t.Any] | None:
|
|
420
|
+
global _leak_detector
|
|
421
|
+
if _leak_detector:
|
|
422
|
+
report = _leak_detector.get_leak_report()
|
|
423
|
+
_leak_detector = None
|
|
424
|
+
return report
|
|
425
|
+
return None
|