ruff-sync is a CLI tool that synchronizes Ruff linter configuration across multiple Python projects. It downloads an upstream pyproject.toml, extracts the [tool.ruff] section, and merges it into a local project's pyproject.toml while preserving formatting, comments, and whitespace.
- GitHub Repository:
Kilo59/ruff-sync - The application uses a
srclayout insrc/ruff_sync/. - Dev tasks are defined in
tasks.pyusing Invoke.
Use the GitHub CLI (gh) to gather extra context about issues, pull requests, and releases before starting work. For detailed workflows on managing issues, see the gh-issues skill in .agents/skills/gh-issues/SKILL.md.
Specific workflows, libraries, and tools are documented in .agents/skills/. Before using unfamiliar tools like Textual, generating MkDocs, or asserting test data structures, check this directory for specialized best practices. If you create a new skill, use the skill-creator tool and avoid documenting full details here to prevent bloat.
- Python ≥ 3.10 (target version
py310) - Package Manager: uv — Use
uv run <command>for all executions to ensure the correct environment.- Note on PATH: On macOS,
uvis often installed in~/.local/bin. Ifuvis not found, add this to yourPATH:export PATH="$PATH:$HOME/.local/bin".
- Note on PATH: On macOS,
- Linter / Formatter: Ruff (
>=0.15.0) - Type Checker: mypy (strict mode)
- Test Framework: pytest with
pytest-asyncio,respx,pyfakefs(See Testing Standards) - Coverage:
coverage+ Codecov - Pre-commit:
pre-commit/prek(see.pre-commit-config.yaml) - TOML Parsing: tomlkit — preserves formatting and comments
- HTTP: httpx (async)
.agents/ # Agent-specific instructions (Deep Standards)
TESTING.md # Mandatory testing patterns and rules
workflows/ # Step-by-step guides for common tasks
decisions/ # Internal Architectural Decision Records (ADRs)
README.md # Index of all architectural decisions
skills/
ruff-sync-usage/ # Agent Skill for users adopting ruff-sync (keep current!)
SKILL.md
references/
configuration.md
troubleshooting.md
ci-integration.md
src/ruff_sync/ # The application source
__init__.py # Public API
__main__.py # CLI entry point (`python -m ruff_sync`)
cli.py # CLI argparse definition and orchestration
constants.py # Project-wide constants and default values
core.py # Core logic for merging, syncing, and repository handling
formatters.py # Logic for CLI output formatting (GitHub, GitLab, etc.)
pre_commit.py # Support for pre-commit hook generation and validation
tasks.py # Invoke tasks: lint, fmt, type-check, deps, new-case, release
pyproject.toml # Project config, dependencies, ruff/mypy settings
tests/
conftest.py # Shared pytest fixtures (mocking, temp dirs)
ruff.toml # Test-specific ruff overrides (extends ../pyproject.toml)
test_basic.py # Unit tests for core functions
test_check.py # Tests for --check mode and drift detection
test_ci_integration.py # CI-specific environment tests
test_ci_validation.py # Environment variable and CI output detection tests
test_config_validation.py # Validation of local configuration
test_constants.py # Tests for internal constants and sentinels
test_corner_cases.py # Edge case tests for TOML merge logic
test_deprecation.py # Tests for handling of deprecated flags/settings
test_e2e.py # End-to-end tests using lifecycle TOML fixtures
test_formatters.py # Serialization and formatting tests
test_git_fetch.py # Mocked git repository fetching tests
test_pre_commit.py # Pre-commit hook generation and sync tests
test_project.py # Tests that validate project config consistency
test_scaffold.py # Tests for the new-case scaffold task
test_serialization.py # Tests for tomlkit serialization edge cases
test_toml_operations.py # Tests for low-level TOML operations
test_url_handling.py # Tests for GitHub and GitLab URL parsing
test_whitespace.py # Tests for whitespace/comment preservation during merge
lifecycle_tomls/ # TOML fixture files (*_initial.toml, *_upstream.toml, *_final.toml)
After ANY code change, you MUST validate with the following tools, in this order. ALWAYS prefix these commands with uv run:
uv run ruff check . --fix- Ruff config lives in
pyproject.tomlunder[tool.ruff]and[tool.ruff.lint]. - Tests have additional overrides in
tests/ruff.toml(extends the root config). - All Python files must include
from __future__ import annotations(enforced by isort ruleI002). - Use
uv run ruff check . --fixto auto-fix issues. Use--unsafe-fixesonly if explicitly asked. - Do NOT disable or ignore rules unless the user explicitly asks you to. You must fix the underlying code to pass the linter, rather than appending
# noqadirectives or adding rules toignoreinpyproject.toml.
If you encounter an unfamiliar lint rule or need to understand why a rule exists:
uv run ruff rule <RULE_CODE>For example: uv run ruff rule TC006 explains the TC006 rule in detail.
Use this to make informed decisions rather than blindly suppressing rules.
uv run ruff format .- Line length: 100 characters.
- Docstring code formatting is enabled (
docstring-code-format = true). - Preview formatting features are enabled.
uv run mypy .- mypy is configured in strict mode with
python_version = "3.10". - It checks
src/,tests/, andtasks.py. - Tests have relaxed rules:
type-argandno-untyped-defare disabled fortests.*. tomlkitreturns complex union types — usecast(Any, ...)in tests when indexing parsed TOML documents to satisfy mypy without verbose type narrowing.
uv run pytest -vvOr with coverage:
uv run coverage run -m pytest -vv- Coverage is tracked for
src/ruff_sync/only. - Tests use
respxto mock HTTP calls andpyfakefsfor filesystem mocking. pytest-asynciois in strict mode — async tests need the@pytest.mark.asynciodecorator.
- Always use
from __future__ import annotationsas the first import. - Do NOT use
from pathlib import Pathorfrom datetime import ...— these are banned by the import conventions config. Useimport pathlibandimport datetime as dtinstead. - Do NOT use
unittest.mockorMagicMock. This project forbidsunittest.mockbecause it encourages bad design and tests that lie to you. Prefer Dependency Injection (DI) and dedicated IO-layer libraries (respx, pyfakefs) over any kind of patching. - Imports used only for type hints should go inside
if TYPE_CHECKING:blocks.
- Use
pathliboveros.path(enforced byPTHrules). - Prefer f-strings for logging (we ignore
G004). - Do not create custom exception classes for simple errors (
TRY003is ignored). - Prefer
NamedTuplefor return types over plain tuples to improve readability and type safety. - Prefer
typing.Protocoloverabc.ABCfor abstract base classes to promote structural subtyping. - Prefer Dependency Injection (DI): Pass dependencies as arguments to functions and classes instead of hard-coding them or relying on global state. This makes code easier to test without patching.
- Always use
tomlkitfor reading/writing TOML. It preserves formatting, comments, and whitespace — which is critical for this project's purpose. - Be aware that
tomlkitreturns proxy objects. When you need to convert them to plain Python for comparisons or re-insertion, use.unwrap(). - Dotted keys (e.g.,
lint.select = [...]) create proxy tables that behave differently from explicit table headers ([tool.ruff.lint]). The merge logic in_recursive_updatehandles this.
- Use the
MissingType.SENTINEL(aliased asMISSING) fromruff_sync.constantswhenever 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 asNoneor an empty list). - Why?: This is particularly important for configuration serialization, as it allows
ruff-syncto distinguish between a setting the user actively chose and one that is simply the default. This keeps the user'spyproject.tomlclean by ensuring only explicit choices are serialized. - Serialization Rule: Only serialize fields to
[tool.ruff-sync]if they arenot MISSING. - Example pattern for resolving configuration (note: use truthiness, not
is not None, so an empty string falls back to config/defaults):The actual MISSING → default resolution (e.g.def _resolve_branch(args: Any, config: Mapping[str, Any]) -> str | MissingType: # Empty string is treated as falsy → falls back to config or DEFAULT_BRANCH if getattr(args, "branch", None): return cast("str", args.branch) if "branch" in config: return cast("str", config["branch"]) return MISSING
MISSING→"main") is handled inresolve_defaultsfromruff_sync.constants, which is the single source of truth used by bothcli.mainandcore._merge_multiple_upstreams.
- New TOML merge edge cases belong in
tests/test_corner_cases.py. - Whitespace/formatting preservation tests go in
tests/test_whitespace.py. - End-to-end lifecycle tests use fixture triples in
tests/lifecycle_tomls/(*_initial.toml,*_upstream.toml,*_final.toml). Use theinvoke new-casetask to scaffold them. - Tests should include both structural/whitespace assertions and semantic assertions (verifying the merged config values are correct).
Defined in tasks.py. ALWAYS run these through uv: uv run invoke <task>
| Task | Alias | Description |
|---|---|---|
lint |
Lint with ruff (auto-fixes by default) | |
fmt |
Format with ruff format | |
type-check |
types |
Type-check with mypy |
deps |
sync |
Sync dependencies with uv |
new-case |
new-lifecycle-tomls |
Scaffold lifecycle TOML fixtures |
docs |
Build or serve documentation | |
release |
Tag and create a GitHub release |
CI is defined in .github/workflows/ci.yaml:
- Static Analysis: runs
lint --check,fmt --check, andtype-checkon Python 3.10. - Tests: matrix across Python 3.10, 3.11, 3.12, 3.13, 3.14.
- Pre-commit.ci: auto-fixes and auto-updates hooks on PRs.
- 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_updatefunction handles this. Don't bypass it. cast(Any, ...)in tests: Usecast(Any, tomlkit.parse(...))["tool"]["ruff"]pattern in tests to avoid mypy complaints abouttomlkit'sItem | Containerreturn types.- Pre-commit ruff version: The ruff version in
.pre-commit-config.yamlmust stay in sync with the version inpyproject.toml. The testtest_pre_commit_versions_are_in_syncenforces this. - Keep
ruff-sync-usagecurrent: Update.agents/skills/ruff-sync-usage/after any CLI behavior changes (flags, config keys, exits) to ensure the documentation stays accurate. Keep details inside the skill directory. - No
autouse=Truefixtures: NEVER useautouse=Truefor 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.
- Prefer
read_url_content: If you only need to extract text or markdown from a public URL, useread_url_content. It is faster and lighter. - Visual Interaction as Last Resort: Only use
read_browser_pageor manual screen control when a page requires JavaScript execution, authentication, or complex visual interaction. - Task Specificity: When using the browser subagent, provide highly specific tasks and clear exit criteria to minimize redundant interactions.
Significant architectural shifts and long-term design decisions are recorded as ADRs in .agents/decisions/. These serve as the "memory" of the project's evolution for both human and agent developers.