ruff-sync 0.1.2.dev1__tar.gz → 0.1.3.dev2__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.3.dev2/.agents/skills/ruff-sync-usage/SKILL.md +141 -0
- ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/ci-integration.md +134 -0
- ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/configuration.md +140 -0
- ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/troubleshooting.md +137 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/ci.yaml +39 -1
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.pre-commit-config.yaml +1 -1
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/AGENTS.md +31 -0
- ruff_sync-0.1.3.dev2/CONTRIBUTING.md +179 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/PKG-INFO +45 -26
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/README.md +42 -23
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/codecov.yml +1 -1
- ruff_sync-0.1.3.dev2/docs/agent-skill.md +45 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/ci-integration.md +5 -2
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/configuration.md +22 -2
- ruff_sync-0.1.3.dev2/docs/contributing.md +96 -0
- ruff_sync-0.1.3.dev2/docs/examples/advanced-config.toml +15 -0
- ruff_sync-0.1.3.dev2/docs/examples/basic-config.toml +6 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/index.md +20 -8
- ruff_sync-0.1.3.dev2/docs/pre-defined-configs.md +147 -0
- ruff_sync-0.1.3.dev2/docs/url-resolution.md +106 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/usage.md +85 -41
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/mkdocs.yml +7 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/pyproject.toml +10 -3
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/cli.py +93 -36
- ruff_sync-0.1.3.dev2/src/ruff_sync/constants.py +90 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/core.py +177 -42
- ruff_sync-0.1.3.dev2/src/ruff_sync/formatters.py +243 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tasks.py +31 -0
- ruff_sync-0.1.3.dev2/tests/conftest.py +64 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_basic.py +172 -5
- ruff_sync-0.1.3.dev2/tests/test_check.py +596 -0
- ruff_sync-0.1.3.dev2/tests/test_constants.py +62 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_deprecation.py +1 -1
- ruff_sync-0.1.3.dev2/tests/test_formatters.py +154 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_scaffold.py +19 -7
- ruff_sync-0.1.3.dev2/tests/test_serialization.py +398 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/uv.lock +239 -213
- ruff_sync-0.1.2.dev1/tests/conftest.py +0 -12
- ruff_sync-0.1.2.dev1/tests/test_check.py +0 -329
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/TESTING.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/examples.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/release-notes-generation/SKILL.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.git-blame-ignore-revs +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/dependabot.yml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/docs.yaml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.gitignore +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.pre-commit-hooks.yaml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/LICENSE.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/data-science-engineering/ruff.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/fastapi/ruff.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/kitchen-sink/ruff.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/favicon.png +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/logo.png +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/ruff_sync_banner.png +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/best-practices.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/gen_ref_pages.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/installation.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/pre-commit.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/troubleshooting.md +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/check_dogfood.sh +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/gitclone_dogfood.sh +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/pull_dogfood.sh +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/skills-lock.json +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/__init__.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/__main__.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/pre_commit.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/__init__.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/ruff.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_config_validation.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_corner_cases.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_e2e.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_git_fetch.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_pre_commit.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_project.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_url_handling.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_whitespace.py +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruff-sync-usage
|
|
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
|
+
| `--output-format` | `text` (default), `json`, `github` (PR annotations) |
|
|
121
|
+
| `--semantic` | Ignore whitespace/comments in `check` |
|
|
122
|
+
| `--pre-commit` | Sync `.pre-commit-config.yaml` hook version |
|
|
123
|
+
| `--save` | Persist CLI args to `pyproject.toml` |
|
|
124
|
+
|
|
125
|
+
## Gotchas
|
|
126
|
+
|
|
127
|
+
- **`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`.
|
|
128
|
+
- **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.
|
|
129
|
+
- **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.
|
|
130
|
+
- **`--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.
|
|
131
|
+
- **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.
|
|
132
|
+
- **Later upstreams win in `upstream` lists.** In a multi-source list, keys set by entry 2 overwrite keys from entry 1.
|
|
133
|
+
- **Config discovery order matters.** When targeting a directory, `ruff-sync` looks for `ruff.toml` -> `.ruff.toml` -> `pyproject.toml` in that order.
|
|
134
|
+
- **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.
|
|
135
|
+
- **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.
|
|
136
|
+
|
|
137
|
+
## References
|
|
138
|
+
|
|
139
|
+
- **[Configuration reference](references/configuration.md)** — All `[tool.ruff-sync]` keys, types, and defaults
|
|
140
|
+
- **[Troubleshooting](references/troubleshooting.md)** — Common errors and how to resolve them
|
|
141
|
+
- **[CI integration recipes](references/ci-integration.md)** — GitHub Actions, GitLab CI, pre-commit hook
|
|
@@ -0,0 +1,134 @@
|
|
|
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. Any non-zero exit code (1 = config drift, 2 = stale hook rev) will fail the CI step:
|
|
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
|
+
---
|
|
59
|
+
|
|
60
|
+
## GitLab CI
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
variables:
|
|
66
|
+
UV_VERSION: "0.10"
|
|
67
|
+
PYTHON_VERSION: "3.12"
|
|
68
|
+
BASE_LAYER: bookworm-slim
|
|
69
|
+
UV_LINK_MODE: copy # required: GitLab mounts build dir separately
|
|
70
|
+
|
|
71
|
+
ruff-sync-check:
|
|
72
|
+
stage: lint
|
|
73
|
+
image: ghcr.io/astral-sh/uv:$UV_VERSION-python$PYTHON_VERSION-$BASE_LAYER
|
|
74
|
+
script:
|
|
75
|
+
- uv tool install ruff-sync
|
|
76
|
+
- ruff-sync check --semantic
|
|
77
|
+
rules:
|
|
78
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
79
|
+
- if: '$CI_COMMIT_BRANCH == "main"'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Pre-commit Hook
|
|
85
|
+
|
|
86
|
+
Run `ruff-sync check` as a pre-commit hook to catch drift before every commit:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
# .pre-commit-config.yaml
|
|
90
|
+
- repo: https://github.com/Kilo59/ruff-sync
|
|
91
|
+
rev: v0.1.0 # pin to a release tag
|
|
92
|
+
hooks:
|
|
93
|
+
- id: ruff-sync-check
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The hook runs `ruff-sync check --semantic` automatically. Update `rev` to the latest ruff-sync version.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Makefile
|
|
101
|
+
|
|
102
|
+
```makefile
|
|
103
|
+
.PHONY: sync-check sync
|
|
104
|
+
|
|
105
|
+
sync-check:
|
|
106
|
+
ruff-sync check --semantic
|
|
107
|
+
|
|
108
|
+
sync:
|
|
109
|
+
ruff-sync
|
|
110
|
+
git diff pyproject.toml
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Deciding: `--semantic` vs. Full String Check
|
|
116
|
+
|
|
117
|
+
| Mode | Fails on | Use when |
|
|
118
|
+
|------|---------|---------|
|
|
119
|
+
| `ruff-sync check --semantic` | Value/rule differences only | CI — avoids false positives from local comment edits |
|
|
120
|
+
| `ruff-sync check` | Any string difference (comments, whitespace, values) | Enforcing exact config file consistency |
|
|
121
|
+
|
|
122
|
+
Recommendation: **use `--semantic` in CI** and save the full-string check for auditing purposes.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Dogfooding (Self-Check)
|
|
127
|
+
|
|
128
|
+
If `ruff-sync` is configured in the project's own `pyproject.toml` (the standard case), just run:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
ruff-sync check
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
No URL argument needed — it reads `upstream` from `[tool.ruff-sync]`.
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
## Full Example
|
|
106
|
+
|
|
107
|
+
```toml
|
|
108
|
+
[tool.ruff-sync]
|
|
109
|
+
upstream = [
|
|
110
|
+
"https://github.com/my-org/python-standards",
|
|
111
|
+
"https://github.com/my-org/backend-team-rules",
|
|
112
|
+
]
|
|
113
|
+
exclude = [
|
|
114
|
+
"target-version",
|
|
115
|
+
"lint.per-file-ignores",
|
|
116
|
+
"lint.ignore",
|
|
117
|
+
"lint.isort.known-first-party",
|
|
118
|
+
]
|
|
119
|
+
branch = "main"
|
|
120
|
+
path = "ruff"
|
|
121
|
+
to = "."
|
|
122
|
+
pre-commit-version-sync = true
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## CLI Overrides
|
|
126
|
+
|
|
127
|
+
All config keys have CLI equivalents. CLI values always win over `pyproject.toml`:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
ruff-sync https://github.com/my-org/standards --exclude lint.ignore --branch develop --output-format github
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Config Discovery for `ruff.toml` Projects
|
|
134
|
+
|
|
135
|
+
If the local target is a directory, ruff-sync looks for config in this order:
|
|
136
|
+
1. `ruff.toml`
|
|
137
|
+
2. `.ruff.toml`
|
|
138
|
+
3. `pyproject.toml`
|
|
139
|
+
|
|
140
|
+
If the upstream is a `ruff.toml`, it syncs the root config object (no `[tool.ruff]` section extraction needed).
|
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
|
|
83
|
+
## Merge Produces Unexpected `tomlkit` Structure
|
|
84
|
+
|
|
85
|
+
**Symptom:** Synced `pyproject.toml` has repeated sections or oddly formatted keys.
|
|
86
|
+
|
|
87
|
+
**Cause:** Mixing dotted-key style (`lint.select = [...]`) with explicit table header style (`[tool.ruff.lint]`) in the same file.
|
|
88
|
+
|
|
89
|
+
**Fix:** Pick one style consistently. `ruff-sync` uses `tomlkit` which preserves formatting, but mixing styles can produce unexpected output.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## SSH Clone Fails
|
|
94
|
+
|
|
95
|
+
**Symptom:** Hangs or fails when using `git@github.com:...` URL.
|
|
96
|
+
|
|
97
|
+
**Cause:** SSH key not configured or not added to the agent.
|
|
98
|
+
|
|
99
|
+
**Fix:** Verify SSH auth first:
|
|
100
|
+
```bash
|
|
101
|
+
ssh -T git@github.com
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Alternatively, switch to HTTPS:
|
|
105
|
+
```toml
|
|
106
|
+
upstream = "https://github.com/my-org/standards"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## `ruff-sync` Not Found After Install
|
|
112
|
+
|
|
113
|
+
**Symptom:** `ruff-sync: command not found`
|
|
114
|
+
|
|
115
|
+
**Fix:**
|
|
116
|
+
```bash
|
|
117
|
+
# If installed with uv tool:
|
|
118
|
+
uv tool list # confirm it appears
|
|
119
|
+
# Ensure uv tools bin dir is on PATH:
|
|
120
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
|
|
121
|
+
|
|
122
|
+
# If using pipx:
|
|
123
|
+
pipx ensurepath
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Getting More Debug Info
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Show what config would be merged without applying it
|
|
132
|
+
ruff-sync check https://github.com/my-org/standards
|
|
133
|
+
|
|
134
|
+
# Show a diff of what changed
|
|
135
|
+
ruff-sync check --semantic # value-only diff
|
|
136
|
+
ruff-sync check # full string diff (catches comment/whitespace changes too)
|
|
137
|
+
```
|
|
@@ -31,6 +31,26 @@ jobs:
|
|
|
31
31
|
|
|
32
32
|
- run: uv run invoke ${{ matrix.task }} --check
|
|
33
33
|
|
|
34
|
+
validate-docs-build:
|
|
35
|
+
name: Validate documentation build
|
|
36
|
+
if: github.event_name == 'pull_request'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- name: Checkout
|
|
40
|
+
uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- name: Install uv
|
|
43
|
+
uses: astral-sh/setup-uv@v5
|
|
44
|
+
|
|
45
|
+
- name: Set up Python
|
|
46
|
+
run: uv python install 3.10
|
|
47
|
+
|
|
48
|
+
- name: Install dependencies
|
|
49
|
+
run: uv sync --group docs --frozen
|
|
50
|
+
|
|
51
|
+
- name: Build documentation
|
|
52
|
+
run: uv run mkdocs build --strict
|
|
53
|
+
|
|
34
54
|
tests:
|
|
35
55
|
strategy:
|
|
36
56
|
fail-fast: ${{ github.event.pull_request.draft == true }}
|
|
@@ -102,6 +122,13 @@ jobs:
|
|
|
102
122
|
echo "Testing ruff-sync check against Kilo59/ruff-sync..."
|
|
103
123
|
ruff-sync check https://github.com/Kilo59/ruff-sync
|
|
104
124
|
|
|
125
|
+
- name: Dogfood Ruff-Sync Check (GitHub Format)
|
|
126
|
+
run: |
|
|
127
|
+
echo "Dogfooding ruff-sync check with GitHub annotations..."
|
|
128
|
+
# This will produce annotations if the current branch's ruff config
|
|
129
|
+
# has drifted from the upstream (main branch).
|
|
130
|
+
ruff-sync check https://github.com/Kilo59/ruff-sync --output-format github
|
|
131
|
+
|
|
105
132
|
- name: Verify semantic check failure
|
|
106
133
|
run: |
|
|
107
134
|
echo "Verifying that --semantic check fails for kitchen-sink config (expected failure)..."
|
|
@@ -131,4 +158,15 @@ jobs:
|
|
|
131
158
|
run: uv build
|
|
132
159
|
|
|
133
160
|
- name: Publish package
|
|
134
|
-
run:
|
|
161
|
+
run: |
|
|
162
|
+
# Capture stderr to a file so we can check for specific error strings
|
|
163
|
+
uv publish 2> publish_err.log || {
|
|
164
|
+
exit_code=$?
|
|
165
|
+
# Check for common "version already exists" markers in the output
|
|
166
|
+
if grep -qi "already exists" publish_err.log; then
|
|
167
|
+
echo "::warning title=Ruff Sync Publish::Version already exists on PyPI. Skipping upload."
|
|
168
|
+
else
|
|
169
|
+
cat publish_err.log
|
|
170
|
+
exit $exit_code
|
|
171
|
+
fi
|
|
172
|
+
}
|
|
@@ -58,6 +58,13 @@ gh label list # See available labels
|
|
|
58
58
|
.agents/ # Agent-specific instructions (Deep Standards)
|
|
59
59
|
TESTING.md # Mandatory testing patterns and rules
|
|
60
60
|
workflows/ # Step-by-step guides for common tasks
|
|
61
|
+
skills/
|
|
62
|
+
ruff-sync-usage/ # Agent Skill for users adopting ruff-sync (keep current!)
|
|
63
|
+
SKILL.md
|
|
64
|
+
references/
|
|
65
|
+
configuration.md
|
|
66
|
+
troubleshooting.md
|
|
67
|
+
ci-integration.md
|
|
61
68
|
src/ruff_sync/ # The application source
|
|
62
69
|
__init__.py # Public API
|
|
63
70
|
cli.py # CLI, merging logic, HTTP
|
|
@@ -151,6 +158,7 @@ uv run coverage run -m pytest -vv
|
|
|
151
158
|
- Prefer f-strings for logging (we ignore `G004`).
|
|
152
159
|
- Do not create custom exception classes for simple errors (`TRY003` is ignored).
|
|
153
160
|
- **Prefer `NamedTuple` for return types** over plain tuples to improve readability and type safety.
|
|
161
|
+
- **Prefer `typing.Protocol` over `abc.ABC`** for abstract base classes to promote structural subtyping.
|
|
154
162
|
|
|
155
163
|
### TOML Handling
|
|
156
164
|
|
|
@@ -158,6 +166,26 @@ uv run coverage run -m pytest -vv
|
|
|
158
166
|
- Be aware that `tomlkit` returns proxy objects. When you need to convert them to plain Python for comparisons or re-insertion, use `.unwrap()`.
|
|
159
167
|
- Dotted keys (e.g., `lint.select = [...]`) create proxy tables that behave differently from explicit table headers (`[tool.ruff.lint]`). The merge logic in `_recursive_update` handles this.
|
|
160
168
|
|
|
169
|
+
### Sentinels & Missing Values
|
|
170
|
+
|
|
171
|
+
- Use the `MissingType.SENTINEL` (aliased as `MISSING`) from `ruff_sync.constants` whenever you need to distinguish between a value that is functionally "absent" and one that was explicitly provided (even if that provided value matches the default, such as `None` or an empty list).
|
|
172
|
+
- **Why?**: This is particularly important for configuration serialization, as it allows `ruff-sync` to distinguish between a setting the user actively chose and one that is simply the default. This keeps the user's `pyproject.toml` clean by ensuring only explicit choices are serialized.
|
|
173
|
+
- **Serialization Rule**: Only serialize fields to `[tool.ruff-sync]` if they are `not MISSING`.
|
|
174
|
+
- Example pattern for resolving configuration (note: use **truthiness**, not `is not None`, so an
|
|
175
|
+
empty string falls back to config/defaults):
|
|
176
|
+
```python
|
|
177
|
+
def _resolve_branch(args: Any, config: Mapping[str, Any]) -> str | MissingType:
|
|
178
|
+
# Empty string is treated as falsy → falls back to config or DEFAULT_BRANCH
|
|
179
|
+
if getattr(args, "branch", None):
|
|
180
|
+
return cast("str", args.branch)
|
|
181
|
+
if "branch" in config:
|
|
182
|
+
return cast("str", config["branch"])
|
|
183
|
+
return MISSING
|
|
184
|
+
```
|
|
185
|
+
The actual MISSING → default resolution (e.g. `MISSING` → `"main"`) is handled in
|
|
186
|
+
`resolve_defaults` from `ruff_sync.constants`, which is the single source of truth used
|
|
187
|
+
by both `cli.main` and `core._merge_multiple_upstreams`.
|
|
188
|
+
|
|
161
189
|
### Testing
|
|
162
190
|
|
|
163
191
|
- New TOML merge edge cases belong in `tests/test_corner_cases.py`.
|
|
@@ -176,6 +204,7 @@ Defined in `tasks.py`. **ALWAYS** run these through uv: `uv run invoke <task>`
|
|
|
176
204
|
| `type-check` | `types` | Type-check with mypy |
|
|
177
205
|
| `deps` | `sync` | Sync dependencies with uv |
|
|
178
206
|
| `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures |
|
|
207
|
+
| `docs` | | Build or serve documentation |
|
|
179
208
|
|
|
180
209
|
## CI
|
|
181
210
|
|
|
@@ -190,3 +219,5 @@ CI is defined in `.github/workflows/ci.yaml`:
|
|
|
190
219
|
1. **tomlkit proxy objects**: When adding new keys to a proxy table (from dotted keys), the proxy must be converted to a real table first. The `_recursive_update` function handles this. Don't bypass it.
|
|
191
220
|
2. **`cast(Any, ...)` in tests**: Use `cast(Any, tomlkit.parse(...))["tool"]["ruff"]` pattern in tests to avoid mypy complaints about `tomlkit`'s `Item | Container` return types.
|
|
192
221
|
3. **Pre-commit ruff version**: The ruff version in `.pre-commit-config.yaml` must stay in sync with the version in `pyproject.toml`. The test `test_pre_commit_versions_are_in_sync` enforces this.
|
|
222
|
+
4. **Keep the ruff-sync-usage skill current**: After any change to CLI behavior (new flags, changed exit codes, new configuration keys, updated URL handling, etc.), update `.agents/skills/ruff-sync-usage/` accordingly. The `SKILL.md` covers quick start, workflows, exit codes, and gotchas. Detailed references live in `references/configuration.md`, `references/troubleshooting.md`, and `references/ci-integration.md`.
|
|
223
|
+
5. **No `autouse=True` fixtures**: NEVER use `autouse=True` for pytest fixtures. All fixtures must be explicitly requested by the test functions that require them. This ensures dependencies are explicit and avoids hidden side effects.
|