ruff-sync 0.1.4.dev0__tar.gz → 0.1.4.dev1__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.4.dev1/.agents/skills/ruff-sync-usage/references/ci-integration.md +213 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.pre-commit-config.yaml +1 -1
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/PKG-INFO +1 -1
- ruff_sync-0.1.4.dev1/docs/ci-integration.md +170 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/pyproject.toml +1 -1
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/cli.py +1 -1
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/constants.py +1 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/core.py +113 -6
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/formatters.py +245 -1
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_basic.py +2 -2
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_check.py +208 -2
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_formatters.py +95 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/uv.lock +1 -1
- ruff_sync-0.1.4.dev0/.agents/skills/ruff-sync-usage/references/ci-integration.md +0 -134
- ruff_sync-0.1.4.dev0/docs/ci-integration.md +0 -103
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/TESTING.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/formatters-architecture.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/gitlab-reports.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/issue-102-context.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/gh-issues/SKILL.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/examples.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/release-notes-generation/SKILL.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/ruff-sync-usage/SKILL.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/ruff-sync-usage/references/configuration.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/skills/ruff-sync-usage/references/troubleshooting.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.git-blame-ignore-revs +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.github/dependabot.yml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.github/workflows/ci.yaml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.github/workflows/docs.yaml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.gitignore +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/.pre-commit-hooks.yaml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/AGENTS.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/CONTRIBUTING.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/LICENSE.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/README.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/codecov.yml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/configs/data-science-engineering/ruff.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/configs/fastapi/ruff.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/configs/kitchen-sink/ruff.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/agent-skill.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/assets/favicon.png +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/assets/github-pr-annotation.png +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/assets/logo.png +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/assets/ruff_sync_banner.png +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/best-practices.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/configuration.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/contributing.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/examples/advanced-config.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/examples/basic-config.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/gen_ref_pages.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/index.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/installation.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/pre-commit.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/pre-defined-configs.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/troubleshooting.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/url-resolution.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/docs/usage.md +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/mkdocs.yml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/scripts/check_dogfood.sh +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/scripts/gitclone_dogfood.sh +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/scripts/pull_dogfood.sh +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/skills-lock.json +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/__init__.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/__main__.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/src/ruff_sync/pre_commit.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tasks.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/__init__.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/conftest.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/ruff.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_config_validation.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_constants.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_corner_cases.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_deprecation.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_e2e.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_git_fetch.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_pre_commit.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_project.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_scaffold.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_serialization.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_url_handling.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/test_whitespace.py +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.4.dev0 → ruff_sync-0.1.4.dev1}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# CI Integration Recipes
|
|
2
|
+
|
|
3
|
+
## GitHub Actions
|
|
4
|
+
|
|
5
|
+
### Basic Drift Check
|
|
6
|
+
|
|
7
|
+
Add this step to any existing workflow (e.g., `.github/workflows/ci.yaml`):
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
- name: Check Ruff config is in sync
|
|
11
|
+
run: ruff-sync check --semantic --output-format github
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`--semantic` ignores cosmetic differences (comments, whitespace) — only real value or rule changes cause failure.
|
|
15
|
+
`--output-format github` creates inline PR annotations for errors and warnings.
|
|
16
|
+
|
|
17
|
+
### Full Workflow Example
|
|
18
|
+
|
|
19
|
+
Uses [`astral-sh/setup-uv`](https://github.com/astral-sh/setup-uv) — the official action that installs uv, adds it to PATH, and handles caching. No separate `setup-python` step needed.
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
name: Ruff sync check
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
push:
|
|
26
|
+
branches: [main]
|
|
27
|
+
pull_request:
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
ruff-sync-check:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Install uv
|
|
36
|
+
uses: astral-sh/setup-uv@v6
|
|
37
|
+
with:
|
|
38
|
+
version: "0.10.x" # pin to a minor range; Dependabot can keep this current
|
|
39
|
+
|
|
40
|
+
- name: Install ruff-sync
|
|
41
|
+
run: uv tool install ruff-sync
|
|
42
|
+
|
|
43
|
+
- name: Check Ruff config is in sync with upstream
|
|
44
|
+
run: ruff-sync check --semantic --output-format github
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### With Pre-commit Sync Check
|
|
48
|
+
|
|
49
|
+
To also verify the pre-commit hook version, add the `--pre-commit` flag:
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
- name: Check Ruff config and pre-commit hook
|
|
53
|
+
run: ruff-sync check --semantic --pre-commit --output-format github
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
(Note: For better consistency, you can instead set `pre-commit-version-sync = true` in your `pyproject.toml` — then `ruff-sync check --semantic` will automatically include this check.)
|
|
57
|
+
|
|
58
|
+
### SARIF Upload (GitHub Advanced Security)
|
|
59
|
+
|
|
60
|
+
For repositories with GitHub Advanced Security enabled, upload SARIF results to track drift findings in the **Security tab** and get per-key inline PR annotations that persist across runs:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
- name: Check Ruff config (SARIF)
|
|
64
|
+
run: ruff-sync check --output-format sarif > ruff-sync.sarif || true
|
|
65
|
+
|
|
66
|
+
- name: Upload SARIF results
|
|
67
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
68
|
+
with:
|
|
69
|
+
sarif_file: ruff-sync.sarif
|
|
70
|
+
category: ruff-sync
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The `|| true` ensures the upload step always runs even when `ruff-sync` exits 1 (drift detected). Without it, GitHub Actions would skip the upload step on failure.
|
|
74
|
+
|
|
75
|
+
> **Why SARIF over `--output-format github`?**
|
|
76
|
+
> The `github` format creates ephemeral workflow annotations that disappear once the check re-runs. SARIF findings are persisted in the Security tab, tracked as "introduced" and "resolved" across branches, and each drifted TOML key (`lint.select`, `target-version`, etc.) is a separate finding with a stable fingerprint — making it easy to trend configuration health over time.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## GitLab CI
|
|
81
|
+
|
|
82
|
+
Use the official [`ghcr.io/astral-sh/uv`](https://docs.astral.sh/uv/guides/integration/gitlab/) image — uv is already on the `PATH`, no install step needed.
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
variables:
|
|
86
|
+
UV_VERSION: "0.10"
|
|
87
|
+
PYTHON_VERSION: "3.12"
|
|
88
|
+
BASE_LAYER: alpine
|
|
89
|
+
UV_LINK_MODE: copy # required: GitLab mounts build dir separately
|
|
90
|
+
|
|
91
|
+
ruff-sync-check:
|
|
92
|
+
stage: lint
|
|
93
|
+
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
|
|
94
|
+
script:
|
|
95
|
+
- uvx ruff-sync check --semantic --output-format gitlab > gl-code-quality-report.json
|
|
96
|
+
artifacts:
|
|
97
|
+
when: always
|
|
98
|
+
reports:
|
|
99
|
+
codequality: gl-code-quality-report.json
|
|
100
|
+
paths:
|
|
101
|
+
- gl-code-quality-report.json
|
|
102
|
+
expire_in: 1 week
|
|
103
|
+
rules:
|
|
104
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
105
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### GitLab SAST Report / SARIF (Ultimate tier)
|
|
109
|
+
|
|
110
|
+
Use `--output-format sarif` to feed the GitLab [Security & Compliance dashboard](https://docs.gitlab.com/user/application_security/) via the `sast` artifact report type:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
variables:
|
|
114
|
+
UV_VERSION: "0.10"
|
|
115
|
+
PYTHON_VERSION: "3.12"
|
|
116
|
+
BASE_LAYER: alpine
|
|
117
|
+
UV_LINK_MODE: copy # required: GitLab mounts build dir separately
|
|
118
|
+
|
|
119
|
+
ruff-sync-sarif:
|
|
120
|
+
stage: lint
|
|
121
|
+
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
|
|
122
|
+
script:
|
|
123
|
+
- uvx ruff-sync check --output-format sarif > ruff-sync.sarif
|
|
124
|
+
artifacts:
|
|
125
|
+
when: always # Upload even when ruff-sync exits 1 (drift detected)
|
|
126
|
+
reports:
|
|
127
|
+
sast: ruff-sync.sarif
|
|
128
|
+
paths:
|
|
129
|
+
- ruff-sync.sarif
|
|
130
|
+
expire_in: 1 week
|
|
131
|
+
rules:
|
|
132
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
> **Why SARIF over `--output-format gitlab` (codequality)?**
|
|
136
|
+
>
|
|
137
|
+
> | Concern | `codequality` | `sarif` |
|
|
138
|
+
> |---------|--------------|--------|
|
|
139
|
+
> | GitLab tier | Free (MR widget), Ultimate (inline diff) | Ultimate (Security dashboard) |
|
|
140
|
+
> | GitHub support | ❌ | ✅ via `upload-sarif` |
|
|
141
|
+
> | Per-key findings | ❌ one issue per file | ✅ one finding per drifted TOML key |
|
|
142
|
+
> | Finding persistence | MR widget only | Security tab, tracked across branches |
|
|
143
|
+
> | Portability | GitLab only | GitHub, GitLab, SonarQube, IDE extensions |
|
|
144
|
+
>
|
|
145
|
+
> **Rule of thumb**: use `codequality` for lightweight GitLab-native linting feedback; use `sarif` when you need cross-platform compatibility or want findings tracked in a security/code-scanning dashboard.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Pre-commit Hook
|
|
150
|
+
|
|
151
|
+
Run `ruff-sync check` as a pre-commit hook to catch drift before every commit:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
# .pre-commit-config.yaml
|
|
155
|
+
- repo: https://github.com/Kilo59/ruff-sync
|
|
156
|
+
rev: v0.1.3 # pin to a release tag
|
|
157
|
+
hooks:
|
|
158
|
+
- id: ruff-sync-check
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The hook runs `ruff-sync check --semantic` automatically. Update `rev` to the latest ruff-sync version.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Makefile
|
|
166
|
+
|
|
167
|
+
```makefile
|
|
168
|
+
.PHONY: sync-check sync
|
|
169
|
+
|
|
170
|
+
sync-check:
|
|
171
|
+
ruff-sync check --semantic
|
|
172
|
+
|
|
173
|
+
sync:
|
|
174
|
+
ruff-sync
|
|
175
|
+
git diff pyproject.toml
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Deciding: `--semantic` vs. Full String Check
|
|
181
|
+
|
|
182
|
+
| Mode | Fails on | Use when |
|
|
183
|
+
|------|---------|---------|
|
|
184
|
+
| `ruff-sync check --semantic` | Value/rule differences only | CI — avoids false positives from local comment edits |
|
|
185
|
+
| `ruff-sync check` | Any string difference (comments, whitespace, values) | Enforcing exact config file consistency |
|
|
186
|
+
|
|
187
|
+
Recommendation: **use `--semantic` in CI** and save the full-string check for auditing purposes.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Dogfooding (Self-Check)
|
|
192
|
+
|
|
193
|
+
If `ruff-sync` is configured in the project's own `pyproject.toml` (the standard case), just run:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
ruff-sync check
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
No URL argument needed — it reads `upstream` from `[tool.ruff-sync]`.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Exit Codes
|
|
204
|
+
|
|
205
|
+
| Code | Meaning |
|
|
206
|
+
|------|----------|
|
|
207
|
+
| **0** | In sync — no drift detected |
|
|
208
|
+
| **1** | Config drift — `[tool.ruff]` values differ from upstream |
|
|
209
|
+
| **2** | CLI usage error — invalid arguments (reserved by argparse) |
|
|
210
|
+
| **3** | Pre-commit hook drift — use `--pre-commit` flag to enable this check |
|
|
211
|
+
| **4** | Upstream unreachable — HTTP error or network failure |
|
|
212
|
+
|
|
213
|
+
All non-zero codes cause a CI step to fail, which is the desired behaviour. To diagnose which failure occurred, check the exit code with `echo $?` after the `ruff-sync check` call.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ruff-sync
|
|
3
|
-
Version: 0.1.4.
|
|
3
|
+
Version: 0.1.4.dev1
|
|
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/
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# CI Integration
|
|
2
|
+
|
|
3
|
+
`ruff-sync` is designed to be run in CI pipelines to ensure that all repositories in an organization stay in sync with the central standards.
|
|
4
|
+
|
|
5
|
+
## Usage in CI
|
|
6
|
+
|
|
7
|
+
The best way to use `ruff-sync` in CI is with the `check` command. If the configuration has drifted, `ruff-sync check` will exit with a non-zero code, failing the build.
|
|
8
|
+
|
|
9
|
+
### GitHub Actions
|
|
10
|
+
|
|
11
|
+
We recommend using `uv` to run `ruff-sync` in GitHub Actions.
|
|
12
|
+
|
|
13
|
+
#### Basic Check
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
name: "Standards Check"
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
pull_request:
|
|
20
|
+
branches: [main]
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
ruff-sync:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v5
|
|
28
|
+
- name: Check Ruff Config
|
|
29
|
+
run: uvx ruff-sync check --semantic --output-format github
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
By using `--output-format github`, `ruff-sync` will emit special workflow commands that GitHub translates into inline annotations directly on your Pull Request's file diff.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
### SARIF Upload (GitHub Advanced Security)
|
|
37
|
+
|
|
38
|
+
For repositories with [GitHub Advanced Security](https://docs.github.com/en/get-started/learning-about-github/about-github-advanced-security) enabled, upload SARIF results to track drift findings in the **Security tab** and get per-key inline PR annotations:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: Check Ruff config (SARIF)
|
|
42
|
+
run: ruff-sync check --output-format sarif > ruff-sync.sarif || true
|
|
43
|
+
|
|
44
|
+
- name: Upload SARIF results
|
|
45
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
46
|
+
with:
|
|
47
|
+
sarif_file: ruff-sync.sarif
|
|
48
|
+
category: ruff-sync
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `|| true` ensures the upload step always runs even when drift is detected (exit code 1).
|
|
52
|
+
|
|
53
|
+
> **Why SARIF over `--output-format github`?** The `github` format creates ephemeral PR annotations that disappear after the check re-runs. SARIF findings are persisted in the Security tab, tracked as "introduced" and "resolved" across branches, and each drifted TOML key (`lint.select`, `target-version`, etc.) appears as a separate, deduplicated finding.
|
|
54
|
+
|
|
55
|
+
### Automated Sync PRs
|
|
56
|
+
|
|
57
|
+
Instead of just checking, you can have a bot automatically open a PR when the upstream configuration changes.
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
name: "Upstream Sync"
|
|
61
|
+
|
|
62
|
+
on:
|
|
63
|
+
schedule:
|
|
64
|
+
- cron: '0 0 * * 1' # Every Monday at midnight
|
|
65
|
+
workflow_dispatch:
|
|
66
|
+
|
|
67
|
+
jobs:
|
|
68
|
+
sync:
|
|
69
|
+
runs-on: ubuntu-latest
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- uses: astral-sh/setup-uv@v5
|
|
73
|
+
- name: Pull upstream
|
|
74
|
+
run: uvx ruff-sync
|
|
75
|
+
- name: Create Pull Request
|
|
76
|
+
uses: peter-evans/create-pull-request@v6
|
|
77
|
+
with:
|
|
78
|
+
commit-message: "chore: sync ruff configuration from upstream"
|
|
79
|
+
title: "chore: sync ruff configuration"
|
|
80
|
+
body: "This PR synchronizes the Ruff configuration with the upstream source."
|
|
81
|
+
branch: "ruff-sync-update"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### GitLab CI
|
|
85
|
+
|
|
86
|
+
#### Code Quality Report (Free tier)
|
|
87
|
+
|
|
88
|
+
Use `--output-format gitlab` to produce a [GitLab Code Quality](https://docs.gitlab.com/ci/testing/code_quality/) report. This appears in the MR widget on the Free tier and as inline diff annotations on Ultimate.
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
ruff-sync-check:
|
|
92
|
+
stage: lint
|
|
93
|
+
image: ghcr.io/astral-sh/uv:latest
|
|
94
|
+
script:
|
|
95
|
+
- uvx ruff-sync check --semantic --output-format gitlab > gl-code-quality-report.json
|
|
96
|
+
artifacts:
|
|
97
|
+
when: always # Upload even when ruff-sync exits 1 (drift detected)
|
|
98
|
+
reports:
|
|
99
|
+
codequality: gl-code-quality-report.json
|
|
100
|
+
paths:
|
|
101
|
+
- gl-code-quality-report.json # Also expose for download/browsing
|
|
102
|
+
expire_in: 1 week
|
|
103
|
+
rules:
|
|
104
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
105
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### SAST Report / SARIF (Ultimate tier)
|
|
109
|
+
|
|
110
|
+
Use `--output-format sarif` to feed the GitLab [Security & Compliance dashboard](https://docs.gitlab.com/user/application_security/) via the `sast` artifact report type:
|
|
111
|
+
|
|
112
|
+
```yaml
|
|
113
|
+
ruff-sync-sarif:
|
|
114
|
+
stage: lint
|
|
115
|
+
image: ghcr.io/astral-sh/uv:latest
|
|
116
|
+
script:
|
|
117
|
+
- uvx ruff-sync check --output-format sarif > ruff-sync.sarif
|
|
118
|
+
artifacts:
|
|
119
|
+
when: always
|
|
120
|
+
reports:
|
|
121
|
+
sast: ruff-sync.sarif
|
|
122
|
+
paths:
|
|
123
|
+
- ruff-sync.sarif
|
|
124
|
+
expire_in: 1 week
|
|
125
|
+
rules:
|
|
126
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Why SARIF over `codequality`?** SARIF is a portable, vendor-neutral format — the same file works for GitHub Advanced Security, GitLab SAST, SonarQube, and IDE extensions. It also carries per-key granularity: each drifted TOML key is a separate finding with a stable fingerprint that is tracked as "introduced" or "resolved" across pipeline runs. Use `codequality` for lightweight GitLab-native linting feedback; use `sarif` when you need cross-platform compatibility or want findings tracked in a security dashboard.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
You can use `ruff-sync` with `pre-commit` to ensure your configuration is always in sync before pushing.
|
|
134
|
+
|
|
135
|
+
See the [Pre-commit Guide](pre-commit.md) for details on using the official hooks.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Exit Codes
|
|
140
|
+
|
|
141
|
+
| Code | Meaning |
|
|
142
|
+
|------|----------|
|
|
143
|
+
| **0** | In sync — no drift detected |
|
|
144
|
+
| **1** | Config drift — `[tool.ruff]` values differ from upstream |
|
|
145
|
+
| **2** | CLI usage error — invalid arguments (reserved by argparse) |
|
|
146
|
+
| **3** | Pre-commit hook drift — use `--pre-commit` flag to enable this check |
|
|
147
|
+
| **4** | Upstream unreachable — HTTP error or network failure |
|
|
148
|
+
|
|
149
|
+
All non-zero codes cause a CI step to fail. Use `artifacts: when: always` (GitLab) or `if: always()` (GitHub Actions) to ensure report artifacts are uploaded even when the job fails.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 💡 Best Practices
|
|
154
|
+
|
|
155
|
+
> [!TIP]
|
|
156
|
+
> Read the complete [Best Practices](best-practices.md) guide for a broader look at organizing `ruff-sync` deployments, including when semantic checks should be blocking vs. informational.
|
|
157
|
+
|
|
158
|
+
### Use `--semantic`
|
|
159
|
+
|
|
160
|
+
In CI, you usually only care about the functional configuration. Using `--semantic` ensures that minor formatting changes don't break your builds, while still guaranteeing that the actual rules are identical.
|
|
161
|
+
|
|
162
|
+
### Handle Exclusions Properly
|
|
163
|
+
|
|
164
|
+
If your project intentionally diverges from the upstream (e.g., using different `per-file-ignores` or ignoring a specific rule), ensure those overrides are listed in the `[tool.ruff-sync]` `exclude` list in your `pyproject.toml`.
|
|
165
|
+
|
|
166
|
+
The `check` command respects your local `exclude` list. If you exclude a setting, `ruff-sync check` will completely ignore it when comparing against the upstream, ensuring that intended deviations never cause CI to fail!
|
|
167
|
+
|
|
168
|
+
### Use a Dedicated Workflow
|
|
169
|
+
|
|
170
|
+
Running `ruff-sync` as a separate job in your linting workflow makes it easy to identify when a failure is due to configuration drift rather than a code quality issue.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.1.4.
|
|
3
|
+
version = "0.1.4.dev1"
|
|
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 = [
|
|
@@ -875,9 +875,11 @@ def _print_diff(
|
|
|
875
875
|
|
|
876
876
|
|
|
877
877
|
def _check_pre_commit_sync(args: Arguments, fmt: ResultFormatter) -> int | None:
|
|
878
|
-
"""Return exit code
|
|
878
|
+
"""Return exit code 3 if pre-commit hook version is out of sync, otherwise None.
|
|
879
879
|
|
|
880
880
|
Shared helper to avoid duplicating the pre-commit synchronization logic.
|
|
881
|
+
Exit code 3 is reserved for pre-commit hook drift to avoid collision with
|
|
882
|
+
argparse (2) and config drift (1).
|
|
881
883
|
"""
|
|
882
884
|
if getattr(args, "pre_commit", False) and not sync_pre_commit(pathlib.Path.cwd(), dry_run=True):
|
|
883
885
|
repo_root = pathlib.Path.cwd()
|
|
@@ -888,10 +890,112 @@ def _check_pre_commit_sync(args: Arguments, fmt: ResultFormatter) -> int | None:
|
|
|
888
890
|
logger=LOGGER,
|
|
889
891
|
file_path=file_path,
|
|
890
892
|
)
|
|
891
|
-
return
|
|
893
|
+
return 3
|
|
892
894
|
return None
|
|
893
895
|
|
|
894
896
|
|
|
897
|
+
def _find_changed_keys(
|
|
898
|
+
source: Any,
|
|
899
|
+
merged: Any,
|
|
900
|
+
prefix: str = "",
|
|
901
|
+
) -> list[str]:
|
|
902
|
+
"""Return a list of dotted TOML keys that differ between *source* and *merged*.
|
|
903
|
+
|
|
904
|
+
Recursively walks both tables and returns keys whose leaf values have
|
|
905
|
+
changed or that are present only in *merged* (added by upstream). Keys
|
|
906
|
+
that exist only in *source* (local-only additions) are intentionally
|
|
907
|
+
excluded — ruff-sync never removes local keys.
|
|
908
|
+
|
|
909
|
+
Args:
|
|
910
|
+
source: The original (local) TOML table or value.
|
|
911
|
+
merged: The merged (upstream-applied) TOML table or value.
|
|
912
|
+
prefix: Dotted key prefix built up during recursion.
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
A list of dotted key paths, e.g. ``["lint.select", "target-version"]``.
|
|
916
|
+
"""
|
|
917
|
+
changed: list[str] = []
|
|
918
|
+
|
|
919
|
+
source_is_mapping = isinstance(source, Mapping)
|
|
920
|
+
merged_is_mapping = isinstance(merged, Mapping)
|
|
921
|
+
|
|
922
|
+
if source_is_mapping and merged_is_mapping:
|
|
923
|
+
for key, merged_val in merged.items():
|
|
924
|
+
full_key = f"{prefix}.{key}" if prefix else key
|
|
925
|
+
source_val = source.get(key)
|
|
926
|
+
if source_val is None and key not in source:
|
|
927
|
+
# Key is new (only in merged)
|
|
928
|
+
changed.append(full_key)
|
|
929
|
+
else:
|
|
930
|
+
# Unwrap tomlkit proxy objects before comparing.
|
|
931
|
+
# Note: source_val is always set here because the key exists in source
|
|
932
|
+
# (the is None + key not in guard above handles the absent case).
|
|
933
|
+
src_unwrapped = (
|
|
934
|
+
source_val.unwrap()
|
|
935
|
+
if source_val is not None and hasattr(source_val, "unwrap")
|
|
936
|
+
else source_val
|
|
937
|
+
)
|
|
938
|
+
mrg_unwrapped = merged_val.unwrap() if hasattr(merged_val, "unwrap") else merged_val
|
|
939
|
+
nested = _find_changed_keys(src_unwrapped, mrg_unwrapped, prefix=full_key)
|
|
940
|
+
if nested:
|
|
941
|
+
changed.extend(nested)
|
|
942
|
+
elif src_unwrapped != mrg_unwrapped:
|
|
943
|
+
changed.append(full_key)
|
|
944
|
+
elif source_is_mapping != merged_is_mapping:
|
|
945
|
+
# Structural type mismatch (table vs scalar or vice-versa): treat the
|
|
946
|
+
# whole node as changed rather than attempting a meaningless value
|
|
947
|
+
# comparison between incompatible TOML node types.
|
|
948
|
+
changed.append(prefix or ".")
|
|
949
|
+
else:
|
|
950
|
+
# Both are leaf values — compare directly.
|
|
951
|
+
src_unwrapped = source.unwrap() if hasattr(source, "unwrap") else source
|
|
952
|
+
mrg_unwrapped = merged.unwrap() if hasattr(merged, "unwrap") else merged
|
|
953
|
+
if src_unwrapped != mrg_unwrapped:
|
|
954
|
+
changed.append(prefix or ".")
|
|
955
|
+
|
|
956
|
+
return changed
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
def _report_drift(
|
|
960
|
+
fmt: ResultFormatter,
|
|
961
|
+
rel_path: pathlib.Path,
|
|
962
|
+
source_doc: TOMLDocument,
|
|
963
|
+
merged_doc: TOMLDocument,
|
|
964
|
+
is_ruff_toml: bool,
|
|
965
|
+
) -> None:
|
|
966
|
+
"""Report configuration drift by emitting one error per changed key.
|
|
967
|
+
|
|
968
|
+
Emits one ``fmt.error()`` call per differing dotted key so that structured
|
|
969
|
+
formatters (SARIF, GitLab, JSON) can build per-key fingerprints. If no
|
|
970
|
+
granular keys are found (whitespace-only diff), falls back to a single
|
|
971
|
+
generic error.
|
|
972
|
+
"""
|
|
973
|
+
if is_ruff_toml:
|
|
974
|
+
source_section: Any = source_doc
|
|
975
|
+
merged_section: Any = merged_doc
|
|
976
|
+
else:
|
|
977
|
+
source_section = source_doc.get("tool", {}).get("ruff") or {}
|
|
978
|
+
merged_section = merged_doc.get("tool", {}).get("ruff") or {}
|
|
979
|
+
changed_keys = sorted(set(_find_changed_keys(source_section, merged_section)))
|
|
980
|
+
|
|
981
|
+
if changed_keys:
|
|
982
|
+
for drift_key in changed_keys:
|
|
983
|
+
fmt.error(
|
|
984
|
+
f"\u274c Key '{drift_key}' in {rel_path} is out of sync! "
|
|
985
|
+
"Run `ruff-sync` to update.",
|
|
986
|
+
file_path=rel_path,
|
|
987
|
+
logger=LOGGER,
|
|
988
|
+
drift_key=drift_key,
|
|
989
|
+
)
|
|
990
|
+
else:
|
|
991
|
+
# Structural change only (e.g. whitespace) — single generic error.
|
|
992
|
+
fmt.error(
|
|
993
|
+
f"\u274c Ruff configuration at {rel_path} is out of sync! Run `ruff-sync` to update.",
|
|
994
|
+
file_path=rel_path,
|
|
995
|
+
logger=LOGGER,
|
|
996
|
+
)
|
|
997
|
+
|
|
998
|
+
|
|
895
999
|
async def check(
|
|
896
1000
|
args: Arguments,
|
|
897
1001
|
) -> int:
|
|
@@ -974,10 +1078,13 @@ async def check(
|
|
|
974
1078
|
rel_path = _source_toml_path.relative_to(pathlib.Path.cwd())
|
|
975
1079
|
except ValueError:
|
|
976
1080
|
rel_path = _source_toml_path
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1081
|
+
|
|
1082
|
+
_report_drift(
|
|
1083
|
+
fmt=fmt,
|
|
1084
|
+
rel_path=rel_path,
|
|
1085
|
+
source_doc=source_doc,
|
|
1086
|
+
merged_doc=merged_doc,
|
|
1087
|
+
is_ruff_toml=is_ruff_toml_file(_source_toml_path.name),
|
|
981
1088
|
)
|
|
982
1089
|
|
|
983
1090
|
if args.diff:
|