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,631 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import typing as t
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from acb.console import Console
|
|
7
|
+
from acb.depends import Inject, depends
|
|
8
|
+
|
|
9
|
+
from crackerjack.core.retry import retry_api_call
|
|
10
|
+
from crackerjack.models.protocols import (
|
|
11
|
+
ChangelogGeneratorProtocol,
|
|
12
|
+
FileSystemInterface,
|
|
13
|
+
GitServiceProtocol,
|
|
14
|
+
RegexPatternsProtocol,
|
|
15
|
+
SecurityServiceProtocol,
|
|
16
|
+
VersionAnalyzerProtocol,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PublishManagerImpl:
|
|
21
|
+
@depends.inject # type: ignore[misc]
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
git_service: Inject[GitServiceProtocol],
|
|
25
|
+
version_analyzer: Inject[VersionAnalyzerProtocol],
|
|
26
|
+
changelog_generator: Inject[ChangelogGeneratorProtocol],
|
|
27
|
+
filesystem: Inject[FileSystemInterface],
|
|
28
|
+
security: Inject[SecurityServiceProtocol],
|
|
29
|
+
regex_patterns: Inject[RegexPatternsProtocol],
|
|
30
|
+
console: Inject[Console],
|
|
31
|
+
pkg_path: Inject[Path],
|
|
32
|
+
dry_run: bool = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
# Foundation dependencies
|
|
35
|
+
self.console = console
|
|
36
|
+
self.pkg_path = pkg_path
|
|
37
|
+
self.dry_run = dry_run
|
|
38
|
+
|
|
39
|
+
# Services injected via ACB DI
|
|
40
|
+
self._git_service = git_service
|
|
41
|
+
self._version_analyzer = version_analyzer
|
|
42
|
+
self._changelog_generator = changelog_generator
|
|
43
|
+
self._regex_patterns = regex_patterns
|
|
44
|
+
self.filesystem = filesystem
|
|
45
|
+
self.security = security
|
|
46
|
+
|
|
47
|
+
def _run_command(
|
|
48
|
+
self,
|
|
49
|
+
cmd: list[str],
|
|
50
|
+
timeout: int = 300,
|
|
51
|
+
) -> subprocess.CompletedProcess[str]:
|
|
52
|
+
secure_env = self.security.create_secure_command_env()
|
|
53
|
+
|
|
54
|
+
result = subprocess.run(
|
|
55
|
+
cmd,
|
|
56
|
+
check=False,
|
|
57
|
+
cwd=self.pkg_path,
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
timeout=timeout,
|
|
61
|
+
env=secure_env,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if result.stdout:
|
|
65
|
+
result.stdout = self.security.mask_tokens(result.stdout)
|
|
66
|
+
if result.stderr:
|
|
67
|
+
result.stderr = self.security.mask_tokens(result.stderr)
|
|
68
|
+
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
def _get_current_version(self) -> str | None:
|
|
72
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
73
|
+
if not pyproject_path.exists():
|
|
74
|
+
return None
|
|
75
|
+
try:
|
|
76
|
+
from tomllib import loads
|
|
77
|
+
|
|
78
|
+
content = self.filesystem.read_file(pyproject_path)
|
|
79
|
+
data = loads(content)
|
|
80
|
+
version = data.get("project", {}).get("version")
|
|
81
|
+
return version if isinstance(version, str) else None
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.console.print(f"[yellow]โ ๏ธ[/ yellow] Error reading version: {e}")
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def _update_version_in_file(self, new_version: str) -> bool:
|
|
87
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
88
|
+
try:
|
|
89
|
+
content = self.filesystem.read_file(pyproject_path)
|
|
90
|
+
|
|
91
|
+
# Use injected service or get through ACB DI
|
|
92
|
+
if self._regex_patterns is not None:
|
|
93
|
+
update_pyproject_version_func = (
|
|
94
|
+
self._regex_patterns.update_pyproject_version
|
|
95
|
+
)
|
|
96
|
+
else:
|
|
97
|
+
from acb.depends import depends
|
|
98
|
+
|
|
99
|
+
update_pyproject_version_func = depends.get_sync(
|
|
100
|
+
RegexPatternsProtocol
|
|
101
|
+
).update_pyproject_version
|
|
102
|
+
|
|
103
|
+
new_content = update_pyproject_version_func(content, new_version)
|
|
104
|
+
if content != new_content:
|
|
105
|
+
if not self.dry_run:
|
|
106
|
+
self.filesystem.write_file(pyproject_path, new_content)
|
|
107
|
+
self.console.print(
|
|
108
|
+
f"[green]โ
[/ green] Updated version to {new_version}",
|
|
109
|
+
)
|
|
110
|
+
return True
|
|
111
|
+
self.console.print(
|
|
112
|
+
"[yellow]โ ๏ธ[/ yellow] Version pattern not found in pyproject.toml",
|
|
113
|
+
)
|
|
114
|
+
return False
|
|
115
|
+
except Exception as e:
|
|
116
|
+
self.console.print(f"[red]โ[/ red] Error updating version: {e}")
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def _calculate_next_version(self, current: str, bump_type: str) -> str:
|
|
120
|
+
try:
|
|
121
|
+
parts = current.split(".")
|
|
122
|
+
if len(parts) != 3:
|
|
123
|
+
msg = f"Invalid version format: {current}"
|
|
124
|
+
raise ValueError(msg)
|
|
125
|
+
major, minor, patch = map(int, parts)
|
|
126
|
+
if bump_type == "major":
|
|
127
|
+
return f"{major + 1}.0.0"
|
|
128
|
+
if bump_type == "minor":
|
|
129
|
+
return f"{major}.{minor + 1}.0"
|
|
130
|
+
if bump_type == "patch":
|
|
131
|
+
return f"{major}.{minor}.{patch + 1}"
|
|
132
|
+
msg = f"Invalid bump type: {bump_type}"
|
|
133
|
+
raise ValueError(msg)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
self.console.print(f"[red]โ[/ red] Error calculating version: {e}")
|
|
136
|
+
raise
|
|
137
|
+
|
|
138
|
+
def bump_version(self, version_type: str) -> str:
|
|
139
|
+
current_version = self._get_current_version()
|
|
140
|
+
if not current_version:
|
|
141
|
+
self.console.print("[red]โ[/ red] Could not determine current version")
|
|
142
|
+
msg = "Cannot determine current version"
|
|
143
|
+
raise ValueError(msg)
|
|
144
|
+
self.console.print(f"[cyan]๐ฆ[/ cyan] Current version: {current_version}")
|
|
145
|
+
|
|
146
|
+
# Get intelligent version analysis and recommendation
|
|
147
|
+
recommendation = self._get_version_recommendation()
|
|
148
|
+
if recommendation and version_type != "interactive":
|
|
149
|
+
self._display_version_analysis(recommendation)
|
|
150
|
+
if version_type == "auto":
|
|
151
|
+
version_type = recommendation.bump_type.value
|
|
152
|
+
self.console.print(
|
|
153
|
+
f"[green]๐ฏ[/green] Using AI-recommended bump type: {version_type}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if version_type == "interactive":
|
|
157
|
+
version_type = self._prompt_for_version_type(recommendation)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
new_version = self._calculate_next_version(current_version, version_type)
|
|
161
|
+
if self.dry_run:
|
|
162
|
+
self.console.print(
|
|
163
|
+
f"[yellow]๐[/ yellow] Would bump {version_type} version: {current_version} โ {new_version}",
|
|
164
|
+
)
|
|
165
|
+
elif self._update_version_in_file(new_version):
|
|
166
|
+
self.console.print(
|
|
167
|
+
f"[green]๐[/ green] Bumped {version_type} version: {current_version} โ {new_version}",
|
|
168
|
+
)
|
|
169
|
+
# Update changelog after successful version bump
|
|
170
|
+
self._update_changelog_for_version(current_version, new_version)
|
|
171
|
+
else:
|
|
172
|
+
msg = "Failed to update version in file"
|
|
173
|
+
raise ValueError(msg)
|
|
174
|
+
|
|
175
|
+
return new_version
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.console.print(f"[red]โ[/ red] Version bump failed: {e}")
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
def _prompt_for_version_type(self, recommendation: t.Any = None) -> str:
|
|
181
|
+
try:
|
|
182
|
+
from rich.prompt import Prompt
|
|
183
|
+
|
|
184
|
+
default_type = "patch"
|
|
185
|
+
if recommendation:
|
|
186
|
+
default_type = recommendation.bump_type.value
|
|
187
|
+
self.console.print(
|
|
188
|
+
f"[dim]AI recommendation: {default_type} (confidence: {recommendation.confidence:.0%})[/dim]"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return Prompt.ask(
|
|
192
|
+
"[cyan]๐ฆ[/ cyan] Select version bump type",
|
|
193
|
+
choices=["patch", "minor", "major"],
|
|
194
|
+
default=default_type,
|
|
195
|
+
)
|
|
196
|
+
except ImportError:
|
|
197
|
+
self.console.print(
|
|
198
|
+
"[yellow]โ ๏ธ[/ yellow] Rich prompt not available, defaulting to patch"
|
|
199
|
+
)
|
|
200
|
+
return "patch"
|
|
201
|
+
|
|
202
|
+
def _get_version_recommendation(self) -> t.Any:
|
|
203
|
+
"""Get AI-powered version bump recommendation based on git history."""
|
|
204
|
+
try:
|
|
205
|
+
import asyncio
|
|
206
|
+
|
|
207
|
+
# Use injected version analyzer service
|
|
208
|
+
version_analyzer = self._version_analyzer
|
|
209
|
+
|
|
210
|
+
# Get recommendation asynchronously
|
|
211
|
+
try:
|
|
212
|
+
# Try to get existing event loop
|
|
213
|
+
loop = asyncio.get_event_loop()
|
|
214
|
+
if loop.is_running():
|
|
215
|
+
# Create a new event loop in a thread if one is already running
|
|
216
|
+
import concurrent.futures
|
|
217
|
+
|
|
218
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
219
|
+
future = executor.submit(
|
|
220
|
+
asyncio.run, version_analyzer.recommend_version_bump()
|
|
221
|
+
)
|
|
222
|
+
recommendation = future.result(timeout=10)
|
|
223
|
+
else:
|
|
224
|
+
recommendation = loop.run_until_complete(
|
|
225
|
+
version_analyzer.recommend_version_bump()
|
|
226
|
+
)
|
|
227
|
+
except RuntimeError:
|
|
228
|
+
# No event loop, create one
|
|
229
|
+
recommendation = asyncio.run(version_analyzer.recommend_version_bump())
|
|
230
|
+
|
|
231
|
+
return recommendation
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
self.console.print(f"[yellow]โ ๏ธ[/yellow] Version analysis failed: {e}")
|
|
235
|
+
return None
|
|
236
|
+
|
|
237
|
+
def _display_version_analysis(self, recommendation: t.Any) -> None:
|
|
238
|
+
"""Display version analysis in a compact format."""
|
|
239
|
+
if not recommendation:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
self.console.print("\n[cyan]๐ฏ AI Version Analysis[/cyan]")
|
|
243
|
+
self.console.print(
|
|
244
|
+
f"Recommended: [bold green]{recommendation.recommended_version}[/bold green] "
|
|
245
|
+
f"({recommendation.bump_type.value.upper()}) - {recommendation.confidence:.0%} confidence"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if recommendation.reasoning:
|
|
249
|
+
self.console.print(f"[dim]โ {recommendation.reasoning[0]}[/dim]")
|
|
250
|
+
|
|
251
|
+
# Show key changes briefly
|
|
252
|
+
if recommendation.breaking_changes:
|
|
253
|
+
self.console.print(
|
|
254
|
+
f"[red]โ ๏ธ[/red] {len(recommendation.breaking_changes)} breaking changes detected"
|
|
255
|
+
)
|
|
256
|
+
elif recommendation.new_features:
|
|
257
|
+
self.console.print(
|
|
258
|
+
f"[green]โจ[/green] {len(recommendation.new_features)} new features detected"
|
|
259
|
+
)
|
|
260
|
+
elif recommendation.bug_fixes:
|
|
261
|
+
self.console.print(
|
|
262
|
+
f"[blue]๐ง[/blue] {len(recommendation.bug_fixes)} bug fixes detected"
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def validate_auth(self) -> bool:
|
|
266
|
+
auth_methods = self._collect_auth_methods()
|
|
267
|
+
return self._report_auth_status(auth_methods)
|
|
268
|
+
|
|
269
|
+
def _collect_auth_methods(self) -> list[str]:
|
|
270
|
+
auth_methods: list[str] = []
|
|
271
|
+
|
|
272
|
+
env_auth = self._check_env_token_auth()
|
|
273
|
+
if env_auth:
|
|
274
|
+
auth_methods.append(env_auth)
|
|
275
|
+
|
|
276
|
+
keyring_auth = self._check_keyring_auth()
|
|
277
|
+
if keyring_auth:
|
|
278
|
+
auth_methods.append(keyring_auth)
|
|
279
|
+
|
|
280
|
+
return auth_methods
|
|
281
|
+
|
|
282
|
+
def _check_env_token_auth(self) -> str | None:
|
|
283
|
+
import os
|
|
284
|
+
|
|
285
|
+
token = os.getenv("UV_PUBLISH_TOKEN")
|
|
286
|
+
if not token:
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
if self.security.validate_token_format(token, "pypi"):
|
|
290
|
+
masked_token = self.security.mask_tokens(token)
|
|
291
|
+
self.console.print(f"[dim]Token format: {masked_token}[/ dim]", style="dim")
|
|
292
|
+
return "Environment variable (UV_PUBLISH_TOKEN)"
|
|
293
|
+
self.console.print(
|
|
294
|
+
"[yellow]โ ๏ธ[/ yellow] UV_PUBLISH_TOKEN format appears invalid",
|
|
295
|
+
)
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def _check_keyring_auth(self) -> str | None:
|
|
299
|
+
try:
|
|
300
|
+
result = self._run_command(
|
|
301
|
+
[
|
|
302
|
+
"keyring",
|
|
303
|
+
"get",
|
|
304
|
+
"https://upload.pypi.org/legacy/",
|
|
305
|
+
"__token__",
|
|
306
|
+
],
|
|
307
|
+
)
|
|
308
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
309
|
+
keyring_token = result.stdout.strip()
|
|
310
|
+
if self.security.validate_token_format(keyring_token, "pypi"):
|
|
311
|
+
return "Keyring storage"
|
|
312
|
+
self.console.print(
|
|
313
|
+
"[yellow]โ ๏ธ[/ yellow] Keyring token format appears invalid",
|
|
314
|
+
)
|
|
315
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError):
|
|
316
|
+
pass
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
def _report_auth_status(self, auth_methods: list[str]) -> bool:
|
|
320
|
+
if auth_methods:
|
|
321
|
+
self.console.print("[green]โ
[/ green] PyPI authentication available: ")
|
|
322
|
+
for method in auth_methods:
|
|
323
|
+
self.console.print(f"-{method}")
|
|
324
|
+
return True
|
|
325
|
+
self._display_auth_setup_instructions()
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
def _display_auth_setup_instructions(self) -> None:
|
|
329
|
+
self.console.print("[red]โ[/ red] No valid PyPI authentication found")
|
|
330
|
+
self.console.print("\n[yellow]๐ก[/ yellow] Setup options: ")
|
|
331
|
+
self.console.print(
|
|
332
|
+
" 1. Set environment variable: export UV_PUBLISH_TOKEN=<your-pypi-token>",
|
|
333
|
+
)
|
|
334
|
+
self.console.print(
|
|
335
|
+
" 2. Use keyring: keyring set[t.Any] https://upload.pypi.org/legacy/ __token__",
|
|
336
|
+
)
|
|
337
|
+
self.console.print(
|
|
338
|
+
" 3. Ensure token starts with 'pypi-' and is properly formatted",
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def build_package(self) -> bool:
|
|
342
|
+
try:
|
|
343
|
+
self.console.print("[yellow]๐จ[/ yellow] Building package")
|
|
344
|
+
|
|
345
|
+
if self.dry_run:
|
|
346
|
+
return self._handle_dry_run_build()
|
|
347
|
+
|
|
348
|
+
return self._execute_build()
|
|
349
|
+
except Exception as e:
|
|
350
|
+
self.console.print(f"[red]โ[/ red] Build error: {e}")
|
|
351
|
+
return False
|
|
352
|
+
|
|
353
|
+
def _handle_dry_run_build(self) -> bool:
|
|
354
|
+
self.console.print("[yellow]๐[/ yellow] Would build package")
|
|
355
|
+
return True
|
|
356
|
+
|
|
357
|
+
def _clean_dist_directory(self) -> None:
|
|
358
|
+
dist_dir = self.pkg_path / "dist"
|
|
359
|
+
if not dist_dir.exists():
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
import shutil
|
|
364
|
+
|
|
365
|
+
shutil.rmtree(dist_dir)
|
|
366
|
+
dist_dir.mkdir(exist_ok=True)
|
|
367
|
+
self.console.print(
|
|
368
|
+
"[cyan]๐งน[/ cyan] Cleaned dist directory for fresh build"
|
|
369
|
+
)
|
|
370
|
+
except Exception as e:
|
|
371
|
+
self.console.print(
|
|
372
|
+
f"[yellow]โ ๏ธ[/ yellow] Warning: Could not clean dist directory: {e}"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
def _execute_build(self) -> bool:
|
|
376
|
+
self._clean_dist_directory()
|
|
377
|
+
|
|
378
|
+
result = self._run_command(["uv", "build"])
|
|
379
|
+
|
|
380
|
+
if result.returncode != 0:
|
|
381
|
+
self.console.print(f"[red]โ[/ red] Build failed: {result.stderr}")
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
self.console.print("[green]โ
[/ green] Package built successfully")
|
|
385
|
+
self._display_build_artifacts()
|
|
386
|
+
return True
|
|
387
|
+
|
|
388
|
+
def _display_build_artifacts(self) -> None:
|
|
389
|
+
dist_dir = self.pkg_path / "dist"
|
|
390
|
+
if not dist_dir.exists():
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
artifacts = list[t.Any](dist_dir.glob("*"))
|
|
394
|
+
self.console.print(f"[cyan]๐ฆ[/ cyan] Build artifacts ({len(artifacts)}): ")
|
|
395
|
+
|
|
396
|
+
for artifact in artifacts[-5:]:
|
|
397
|
+
size_str = self._format_file_size(artifact.stat().st_size)
|
|
398
|
+
self.console.print(f"-{artifact.name} ({size_str})")
|
|
399
|
+
|
|
400
|
+
def _format_file_size(self, size: int) -> str:
|
|
401
|
+
if size < 1024 * 1024:
|
|
402
|
+
return f"{size / 1024: .1f}KB"
|
|
403
|
+
return f"{size / (1024 * 1024): .1f}MB"
|
|
404
|
+
|
|
405
|
+
def publish_package(self) -> bool:
|
|
406
|
+
if not self._validate_prerequisites():
|
|
407
|
+
return False
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
self.console.print("[yellow]๐[/ yellow] Publishing to PyPI")
|
|
411
|
+
return self._perform_publish_workflow_with_retry()
|
|
412
|
+
except Exception as e:
|
|
413
|
+
self.console.print(f"[red]โ[/ red] Publish error: {e}")
|
|
414
|
+
return False
|
|
415
|
+
|
|
416
|
+
def _validate_prerequisites(self) -> bool:
|
|
417
|
+
return self.validate_auth()
|
|
418
|
+
|
|
419
|
+
def _perform_publish_workflow(self) -> bool:
|
|
420
|
+
if self.dry_run:
|
|
421
|
+
return self._handle_dry_run_publish()
|
|
422
|
+
|
|
423
|
+
if not self.build_package():
|
|
424
|
+
return False
|
|
425
|
+
|
|
426
|
+
return self._execute_publish()
|
|
427
|
+
|
|
428
|
+
@retry_api_call(max_attempts=3, delay=2.0, backoff=2.0, max_delay=60.0)
|
|
429
|
+
def _perform_publish_workflow_with_retry(self) -> bool:
|
|
430
|
+
"""Perform the publish workflow with retry logic for API connection errors."""
|
|
431
|
+
if self.dry_run:
|
|
432
|
+
return self._handle_dry_run_publish()
|
|
433
|
+
|
|
434
|
+
if not self.build_package():
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
return self._execute_publish()
|
|
438
|
+
|
|
439
|
+
def _handle_dry_run_publish(self) -> bool:
|
|
440
|
+
self.console.print("[yellow]๐[/ yellow] Would publish package to PyPI")
|
|
441
|
+
return True
|
|
442
|
+
|
|
443
|
+
def _execute_publish(self) -> bool:
|
|
444
|
+
result = self._run_command(["uv", "publish"])
|
|
445
|
+
|
|
446
|
+
# Check for success indicators in output even if return code is non-zero
|
|
447
|
+
# UV can return non-zero codes for warnings while still succeeding
|
|
448
|
+
success_indicators = [
|
|
449
|
+
"Successfully uploaded",
|
|
450
|
+
"Package uploaded successfully",
|
|
451
|
+
"Upload successful",
|
|
452
|
+
"Successfully published",
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
has_success_indicator = (
|
|
456
|
+
any(indicator in result.stdout for indicator in success_indicators)
|
|
457
|
+
if result.stdout
|
|
458
|
+
else False
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
# Consider it successful if either return code is 0 OR we find success indicators
|
|
462
|
+
success = result.returncode == 0 or has_success_indicator
|
|
463
|
+
|
|
464
|
+
if success:
|
|
465
|
+
self._handle_publish_success()
|
|
466
|
+
return True
|
|
467
|
+
|
|
468
|
+
self._handle_publish_failure(result.stderr)
|
|
469
|
+
return False
|
|
470
|
+
|
|
471
|
+
def _handle_publish_failure(self, error_msg: str) -> None:
|
|
472
|
+
self.console.print(f"[red]โ[/ red] Publish failed: {error_msg}")
|
|
473
|
+
|
|
474
|
+
def _handle_publish_success(self) -> None:
|
|
475
|
+
self.console.print("[green]๐[/ green] Package published successfully !")
|
|
476
|
+
self._display_package_url()
|
|
477
|
+
|
|
478
|
+
def _display_package_url(self) -> None:
|
|
479
|
+
current_version = self._get_current_version()
|
|
480
|
+
package_name = self._get_package_name()
|
|
481
|
+
|
|
482
|
+
if package_name and current_version:
|
|
483
|
+
url = f"https://pypi.org/project/{package_name}/{current_version}/"
|
|
484
|
+
self.console.print(f"[cyan]๐[/ cyan] Package URL: {url}")
|
|
485
|
+
|
|
486
|
+
def _get_package_name(self) -> str | None:
|
|
487
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
488
|
+
|
|
489
|
+
with suppress(Exception):
|
|
490
|
+
from tomllib import loads
|
|
491
|
+
|
|
492
|
+
content = self.filesystem.read_file(pyproject_path)
|
|
493
|
+
data = loads(content)
|
|
494
|
+
name = data.get("project", {}).get("name", "")
|
|
495
|
+
return name if isinstance(name, str) else None
|
|
496
|
+
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
def cleanup_old_releases(self, keep_releases: int = 10) -> bool:
|
|
500
|
+
try:
|
|
501
|
+
self.console.print(
|
|
502
|
+
f"[yellow]๐งน[/ yellow] Cleaning up old releases (keeping {keep_releases})...",
|
|
503
|
+
)
|
|
504
|
+
if self.dry_run:
|
|
505
|
+
self.console.print(
|
|
506
|
+
"[yellow]๐[/ yellow] Would clean up old PyPI releases",
|
|
507
|
+
)
|
|
508
|
+
return True
|
|
509
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
510
|
+
from tomllib import loads
|
|
511
|
+
|
|
512
|
+
content = self.filesystem.read_file(pyproject_path)
|
|
513
|
+
data = loads(content)
|
|
514
|
+
package_name = data.get("project", {}).get("name", "")
|
|
515
|
+
if not package_name:
|
|
516
|
+
self.console.print(
|
|
517
|
+
"[yellow]โ ๏ธ[/ yellow] Could not determine package name",
|
|
518
|
+
)
|
|
519
|
+
return False
|
|
520
|
+
self.console.print(
|
|
521
|
+
f"[cyan]๐ฆ[/ cyan] Would analyze releases for {package_name}",
|
|
522
|
+
)
|
|
523
|
+
self.console.print(
|
|
524
|
+
f"[cyan]๐ง[/ cyan] Would keep {keep_releases} most recent releases",
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
return True
|
|
528
|
+
except Exception as e:
|
|
529
|
+
self.console.print(f"[red]โ[/ red] Cleanup error: {e}")
|
|
530
|
+
return False
|
|
531
|
+
|
|
532
|
+
def create_git_tag_local(self, version: str) -> bool:
|
|
533
|
+
"""Create git tag locally without pushing (for use with push_with_tags)."""
|
|
534
|
+
try:
|
|
535
|
+
if self.dry_run:
|
|
536
|
+
self.console.print(
|
|
537
|
+
f"[yellow]๐[/ yellow] Would create git tag: v{version}",
|
|
538
|
+
)
|
|
539
|
+
return True
|
|
540
|
+
result = self._run_command(["git", "tag", f"v{version}"])
|
|
541
|
+
if result.returncode == 0:
|
|
542
|
+
self.console.print(f"[green]๐ท๏ธ[/ green] Created git tag: v{version}")
|
|
543
|
+
return True
|
|
544
|
+
self.console.print(
|
|
545
|
+
f"[red]โ[/ red] Failed to create tag: {result.stderr}",
|
|
546
|
+
)
|
|
547
|
+
return False
|
|
548
|
+
except Exception as e:
|
|
549
|
+
self.console.print(f"[red]โ[/ red] Tag creation error: {e}")
|
|
550
|
+
return False
|
|
551
|
+
|
|
552
|
+
def create_git_tag(self, version: str) -> bool:
|
|
553
|
+
"""Create git tag and push it immediately (legacy method for standalone use)."""
|
|
554
|
+
try:
|
|
555
|
+
if self.dry_run:
|
|
556
|
+
self.console.print(
|
|
557
|
+
f"[yellow]๐[/ yellow] Would create git tag: v{version}",
|
|
558
|
+
)
|
|
559
|
+
return True
|
|
560
|
+
result = self._run_command(["git", "tag", f"v{version}"])
|
|
561
|
+
if result.returncode == 0:
|
|
562
|
+
self.console.print(f"[green]๐ท๏ธ[/ green] Created git tag: v{version}")
|
|
563
|
+
push_result = self._run_command(
|
|
564
|
+
["git", "push", "origin", f"v{version}"],
|
|
565
|
+
)
|
|
566
|
+
if push_result.returncode == 0:
|
|
567
|
+
self.console.print("[green]๐ค[/ green] Pushed tag to remote")
|
|
568
|
+
else:
|
|
569
|
+
self.console.print(
|
|
570
|
+
f"[yellow]โ ๏ธ[/ yellow] Tag created but push failed: {push_result.stderr}",
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
return True
|
|
574
|
+
self.console.print(
|
|
575
|
+
f"[red]โ[/ red] Failed to create tag: {result.stderr}",
|
|
576
|
+
)
|
|
577
|
+
return False
|
|
578
|
+
except Exception as e:
|
|
579
|
+
self.console.print(f"[red]โ[/ red] Tag creation error: {e}")
|
|
580
|
+
return False
|
|
581
|
+
|
|
582
|
+
def get_package_info(self) -> dict[str, t.Any]:
|
|
583
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
584
|
+
if not pyproject_path.exists():
|
|
585
|
+
return {}
|
|
586
|
+
try:
|
|
587
|
+
from tomllib import loads
|
|
588
|
+
|
|
589
|
+
content = self.filesystem.read_file(pyproject_path)
|
|
590
|
+
data = loads(content)
|
|
591
|
+
project = data.get("project", {})
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
"name": project.get("name", ""),
|
|
595
|
+
"version": project.get("version", ""),
|
|
596
|
+
"description": project.get("description", ""),
|
|
597
|
+
"authors": project.get("authors", []),
|
|
598
|
+
"dependencies": project.get("dependencies", []),
|
|
599
|
+
"python_requires": project.get("requires-python", ""),
|
|
600
|
+
}
|
|
601
|
+
except Exception as e:
|
|
602
|
+
self.console.print(f"[yellow]โ ๏ธ[/ yellow] Error reading package info: {e}")
|
|
603
|
+
return {}
|
|
604
|
+
|
|
605
|
+
def _update_changelog_for_version(self, old_version: str, new_version: str) -> None:
|
|
606
|
+
"""Update changelog with entries from git commits since last version."""
|
|
607
|
+
try:
|
|
608
|
+
# Use injected changelog generator service
|
|
609
|
+
changelog_generator = self._changelog_generator
|
|
610
|
+
|
|
611
|
+
# Look for changelog file
|
|
612
|
+
changelog_path = self.pkg_path / "CHANGELOG.md"
|
|
613
|
+
|
|
614
|
+
# Generate changelog entries since last version
|
|
615
|
+
success = changelog_generator.generate_changelog_from_commits(
|
|
616
|
+
changelog_path=changelog_path,
|
|
617
|
+
version=new_version,
|
|
618
|
+
since_version=f"v{old_version}", # Assumes git tags are prefixed with 'v'
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
if success:
|
|
622
|
+
self.console.print(
|
|
623
|
+
f"[green]๐[/green] Updated changelog for version {new_version}"
|
|
624
|
+
)
|
|
625
|
+
else:
|
|
626
|
+
self.console.print(
|
|
627
|
+
"[yellow]โ ๏ธ[/yellow] Changelog update encountered issues"
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
except Exception as e:
|
|
631
|
+
self.console.print(f"[yellow]โ ๏ธ[/yellow] Failed to update changelog: {e}")
|