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,461 @@
|
|
|
1
|
+
"""Intelligent version bump analysis based on code changes and commit patterns."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from acb.console import Console
|
|
10
|
+
from acb.depends import Inject, depends
|
|
11
|
+
|
|
12
|
+
from .changelog_automation import ChangelogEntry, ChangelogGenerator
|
|
13
|
+
from .git import GitService
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VersionBumpType(Enum):
|
|
17
|
+
"""Semantic versioning bump types."""
|
|
18
|
+
|
|
19
|
+
MAJOR = "major" # Breaking changes (x.y.z -> x+1.0.0)
|
|
20
|
+
MINOR = "minor" # New features (x.y.z -> x.y+1.0)
|
|
21
|
+
PATCH = "patch" # Bug fixes (x.y.z -> x.y.z+1)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class VersionBumpRecommendation:
|
|
26
|
+
"""Recommendation for version bump with reasoning."""
|
|
27
|
+
|
|
28
|
+
bump_type: VersionBumpType
|
|
29
|
+
confidence: float # 0.0 to 1.0
|
|
30
|
+
reasoning: list[str]
|
|
31
|
+
current_version: str
|
|
32
|
+
recommended_version: str
|
|
33
|
+
breaking_changes: list[str]
|
|
34
|
+
new_features: list[str]
|
|
35
|
+
bug_fixes: list[str]
|
|
36
|
+
commit_analysis: dict[str, Any]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BreakingChangeAnalyzer:
|
|
40
|
+
"""Analyzes commits for breaking changes that require MAJOR version bump."""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
# Patterns that indicate breaking changes
|
|
44
|
+
self.breaking_patterns = [
|
|
45
|
+
re.compile(
|
|
46
|
+
r"BREAKING\s*CHANGE[:\s]", re.IGNORECASE
|
|
47
|
+
), # REGEX OK: breaking change detection
|
|
48
|
+
re.compile(
|
|
49
|
+
r"^[^:\n]*!:", re.MULTILINE
|
|
50
|
+
), # REGEX OK: conventional commit breaking marker
|
|
51
|
+
re.compile(
|
|
52
|
+
r"\bremove\s+\w+\s+(api|function|method|class)", re.IGNORECASE
|
|
53
|
+
), # REGEX OK: API removal detection
|
|
54
|
+
re.compile(
|
|
55
|
+
r"\bdelete\s+\w+\s+(api|endpoint|interface)", re.IGNORECASE
|
|
56
|
+
), # REGEX OK: API deletion detection
|
|
57
|
+
re.compile(
|
|
58
|
+
r"\bchange\s+\w+\s+(signature|interface|api)", re.IGNORECASE
|
|
59
|
+
), # REGEX OK: API signature change
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
def analyze(self, entries: list[ChangelogEntry]) -> tuple[bool, list[str], float]:
|
|
63
|
+
"""
|
|
64
|
+
Analyze changelog entries for breaking changes.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
(has_breaking_changes, breaking_change_descriptions, confidence)
|
|
68
|
+
"""
|
|
69
|
+
breaking_changes: list[str] = []
|
|
70
|
+
|
|
71
|
+
for entry in entries:
|
|
72
|
+
# Check if entry is already marked as breaking
|
|
73
|
+
if entry.breaking_change:
|
|
74
|
+
breaking_changes.append(entry.description)
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
# Check description against breaking change patterns
|
|
78
|
+
for pattern in self.breaking_patterns:
|
|
79
|
+
if pattern.search(entry.description):
|
|
80
|
+
breaking_changes.append(entry.description)
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
has_breaking = len(breaking_changes) > 0
|
|
84
|
+
confidence = 0.9 if has_breaking else 0.0
|
|
85
|
+
|
|
86
|
+
return has_breaking, breaking_changes, confidence
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class FeatureAnalyzer:
|
|
90
|
+
"""Analyzes commits for new features that require MINOR version bump."""
|
|
91
|
+
|
|
92
|
+
def __init__(self) -> None:
|
|
93
|
+
# Patterns that indicate new features
|
|
94
|
+
self.feature_patterns = [
|
|
95
|
+
re.compile(
|
|
96
|
+
r"^feat[(\[]", re.IGNORECASE
|
|
97
|
+
), # REGEX OK: conventional commit feat
|
|
98
|
+
re.compile(
|
|
99
|
+
r"\badd\s+(new\s+)?\w+", re.IGNORECASE
|
|
100
|
+
), # REGEX OK: addition detection
|
|
101
|
+
re.compile(
|
|
102
|
+
r"\bimplement\s+\w+", re.IGNORECASE
|
|
103
|
+
), # REGEX OK: implementation detection
|
|
104
|
+
re.compile(
|
|
105
|
+
r"\bintroduce\s+\w+", re.IGNORECASE
|
|
106
|
+
), # REGEX OK: introduction detection
|
|
107
|
+
re.compile(
|
|
108
|
+
r"\bcreate\s+(new\s+)?\w+", re.IGNORECASE
|
|
109
|
+
), # REGEX OK: creation detection
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
def analyze(self, entries: list[ChangelogEntry]) -> tuple[bool, list[str], float]:
|
|
113
|
+
"""
|
|
114
|
+
Analyze changelog entries for new features.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
(has_new_features, feature_descriptions, confidence)
|
|
118
|
+
"""
|
|
119
|
+
new_features: list[str] = []
|
|
120
|
+
|
|
121
|
+
for entry in entries:
|
|
122
|
+
# Check entry type
|
|
123
|
+
if entry.type in ("Added", "feat"):
|
|
124
|
+
new_features.append(entry.description)
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
# Check description against feature patterns
|
|
128
|
+
for pattern in self.feature_patterns:
|
|
129
|
+
if pattern.search(entry.description):
|
|
130
|
+
new_features.append(entry.description)
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
has_features = len(new_features) > 0
|
|
134
|
+
confidence = 0.8 if has_features else 0.0
|
|
135
|
+
|
|
136
|
+
return has_features, new_features, confidence
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ConventionalCommitAnalyzer:
|
|
140
|
+
"""Analyzes conventional commit messages for semantic versioning hints."""
|
|
141
|
+
|
|
142
|
+
def __init__(self) -> None:
|
|
143
|
+
# Commit type mappings to version bump types
|
|
144
|
+
self.commit_type_mappings = {
|
|
145
|
+
# MAJOR bump triggers
|
|
146
|
+
"breaking": VersionBumpType.MAJOR,
|
|
147
|
+
# MINOR bump triggers
|
|
148
|
+
"feat": VersionBumpType.MINOR,
|
|
149
|
+
"feature": VersionBumpType.MINOR,
|
|
150
|
+
# PATCH bump triggers
|
|
151
|
+
"fix": VersionBumpType.PATCH,
|
|
152
|
+
"bugfix": VersionBumpType.PATCH,
|
|
153
|
+
"patch": VersionBumpType.PATCH,
|
|
154
|
+
"hotfix": VersionBumpType.PATCH,
|
|
155
|
+
# No version bump (could be argued either way)
|
|
156
|
+
"docs": None,
|
|
157
|
+
"style": None,
|
|
158
|
+
"refactor": None,
|
|
159
|
+
"test": None,
|
|
160
|
+
"chore": None,
|
|
161
|
+
"build": None,
|
|
162
|
+
"ci": None,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
def analyze(self, entries: list[ChangelogEntry]) -> dict[str, Any]:
|
|
166
|
+
"""
|
|
167
|
+
Analyze conventional commit patterns in changelog entries.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
Analysis results including type counts and recommendations
|
|
171
|
+
"""
|
|
172
|
+
type_counts: dict[str, int] = {}
|
|
173
|
+
recommended_bumps: list[VersionBumpType] = []
|
|
174
|
+
|
|
175
|
+
for entry in entries:
|
|
176
|
+
# Parse entry type and increment count
|
|
177
|
+
entry_type = entry.type.lower()
|
|
178
|
+
type_counts[entry_type] = type_counts.get(entry_type, 0) + 1
|
|
179
|
+
|
|
180
|
+
# Determine recommended bump type
|
|
181
|
+
if entry.breaking_change:
|
|
182
|
+
recommended_bumps.append(VersionBumpType.MAJOR)
|
|
183
|
+
elif entry_type in self.commit_type_mappings:
|
|
184
|
+
bump_type = self.commit_type_mappings[entry_type]
|
|
185
|
+
if bump_type:
|
|
186
|
+
recommended_bumps.append(bump_type)
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
"type_counts": type_counts,
|
|
190
|
+
"recommended_bumps": recommended_bumps,
|
|
191
|
+
"total_entries": len(entries),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class VersionAnalyzer:
|
|
196
|
+
"""Main service for analyzing changes and recommending version bumps."""
|
|
197
|
+
|
|
198
|
+
@depends.inject
|
|
199
|
+
def __init__(self, console: Inject[Console], git_service: GitService) -> None:
|
|
200
|
+
self.console = console
|
|
201
|
+
self.git = git_service
|
|
202
|
+
|
|
203
|
+
# Initialize specialized analyzers
|
|
204
|
+
self.breaking_analyzer = BreakingChangeAnalyzer()
|
|
205
|
+
self.feature_analyzer = FeatureAnalyzer()
|
|
206
|
+
self.commit_analyzer = ConventionalCommitAnalyzer()
|
|
207
|
+
|
|
208
|
+
# Initialize changelog generator for getting entries (ACB DI injects dependencies)
|
|
209
|
+
self.changelog_generator = ChangelogGenerator()
|
|
210
|
+
|
|
211
|
+
def _get_current_version(self) -> str | None:
|
|
212
|
+
"""Get current version from pyproject.toml."""
|
|
213
|
+
pyproject_path = Path("pyproject.toml")
|
|
214
|
+
if not pyproject_path.exists():
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
from tomllib import loads
|
|
219
|
+
|
|
220
|
+
content = pyproject_path.read_text(encoding="utf-8")
|
|
221
|
+
data = loads(content)
|
|
222
|
+
version: str | None = data.get("project", {}).get("version")
|
|
223
|
+
return version
|
|
224
|
+
except Exception:
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def _calculate_next_version(self, current: str, bump_type: VersionBumpType) -> str:
|
|
228
|
+
"""Calculate next version based on current version and bump type."""
|
|
229
|
+
try:
|
|
230
|
+
parts = current.split(".")
|
|
231
|
+
if len(parts) != 3:
|
|
232
|
+
msg = f"Invalid version format: {current}"
|
|
233
|
+
raise ValueError(msg)
|
|
234
|
+
|
|
235
|
+
major, minor, patch = map(int, parts)
|
|
236
|
+
|
|
237
|
+
if bump_type == VersionBumpType.MAJOR:
|
|
238
|
+
return f"{major + 1}.0.0"
|
|
239
|
+
elif bump_type == VersionBumpType.MINOR:
|
|
240
|
+
return f"{major}.{minor + 1}.0"
|
|
241
|
+
elif bump_type == VersionBumpType.PATCH:
|
|
242
|
+
return f"{major}.{minor}.{patch + 1}"
|
|
243
|
+
else:
|
|
244
|
+
# All enum cases are covered above
|
|
245
|
+
from typing import assert_never
|
|
246
|
+
|
|
247
|
+
assert_never(bump_type)
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
self.console.print(f"[red]❌[/red] Error calculating version: {e}")
|
|
251
|
+
raise
|
|
252
|
+
|
|
253
|
+
async def recommend_version_bump(
|
|
254
|
+
self, since_version: str | None = None
|
|
255
|
+
) -> VersionBumpRecommendation:
|
|
256
|
+
"""
|
|
257
|
+
Analyze changes since last version and recommend version bump.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
since_version: Version/tag to analyze changes since (default: latest tag)
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
VersionBumpRecommendation with analysis and recommendation
|
|
264
|
+
"""
|
|
265
|
+
current_version = self._get_current_version()
|
|
266
|
+
if not current_version:
|
|
267
|
+
msg = "Could not determine current version from pyproject.toml"
|
|
268
|
+
raise ValueError(msg)
|
|
269
|
+
|
|
270
|
+
all_entries = self._collect_changelog_entries(since_version)
|
|
271
|
+
|
|
272
|
+
if not all_entries:
|
|
273
|
+
return self._create_no_changes_recommendation(current_version)
|
|
274
|
+
|
|
275
|
+
return self._analyze_entries_and_recommend(current_version, all_entries)
|
|
276
|
+
|
|
277
|
+
def _collect_changelog_entries(
|
|
278
|
+
self, since_version: str | None
|
|
279
|
+
) -> list[ChangelogEntry]:
|
|
280
|
+
"""Collect and flatten changelog entries for analysis."""
|
|
281
|
+
entries_by_type = self.changelog_generator.generate_changelog_entries(
|
|
282
|
+
since_version
|
|
283
|
+
)
|
|
284
|
+
all_entries: list[ChangelogEntry] = []
|
|
285
|
+
for entries in entries_by_type.values():
|
|
286
|
+
all_entries.extend(entries)
|
|
287
|
+
return all_entries
|
|
288
|
+
|
|
289
|
+
def _create_no_changes_recommendation(
|
|
290
|
+
self, current_version: str
|
|
291
|
+
) -> VersionBumpRecommendation:
|
|
292
|
+
"""Create recommendation when no changes are detected."""
|
|
293
|
+
return VersionBumpRecommendation(
|
|
294
|
+
bump_type=VersionBumpType.PATCH,
|
|
295
|
+
confidence=1.0,
|
|
296
|
+
reasoning=["No significant changes detected - patch bump recommended"],
|
|
297
|
+
current_version=current_version,
|
|
298
|
+
recommended_version=self._calculate_next_version(
|
|
299
|
+
current_version, VersionBumpType.PATCH
|
|
300
|
+
),
|
|
301
|
+
breaking_changes=[],
|
|
302
|
+
new_features=[],
|
|
303
|
+
bug_fixes=[],
|
|
304
|
+
commit_analysis={
|
|
305
|
+
"type_counts": {},
|
|
306
|
+
"recommended_bumps": [],
|
|
307
|
+
"total_entries": 0,
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def _analyze_entries_and_recommend(
|
|
312
|
+
self, current_version: str, all_entries: list[ChangelogEntry]
|
|
313
|
+
) -> VersionBumpRecommendation:
|
|
314
|
+
"""Analyze entries and create version bump recommendation."""
|
|
315
|
+
# Run specialized analyses
|
|
316
|
+
has_breaking, breaking_changes, breaking_confidence = (
|
|
317
|
+
self.breaking_analyzer.analyze(all_entries)
|
|
318
|
+
)
|
|
319
|
+
has_features, new_features, feature_confidence = self.feature_analyzer.analyze(
|
|
320
|
+
all_entries
|
|
321
|
+
)
|
|
322
|
+
commit_analysis = self.commit_analyzer.analyze(all_entries)
|
|
323
|
+
|
|
324
|
+
bug_fixes = [
|
|
325
|
+
entry.description
|
|
326
|
+
for entry in all_entries
|
|
327
|
+
if entry.type.lower() in ("fixed", "fix", "bugfix", "patch")
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
bump_type, confidence, reasoning = self._determine_bump_type(
|
|
331
|
+
has_breaking,
|
|
332
|
+
breaking_changes,
|
|
333
|
+
breaking_confidence,
|
|
334
|
+
has_features,
|
|
335
|
+
new_features,
|
|
336
|
+
feature_confidence,
|
|
337
|
+
bug_fixes,
|
|
338
|
+
all_entries,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
recommended_version = self._calculate_next_version(current_version, bump_type)
|
|
342
|
+
|
|
343
|
+
return VersionBumpRecommendation(
|
|
344
|
+
bump_type=bump_type,
|
|
345
|
+
confidence=confidence,
|
|
346
|
+
reasoning=reasoning,
|
|
347
|
+
current_version=current_version,
|
|
348
|
+
recommended_version=recommended_version,
|
|
349
|
+
breaking_changes=breaking_changes,
|
|
350
|
+
new_features=new_features,
|
|
351
|
+
bug_fixes=bug_fixes,
|
|
352
|
+
commit_analysis=commit_analysis,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _determine_bump_type(
|
|
356
|
+
self,
|
|
357
|
+
has_breaking: bool,
|
|
358
|
+
breaking_changes: list[str],
|
|
359
|
+
breaking_confidence: float,
|
|
360
|
+
has_features: bool,
|
|
361
|
+
new_features: list[str],
|
|
362
|
+
feature_confidence: float,
|
|
363
|
+
bug_fixes: list[str],
|
|
364
|
+
all_entries: list[ChangelogEntry],
|
|
365
|
+
) -> tuple[VersionBumpType, float, list[str]]:
|
|
366
|
+
"""Determine the appropriate version bump type and reasoning."""
|
|
367
|
+
if has_breaking:
|
|
368
|
+
return (
|
|
369
|
+
VersionBumpType.MAJOR,
|
|
370
|
+
breaking_confidence,
|
|
371
|
+
[
|
|
372
|
+
f"Breaking changes detected ({len(breaking_changes)} found)",
|
|
373
|
+
"MAJOR version bump required to maintain semantic versioning",
|
|
374
|
+
],
|
|
375
|
+
)
|
|
376
|
+
elif has_features:
|
|
377
|
+
return (
|
|
378
|
+
VersionBumpType.MINOR,
|
|
379
|
+
feature_confidence,
|
|
380
|
+
[
|
|
381
|
+
f"New features detected ({len(new_features)} found)",
|
|
382
|
+
"MINOR version bump recommended for backward-compatible functionality",
|
|
383
|
+
],
|
|
384
|
+
)
|
|
385
|
+
elif bug_fixes:
|
|
386
|
+
return (
|
|
387
|
+
VersionBumpType.PATCH,
|
|
388
|
+
0.9,
|
|
389
|
+
[
|
|
390
|
+
f"Bug fixes detected ({len(bug_fixes)} found)",
|
|
391
|
+
"PATCH version bump recommended for backward-compatible fixes",
|
|
392
|
+
],
|
|
393
|
+
)
|
|
394
|
+
return (
|
|
395
|
+
VersionBumpType.PATCH,
|
|
396
|
+
0.5,
|
|
397
|
+
[
|
|
398
|
+
f"Changes detected ({len(all_entries)} commits) with unclear impact",
|
|
399
|
+
"PATCH version bump recommended as conservative choice",
|
|
400
|
+
],
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def display_recommendation(self, recommendation: VersionBumpRecommendation) -> None:
|
|
404
|
+
"""Display version bump recommendation in a user-friendly format."""
|
|
405
|
+
self._display_summary(recommendation)
|
|
406
|
+
self._display_reasoning(recommendation)
|
|
407
|
+
self._display_changes(recommendation)
|
|
408
|
+
self._display_commit_analysis(recommendation)
|
|
409
|
+
|
|
410
|
+
def _display_summary(self, recommendation: VersionBumpRecommendation) -> None:
|
|
411
|
+
"""Display the main version bump summary."""
|
|
412
|
+
self.console.print("\n[cyan]📊 Version Bump Analysis[/cyan]")
|
|
413
|
+
self.console.print(
|
|
414
|
+
f"Current version: [bold]{recommendation.current_version}[/bold]"
|
|
415
|
+
)
|
|
416
|
+
self.console.print(
|
|
417
|
+
f"Recommended version: [bold green]{recommendation.recommended_version}[/bold green]"
|
|
418
|
+
)
|
|
419
|
+
self.console.print(
|
|
420
|
+
f"Bump type: [bold]{recommendation.bump_type.value.upper()}[/bold]"
|
|
421
|
+
)
|
|
422
|
+
self.console.print(f"Confidence: [bold]{recommendation.confidence:.0%}[/bold]")
|
|
423
|
+
|
|
424
|
+
def _display_reasoning(self, recommendation: VersionBumpRecommendation) -> None:
|
|
425
|
+
"""Display the reasoning behind the recommendation."""
|
|
426
|
+
self.console.print("\n[yellow]💡 Reasoning:[/yellow]")
|
|
427
|
+
for reason in recommendation.reasoning:
|
|
428
|
+
self.console.print(f" • {reason}")
|
|
429
|
+
|
|
430
|
+
def _display_changes(self, recommendation: VersionBumpRecommendation) -> None:
|
|
431
|
+
"""Display breaking changes, new features, and bug fixes."""
|
|
432
|
+
self._display_change_list(
|
|
433
|
+
recommendation.breaking_changes, "[red]⚠️ Breaking Changes", "red"
|
|
434
|
+
)
|
|
435
|
+
self._display_change_list(
|
|
436
|
+
recommendation.new_features, "[green]✨ New Features", "green"
|
|
437
|
+
)
|
|
438
|
+
self._display_change_list(
|
|
439
|
+
recommendation.bug_fixes, "[blue]🔧 Bug Fixes", "blue"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def _display_change_list(self, changes: list[str], title: str, color: str) -> None:
|
|
443
|
+
"""Display a list[t.Any] of changes with truncation."""
|
|
444
|
+
if changes:
|
|
445
|
+
self.console.print(f"\n{title} ({len(changes)}):[/{color}]")
|
|
446
|
+
for change in changes[:3]:
|
|
447
|
+
self.console.print(f" • {change}")
|
|
448
|
+
if len(changes) > 3:
|
|
449
|
+
self.console.print(f" • ... and {len(changes) - 3} more")
|
|
450
|
+
|
|
451
|
+
def _display_commit_analysis(
|
|
452
|
+
self, recommendation: VersionBumpRecommendation
|
|
453
|
+
) -> None:
|
|
454
|
+
"""Display commit analysis summary."""
|
|
455
|
+
analysis = recommendation.commit_analysis
|
|
456
|
+
if analysis.get("type_counts"):
|
|
457
|
+
self.console.print("\n[dim]📈 Commit Analysis:[/dim]")
|
|
458
|
+
total = analysis["total_entries"]
|
|
459
|
+
for commit_type, count in sorted(analysis["type_counts"].items()):
|
|
460
|
+
percentage = (count / total * 100) if total > 0 else 0
|
|
461
|
+
self.console.print(f" {commit_type}: {count} ({percentage:.0f}%)")
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import typing as t
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
from acb.console import Console
|
|
7
|
+
from acb.depends import Inject, depends
|
|
8
|
+
|
|
9
|
+
from crackerjack.core.retry import retry_api_call
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class VersionInfo:
|
|
14
|
+
tool_name: str
|
|
15
|
+
current_version: str
|
|
16
|
+
latest_version: str | None = None
|
|
17
|
+
update_available: bool = False
|
|
18
|
+
error: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VersionChecker:
|
|
22
|
+
@depends.inject
|
|
23
|
+
def __init__(self, console: Inject[Console]) -> None:
|
|
24
|
+
self.console = console
|
|
25
|
+
self.tools_to_check = {
|
|
26
|
+
"ruff": self._get_ruff_version,
|
|
27
|
+
"pyright": self._get_pyright_version,
|
|
28
|
+
"pre-commit": self._get_precommit_version,
|
|
29
|
+
"uv": self._get_uv_version,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async def check_tool_updates(self) -> dict[str, VersionInfo]:
|
|
33
|
+
results = {}
|
|
34
|
+
for tool_name, version_getter in self.tools_to_check.items():
|
|
35
|
+
results[tool_name] = await self._check_single_tool(
|
|
36
|
+
tool_name, version_getter
|
|
37
|
+
)
|
|
38
|
+
return results
|
|
39
|
+
|
|
40
|
+
async def _check_single_tool(
|
|
41
|
+
self, tool_name: str, version_getter: t.Callable[[], str | None]
|
|
42
|
+
) -> VersionInfo:
|
|
43
|
+
try:
|
|
44
|
+
current_version = version_getter()
|
|
45
|
+
if current_version:
|
|
46
|
+
latest_version = await self._fetch_latest_version(tool_name)
|
|
47
|
+
return self._create_installed_version_info(
|
|
48
|
+
tool_name, current_version, latest_version
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
return self._create_missing_tool_info(tool_name)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
return self._create_error_version_info(tool_name, e)
|
|
54
|
+
|
|
55
|
+
def _create_installed_version_info(
|
|
56
|
+
self, tool_name: str, current_version: str, latest_version: str | None
|
|
57
|
+
) -> VersionInfo:
|
|
58
|
+
update_available = (
|
|
59
|
+
latest_version is not None
|
|
60
|
+
and self._version_compare(current_version, latest_version) < 0
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if update_available:
|
|
64
|
+
self.console.print(
|
|
65
|
+
f"[yellow]🔄 {tool_name} update available: "
|
|
66
|
+
f"{current_version} → {latest_version}[/ yellow]"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return VersionInfo(
|
|
70
|
+
tool_name=tool_name,
|
|
71
|
+
current_version=current_version,
|
|
72
|
+
latest_version=latest_version,
|
|
73
|
+
update_available=update_available,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def _create_missing_tool_info(self, tool_name: str) -> VersionInfo:
|
|
77
|
+
self.console.print(f"[red]⚠️ {tool_name} not installed[/ red]")
|
|
78
|
+
return VersionInfo(
|
|
79
|
+
tool_name=tool_name,
|
|
80
|
+
current_version="not installed",
|
|
81
|
+
error=f"{tool_name} not found or not installed",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _create_error_version_info(
|
|
85
|
+
self, tool_name: str, error: Exception
|
|
86
|
+
) -> VersionInfo:
|
|
87
|
+
self.console.print(f"[red]❌ Error checking {tool_name}: {error}[/ red]")
|
|
88
|
+
return VersionInfo(
|
|
89
|
+
tool_name=tool_name,
|
|
90
|
+
current_version="unknown",
|
|
91
|
+
error=str(error),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _get_ruff_version(self) -> str | None:
|
|
95
|
+
return self._get_tool_version("ruff")
|
|
96
|
+
|
|
97
|
+
def _get_pyright_version(self) -> str | None:
|
|
98
|
+
return self._get_tool_version("pyright")
|
|
99
|
+
|
|
100
|
+
def _get_precommit_version(self) -> str | None:
|
|
101
|
+
return self._get_tool_version("pre-commit")
|
|
102
|
+
|
|
103
|
+
def _get_uv_version(self) -> str | None:
|
|
104
|
+
return self._get_tool_version("uv")
|
|
105
|
+
|
|
106
|
+
def _get_tool_version(self, tool_name: str) -> str | None:
|
|
107
|
+
try:
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
[tool_name, "--version"],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
timeout=10,
|
|
113
|
+
check=False,
|
|
114
|
+
)
|
|
115
|
+
if result.returncode == 0:
|
|
116
|
+
version_line = result.stdout.strip()
|
|
117
|
+
return version_line.split()[-1] if version_line else None
|
|
118
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
119
|
+
pass
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
@retry_api_call(max_attempts=3, delay=1.0, backoff=2.0, max_delay=30.0)
|
|
123
|
+
async def _fetch_latest_version(self, tool_name: str) -> str | None:
|
|
124
|
+
try:
|
|
125
|
+
# Fix URLs - remove spaces that were added
|
|
126
|
+
pypi_urls = {
|
|
127
|
+
"ruff": "https://pypi.org/pypi/ruff/json",
|
|
128
|
+
"pyright": "https://pypi.org/pypi/pyright/json",
|
|
129
|
+
"pre-commit": "https://pypi.org/pypi/pre-commit/json",
|
|
130
|
+
"uv": "https://pypi.org/pypi/uv/json",
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
url = pypi_urls.get(tool_name)
|
|
134
|
+
if not url:
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
timeout = aiohttp.ClientTimeout(total=10.0)
|
|
138
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
139
|
+
async with session.get(url) as response:
|
|
140
|
+
response.raise_for_status()
|
|
141
|
+
data: dict[str, t.Any] = await response.json()
|
|
142
|
+
return data.get("info", {}).get("version")
|
|
143
|
+
|
|
144
|
+
except Exception:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
def _version_compare(self, current: str, latest: str) -> int:
|
|
148
|
+
try:
|
|
149
|
+
current_parts, current_len = self._parse_version_parts(current)
|
|
150
|
+
latest_parts, latest_len = self._parse_version_parts(latest)
|
|
151
|
+
|
|
152
|
+
normalized_current, normalized_latest = self._normalize_version_parts(
|
|
153
|
+
current_parts, latest_parts
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
numeric_result = self._compare_numeric_parts(
|
|
157
|
+
normalized_current, normalized_latest
|
|
158
|
+
)
|
|
159
|
+
if numeric_result != 0:
|
|
160
|
+
return numeric_result
|
|
161
|
+
|
|
162
|
+
return self._handle_length_differences(
|
|
163
|
+
current_len, latest_len, normalized_current, normalized_latest
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
except (ValueError, AttributeError):
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
def _parse_version_parts(self, version: str) -> tuple[list[int], int]:
|
|
170
|
+
parts = [int(x) for x in version.split(".")]
|
|
171
|
+
return parts, len(parts)
|
|
172
|
+
|
|
173
|
+
def _normalize_version_parts(
|
|
174
|
+
self, current_parts: list[int], latest_parts: list[int]
|
|
175
|
+
) -> tuple[list[int], list[int]]:
|
|
176
|
+
max_len = max(len(current_parts), len(latest_parts))
|
|
177
|
+
current_normalized = current_parts + [0] * (max_len - len(current_parts))
|
|
178
|
+
latest_normalized = latest_parts + [0] * (max_len - len(latest_parts))
|
|
179
|
+
return current_normalized, latest_normalized
|
|
180
|
+
|
|
181
|
+
def _compare_numeric_parts(
|
|
182
|
+
self, current_parts: list[int], latest_parts: list[int]
|
|
183
|
+
) -> int:
|
|
184
|
+
for current_part, latest_part in zip(current_parts, latest_parts):
|
|
185
|
+
if current_part < latest_part:
|
|
186
|
+
return -1
|
|
187
|
+
if current_part > latest_part:
|
|
188
|
+
return 1
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
def _handle_length_differences(
|
|
192
|
+
self,
|
|
193
|
+
current_len: int,
|
|
194
|
+
latest_len: int,
|
|
195
|
+
current_parts: list[int],
|
|
196
|
+
latest_parts: list[int],
|
|
197
|
+
) -> int:
|
|
198
|
+
if current_len == latest_len:
|
|
199
|
+
return 0
|
|
200
|
+
|
|
201
|
+
if current_len < latest_len:
|
|
202
|
+
return self._compare_when_current_shorter(
|
|
203
|
+
current_len, latest_len, latest_parts
|
|
204
|
+
)
|
|
205
|
+
return self._compare_when_latest_shorter(latest_len, current_len, current_parts)
|
|
206
|
+
|
|
207
|
+
def _compare_when_current_shorter(
|
|
208
|
+
self, current_len: int, latest_len: int, latest_parts: list[int]
|
|
209
|
+
) -> int:
|
|
210
|
+
extra_parts = latest_parts[current_len:]
|
|
211
|
+
if any(part != 0 for part in extra_parts):
|
|
212
|
+
return -1
|
|
213
|
+
|
|
214
|
+
return -1 if current_len > 1 else 0
|
|
215
|
+
|
|
216
|
+
def _compare_when_latest_shorter(
|
|
217
|
+
self, latest_len: int, current_len: int, current_parts: list[int]
|
|
218
|
+
) -> int:
|
|
219
|
+
extra_parts = current_parts[latest_len:]
|
|
220
|
+
if any(part != 0 for part in extra_parts):
|
|
221
|
+
return 1
|
|
222
|
+
|
|
223
|
+
return 1 if latest_len > 1 else 0
|