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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Validate TOML file syntax.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
check-toml hook. It scans TOML files and validates their syntax.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.check_toml [files...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
0: All TOML files are valid
|
|
11
|
+
1: One or more TOML files have syntax errors
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
import tomllib
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ._git_utils import get_files_by_extension
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate_toml_file(file_path: Path) -> tuple[bool, str | None]:
|
|
25
|
+
"""Validate a TOML file's syntax.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
file_path: Path to TOML file to validate
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (is_valid, error_message)
|
|
32
|
+
- is_valid: True if file has valid TOML syntax
|
|
33
|
+
- error_message: Error description if is_valid is False, None otherwise
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
with file_path.open("rb") as f:
|
|
37
|
+
# Load TOML and validate structure
|
|
38
|
+
tomllib.load(f)
|
|
39
|
+
return True, None
|
|
40
|
+
except tomllib.TOMLDecodeError as e:
|
|
41
|
+
return False, str(e)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
return False, f"Error reading file: {e}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main(argv: list[str] | None = None) -> int:
|
|
47
|
+
"""Main entry point for check-toml tool.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Exit code: 0 if all TOML valid, 1 if any errors found
|
|
54
|
+
"""
|
|
55
|
+
parser = argparse.ArgumentParser(description="Validate TOML file syntax")
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"files",
|
|
58
|
+
nargs="*",
|
|
59
|
+
type=Path,
|
|
60
|
+
help="TOML files to check (default: all .toml files)",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
args = parser.parse_args(argv)
|
|
64
|
+
|
|
65
|
+
# Default to all git-tracked TOML files if none specified
|
|
66
|
+
if not args.files:
|
|
67
|
+
# Get all tracked TOML files (respects .gitignore via git ls-files)
|
|
68
|
+
files = get_files_by_extension([".toml"])
|
|
69
|
+
if not files:
|
|
70
|
+
# Fallback to rglob if not in git repo
|
|
71
|
+
files = list(Path.cwd().rglob("*.toml"))
|
|
72
|
+
else:
|
|
73
|
+
files = args.files
|
|
74
|
+
|
|
75
|
+
# Filter to existing files only
|
|
76
|
+
files = [f for f in files if f.is_file()]
|
|
77
|
+
|
|
78
|
+
if not files:
|
|
79
|
+
print("No TOML files to check") # noqa: T201
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
# Process files
|
|
83
|
+
error_count = 0
|
|
84
|
+
for file_path in files:
|
|
85
|
+
is_valid, error_msg = validate_toml_file(file_path)
|
|
86
|
+
|
|
87
|
+
if not is_valid:
|
|
88
|
+
print(f"✗ {file_path}: {error_msg}", file=sys.stderr) # noqa: T201
|
|
89
|
+
error_count += 1
|
|
90
|
+
else:
|
|
91
|
+
print(f"✓ {file_path}: Valid TOML") # noqa: T201
|
|
92
|
+
|
|
93
|
+
# Return appropriate exit code
|
|
94
|
+
if error_count > 0:
|
|
95
|
+
print(f"\n{error_count} TOML file(s) with errors", file=sys.stderr) # noqa: T201
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
print(f"\nAll {len(files)} TOML file(s) are valid") # noqa: T201
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
sys.exit(main())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Validate YAML file syntax.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
check-yaml hook. It scans YAML files and validates their syntax.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.check_yaml [files...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
0: All YAML files are valid
|
|
11
|
+
1: One or more YAML files have syntax errors
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
from ._git_utils import get_files_by_extension
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def validate_yaml_file(file_path: Path) -> tuple[bool, str | None]:
|
|
26
|
+
"""Validate a YAML file's syntax.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
file_path: Path to YAML file to validate
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Tuple of (is_valid, error_message)
|
|
33
|
+
- is_valid: True if file has valid YAML syntax
|
|
34
|
+
- error_message: Error description if is_valid is False, None otherwise
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
with file_path.open(encoding="utf-8") as f:
|
|
38
|
+
# Load YAML and validate structure
|
|
39
|
+
yaml.safe_load(f)
|
|
40
|
+
return True, None
|
|
41
|
+
except yaml.YAMLError as e:
|
|
42
|
+
return False, str(e)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return False, f"Error reading file: {e}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def main(argv: list[str] | None = None) -> int:
|
|
48
|
+
"""Main entry point for check-yaml tool.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Exit code: 0 if all YAML valid, 1 if any errors found
|
|
55
|
+
"""
|
|
56
|
+
parser = argparse.ArgumentParser(description="Validate YAML file syntax")
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"files",
|
|
59
|
+
nargs="*",
|
|
60
|
+
type=Path,
|
|
61
|
+
help="YAML files to check (default: all .yaml/.yml files)",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--unsafe",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Use unsafe YAML loader (allows Python object instantiation)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
args = parser.parse_args(argv)
|
|
70
|
+
|
|
71
|
+
# Default to all git-tracked YAML files if none specified
|
|
72
|
+
if not args.files:
|
|
73
|
+
# Get all tracked YAML files (respects .gitignore via git ls-files)
|
|
74
|
+
files = get_files_by_extension([".yaml", ".yml"])
|
|
75
|
+
if not files:
|
|
76
|
+
# Fallback to rglob if not in git repo
|
|
77
|
+
files = list(Path.cwd().rglob("*.yaml"))
|
|
78
|
+
files.extend(Path.cwd().rglob("*.yml"))
|
|
79
|
+
else:
|
|
80
|
+
files = args.files
|
|
81
|
+
|
|
82
|
+
# Filter to existing files only
|
|
83
|
+
files = [f for f in files if f.is_file()]
|
|
84
|
+
|
|
85
|
+
if not files:
|
|
86
|
+
print("No YAML files to check") # noqa: T201
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
# Process files
|
|
90
|
+
error_count = 0
|
|
91
|
+
for file_path in files:
|
|
92
|
+
is_valid, error_msg = validate_yaml_file(file_path)
|
|
93
|
+
|
|
94
|
+
if not is_valid:
|
|
95
|
+
print(f"✗ {file_path}: {error_msg}", file=sys.stderr) # noqa: T201
|
|
96
|
+
error_count += 1
|
|
97
|
+
else:
|
|
98
|
+
print(f"✓ {file_path}: Valid YAML") # noqa: T201
|
|
99
|
+
|
|
100
|
+
# Return appropriate exit code
|
|
101
|
+
if error_count > 0:
|
|
102
|
+
print(f"\n{error_count} YAML file(s) with errors", file=sys.stderr) # noqa: T201
|
|
103
|
+
return 1
|
|
104
|
+
|
|
105
|
+
print(f"\nAll {len(files)} YAML file(s) are valid") # noqa: T201
|
|
106
|
+
return 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Git-aware wrapper for codespell.
|
|
2
|
+
|
|
3
|
+
This wrapper ensures codespell only checks git-tracked files, automatically
|
|
4
|
+
respecting .gitignore patterns without needing manual skip configuration.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.codespell_wrapper [codespell args...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
Same as codespell (0 = no issues, 1 = issues found, etc.)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from ._git_utils import get_git_tracked_files
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(argv: list[str] | None = None) -> int:
|
|
23
|
+
"""Run codespell on git-tracked files only.
|
|
24
|
+
|
|
25
|
+
This wrapper automatically discovers git-tracked files and passes them
|
|
26
|
+
to codespell, ensuring .gitignore patterns are respected without manual
|
|
27
|
+
skip configuration.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
argv: Optional arguments to pass to codespell
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Exit code from codespell command
|
|
34
|
+
"""
|
|
35
|
+
# Get all git-tracked files (automatically respects .gitignore)
|
|
36
|
+
files = get_git_tracked_files()
|
|
37
|
+
|
|
38
|
+
if not files:
|
|
39
|
+
print("No git-tracked files found", file=sys.stderr) # noqa: T201
|
|
40
|
+
return 1
|
|
41
|
+
|
|
42
|
+
# Build codespell command with git-tracked files
|
|
43
|
+
cmd = ["codespell"]
|
|
44
|
+
|
|
45
|
+
# Add any additional arguments passed to wrapper
|
|
46
|
+
if argv:
|
|
47
|
+
cmd.extend(argv)
|
|
48
|
+
|
|
49
|
+
# Add file paths at the end
|
|
50
|
+
cmd.extend([str(f) for f in files])
|
|
51
|
+
|
|
52
|
+
# Execute codespell
|
|
53
|
+
try:
|
|
54
|
+
result = subprocess.run(
|
|
55
|
+
cmd,
|
|
56
|
+
cwd=Path.cwd(),
|
|
57
|
+
check=False, # Don't raise on non-zero exit (codespell returns 1 for issues found)
|
|
58
|
+
)
|
|
59
|
+
return result.returncode
|
|
60
|
+
except FileNotFoundError:
|
|
61
|
+
print(
|
|
62
|
+
"Error: codespell not found. Install with: uv pip install codespell",
|
|
63
|
+
file=sys.stderr,
|
|
64
|
+
) # noqa: T201
|
|
65
|
+
return 127 # Command not found
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"Error running codespell: {e}", file=sys.stderr) # noqa: T201
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Ensure files end with a newline.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
end-of-file-fixer hook. It scans files and ensures they end with exactly
|
|
5
|
+
one newline character.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python -m crackerjack.tools.end_of_file_fixer [files...]
|
|
9
|
+
|
|
10
|
+
Exit Codes:
|
|
11
|
+
0: All files end with newline (or successfully fixed)
|
|
12
|
+
1: Files were modified to add/fix ending newline
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ._git_utils import get_files_by_extension
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def needs_newline_fix(content: bytes) -> tuple[bool, bytes | None]:
|
|
25
|
+
"""Check if file needs newline added or fixed.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
content: File content as bytes
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (needs_fix, fixed_content)
|
|
32
|
+
- needs_fix: True if file needs modification
|
|
33
|
+
- fixed_content: Fixed content if needs_fix is True, None otherwise
|
|
34
|
+
"""
|
|
35
|
+
if not content:
|
|
36
|
+
# Empty file doesn't need newline
|
|
37
|
+
return False, None
|
|
38
|
+
|
|
39
|
+
# Check if file ends with newline
|
|
40
|
+
if content.endswith(b"\n"):
|
|
41
|
+
# Check for multiple trailing newlines
|
|
42
|
+
stripped = content.rstrip(b"\n")
|
|
43
|
+
if len(content) - len(stripped) > 1:
|
|
44
|
+
# Multiple trailing newlines, fix to exactly one
|
|
45
|
+
return True, stripped + b"\n"
|
|
46
|
+
# Exactly one newline, no fix needed
|
|
47
|
+
return False, None
|
|
48
|
+
|
|
49
|
+
# No trailing newline, add one
|
|
50
|
+
return True, content + b"\n"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def fix_end_of_file(file_path: Path) -> bool:
|
|
54
|
+
"""Ensure file ends with exactly one newline.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
file_path: Path to file to process
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
True if file was modified, False if no changes needed
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Read file content as bytes to preserve encoding
|
|
64
|
+
content = file_path.read_bytes()
|
|
65
|
+
|
|
66
|
+
# Check if fix is needed
|
|
67
|
+
needs_fix, fixed_content = needs_newline_fix(content)
|
|
68
|
+
|
|
69
|
+
if needs_fix and fixed_content is not None:
|
|
70
|
+
# Write fixed content
|
|
71
|
+
file_path.write_bytes(fixed_content)
|
|
72
|
+
print(f"Fixed end-of-file: {file_path}") # noqa: T201
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"Error processing {file_path}: {e}", file=sys.stderr) # noqa: T201
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _collect_files_to_check(args: argparse.Namespace) -> list[Path]:
|
|
83
|
+
"""Collect files to check for end-of-file issues.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
args: Parsed command-line arguments
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of file paths to process
|
|
90
|
+
"""
|
|
91
|
+
# Default to all git-tracked text files if none specified
|
|
92
|
+
if not args.files:
|
|
93
|
+
# Get all tracked text files (respects .gitignore via git ls-files)
|
|
94
|
+
files = get_files_by_extension(
|
|
95
|
+
[".py", ".md", ".txt", ".yaml", ".yml", ".toml", ".json"]
|
|
96
|
+
)
|
|
97
|
+
if not files:
|
|
98
|
+
# Fallback to Python files if not in git repo
|
|
99
|
+
files = list(Path.cwd().rglob("*.py"))
|
|
100
|
+
else:
|
|
101
|
+
files = args.files
|
|
102
|
+
|
|
103
|
+
# Filter to existing files only
|
|
104
|
+
return [f for f in files if f.is_file()]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _process_files_in_check_mode(files: list[Path]) -> int:
|
|
108
|
+
"""Process files in check-only mode.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
files: List of file paths to check
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Count of files with end-of-file issues
|
|
115
|
+
"""
|
|
116
|
+
modified_count = 0
|
|
117
|
+
for file_path in files:
|
|
118
|
+
try:
|
|
119
|
+
content = file_path.read_bytes()
|
|
120
|
+
needs_fix, _ = needs_newline_fix(content)
|
|
121
|
+
|
|
122
|
+
if needs_fix:
|
|
123
|
+
print(f"Missing/incorrect end-of-file: {file_path}") # noqa: T201
|
|
124
|
+
modified_count += 1
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print(f"Error processing {file_path}: {e}", file=sys.stderr) # noqa: T201
|
|
127
|
+
return modified_count
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _process_files_in_fix_mode(files: list[Path]) -> int:
|
|
131
|
+
"""Process files in fix mode.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
files: List of file paths to fix
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Count of files modified
|
|
138
|
+
"""
|
|
139
|
+
modified_count = 0
|
|
140
|
+
for file_path in files:
|
|
141
|
+
try:
|
|
142
|
+
if fix_end_of_file(file_path):
|
|
143
|
+
modified_count += 1
|
|
144
|
+
except Exception as e:
|
|
145
|
+
print(f"Error processing {file_path}: {e}", file=sys.stderr) # noqa: T201
|
|
146
|
+
return modified_count
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def main(argv: list[str] | None = None) -> int:
|
|
150
|
+
"""Main entry point for end-of-file-fixer tool.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Exit code: 0 if no files modified, 1 if files were modified
|
|
157
|
+
"""
|
|
158
|
+
parser = argparse.ArgumentParser(
|
|
159
|
+
description="Ensure files end with exactly one newline"
|
|
160
|
+
)
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
"files",
|
|
163
|
+
nargs="*",
|
|
164
|
+
type=Path,
|
|
165
|
+
help="Files to check (default: all text files in current directory)",
|
|
166
|
+
)
|
|
167
|
+
parser.add_argument(
|
|
168
|
+
"--check",
|
|
169
|
+
action="store_true",
|
|
170
|
+
help="Check only, don't modify files",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
args = parser.parse_args(argv)
|
|
174
|
+
|
|
175
|
+
files = _collect_files_to_check(args)
|
|
176
|
+
|
|
177
|
+
if not files:
|
|
178
|
+
print("No files to check") # noqa: T201
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
# Process files based on mode
|
|
182
|
+
if args.check:
|
|
183
|
+
modified_count = _process_files_in_check_mode(files)
|
|
184
|
+
else:
|
|
185
|
+
modified_count = _process_files_in_fix_mode(files)
|
|
186
|
+
|
|
187
|
+
# Return appropriate exit code
|
|
188
|
+
if modified_count > 0:
|
|
189
|
+
if args.check:
|
|
190
|
+
print(f"\n{modified_count} file(s) with incorrect end-of-file") # noqa: T201
|
|
191
|
+
else:
|
|
192
|
+
print(f"\nFixed {modified_count} file(s)") # noqa: T201
|
|
193
|
+
# Align with pre-commit semantics so HookExecutor treats this as pass
|
|
194
|
+
print("files were modified by this hook") # noqa: T201
|
|
195
|
+
return 1
|
|
196
|
+
|
|
197
|
+
print("All files end with correct newline") # noqa: T201
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
sys.exit(main())
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Format JSON files to ensure consistent style.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation for formatting JSON files
|
|
4
|
+
to ensure consistent indentation and style. It reads JSON files and
|
|
5
|
+
writes them back with standardized formatting.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
python -m crackerjack.tools.format_json [files...]
|
|
9
|
+
|
|
10
|
+
Exit Codes:
|
|
11
|
+
0: All JSON files formatted successfully
|
|
12
|
+
1: One or more JSON files have syntax errors or could not be formatted
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from ._git_utils import get_files_by_extension
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def format_json_file(file_path: Path) -> tuple[bool, str | None]:
|
|
26
|
+
"""Format a JSON file to ensure consistent style.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
file_path: Path to JSON file to format
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Tuple of (is_success, error_message)
|
|
33
|
+
- is_success: True if file was formatted successfully
|
|
34
|
+
- error_message: Error description if is_success is False, None otherwise
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
# Read the file content
|
|
38
|
+
with file_path.open(encoding="utf-8") as f:
|
|
39
|
+
content = f.read().strip()
|
|
40
|
+
if not content:
|
|
41
|
+
return True, "File is empty, nothing to format"
|
|
42
|
+
|
|
43
|
+
# Parse the JSON
|
|
44
|
+
data = json.loads(content)
|
|
45
|
+
|
|
46
|
+
# Format with consistent indentation
|
|
47
|
+
formatted_content = json.dumps(
|
|
48
|
+
data, indent=2, ensure_ascii=False, sort_keys=True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Add a newline at the end to match common formatting practices
|
|
52
|
+
formatted_content += "\n"
|
|
53
|
+
|
|
54
|
+
# Write the formatted content back to the file
|
|
55
|
+
with file_path.open("w", encoding="utf-8") as f:
|
|
56
|
+
f.write(formatted_content)
|
|
57
|
+
|
|
58
|
+
return True, None
|
|
59
|
+
|
|
60
|
+
except json.JSONDecodeError as e:
|
|
61
|
+
return False, f"Invalid JSON syntax: {e}"
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return False, f"Error formatting file: {e}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main(argv: list[str] | None = None) -> int:
|
|
67
|
+
"""Main entry point for format-json tool.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Exit code: 0 if all JSON files formatted successfully, 1 if any errors
|
|
74
|
+
"""
|
|
75
|
+
parser = argparse.ArgumentParser(
|
|
76
|
+
description="Format JSON files to ensure consistent style"
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"files",
|
|
80
|
+
nargs="*",
|
|
81
|
+
type=Path,
|
|
82
|
+
help="JSON files to format (default: all .json files)",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
args = parser.parse_args(argv)
|
|
86
|
+
|
|
87
|
+
# Default to all git-tracked JSON files if none specified
|
|
88
|
+
if not args.files:
|
|
89
|
+
# Get all tracked JSON files (respects .gitignore via git ls-files)
|
|
90
|
+
files = get_files_by_extension([".json"])
|
|
91
|
+
if not files:
|
|
92
|
+
# Fallback to rglob if not in git repo
|
|
93
|
+
files = list(Path.cwd().rglob("*.json"))
|
|
94
|
+
else:
|
|
95
|
+
files = args.files
|
|
96
|
+
|
|
97
|
+
# Filter to existing files only
|
|
98
|
+
files = [f for f in files if f.is_file()]
|
|
99
|
+
|
|
100
|
+
if not files:
|
|
101
|
+
print("No JSON files to format") # noqa: T201
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
# Process files
|
|
105
|
+
error_count = 0
|
|
106
|
+
for file_path in files:
|
|
107
|
+
is_success, error_msg = format_json_file(file_path)
|
|
108
|
+
|
|
109
|
+
if not is_success:
|
|
110
|
+
print(f"✗ {file_path}: {error_msg}", file=sys.stderr) # noqa: T201
|
|
111
|
+
error_count += 1
|
|
112
|
+
else:
|
|
113
|
+
if error_msg: # File was empty
|
|
114
|
+
print(f"→ {file_path}: {error_msg}") # noqa: T201
|
|
115
|
+
else:
|
|
116
|
+
print(f"✓ {file_path}: Formatted successfully") # noqa: T201
|
|
117
|
+
|
|
118
|
+
# Return appropriate exit code
|
|
119
|
+
if error_count > 0:
|
|
120
|
+
print(f"\n{error_count} JSON file(s) failed to format", file=sys.stderr) # noqa: T201
|
|
121
|
+
return 1
|
|
122
|
+
|
|
123
|
+
print(f"\nAll {len(files)} JSON file(s) formatted successfully") # noqa: T201
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
sys.exit(main())
|