ruff-sync 0.1.7.dev2__py3-none-any.whl → 0.1.9.dev0__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.
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: ruff-sync
3
+ description: >-
4
+ Configure and operate ruff-sync to synchronize Ruff linter settings across Python projects.
5
+ Use when the user wants to set up ruff-sync, sync Ruff config from an upstream source,
6
+ check for configuration drift, integrate ruff-sync into CI, troubleshoot sync issues,
7
+ or keep Ruff rules consistent across multiple repositories.
8
+ ---
9
+
10
+ # ruff-sync Usage
11
+
12
+ `ruff-sync` pulls a canonical Ruff configuration from an upstream URL and merges it into your local project, preserving comments, whitespace, and per-project overrides.
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ # 1. Install
18
+ uv tool install ruff-sync
19
+
20
+ # 2. Sync from an upstream repo (extracts [tool.ruff] / ruff.toml automatically)
21
+ ruff-sync https://github.com/my-org/standards
22
+
23
+ # 3. Review the changes before committing
24
+ git diff pyproject.toml
25
+ ```
26
+
27
+ ## Persist Configuration
28
+
29
+ Add to `pyproject.toml` so you don't need to pass CLI args every time:
30
+
31
+ ```toml
32
+ [tool.ruff-sync]
33
+ upstream = "https://github.com/my-org/standards"
34
+ exclude = [
35
+ "target-version", # each project uses its own Python version
36
+ "lint.per-file-ignores", # project-specific suppressions
37
+ "lint.ignore",
38
+ "lint.isort.known-first-party",
39
+ ]
40
+ ```
41
+
42
+ Then just run `ruff-sync` (no arguments needed).
43
+
44
+ See [references/configuration.md](references/configuration.md) for all config keys and defaults.
45
+
46
+ ## Common Workflows
47
+
48
+ ### Initial Project Setup
49
+
50
+ ```
51
+ Setup Progress:
52
+ - [ ] 1. Install ruff-sync (uv tool install ruff-sync)
53
+ - [ ] 2. Check for `.pre-commit-config.yaml` — if present, ensure the `ruff` hook is used
54
+ - [ ] 3. Add `[tool.ruff-sync]` to `pyproject.toml` with upstream URL and exclusions
55
+ - [ ] 4. If `.pre-commit-config.yaml` exists, set `pre-commit-version-sync = true` in `[tool.ruff-sync]`
56
+ - [ ] 5. Run `ruff-sync` to pull the upstream config
57
+ - [ ] 6. Review `git diff`
58
+ - [ ] 7. Fix any new lint errors: `uv run ruff check . --fix`
59
+ - [ ] 8. Commit
60
+ ```
61
+
62
+ ### Upstream Layers (multi-source)
63
+
64
+ Stack multiple upstream sources — later entries win on conflict:
65
+
66
+ ```toml
67
+ [tool.ruff-sync]
68
+ upstream = [
69
+ "https://github.com/my-org/python-standards", # base company rules
70
+ "https://github.com/my-org/ml-team-tweaks", # team-specific overrides
71
+ ]
72
+ ```
73
+
74
+ ### CI Drift Check
75
+
76
+ ```
77
+ CI Setup Progress:
78
+ - [ ] 1. Add ruff-sync check step to CI workflow (see references/ci-integration.md)
79
+ - [ ] 2. Decide: --semantic for value-only checks, or full string comparison
80
+ - [ ] 3. Set output format: --output-format github for PR annotations
81
+ - [ ] 4. Set exit-code expectations (0 = in sync, 1 = config drift, 2 = pre-commit only)
82
+ - [ ] 5. Verify locally: `ruff-sync check --semantic`
83
+ ```
84
+
85
+ Keep the `ruff-pre-commit` hook version in `.pre-commit-config.yaml` aligned with the project's Ruff version.
86
+
87
+ **Recommendation:** Always prefer the persistent TOML configuration over the ephemeral `--pre-commit` CLI flag.
88
+
89
+ ```toml
90
+ [tool.ruff-sync]
91
+ pre-commit-version-sync = true
92
+ ```
93
+
94
+ Then run `ruff-sync` — it updates the hook rev automatically. Exit code 2 means only the hook version is out of sync (Ruff config itself is fine).
95
+
96
+ ## Exit Codes
97
+
98
+ | Code | Meaning |
99
+ |------|---------|
100
+ | `0` | In sync — no action needed |
101
+ | `1` | Ruff config is out of sync with upstream |
102
+ | `2` | Config is in sync, but pre-commit hook version is stale (only when `--pre-commit` is active) |
103
+
104
+ ## URL Formats Supported
105
+
106
+ All of these work as the `upstream` value:
107
+
108
+ ```bash
109
+ ruff-sync https://github.com/my-org/standards # repo root
110
+ ruff-sync https://github.com/my-org/standards/tree/main/cfg # subdirectory
111
+ ruff-sync https://github.com/my-org/standards/blob/main/ruff.toml # specific file
112
+ ruff-sync https://raw.githubusercontent.com/my-org/standards/main/pyproject.toml
113
+ ruff-sync git@github.com:my-org/standards.git # SSH (shallow clone)
114
+ ```
115
+
116
+ ## CLI Reference (Short)
117
+
118
+ | Flag | Meaning |
119
+ |------|---------|
120
+ | `--validate` | Validate merged config with Ruff before writing; aborts if Ruff rejects it |
121
+ | `--strict` | Like `--validate` but also treats warnings (version mismatch, deprecated rules) as failures. Implies `--validate` |
122
+ | `--output-format` | `text` (default), `json`, `github`, `gitlab`, `sarif` (auto-detected in CI) |
123
+ | `--semantic` | Ignore whitespace/comments in `check` |
124
+ | `--pre-commit` | Sync `.pre-commit-config.yaml` hook version |
125
+ | `--save` | Persist CLI args to `pyproject.toml` |
126
+
127
+ ## Gotchas
128
+
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. 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.
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.
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.
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.
136
+ - **`--semantic` ignores comments and whitespace.** Use it in CI to avoid false positives from cosmetic local edits. Omit it for strict byte-for-byte checks.
137
+ - **SSH URLs trigger a shallow clone.** `git@github.com:...` URLs use `git clone --filter=blob:none --depth=1` — no `git` credential issues as long as SSH auth is configured.
138
+ - **Later upstreams win in `upstream` lists.** In a multi-source list, keys set by entry 2 overwrite keys from entry 1.
139
+ - **Config discovery order matters.** When targeting a directory, `ruff-sync` looks for `ruff.toml` -> `.ruff.toml` -> `pyproject.toml` in that order.
140
+ - **Pre-commit exit code 2 is intentional.** A `2` exit from `ruff-sync check` means the Ruff _config_ is fine, only the pre-commit hook tag is stale. You may want to treat this differently from a full config drift (exit 1) in CI.
141
+ - **Prefer TOML for pre-commit sync.** While `--pre-commit` works on the CLI, setting `pre-commit-version-sync = true` in `pyproject.toml` is the recommended way to ensure hook versioning stays consistent for all contributors.
142
+
143
+ ## References
144
+
145
+ - **[Configuration reference](references/configuration.md)** — All `[tool.ruff-sync]` keys, types, and defaults
146
+ - **[Troubleshooting](references/troubleshooting.md)** — Common errors and how to resolve them
147
+ - **[CI integration recipes](references/ci-integration.md)** — GitHub Actions, GitLab CI, pre-commit hook
@@ -0,0 +1,233 @@
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 and a structured Job Summary report.
16
+
17
+ ### With Strict Validation
18
+
19
+ To also catch deprecated rules and Python version mismatches in CI:
20
+
21
+ ```yaml
22
+ - name: Check Ruff config (with validation)
23
+ run: ruff-sync check --semantic --strict --output-format github
24
+ ```
25
+
26
+ `--strict` implies `--validate`. Deprecated rules and version mismatches are upgraded to failures.
27
+
28
+ > [!TIP]
29
+ > `ruff-sync` automatically groups multiple drifts in the same file into a single annotation to reduce PR noise.
30
+
31
+ ### Full Workflow Example
32
+
33
+ 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.
34
+
35
+ ```yaml
36
+ name: Ruff sync check
37
+
38
+ on:
39
+ push:
40
+ branches: [main]
41
+ pull_request:
42
+
43
+ jobs:
44
+ ruff-sync-check:
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - name: Install uv
50
+ uses: astral-sh/setup-uv@v6
51
+ with:
52
+ version: "0.10.x" # pin to a minor range; Dependabot can keep this current
53
+
54
+ - name: Install ruff-sync
55
+ run: uv tool install ruff-sync
56
+
57
+ - name: Check Ruff config is in sync with upstream
58
+ run: ruff-sync check --semantic --output-format github
59
+ ```
60
+
61
+ ### With Pre-commit Sync Check
62
+
63
+ To also verify the pre-commit hook version, add the `--pre-commit` flag:
64
+
65
+ ```yaml
66
+ - name: Check Ruff config and pre-commit hook
67
+ run: ruff-sync check --semantic --pre-commit --output-format github
68
+ ```
69
+
70
+ (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.)
71
+
72
+ ### SARIF Upload (GitHub Advanced Security)
73
+
74
+ 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:
75
+
76
+ ```yaml
77
+ - name: Check Ruff config (SARIF)
78
+ run: ruff-sync check --output-format sarif > ruff-sync.sarif || true
79
+
80
+ - name: Upload SARIF results
81
+ uses: github/codeql-action/upload-sarif@v3
82
+ with:
83
+ sarif_file: ruff-sync.sarif
84
+ category: ruff-sync
85
+ ```
86
+
87
+ 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.
88
+
89
+ > **Why SARIF over `--output-format github`?**
90
+ >
91
+ > | Feature | `github` | `sarif` |
92
+ > |---------|----------|---------|
93
+ > | **PR Feedback** | Inline annotations (grouped) | Inline annotations (per-key) |
94
+ > | **Job Summary** | ✅ Markdown table | ❌ (requires separate parsing) |
95
+ > | **Persistence** | Ephemeral (until re-run) | Persistent (Security tab) |
96
+ > | **Tracking** | Manual | Automated "introduced/resolved" |
97
+
98
+ ---
99
+
100
+ ## GitLab CI
101
+
102
+ 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.
103
+
104
+ ```yaml
105
+ variables:
106
+ UV_VERSION: "0.10"
107
+ PYTHON_VERSION: "3.12"
108
+ BASE_LAYER: alpine
109
+ UV_LINK_MODE: copy # required: GitLab mounts build dir separately
110
+
111
+ ruff-sync-check:
112
+ stage: lint
113
+ image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
114
+ script:
115
+ - uvx ruff-sync check --semantic --output-format gitlab > gl-code-quality-report.json
116
+ artifacts:
117
+ when: always
118
+ reports:
119
+ codequality: gl-code-quality-report.json
120
+ paths:
121
+ - gl-code-quality-report.json
122
+ expire_in: 1 week
123
+ rules:
124
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
125
+ - if: '$CI_COMMIT_BRANCH == "main"'
126
+ ```
127
+
128
+ ### GitLab SAST Report / SARIF (Ultimate tier)
129
+
130
+ Use `--output-format sarif` to feed the GitLab [Security & Compliance dashboard](https://docs.gitlab.com/user/application_security/) via the `sast` artifact report type:
131
+
132
+ ```yaml
133
+ variables:
134
+ UV_VERSION: "0.10"
135
+ PYTHON_VERSION: "3.12"
136
+ BASE_LAYER: alpine
137
+ UV_LINK_MODE: copy # required: GitLab mounts build dir separately
138
+
139
+ ruff-sync-sarif:
140
+ stage: lint
141
+ image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
142
+ script:
143
+ - uvx ruff-sync check --output-format sarif > ruff-sync.sarif
144
+ artifacts:
145
+ when: always # Upload even when ruff-sync exits 1 (drift detected)
146
+ reports:
147
+ sast: ruff-sync.sarif
148
+ paths:
149
+ - ruff-sync.sarif
150
+ expire_in: 1 week
151
+ rules:
152
+ - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
153
+ ```
154
+
155
+ > **Why SARIF over `--output-format gitlab` (codequality)?**
156
+ >
157
+ > | Concern | `codequality` | `sarif` |
158
+ > |---------|--------------|--------|
159
+ > | GitLab tier | Free (MR widget), Ultimate (inline diff) | Ultimate (Security dashboard) |
160
+ > | GitHub support | ❌ | ✅ via `upload-sarif` |
161
+ > | Per-key findings | ❌ one issue per file | ✅ one finding per drifted TOML key |
162
+ > | Finding persistence | MR widget only | Security tab, tracked across branches |
163
+ > | Portability | GitLab only | GitHub, GitLab, SonarQube, IDE extensions |
164
+ >
165
+ > **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.
166
+
167
+ ---
168
+
169
+ ## Pre-commit Hook
170
+
171
+ Run `ruff-sync check` as a pre-commit hook to catch drift before every commit:
172
+
173
+ ```yaml
174
+ # .pre-commit-config.yaml
175
+ - repo: https://github.com/Kilo59/ruff-sync
176
+ rev: v0.1.6 # pin to a release tag
177
+ hooks:
178
+ - id: ruff-sync-check
179
+ ```
180
+
181
+ The hook runs `ruff-sync check --semantic` automatically. Update `rev` to the latest ruff-sync version.
182
+
183
+ ---
184
+
185
+ ## Makefile
186
+
187
+ ```makefile
188
+ .PHONY: sync-check sync
189
+
190
+ sync-check:
191
+ ruff-sync check --semantic
192
+
193
+ sync:
194
+ ruff-sync
195
+ git diff pyproject.toml
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Deciding: `--semantic` vs. Full String Check
201
+
202
+ | Mode | Fails on | Use when |
203
+ |------|---------|---------|
204
+ | `ruff-sync check --semantic` | Value/rule differences only | CI — avoids false positives from local comment edits |
205
+ | `ruff-sync check` | Any string difference (comments, whitespace, values) | Enforcing exact config file consistency |
206
+
207
+ Recommendation: **use `--semantic` in CI** and save the full-string check for auditing purposes.
208
+
209
+ ---
210
+
211
+ ## Dogfooding (Self-Check)
212
+
213
+ If `ruff-sync` is configured in the project's own `pyproject.toml` (the standard case), just run:
214
+
215
+ ```bash
216
+ ruff-sync check
217
+ ```
218
+
219
+ No URL argument needed — it reads `upstream` from `[tool.ruff-sync]`.
220
+
221
+ ---
222
+
223
+ ## Exit Codes
224
+
225
+ | Code | Meaning |
226
+ |------|----------|
227
+ | **0** | In sync — no drift detected |
228
+ | **1** | Config drift (`check`), validation failure (`pull --validate`), or sync error |
229
+ | **2** | CLI usage error — invalid arguments (reserved by argparse) |
230
+ | **3** | Pre-commit hook drift — use `--pre-commit` flag to enable this check |
231
+ | **4** | Upstream unreachable — HTTP error or network failure |
232
+
233
+ 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.
@@ -0,0 +1,164 @@
1
+ # ruff-sync Configuration Reference
2
+
3
+ All keys live under `[tool.ruff-sync]` in `pyproject.toml`.
4
+
5
+ ## Keys
6
+
7
+ ### `upstream` *(required unless passed on CLI)*
8
+
9
+ The URL(s) of your canonical Ruff configuration. Accepts a single string or a list. When a list is given, sources are merged in order — later entries win on conflict.
10
+
11
+ ```toml
12
+ # Single source
13
+ upstream = "https://github.com/my-org/standards"
14
+
15
+ # Multiple sources (base + team overlay)
16
+ upstream = [
17
+ "https://github.com/my-org/python-standards",
18
+ "https://github.com/my-org/ml-team-tweaks",
19
+ ]
20
+ ```
21
+
22
+ **Supported URL formats (with automatic browser link resolution):**
23
+ - GitHub/GitLab repo root: `https://github.com/<org>/<repo>`
24
+ - Subdirectory (tree): `https://github.com/<org>/<repo>/tree/main/configs/ruff`
25
+ - Specific file (blob): `https://github.com/<org>/<repo>/blob/main/pyproject.toml`
26
+ - Raw URL: `https://raw.githubusercontent.com/<org>/<repo>/main/pyproject.toml`
27
+ - SSH (triggers shallow clone): `git@github.com:<org>/<repo>.git`
28
+
29
+ ---
30
+
31
+ ### `exclude` *(default: `["lint.per-file-ignores"]`)*
32
+
33
+ List of config keys to protect from being overwritten by upstream. Use dotted paths to reference nested keys.
34
+
35
+ ```toml
36
+ exclude = [
37
+ "target-version", # keep per-project Python version
38
+ "lint.per-file-ignores", # keep per-file suppressions
39
+ "lint.ignore", # keep repo-specific rule suppressions
40
+ "lint.isort.known-first-party", # keep first-party package list
41
+ "lint.flake8-tidy-imports.banned-api", # keep banned API list
42
+ "lint.pydocstyle.convention", # keep docstring style choice
43
+ ]
44
+ ```
45
+
46
+ **Key format:**
47
+ - Top-level keys: `"target-version"`, `"line-length"`
48
+ - Nested keys (dotted): `"lint.select"`, `"lint.per-file-ignores"`, `"format.quote-style"`
49
+ - These are ruff config key names, NOT TOML paths — do NOT include `tool.ruff.` prefix.
50
+
51
+ ---
52
+
53
+ ### `branch` *(default: `"main"`)*
54
+
55
+ The branch, tag, or commit hash to use when fetching from a Git repo URL.
56
+
57
+ ```toml
58
+ branch = "develop"
59
+ ```
60
+
61
+ ---
62
+
63
+ ### `path` *(default: `""`)*
64
+
65
+ Path inside the repository where the config lives, when it's not at the repo root.
66
+
67
+ ```toml
68
+ path = "configs/ruff"
69
+ ```
70
+
71
+ Useful when combined with a repo-root `upstream` URL:
72
+
73
+ ```toml
74
+ upstream = "git@github.com:my-org/standards.git"
75
+ path = "configs/strict"
76
+ ```
77
+
78
+ ---
79
+
80
+ ### `to` *(default: `"."`)*
81
+
82
+ Local target directory or file path to sync into.
83
+
84
+ ```toml
85
+ to = "services/api" # sync into a subdirectory of the monorepo
86
+ ```
87
+
88
+ ---
89
+
90
+ ### `pre-commit-version-sync` *(default: `false`)*
91
+
92
+ When `true`, `ruff-sync` also updates the `ruff-pre-commit` hook rev in `.pre-commit-config.yaml` to match the Ruff version installed in the project.
93
+
94
+ > [!TIP]
95
+ > This is the preferred way to enable pre-commit hook synchronization as it persists the setting in your project configuration.
96
+
97
+ ```toml
98
+ pre-commit-version-sync = true
99
+ ```
100
+
101
+ Requires a `- repo: https://github.com/astral-sh/ruff-pre-commit` entry in `.pre-commit-config.yaml`.
102
+
103
+ ---
104
+
105
+ ### `validate` *(default: `false`)*
106
+
107
+ When `true`, `ruff-sync` validates the merged configuration by running it through Ruff before writing to disk. If Ruff rejects the config (e.g., due to an unknown key), the sync is aborted and the local file is left unchanged.
108
+
109
+ ```toml
110
+ validate = true
111
+ ```
112
+
113
+ > [!NOTE]
114
+ > Validation requires `ruff` to be available on PATH. If `ruff` is not found, validation is skipped with a warning.
115
+
116
+ ---
117
+
118
+ ### `strict` *(default: `false`)*
119
+
120
+ When `true`, all validation warnings (Python version mismatches, deprecated rules) are treated as hard failures. Implies `validate = true`.
121
+
122
+ ```toml
123
+ strict = true
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Full Example
129
+
130
+ ```toml
131
+ [tool.ruff-sync]
132
+ upstream = [
133
+ "https://github.com/my-org/python-standards",
134
+ "https://github.com/my-org/backend-team-rules",
135
+ ]
136
+ exclude = [
137
+ "target-version",
138
+ "lint.per-file-ignores",
139
+ "lint.ignore",
140
+ "lint.isort.known-first-party",
141
+ ]
142
+ branch = "main"
143
+ path = "ruff"
144
+ to = "."
145
+ pre-commit-version-sync = true
146
+ validate = true
147
+ ```
148
+
149
+ ## CLI Overrides
150
+
151
+ All config keys have CLI equivalents. CLI values always win over `pyproject.toml`:
152
+
153
+ ```bash
154
+ ruff-sync https://github.com/my-org/standards --exclude lint.ignore --branch develop --output-format github
155
+ ```
156
+
157
+ ## Config Discovery for `ruff.toml` Projects
158
+
159
+ If the local target is a directory, ruff-sync looks for config in this order:
160
+ 1. `ruff.toml`
161
+ 2. `.ruff.toml`
162
+ 3. `pyproject.toml`
163
+
164
+ If the upstream is a `ruff.toml`, it syncs the root config object (no `[tool.ruff]` section extraction needed).
@@ -0,0 +1,157 @@
1
+ # Troubleshooting ruff-sync
2
+
3
+ ## Config Not Changing After Sync
4
+
5
+ **Symptom:** `ruff-sync` runs without error, but `git diff` shows no changes.
6
+
7
+ **Likely causes:**
8
+ 1. The key is in `exclude`. Check `[tool.ruff-sync] exclude` in your `pyproject.toml`.
9
+ 2. The local value already matches upstream — you're already in sync.
10
+ 3. The upstream URL resolved to the wrong file. Verify with `ruff-sync check --semantic` and look at the diff output.
11
+
12
+ ---
13
+
14
+ ## `UpstreamError` / HTTP Failure
15
+
16
+ **Symptom:**
17
+ ```
18
+ UpstreamError: Failed to fetch https://...
19
+ ```
20
+
21
+ **Steps:**
22
+ 1. Check the URL works in a browser or with `curl -I <url>`.
23
+ 2. For GitHub URLs, make sure you're pointing at a _raw_ or _tree/blob_ URL, not an HTML page.
24
+ 3. If using a private repo, the raw URL requires authentication. Use an SSH URL instead:
25
+ ```bash
26
+ ruff-sync git@github.com:my-org/private-standards.git
27
+ ```
28
+ 4. Check network/proxy settings if in a corporate environment.
29
+
30
+ ---
31
+
32
+ ## HTTP 404 on GitHub URL
33
+
34
+ **Symptom:** 404 when fetching a GitHub tree or blob URL.
35
+
36
+ **Cause:** The branch or path doesn't exist, or the repo is private.
37
+
38
+ **Fix:**
39
+ ```toml
40
+ [tool.ruff-sync]
41
+ upstream = "https://github.com/my-org/standards"
42
+ branch = "main" # make sure this branch exists
43
+ path = "configs" # make sure this path exists on that branch
44
+ ```
45
+
46
+ Validate: `curl https://github.com/my-org/standards/tree/main/configs` should return 200.
47
+
48
+ ---
49
+
50
+ ## `FileNotFoundError` — No Local Config
51
+
52
+ **Symptom:**
53
+ ```
54
+ FileNotFoundError: No pyproject.toml or ruff.toml found in .
55
+ ```
56
+
57
+ **Fix:** Use `--init` to scaffold a new config file before syncing:
58
+ ```bash
59
+ ruff-sync https://github.com/my-org/standards --init
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Pre-commit Version Mismatch (Exit Code 2)
65
+
66
+ **Symptom:** `ruff-sync check` exits with code 2 and a warning like:
67
+ ```
68
+ ⚠️ Pre-commit ruff hook version is out of sync: .pre-commit-config.yaml uses v0.9.3, project has ruff==0.10.0
69
+ ```
70
+
71
+ **This is not a config drift.** The Ruff configuration is in sync. Only the pre-commit hook tag is stale.
72
+
73
+ **Fix:**
74
+ ```bash
75
+ ruff-sync # pulls config AND updates the hook rev (if pre-commit-version-sync = true)
76
+ # or manually: update the 'rev:' line in .pre-commit-config.yaml
77
+ ```
78
+
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.
100
+
101
+ ---
102
+
103
+ ## Merge Produces Unexpected `tomlkit` Structure
104
+
105
+ **Symptom:** Synced `pyproject.toml` has repeated sections or oddly formatted keys.
106
+
107
+ **Cause:** Mixing dotted-key style (`lint.select = [...]`) with explicit table header style (`[tool.ruff.lint]`) in the same file.
108
+
109
+ **Fix:** Pick one style consistently. `ruff-sync` uses `tomlkit` which preserves formatting, but mixing styles can produce unexpected output.
110
+
111
+ ---
112
+
113
+ ## SSH Clone Fails
114
+
115
+ **Symptom:** Hangs or fails when using `git@github.com:...` URL.
116
+
117
+ **Cause:** SSH key not configured or not added to the agent.
118
+
119
+ **Fix:** Verify SSH auth first:
120
+ ```bash
121
+ ssh -T git@github.com
122
+ ```
123
+
124
+ Alternatively, switch to HTTPS:
125
+ ```toml
126
+ upstream = "https://github.com/my-org/standards"
127
+ ```
128
+
129
+ ---
130
+
131
+ ## `ruff-sync` Not Found After Install
132
+
133
+ **Symptom:** `ruff-sync: command not found`
134
+
135
+ **Fix:**
136
+ ```bash
137
+ # If installed with uv tool:
138
+ uv tool list # confirm it appears
139
+ # Ensure uv tools bin dir is on PATH:
140
+ echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
141
+
142
+ # If using pipx:
143
+ pipx ensurepath
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Getting More Debug Info
149
+
150
+ ```bash
151
+ # Show what config would be merged without applying it
152
+ ruff-sync check https://github.com/my-org/standards
153
+
154
+ # Show a diff of what changed
155
+ ruff-sync check --semantic # value-only diff
156
+ ruff-sync check # full string diff (catches comment/whitespace changes too)
157
+ ```
ruff_sync/config_io.py CHANGED
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
17
17
 
18
18
 
19
19
  @enum.unique
20
- class RuffConfigFileName(str, enum.Enum):
20
+ class RuffConfigFileName(enum.StrEnum):
21
21
  """Enumeration of Ruff configuration filenames."""
22
22
 
23
23
  PYPROJECT_TOML = "pyproject.toml"
ruff_sync/constants.py CHANGED
@@ -47,7 +47,7 @@ MISSING: Final[MissingType] = MissingType.SENTINEL
47
47
 
48
48
 
49
49
  @enum.unique
50
- class OutputFormat(str, enum.Enum):
50
+ class OutputFormat(enum.StrEnum):
51
51
  """Output formats for the CLI."""
52
52
 
53
53
  TEXT = "text"
@@ -63,7 +63,7 @@ class OutputFormat(str, enum.Enum):
63
63
 
64
64
 
65
65
  @enum.unique
66
- class ConfKey(str, enum.Enum):
66
+ class ConfKey(enum.StrEnum):
67
67
  """Centralized configuration keys for [tool.ruff-sync].
68
68
 
69
69
  These are the canonical names used in the pyproject.toml configuration file.
@@ -129,7 +129,7 @@ class ConfKey(str, enum.Enum):
129
129
 
130
130
  def resolve_defaults(
131
131
  branch: str | MissingType,
132
- path: str | None | MissingType,
132
+ path: str | MissingType | None,
133
133
  exclude: Iterable[str] | MissingType,
134
134
  ) -> tuple[str, str | None, Iterable[str]]:
135
135
  """Resolve MISSING sentinel values to their effective defaults.
ruff_sync/core.py CHANGED
@@ -697,51 +697,24 @@ async def fetch_upstreams_concurrently(
697
697
  UpstreamError: If one or more upstreams fail to fetch.
698
698
  """
699
699
  upstream_list = list(upstreams)
700
- if sys.version_info >= (3, 11):
701
- # Use structured concurrency on Python 3.11+
702
- tasks: list[asyncio.Task[FetchResult]] = []
703
- try:
704
- async with asyncio.TaskGroup() as tg:
705
- tasks = [
706
- tg.create_task(fetch_upstream_config(url, client, branch, path))
707
- for url in upstream_list
708
- ]
709
- return [t.result() for t in tasks]
710
- except BaseException as eg:
711
- if isinstance(eg, (asyncio.CancelledError, KeyboardInterrupt)):
712
- raise
713
- # TODO: Use `except*` once Python 3.11+ is the minimum supported version.
714
- # On Python 3.11+, TaskGroup raises an ExceptionGroup.
715
- # Catching it as Exception is safe and compatible with Python 3.10 syntax.
716
- errors = [
717
- (upstream_list[i], t.exception())
718
- for i, t in enumerate(tasks)
719
- if t.done() and t.exception() is not None
700
+ tasks: list[asyncio.Task[FetchResult]] = []
701
+ try:
702
+ async with asyncio.TaskGroup() as tg:
703
+ tasks = [
704
+ tg.create_task(fetch_upstream_config(url, client, branch, path))
705
+ for url in upstream_list
720
706
  ]
721
- if errors:
722
- raise UpstreamError(errors) from eg
723
- raise
724
- else:
725
- # Fallback for Python 3.10
726
- fetch_tasks = [fetch_upstream_config(url, client, branch, path) for url in upstream_list]
727
- results = await asyncio.gather(*fetch_tasks, return_exceptions=True)
728
-
729
- errors_list: list[tuple[URL, BaseException]] = []
730
- fetch_results: list[FetchResult] = []
731
-
732
- for i, res in enumerate(results):
733
- if isinstance(res, BaseException):
734
- errors_list.append((upstream_list[i], res))
735
- elif isinstance(res, FetchResult):
736
- fetch_results.append(res)
737
- else:
738
- msg = f"Unexpected result type from fetch: {type(res)}"
739
- raise TypeError(msg)
740
-
741
- if errors_list:
742
- raise UpstreamError(errors_list)
743
-
744
- return fetch_results
707
+ return [t.result() for t in tasks]
708
+ except* Exception as eg:
709
+ errors: list[tuple[URL, BaseException]] = []
710
+ for i, t in enumerate(tasks):
711
+ if t.done() and not t.cancelled():
712
+ exc = t.exception()
713
+ if exc is not None:
714
+ errors.append((upstream_list[i], exc))
715
+ if errors:
716
+ raise UpstreamError(errors) from eg
717
+ raise
745
718
 
746
719
 
747
720
  async def _merge_multiple_upstreams(
ruff_sync/types_.py CHANGED
@@ -2,9 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any, Literal, TypedDict
6
-
7
- from typing_extensions import NotRequired
5
+ from typing import Any, Literal, NotRequired, TypedDict
8
6
 
9
7
  RuleSyncStatus = Literal["Enabled", "Ignored", "Disabled"]
10
8
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: ruff-sync
3
- Version: 0.1.7.dev2
3
+ Version: 0.1.9.dev0
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/
@@ -17,14 +17,13 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Operating System :: OS Independent
18
18
  Classifier: Programming Language :: Python
19
19
  Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Programming Language :: Python :: 3.12
23
22
  Classifier: Programming Language :: Python :: 3.13
24
23
  Classifier: Programming Language :: Python :: 3.14
25
24
  Classifier: Topic :: Software Development :: Quality Assurance
26
- Requires-Python: >=3.10
27
- Requires-Dist: httpx2>=2.3.0
25
+ Requires-Python: >=3.11
26
+ Requires-Dist: httpx2>=2.9.1
28
27
  Requires-Dist: tomlkit<2.0.0,>=0.12.3
29
28
  Requires-Dist: typing-extensions>=4.5.0
30
29
  Provides-Extra: tui
@@ -38,6 +37,7 @@ Description-Content-Type: text/markdown
38
37
  <br>
39
38
  <a href="https://kilo59.github.io/ruff-sync/"><img src="https://img.shields.io/badge/docs-MkDocs-blue.svg" alt="Documentation"></a>
40
39
  <a href="https://pypi.org/project/ruff-sync/"><img src="https://img.shields.io/pypi/v/ruff-sync" alt="PyPI version"></a>
40
+ <a href="https://pepy.tech/projects/ruff-sync"><img src="https://static.pepy.tech/personalized-badge/ruff-sync?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads" alt="PyPI Downloads"></a>
41
41
  <a href="https://codecov.io/gh/Kilo59/ruff-sync"><img src="https://codecov.io/gh/Kilo59/ruff-sync/graph/badge.svg?token=kMZw0XtoFW" alt="codecov"></a>
42
42
  <a href="https://results.pre-commit.ci/latest/github/Kilo59/ruff-sync/main"><img src="https://results.pre-commit.ci/badge/github/Kilo59/ruff-sync/main.svg" alt="pre-commit.ci status"></a>
43
43
  <a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
@@ -353,7 +353,15 @@ $ ruff-sync check --semantic
353
353
 
354
354
  ## Agent Skill
355
355
 
356
- `ruff-sync` ships a bundled [Agent Skill](https://agentskills.io/home) at [`.agents/skills/ruff-sync-usage/`](.agents/skills/ruff-sync-usage/). AI coding agents that support the [Agent Skills format](https://agentskills.io/what-are-skills) (GitHub Copilot, Claude Code, Cursor, etc.) will automatically use it to guide you through setup, configuration, CI integration, and troubleshooting — without you needing to explain the tool each time.
356
+ `ruff-sync` supports the [Library Skills](https://library-skills.io/) specification. AI coding agents that support the [Agent Skills format](https://agentskills.io/what-are-skills) (GitHub Copilot, Claude Code, Cursor, etc.) can automatically load this skill to guide you through setup, configuration, CI integration, and troubleshooting.
357
+
358
+ You can install it automatically using `uvx` (or `npx` in a Node.js project):
359
+
360
+ ```console
361
+ $ uvx library-skills
362
+ ```
363
+
364
+ This will link the `ruff-sync` skill (bundled inside the installed package) into your project's `.agents/skills/ruff-sync` folder.
357
365
 
358
366
  See the **[Agent Skill guide](https://kilo59.github.io/ruff-sync/stable/agent-skill/)** for details.
359
367
 
@@ -477,6 +485,24 @@ flowchart TD
477
485
  style SemanticNode fill:#4b5563,color:#fff,stroke:#374151
478
486
  ```
479
487
 
488
+ ## Development
489
+
490
+ For local development and testing:
491
+
492
+ ```console
493
+ # Run test suite
494
+ uv run pytest -vv
495
+
496
+ # Run linter and formatter
497
+ uv run ruff check . --fix
498
+ uv run ruff format .
499
+
500
+ # Run type checker
501
+ uv run mypy .
502
+ ```
503
+
504
+ > **Note on Test Environment**: Test mocking uses `respx` with `httpx2`. To ensure `respx` intercepts `httpx2` network calls during `pytest` runs before plugin entrypoints load, a guarded `src/sitecustomize.py` script is used in development. This script is strictly excluded from production wheel builds (`packages = ["src/ruff_sync"]`) and is never shipped to end users.
505
+
480
506
  ## Dogfooding
481
507
 
482
508
  To see `ruff-sync` in action, this project automatically "dogfoods" its own configuration. Every pull request runs a `ruff-sync check` against the repository's own `pyproject.toml` using the `--output-format github` flag, providing real-time feedback and inline annotations whenever configuration drift is detected.
@@ -1,15 +1,19 @@
1
1
  ruff_sync/__init__.py,sha256=M_1YK0BcPzlLlxlBOEPEBTCkAVm37r-yoQzGGZnfQ7Y,1206
2
2
  ruff_sync/__main__.py,sha256=oMIbv9ywToleBKJrZ-GMo_KhM4lHg02nJe7t6neWzJE,179
3
3
  ruff_sync/cli.py,sha256=IICm0f60_-fKRjFH3gMLUn3sK9yxB9auNglI7E6P-WU,25346
4
- ruff_sync/config_io.py,sha256=Hfwo0VC2rbs1ToXPwB0_oQae__opyj-Us8pDsgI3-w0,4349
5
- ruff_sync/constants.py,sha256=g7ZU8mGdQ2682tKQuKkhRaTRRmg0K2vMVp3Ljs60D70,5822
6
- ruff_sync/core.py,sha256=69AQ0YraOtl3KQ0fSeBtqpw-LPBMRHV5G2QQr9P_A0k,44309
4
+ ruff_sync/config_io.py,sha256=K8wVXRoJ6KYbS5TYs9ND2hWR7DggMl0UwX9QlZAdEnY,4347
5
+ ruff_sync/constants.py,sha256=FEJQjAABC2kJStmtjBf6FVpoxEmmgCCvof53DldwjXI,5818
6
+ ruff_sync/core.py,sha256=N353z7Die51b_CHhO5p5L05xWhFKWsogVcDwESrngHQ,43119
7
7
  ruff_sync/dependencies.py,sha256=RpJXAsrB-Y-ol_ZH4U92XIuyq8_bWaYwp9sn9KpxraA,2096
8
8
  ruff_sync/formatters.py,sha256=A4p2XAyc-xnteUmdtRXbp63fIjT9MdyKF6qKvcyNMho,29908
9
9
  ruff_sync/pre_commit.py,sha256=0ltXUkmocbrSwwf6cPbNc_K5K9n-gvou98xJy9YUhr0,5303
10
10
  ruff_sync/system.py,sha256=vF9Jjb-Z9MuLwo3wiOqcCDxniNtjvo3O-aFt4NJf-2Y,5715
11
- ruff_sync/types_.py,sha256=BejR6sgctR0qr5fKgRTtgHp2TqnvT8zTRo1NJi2VHLo,824
11
+ ruff_sync/types_.py,sha256=9gSZpsDXl40FfXCd95aBToZTjOm1vnBZmqaDKXL4sJA,794
12
12
  ruff_sync/validation.py,sha256=OatdkWUKEyxw7CYTOFNpH6IRsbVfU5EHjt6Lu6nOO9U,11585
13
+ ruff_sync/.agents/skills/ruff-sync/SKILL.md,sha256=06D6Sw-WuWQNezRtce-Y724wJcfHmBUb66eA7sNYmWQ,7419
14
+ ruff_sync/.agents/skills/ruff-sync/references/ci-integration.md,sha256=s3CmTFM8uEJU00_Bt99wLGNt3GIadhidwxMW70XxsaY,7325
15
+ ruff_sync/.agents/skills/ruff-sync/references/configuration.md,sha256=xoF6DDbKz_eAipgm-iuwbo5QxM-_49rju8pxjbbIfBQ,4519
16
+ ruff_sync/.agents/skills/ruff-sync/references/troubleshooting.md,sha256=vQxMLg0DA3Hmd7jJAVcvTZFIis9Gg4mZDItRRMoHnw4,4537
13
17
  ruff_sync/tui/__init__.py,sha256=DcwvE70ETjBcC6pQeHpjrYtkJp8z4StGM8L3EuaVcIo,635
14
18
  ruff_sync/tui/app.py,sha256=soq0fkFsLeB7ZxJWNZM_H6bNj7H8p6nEYyirVcskN54,9317
15
19
  ruff_sync/tui/constants.py,sha256=fEPyp2k817QLluyiVHqDObzDONlHz_L3Ec0SYAUzzMs,267
@@ -17,8 +21,8 @@ ruff_sync/tui/screens.py,sha256=NVIlqNIr64JrFFWeec7usqJCb1x41NEmSfRZGeEcwuo,5557
17
21
  ruff_sync/tui/themes.py,sha256=-x9u_8iZPa1XZuo8O_tWVHyCImN9Qs9F7xPhl0VLwZw,654
18
22
  ruff_sync/tui/types_.py,sha256=poNUBJotgUso2IkUi3nXUuxz6wv1goFWZimj4VttdJE,8271
19
23
  ruff_sync/tui/widgets.py,sha256=2m3gyH4xDBpM0CK-5eQmmUCZXYKJujrbyVkFTxqmHcg,9760
20
- ruff_sync-0.1.7.dev2.dist-info/METADATA,sha256=iysOAshz9XPsmyasNIGUE_zkL8vK8rQ4gUmxLeOVOn0,23134
21
- ruff_sync-0.1.7.dev2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
22
- ruff_sync-0.1.7.dev2.dist-info/entry_points.txt,sha256=QQ0pjxMIN0vp5EmNFkyb-1Yk8JOIkid8zAPN3IpAExA,78
23
- ruff_sync-0.1.7.dev2.dist-info/licenses/LICENSE.md,sha256=RcCP8t8xyCx2cvMZSLjBrPqJfctciWC6q85ceQAcEfA,1080
24
- ruff_sync-0.1.7.dev2.dist-info/RECORD,,
24
+ ruff_sync-0.1.9.dev0.dist-info/METADATA,sha256=KL05bz9etJdfbrqobWgiibwOWrQR4j235ci1xDFAol0,24046
25
+ ruff_sync-0.1.9.dev0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
26
+ ruff_sync-0.1.9.dev0.dist-info/entry_points.txt,sha256=QQ0pjxMIN0vp5EmNFkyb-1Yk8JOIkid8zAPN3IpAExA,78
27
+ ruff_sync-0.1.9.dev0.dist-info/licenses/LICENSE.md,sha256=RcCP8t8xyCx2cvMZSLjBrPqJfctciWC6q85ceQAcEfA,1080
28
+ ruff_sync-0.1.9.dev0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any