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,139 @@
|
|
|
1
|
+
"""Check for large files added to git repository.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
check-added-large-files hook. It warns about files exceeding a size threshold.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.check_added_large_files [files...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
0: No large files found
|
|
11
|
+
1: One or more large files detected
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from crackerjack.tools._git_utils import get_git_tracked_files
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_file_size(file_path: Path) -> int:
|
|
24
|
+
"""Get file size in bytes.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
file_path: Path to file
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
File size in bytes, or 0 if file doesn't exist
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
return file_path.stat().st_size
|
|
34
|
+
except (FileNotFoundError, OSError):
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def format_size(size_bytes: int | float) -> str:
|
|
39
|
+
"""Format file size in human-readable format.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
size_bytes: Size in bytes
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Human-readable size string (e.g., "1.5 MB")
|
|
46
|
+
"""
|
|
47
|
+
size: float = float(size_bytes)
|
|
48
|
+
for unit in ("B", "KB", "MB", "GB"):
|
|
49
|
+
if size < 1024.0:
|
|
50
|
+
return f"{size:.1f} {unit}"
|
|
51
|
+
size /= 1024.0
|
|
52
|
+
return f"{size:.1f} TB"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main(argv: list[str] | None = None) -> int:
|
|
56
|
+
"""Main entry point for check-added-large-files tool.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Exit code: 0 if no large files, 1 if large files found
|
|
63
|
+
"""
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
description="Check for large files in git repository"
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"files",
|
|
69
|
+
nargs="*",
|
|
70
|
+
type=Path,
|
|
71
|
+
help="Files to check (default: all git-tracked files)",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--maxkb",
|
|
75
|
+
type=int,
|
|
76
|
+
default=1000,
|
|
77
|
+
help="Maximum file size in KB (default: 1000)",
|
|
78
|
+
)
|
|
79
|
+
parser.add_argument(
|
|
80
|
+
"--enforce-all",
|
|
81
|
+
action="store_true",
|
|
82
|
+
help="Check all files, not just newly added ones",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# When called from tests, avoid picking up pytest argv
|
|
86
|
+
if argv is None:
|
|
87
|
+
argv = []
|
|
88
|
+
args = parser.parse_args(argv)
|
|
89
|
+
|
|
90
|
+
# Convert KB to bytes
|
|
91
|
+
max_size_bytes = args.maxkb * 1024
|
|
92
|
+
|
|
93
|
+
# Determine which files to check
|
|
94
|
+
if not args.files:
|
|
95
|
+
# Get all git-tracked files, respecting gitignore
|
|
96
|
+
files = get_git_tracked_files()
|
|
97
|
+
if not files:
|
|
98
|
+
# Fallback to all files in current directory if not in git repo
|
|
99
|
+
files = list(Path.cwd().rglob("*"))
|
|
100
|
+
files = [f for f in files if f.is_file()]
|
|
101
|
+
else:
|
|
102
|
+
files = args.files
|
|
103
|
+
|
|
104
|
+
# Filter to existing files only
|
|
105
|
+
files = [f for f in files if f.is_file()]
|
|
106
|
+
|
|
107
|
+
if not files:
|
|
108
|
+
print("No files to check") # noqa: T201
|
|
109
|
+
return 0
|
|
110
|
+
|
|
111
|
+
# Process files
|
|
112
|
+
large_files = []
|
|
113
|
+
for file_path in files:
|
|
114
|
+
size = get_file_size(file_path)
|
|
115
|
+
if size > max_size_bytes:
|
|
116
|
+
large_files.append((file_path, size))
|
|
117
|
+
|
|
118
|
+
# Report results
|
|
119
|
+
if large_files:
|
|
120
|
+
print("Large files detected:", file=sys.stderr) # noqa: T201
|
|
121
|
+
for file_path, size in large_files:
|
|
122
|
+
print(
|
|
123
|
+
f" {file_path}: {format_size(size)} "
|
|
124
|
+
f"(exceeds {format_size(max_size_bytes)})",
|
|
125
|
+
file=sys.stderr,
|
|
126
|
+
) # noqa: T201
|
|
127
|
+
print(
|
|
128
|
+
f"\n{len(large_files)} large file(s) found. "
|
|
129
|
+
f"Consider using Git LFS for large files.",
|
|
130
|
+
file=sys.stderr,
|
|
131
|
+
) # noqa: T201
|
|
132
|
+
return 1
|
|
133
|
+
|
|
134
|
+
print("All files are under size limit") # noqa: T201
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
if __name__ == "__main__":
|
|
139
|
+
sys.exit(main())
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Validate Python file AST syntax.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
check-ast hook. It scans Python files and validates their AST syntax.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.check_ast [files...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
0: All Python files have valid ASTs
|
|
11
|
+
1: One or more Python files have syntax errors (invalid ASTs)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import ast
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ._git_utils import get_files_by_extension
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate_ast_file(file_path: Path) -> tuple[bool, str | None]:
|
|
25
|
+
"""Validate a Python file's AST syntax.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
file_path: Path to Python file to validate
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (is_valid, error_message)
|
|
32
|
+
- is_valid: True if file has valid Python AST syntax
|
|
33
|
+
- error_message: Error description if is_valid is False, None otherwise
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
with file_path.open(encoding="utf-8") as f:
|
|
37
|
+
content = f.read()
|
|
38
|
+
|
|
39
|
+
# Parse the content into an AST
|
|
40
|
+
ast.parse(content)
|
|
41
|
+
return True, None
|
|
42
|
+
except SyntaxError as e:
|
|
43
|
+
return False, f"Syntax error at line {e.lineno}: {e.msg}"
|
|
44
|
+
except Exception as e:
|
|
45
|
+
return False, f"Error reading file: {e}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv: list[str] | None = None) -> int:
|
|
49
|
+
"""Main entry point for check-ast tool.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Exit code: 0 if all Python files have valid ASTs, 1 if any errors found
|
|
56
|
+
"""
|
|
57
|
+
parser = argparse.ArgumentParser(description="Validate Python file AST syntax")
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"files",
|
|
60
|
+
nargs="*",
|
|
61
|
+
type=Path,
|
|
62
|
+
help="Python files to check (default: all .py files)",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
args = parser.parse_args(argv)
|
|
66
|
+
|
|
67
|
+
# Default to all git-tracked Python files if none specified
|
|
68
|
+
if not args.files:
|
|
69
|
+
# Get all tracked Python files (respects .gitignore via git ls-files)
|
|
70
|
+
files = get_files_by_extension([".py"])
|
|
71
|
+
if not files:
|
|
72
|
+
# Fallback to rglob if not in git repo
|
|
73
|
+
files = list(Path.cwd().rglob("*.py"))
|
|
74
|
+
else:
|
|
75
|
+
files = args.files
|
|
76
|
+
|
|
77
|
+
# Filter to existing files only
|
|
78
|
+
files = [f for f in files if f.is_file()]
|
|
79
|
+
|
|
80
|
+
if not files:
|
|
81
|
+
print("No Python files to check") # noqa: T201
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
# Process files
|
|
85
|
+
error_count = 0
|
|
86
|
+
for file_path in files:
|
|
87
|
+
is_valid, error_msg = validate_ast_file(file_path)
|
|
88
|
+
|
|
89
|
+
if not is_valid:
|
|
90
|
+
print(f"✗ {file_path}: {error_msg}", file=sys.stderr) # noqa: T201
|
|
91
|
+
error_count += 1
|
|
92
|
+
else:
|
|
93
|
+
print(f"✓ {file_path}: Valid AST") # noqa: T201
|
|
94
|
+
|
|
95
|
+
# Return appropriate exit code
|
|
96
|
+
if error_count > 0:
|
|
97
|
+
print(f"\n{error_count} Python file(s) with AST errors", file=sys.stderr) # noqa: T201
|
|
98
|
+
return 1
|
|
99
|
+
|
|
100
|
+
print(f"\nAll {len(files)} Python file(s) have valid ASTs") # noqa: T201
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
sys.exit(main())
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Validate JSON file syntax.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation replacing pre-commit's
|
|
4
|
+
check-json hook. It scans JSON files and validates their syntax.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m crackerjack.tools.check_json [files...]
|
|
8
|
+
|
|
9
|
+
Exit Codes:
|
|
10
|
+
0: All JSON files are valid
|
|
11
|
+
1: One or more JSON files have syntax errors
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ._git_utils import get_files_by_extension
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def validate_json_file(file_path: Path) -> tuple[bool, str | None]:
|
|
25
|
+
"""Validate a JSON file's syntax.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
file_path: Path to JSON file to validate
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Tuple of (is_valid, error_message)
|
|
32
|
+
- is_valid: True if file has valid JSON syntax
|
|
33
|
+
- error_message: Error description if is_valid is False, None otherwise
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
with file_path.open(encoding="utf-8") as f:
|
|
37
|
+
# Load JSON and validate structure
|
|
38
|
+
json.load(f)
|
|
39
|
+
return True, None
|
|
40
|
+
except json.JSONDecodeError 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-json tool.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Exit code: 0 if all JSON valid, 1 if any errors found
|
|
54
|
+
"""
|
|
55
|
+
parser = argparse.ArgumentParser(description="Validate JSON file syntax")
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"files",
|
|
58
|
+
nargs="*",
|
|
59
|
+
type=Path,
|
|
60
|
+
help="JSON files to check (default: all .json files)",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
args = parser.parse_args(argv)
|
|
64
|
+
|
|
65
|
+
# Default to all git-tracked JSON files if none specified
|
|
66
|
+
if not args.files:
|
|
67
|
+
# Get all tracked JSON files (respects .gitignore via git ls-files)
|
|
68
|
+
files = get_files_by_extension([".json"])
|
|
69
|
+
if not files:
|
|
70
|
+
# Fallback to rglob if not in git repo
|
|
71
|
+
files = list(Path.cwd().rglob("*.json"))
|
|
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 JSON 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_json_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 JSON") # noqa: T201
|
|
92
|
+
|
|
93
|
+
# Return appropriate exit code
|
|
94
|
+
if error_count > 0:
|
|
95
|
+
print(f"\n{error_count} JSON file(s) with errors", file=sys.stderr) # noqa: T201
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
print(f"\nAll {len(files)} JSON file(s) are valid") # noqa: T201
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
sys.exit(main())
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Validate JSON files against JSON Schema definitions.
|
|
2
|
+
|
|
3
|
+
This tool is a native Python implementation for validating JSON files
|
|
4
|
+
against their corresponding JSON Schema definitions. It automatically
|
|
5
|
+
finds .json files and attempts to validate them against similarly named
|
|
6
|
+
.schema.json or .json files that follow the JSON Schema standard.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python -m crackerjack.tools.check_jsonschema [files...]
|
|
10
|
+
|
|
11
|
+
Exit Codes:
|
|
12
|
+
0: All JSON files are valid against their schemas
|
|
13
|
+
1: One or more JSON files fail schema validation
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import json
|
|
20
|
+
import sys
|
|
21
|
+
import typing as t
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from ._git_utils import get_files_by_extension
|
|
25
|
+
|
|
26
|
+
if t.TYPE_CHECKING:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_filename_pattern_schema(json_file: Path) -> Path | None:
|
|
31
|
+
"""Check for {name}.schema.json pattern."""
|
|
32
|
+
schema_path = json_file.with_name(f"{json_file.stem}.schema.json")
|
|
33
|
+
if schema_path.is_file():
|
|
34
|
+
return schema_path
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_local_schema_path(json_file: Path, schema_ref: str) -> Path | None:
|
|
39
|
+
"""Resolve a local schema file path.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
json_file: The JSON file containing the schema reference
|
|
43
|
+
schema_ref: The schema reference string
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Path to schema file if found, None otherwise
|
|
47
|
+
"""
|
|
48
|
+
if schema_ref.endswith((".json", ".schema.json")):
|
|
49
|
+
schema_path = json_file.parent / schema_ref
|
|
50
|
+
if schema_path.is_file():
|
|
51
|
+
return schema_path
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _check_internal_schema_ref(json_file: Path) -> Path | None:
|
|
56
|
+
"""Try to find a schema reference inside the JSON file."""
|
|
57
|
+
try:
|
|
58
|
+
with json_file.open(encoding="utf-8") as f:
|
|
59
|
+
data = json.load(f)
|
|
60
|
+
|
|
61
|
+
if isinstance(data, dict):
|
|
62
|
+
schema_ref = data.get("$schema")
|
|
63
|
+
if schema_ref and isinstance(schema_ref, str):
|
|
64
|
+
return _resolve_local_schema_path(json_file, schema_ref)
|
|
65
|
+
except (OSError, json.JSONDecodeError):
|
|
66
|
+
pass
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _check_same_dir_schema(json_file: Path) -> Path | None:
|
|
71
|
+
"""Try schema.json in the same directory."""
|
|
72
|
+
schema_path = json_file.with_name("schema.json")
|
|
73
|
+
if schema_path.is_file():
|
|
74
|
+
return schema_path
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _check_parent_dir_schemas(json_file: Path) -> Path | None:
|
|
79
|
+
"""Try finding schema.json in parent directories (up to 3 levels)."""
|
|
80
|
+
current_dir = json_file.parent
|
|
81
|
+
for _ in range(3):
|
|
82
|
+
schema_path = current_dir / "schema.json"
|
|
83
|
+
if schema_path.is_file():
|
|
84
|
+
return schema_path
|
|
85
|
+
if current_dir.parent == current_dir: # reached root
|
|
86
|
+
break
|
|
87
|
+
current_dir = current_dir.parent
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def find_schema_for_json(json_file: Path) -> Path | None:
|
|
92
|
+
"""Find the corresponding schema file for a JSON file.
|
|
93
|
+
|
|
94
|
+
Looks for schema files in common patterns:
|
|
95
|
+
- {name}.schema.json
|
|
96
|
+
- {name}.json (if it contains $schema reference)
|
|
97
|
+
- schema.json in the same directory
|
|
98
|
+
- schema.json in parent directories
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
json_file: Path to the JSON file to validate
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Path to schema file, or None if not found
|
|
105
|
+
"""
|
|
106
|
+
# Try {name}.schema.json pattern
|
|
107
|
+
result = _check_filename_pattern_schema(json_file)
|
|
108
|
+
if result:
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
# Try to find a schema reference inside the JSON file
|
|
112
|
+
result = _check_internal_schema_ref(json_file)
|
|
113
|
+
if result:
|
|
114
|
+
return result
|
|
115
|
+
|
|
116
|
+
# Try schema.json in the same directory
|
|
117
|
+
result = _check_same_dir_schema(json_file)
|
|
118
|
+
if result:
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
# Try finding schema.json in parent directories (up to 3 levels)
|
|
122
|
+
return _check_parent_dir_schemas(json_file)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def load_schema(schema_path: Path) -> dict[str, t.Any] | None:
|
|
126
|
+
"""Load a JSON schema from file.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
schema_path: Path to the schema file
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Schema as dictionary, or None if loading fails
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
with schema_path.open(encoding="utf-8") as f:
|
|
136
|
+
schema = json.load(f)
|
|
137
|
+
|
|
138
|
+
# Basic validation that this looks like a schema
|
|
139
|
+
if not isinstance(schema, dict):
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
return schema
|
|
143
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
144
|
+
print(f"Could not load schema {schema_path}: {e}", file=sys.stderr) # noqa: T201
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def validate_json_against_schema(
|
|
149
|
+
json_file: Path, schema_path: Path
|
|
150
|
+
) -> tuple[bool, str | None]:
|
|
151
|
+
"""Validate a JSON file against a schema.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
json_file: Path to the JSON file to validate
|
|
155
|
+
schema_path: Path to the schema file
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Tuple of (is_valid, error_message)
|
|
159
|
+
- is_valid: True if file validates against schema
|
|
160
|
+
- error_message: Error description if is_valid is False, None otherwise
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
# Load schema
|
|
164
|
+
schema = load_schema(schema_path)
|
|
165
|
+
if not schema:
|
|
166
|
+
return False, f"Could not load schema: {schema_path}"
|
|
167
|
+
|
|
168
|
+
# Load JSON data
|
|
169
|
+
with json_file.open(encoding="utf-8") as f:
|
|
170
|
+
data = json.load(f)
|
|
171
|
+
|
|
172
|
+
# Import jsonschema only when needed to avoid dependency issues
|
|
173
|
+
import jsonschema
|
|
174
|
+
|
|
175
|
+
# Validate the data against the schema
|
|
176
|
+
validator_class = jsonschema.Draft7Validator
|
|
177
|
+
if hasattr(validator_class, "check_schema"):
|
|
178
|
+
# Validate the schema itself first
|
|
179
|
+
validator_class.check_schema(schema)
|
|
180
|
+
|
|
181
|
+
validator = validator_class(schema)
|
|
182
|
+
errors = list(validator.iter_errors(data))
|
|
183
|
+
|
|
184
|
+
if errors:
|
|
185
|
+
error_messages = [
|
|
186
|
+
f" {error.message}" for error in errors[:5]
|
|
187
|
+
] # Limit to first 5 errors
|
|
188
|
+
if len(errors) > 5:
|
|
189
|
+
error_messages.append(f" ... and {len(errors) - 5} more errors")
|
|
190
|
+
return False, "Schema validation failed:\n" + "\n".join(error_messages)
|
|
191
|
+
|
|
192
|
+
return True, None
|
|
193
|
+
|
|
194
|
+
except ImportError:
|
|
195
|
+
return (
|
|
196
|
+
False,
|
|
197
|
+
"jsonschema library not available. Install with: pip install jsonschema",
|
|
198
|
+
)
|
|
199
|
+
except jsonschema.SchemaError as e:
|
|
200
|
+
return False, f"Invalid schema: {e}"
|
|
201
|
+
except json.JSONDecodeError as e:
|
|
202
|
+
return False, f"Invalid JSON in file: {e}"
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return False, f"Validation error: {e}"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
208
|
+
"""Parse command-line arguments."""
|
|
209
|
+
parser = argparse.ArgumentParser(
|
|
210
|
+
description="Validate JSON files against JSON Schema definitions"
|
|
211
|
+
)
|
|
212
|
+
parser.add_argument(
|
|
213
|
+
"files",
|
|
214
|
+
nargs="*",
|
|
215
|
+
type=Path,
|
|
216
|
+
help="JSON files to validate against schemas (default: all .json files)",
|
|
217
|
+
)
|
|
218
|
+
parser.add_argument(
|
|
219
|
+
"--strict",
|
|
220
|
+
action="store_true",
|
|
221
|
+
help="Fail if no schema is found for a JSON file",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return parser.parse_args(argv)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _get_json_files(args: argparse.Namespace) -> list[Path]:
|
|
228
|
+
"""Get list of JSON files to validate."""
|
|
229
|
+
if not args.files:
|
|
230
|
+
# Get all tracked JSON files (respects .gitignore via git ls-files)
|
|
231
|
+
files = get_files_by_extension([".json"])
|
|
232
|
+
if not files:
|
|
233
|
+
# Fallback to rglob if not in git repo
|
|
234
|
+
files = list(Path.cwd().rglob("*.json"))
|
|
235
|
+
else:
|
|
236
|
+
files = args.files
|
|
237
|
+
|
|
238
|
+
# Filter to existing files only
|
|
239
|
+
return [f for f in files if f.is_file()]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _process_file(file_path: Path, schema_path: Path | None, strict: bool) -> int:
|
|
243
|
+
"""Process a single file and return error count increment."""
|
|
244
|
+
if not schema_path:
|
|
245
|
+
if strict:
|
|
246
|
+
print(f"✗ {file_path}: No schema found", file=sys.stderr) # noqa: T201
|
|
247
|
+
return 1
|
|
248
|
+
else:
|
|
249
|
+
print(f"→ {file_path}: No schema found, skipping validation") # noqa: T201
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
is_valid, error_msg = validate_json_against_schema(file_path, schema_path)
|
|
253
|
+
|
|
254
|
+
if not is_valid:
|
|
255
|
+
print(f"✗ {file_path}: {error_msg}", file=sys.stderr) # noqa: T201
|
|
256
|
+
return 1
|
|
257
|
+
else:
|
|
258
|
+
print(f"✓ {file_path}: Valid against {schema_path.name}") # noqa: T201
|
|
259
|
+
return 0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def main(argv: list[str] | None = None) -> int:
|
|
263
|
+
"""Main entry point for check-jsonschema tool.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
argv: Command-line arguments (defaults to sys.argv[1:])
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Exit code: 0 if all JSON files validate against schemas, 1 if any errors found
|
|
270
|
+
"""
|
|
271
|
+
args = _parse_args(argv)
|
|
272
|
+
|
|
273
|
+
files = _get_json_files(args)
|
|
274
|
+
|
|
275
|
+
if not files:
|
|
276
|
+
print("No JSON files to validate") # noqa: T201
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
# Process files
|
|
280
|
+
error_count = 0
|
|
281
|
+
for file_path in files:
|
|
282
|
+
schema_path = find_schema_for_json(file_path)
|
|
283
|
+
error_count += _process_file(file_path, schema_path, args.strict)
|
|
284
|
+
|
|
285
|
+
# Return appropriate exit code
|
|
286
|
+
if error_count > 0:
|
|
287
|
+
print(f"\n{error_count} JSON file(s) failed schema validation", file=sys.stderr) # noqa: T201
|
|
288
|
+
return 1
|
|
289
|
+
|
|
290
|
+
print(
|
|
291
|
+
f"\nAll {len([f for f in files if find_schema_for_json(f)])} JSON file(s) passed schema validation"
|
|
292
|
+
) # noqa: T201
|
|
293
|
+
return 0
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
sys.exit(main())
|