ruff-sync 0.1.6.dev1__tar.gz → 0.1.6.dev3__tar.gz
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.
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/plans/issue-116-config-validation.md +6 -6
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/ruff-sync-usage/SKILL.md +3 -2
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +20 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/AGENTS.md +1 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/PKG-INFO +1 -1
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/configuration.md +3 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/usage.md +4 -1
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/pyproject.toml +1 -1
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/core.py +1 -1
- ruff_sync-0.1.6.dev3/src/ruff_sync/validation.py +323 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_config_validation.py +238 -2
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/uv.lock +1 -1
- ruff_sync-0.1.6.dev1/src/ruff_sync/validation.py +0 -125
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/DEPENDENCIES.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/TESTING.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/decisions/0001-type-refactoring-strategy.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/decisions/0002-tui-node-ast.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/decisions/README.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/doc-fix.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/formatters-architecture.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/gitlab-reports.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/issue-102-context.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/plans/issue-100-roadmap-plan.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/ruff.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/adr/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/dirty-equals/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/dirty-equals/references/common-matchers.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/dirty-equals/references/toml-matching.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/dirty-equals/trigger_eval.json +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/gh-issues/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mike/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mike/references/commands.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/examples.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/release-notes-generation/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/best-practices.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/evaluating-skills.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/optimizing-descriptions.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/quickstart.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/sources.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/references/using-scripts.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/skill-creator/scripts/scaffold_skill.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/examples/basic_app.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/examples/reactive_example.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/references/events.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/references/styling.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/references/testing.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/textual/references/widgets.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/evals.json +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/advanced-narrowing.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/error-code-lookup.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/generics.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/naming.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/protocol-patterns.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/quickstart.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/references/refactoring-patterns.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/type-checking/scripts/audit_types.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/skills/warnings-control/SKILL.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/tui_design.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/tui_requirements.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/tui_rule_browsing.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/tui_rule_browsing_design.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.agents/workflows/update-screenshots.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.git-blame-ignore-revs +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.github/dependabot.yml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.github/workflows/ci.yaml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.gitignore +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.pre-commit-config.yaml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/.pre-commit-hooks.yaml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/CONTRIBUTING.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/LICENSE.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/README.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/codecov.yml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/configs/data-science-engineering/ruff.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/configs/fastapi/ruff.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/configs/kitchen-sink/ruff.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/agent-skill.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/favicon.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/github-job-summary.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/github-pr-annotation.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/inspect-main.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/inspect-search.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/logo.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/ruff_sync_banner.png +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/screenshots/dashboard.svg +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/screenshots/legend_help.svg +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/screenshots/rule_details.svg +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/screenshots/screenshot_sample.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/assets/screenshots/search_omnibox.svg +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/best-practices.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/ci-integration.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/contributing.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/examples/advanced-config.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/examples/basic-config.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/gen_ref_pages.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/index.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/inspect.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/installation.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/overrides/main.html +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/overrides/partials/version_warning.html +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/pre-commit.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/pre-defined-configs.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/troubleshooting.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/docs/url-resolution.md +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/mkdocs.yml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/scripts/check_dogfood.sh +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/scripts/generate_tui_screenshots.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/scripts/gitclone_dogfood.sh +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/scripts/pull_dogfood.sh +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/skills-lock.json +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/__init__.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/__main__.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/cli.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/config_io.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/constants.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/dependencies.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/formatters.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/pre_commit.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/system.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/__init__.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/app.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/constants.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/screens.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/themes.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/types_.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/tui/widgets.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/src/ruff_sync/types_.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tasks.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/__init__.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/conftest.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/ruff.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_basic.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_check.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_ci_integration.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_ci_validation.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_config_io.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_constants.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_corner_cases.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_dependencies.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_deprecation.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_e2e.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_formatters.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_git_fetch.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_minimal_imports.sh +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_pre_commit.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_project.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_rule_logic.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_scaffold.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_serialization.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_system.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_url_handling.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/test_whitespace.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/tui/__init__.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/tui/conftest.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/tui/test_themes.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/tui/test_tui.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/tui/test_tui_types.py +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.6.dev1 → ruff_sync-0.1.6.dev3}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -24,12 +24,12 @@ passing both.
|
|
|
24
24
|
> Use this to pick your agent tier. Simple phases can be handled by a weak/cheap model;
|
|
25
25
|
> Complex phases need a capable reasoning model.
|
|
26
26
|
|
|
27
|
-
| Priority | Label | Complexity | Feature | Scope |
|
|
28
|
-
|
|
29
|
-
| 1 (Must Have) | 🔴 | 🟡 Moderate | TOML syntax + Ruff CLI validation | New `validation.py` module; gated by `--validate` in `pull()` |
|
|
30
|
-
| 2 (Should Have) | 🟠 | 🟢 Simple | Python version consistency check | `validation.py` + warn when `--validate` is active |
|
|
31
|
-
| 3 (Could Have) | 🟡 | 🔴 Complex | Rule deprecation warnings | `validation.py` + subprocess + JSON + DI; gated by `--validate` |
|
|
32
|
-
| 4 (Nice to Have) | 🟢 | 🟢 Simple | `--strict` flag | Upgrades warnings to failures; implies `--validate` |
|
|
27
|
+
| Priority | Label | Complexity | Feature | Scope | Status |
|
|
28
|
+
|---|---|---|---|---|---|
|
|
29
|
+
| 1 (Must Have) | 🔴 | 🟡 Moderate | TOML syntax + Ruff CLI validation | New `validation.py` module; gated by `--validate` in `pull()` | ✅ Completed |
|
|
30
|
+
| 2 (Should Have) | 🟠 | 🟢 Simple | Python version consistency check | `validation.py` + warn when `--validate` is active | ✅ Completed |
|
|
31
|
+
| 3 (Could Have) | 🟡 | 🔴 Complex | Rule deprecation warnings | `validation.py` + subprocess + JSON + DI; gated by `--validate` | ✅ Completed |
|
|
32
|
+
| 4 (Nice to Have) | 🟢 | 🟢 Simple | `--strict` flag | Upgrades warnings to failures; implies `--validate` | ✅ Completed |
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
@@ -127,8 +127,9 @@ ruff-sync git@github.com:my-org/standards.git # SSH (shallow clone)
|
|
|
127
127
|
## Gotchas
|
|
128
128
|
|
|
129
129
|
- **`exclude` uses dotted paths, not TOML paths.** `lint.per-file-ignores` refers to the `per-file-ignores` key inside `[tool.ruff.lint]`. Do NOT write `tool.ruff.lint.per-file-ignores`.
|
|
130
|
-
- **`--validate` is opt-in.** Validation is off by default. Pass `--validate` (or `--strict`) to run the merged config through Ruff before writing. The sync is aborted if Ruff rejects the config, leaving the local file untouched.
|
|
131
|
-
-
|
|
130
|
+
- **`--validate` is opt-in.** Validation is off by default. Pass `--validate` (or `--strict`) to run the merged config through Ruff before writing. The sync is aborted if Ruff rejects the config, leaving the local file untouched. This includes a **Python version consistency check** that compares upstream `target-version` against local `requires-python`.
|
|
131
|
+
- **Excluding `target-version` skips consistency validation.** If `target-version` (or `tool.ruff.target-version`) is in the `exclude` list, `ruff-sync` will skip the version consistency check and log a warning. This is useful for projects that intentionally deviate from upstream Python versions.
|
|
132
|
+
- **`--strict` implies `--validate`.** If you want warnings (like version mismatches or deprecated rules) treated as failures, only `--strict` is needed — don't pass both.
|
|
132
133
|
- **Validation soft-fails if `ruff` is not on PATH.** If the `ruff` binary can't be found, a warning is logged and the sync continues normally.
|
|
133
134
|
- **Use `--init` only for new projects.** `ruff-sync` requires an existing `pyproject.toml` or `ruff.toml`. Pass `--init` to scaffold one if the directory is empty. This will automatically generate a `[tool.ruff-sync]` configuration block for future syncs.
|
|
134
135
|
- **Use `--save` to persist CLI arguments.** If you want to update an existing `pyproject.toml` with a new upstream URL or exclusion, pass `--save` to write the new configuration to the file.
|
|
@@ -77,6 +77,26 @@ ruff-sync # pulls config AND updates the hook rev (if pre-commit-versio
|
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
To suppress this check entirely, omit `--pre-commit` from `ruff-sync check` or set `pre-commit-version-sync = false`.
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Python Version Mismatch Warning
|
|
83
|
+
|
|
84
|
+
**Symptom:** Sync succeeds but logs a warning like:
|
|
85
|
+
```
|
|
86
|
+
⚠️ Version mismatch: upstream [tool.ruff] target-version='py39' targets Python 3.9, but local [project] requires-python='>=3.10' requires Python >= 3.10.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Cause:** The upstream configuration is targeting an older Python version than your project requires. This can lead to Ruff not using newer rules or features available to your project.
|
|
90
|
+
|
|
91
|
+
**Fix:**
|
|
92
|
+
1. **Update Upstream:** The ideal fix is to update the `target-version` in the upstream repository to a newer version (e.g., `py311`).
|
|
93
|
+
2. **Exclude the key:** If you need to stay on a specific version locally, add `target-version` to your `exclude` list:
|
|
94
|
+
```toml
|
|
95
|
+
[tool.ruff-sync]
|
|
96
|
+
exclude = ["target-version"]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
In **`--strict`** mode, this warning is upgraded to a hard failure and will abort the sync.
|
|
80
100
|
|
|
81
101
|
---
|
|
82
102
|
|
|
@@ -20,6 +20,7 @@ Specific workflows, libraries, and tools are documented in `.agents/skills/`. Be
|
|
|
20
20
|
|
|
21
21
|
- **Python** ≥ 3.10 (target version `py310`)
|
|
22
22
|
- **Package Manager**: [uv](https://docs.astral.sh/uv/) — Use `uv run <command>` for all executions to ensure the correct environment.
|
|
23
|
+
- **Note on PATH**: On macOS, `uv` is often installed in `~/.local/bin`. If `uv` is not found, add this to your `PATH`: `export PATH="$PATH:$HOME/.local/bin"`.
|
|
23
24
|
- **Linter / Formatter**: [Ruff](https://docs.astral.sh/ruff/) (`>=0.15.0`)
|
|
24
25
|
- **Type Checker**: [mypy](https://mypy-lang.org/) (strict mode)
|
|
25
26
|
- **Test Framework**: [pytest](https://docs.pytest.org/) with `pytest-asyncio`, `respx`, `pyfakefs` (See [Testing Standards](.agents/TESTING.md))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ruff-sync
|
|
3
|
-
Version: 0.1.6.
|
|
3
|
+
Version: 0.1.6.dev3
|
|
4
4
|
Summary: Synchronize Ruff linter configuration across projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
|
|
6
6
|
Project-URL: Documentation, https://kilo59.github.io/ruff-sync/
|
|
@@ -62,6 +62,9 @@ If your projects are on different Python versions but share linting rules:
|
|
|
62
62
|
exclude = ["target-version"]
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
> [!NOTE]
|
|
66
|
+
> Excluding `target-version` also automatically skips the Python version consistency check otherwise performed during `--validate` or `--strict` runs.
|
|
67
|
+
|
|
65
68
|
#### Sequential merging of multiple sources
|
|
66
69
|
|
|
67
70
|
You can specify multiple upstream sources as a list. They will be merged in order—from top to bottom—with later sources overriding or extending earlier ones.
|
|
@@ -122,12 +122,15 @@ ruff-sync --validate
|
|
|
122
122
|
|
|
123
123
|
If Ruff rejects the merged config (e.g., because the upstream introduced an unknown key), the sync is aborted and your local file is left untouched.
|
|
124
124
|
|
|
125
|
-
For stricter enforcement, use `--strict`. This upgrades any validation *warnings*
|
|
125
|
+
For stricter enforcement, use `--strict`. This upgrades any validation *warnings* into hard failures. This includes a **Python version consistency check** that compares the upstream `[tool.ruff] target-version` against your local `[project] requires-python`:
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
128
|
ruff-sync --strict
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
> [!TIP]
|
|
132
|
+
> If you intentionally manage your Python version locally and it differs from the upstream, you can add `target-version` to your `exclude` list. `ruff-sync` will then skip the consistency check and log a warning explaining why.
|
|
133
|
+
|
|
131
134
|
> [!NOTE]
|
|
132
135
|
> `--strict` implies `--validate`. You do not need to pass both flags.
|
|
133
136
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.1.6.
|
|
3
|
+
version = "0.1.6.dev3"
|
|
4
4
|
description = "Synchronize Ruff linter configuration across projects"
|
|
5
5
|
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit", "pre-commit"]
|
|
6
6
|
authors = [
|
|
@@ -1164,7 +1164,7 @@ async def pull(
|
|
|
1164
1164
|
|
|
1165
1165
|
is_ruff_toml = is_ruff_toml_file(_source_toml_path.name)
|
|
1166
1166
|
if not validate_merged_config(
|
|
1167
|
-
source_doc, is_ruff_toml=is_ruff_toml, strict=args.strict
|
|
1167
|
+
source_doc, is_ruff_toml=is_ruff_toml, strict=args.strict, exclude=args.exclude
|
|
1168
1168
|
):
|
|
1169
1169
|
fmt.error(
|
|
1170
1170
|
"❌ Merged config failed validation. Local file left unchanged.",
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Validation logic for ruff-sync merged configurations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import pathlib
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
import tomlkit
|
|
15
|
+
from tomlkit import TOMLDocument
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"validate_merged_config",
|
|
22
|
+
"validate_ruff_accepts_config",
|
|
23
|
+
"validate_toml_syntax",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
LOGGER = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
_RUFF_TARGET_VERSION_PATTERN = re.compile(r"^py(\d)(\d+)$")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _ruff_target_to_tuple(target_version: str) -> tuple[int, int] | None:
|
|
32
|
+
"""Parse a Ruff target-version string (e.g. 'py311') into a (major, minor) tuple."""
|
|
33
|
+
m = _RUFF_TARGET_VERSION_PATTERN.match(target_version)
|
|
34
|
+
if not m:
|
|
35
|
+
return None
|
|
36
|
+
return int(m.group(1)), int(m.group(2))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _requires_python_min_version(requires_python: str) -> tuple[int, int] | None:
|
|
40
|
+
"""Extract the minimum Python version from a PEP 440 requires-python string.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
'>=3.10' -> (3, 10)
|
|
44
|
+
'^3.11' -> (3, 11)
|
|
45
|
+
'~=3.9' -> (3, 9)
|
|
46
|
+
"""
|
|
47
|
+
# TODO: Consider using packaging.specifiers.SpecifierSet optionally if available
|
|
48
|
+
# Match all version specifiers of the form X.Y
|
|
49
|
+
matches = re.findall(r"(\d+)\.(\d+)", requires_python)
|
|
50
|
+
if matches:
|
|
51
|
+
# Convert to list of (int, int) and return the minimum semantic version
|
|
52
|
+
versions = [(int(major), int(minor)) for major, minor in matches]
|
|
53
|
+
return min(versions)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def check_python_version_consistency(
|
|
58
|
+
doc: TOMLDocument, strict: bool = False, exclude: Iterable[str] = ()
|
|
59
|
+
) -> bool:
|
|
60
|
+
"""Warn if the merged ruff target-version conflicts with requires-python.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
doc: The merged TOML document (pyproject.toml format).
|
|
64
|
+
strict: If True, treat version mismatch as a failure.
|
|
65
|
+
exclude: List of keys excluded from the ruff configuration.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
True if versions are consistent or if check is skipped.
|
|
69
|
+
False if strict=True and versions are inconsistent.
|
|
70
|
+
"""
|
|
71
|
+
if "target-version" in exclude or "tool.ruff.target-version" in exclude:
|
|
72
|
+
LOGGER.warning(
|
|
73
|
+
"Skipping Python version consistency check: 'target-version' is "
|
|
74
|
+
"excluded in [tool.ruff-sync]."
|
|
75
|
+
)
|
|
76
|
+
return True
|
|
77
|
+
try:
|
|
78
|
+
ruff_section = doc.get("tool", {}).get("ruff", {})
|
|
79
|
+
target_version = ruff_section.get("target-version")
|
|
80
|
+
requires_python = doc.get("project", {}).get("requires-python")
|
|
81
|
+
except (AttributeError, TypeError):
|
|
82
|
+
return True # Don't crash on unexpected doc shapes
|
|
83
|
+
|
|
84
|
+
if not target_version or not requires_python:
|
|
85
|
+
missing = []
|
|
86
|
+
if not target_version:
|
|
87
|
+
missing.append("[tool.ruff] target-version")
|
|
88
|
+
if not requires_python:
|
|
89
|
+
missing.append("[project] requires-python")
|
|
90
|
+
LOGGER.warning(f"Skipping Python version consistency check: missing {', '.join(missing)}.")
|
|
91
|
+
return True # Nothing to compare
|
|
92
|
+
|
|
93
|
+
ruff_min = _ruff_target_to_tuple(str(target_version))
|
|
94
|
+
proj_min = _requires_python_min_version(str(requires_python))
|
|
95
|
+
|
|
96
|
+
if ruff_min is None or proj_min is None:
|
|
97
|
+
return True # Couldn't parse one of the versions
|
|
98
|
+
|
|
99
|
+
if ruff_min < proj_min:
|
|
100
|
+
msg = (
|
|
101
|
+
f"Version mismatch: upstream [tool.ruff] target-version='{target_version}' "
|
|
102
|
+
f"targets Python {ruff_min[0]}.{ruff_min[1]}, but local [project] requires-python="
|
|
103
|
+
f"'{requires_python}' requires Python >= {proj_min[0]}.{proj_min[1]}. "
|
|
104
|
+
"Consider updating target-version in the upstream config."
|
|
105
|
+
)
|
|
106
|
+
if strict:
|
|
107
|
+
LOGGER.error(f"❌ {msg}")
|
|
108
|
+
return False
|
|
109
|
+
LOGGER.warning(f"⚠️ {msg}")
|
|
110
|
+
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_RULE_LIST_KEYS: frozenset[str] = frozenset({"select", "extend-select", "ignore", "extend-ignore"})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@functools.lru_cache(maxsize=1)
|
|
118
|
+
def _get_deprecated_rule_codes() -> frozenset[str]:
|
|
119
|
+
"""Return the set of deprecated rule codes reported by the installed Ruff version.
|
|
120
|
+
|
|
121
|
+
Returns an empty frozenset if ruff is not found or output cannot be parsed.
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
result = subprocess.run(
|
|
125
|
+
["ruff", "rule", "--all", "--output-format", "json"], # noqa: S607
|
|
126
|
+
capture_output=True,
|
|
127
|
+
text=True,
|
|
128
|
+
timeout=30,
|
|
129
|
+
check=False,
|
|
130
|
+
)
|
|
131
|
+
if result.returncode != 0:
|
|
132
|
+
return frozenset()
|
|
133
|
+
rules = json.loads(result.stdout)
|
|
134
|
+
return frozenset(r["code"] for r in rules if r.get("deprecated") is True)
|
|
135
|
+
except (
|
|
136
|
+
FileNotFoundError,
|
|
137
|
+
subprocess.TimeoutExpired,
|
|
138
|
+
json.JSONDecodeError,
|
|
139
|
+
KeyError,
|
|
140
|
+
TypeError,
|
|
141
|
+
):
|
|
142
|
+
return frozenset()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def check_deprecated_rules(
|
|
146
|
+
doc: TOMLDocument,
|
|
147
|
+
is_ruff_toml: bool = False,
|
|
148
|
+
strict: bool = False,
|
|
149
|
+
_deprecated_codes: frozenset[str] | None = None,
|
|
150
|
+
exclude: Iterable[str] = (),
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""Warn if the merged config references any deprecated Ruff rules.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
doc: The merged TOML document.
|
|
156
|
+
is_ruff_toml: True if the document is a standalone ruff.toml.
|
|
157
|
+
strict: If True, treat deprecated rules as a failure.
|
|
158
|
+
_deprecated_codes: Optional set of deprecated codes to use (for testing).
|
|
159
|
+
exclude: List of keys excluded from the ruff configuration.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
True if no deprecated rules are found, or if strict=False.
|
|
163
|
+
False if strict=True and deprecated rules are found.
|
|
164
|
+
"""
|
|
165
|
+
deprecated_codes = (
|
|
166
|
+
_deprecated_codes if _deprecated_codes is not None else _get_deprecated_rule_codes()
|
|
167
|
+
)
|
|
168
|
+
if not deprecated_codes:
|
|
169
|
+
return True # Nothing to check (ruff not found or returned no deprecated rules)
|
|
170
|
+
|
|
171
|
+
if is_ruff_toml:
|
|
172
|
+
lint_section = doc.get("lint", {})
|
|
173
|
+
else:
|
|
174
|
+
lint_section = doc.get("tool", {}).get("ruff", {}).get("lint", {})
|
|
175
|
+
|
|
176
|
+
if not isinstance(lint_section, dict):
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
found_deprecated = False
|
|
180
|
+
for key in _RULE_LIST_KEYS:
|
|
181
|
+
full_key = f"lint.{key}" if is_ruff_toml else f"tool.ruff.lint.{key}"
|
|
182
|
+
if key in exclude or full_key in exclude:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
rule_list = lint_section.get(key, [])
|
|
186
|
+
if not isinstance(rule_list, list):
|
|
187
|
+
continue
|
|
188
|
+
for rule_code in rule_list:
|
|
189
|
+
rule_str = str(rule_code).strip().upper()
|
|
190
|
+
if rule_str in deprecated_codes:
|
|
191
|
+
found_deprecated = True
|
|
192
|
+
msg = (
|
|
193
|
+
f"Upstream config uses deprecated rule '{rule_str}' "
|
|
194
|
+
f"(found in [{'lint' if is_ruff_toml else 'tool.ruff.lint'}].{key}). "
|
|
195
|
+
"This rule may be removed in a future Ruff version."
|
|
196
|
+
)
|
|
197
|
+
if strict:
|
|
198
|
+
LOGGER.error(f"❌ {msg}")
|
|
199
|
+
else:
|
|
200
|
+
LOGGER.warning(f"⚠️ {msg}")
|
|
201
|
+
|
|
202
|
+
return not (strict and found_deprecated)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def validate_toml_syntax(doc: TOMLDocument) -> bool:
|
|
206
|
+
"""Return True if the document serializes to valid TOML.
|
|
207
|
+
|
|
208
|
+
tomlkit always produces valid TOML, so this catches any edge cases
|
|
209
|
+
where serialization itself raises an unexpected exception.
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
tomlkit.parse(doc.as_string())
|
|
213
|
+
return True # noqa: TRY300
|
|
214
|
+
except tomlkit.exceptions.TOMLKitError:
|
|
215
|
+
LOGGER.exception("❌ Merged config failed TOML syntax check")
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def validate_ruff_accepts_config(
|
|
220
|
+
doc: TOMLDocument, is_ruff_toml: bool = False, strict: bool = False
|
|
221
|
+
) -> bool:
|
|
222
|
+
"""Return True if Ruff accepts the merged configuration.
|
|
223
|
+
|
|
224
|
+
Writes the merged config to a temporary file and runs:
|
|
225
|
+
ruff check --isolated --config <tmp> <dummy-py-file>
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
doc: The merged TOML document to validate.
|
|
229
|
+
is_ruff_toml: True if the document is a ruff.toml (not pyproject.toml).
|
|
230
|
+
strict: If True, treat configuration warnings (deprecated rules, etc.) as failures.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if Ruff accepts the config without errors, False otherwise.
|
|
234
|
+
"""
|
|
235
|
+
# The filename matters: ruff.toml uses flat format, pyproject.toml uses [tool.ruff]
|
|
236
|
+
config_filename = "ruff.toml" if is_ruff_toml else "pyproject.toml"
|
|
237
|
+
|
|
238
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
|
239
|
+
tmp_path = pathlib.Path(tmp_dir) / config_filename
|
|
240
|
+
tmp_path.write_text(doc.as_string(), encoding="utf-8")
|
|
241
|
+
|
|
242
|
+
# Create a minimal dummy Python file for ruff to lint
|
|
243
|
+
dummy_py = pathlib.Path(tmp_dir) / "dummy.py"
|
|
244
|
+
dummy_py.write_text("# ruff-sync config validation\n", encoding="utf-8")
|
|
245
|
+
|
|
246
|
+
config_flag = f"--config={tmp_path}"
|
|
247
|
+
# Using --config should be enough to isolate from project config
|
|
248
|
+
cmd = ["ruff", "check", config_flag, str(dummy_py)]
|
|
249
|
+
LOGGER.debug(f"Running ruff config validation: {' '.join(cmd)}")
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
result = subprocess.run( # noqa: S603
|
|
253
|
+
cmd,
|
|
254
|
+
capture_output=True,
|
|
255
|
+
text=True,
|
|
256
|
+
timeout=30,
|
|
257
|
+
check=False,
|
|
258
|
+
)
|
|
259
|
+
except FileNotFoundError:
|
|
260
|
+
LOGGER.warning("⚠️ `ruff` not found on PATH — skipping Ruff config validation.")
|
|
261
|
+
return True # Soft fail: don't block if ruff isn't installed
|
|
262
|
+
except subprocess.TimeoutExpired:
|
|
263
|
+
LOGGER.warning("⚠️ Ruff config validation timed out — skipping.")
|
|
264
|
+
return True # Soft fail on timeout
|
|
265
|
+
|
|
266
|
+
# Exit 0 = no issues found, exit 1 = issues found — both mean ruff
|
|
267
|
+
# parsed the config successfully. Exit 2 = config/usage error.
|
|
268
|
+
if result.returncode not in (0, 1):
|
|
269
|
+
LOGGER.error(
|
|
270
|
+
f"❌ Ruff rejected the merged config (exit {result.returncode}):\n"
|
|
271
|
+
f"{result.stderr.strip()}"
|
|
272
|
+
)
|
|
273
|
+
return False
|
|
274
|
+
|
|
275
|
+
# If strict mode is enabled, check stderr for configuration warnings
|
|
276
|
+
if strict and result.stderr:
|
|
277
|
+
# Ruff emits "warning: ..." or "deprecated ..." to stderr for config issues
|
|
278
|
+
# that aren't fatal enough to cause exit code 2.
|
|
279
|
+
lower_stderr = result.stderr.lower()
|
|
280
|
+
if "warning:" in lower_stderr or "deprecated" in lower_stderr:
|
|
281
|
+
LOGGER.error(
|
|
282
|
+
"❌ Ruff validation warning(s) detected in strict mode:\n"
|
|
283
|
+
f"{result.stderr.strip()}"
|
|
284
|
+
)
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
return True
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def validate_merged_config(
|
|
291
|
+
doc: TOMLDocument,
|
|
292
|
+
is_ruff_toml: bool = False,
|
|
293
|
+
strict: bool = False,
|
|
294
|
+
exclude: Iterable[str] = (),
|
|
295
|
+
) -> bool:
|
|
296
|
+
"""Run all validation checks on the merged TOML document.
|
|
297
|
+
|
|
298
|
+
Returns True only if all checks pass. Returns False and logs errors
|
|
299
|
+
if any check fails.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
doc: The merged TOML document to validate.
|
|
303
|
+
is_ruff_toml: True if the document is a standalone ruff.toml.
|
|
304
|
+
strict: If True, treat configuration warnings as hard failures.
|
|
305
|
+
exclude: List of keys excluded from the ruff configuration.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
True if all validation checks pass, False otherwise.
|
|
309
|
+
"""
|
|
310
|
+
if not validate_toml_syntax(doc):
|
|
311
|
+
return False
|
|
312
|
+
if not validate_ruff_accepts_config(doc, is_ruff_toml=is_ruff_toml, strict=strict):
|
|
313
|
+
return False
|
|
314
|
+
|
|
315
|
+
version_ok = True
|
|
316
|
+
if not is_ruff_toml:
|
|
317
|
+
version_ok = check_python_version_consistency(doc, strict=strict, exclude=exclude)
|
|
318
|
+
|
|
319
|
+
deprecated_ok = check_deprecated_rules(
|
|
320
|
+
doc, is_ruff_toml=is_ruff_toml, strict=strict, exclude=exclude
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return version_ok and deprecated_ok
|