-
Notifications
You must be signed in to change notification settings - Fork 5
Feature roadmap: ecosystem-inspired improvements for ruff-sync #100
Description
Feature Roadmap: Ecosystem-Inspired Improvements
After surveying how 8+ ecosystems handle shared linter/formatter configuration, here is a prioritized set of features and improvements to consider for ruff-sync.
Ecosystems Surveyed
| Ecosystem | Tool | Mechanism | Key Insight |
|---|---|---|---|
| JavaScript | ESLint | extends: ["my-org-config"] via npm; flat config imports arrays |
Composable config arrays with explicit precedence |
| JavaScript | Prettier | "prettier": "@my-org/prettier-config" in package.json |
npm package as single source of truth |
| JavaScript | Stylelint | extends: "stylelint-config-standard" via npm |
Same extends + overrides pattern as ESLint |
| Ruby | RuboCop | inherit_from: https://raw.githubusercontent.com/.../rubocop.yml |
Native remote URL inheritance — closest analog to ruff-sync |
| Go | golangci-lint | --config path/to/file; no native remote extends |
Teams resort to CI scripts that fetch configs — the exact gap ruff-sync fills |
| Rust | rustfmt/clippy | Parent-directory search; [workspace.lints] inheritance |
Only works within monorepos; cross-repo sharing needs external tooling |
| Python | Ruff (extend) |
extend = "../parent/ruff.toml" — local paths only |
astral-sh/ruff#12352 requests remote URL support |
| Python | pre-commit | No include; each repo needs its own .pre-commit-config.yaml |
Same config centralization gap |
| Multi-lang | Projen | Generates project files from typed definitions | "Config-as-code" generator model |
RuboCop's
inherit_fromwith remote URLs is the closest native peer to what ruff-sync does. If Ruff ever adopts remoteextend(ruff#12352), ruff-sync would still add value through selective exclusions, format-preserving merges, check/diff commands, and multi-tool support.
🟢 Tier 1: High Impact, Natural Extensions
1. ✅ Config inheritance / layering (like ESLint flat config)
Allow composing config from multiple upstream sources with explicit precedence.
[tool.ruff-sync]
upstream = [
"https://github.com/my-org/base-standards", # base layer
"https://github.com/my-org/python-web-standards", # domain layer (overrides base)
]Why: Organizations often have a base config + domain-specific overlays (web, data science, CLI tools). ESLint's flat config is an ordered array; RuboCop's inherit_from accepts a list.
2. Drift detection with richer CI output
Enhance check command for CI consumption.
--output-format json|sarif|githubfor machine-readable output- GitHub Actions annotations (
::error file=...) to surface drift directly on PRs - Exit code differentiation (e.g., 1 = out of sync, 2 = upstream unreachable)
Inspiration: golangci-lint outputs SARIF, JSON, and GitHub annotations; ESLint supports --format json and custom formatters.
3. Lock/pin upstream versions
Pin to a specific commit SHA or tag for deterministic, reproducible syncing.
- Generate a
ruff-sync.lock(or section inpyproject.toml) recording the resolved commit SHA afterpull checkverifies against the locked versionruff-sync updateto bump to latest and update the lock
Inspiration: npm package-lock.json, Go go.sum, pre-commit rev: pinning.
Why: Without pinning, a pull in CI could silently pick up breaking upstream changes.
🟡 Tier 2: Valuable Additions
4. Dry-run / preview mode
pull --dry-run that shows the full merged output or writes to stdout without modifying files. check --diff partially covers this, but a dedicated dry-run for pull would complete the picture.
Inspiration: terraform plan, npm pack --dry-run.
5. Merge strategy options per key
Instead of binary sync-or-skip, support different merge behaviors per key:
[tool.ruff-sync]
# Append upstream values to local list instead of replacing
merge-strategy.lint.extend-select = "append"
# Never touch this key
merge-strategy.target-version = "local-only"Inspiration: Git merge strategies, ESLint flat config composition.
6. status command — richer sync reporting
$ ruff-sync status
ruff-sync v0.2.0
upstream: https://github.com/my-org/standards (main @ abc1234)
target: pyproject.toml
ruff ✅ in sync (last pulled: 2026-03-10)
mypy ❌ out of sync (3 keys differ)
7. Monorepo support
Sync multiple sub-projects within a single repository, each with different exclusions:
[[tool.ruff-sync.targets]]
to = "packages/api"
exclude = ["lint.per-file-ignores"]
[[tool.ruff-sync.targets]]
to = "packages/worker"
exclude = ["target-version"]🔵 Tier 3: Forward-Looking / Exploratory
8. Bidirectional sync / push command
Push local config changes back upstream by opening a PR. Use case: a team member discovers a useful rule downstream and wants to propose it upstream.
9. Webhook / GitHub App for automatic PRs
A hosted service watching the upstream repo and automatically opening PRs on downstream repos when config changes. (Renovate / Dependabot / pre-commit.ci model.)
10. Config validation
Validate the merged config before applying:
- Warn if upstream references deprecated Ruff rules
- Warn if upstream
target-versionconflicts with localrequires-python - Optionally run
ruff checkagainst the merged config to validate syntax
README: Additional Ecosystems to Document
The README's "How Other Ecosystems Solve This" section currently mentions ESLint, Prettier, and Ruff's extend. Consider adding:
- RuboCop (Ruby) — native
inherit_from: <URL>is the closest peer - Stylelint (CSS) —
extendsvia npm packages - golangci-lint (Go) — no native remote config; teams use CI workarounds
- Rust —
[workspace.lints]for monorepos only - pre-commit — no
include/extends; repos must duplicate config
Related Issues
- Support syncing other pyproject.toml tool configurations (e.g., mypy, pytest) #69 — Multi-tool config syncing (overlaps with Tier 1 scope expansion)
- Add short, non‑ruff CLI aliases (e.g., tms, cfgsync, syncup) for occasional use #74 — Short CLI aliases (makes more sense if multi-tool lands)
- Add example Ruff configurations for various domains #83, Example Config: Data Science / Data Engineering #87 — Example config recipes (Tier 2)