crackerjack 0.37.9__py3-none-any.whl → 0.45.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crackerjack/README.md +19 -0
- crackerjack/__init__.py +30 -1
- crackerjack/__main__.py +342 -1263
- crackerjack/adapters/README.md +18 -0
- crackerjack/adapters/__init__.py +27 -5
- crackerjack/adapters/_output_paths.py +167 -0
- crackerjack/adapters/_qa_adapter_base.py +309 -0
- crackerjack/adapters/_tool_adapter_base.py +706 -0
- crackerjack/adapters/ai/README.md +65 -0
- crackerjack/adapters/ai/__init__.py +5 -0
- crackerjack/adapters/ai/claude.py +853 -0
- crackerjack/adapters/complexity/README.md +53 -0
- crackerjack/adapters/complexity/__init__.py +10 -0
- crackerjack/adapters/complexity/complexipy.py +641 -0
- crackerjack/adapters/dependency/__init__.py +22 -0
- crackerjack/adapters/dependency/pip_audit.py +418 -0
- crackerjack/adapters/format/README.md +72 -0
- crackerjack/adapters/format/__init__.py +11 -0
- crackerjack/adapters/format/mdformat.py +313 -0
- crackerjack/adapters/format/ruff.py +516 -0
- crackerjack/adapters/lint/README.md +47 -0
- crackerjack/adapters/lint/__init__.py +11 -0
- crackerjack/adapters/lint/codespell.py +273 -0
- crackerjack/adapters/lsp/README.md +49 -0
- crackerjack/adapters/lsp/__init__.py +27 -0
- crackerjack/adapters/{rust_tool_manager.py → lsp/_manager.py} +3 -3
- crackerjack/adapters/{skylos_adapter.py → lsp/skylos.py} +59 -7
- crackerjack/adapters/{zuban_adapter.py → lsp/zuban.py} +3 -6
- crackerjack/adapters/refactor/README.md +59 -0
- crackerjack/adapters/refactor/__init__.py +12 -0
- crackerjack/adapters/refactor/creosote.py +318 -0
- crackerjack/adapters/refactor/refurb.py +406 -0
- crackerjack/adapters/refactor/skylos.py +494 -0
- crackerjack/adapters/sast/README.md +132 -0
- crackerjack/adapters/sast/__init__.py +32 -0
- crackerjack/adapters/sast/_base.py +201 -0
- crackerjack/adapters/sast/bandit.py +423 -0
- crackerjack/adapters/sast/pyscn.py +405 -0
- crackerjack/adapters/sast/semgrep.py +241 -0
- crackerjack/adapters/security/README.md +111 -0
- crackerjack/adapters/security/__init__.py +17 -0
- crackerjack/adapters/security/gitleaks.py +339 -0
- crackerjack/adapters/type/README.md +52 -0
- crackerjack/adapters/type/__init__.py +12 -0
- crackerjack/adapters/type/pyrefly.py +402 -0
- crackerjack/adapters/type/ty.py +402 -0
- crackerjack/adapters/type/zuban.py +522 -0
- crackerjack/adapters/utility/README.md +51 -0
- crackerjack/adapters/utility/__init__.py +10 -0
- crackerjack/adapters/utility/checks.py +884 -0
- crackerjack/agents/README.md +264 -0
- crackerjack/agents/__init__.py +40 -12
- crackerjack/agents/base.py +1 -0
- crackerjack/agents/claude_code_bridge.py +641 -0
- crackerjack/agents/coordinator.py +49 -53
- crackerjack/agents/dry_agent.py +187 -3
- crackerjack/agents/enhanced_coordinator.py +279 -0
- crackerjack/agents/enhanced_proactive_agent.py +185 -0
- crackerjack/agents/error_middleware.py +53 -0
- crackerjack/agents/formatting_agent.py +6 -8
- crackerjack/agents/helpers/__init__.py +9 -0
- crackerjack/agents/helpers/performance/__init__.py +22 -0
- crackerjack/agents/helpers/performance/performance_ast_analyzer.py +357 -0
- crackerjack/agents/helpers/performance/performance_pattern_detector.py +909 -0
- crackerjack/agents/helpers/performance/performance_recommender.py +572 -0
- crackerjack/agents/helpers/refactoring/__init__.py +22 -0
- crackerjack/agents/helpers/refactoring/code_transformer.py +536 -0
- crackerjack/agents/helpers/refactoring/complexity_analyzer.py +344 -0
- crackerjack/agents/helpers/refactoring/dead_code_detector.py +437 -0
- crackerjack/agents/helpers/test_creation/__init__.py +19 -0
- crackerjack/agents/helpers/test_creation/test_ast_analyzer.py +216 -0
- crackerjack/agents/helpers/test_creation/test_coverage_analyzer.py +643 -0
- crackerjack/agents/helpers/test_creation/test_template_generator.py +1031 -0
- crackerjack/agents/performance_agent.py +121 -1152
- crackerjack/agents/refactoring_agent.py +156 -655
- crackerjack/agents/semantic_agent.py +479 -0
- crackerjack/agents/semantic_helpers.py +356 -0
- crackerjack/agents/test_creation_agent.py +19 -1605
- crackerjack/api.py +5 -7
- crackerjack/cli/README.md +394 -0
- crackerjack/cli/__init__.py +1 -1
- crackerjack/cli/cache_handlers.py +23 -18
- crackerjack/cli/cache_handlers_enhanced.py +1 -4
- crackerjack/cli/facade.py +70 -8
- crackerjack/cli/formatting.py +13 -0
- crackerjack/cli/handlers/__init__.py +85 -0
- crackerjack/cli/handlers/advanced.py +103 -0
- crackerjack/cli/handlers/ai_features.py +62 -0
- crackerjack/cli/handlers/analytics.py +479 -0
- crackerjack/cli/handlers/changelog.py +271 -0
- crackerjack/cli/handlers/config_handlers.py +16 -0
- crackerjack/cli/handlers/coverage.py +84 -0
- crackerjack/cli/handlers/documentation.py +280 -0
- crackerjack/cli/handlers/main_handlers.py +497 -0
- crackerjack/cli/handlers/monitoring.py +371 -0
- crackerjack/cli/handlers.py +249 -49
- crackerjack/cli/interactive.py +8 -5
- crackerjack/cli/options.py +203 -110
- crackerjack/cli/semantic_handlers.py +292 -0
- crackerjack/cli/version.py +19 -0
- crackerjack/code_cleaner.py +60 -24
- crackerjack/config/README.md +472 -0
- crackerjack/config/__init__.py +256 -0
- crackerjack/config/global_lock_config.py +191 -54
- crackerjack/config/hooks.py +188 -16
- crackerjack/config/loader.py +239 -0
- crackerjack/config/settings.py +141 -0
- crackerjack/config/tool_commands.py +331 -0
- crackerjack/core/README.md +393 -0
- crackerjack/core/async_workflow_orchestrator.py +79 -53
- crackerjack/core/autofix_coordinator.py +22 -9
- crackerjack/core/container.py +10 -9
- crackerjack/core/enhanced_container.py +9 -9
- crackerjack/core/performance.py +1 -1
- crackerjack/core/performance_monitor.py +5 -3
- crackerjack/core/phase_coordinator.py +1018 -634
- crackerjack/core/proactive_workflow.py +3 -3
- crackerjack/core/retry.py +275 -0
- crackerjack/core/service_watchdog.py +167 -23
- crackerjack/core/session_coordinator.py +187 -382
- crackerjack/core/timeout_manager.py +161 -44
- crackerjack/core/workflow/__init__.py +21 -0
- crackerjack/core/workflow/workflow_ai_coordinator.py +863 -0
- crackerjack/core/workflow/workflow_event_orchestrator.py +1107 -0
- crackerjack/core/workflow/workflow_issue_parser.py +714 -0
- crackerjack/core/workflow/workflow_phase_executor.py +1158 -0
- crackerjack/core/workflow/workflow_security_gates.py +400 -0
- crackerjack/core/workflow_orchestrator.py +1247 -953
- crackerjack/data/README.md +11 -0
- crackerjack/data/__init__.py +8 -0
- crackerjack/data/models.py +79 -0
- crackerjack/data/repository.py +210 -0
- crackerjack/decorators/README.md +180 -0
- crackerjack/decorators/__init__.py +35 -0
- crackerjack/decorators/error_handling.py +649 -0
- crackerjack/decorators/error_handling_decorators.py +334 -0
- crackerjack/decorators/helpers.py +58 -0
- crackerjack/decorators/patterns.py +281 -0
- crackerjack/decorators/utils.py +58 -0
- crackerjack/docs/README.md +11 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +1 -1
- crackerjack/documentation/README.md +11 -0
- crackerjack/documentation/ai_templates.py +1 -1
- crackerjack/documentation/dual_output_generator.py +11 -9
- crackerjack/documentation/reference_generator.py +104 -59
- crackerjack/dynamic_config.py +52 -61
- crackerjack/errors.py +1 -1
- crackerjack/events/README.md +11 -0
- crackerjack/events/__init__.py +16 -0
- crackerjack/events/telemetry.py +175 -0
- crackerjack/events/workflow_bus.py +346 -0
- crackerjack/exceptions/README.md +301 -0
- crackerjack/exceptions/__init__.py +5 -0
- crackerjack/exceptions/config.py +4 -0
- crackerjack/exceptions/tool_execution_error.py +245 -0
- crackerjack/executors/README.md +591 -0
- crackerjack/executors/__init__.py +2 -0
- crackerjack/executors/async_hook_executor.py +539 -77
- crackerjack/executors/cached_hook_executor.py +3 -3
- crackerjack/executors/hook_executor.py +967 -102
- crackerjack/executors/hook_lock_manager.py +31 -22
- crackerjack/executors/individual_hook_executor.py +66 -32
- crackerjack/executors/lsp_aware_hook_executor.py +136 -57
- crackerjack/executors/progress_hook_executor.py +282 -0
- crackerjack/executors/tool_proxy.py +23 -7
- crackerjack/hooks/README.md +485 -0
- crackerjack/hooks/lsp_hook.py +8 -9
- crackerjack/intelligence/README.md +557 -0
- crackerjack/interactive.py +37 -10
- crackerjack/managers/README.md +369 -0
- crackerjack/managers/async_hook_manager.py +41 -57
- crackerjack/managers/hook_manager.py +449 -79
- crackerjack/managers/publish_manager.py +81 -36
- crackerjack/managers/test_command_builder.py +290 -12
- crackerjack/managers/test_executor.py +93 -8
- crackerjack/managers/test_manager.py +1082 -75
- crackerjack/managers/test_progress.py +118 -26
- crackerjack/mcp/README.md +374 -0
- crackerjack/mcp/cache.py +25 -2
- crackerjack/mcp/client_runner.py +35 -18
- crackerjack/mcp/context.py +9 -9
- crackerjack/mcp/dashboard.py +24 -8
- crackerjack/mcp/enhanced_progress_monitor.py +34 -23
- crackerjack/mcp/file_monitor.py +27 -6
- crackerjack/mcp/progress_components.py +45 -34
- crackerjack/mcp/progress_monitor.py +6 -9
- crackerjack/mcp/rate_limiter.py +11 -7
- crackerjack/mcp/server.py +2 -0
- crackerjack/mcp/server_core.py +187 -55
- crackerjack/mcp/service_watchdog.py +12 -9
- crackerjack/mcp/task_manager.py +2 -2
- crackerjack/mcp/tools/README.md +27 -0
- crackerjack/mcp/tools/__init__.py +2 -0
- crackerjack/mcp/tools/core_tools.py +75 -52
- crackerjack/mcp/tools/execution_tools.py +87 -31
- crackerjack/mcp/tools/intelligence_tools.py +2 -2
- crackerjack/mcp/tools/proactive_tools.py +1 -1
- crackerjack/mcp/tools/semantic_tools.py +584 -0
- crackerjack/mcp/tools/utility_tools.py +180 -132
- crackerjack/mcp/tools/workflow_executor.py +87 -46
- crackerjack/mcp/websocket/README.md +31 -0
- crackerjack/mcp/websocket/app.py +11 -1
- crackerjack/mcp/websocket/event_bridge.py +188 -0
- crackerjack/mcp/websocket/jobs.py +27 -4
- crackerjack/mcp/websocket/monitoring/__init__.py +25 -0
- crackerjack/mcp/websocket/monitoring/api/__init__.py +19 -0
- crackerjack/mcp/websocket/monitoring/api/dependencies.py +141 -0
- crackerjack/mcp/websocket/monitoring/api/heatmap.py +154 -0
- crackerjack/mcp/websocket/monitoring/api/intelligence.py +199 -0
- crackerjack/mcp/websocket/monitoring/api/metrics.py +203 -0
- crackerjack/mcp/websocket/monitoring/api/telemetry.py +101 -0
- crackerjack/mcp/websocket/monitoring/dashboard.py +18 -0
- crackerjack/mcp/websocket/monitoring/factory.py +109 -0
- crackerjack/mcp/websocket/monitoring/filters.py +10 -0
- crackerjack/mcp/websocket/monitoring/metrics.py +64 -0
- crackerjack/mcp/websocket/monitoring/models.py +90 -0
- crackerjack/mcp/websocket/monitoring/utils.py +171 -0
- crackerjack/mcp/websocket/monitoring/websocket_manager.py +78 -0
- crackerjack/mcp/websocket/monitoring/websockets/__init__.py +17 -0
- crackerjack/mcp/websocket/monitoring/websockets/dependencies.py +126 -0
- crackerjack/mcp/websocket/monitoring/websockets/heatmap.py +176 -0
- crackerjack/mcp/websocket/monitoring/websockets/intelligence.py +291 -0
- crackerjack/mcp/websocket/monitoring/websockets/metrics.py +291 -0
- crackerjack/mcp/websocket/monitoring_endpoints.py +16 -2930
- crackerjack/mcp/websocket/server.py +1 -3
- crackerjack/mcp/websocket/websocket_handler.py +107 -6
- crackerjack/models/README.md +308 -0
- crackerjack/models/__init__.py +10 -1
- crackerjack/models/config.py +639 -22
- crackerjack/models/config_adapter.py +6 -6
- crackerjack/models/protocols.py +1167 -23
- crackerjack/models/pydantic_models.py +320 -0
- crackerjack/models/qa_config.py +145 -0
- crackerjack/models/qa_results.py +134 -0
- crackerjack/models/results.py +35 -0
- crackerjack/models/semantic_models.py +258 -0
- crackerjack/models/task.py +19 -3
- crackerjack/models/test_models.py +60 -0
- crackerjack/monitoring/README.md +11 -0
- crackerjack/monitoring/ai_agent_watchdog.py +5 -4
- crackerjack/monitoring/metrics_collector.py +4 -3
- crackerjack/monitoring/regression_prevention.py +4 -3
- crackerjack/monitoring/websocket_server.py +4 -241
- crackerjack/orchestration/README.md +340 -0
- crackerjack/orchestration/__init__.py +43 -0
- crackerjack/orchestration/advanced_orchestrator.py +20 -67
- crackerjack/orchestration/cache/README.md +312 -0
- crackerjack/orchestration/cache/__init__.py +37 -0
- crackerjack/orchestration/cache/memory_cache.py +338 -0
- crackerjack/orchestration/cache/tool_proxy_cache.py +340 -0
- crackerjack/orchestration/config.py +297 -0
- crackerjack/orchestration/coverage_improvement.py +13 -6
- crackerjack/orchestration/execution_strategies.py +6 -6
- crackerjack/orchestration/hook_orchestrator.py +1398 -0
- crackerjack/orchestration/strategies/README.md +401 -0
- crackerjack/orchestration/strategies/__init__.py +39 -0
- crackerjack/orchestration/strategies/adaptive_strategy.py +630 -0
- crackerjack/orchestration/strategies/parallel_strategy.py +237 -0
- crackerjack/orchestration/strategies/sequential_strategy.py +299 -0
- crackerjack/orchestration/test_progress_streamer.py +1 -1
- crackerjack/plugins/README.md +11 -0
- crackerjack/plugins/hooks.py +3 -2
- crackerjack/plugins/loader.py +3 -3
- crackerjack/plugins/managers.py +1 -1
- crackerjack/py313.py +191 -0
- crackerjack/security/README.md +11 -0
- crackerjack/services/README.md +374 -0
- crackerjack/services/__init__.py +8 -21
- crackerjack/services/ai/README.md +295 -0
- crackerjack/services/ai/__init__.py +7 -0
- crackerjack/services/ai/advanced_optimizer.py +878 -0
- crackerjack/services/{contextual_ai_assistant.py → ai/contextual_ai_assistant.py} +5 -3
- crackerjack/services/ai/embeddings.py +444 -0
- crackerjack/services/ai/intelligent_commit.py +328 -0
- crackerjack/services/ai/predictive_analytics.py +510 -0
- crackerjack/services/api_extractor.py +5 -3
- crackerjack/services/bounded_status_operations.py +45 -5
- crackerjack/services/cache.py +249 -318
- crackerjack/services/changelog_automation.py +7 -3
- crackerjack/services/command_execution_service.py +305 -0
- crackerjack/services/config_integrity.py +83 -39
- crackerjack/services/config_merge.py +9 -6
- crackerjack/services/config_service.py +198 -0
- crackerjack/services/config_template.py +13 -26
- crackerjack/services/coverage_badge_service.py +6 -4
- crackerjack/services/coverage_ratchet.py +53 -27
- crackerjack/services/debug.py +18 -7
- crackerjack/services/dependency_analyzer.py +4 -4
- crackerjack/services/dependency_monitor.py +13 -13
- crackerjack/services/documentation_generator.py +4 -2
- crackerjack/services/documentation_service.py +62 -33
- crackerjack/services/enhanced_filesystem.py +81 -27
- crackerjack/services/enterprise_optimizer.py +1 -1
- crackerjack/services/error_pattern_analyzer.py +10 -10
- crackerjack/services/file_filter.py +221 -0
- crackerjack/services/file_hasher.py +5 -7
- crackerjack/services/file_io_service.py +361 -0
- crackerjack/services/file_modifier.py +615 -0
- crackerjack/services/filesystem.py +80 -109
- crackerjack/services/git.py +99 -5
- crackerjack/services/health_metrics.py +4 -6
- crackerjack/services/heatmap_generator.py +12 -3
- crackerjack/services/incremental_executor.py +380 -0
- crackerjack/services/initialization.py +101 -49
- crackerjack/services/log_manager.py +2 -2
- crackerjack/services/logging.py +120 -68
- crackerjack/services/lsp_client.py +12 -12
- crackerjack/services/memory_optimizer.py +27 -22
- crackerjack/services/monitoring/README.md +30 -0
- crackerjack/services/monitoring/__init__.py +9 -0
- crackerjack/services/monitoring/dependency_monitor.py +678 -0
- crackerjack/services/monitoring/error_pattern_analyzer.py +676 -0
- crackerjack/services/monitoring/health_metrics.py +716 -0
- crackerjack/services/monitoring/metrics.py +587 -0
- crackerjack/services/{performance_benchmarks.py → monitoring/performance_benchmarks.py} +100 -14
- crackerjack/services/{performance_cache.py → monitoring/performance_cache.py} +21 -15
- crackerjack/services/{performance_monitor.py → monitoring/performance_monitor.py} +10 -6
- crackerjack/services/parallel_executor.py +166 -55
- crackerjack/services/patterns/__init__.py +142 -0
- crackerjack/services/patterns/agents.py +107 -0
- crackerjack/services/patterns/code/__init__.py +15 -0
- crackerjack/services/patterns/code/detection.py +118 -0
- crackerjack/services/patterns/code/imports.py +107 -0
- crackerjack/services/patterns/code/paths.py +159 -0
- crackerjack/services/patterns/code/performance.py +119 -0
- crackerjack/services/patterns/code/replacement.py +36 -0
- crackerjack/services/patterns/core.py +212 -0
- crackerjack/services/patterns/documentation/__init__.py +14 -0
- crackerjack/services/patterns/documentation/badges_markdown.py +96 -0
- crackerjack/services/patterns/documentation/comments_blocks.py +83 -0
- crackerjack/services/patterns/documentation/docstrings.py +89 -0
- crackerjack/services/patterns/formatting.py +226 -0
- crackerjack/services/patterns/operations.py +339 -0
- crackerjack/services/patterns/security/__init__.py +23 -0
- crackerjack/services/patterns/security/code_injection.py +122 -0
- crackerjack/services/patterns/security/credentials.py +190 -0
- crackerjack/services/patterns/security/path_traversal.py +221 -0
- crackerjack/services/patterns/security/unsafe_operations.py +216 -0
- crackerjack/services/patterns/templates.py +62 -0
- crackerjack/services/patterns/testing/__init__.py +18 -0
- crackerjack/services/patterns/testing/error_patterns.py +107 -0
- crackerjack/services/patterns/testing/pytest_output.py +126 -0
- crackerjack/services/patterns/tool_output/__init__.py +16 -0
- crackerjack/services/patterns/tool_output/bandit.py +72 -0
- crackerjack/services/patterns/tool_output/other.py +97 -0
- crackerjack/services/patterns/tool_output/pyright.py +67 -0
- crackerjack/services/patterns/tool_output/ruff.py +44 -0
- crackerjack/services/patterns/url_sanitization.py +114 -0
- crackerjack/services/patterns/utilities.py +42 -0
- crackerjack/services/patterns/utils.py +339 -0
- crackerjack/services/patterns/validation.py +46 -0
- crackerjack/services/patterns/versioning.py +62 -0
- crackerjack/services/predictive_analytics.py +21 -8
- crackerjack/services/profiler.py +280 -0
- crackerjack/services/quality/README.md +415 -0
- crackerjack/services/quality/__init__.py +11 -0
- crackerjack/services/quality/anomaly_detector.py +392 -0
- crackerjack/services/quality/pattern_cache.py +333 -0
- crackerjack/services/quality/pattern_detector.py +479 -0
- crackerjack/services/quality/qa_orchestrator.py +491 -0
- crackerjack/services/{quality_baseline.py → quality/quality_baseline.py} +163 -2
- crackerjack/services/{quality_baseline_enhanced.py → quality/quality_baseline_enhanced.py} +4 -1
- crackerjack/services/{quality_intelligence.py → quality/quality_intelligence.py} +180 -16
- crackerjack/services/regex_patterns.py +58 -2987
- crackerjack/services/regex_utils.py +55 -29
- crackerjack/services/secure_status_formatter.py +42 -15
- crackerjack/services/secure_subprocess.py +35 -2
- crackerjack/services/security.py +16 -8
- crackerjack/services/server_manager.py +40 -51
- crackerjack/services/smart_scheduling.py +46 -6
- crackerjack/services/status_authentication.py +3 -3
- crackerjack/services/thread_safe_status_collector.py +1 -0
- crackerjack/services/tool_filter.py +368 -0
- crackerjack/services/tool_version_service.py +9 -5
- crackerjack/services/unified_config.py +43 -351
- crackerjack/services/vector_store.py +689 -0
- crackerjack/services/version_analyzer.py +6 -4
- crackerjack/services/version_checker.py +14 -8
- crackerjack/services/zuban_lsp_service.py +5 -4
- crackerjack/slash_commands/README.md +11 -0
- crackerjack/slash_commands/init.md +2 -12
- crackerjack/slash_commands/run.md +84 -50
- crackerjack/tools/README.md +11 -0
- crackerjack/tools/__init__.py +30 -0
- crackerjack/tools/_git_utils.py +105 -0
- crackerjack/tools/check_added_large_files.py +139 -0
- crackerjack/tools/check_ast.py +105 -0
- crackerjack/tools/check_json.py +103 -0
- crackerjack/tools/check_jsonschema.py +297 -0
- crackerjack/tools/check_toml.py +103 -0
- crackerjack/tools/check_yaml.py +110 -0
- crackerjack/tools/codespell_wrapper.py +72 -0
- crackerjack/tools/end_of_file_fixer.py +202 -0
- crackerjack/tools/format_json.py +128 -0
- crackerjack/tools/mdformat_wrapper.py +114 -0
- crackerjack/tools/trailing_whitespace.py +198 -0
- crackerjack/tools/validate_regex_patterns.py +7 -3
- crackerjack/ui/README.md +11 -0
- crackerjack/ui/dashboard_renderer.py +28 -0
- crackerjack/ui/templates/README.md +11 -0
- crackerjack/utils/console_utils.py +13 -0
- crackerjack/utils/dependency_guard.py +230 -0
- crackerjack/utils/retry_utils.py +275 -0
- crackerjack/workflows/README.md +590 -0
- crackerjack/workflows/__init__.py +46 -0
- crackerjack/workflows/actions.py +811 -0
- crackerjack/workflows/auto_fix.py +444 -0
- crackerjack/workflows/container_builder.py +499 -0
- crackerjack/workflows/definitions.py +443 -0
- crackerjack/workflows/engine.py +177 -0
- crackerjack/workflows/event_bridge.py +242 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/METADATA +678 -98
- crackerjack-0.45.2.dist-info/RECORD +478 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/WHEEL +1 -1
- crackerjack/managers/test_manager_backup.py +0 -1075
- crackerjack/mcp/tools/execution_tools_backup.py +0 -1011
- crackerjack/mixins/__init__.py +0 -3
- crackerjack/mixins/error_handling.py +0 -145
- crackerjack/services/config.py +0 -358
- crackerjack/ui/server_panels.py +0 -125
- crackerjack-0.37.9.dist-info/RECORD +0 -231
- /crackerjack/adapters/{rust_tool_adapter.py → lsp/_base.py} +0 -0
- /crackerjack/adapters/{lsp_client.py → lsp/_client.py} +0 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.37.9.dist-info → crackerjack-0.45.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,34 +3,44 @@ import typing as t
|
|
|
3
3
|
from contextlib import suppress
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
-
from
|
|
6
|
+
from acb.console import Console
|
|
7
|
+
from acb.depends import Inject, depends
|
|
7
8
|
|
|
8
|
-
from crackerjack.
|
|
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
|
+
)
|
|
9
18
|
|
|
10
19
|
|
|
11
20
|
class PublishManagerImpl:
|
|
21
|
+
@depends.inject # type: ignore[misc]
|
|
12
22
|
def __init__(
|
|
13
23
|
self,
|
|
14
|
-
|
|
15
|
-
|
|
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],
|
|
16
32
|
dry_run: bool = False,
|
|
17
|
-
filesystem: FileSystemInterface | None = None,
|
|
18
|
-
security: SecurityServiceProtocol | None = None,
|
|
19
33
|
) -> None:
|
|
34
|
+
# Foundation dependencies
|
|
20
35
|
self.console = console
|
|
21
36
|
self.pkg_path = pkg_path
|
|
22
37
|
self.dry_run = dry_run
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if security is None:
|
|
30
|
-
from crackerjack.services.security import SecurityService
|
|
31
|
-
|
|
32
|
-
security = SecurityService()
|
|
33
|
-
|
|
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
|
|
34
44
|
self.filesystem = filesystem
|
|
35
45
|
self.security = security
|
|
36
46
|
|
|
@@ -77,9 +87,20 @@ class PublishManagerImpl:
|
|
|
77
87
|
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
78
88
|
try:
|
|
79
89
|
content = self.filesystem.read_file(pyproject_path)
|
|
80
|
-
from crackerjack.services.regex_patterns import update_pyproject_version
|
|
81
90
|
|
|
82
|
-
|
|
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)
|
|
83
104
|
if content != new_content:
|
|
84
105
|
if not self.dry_run:
|
|
85
106
|
self.filesystem.write_file(pyproject_path, new_content)
|
|
@@ -156,7 +177,7 @@ class PublishManagerImpl:
|
|
|
156
177
|
self.console.print(f"[red]❌[/ red] Version bump failed: {e}")
|
|
157
178
|
raise
|
|
158
179
|
|
|
159
|
-
def _prompt_for_version_type(self, recommendation=None) -> str:
|
|
180
|
+
def _prompt_for_version_type(self, recommendation: t.Any = None) -> str:
|
|
160
181
|
try:
|
|
161
182
|
from rich.prompt import Prompt
|
|
162
183
|
|
|
@@ -178,17 +199,13 @@ class PublishManagerImpl:
|
|
|
178
199
|
)
|
|
179
200
|
return "patch"
|
|
180
201
|
|
|
181
|
-
def _get_version_recommendation(self):
|
|
202
|
+
def _get_version_recommendation(self) -> t.Any:
|
|
182
203
|
"""Get AI-powered version bump recommendation based on git history."""
|
|
183
204
|
try:
|
|
184
205
|
import asyncio
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# Initialize services
|
|
190
|
-
git_service = GitService(self.console, self.pkg_path)
|
|
191
|
-
version_analyzer = VersionAnalyzer(self.console, git_service)
|
|
207
|
+
# Use injected version analyzer service
|
|
208
|
+
version_analyzer = self._version_analyzer
|
|
192
209
|
|
|
193
210
|
# Get recommendation asynchronously
|
|
194
211
|
try:
|
|
@@ -217,7 +234,7 @@ class PublishManagerImpl:
|
|
|
217
234
|
self.console.print(f"[yellow]⚠️[/yellow] Version analysis failed: {e}")
|
|
218
235
|
return None
|
|
219
236
|
|
|
220
|
-
def _display_version_analysis(self, recommendation):
|
|
237
|
+
def _display_version_analysis(self, recommendation: t.Any) -> None:
|
|
221
238
|
"""Display version analysis in a compact format."""
|
|
222
239
|
if not recommendation:
|
|
223
240
|
return
|
|
@@ -323,7 +340,7 @@ class PublishManagerImpl:
|
|
|
323
340
|
|
|
324
341
|
def build_package(self) -> bool:
|
|
325
342
|
try:
|
|
326
|
-
self.console.print("[yellow]🔨[/ yellow] Building package
|
|
343
|
+
self.console.print("[yellow]🔨[/ yellow] Building package")
|
|
327
344
|
|
|
328
345
|
if self.dry_run:
|
|
329
346
|
return self._handle_dry_run_build()
|
|
@@ -390,8 +407,8 @@ class PublishManagerImpl:
|
|
|
390
407
|
return False
|
|
391
408
|
|
|
392
409
|
try:
|
|
393
|
-
self.console.print("[yellow]🚀[/ yellow] Publishing to PyPI
|
|
394
|
-
return self.
|
|
410
|
+
self.console.print("[yellow]🚀[/ yellow] Publishing to PyPI")
|
|
411
|
+
return self._perform_publish_workflow_with_retry()
|
|
395
412
|
except Exception as e:
|
|
396
413
|
self.console.print(f"[red]❌[/ red] Publish error: {e}")
|
|
397
414
|
return False
|
|
@@ -408,6 +425,17 @@ class PublishManagerImpl:
|
|
|
408
425
|
|
|
409
426
|
return self._execute_publish()
|
|
410
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
|
+
|
|
411
439
|
def _handle_dry_run_publish(self) -> bool:
|
|
412
440
|
self.console.print("[yellow]🔍[/ yellow] Would publish package to PyPI")
|
|
413
441
|
return True
|
|
@@ -501,7 +529,28 @@ class PublishManagerImpl:
|
|
|
501
529
|
self.console.print(f"[red]❌[/ red] Cleanup error: {e}")
|
|
502
530
|
return False
|
|
503
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
|
+
|
|
504
552
|
def create_git_tag(self, version: str) -> bool:
|
|
553
|
+
"""Create git tag and push it immediately (legacy method for standalone use)."""
|
|
505
554
|
try:
|
|
506
555
|
if self.dry_run:
|
|
507
556
|
self.console.print(
|
|
@@ -556,12 +605,8 @@ class PublishManagerImpl:
|
|
|
556
605
|
def _update_changelog_for_version(self, old_version: str, new_version: str) -> None:
|
|
557
606
|
"""Update changelog with entries from git commits since last version."""
|
|
558
607
|
try:
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
# Initialize services
|
|
563
|
-
git_service = GitService(self.console, self.pkg_path)
|
|
564
|
-
changelog_generator = ChangelogGenerator(self.console, git_service)
|
|
608
|
+
# Use injected changelog generator service
|
|
609
|
+
changelog_generator = self._changelog_generator
|
|
565
610
|
|
|
566
611
|
# Look for changelog file
|
|
567
612
|
changelog_path = self.pkg_path / "CHANGELOG.md"
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
4
|
+
import psutil
|
|
5
|
+
from acb.console import Console
|
|
6
|
+
from acb.depends import Inject, depends
|
|
7
|
+
|
|
8
|
+
from crackerjack.config.settings import CrackerjackSettings
|
|
3
9
|
from crackerjack.models.protocols import OptionsProtocol
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
class TestCommandBuilder:
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
@depends.inject
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
pkg_path: Inject[Path],
|
|
17
|
+
console: Inject[Console],
|
|
18
|
+
settings: Inject[CrackerjackSettings],
|
|
19
|
+
) -> None:
|
|
20
|
+
# Normalize to pathlib.Path to avoid async path methods
|
|
21
|
+
try:
|
|
22
|
+
self.pkg_path = Path(str(pkg_path))
|
|
23
|
+
except Exception:
|
|
24
|
+
self.pkg_path = Path(pkg_path)
|
|
25
|
+
|
|
26
|
+
self.console = console
|
|
27
|
+
self.settings = settings
|
|
9
28
|
|
|
10
29
|
def build_command(self, options: OptionsProtocol) -> list[str]:
|
|
11
30
|
cmd = ["uv", "run", "python", "-m", "pytest"]
|
|
@@ -19,11 +38,185 @@ class TestCommandBuilder:
|
|
|
19
38
|
|
|
20
39
|
return cmd
|
|
21
40
|
|
|
22
|
-
def
|
|
23
|
-
|
|
41
|
+
def _handle_not_implemented_error(self, print_info: bool) -> int:
|
|
42
|
+
"""Handle NotImplementedError specifically."""
|
|
43
|
+
if print_info and self.console:
|
|
44
|
+
self.console.print(
|
|
45
|
+
"[yellow]⚠️ CPU detection unavailable, using 2 workers[/yellow]"
|
|
46
|
+
)
|
|
47
|
+
return 2
|
|
48
|
+
|
|
49
|
+
def _handle_general_error(self, print_info: bool, e: Exception) -> int:
|
|
50
|
+
"""Handle general exceptions gracefully."""
|
|
51
|
+
if print_info and self.console:
|
|
52
|
+
self.console.print(
|
|
53
|
+
f"[yellow]⚠️ Worker detection failed: {e}. Using 2 workers.[/yellow]"
|
|
54
|
+
)
|
|
55
|
+
return 2
|
|
56
|
+
|
|
57
|
+
def get_optimal_workers(
|
|
58
|
+
self, options: OptionsProtocol, print_info: bool = True
|
|
59
|
+
) -> int | str:
|
|
60
|
+
"""Calculate optimal worker count using pytest-xdist.
|
|
61
|
+
|
|
62
|
+
This method leverages pytest-xdist's built-in '-n auto' for CPU detection
|
|
63
|
+
while adding memory safety checks and support for custom worker configurations.
|
|
64
|
+
|
|
65
|
+
Worker Selection Logic:
|
|
66
|
+
----------------------
|
|
67
|
+
1. Emergency rollback: CRACKERJACK_DISABLE_AUTO_WORKERS=1 → 1 worker
|
|
68
|
+
2. Explicit value (test_workers > 0): Use as-is
|
|
69
|
+
3. Auto-detect (test_workers = 0 AND auto_detect_workers=True): Return "auto"
|
|
70
|
+
4. Legacy mode (test_workers = 0 AND auto_detect_workers=False): 1 worker
|
|
71
|
+
5. Fractional (test_workers < 0): Divide CPU count by abs(value)
|
|
72
|
+
|
|
73
|
+
Safety Bounds:
|
|
74
|
+
-------------
|
|
75
|
+
- Minimum: 1 worker (sequential execution)
|
|
76
|
+
- Maximum: 8 workers (configurable via settings.testing.max_workers)
|
|
77
|
+
- Memory: 2GB per worker minimum (configurable)
|
|
78
|
+
|
|
79
|
+
Examples:
|
|
80
|
+
--------
|
|
81
|
+
>>> options = Options(test_workers=4)
|
|
82
|
+
>>> builder.get_optimal_workers(options)
|
|
83
|
+
4 # Explicit value
|
|
84
|
+
|
|
85
|
+
>>> options = Options(test_workers=0) # auto_detect_workers=True
|
|
86
|
+
>>> builder.get_optimal_workers(options)
|
|
87
|
+
"auto" # Delegates to pytest-xdist
|
|
88
|
+
|
|
89
|
+
>>> options = Options(test_workers=-2) # 8-core machine
|
|
90
|
+
>>> builder.get_optimal_workers(options)
|
|
91
|
+
4 # 8 // 2 = 4 (with memory safety check)
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
int | str: Number of workers (1-8) or "auto" for pytest-xdist detection
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
Never raises - returns safe default (2) on any error
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
# Check for emergency rollback
|
|
101
|
+
if self._check_emergency_rollback(print_info):
|
|
102
|
+
return 1
|
|
103
|
+
|
|
104
|
+
# Check explicit worker count
|
|
105
|
+
explicit_result = self._check_explicit_workers(options, print_info)
|
|
106
|
+
if explicit_result is not None:
|
|
107
|
+
return explicit_result
|
|
108
|
+
|
|
109
|
+
# Check auto-detection
|
|
110
|
+
auto_result = self._check_auto_detection(options, print_info)
|
|
111
|
+
if auto_result is not None:
|
|
112
|
+
return auto_result
|
|
113
|
+
|
|
114
|
+
# Check fractional workers
|
|
115
|
+
fractional_result = self._check_fractional_workers(options, print_info)
|
|
116
|
+
if fractional_result is not None:
|
|
117
|
+
return fractional_result
|
|
118
|
+
|
|
119
|
+
# Safe default if no conditions match
|
|
120
|
+
return 2
|
|
121
|
+
|
|
122
|
+
except NotImplementedError:
|
|
123
|
+
return self._handle_not_implemented_error(print_info)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
return self._handle_general_error(print_info, e)
|
|
126
|
+
|
|
127
|
+
def _check_emergency_rollback(self, print_info: bool) -> bool:
|
|
128
|
+
"""Check for emergency rollback via environment variable."""
|
|
129
|
+
if os.getenv("CRACKERJACK_DISABLE_AUTO_WORKERS") == "1":
|
|
130
|
+
if print_info and self.console:
|
|
131
|
+
self.console.print(
|
|
132
|
+
"[yellow]⚠️ Auto-detection disabled via environment variable[/yellow]"
|
|
133
|
+
)
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def _check_explicit_workers(
|
|
138
|
+
self, options: OptionsProtocol, print_info: bool
|
|
139
|
+
) -> int | None:
|
|
140
|
+
"""Check for explicit worker count."""
|
|
141
|
+
if hasattr(options, "test_workers") and options.test_workers > 0:
|
|
24
142
|
return options.test_workers
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
def _check_auto_detection(
|
|
146
|
+
self, options: OptionsProtocol, print_info: bool
|
|
147
|
+
) -> str | int | None:
|
|
148
|
+
"""Check for auto-detection setting."""
|
|
149
|
+
if hasattr(options, "test_workers") and options.test_workers == 0:
|
|
150
|
+
# Check if auto-detection is enabled in settings
|
|
151
|
+
if self.settings and self.settings.testing.auto_detect_workers:
|
|
152
|
+
# Show message only when getting optimal workers with print_info=True
|
|
153
|
+
if print_info and self.console:
|
|
154
|
+
self.console.print(
|
|
155
|
+
"[cyan]🔧 Using pytest-xdist auto-detection for workers[/cyan]"
|
|
156
|
+
)
|
|
157
|
+
return "auto" # pytest-xdist will handle CPU detection
|
|
158
|
+
|
|
159
|
+
# Legacy behavior: auto_detect_workers=False
|
|
160
|
+
return 1
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
def _check_fractional_workers(
|
|
164
|
+
self, options: OptionsProtocol, print_info: bool
|
|
165
|
+
) -> int | None:
|
|
166
|
+
"""Check for fractional worker setting."""
|
|
167
|
+
if hasattr(options, "test_workers") and options.test_workers < 0:
|
|
168
|
+
import multiprocessing
|
|
169
|
+
|
|
170
|
+
cpu_count = multiprocessing.cpu_count()
|
|
171
|
+
divisor = abs(options.test_workers)
|
|
172
|
+
workers = max(1, cpu_count // divisor)
|
|
25
173
|
|
|
26
|
-
|
|
174
|
+
# Apply memory safety check
|
|
175
|
+
workers = self._apply_memory_limit(workers)
|
|
176
|
+
|
|
177
|
+
if print_info and self.console:
|
|
178
|
+
self.console.print(
|
|
179
|
+
f"[cyan]🔧 Fractional workers: {cpu_count} cores ÷ {divisor} = {workers} workers[/cyan]"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return workers
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def _apply_memory_limit(self, workers: int) -> int:
|
|
186
|
+
"""Limit workers based on available memory to prevent OOM.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
workers: Desired number of workers
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
int: Worker count limited by available memory
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
# Get memory threshold from settings (default 2GB per worker)
|
|
196
|
+
memory_per_worker = (
|
|
197
|
+
self.settings.testing.memory_per_worker_gb if self.settings else 2.0
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Calculate available memory in GB
|
|
201
|
+
available_gb = psutil.virtual_memory().available / (1024**3)
|
|
202
|
+
|
|
203
|
+
# Calculate max workers based on memory
|
|
204
|
+
max_by_memory = max(1, int(available_gb / memory_per_worker))
|
|
205
|
+
|
|
206
|
+
# Return the minimum of desired workers and memory-limited workers
|
|
207
|
+
limited_workers = min(workers, max_by_memory)
|
|
208
|
+
|
|
209
|
+
# Log if we're limiting due to memory
|
|
210
|
+
if limited_workers < workers and self.console:
|
|
211
|
+
self.console.print(
|
|
212
|
+
f"[yellow]⚠️ Limited to {limited_workers} workers (available memory: {available_gb:.1f}GB)[/yellow]"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return limited_workers
|
|
216
|
+
|
|
217
|
+
except Exception:
|
|
218
|
+
# Conservative fallback if psutil fails
|
|
219
|
+
return min(workers, 4)
|
|
27
220
|
|
|
28
221
|
def get_test_timeout(self, options: OptionsProtocol) -> int:
|
|
29
222
|
if hasattr(options, "test_timeout") and options.test_timeout:
|
|
@@ -33,20 +226,86 @@ class TestCommandBuilder:
|
|
|
33
226
|
return 900
|
|
34
227
|
return 300
|
|
35
228
|
|
|
229
|
+
def _detect_package_name(self) -> str:
|
|
230
|
+
"""Detect the main package name for coverage reporting."""
|
|
231
|
+
# Method 1: Try to read from pyproject.toml
|
|
232
|
+
pyproject_path = self.pkg_path / "pyproject.toml"
|
|
233
|
+
if pyproject_path.exists():
|
|
234
|
+
from contextlib import suppress
|
|
235
|
+
|
|
236
|
+
with suppress(Exception):
|
|
237
|
+
import tomllib
|
|
238
|
+
|
|
239
|
+
with pyproject_path.open("rb") as f:
|
|
240
|
+
data = tomllib.load(f)
|
|
241
|
+
project_name = data.get("project", {}).get("name")
|
|
242
|
+
if project_name:
|
|
243
|
+
# Convert project name to package name (hyphens to underscores)
|
|
244
|
+
return project_name.replace("-", "_")
|
|
245
|
+
# Fall back to directory detection
|
|
246
|
+
|
|
247
|
+
# Method 2: Look for Python packages in the project root
|
|
248
|
+
for item in self.pkg_path.iterdir():
|
|
249
|
+
if (
|
|
250
|
+
item.is_dir()
|
|
251
|
+
and not item.name.startswith(".")
|
|
252
|
+
and item.name not in ("tests", "docs", "build", "dist", "__pycache__")
|
|
253
|
+
and (item / "__init__.py").exists()
|
|
254
|
+
):
|
|
255
|
+
return item.name
|
|
256
|
+
|
|
257
|
+
# Method 3: Fallback to crackerjack if nothing found (for crackerjack itself)
|
|
258
|
+
return "crackerjack"
|
|
259
|
+
|
|
36
260
|
def _add_coverage_options(self, cmd: list[str], options: OptionsProtocol) -> None:
|
|
261
|
+
# Determine package name from project structure
|
|
262
|
+
package_name = self._detect_package_name()
|
|
263
|
+
|
|
37
264
|
cmd.extend(
|
|
38
265
|
[
|
|
39
|
-
"--cov=
|
|
266
|
+
f"--cov={package_name}",
|
|
40
267
|
"--cov-report=term-missing",
|
|
41
268
|
"--cov-report=html",
|
|
269
|
+
"--cov-report=json", # Required for badge updates
|
|
42
270
|
"--cov-fail-under=0",
|
|
43
271
|
]
|
|
44
272
|
)
|
|
45
273
|
|
|
46
274
|
def _add_worker_options(self, cmd: list[str], options: OptionsProtocol) -> None:
|
|
275
|
+
"""Add pytest-xdist worker options with proper distribution strategy.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
cmd: Command list to append worker options to
|
|
279
|
+
options: Test options containing worker configuration
|
|
280
|
+
"""
|
|
47
281
|
workers = self.get_optimal_workers(options)
|
|
48
|
-
|
|
49
|
-
|
|
282
|
+
|
|
283
|
+
# Skip benchmarks for parallelization (results get skewed)
|
|
284
|
+
if hasattr(options, "benchmark") and options.benchmark:
|
|
285
|
+
if self.console:
|
|
286
|
+
self.console.print(
|
|
287
|
+
"[yellow]⚠️ Benchmarks running sequentially (parallel execution skews results)[/yellow]"
|
|
288
|
+
)
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
if workers == "auto":
|
|
292
|
+
# Use pytest-xdist's native auto-detection
|
|
293
|
+
cmd.extend(["-n", "auto", "--dist=loadfile"])
|
|
294
|
+
if self.console:
|
|
295
|
+
self.console.print(
|
|
296
|
+
"[cyan]🚀 Tests running with auto-detected workers (--dist=loadfile)[/cyan]"
|
|
297
|
+
)
|
|
298
|
+
elif isinstance(workers, int) and workers > 1:
|
|
299
|
+
# Explicit worker count
|
|
300
|
+
cmd.extend(["-n", str(workers), "--dist=loadfile"])
|
|
301
|
+
if self.console:
|
|
302
|
+
self.console.print(
|
|
303
|
+
f"[cyan]🚀 Tests running with {workers} workers (--dist=loadfile)[/cyan]"
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
# Sequential execution (workers == 1)
|
|
307
|
+
if self.console:
|
|
308
|
+
self.console.print("[cyan]🧪 Tests running sequentially[/cyan]")
|
|
50
309
|
|
|
51
310
|
def _add_benchmark_options(self, cmd: list[str], options: OptionsProtocol) -> None:
|
|
52
311
|
if hasattr(options, "benchmark") and options.benchmark:
|
|
@@ -63,11 +322,30 @@ class TestCommandBuilder:
|
|
|
63
322
|
cmd.extend(["--timeout", str(timeout)])
|
|
64
323
|
|
|
65
324
|
def _add_verbosity_options(self, cmd: list[str], options: OptionsProtocol) -> None:
|
|
66
|
-
|
|
325
|
+
"""Add verbosity options with enhanced detail levels.
|
|
326
|
+
|
|
327
|
+
Verbosity Levels:
|
|
328
|
+
- Standard (-v): Basic test names
|
|
329
|
+
- Verbose (-vv): Assertions, captured output, test details
|
|
330
|
+
- Extra verbose (-vvv): Full locals, all test internals (ai_debug mode)
|
|
331
|
+
"""
|
|
332
|
+
# Determine verbosity level
|
|
333
|
+
if options.verbose:
|
|
334
|
+
if getattr(options, "ai_debug", False):
|
|
335
|
+
cmd.append("-vvv") # Extra verbose for AI debugging
|
|
336
|
+
self.console.print("[cyan]🔍 Using extra verbose mode (-vvv)[/cyan]")
|
|
337
|
+
else:
|
|
338
|
+
cmd.append("-vv") # Double verbose shows more context
|
|
339
|
+
self.console.print("[cyan]🔍 Using verbose mode (-vv)[/cyan]")
|
|
340
|
+
else:
|
|
341
|
+
cmd.append("-v") # Standard verbose
|
|
67
342
|
|
|
68
343
|
cmd.extend(
|
|
69
344
|
[
|
|
70
|
-
|
|
345
|
+
# Longer tracebacks in verbose mode
|
|
346
|
+
"--tb=long" if options.verbose else "--tb=short",
|
|
347
|
+
# Show all test outcomes summary (not just failures)
|
|
348
|
+
"-ra",
|
|
71
349
|
"--strict-markers",
|
|
72
350
|
"--strict-config",
|
|
73
351
|
]
|
|
@@ -85,7 +363,7 @@ class TestCommandBuilder:
|
|
|
85
363
|
cmd.append(str(self.pkg_path))
|
|
86
364
|
|
|
87
365
|
def build_specific_test_command(self, test_pattern: str) -> list[str]:
|
|
88
|
-
cmd = ["uv", "run", "python", "-m", "pytest", "-v"]
|
|
366
|
+
cmd = ["uv", "run", "python", "-m", "pytest", "-v"] # Always use verbose mode
|
|
89
367
|
|
|
90
368
|
cmd.extend(
|
|
91
369
|
[
|
|
@@ -108,6 +386,6 @@ class TestCommandBuilder:
|
|
|
108
386
|
"-m",
|
|
109
387
|
"pytest",
|
|
110
388
|
"--collect-only",
|
|
111
|
-
|
|
389
|
+
# Removed --quiet to ensure collection headers are visible
|
|
112
390
|
"tests" if (self.pkg_path / "tests").exists() else ".",
|
|
113
391
|
]
|