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.
Files changed (106) hide show
  1. ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/SKILL.md +141 -0
  2. ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/ci-integration.md +134 -0
  3. ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/configuration.md +140 -0
  4. ruff_sync-0.1.3.dev2/.agents/skills/ruff-sync-usage/references/troubleshooting.md +137 -0
  5. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/ci.yaml +39 -1
  6. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.pre-commit-config.yaml +1 -1
  7. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/AGENTS.md +31 -0
  8. ruff_sync-0.1.3.dev2/CONTRIBUTING.md +179 -0
  9. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/PKG-INFO +45 -26
  10. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/README.md +42 -23
  11. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/codecov.yml +1 -1
  12. ruff_sync-0.1.3.dev2/docs/agent-skill.md +45 -0
  13. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/ci-integration.md +5 -2
  14. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/configuration.md +22 -2
  15. ruff_sync-0.1.3.dev2/docs/contributing.md +96 -0
  16. ruff_sync-0.1.3.dev2/docs/examples/advanced-config.toml +15 -0
  17. ruff_sync-0.1.3.dev2/docs/examples/basic-config.toml +6 -0
  18. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/index.md +20 -8
  19. ruff_sync-0.1.3.dev2/docs/pre-defined-configs.md +147 -0
  20. ruff_sync-0.1.3.dev2/docs/url-resolution.md +106 -0
  21. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/usage.md +85 -41
  22. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/mkdocs.yml +7 -0
  23. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/pyproject.toml +10 -3
  24. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/cli.py +93 -36
  25. ruff_sync-0.1.3.dev2/src/ruff_sync/constants.py +90 -0
  26. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/core.py +177 -42
  27. ruff_sync-0.1.3.dev2/src/ruff_sync/formatters.py +243 -0
  28. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tasks.py +31 -0
  29. ruff_sync-0.1.3.dev2/tests/conftest.py +64 -0
  30. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_basic.py +172 -5
  31. ruff_sync-0.1.3.dev2/tests/test_check.py +596 -0
  32. ruff_sync-0.1.3.dev2/tests/test_constants.py +62 -0
  33. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_deprecation.py +1 -1
  34. ruff_sync-0.1.3.dev2/tests/test_formatters.py +154 -0
  35. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_scaffold.py +19 -7
  36. ruff_sync-0.1.3.dev2/tests/test_serialization.py +398 -0
  37. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/uv.lock +239 -213
  38. ruff_sync-0.1.2.dev1/tests/conftest.py +0 -12
  39. ruff_sync-0.1.2.dev1/tests/test_check.py +0 -329
  40. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/TESTING.md +0 -0
  41. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/SKILL.md +0 -0
  42. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/examples.md +0 -0
  43. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/api-reference.md +0 -0
  44. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/getting-started.md +0 -0
  45. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/index.md +0 -0
  46. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/mkdocs-generation/templates/mkdocs.yml +0 -0
  47. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/skills/release-notes-generation/SKILL.md +0 -0
  48. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.agents/workflows/add-test-case.md +0 -0
  49. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.git-blame-ignore-revs +0 -0
  50. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/dependabot.yml +0 -0
  51. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/complexity.yaml +0 -0
  52. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.github/workflows/docs.yaml +0 -0
  53. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.gitignore +0 -0
  54. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/.pre-commit-hooks.yaml +0 -0
  55. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/LICENSE.md +0 -0
  56. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/data-science-engineering/ruff.toml +0 -0
  57. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/fastapi/ruff.toml +0 -0
  58. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/configs/kitchen-sink/ruff.toml +0 -0
  59. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/favicon.png +0 -0
  60. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/logo.png +0 -0
  61. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/assets/ruff_sync_banner.png +0 -0
  62. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/best-practices.md +0 -0
  63. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/gen_ref_pages.py +0 -0
  64. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/installation.md +0 -0
  65. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/pre-commit.md +0 -0
  66. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/docs/troubleshooting.md +0 -0
  67. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/check_dogfood.sh +0 -0
  68. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/gitclone_dogfood.sh +0 -0
  69. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/scripts/pull_dogfood.sh +0 -0
  70. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/skills-lock.json +0 -0
  71. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/__init__.py +0 -0
  72. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/__main__.py +0 -0
  73. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/src/ruff_sync/pre_commit.py +0 -0
  74. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/__init__.py +0 -0
  75. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_final.toml +0 -0
  76. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_initial.toml +0 -0
  77. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_up1.toml +0 -0
  78. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/multi_upstream_up2.toml +0 -0
  79. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  80. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  81. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  82. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  83. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  84. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  85. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  86. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  87. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  88. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  89. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  90. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  91. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_final.toml +0 -0
  92. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  93. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  94. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/ruff.toml +0 -0
  95. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_config_validation.py +0 -0
  96. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_corner_cases.py +0 -0
  97. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_e2e.py +0 -0
  98. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_git_fetch.py +0 -0
  99. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_pre_commit.py +0 -0
  100. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_project.py +0 -0
  101. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_toml_operations.py +0 -0
  102. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_url_handling.py +0 -0
  103. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/test_whitespace.py +0 -0
  104. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  105. {ruff_sync-0.1.2.dev1 → ruff_sync-0.1.3.dev2}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  106. {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: uv publish
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
+ }
@@ -16,7 +16,7 @@ repos:
16
16
  - id: no-commit-to-branch
17
17
  args: [--branch, develop, --branch, main]
18
18
  - repo: https://github.com/astral-sh/ruff-pre-commit
19
- rev: "v0.15.6"
19
+ rev: "v0.15.8"
20
20
  hooks:
21
21
  - id: ruff-check
22
22
  args: ["--fix"]
@@ -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.