Skip to content

Feature roadmap: ecosystem-inspired improvements for ruff-sync #100

@Kilo59

Description

@Kilo59

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_from with remote URLs is the closest native peer to what ruff-sync does. If Ruff ever adopts remote extend (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|github for 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 in pyproject.toml) recording the resolved commit SHA after pull
  • check verifies against the locked version
  • ruff-sync update to 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-version conflicts with local requires-python
  • Optionally run ruff check against 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) — extends via 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions