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,173 @@
|
|
|
1
|
+
from contextlib import suppress
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from acb.console import Console
|
|
5
|
+
from acb.depends import Inject, depends
|
|
6
|
+
|
|
7
|
+
from .regex_patterns import SAFE_PATTERNS
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CoverageBadgeService:
|
|
11
|
+
"""Service for managing coverage badges in README.md files."""
|
|
12
|
+
|
|
13
|
+
@depends.inject
|
|
14
|
+
def __init__(self, console: Inject[Console], project_root: Path) -> None:
|
|
15
|
+
self.console = console
|
|
16
|
+
self.project_root = project_root
|
|
17
|
+
self.readme_path = project_root / "README.md"
|
|
18
|
+
|
|
19
|
+
def update_readme_coverage_badge(self, coverage_percent: float) -> bool:
|
|
20
|
+
"""Update or insert coverage badge in README.md with current coverage percentage."""
|
|
21
|
+
if not self.readme_path.exists():
|
|
22
|
+
self.console.print(
|
|
23
|
+
"[yellow]⚠️[/yellow] README.md not found, skipping badge update"
|
|
24
|
+
)
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
readme_content = self.readme_path.read_text(encoding="utf-8")
|
|
29
|
+
badge_url = self._generate_badge_url(coverage_percent)
|
|
30
|
+
|
|
31
|
+
if self._has_coverage_badge(readme_content):
|
|
32
|
+
updated_content = self._update_existing_badge(readme_content, badge_url)
|
|
33
|
+
action = "updated"
|
|
34
|
+
else:
|
|
35
|
+
updated_content = self._insert_new_badge(readme_content, badge_url)
|
|
36
|
+
action = "added"
|
|
37
|
+
|
|
38
|
+
if updated_content != readme_content:
|
|
39
|
+
self.readme_path.write_text(updated_content, encoding="utf-8")
|
|
40
|
+
self.console.print(
|
|
41
|
+
f"[green]📊[/green] Coverage badge {action}: {coverage_percent:.1f}%"
|
|
42
|
+
)
|
|
43
|
+
return True
|
|
44
|
+
else:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
self.console.print(f"[red]❌[/red] Failed to update coverage badge: {e}")
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def _generate_badge_url(self, coverage_percent: float) -> str:
|
|
52
|
+
"""Generate shields.io badge URL with appropriate color coding."""
|
|
53
|
+
color = self._get_badge_color(coverage_percent)
|
|
54
|
+
# URL encode the % symbol as %25
|
|
55
|
+
encoded_percent = f"{coverage_percent:.1f}%25"
|
|
56
|
+
return f"https://img.shields.io/badge/coverage-{encoded_percent}-{color}"
|
|
57
|
+
|
|
58
|
+
def _get_badge_color(self, coverage_percent: float) -> str:
|
|
59
|
+
"""Determine badge color based on coverage percentage."""
|
|
60
|
+
if coverage_percent < 50:
|
|
61
|
+
return "red"
|
|
62
|
+
elif coverage_percent < 80:
|
|
63
|
+
return "yellow"
|
|
64
|
+
return "brightgreen"
|
|
65
|
+
|
|
66
|
+
def _has_coverage_badge(self, content: str) -> bool:
|
|
67
|
+
"""Check if README already contains a coverage badge."""
|
|
68
|
+
# Use safe pattern for badge detection
|
|
69
|
+
return SAFE_PATTERNS["detect_coverage_badge"].search(content) is not None
|
|
70
|
+
|
|
71
|
+
def _update_existing_badge(self, content: str, new_badge_url: str) -> str:
|
|
72
|
+
"""Replace existing coverage badge with new one."""
|
|
73
|
+
# Try different safe patterns for badge replacement
|
|
74
|
+
patterns_to_try = [
|
|
75
|
+
"update_coverage_badge_url",
|
|
76
|
+
"update_coverage_badge_any",
|
|
77
|
+
"update_shields_coverage_url",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for pattern_name in patterns_to_try:
|
|
81
|
+
pattern_obj = SAFE_PATTERNS[pattern_name]
|
|
82
|
+
# Use the pattern and manually replace NEW_BADGE_URL with actual URL
|
|
83
|
+
temp_content = pattern_obj.apply(content)
|
|
84
|
+
if temp_content != content:
|
|
85
|
+
# Replace placeholder with actual URL
|
|
86
|
+
new_content = temp_content.replace("NEW_BADGE_URL", new_badge_url)
|
|
87
|
+
return new_content
|
|
88
|
+
|
|
89
|
+
return content
|
|
90
|
+
|
|
91
|
+
def _insert_new_badge(self, content: str, badge_url: str) -> str:
|
|
92
|
+
"""Insert new coverage badge in the appropriate location."""
|
|
93
|
+
lines = content.split("\n")
|
|
94
|
+
|
|
95
|
+
# Find the badge section (after title, before first heading)
|
|
96
|
+
insert_index = self._find_badge_insertion_point(lines)
|
|
97
|
+
|
|
98
|
+
if insert_index is not None:
|
|
99
|
+
coverage_badge = f""
|
|
100
|
+
lines.insert(insert_index, coverage_badge)
|
|
101
|
+
return "\n".join(lines)
|
|
102
|
+
# Fallback: add after title
|
|
103
|
+
return self._insert_after_title(content, badge_url)
|
|
104
|
+
|
|
105
|
+
def _find_badge_insertion_point(self, lines: list[str]) -> int | None:
|
|
106
|
+
"""Find the best location to insert the coverage badge."""
|
|
107
|
+
# Look for existing badge lines
|
|
108
|
+
badge_lines = [
|
|
109
|
+
i for i, line in enumerate(lines) if line.strip().startswith(("[![", "!["))
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
if badge_lines:
|
|
113
|
+
# Insert after the last existing badge
|
|
114
|
+
return badge_lines[-1] + 1
|
|
115
|
+
|
|
116
|
+
# Look for first non-empty line after title
|
|
117
|
+
title_found = False
|
|
118
|
+
for i, line in enumerate(lines):
|
|
119
|
+
if line.startswith("#") and not title_found:
|
|
120
|
+
title_found = True
|
|
121
|
+
continue
|
|
122
|
+
elif title_found and line.strip() == "":
|
|
123
|
+
continue
|
|
124
|
+
elif title_found and line.strip():
|
|
125
|
+
return i
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
def _insert_after_title(self, content: str, badge_url: str) -> str:
|
|
130
|
+
"""Fallback method to insert badge after the title."""
|
|
131
|
+
lines = content.split("\n")
|
|
132
|
+
|
|
133
|
+
# Find title line
|
|
134
|
+
for i, line in enumerate(lines):
|
|
135
|
+
if line.startswith("#"):
|
|
136
|
+
# Insert after title with blank line
|
|
137
|
+
coverage_badge = f""
|
|
138
|
+
if i + 1 < len(lines) and lines[i + 1].strip() == "":
|
|
139
|
+
lines.insert(i + 2, coverage_badge)
|
|
140
|
+
else:
|
|
141
|
+
lines.insert(i + 1, "")
|
|
142
|
+
lines.insert(i + 2, coverage_badge)
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
return "\n".join(lines)
|
|
146
|
+
|
|
147
|
+
def should_update_badge(self, coverage_percent: float) -> bool:
|
|
148
|
+
"""Check if badge should be updated based on coverage change."""
|
|
149
|
+
if not self.readme_path.exists():
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
content = self.readme_path.read_text(encoding="utf-8")
|
|
154
|
+
current_coverage = self._extract_current_coverage(content)
|
|
155
|
+
|
|
156
|
+
if current_coverage is None:
|
|
157
|
+
return True # No badge exists, should add one
|
|
158
|
+
|
|
159
|
+
# Only update if coverage changed by at least 0.01% (more accurate reporting)
|
|
160
|
+
return abs(coverage_percent - current_coverage) >= 0.01
|
|
161
|
+
|
|
162
|
+
except Exception:
|
|
163
|
+
return True # On error, attempt update
|
|
164
|
+
|
|
165
|
+
def _extract_current_coverage(self, content: str) -> float | None:
|
|
166
|
+
"""Extract current coverage percentage from existing badge."""
|
|
167
|
+
match = SAFE_PATTERNS["extract_coverage_percentage"].search(content)
|
|
168
|
+
|
|
169
|
+
if match:
|
|
170
|
+
with suppress(ValueError):
|
|
171
|
+
return float(match.group(1))
|
|
172
|
+
|
|
173
|
+
return None
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import typing as t
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from acb.console import Console
|
|
7
|
+
from acb.depends import Inject, depends
|
|
8
|
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
|
|
9
|
+
|
|
10
|
+
from crackerjack.models.protocols import CoverageRatchetProtocol
|
|
11
|
+
from crackerjack.services.filesystem import FileSystemService
|
|
12
|
+
from crackerjack.services.regex_patterns import update_coverage_requirement
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CoverageRatchetService(CoverageRatchetProtocol):
|
|
16
|
+
MILESTONES = [15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 95, 100]
|
|
17
|
+
|
|
18
|
+
TOLERANCE_MARGIN = 2.0
|
|
19
|
+
|
|
20
|
+
@depends.inject
|
|
21
|
+
def __init__(self, pkg_path: Path, console: Inject[Console]) -> None:
|
|
22
|
+
# Normalize to pathlib.Path to avoid async path behaviors
|
|
23
|
+
try:
|
|
24
|
+
self.pkg_path = Path(str(pkg_path))
|
|
25
|
+
except Exception:
|
|
26
|
+
self.pkg_path = Path(pkg_path)
|
|
27
|
+
self.console = console
|
|
28
|
+
self.ratchet_file = self.pkg_path / ".coverage-ratchet.json"
|
|
29
|
+
self.pyproject_file = self.pkg_path / "pyproject.toml"
|
|
30
|
+
|
|
31
|
+
def initialize(self) -> None:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
def cleanup(self) -> None:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def health_check(self) -> bool:
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
def shutdown(self) -> None:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def metrics(self) -> dict[str, t.Any]:
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
def is_healthy(self) -> bool:
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def register_resource(self, resource: t.Any) -> None:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def cleanup_resource(self, resource: t.Any) -> None:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def record_error(self, error: Exception) -> None:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def increment_requests(self) -> None:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def get_custom_metric(self, name: str) -> t.Any:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
def set_custom_metric(self, name: str, value: t.Any) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def initialize_baseline(self, initial_coverage: float) -> None:
|
|
68
|
+
if self.ratchet_file.exists():
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
ratchet_data: dict[str, t.Any] = {
|
|
72
|
+
"baseline": initial_coverage,
|
|
73
|
+
"current_minimum": initial_coverage,
|
|
74
|
+
"target": 100.0,
|
|
75
|
+
"last_updated": datetime.now().isoformat(),
|
|
76
|
+
"history": [
|
|
77
|
+
{
|
|
78
|
+
"date": datetime.now().isoformat(),
|
|
79
|
+
"coverage": initial_coverage,
|
|
80
|
+
"commit": "baseline",
|
|
81
|
+
"milestone": False,
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"milestones_achieved": [],
|
|
85
|
+
"next_milestone": self._get_next_milestone(initial_coverage),
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
self.ratchet_file.write_text(json.dumps(ratchet_data, indent=2))
|
|
89
|
+
self.console.print(
|
|
90
|
+
f"[cyan]📊[/ cyan] Coverage ratchet initialized at {initial_coverage: .2f}% baseline"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def get_ratchet_data(self) -> dict[str, t.Any]:
|
|
94
|
+
if not self.ratchet_file.exists():
|
|
95
|
+
return {}
|
|
96
|
+
return t.cast(dict[str, t.Any], json.loads(self.ratchet_file.read_text()))
|
|
97
|
+
|
|
98
|
+
def get_status_report(self) -> dict[str, t.Any]:
|
|
99
|
+
"""Get status report for coverage ratchet service."""
|
|
100
|
+
return self.get_ratchet_data()
|
|
101
|
+
|
|
102
|
+
def get_baseline(self) -> float:
|
|
103
|
+
data = self.get_ratchet_data()
|
|
104
|
+
baseline = data.get("baseline")
|
|
105
|
+
return float(baseline) if baseline is not None else 0.0
|
|
106
|
+
|
|
107
|
+
def get_baseline_coverage(self) -> float:
|
|
108
|
+
return self.get_baseline()
|
|
109
|
+
|
|
110
|
+
def update_baseline_coverage(self, new_coverage: float) -> bool:
|
|
111
|
+
result: bool = self.update_coverage(new_coverage).get("success", False)
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
def is_coverage_regression(self, current_coverage: float) -> bool:
|
|
115
|
+
baseline = self.get_baseline()
|
|
116
|
+
return current_coverage < (baseline - self.TOLERANCE_MARGIN)
|
|
117
|
+
|
|
118
|
+
def calculate_coverage_gap(self) -> float:
|
|
119
|
+
data = self.get_ratchet_data()
|
|
120
|
+
baseline = data.get("baseline")
|
|
121
|
+
baseline = float(baseline) if baseline is not None else 0.0
|
|
122
|
+
next_milestone = data.get("next_milestone")
|
|
123
|
+
next_milestone = float(next_milestone) if next_milestone is not None else None
|
|
124
|
+
if next_milestone:
|
|
125
|
+
return next_milestone - baseline
|
|
126
|
+
return 100.0 - baseline
|
|
127
|
+
|
|
128
|
+
def update_coverage(self, new_coverage: float) -> dict[str, t.Any]:
|
|
129
|
+
if not self.ratchet_file.exists():
|
|
130
|
+
self.initialize_baseline(new_coverage)
|
|
131
|
+
return {
|
|
132
|
+
"status": "initialized",
|
|
133
|
+
"message": f"Coverage ratchet initialized at {new_coverage: .2f}%",
|
|
134
|
+
"milestones": [],
|
|
135
|
+
"progress_to_100": f"{new_coverage: .1f}% of the way to 100 % coverage",
|
|
136
|
+
"allowed": True,
|
|
137
|
+
"baseline_updated": True,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
data = self.get_ratchet_data()
|
|
141
|
+
current_baseline = data["baseline"]
|
|
142
|
+
|
|
143
|
+
tolerance_threshold = current_baseline - self.TOLERANCE_MARGIN
|
|
144
|
+
if new_coverage < tolerance_threshold:
|
|
145
|
+
return {
|
|
146
|
+
"status": "regression",
|
|
147
|
+
"message": f"Coverage decreased from {current_baseline: .2f}% to {new_coverage: .2f}% (below {self.TOLERANCE_MARGIN}% tolerance margin)",
|
|
148
|
+
"regression_amount": current_baseline - new_coverage,
|
|
149
|
+
"tolerance_threshold": tolerance_threshold,
|
|
150
|
+
"allowed": False,
|
|
151
|
+
"baseline_updated": False,
|
|
152
|
+
}
|
|
153
|
+
elif new_coverage > current_baseline + 0.01:
|
|
154
|
+
milestones_hit = self._check_milestones(
|
|
155
|
+
current_baseline, new_coverage, data
|
|
156
|
+
)
|
|
157
|
+
self._update_baseline(new_coverage, data, milestones_hit)
|
|
158
|
+
self._update_pyproject_requirement(new_coverage)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
"status": "improved",
|
|
162
|
+
"message": f"Coverage improved from {current_baseline: .2f}% to {new_coverage: .2f}% !",
|
|
163
|
+
"improvement": new_coverage - current_baseline,
|
|
164
|
+
"milestones": milestones_hit,
|
|
165
|
+
"progress_to_100": f"{new_coverage: .1f}% of the way to 100 % coverage",
|
|
166
|
+
"next_milestone": self._get_next_milestone(new_coverage),
|
|
167
|
+
"points_to_next": (next_milestone - new_coverage)
|
|
168
|
+
if (next_milestone := self._get_next_milestone(new_coverage))
|
|
169
|
+
is not None
|
|
170
|
+
else 0,
|
|
171
|
+
"allowed": True,
|
|
172
|
+
"baseline_updated": True,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"status": "maintained",
|
|
177
|
+
"message": f"Coverage maintained at {new_coverage: .2f}% (within {self.TOLERANCE_MARGIN}% tolerance margin)",
|
|
178
|
+
"allowed": True,
|
|
179
|
+
"baseline_updated": False,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def _check_milestones(
|
|
183
|
+
self, old_coverage: float, new_coverage: float, data: dict[str, t.Any]
|
|
184
|
+
) -> list[float]:
|
|
185
|
+
achieved_milestones = set(data.get("milestones_achieved", []))
|
|
186
|
+
return [
|
|
187
|
+
milestone
|
|
188
|
+
for milestone in self.MILESTONES
|
|
189
|
+
if (
|
|
190
|
+
old_coverage < milestone <= new_coverage
|
|
191
|
+
and milestone not in achieved_milestones
|
|
192
|
+
)
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
def _get_next_milestone(self, coverage: float) -> float | None:
|
|
196
|
+
for milestone in self.MILESTONES:
|
|
197
|
+
if milestone > coverage:
|
|
198
|
+
return milestone
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
def _update_baseline(
|
|
202
|
+
self, new_coverage: float, data: dict[str, t.Any], milestones_hit: list[float]
|
|
203
|
+
) -> None:
|
|
204
|
+
data["baseline"] = new_coverage
|
|
205
|
+
data["current_minimum"] = new_coverage
|
|
206
|
+
data["last_updated"] = datetime.now().isoformat()
|
|
207
|
+
|
|
208
|
+
data["history"].append(
|
|
209
|
+
{
|
|
210
|
+
"date": datetime.now().isoformat(),
|
|
211
|
+
"coverage": new_coverage,
|
|
212
|
+
"commit": "current",
|
|
213
|
+
"milestone": len(milestones_hit) > 0,
|
|
214
|
+
"milestones_hit": milestones_hit,
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
for milestone in milestones_hit:
|
|
219
|
+
if milestone not in data["milestones_achieved"]:
|
|
220
|
+
data["milestones_achieved"].append(milestone)
|
|
221
|
+
|
|
222
|
+
data["next_milestone"] = self._get_next_milestone(new_coverage)
|
|
223
|
+
|
|
224
|
+
if len(data["history"]) > 50:
|
|
225
|
+
data["history"] = data["history"][-50:]
|
|
226
|
+
|
|
227
|
+
self.ratchet_file.write_text(json.dumps(data, indent=2))
|
|
228
|
+
|
|
229
|
+
def _update_pyproject_requirement(self, new_coverage: float) -> None:
|
|
230
|
+
try:
|
|
231
|
+
content = self.pyproject_file.read_text()
|
|
232
|
+
|
|
233
|
+
updated_content = update_coverage_requirement(content, new_coverage)
|
|
234
|
+
|
|
235
|
+
if updated_content != content:
|
|
236
|
+
updated_content = (
|
|
237
|
+
FileSystemService.clean_trailing_whitespace_and_newlines(
|
|
238
|
+
updated_content
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
self.pyproject_file.write_text(updated_content)
|
|
243
|
+
self.console.print(
|
|
244
|
+
f"[cyan]📝[/ cyan] Updated pyproject.toml coverage requirement to {new_coverage: .0f}%"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
except Exception as e:
|
|
248
|
+
self.console.print(
|
|
249
|
+
f"[yellow]⚠️[/ yellow] Failed to update pyproject.toml: {e}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def get_progress_visualization(self) -> str:
|
|
253
|
+
data = self.get_ratchet_data()
|
|
254
|
+
if not data:
|
|
255
|
+
return "Coverage ratchet not initialized"
|
|
256
|
+
|
|
257
|
+
current = data["baseline"]
|
|
258
|
+
target = 100.0
|
|
259
|
+
next_milestone = data.get("next_milestone")
|
|
260
|
+
|
|
261
|
+
progress_chars = int(current / target * 20)
|
|
262
|
+
bar = "█" * progress_chars + "░" * (20 - progress_chars)
|
|
263
|
+
|
|
264
|
+
result = f"Coverage Progress: {current: .2f}% [{bar}] → 100 %\n"
|
|
265
|
+
result += f" Current ─┘{'': > 18} └─ Goal\n"
|
|
266
|
+
|
|
267
|
+
if next_milestone:
|
|
268
|
+
points_needed = next_milestone - current
|
|
269
|
+
result += f"Next milestone: {next_milestone: .0f}% (+{points_needed: .2f}% needed)\n"
|
|
270
|
+
|
|
271
|
+
return result
|
|
272
|
+
|
|
273
|
+
def get_coverage_improvement_needed(self) -> float:
|
|
274
|
+
"""Get percentage improvement needed to reach next milestone."""
|
|
275
|
+
current = self.get_baseline_coverage()
|
|
276
|
+
for milestone in self.MILESTONES:
|
|
277
|
+
if current < milestone:
|
|
278
|
+
needed = milestone - current
|
|
279
|
+
return max(0.0, needed)
|
|
280
|
+
return 0.0
|
|
281
|
+
|
|
282
|
+
def _calculate_trend(self, data: dict[str, t.Any]) -> str:
|
|
283
|
+
history = data.get("history", [])
|
|
284
|
+
if len(history) < 2:
|
|
285
|
+
return "insufficient_data"
|
|
286
|
+
|
|
287
|
+
recent_entries = history[-5:]
|
|
288
|
+
if len(recent_entries) < 2:
|
|
289
|
+
return "insufficient_data"
|
|
290
|
+
|
|
291
|
+
start_coverage = recent_entries[0]["coverage"]
|
|
292
|
+
end_coverage = recent_entries[-1]["coverage"]
|
|
293
|
+
|
|
294
|
+
if end_coverage > start_coverage + 0.5:
|
|
295
|
+
return "improving"
|
|
296
|
+
elif end_coverage < start_coverage - 0.5:
|
|
297
|
+
return "declining"
|
|
298
|
+
return "stable"
|
|
299
|
+
|
|
300
|
+
def display_milestone_celebration(self, milestones: list[float]) -> None:
|
|
301
|
+
for milestone in milestones:
|
|
302
|
+
if milestone == 100.0:
|
|
303
|
+
self.console.print(
|
|
304
|
+
"[gold]🎉🏆 PERFECT ! 100 % COVERAGE ACHIEVED ! 🏆🎉[/ gold]"
|
|
305
|
+
)
|
|
306
|
+
elif milestone >= 90:
|
|
307
|
+
self.console.print(
|
|
308
|
+
f"[gold]🏆 Milestone achieved: {milestone: .0f}% coverage ! Approaching perfection ![/ gold]"
|
|
309
|
+
)
|
|
310
|
+
elif milestone >= 50:
|
|
311
|
+
self.console.print(
|
|
312
|
+
f"[green]🎯 Milestone achieved: {milestone: .0f}% coverage ! Great progress ![/ green]"
|
|
313
|
+
)
|
|
314
|
+
else:
|
|
315
|
+
self.console.print(
|
|
316
|
+
f"[cyan]📈 Milestone achieved: {milestone: .0f}% coverage ! Keep it up ![/ cyan]"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def show_progress_with_spinner(self) -> None:
|
|
320
|
+
data = self.get_ratchet_data()
|
|
321
|
+
if not data:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
current = data["baseline"]
|
|
325
|
+
target = 100.0
|
|
326
|
+
|
|
327
|
+
with Progress(
|
|
328
|
+
SpinnerColumn(),
|
|
329
|
+
TextColumn("[progress.description]{task.description}"),
|
|
330
|
+
BarColumn(),
|
|
331
|
+
TextColumn("[progress.percentage]{task.percentage: > 3.0f}%"),
|
|
332
|
+
) as progress:
|
|
333
|
+
task = progress.add_task(
|
|
334
|
+
"Coverage Progress", total=target, completed=current
|
|
335
|
+
)
|
|
336
|
+
progress.update(task, description=f"Coverage: {current: .1f}% / 100 %")
|
|
337
|
+
|
|
338
|
+
def get_coverage_report(self) -> str | None:
|
|
339
|
+
data = self.get_ratchet_data()
|
|
340
|
+
if not data:
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
current_coverage = data.get("baseline", 0.0)
|
|
344
|
+
next_milestone = data.get("next_milestone")
|
|
345
|
+
|
|
346
|
+
report = f"Coverage: {current_coverage: .2f}%"
|
|
347
|
+
if next_milestone:
|
|
348
|
+
progress = (current_coverage / next_milestone) * 100
|
|
349
|
+
report += (
|
|
350
|
+
f" (next milestone: {next_milestone: .0f}%, {progress: .1f}% there)"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return report
|
|
354
|
+
|
|
355
|
+
def check_and_update_coverage(self) -> dict[str, t.Any]:
|
|
356
|
+
try:
|
|
357
|
+
coverage_file = self.pkg_path / "coverage.json"
|
|
358
|
+
if not coverage_file.exists():
|
|
359
|
+
return {
|
|
360
|
+
"success": True,
|
|
361
|
+
"status": "no_coverage_data",
|
|
362
|
+
"message": "No coverage data found-tests passed without coverage",
|
|
363
|
+
"allowed": True,
|
|
364
|
+
"baseline_updated": False,
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
coverage_data = json.loads(coverage_file.read_text())
|
|
368
|
+
current_coverage = coverage_data.get("totals", {}).get(
|
|
369
|
+
"percent_covered", 0.0
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
result = self.update_coverage(current_coverage)
|
|
373
|
+
result["success"] = result.get("allowed", True)
|
|
374
|
+
return result
|
|
375
|
+
|
|
376
|
+
except Exception as e:
|
|
377
|
+
return {
|
|
378
|
+
"success": False,
|
|
379
|
+
"error": str(e),
|
|
380
|
+
"message": "Failed to read coverage data",
|
|
381
|
+
}
|