crackerjack 0.37.9__py3-none-any.whl → 0.45.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +30 -1
- crackerjack/__main__.py +342 -1263
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +27 -5
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/{rust_tool_manager.py → lsp/_manager.py} +3 -3
- crackerjack/adapters/{skylos_adapter.py → lsp/skylos.py} +59 -7
- crackerjack/adapters/{zuban_adapter.py → lsp/zuban.py} +3 -6
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +40 -12
- crackerjack/agents/base.py +1 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +49 -53
- crackerjack/agents/dry_agent.py +187 -3
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +6 -8
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/performance_agent.py +121 -1152
- crackerjack/agents/refactoring_agent.py +156 -655
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +19 -1605
- crackerjack/api.py +5 -7
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +1 -1
- crackerjack/cli/cache_handlers.py +23 -18
- crackerjack/cli/cache_handlers_enhanced.py +1 -4
- crackerjack/cli/facade.py +70 -8
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +249 -49
- crackerjack/cli/interactive.py +8 -5
- crackerjack/cli/options.py +203 -110
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +60 -24
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +256 -0
- crackerjack/config/global_lock_config.py +191 -54
- crackerjack/config/hooks.py +188 -16
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/async_workflow_orchestrator.py +79 -53
- crackerjack/core/autofix_coordinator.py +22 -9
- crackerjack/core/container.py +10 -9
- crackerjack/core/enhanced_container.py +9 -9
- crackerjack/core/performance.py +1 -1
- crackerjack/core/performance_monitor.py +5 -3
- crackerjack/core/phase_coordinator.py +1018 -634
- crackerjack/core/proactive_workflow.py +3 -3
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +167 -23
- crackerjack/core/session_coordinator.py +187 -382
- crackerjack/core/timeout_manager.py +161 -44
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +1247 -953
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +1 -1
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/ai_templates.py +1 -1
- crackerjack/documentation/dual_output_generator.py +11 -9
- crackerjack/documentation/reference_generator.py +104 -59
- crackerjack/dynamic_config.py +52 -61
- crackerjack/errors.py +1 -1
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +2 -0
- crackerjack/executors/async_hook_executor.py +539 -77
- crackerjack/executors/cached_hook_executor.py +3 -3
- crackerjack/executors/hook_executor.py +967 -102
- crackerjack/executors/hook_lock_manager.py +31 -22
- crackerjack/executors/individual_hook_executor.py +66 -32
- crackerjack/executors/lsp_aware_hook_executor.py +136 -57
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +23 -7
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +8 -9
- crackerjack/intelligence/README.md +557 -0
- crackerjack/interactive.py +37 -10
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/async_hook_manager.py +41 -57
- crackerjack/managers/hook_manager.py +449 -79
- crackerjack/managers/publish_manager.py +81 -36
- crackerjack/managers/test_command_builder.py +290 -12
- crackerjack/managers/test_executor.py +93 -8
- crackerjack/managers/test_manager.py +1082 -75
- crackerjack/managers/test_progress.py +118 -26
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/cache.py +25 -2
- crackerjack/mcp/client_runner.py +35 -18
- crackerjack/mcp/context.py +9 -9
- crackerjack/mcp/dashboard.py +24 -8
- crackerjack/mcp/enhanced_progress_monitor.py +34 -23
- crackerjack/mcp/file_monitor.py +27 -6
- crackerjack/mcp/progress_components.py +45 -34
- crackerjack/mcp/progress_monitor.py +6 -9
- crackerjack/mcp/rate_limiter.py +11 -7
- crackerjack/mcp/server.py +2 -0
- crackerjack/mcp/server_core.py +187 -55
- crackerjack/mcp/service_watchdog.py +12 -9
- crackerjack/mcp/task_manager.py +2 -2
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +2 -0
- crackerjack/mcp/tools/core_tools.py +75 -52
- crackerjack/mcp/tools/execution_tools.py +87 -31
- crackerjack/mcp/tools/intelligence_tools.py +2 -2
- crackerjack/mcp/tools/proactive_tools.py +1 -1
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +180 -132
- crackerjack/mcp/tools/workflow_executor.py +87 -46
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/app.py +11 -1
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +27 -4
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +16 -2930
- crackerjack/mcp/websocket/server.py +1 -3
- crackerjack/mcp/websocket/websocket_handler.py +107 -6
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +10 -1
- crackerjack/models/config.py +639 -22
- crackerjack/models/config_adapter.py +6 -6
- crackerjack/models/protocols.py +1167 -23
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +19 -3
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/ai_agent_watchdog.py +5 -4
- crackerjack/monitoring/metrics_collector.py +4 -3
- crackerjack/monitoring/regression_prevention.py +4 -3
- crackerjack/monitoring/websocket_server.py +4 -241
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +20 -67
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +13 -6
- crackerjack/orchestration/execution_strategies.py +6 -6
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +1 -1
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/hooks.py +3 -2
- crackerjack/plugins/loader.py +3 -3
- crackerjack/plugins/managers.py +1 -1
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +8 -21
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/{contextual_ai_assistant.py → ai/contextual_ai_assistant.py} +5 -3
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/api_extractor.py +5 -3
- crackerjack/services/bounded_status_operations.py +45 -5
- crackerjack/services/cache.py +249 -318
- crackerjack/services/changelog_automation.py +7 -3
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +83 -39
- crackerjack/services/config_merge.py +9 -6
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +13 -26
- crackerjack/services/coverage_badge_service.py +6 -4
- crackerjack/services/coverage_ratchet.py +53 -27
- crackerjack/services/debug.py +18 -7
- crackerjack/services/dependency_analyzer.py +4 -4
- crackerjack/services/dependency_monitor.py +13 -13
- crackerjack/services/documentation_generator.py +4 -2
- crackerjack/services/documentation_service.py +62 -33
- crackerjack/services/enhanced_filesystem.py +81 -27
- crackerjack/services/enterprise_optimizer.py +1 -1
- crackerjack/services/error_pattern_analyzer.py +10 -10
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +5 -7
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +80 -109
- crackerjack/services/git.py +99 -5
- crackerjack/services/health_metrics.py +4 -6
- crackerjack/services/heatmap_generator.py +12 -3
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +101 -49
- crackerjack/services/log_manager.py +2 -2
- crackerjack/services/logging.py +120 -68
- crackerjack/services/lsp_client.py +12 -12
- crackerjack/services/memory_optimizer.py +27 -22
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/{performance_benchmarks.py → monitoring/performance_benchmarks.py} +100 -14
- crackerjack/services/{performance_cache.py → monitoring/performance_cache.py} +21 -15
- crackerjack/services/{performance_monitor.py → monitoring/performance_monitor.py} +10 -6
- crackerjack/services/parallel_executor.py +166 -55
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +21 -8
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/{quality_baseline.py → quality/quality_baseline.py} +163 -2
- crackerjack/services/{quality_baseline_enhanced.py → quality/quality_baseline_enhanced.py} +4 -1
- crackerjack/services/{quality_intelligence.py → quality/quality_intelligence.py} +180 -16
- crackerjack/services/regex_patterns.py +58 -2987
- crackerjack/services/regex_utils.py +55 -29
- crackerjack/services/secure_status_formatter.py +42 -15
- crackerjack/services/secure_subprocess.py +35 -2
- crackerjack/services/security.py +16 -8
- crackerjack/services/server_manager.py +40 -51
- crackerjack/services/smart_scheduling.py +46 -6
- crackerjack/services/status_authentication.py +3 -3
- crackerjack/services/thread_safe_status_collector.py +1 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +9 -5
- crackerjack/services/unified_config.py +43 -351
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +6 -4
- crackerjack/services/version_checker.py +14 -8
- crackerjack/services/zuban_lsp_service.py +5 -4
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/init.md +2 -12
- crackerjack/slash_commands/run.md +84 -50
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_regex_patterns.py +7 -3
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/METADATA +678 -98
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack/managers/test_manager_backup.py +0 -1075
- crackerjack/mcp/tools/execution_tools_backup.py +0 -1011
- crackerjack/mixins/__init__.py +0 -3
- crackerjack/mixins/error_handling.py +0 -145
- crackerjack/services/config.py +0 -358
- crackerjack/ui/server_panels.py +0 -125
- crackerjack-0.37.9.dist-info/RECORD +0 -231
- /crackerjack/adapters/{rust_tool_adapter.py → lsp/_base.py} +0 -0
- /crackerjack/adapters/{lsp_client.py → lsp/_client.py} +0 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""In-memory LRU cache adapter for testing and development.
|
|
2
|
+
|
|
3
|
+
Provides a simple in-memory cache with LRU eviction for testing orchestration
|
|
4
|
+
without persisting to disk. Useful for unit tests and development workflows.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import typing as t
|
|
13
|
+
from collections import OrderedDict
|
|
14
|
+
from contextlib import suppress
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
from crackerjack.models.task import HookResult
|
|
21
|
+
|
|
22
|
+
if t.TYPE_CHECKING:
|
|
23
|
+
from crackerjack.config.hooks import HookDefinition
|
|
24
|
+
|
|
25
|
+
# ACB Module Registration (REQUIRED)
|
|
26
|
+
MODULE_ID = UUID("01937d86-ace0-7000-8000-000000000005") # Static UUID7
|
|
27
|
+
MODULE_STATUS = "stable"
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MemoryCacheSettings(BaseModel):
|
|
33
|
+
"""Settings for in-memory cache adapter."""
|
|
34
|
+
|
|
35
|
+
max_entries: int = Field(default=100, ge=10, le=1000)
|
|
36
|
+
default_ttl: int = Field(default=3600, ge=60, le=86400)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MemoryCacheAdapter:
|
|
40
|
+
"""In-memory LRU cache adapter for testing.
|
|
41
|
+
|
|
42
|
+
Features:
|
|
43
|
+
- LRU eviction when max_entries reached
|
|
44
|
+
- TTL-based expiration
|
|
45
|
+
- No disk persistence (ephemeral)
|
|
46
|
+
- Thread-safe operations
|
|
47
|
+
|
|
48
|
+
Use Cases:
|
|
49
|
+
- Unit testing orchestration without disk I/O
|
|
50
|
+
- Development workflows requiring fast cache
|
|
51
|
+
- CI/CD pipelines with ephemeral environments
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
```python
|
|
55
|
+
cache = MemoryCacheAdapter(settings=MemoryCacheSettings(max_entries=50))
|
|
56
|
+
await cache.init()
|
|
57
|
+
|
|
58
|
+
# Cache operations work identically to ToolProxyCacheAdapter
|
|
59
|
+
result = await cache.get(key)
|
|
60
|
+
if not result:
|
|
61
|
+
result = await execute_hook(hook)
|
|
62
|
+
await cache.set(key, result)
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
settings: MemoryCacheSettings | None = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Initialize in-memory cache adapter.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
settings: Optional cache settings
|
|
74
|
+
"""
|
|
75
|
+
self.settings = settings or MemoryCacheSettings()
|
|
76
|
+
self._cache: OrderedDict[str, tuple[HookResult, float]] = OrderedDict()
|
|
77
|
+
self._initialized = False
|
|
78
|
+
|
|
79
|
+
logger.debug(
|
|
80
|
+
"MemoryCacheAdapter initializing",
|
|
81
|
+
extra={
|
|
82
|
+
"max_entries": self.settings.max_entries,
|
|
83
|
+
"default_ttl": self.settings.default_ttl,
|
|
84
|
+
},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def init(self) -> None:
|
|
88
|
+
"""Initialize cache adapter (no-op for memory cache)."""
|
|
89
|
+
if self._initialized:
|
|
90
|
+
logger.debug("Memory cache already initialized")
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
self._initialized = True
|
|
94
|
+
|
|
95
|
+
logger.info(
|
|
96
|
+
"MemoryCacheAdapter initialized",
|
|
97
|
+
extra={
|
|
98
|
+
"max_entries": self.settings.max_entries,
|
|
99
|
+
"default_ttl": self.settings.default_ttl,
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def get(self, key: str) -> HookResult | None:
|
|
104
|
+
"""Retrieve cached hook result.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
key: Cache key
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Cached HookResult if found and not expired, None otherwise
|
|
111
|
+
"""
|
|
112
|
+
if not self._initialized:
|
|
113
|
+
logger.warning("Memory cache not initialized, returning None")
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
import time
|
|
118
|
+
|
|
119
|
+
if key in self._cache:
|
|
120
|
+
result, expiry = self._cache[key]
|
|
121
|
+
|
|
122
|
+
# Check expiration
|
|
123
|
+
if time.time() < expiry:
|
|
124
|
+
# Move to end (LRU update)
|
|
125
|
+
self._cache.move_to_end(key)
|
|
126
|
+
|
|
127
|
+
logger.debug(
|
|
128
|
+
"Cache hit",
|
|
129
|
+
extra={
|
|
130
|
+
"key": key,
|
|
131
|
+
"hook_name": result.name,
|
|
132
|
+
"status": result.status,
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
return result
|
|
136
|
+
else:
|
|
137
|
+
# Expired - remove from cache
|
|
138
|
+
del self._cache[key]
|
|
139
|
+
logger.debug("Cache entry expired", extra={"key": key})
|
|
140
|
+
|
|
141
|
+
logger.debug("Cache miss", extra={"key": key})
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(
|
|
146
|
+
"Failed to retrieve from cache",
|
|
147
|
+
extra={
|
|
148
|
+
"key": key,
|
|
149
|
+
"error": str(e),
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
async def set(
|
|
155
|
+
self,
|
|
156
|
+
key: str,
|
|
157
|
+
result: HookResult,
|
|
158
|
+
ttl: int | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Cache hook result with TTL and LRU eviction.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
key: Cache key
|
|
164
|
+
result: HookResult to cache
|
|
165
|
+
ttl: Optional time-to-live in seconds
|
|
166
|
+
"""
|
|
167
|
+
if not self._initialized:
|
|
168
|
+
logger.warning("Memory cache not initialized, skipping cache write")
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
import time
|
|
173
|
+
|
|
174
|
+
ttl_sec = ttl or self.settings.default_ttl
|
|
175
|
+
expiry = time.time() + ttl_sec
|
|
176
|
+
|
|
177
|
+
# LRU eviction if at capacity
|
|
178
|
+
if len(self._cache) >= self.settings.max_entries and key not in self._cache:
|
|
179
|
+
# Remove oldest entry (first item in OrderedDict)
|
|
180
|
+
evicted_key, _ = self._cache.popitem(last=False)
|
|
181
|
+
logger.debug(
|
|
182
|
+
"LRU eviction",
|
|
183
|
+
extra={
|
|
184
|
+
"evicted_key": evicted_key,
|
|
185
|
+
"cache_size": len(self._cache),
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
self._cache[key] = (result, expiry)
|
|
190
|
+
# Move to end (most recently used)
|
|
191
|
+
self._cache.move_to_end(key)
|
|
192
|
+
|
|
193
|
+
logger.debug(
|
|
194
|
+
"Cache write",
|
|
195
|
+
extra={
|
|
196
|
+
"key": key,
|
|
197
|
+
"hook_name": result.name,
|
|
198
|
+
"status": result.status,
|
|
199
|
+
"ttl": ttl_sec,
|
|
200
|
+
"cache_size": len(self._cache),
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
logger.error(
|
|
206
|
+
"Failed to write to cache",
|
|
207
|
+
extra={
|
|
208
|
+
"key": key,
|
|
209
|
+
"error": str(e),
|
|
210
|
+
},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def compute_key(
|
|
214
|
+
self,
|
|
215
|
+
hook: HookDefinition,
|
|
216
|
+
files: list[Path],
|
|
217
|
+
) -> str:
|
|
218
|
+
"""Compute content-based cache key.
|
|
219
|
+
|
|
220
|
+
Uses same algorithm as ToolProxyCacheAdapter for consistency.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
hook: Hook definition
|
|
224
|
+
files: List of files to be checked by hook
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Cache key string
|
|
228
|
+
"""
|
|
229
|
+
try:
|
|
230
|
+
# Hash hook configuration (Phase 8+ direct invocation API)
|
|
231
|
+
config_data = {
|
|
232
|
+
"name": hook.name,
|
|
233
|
+
"command": hook.command, # Direct tool invocation command
|
|
234
|
+
"timeout": hook.timeout,
|
|
235
|
+
"stage": hook.stage.value
|
|
236
|
+
if hasattr(hook.stage, "value")
|
|
237
|
+
else str(hook.stage),
|
|
238
|
+
"security_level": hook.security_level.value
|
|
239
|
+
if hasattr(hook.security_level, "value")
|
|
240
|
+
else str(hook.security_level),
|
|
241
|
+
}
|
|
242
|
+
config_json = json.dumps(config_data, sort_keys=True)
|
|
243
|
+
config_hash = hashlib.sha256(config_json.encode()).hexdigest()[:16]
|
|
244
|
+
|
|
245
|
+
# Hash file contents
|
|
246
|
+
content_hasher = hashlib.sha256()
|
|
247
|
+
for file_path in sorted(files):
|
|
248
|
+
try:
|
|
249
|
+
if file_path.exists() and file_path.is_file():
|
|
250
|
+
content_hasher.update(file_path.read_bytes())
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.warning(
|
|
253
|
+
f"Failed to hash file {file_path}: {e}",
|
|
254
|
+
extra={"file": str(file_path), "error": str(e)},
|
|
255
|
+
)
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
content_hash = content_hasher.hexdigest()[:16]
|
|
259
|
+
|
|
260
|
+
cache_key = f"{hook.name}:{config_hash}:{content_hash}"
|
|
261
|
+
|
|
262
|
+
logger.debug(
|
|
263
|
+
"Cache key computed",
|
|
264
|
+
extra={
|
|
265
|
+
"hook_name": hook.name,
|
|
266
|
+
"file_count": len(files),
|
|
267
|
+
"config_hash": config_hash,
|
|
268
|
+
"content_hash": content_hash,
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
return cache_key
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(
|
|
276
|
+
"Failed to compute cache key",
|
|
277
|
+
extra={
|
|
278
|
+
"hook_name": hook.name,
|
|
279
|
+
"error": str(e),
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
return f"{hook.name}:error"
|
|
283
|
+
|
|
284
|
+
async def clear(self) -> None:
|
|
285
|
+
"""Clear all cached results."""
|
|
286
|
+
try:
|
|
287
|
+
self._cache.clear()
|
|
288
|
+
logger.info("Memory cache cleared")
|
|
289
|
+
except Exception as e:
|
|
290
|
+
logger.error("Failed to clear cache", extra={"error": str(e)})
|
|
291
|
+
|
|
292
|
+
async def get_stats(self) -> dict[str, t.Any]:
|
|
293
|
+
"""Get cache statistics.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dictionary with cache statistics
|
|
297
|
+
"""
|
|
298
|
+
import time
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
total_entries = len(self._cache)
|
|
302
|
+
expired_entries = sum(
|
|
303
|
+
1 for _, expiry in self._cache.values() if time.time() >= expiry
|
|
304
|
+
)
|
|
305
|
+
active_entries = total_entries - expired_entries
|
|
306
|
+
|
|
307
|
+
stats = {
|
|
308
|
+
"total_entries": total_entries,
|
|
309
|
+
"active_entries": active_entries,
|
|
310
|
+
"expired_entries": expired_entries,
|
|
311
|
+
"max_entries": self.settings.max_entries,
|
|
312
|
+
"default_ttl": self.settings.default_ttl,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
logger.debug("Memory cache statistics", extra=stats)
|
|
316
|
+
|
|
317
|
+
return stats
|
|
318
|
+
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.error("Failed to get cache statistics", extra={"error": str(e)})
|
|
321
|
+
return {}
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def module_id(self) -> UUID:
|
|
325
|
+
"""Reference to module-level MODULE_ID."""
|
|
326
|
+
return MODULE_ID
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
def adapter_name(self) -> str:
|
|
330
|
+
"""Human-readable adapter name."""
|
|
331
|
+
return "MemoryCacheAdapter"
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ACB Registration (REQUIRED at module level)
|
|
335
|
+
with suppress(Exception):
|
|
336
|
+
from acb.depends import depends
|
|
337
|
+
|
|
338
|
+
depends.set(MemoryCacheAdapter)
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Tool proxy cache adapter for hook result caching.
|
|
2
|
+
|
|
3
|
+
Bridges to existing tool_proxy cache infrastructure for consistent caching
|
|
4
|
+
across the crackerjack ecosystem. Implements content-based cache keys using
|
|
5
|
+
hash of hook configuration and file contents.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import typing as t
|
|
14
|
+
from contextlib import suppress
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field
|
|
19
|
+
|
|
20
|
+
from crackerjack.models.task import HookResult
|
|
21
|
+
|
|
22
|
+
if t.TYPE_CHECKING:
|
|
23
|
+
from crackerjack.config.hooks import HookDefinition
|
|
24
|
+
|
|
25
|
+
# ACB Module Registration (REQUIRED)
|
|
26
|
+
MODULE_ID = UUID("01937d86-ace0-7000-8000-000000000004") # Static UUID7
|
|
27
|
+
MODULE_STATUS = "stable"
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ToolProxyCacheSettings(BaseModel):
|
|
33
|
+
"""Settings for tool proxy cache adapter."""
|
|
34
|
+
|
|
35
|
+
default_ttl: int = Field(default=3600, ge=60, le=86400) # 1 minute to 24 hours
|
|
36
|
+
max_cache_size_mb: int = Field(default=100, ge=10, le=1000)
|
|
37
|
+
enable_compression: bool = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ToolProxyCacheAdapter:
|
|
41
|
+
"""Cache adapter bridging to tool_proxy infrastructure.
|
|
42
|
+
|
|
43
|
+
Features:
|
|
44
|
+
- Content-based cache keys (config + file hashes)
|
|
45
|
+
- Configurable TTL per hook result
|
|
46
|
+
- Integration with existing tool_proxy cache
|
|
47
|
+
- Automatic cache invalidation on content changes
|
|
48
|
+
|
|
49
|
+
Cache Key Format:
|
|
50
|
+
{hook_name}:{config_hash}:{content_hash}
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
```python
|
|
54
|
+
cache = ToolProxyCacheAdapter()
|
|
55
|
+
await cache.init()
|
|
56
|
+
|
|
57
|
+
# Check cache
|
|
58
|
+
cached_result = await cache.get(cache_key)
|
|
59
|
+
if cached_result:
|
|
60
|
+
return cached_result
|
|
61
|
+
|
|
62
|
+
# Execute hook and cache result
|
|
63
|
+
result = await execute_hook(hook)
|
|
64
|
+
await cache.set(cache_key, result, ttl=3600)
|
|
65
|
+
```
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
settings: ToolProxyCacheSettings | None = None,
|
|
71
|
+
cache_dir: Path | None = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Initialize tool proxy cache adapter.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
settings: Optional cache settings
|
|
77
|
+
cache_dir: Optional cache directory (defaults to .crackerjack/cache)
|
|
78
|
+
"""
|
|
79
|
+
self.settings = settings or ToolProxyCacheSettings()
|
|
80
|
+
self._cache_dir = cache_dir or Path.cwd() / ".crackerjack" / "cache"
|
|
81
|
+
self._cache: dict[
|
|
82
|
+
str, tuple[HookResult, float]
|
|
83
|
+
] = {} # key -> (result, expiry_timestamp)
|
|
84
|
+
self._initialized = False
|
|
85
|
+
|
|
86
|
+
logger.debug(
|
|
87
|
+
"ToolProxyCacheAdapter initializing",
|
|
88
|
+
extra={
|
|
89
|
+
"cache_dir": str(self._cache_dir),
|
|
90
|
+
"default_ttl": self.settings.default_ttl,
|
|
91
|
+
"enable_compression": self.settings.enable_compression,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
async def init(self) -> None:
|
|
96
|
+
"""Initialize cache adapter and ensure cache directory exists."""
|
|
97
|
+
if self._initialized:
|
|
98
|
+
logger.debug("Cache adapter already initialized")
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
self._initialized = True
|
|
104
|
+
|
|
105
|
+
logger.info(
|
|
106
|
+
"ToolProxyCacheAdapter initialized",
|
|
107
|
+
extra={
|
|
108
|
+
"cache_dir": str(self._cache_dir),
|
|
109
|
+
"default_ttl": self.settings.default_ttl,
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(
|
|
114
|
+
"Failed to initialize cache adapter",
|
|
115
|
+
extra={
|
|
116
|
+
"error": str(e),
|
|
117
|
+
"cache_dir": str(self._cache_dir),
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
raise
|
|
121
|
+
|
|
122
|
+
async def get(self, key: str) -> HookResult | None:
|
|
123
|
+
"""Retrieve cached hook result.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
key: Cache key (computed via compute_key())
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Cached HookResult if found and not expired, None otherwise
|
|
130
|
+
"""
|
|
131
|
+
if not self._initialized:
|
|
132
|
+
logger.warning("Cache adapter not initialized, returning None")
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
import time
|
|
137
|
+
|
|
138
|
+
if key in self._cache:
|
|
139
|
+
result, expiry = self._cache[key]
|
|
140
|
+
|
|
141
|
+
# Check expiration
|
|
142
|
+
if time.time() < expiry:
|
|
143
|
+
logger.debug(
|
|
144
|
+
"Cache hit",
|
|
145
|
+
extra={
|
|
146
|
+
"key": key,
|
|
147
|
+
"hook_name": result.name,
|
|
148
|
+
"status": result.status,
|
|
149
|
+
},
|
|
150
|
+
)
|
|
151
|
+
return result
|
|
152
|
+
else:
|
|
153
|
+
# Expired - remove from cache
|
|
154
|
+
del self._cache[key]
|
|
155
|
+
logger.debug("Cache entry expired", extra={"key": key})
|
|
156
|
+
|
|
157
|
+
logger.debug("Cache miss", extra={"key": key})
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(
|
|
162
|
+
"Failed to retrieve from cache",
|
|
163
|
+
extra={
|
|
164
|
+
"key": key,
|
|
165
|
+
"error": str(e),
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
async def set(
|
|
171
|
+
self,
|
|
172
|
+
key: str,
|
|
173
|
+
result: HookResult,
|
|
174
|
+
ttl: int | None = None,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Cache hook result with TTL.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
key: Cache key (computed via compute_key())
|
|
180
|
+
result: HookResult to cache
|
|
181
|
+
ttl: Optional time-to-live in seconds (defaults to settings.default_ttl)
|
|
182
|
+
"""
|
|
183
|
+
if not self._initialized:
|
|
184
|
+
logger.warning("Cache adapter not initialized, skipping cache write")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
import time
|
|
189
|
+
|
|
190
|
+
ttl_sec = ttl or self.settings.default_ttl
|
|
191
|
+
expiry = time.time() + ttl_sec
|
|
192
|
+
|
|
193
|
+
self._cache[key] = (result, expiry)
|
|
194
|
+
|
|
195
|
+
logger.debug(
|
|
196
|
+
"Cache write",
|
|
197
|
+
extra={
|
|
198
|
+
"key": key,
|
|
199
|
+
"hook_name": result.name,
|
|
200
|
+
"status": result.status,
|
|
201
|
+
"ttl": ttl_sec,
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.error(
|
|
207
|
+
"Failed to write to cache",
|
|
208
|
+
extra={
|
|
209
|
+
"key": key,
|
|
210
|
+
"error": str(e),
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def compute_key(
|
|
215
|
+
self,
|
|
216
|
+
hook: HookDefinition,
|
|
217
|
+
files: list[Path],
|
|
218
|
+
) -> str:
|
|
219
|
+
"""Compute content-based cache key.
|
|
220
|
+
|
|
221
|
+
Cache key format: {hook_name}:{config_hash}:{content_hash}
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
hook: Hook definition
|
|
225
|
+
files: List of files to be checked by hook
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Cache key string
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
# Hash hook configuration (Phase 8+ direct invocation API)
|
|
232
|
+
config_data = {
|
|
233
|
+
"name": hook.name,
|
|
234
|
+
"command": hook.command, # Direct tool invocation command
|
|
235
|
+
"timeout": hook.timeout,
|
|
236
|
+
"stage": hook.stage.value
|
|
237
|
+
if hasattr(hook.stage, "value")
|
|
238
|
+
else str(hook.stage),
|
|
239
|
+
"security_level": hook.security_level.value
|
|
240
|
+
if hasattr(hook.security_level, "value")
|
|
241
|
+
else str(hook.security_level),
|
|
242
|
+
}
|
|
243
|
+
config_json = json.dumps(config_data, sort_keys=True)
|
|
244
|
+
config_hash = hashlib.sha256(config_json.encode()).hexdigest()[:16]
|
|
245
|
+
|
|
246
|
+
# Hash file contents (for cache invalidation on content changes)
|
|
247
|
+
content_hasher = hashlib.sha256()
|
|
248
|
+
for file_path in sorted(files):
|
|
249
|
+
try:
|
|
250
|
+
if file_path.exists() and file_path.is_file():
|
|
251
|
+
content_hasher.update(file_path.read_bytes())
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.warning(
|
|
254
|
+
f"Failed to hash file {file_path}: {e}",
|
|
255
|
+
extra={"file": str(file_path), "error": str(e)},
|
|
256
|
+
)
|
|
257
|
+
continue
|
|
258
|
+
|
|
259
|
+
content_hash = content_hasher.hexdigest()[:16]
|
|
260
|
+
|
|
261
|
+
cache_key = f"{hook.name}:{config_hash}:{content_hash}"
|
|
262
|
+
|
|
263
|
+
logger.debug(
|
|
264
|
+
"Cache key computed",
|
|
265
|
+
extra={
|
|
266
|
+
"hook_name": hook.name,
|
|
267
|
+
"file_count": len(files),
|
|
268
|
+
"config_hash": config_hash,
|
|
269
|
+
"content_hash": content_hash,
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return cache_key
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.error(
|
|
277
|
+
"Failed to compute cache key",
|
|
278
|
+
extra={
|
|
279
|
+
"hook_name": hook.name,
|
|
280
|
+
"error": str(e),
|
|
281
|
+
},
|
|
282
|
+
)
|
|
283
|
+
# Fallback to simple key
|
|
284
|
+
return f"{hook.name}:error"
|
|
285
|
+
|
|
286
|
+
async def clear(self) -> None:
|
|
287
|
+
"""Clear all cached results."""
|
|
288
|
+
try:
|
|
289
|
+
self._cache.clear()
|
|
290
|
+
logger.info("Cache cleared")
|
|
291
|
+
except Exception as e:
|
|
292
|
+
logger.error("Failed to clear cache", extra={"error": str(e)})
|
|
293
|
+
|
|
294
|
+
async def get_stats(self) -> dict[str, t.Any]:
|
|
295
|
+
"""Get cache statistics.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dictionary with cache statistics
|
|
299
|
+
"""
|
|
300
|
+
import time
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
total_entries = len(self._cache)
|
|
304
|
+
expired_entries = sum(
|
|
305
|
+
1 for _, expiry in self._cache.values() if time.time() >= expiry
|
|
306
|
+
)
|
|
307
|
+
active_entries = total_entries - expired_entries
|
|
308
|
+
|
|
309
|
+
stats = {
|
|
310
|
+
"total_entries": total_entries,
|
|
311
|
+
"active_entries": active_entries,
|
|
312
|
+
"expired_entries": expired_entries,
|
|
313
|
+
"cache_dir": str(self._cache_dir),
|
|
314
|
+
"default_ttl": self.settings.default_ttl,
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
logger.debug("Cache statistics", extra=stats)
|
|
318
|
+
|
|
319
|
+
return stats
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.error("Failed to get cache statistics", extra={"error": str(e)})
|
|
323
|
+
return {}
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def module_id(self) -> UUID:
|
|
327
|
+
"""Reference to module-level MODULE_ID."""
|
|
328
|
+
return MODULE_ID
|
|
329
|
+
|
|
330
|
+
@property
|
|
331
|
+
def adapter_name(self) -> str:
|
|
332
|
+
"""Human-readable adapter name."""
|
|
333
|
+
return "ToolProxyCacheAdapter"
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
# ACB Registration (REQUIRED at module level)
|
|
337
|
+
with suppress(Exception):
|
|
338
|
+
from acb.depends import depends
|
|
339
|
+
|
|
340
|
+
depends.set(ToolProxyCacheAdapter)
|