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
crackerjack/services/cache.py
CHANGED
|
@@ -1,276 +1,193 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import hashlib
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from dataclasses import asdict, dataclass, field
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Awaitable
|
|
5
|
+
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass
|
|
12
|
-
class CacheEntry:
|
|
13
|
-
key: str
|
|
14
|
-
value: t.Any
|
|
15
|
-
created_at: float = field(default_factory=time.time)
|
|
16
|
-
accessed_at: float = field(default_factory=time.time)
|
|
17
|
-
ttl_seconds: int = 3600
|
|
18
|
-
access_count: int = 0
|
|
9
|
+
from acb.adapters import AdapterNotInstalled, import_adapter
|
|
10
|
+
from acb.depends import depends
|
|
19
11
|
|
|
20
|
-
|
|
21
|
-
def is_expired(self) -> bool:
|
|
22
|
-
return (time.time() - self.created_at) > self.ttl_seconds
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def age_seconds(self) -> int:
|
|
26
|
-
return int(time.time() - self.created_at)
|
|
12
|
+
from crackerjack.models.task import HookResult
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
self.accessed_at = time.time()
|
|
30
|
-
self.access_count += 1
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
31
15
|
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
Cache: Any | None = None
|
|
17
|
+
_cache_import_error: Exception | None = None
|
|
34
18
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
19
|
+
if import_adapter is not None:
|
|
20
|
+
try:
|
|
21
|
+
Cache = import_adapter("cache")
|
|
22
|
+
except AdapterNotInstalled as e: # pragma: no cover - depends on env
|
|
23
|
+
_cache_import_error = e
|
|
24
|
+
Cache = None
|
|
25
|
+
except Exception as e: # pragma: no cover - defensive
|
|
26
|
+
_cache_import_error = e
|
|
27
|
+
Cache = None
|
|
38
28
|
|
|
39
29
|
|
|
40
30
|
@dataclass
|
|
41
31
|
class CacheStats:
|
|
32
|
+
"""Cache statistics compatible with legacy cache implementation."""
|
|
33
|
+
|
|
42
34
|
hits: int = 0
|
|
43
35
|
misses: int = 0
|
|
44
36
|
evictions: int = 0
|
|
45
37
|
total_entries: int = 0
|
|
46
|
-
total_size_bytes: int = 0
|
|
47
38
|
|
|
48
39
|
@property
|
|
49
40
|
def hit_rate(self) -> float:
|
|
50
|
-
|
|
51
|
-
return (self.hits /
|
|
41
|
+
total_requests = self.hits + self.misses
|
|
42
|
+
return (self.hits / total_requests * 100) if total_requests else 0.0
|
|
52
43
|
|
|
53
|
-
def to_dict(self) -> dict[str,
|
|
44
|
+
def to_dict(self) -> dict[str, Any]:
|
|
54
45
|
return {
|
|
55
46
|
"hits": self.hits,
|
|
56
47
|
"misses": self.misses,
|
|
57
48
|
"evictions": self.evictions,
|
|
58
49
|
"total_entries": self.total_entries,
|
|
59
50
|
"hit_rate_percent": round(self.hit_rate, 2),
|
|
60
|
-
"total_size_mb": round(self.total_size_bytes / 1024 / 1024, 2),
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def get(self, key: str) -> t.Any | None:
|
|
72
|
-
entry = self._cache.get(key)
|
|
73
|
-
|
|
74
|
-
if entry is None:
|
|
75
|
-
self.stats.misses += 1
|
|
76
|
-
return None
|
|
77
|
-
|
|
78
|
-
if entry.is_expired:
|
|
79
|
-
del self._cache[key]
|
|
80
|
-
self.stats.misses += 1
|
|
81
|
-
self.stats.evictions += 1
|
|
82
|
-
return None
|
|
83
|
-
|
|
84
|
-
entry.touch()
|
|
85
|
-
self.stats.hits += 1
|
|
86
|
-
return entry.value
|
|
87
|
-
|
|
88
|
-
def set(self, key: str, value: t.Any, ttl_seconds: int | None = None) -> None:
|
|
89
|
-
if ttl_seconds is None:
|
|
90
|
-
ttl_seconds = self.default_ttl
|
|
91
|
-
|
|
92
|
-
if len(self._cache) >= self.max_entries:
|
|
93
|
-
self._evict_lru()
|
|
94
|
-
|
|
95
|
-
self._cache[key] = CacheEntry(
|
|
96
|
-
key=key,
|
|
97
|
-
value=value,
|
|
98
|
-
ttl_seconds=ttl_seconds,
|
|
54
|
+
def get_cache() -> Any:
|
|
55
|
+
"""Return the configured cache backend from ACB."""
|
|
56
|
+
if Cache is None or depends is None:
|
|
57
|
+
reason = (
|
|
58
|
+
f"{type(_cache_import_error).__name__}: {_cache_import_error}"
|
|
59
|
+
if _cache_import_error is not None
|
|
60
|
+
else "cache adapter import failed"
|
|
99
61
|
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def invalidate(self, key: str) -> bool:
|
|
104
|
-
if key in self._cache:
|
|
105
|
-
del self._cache[key]
|
|
106
|
-
self.stats.total_entries = len(self._cache)
|
|
107
|
-
return True
|
|
108
|
-
return False
|
|
109
|
-
|
|
110
|
-
def clear(self) -> None:
|
|
111
|
-
evicted = len(self._cache)
|
|
112
|
-
self._cache.clear()
|
|
113
|
-
self.stats.evictions += evicted
|
|
114
|
-
self.stats.total_entries = 0
|
|
115
|
-
|
|
116
|
-
def cleanup_expired(self) -> int:
|
|
117
|
-
expired_keys = [key for key, entry in self._cache.items() if entry.is_expired]
|
|
118
|
-
|
|
119
|
-
for key in expired_keys:
|
|
120
|
-
del self._cache[key]
|
|
121
|
-
|
|
122
|
-
self.stats.evictions += len(expired_keys)
|
|
123
|
-
self.stats.total_entries = len(self._cache)
|
|
124
|
-
return len(expired_keys)
|
|
125
|
-
|
|
126
|
-
def _evict_lru(self) -> None:
|
|
127
|
-
if not self._cache:
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
lru_key = min(self._cache.keys(), key=lambda k: self._cache[k].accessed_at)
|
|
131
|
-
|
|
132
|
-
del self._cache[lru_key]
|
|
133
|
-
self.stats.evictions += 1
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class FileCache:
|
|
137
|
-
def __init__(self, cache_dir: Path, namespace: str = "crackerjack") -> None:
|
|
138
|
-
self.cache_dir = cache_dir / namespace
|
|
139
|
-
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
140
|
-
self.stats = CacheStats()
|
|
141
|
-
|
|
142
|
-
def get(self, key: str) -> t.Any | None:
|
|
143
|
-
cache_file = self._get_cache_file(key)
|
|
144
|
-
|
|
145
|
-
if not cache_file.exists():
|
|
146
|
-
self.stats.misses += 1
|
|
147
|
-
return None
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
with cache_file.open(encoding="utf-8") as f:
|
|
151
|
-
data = json.load(f)
|
|
152
|
-
entry = CacheEntry.from_dict(data)
|
|
153
|
-
|
|
154
|
-
if entry.is_expired:
|
|
155
|
-
cache_file.unlink(missing_ok=True)
|
|
156
|
-
self.stats.misses += 1
|
|
157
|
-
self.stats.evictions += 1
|
|
158
|
-
return None
|
|
159
|
-
|
|
160
|
-
entry.touch()
|
|
161
|
-
|
|
162
|
-
with cache_file.open("w", encoding="utf-8") as f:
|
|
163
|
-
json.dump(entry.to_dict(), f)
|
|
164
|
-
|
|
165
|
-
self.stats.hits += 1
|
|
166
|
-
return entry.value
|
|
167
|
-
|
|
168
|
-
except (json.JSONDecodeError, FileNotFoundError, OSError, KeyError):
|
|
169
|
-
self.stats.misses += 1
|
|
170
|
-
cache_file.unlink(missing_ok=True)
|
|
171
|
-
return None
|
|
172
|
-
|
|
173
|
-
def set(self, key: str, value: t.Any, ttl_seconds: int = 3600) -> None:
|
|
174
|
-
cache_file = self._get_cache_file(key)
|
|
175
|
-
|
|
176
|
-
entry = CacheEntry(
|
|
177
|
-
key=key,
|
|
178
|
-
value=value,
|
|
179
|
-
ttl_seconds=ttl_seconds,
|
|
62
|
+
msg = (
|
|
63
|
+
"ACB cache adapter is unavailable. "
|
|
64
|
+
f"Resolve adapter configuration before continuing ({reason})."
|
|
180
65
|
)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
for cache_file in self.cache_dir.glob("*.cache"):
|
|
202
|
-
try:
|
|
203
|
-
with cache_file.open(encoding="utf-8") as f:
|
|
204
|
-
data = json.load(f)
|
|
205
|
-
entry = CacheEntry.from_dict(data)
|
|
206
|
-
|
|
207
|
-
if entry.is_expired:
|
|
208
|
-
cache_file.unlink()
|
|
209
|
-
removed += 1
|
|
210
|
-
except (json.JSONDecodeError, FileNotFoundError, OSError, KeyError):
|
|
211
|
-
cache_file.unlink(missing_ok=True)
|
|
212
|
-
removed += 1
|
|
213
|
-
|
|
214
|
-
self.stats.evictions += removed
|
|
215
|
-
return removed
|
|
216
|
-
|
|
217
|
-
def _get_cache_file(self, key: str) -> Path:
|
|
218
|
-
safe_key = hashlib.md5(key.encode(), usedforsecurity=False).hexdigest()
|
|
219
|
-
return self.cache_dir / f"{safe_key}.cache"
|
|
66
|
+
raise RuntimeError(msg)
|
|
67
|
+
try:
|
|
68
|
+
return depends.get_sync(Cache)
|
|
69
|
+
except Exception as exception: # pragma: no cover - runtime safety
|
|
70
|
+
# Check if the error is specifically about the adapter not being found
|
|
71
|
+
error_msg = str(exception).lower()
|
|
72
|
+
if "adapter" in error_msg and "not found" in error_msg and "cache" in error_msg:
|
|
73
|
+
# Instead of raising an error, return None to trigger fallback behavior
|
|
74
|
+
import logging
|
|
75
|
+
|
|
76
|
+
logging.getLogger(__name__).warning(
|
|
77
|
+
"ACB cache adapter not found or not installed, using fallback behavior"
|
|
78
|
+
)
|
|
79
|
+
return None
|
|
80
|
+
else:
|
|
81
|
+
msg = (
|
|
82
|
+
"Failed to resolve ACB cache adapter via dependency injection. "
|
|
83
|
+
"Ensure adapters.yml specifies a valid cache adapter."
|
|
84
|
+
)
|
|
85
|
+
raise RuntimeError(msg) from exception
|
|
220
86
|
|
|
221
87
|
|
|
222
88
|
class CrackerjackCache:
|
|
223
|
-
|
|
89
|
+
"""ACB-backed cache adapter with in-memory fallback when adapter missing."""
|
|
90
|
+
|
|
224
91
|
EXPENSIVE_HOOKS = {
|
|
225
|
-
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
"
|
|
231
|
-
"
|
|
92
|
+
# Type checking and analysis
|
|
93
|
+
"pyright", # Legacy, keep for backward compatibility
|
|
94
|
+
"zuban", # Fast Rust-based type checking
|
|
95
|
+
"skylos", # Rust-based dead code detection
|
|
96
|
+
# Security and vulnerability scanning
|
|
97
|
+
"bandit", # Python security linter
|
|
98
|
+
"gitleaks", # Secret scanning
|
|
99
|
+
"semgrep", # SAST scanning
|
|
100
|
+
"pyscn", # Security scanning
|
|
101
|
+
"pip-audit", # Dependency vulnerability scanning
|
|
102
|
+
# Code quality and complexity
|
|
103
|
+
"vulture", # Dead code detection (Python)
|
|
104
|
+
"complexipy", # Complexity analysis
|
|
105
|
+
"refurb", # Python code modernization
|
|
106
|
+
# Schema and data validation
|
|
107
|
+
"check-jsonschema", # JSON schema validation (8466+ files)
|
|
108
|
+
# Text and formatting
|
|
109
|
+
"codespell", # Spell checking across entire codebase
|
|
110
|
+
"ruff-check", # Comprehensive Python linting
|
|
111
|
+
"mdformat", # Markdown formatting
|
|
232
112
|
}
|
|
233
113
|
|
|
234
|
-
# TTL configuration for different cache types (in seconds)
|
|
235
114
|
HOOK_DISK_TTLS = {
|
|
236
|
-
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
|
|
241
|
-
"
|
|
242
|
-
"
|
|
115
|
+
# Type checking - daily updates as code changes
|
|
116
|
+
"pyright": 86400, # 1 day
|
|
117
|
+
"zuban": 86400, # 1 day
|
|
118
|
+
"skylos": 86400 * 2, # 2 days - dead code analysis less volatile
|
|
119
|
+
# Security - longer TTLs, check periodically for new vulnerabilities
|
|
120
|
+
"bandit": 86400 * 3, # 3 days
|
|
121
|
+
"gitleaks": 86400 * 7, # 7 days - secrets rarely change once clean
|
|
122
|
+
"semgrep": 86400 * 3, # 3 days - security rules change occasionally
|
|
123
|
+
"pyscn": 86400 * 3, # 3 days
|
|
124
|
+
"pip-audit": 86400, # 1 day - check daily for new CVEs
|
|
125
|
+
# Code quality
|
|
126
|
+
"vulture": 86400 * 2, # 2 days
|
|
127
|
+
"complexipy": 86400, # 1 day
|
|
128
|
+
"refurb": 86400, # 1 day
|
|
129
|
+
# Validation and formatting - longest TTLs
|
|
130
|
+
"check-jsonschema": 86400 * 7, # 7 days - schemas rarely change
|
|
131
|
+
"codespell": 86400 * 7, # 7 days - spelling errors are rare
|
|
132
|
+
"ruff-check": 86400, # 1 day - linting rules can change
|
|
133
|
+
"mdformat": 86400 * 7, # 7 days - markdown style rarely changes
|
|
243
134
|
}
|
|
244
135
|
|
|
245
|
-
# Agent version for cache invalidation when agent logic changes
|
|
246
136
|
AGENT_VERSION = "1.0.0"
|
|
247
137
|
|
|
248
138
|
def __init__(
|
|
249
139
|
self,
|
|
250
140
|
cache_dir: Path | None = None,
|
|
251
141
|
enable_disk_cache: bool = True,
|
|
142
|
+
backend: Any | None = None,
|
|
252
143
|
) -> None:
|
|
253
|
-
|
|
254
|
-
self.cache_dir = cache_dir
|
|
255
|
-
else:
|
|
256
|
-
self.cache_dir = Path.cwd() / ".crackerjack" / "cache"
|
|
257
|
-
|
|
144
|
+
self.cache_dir = cache_dir or Path.cwd() / ".crackerjack" / "cache"
|
|
258
145
|
self.enable_disk_cache = enable_disk_cache
|
|
146
|
+
self.stats = CacheStats()
|
|
147
|
+
|
|
148
|
+
if backend is not None:
|
|
149
|
+
self._backend = backend
|
|
150
|
+
else:
|
|
151
|
+
# Try to get ACB cache adapter, fallback to None if unavailable
|
|
152
|
+
try:
|
|
153
|
+
self._backend = get_cache()
|
|
154
|
+
except RuntimeError:
|
|
155
|
+
# ACB cache adapter not available - use in-memory fallback
|
|
156
|
+
logger.info("ACB cache adapter unavailable, using in-memory cache")
|
|
157
|
+
self._backend = None
|
|
259
158
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _run_async(coro: Awaitable[Any]) -> Any:
|
|
161
|
+
async def _await(value: Awaitable[Any]) -> Any:
|
|
162
|
+
return await value
|
|
263
163
|
|
|
264
|
-
|
|
265
|
-
|
|
164
|
+
try:
|
|
165
|
+
return asyncio.run(_await(coro))
|
|
166
|
+
except RuntimeError as exception:
|
|
167
|
+
message = str(exception)
|
|
168
|
+
if "asyncio.run()" not in message:
|
|
169
|
+
raise
|
|
170
|
+
loop = asyncio.new_event_loop()
|
|
171
|
+
try:
|
|
172
|
+
return loop.run_until_complete(_await(coro))
|
|
173
|
+
finally:
|
|
174
|
+
loop.close()
|
|
266
175
|
|
|
267
176
|
def get_hook_result(
|
|
268
177
|
self,
|
|
269
178
|
hook_name: str,
|
|
270
179
|
file_hashes: list[str],
|
|
271
180
|
) -> HookResult | None:
|
|
181
|
+
if self._backend is None:
|
|
182
|
+
self.stats.misses += 1
|
|
183
|
+
return None
|
|
272
184
|
cache_key = self._get_hook_cache_key(hook_name, file_hashes)
|
|
273
|
-
|
|
185
|
+
result = self._run_async(self._backend.get(cache_key))
|
|
186
|
+
if result is None:
|
|
187
|
+
self.stats.misses += 1
|
|
188
|
+
else:
|
|
189
|
+
self.stats.hits += 1
|
|
190
|
+
return result
|
|
274
191
|
|
|
275
192
|
def set_hook_result(
|
|
276
193
|
self,
|
|
@@ -278,8 +195,11 @@ class CrackerjackCache:
|
|
|
278
195
|
file_hashes: list[str],
|
|
279
196
|
result: HookResult,
|
|
280
197
|
) -> None:
|
|
198
|
+
if self._backend is None:
|
|
199
|
+
return
|
|
281
200
|
cache_key = self._get_hook_cache_key(hook_name, file_hashes)
|
|
282
|
-
self.
|
|
201
|
+
self._run_async(self._backend.set(cache_key, result, ttl=1800))
|
|
202
|
+
self.stats.total_entries += 1
|
|
283
203
|
|
|
284
204
|
def get_expensive_hook_result(
|
|
285
205
|
self,
|
|
@@ -287,20 +207,25 @@ class CrackerjackCache:
|
|
|
287
207
|
file_hashes: list[str],
|
|
288
208
|
tool_version: str | None = None,
|
|
289
209
|
) -> HookResult | None:
|
|
290
|
-
|
|
291
|
-
|
|
210
|
+
if self._backend is None:
|
|
211
|
+
self.stats.misses += 1
|
|
212
|
+
return None
|
|
292
213
|
result = self.get_hook_result(hook_name, file_hashes)
|
|
293
|
-
if result:
|
|
214
|
+
if result is not None:
|
|
294
215
|
return result
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
216
|
+
if not self.enable_disk_cache or hook_name not in self.EXPENSIVE_HOOKS:
|
|
217
|
+
return None
|
|
218
|
+
cache_key = self._get_versioned_hook_cache_key(
|
|
219
|
+
hook_name,
|
|
220
|
+
file_hashes,
|
|
221
|
+
tool_version,
|
|
222
|
+
)
|
|
223
|
+
result = self._run_async(self._backend.get(cache_key))
|
|
224
|
+
if result is None:
|
|
225
|
+
self.stats.misses += 1
|
|
226
|
+
else:
|
|
227
|
+
self.stats.hits += 1
|
|
228
|
+
return result
|
|
304
229
|
|
|
305
230
|
def set_expensive_hook_result(
|
|
306
231
|
self,
|
|
@@ -309,119 +234,126 @@ class CrackerjackCache:
|
|
|
309
234
|
result: HookResult,
|
|
310
235
|
tool_version: str | None = None,
|
|
311
236
|
) -> None:
|
|
312
|
-
|
|
313
|
-
|
|
237
|
+
if self._backend is None:
|
|
238
|
+
return
|
|
314
239
|
self.set_hook_result(hook_name, file_hashes, result)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
240
|
+
if not self.enable_disk_cache or hook_name not in self.EXPENSIVE_HOOKS:
|
|
241
|
+
return
|
|
242
|
+
cache_key = self._get_versioned_hook_cache_key(
|
|
243
|
+
hook_name,
|
|
244
|
+
file_hashes,
|
|
245
|
+
tool_version,
|
|
246
|
+
)
|
|
247
|
+
ttl = self.HOOK_DISK_TTLS.get(hook_name, 86400)
|
|
248
|
+
self._run_async(self._backend.set(cache_key, result, ttl=ttl))
|
|
323
249
|
|
|
324
250
|
def get_file_hash(self, file_path: Path) -> str | None:
|
|
251
|
+
if self._backend is None:
|
|
252
|
+
self.stats.misses += 1
|
|
253
|
+
return None
|
|
325
254
|
stat = file_path.stat()
|
|
326
|
-
cache_key = f"file_hash:
|
|
327
|
-
|
|
255
|
+
cache_key = f"file_hash:{file_path}:{stat.st_mtime}:{stat.st_size}"
|
|
256
|
+
result = self._run_async(self._backend.get(cache_key))
|
|
257
|
+
if result is None:
|
|
258
|
+
self.stats.misses += 1
|
|
259
|
+
else:
|
|
260
|
+
self.stats.hits += 1
|
|
261
|
+
return result
|
|
328
262
|
|
|
329
263
|
def set_file_hash(self, file_path: Path, file_hash: str) -> None:
|
|
264
|
+
if self._backend is None:
|
|
265
|
+
return
|
|
330
266
|
stat = file_path.stat()
|
|
331
|
-
cache_key = f"file_hash:
|
|
332
|
-
self.
|
|
267
|
+
cache_key = f"file_hash:{file_path}:{stat.st_mtime}:{stat.st_size}"
|
|
268
|
+
self._run_async(self._backend.set(cache_key, file_hash, ttl=3600))
|
|
269
|
+
self.stats.total_entries += 1
|
|
333
270
|
|
|
334
|
-
def get_config_data(self, config_key: str) ->
|
|
335
|
-
|
|
271
|
+
def get_config_data(self, config_key: str) -> Any | None:
|
|
272
|
+
if self._backend is None:
|
|
273
|
+
self.stats.misses += 1
|
|
274
|
+
return None
|
|
275
|
+
result = self._run_async(self._backend.get(f"config:{config_key}"))
|
|
276
|
+
if result is None:
|
|
277
|
+
self.stats.misses += 1
|
|
278
|
+
else:
|
|
279
|
+
self.stats.hits += 1
|
|
280
|
+
return result
|
|
336
281
|
|
|
337
|
-
def set_config_data(self, config_key: str, data:
|
|
338
|
-
self.
|
|
282
|
+
def set_config_data(self, config_key: str, data: Any) -> None:
|
|
283
|
+
if self._backend is None:
|
|
284
|
+
return
|
|
285
|
+
self._run_async(self._backend.set(f"config:{config_key}", data, ttl=7200))
|
|
286
|
+
self.stats.total_entries += 1
|
|
339
287
|
|
|
340
|
-
def get(self, key: str, default:
|
|
341
|
-
|
|
342
|
-
|
|
288
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
289
|
+
if self._backend is None:
|
|
290
|
+
return default
|
|
291
|
+
result = self._run_async(self._backend.get(key))
|
|
292
|
+
return result if result is not None else default
|
|
343
293
|
|
|
344
|
-
def set(self, key: str, value:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
294
|
+
def set(self, key: str, value: Any, ttl_seconds: int | None = None) -> None:
|
|
295
|
+
if self._backend is None:
|
|
296
|
+
return
|
|
297
|
+
ttl = ttl_seconds if ttl_seconds is not None else 3600
|
|
298
|
+
self._run_async(self._backend.set(key, value, ttl=ttl))
|
|
348
299
|
|
|
349
|
-
def get_agent_decision(self, agent_name: str, issue_hash: str) ->
|
|
350
|
-
|
|
351
|
-
if not self.enable_disk_cache:
|
|
300
|
+
def get_agent_decision(self, agent_name: str, issue_hash: str) -> Any | None:
|
|
301
|
+
if self._backend is None or not self.enable_disk_cache:
|
|
352
302
|
return None
|
|
353
|
-
|
|
354
303
|
cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
|
|
355
|
-
return self.
|
|
304
|
+
return self._run_async(self._backend.get(cache_key))
|
|
356
305
|
|
|
357
306
|
def set_agent_decision(
|
|
358
|
-
self,
|
|
307
|
+
self,
|
|
308
|
+
agent_name: str,
|
|
309
|
+
issue_hash: str,
|
|
310
|
+
decision: Any,
|
|
359
311
|
) -> None:
|
|
360
|
-
|
|
361
|
-
if not self.enable_disk_cache:
|
|
312
|
+
if self._backend is None or not self.enable_disk_cache:
|
|
362
313
|
return
|
|
363
|
-
|
|
364
314
|
cache_key = f"agent:{agent_name}:{issue_hash}:{self.AGENT_VERSION}"
|
|
365
|
-
self.
|
|
315
|
+
self._run_async(self._backend.set(cache_key, decision, ttl=604800))
|
|
366
316
|
|
|
367
|
-
def get_quality_baseline(self, git_hash: str) -> dict[str,
|
|
368
|
-
|
|
369
|
-
if not self.enable_disk_cache:
|
|
317
|
+
def get_quality_baseline(self, git_hash: str) -> dict[str, Any] | None:
|
|
318
|
+
if self._backend is None or not self.enable_disk_cache:
|
|
370
319
|
return None
|
|
320
|
+
return self._run_async(self._backend.get(f"baseline:{git_hash}"))
|
|
371
321
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
322
|
+
def set_quality_baseline(
|
|
323
|
+
self,
|
|
324
|
+
git_hash: str,
|
|
325
|
+
metrics: dict[str, Any],
|
|
326
|
+
) -> None:
|
|
327
|
+
if self._backend is None or not self.enable_disk_cache:
|
|
377
328
|
return
|
|
329
|
+
self._run_async(self._backend.set(f"baseline:{git_hash}", metrics, ttl=2592000))
|
|
378
330
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
keys_to_remove = [
|
|
386
|
-
key
|
|
387
|
-
for key in self.hook_results_cache._cache
|
|
388
|
-
if key.startswith(f"hook_result: {hook_name}: ")
|
|
389
|
-
]
|
|
390
|
-
for key in keys_to_remove:
|
|
391
|
-
self.hook_results_cache.invalidate(key)
|
|
392
|
-
else:
|
|
393
|
-
self.hook_results_cache.clear()
|
|
394
|
-
|
|
395
|
-
def cleanup_all(self) -> dict[str, int]:
|
|
396
|
-
results = {
|
|
397
|
-
"hook_results": self.hook_results_cache.cleanup_expired(),
|
|
398
|
-
"file_hashes": self.file_hash_cache.cleanup_expired(),
|
|
399
|
-
"config": self.config_cache.cleanup_expired(),
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if self.enable_disk_cache:
|
|
403
|
-
results["disk_cache"] = self.disk_cache.cleanup_expired()
|
|
404
|
-
|
|
405
|
-
return results
|
|
331
|
+
@staticmethod
|
|
332
|
+
def invalidate_hook_cache(hook_name: str | None = None) -> None:
|
|
333
|
+
logger.warning(
|
|
334
|
+
"ACB cache fallback does not support selective invalidation (hook=%s).",
|
|
335
|
+
hook_name,
|
|
336
|
+
)
|
|
406
337
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
"
|
|
411
|
-
"
|
|
338
|
+
@staticmethod
|
|
339
|
+
def cleanup_all() -> dict[str, int]:
|
|
340
|
+
return {
|
|
341
|
+
"hook_results": 0,
|
|
342
|
+
"file_hashes": 0,
|
|
343
|
+
"config": 0,
|
|
344
|
+
"disk_cache": 0,
|
|
412
345
|
}
|
|
413
346
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return stats
|
|
347
|
+
def get_cache_stats(self) -> dict[str, Any]:
|
|
348
|
+
return {"acb_cache": self.stats.to_dict()}
|
|
418
349
|
|
|
419
|
-
|
|
350
|
+
@staticmethod
|
|
351
|
+
def _get_hook_cache_key(hook_name: str, file_hashes: list[str]) -> str:
|
|
420
352
|
hash_signature = hashlib.md5(
|
|
421
|
-
",
|
|
353
|
+
",".join(sorted(file_hashes)).encode(),
|
|
422
354
|
usedforsecurity=False,
|
|
423
355
|
).hexdigest()
|
|
424
|
-
return f"hook_result:
|
|
356
|
+
return f"hook_result:{hook_name}:{hash_signature}"
|
|
425
357
|
|
|
426
358
|
def _get_versioned_hook_cache_key(
|
|
427
359
|
self,
|
|
@@ -429,10 +361,9 @@ class CrackerjackCache:
|
|
|
429
361
|
file_hashes: list[str],
|
|
430
362
|
tool_version: str | None = None,
|
|
431
363
|
) -> str:
|
|
432
|
-
"""Get cache key with tool version for disk cache invalidation."""
|
|
433
|
-
hash_signature = hashlib.md5(
|
|
434
|
-
", ".join(sorted(file_hashes)).encode(),
|
|
435
|
-
usedforsecurity=False,
|
|
436
|
-
).hexdigest()
|
|
437
364
|
version_part = f":{tool_version}" if tool_version else ""
|
|
438
|
-
|
|
365
|
+
base_key = self._get_hook_cache_key(hook_name, file_hashes)
|
|
366
|
+
return f"{base_key}{version_part}"
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
__all__ = ["CrackerjackCache", "CacheStats", "Cache", "get_cache"]
|