mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description Ruff currently has three independent versions: `uv.lock` resolves `0.14.14`, pre-commit runs `0.9.4`, and CI installs `0.15.17`. Contributors can therefore pass one formatter path and fail another. Make the exact Ruff pin in `pyproject.toml` the source of truth, align the lockfile and pre-commit hook to it, and make CI read that pin through a deterministic consistency verifier instead of carrying another hardcoded version. Closes #2398 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that causes existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Pin the existing `[dev]` Ruff dependency to `0.15.17`, the formatter baseline already used by CI. - Refresh only Ruff in `uv.lock` with `uv 0.11.29`. - Align `ruff-pre-commit` to `v0.15.17`. - Add `scripts/verify-ruff-version.py` and run it from pre-commit and CI. - Make CI install the verified version read from `pyproject.toml` rather than a separate literal. ## Testing - [ ] Unit tests pass (`pytest`) — not run; no runtime source or test behavior changed. - [x] Linting passes (`ruff check .`) - [x] Type checking passes (`mypy headroom`) - [x] New deterministic guard proves the configuration fix - [x] Manual testing performed ### Test Output ```text # Before: run the verifier with the patched pyproject pin but base-branch # uv.lock, pre-commit config, and workflow. Ruff version mismatch detected: uv.lock uses Ruff 0.14.14, expected 0.15.17 .pre-commit-config.yaml uses Ruff 0.9.4, expected 0.15.17 ci.yml does not run 'python scripts/verify-ruff-version.py --print-version' ci.yml does not install Ruff from 'steps.ruff-version.outputs.version' $ python3 scripts/verify-ruff-version.py Ruff versions aligned at 0.15.17 $ uvx uv@0.11.29 lock --check Resolved 269 packages $ uvx uv@0.11.29 tree --locked --package ruff ruff v0.15.17 $ uvx ruff@0.15.17 check . All checks passed! $ uvx ruff@0.15.17 format --check . 1322 files already formatted $ uvx mypy@1.20.2 headroom --ignore-missing-imports Success: no issues found in 505 source files $ uvx --with tomli mypy@1.20.2 scripts/verify-ruff-version.py --ignore-missing-imports Success: no issues found in 1 source file $ uvx pre-commit run ruff --all-files Passed $ uvx pre-commit run ruff-format --all-files Passed $ uvx pre-commit run verify-ruff-version --all-files Passed ``` ## Real Behavior Proof - Environment: macOS 26.5, Python 3.14.6 locally; Python 3.10 fallback also exercised; `uv 0.11.29`, Ruff `0.15.17`, mypy `1.20.2`. - Exact command / steps: reproduced the mismatch using the base branch's real `uv.lock`, `.pre-commit-config.yaml`, and `.github/workflows/ci.yml`; then ran the verifier, locked Ruff tree, full Ruff check/format, mypy, and actual pre-commit hooks after the patch. - Observed result: the base state fails with all four drift points listed; the patched state reports one aligned Ruff version (`0.15.17`) and every formatter path passes. - Not tested: runtime proxy behavior and the pytest suite, because the change is limited to development-tool configuration, lock metadata, pre-commit, and CI wiring. ## Dependency / Supply-Chain Justification - Ruff is an existing development-only formatter maintained by Astral; this PR adds no new package. - `0.15.17` is required to fix local/CI reproducibility and has already been the repository's CI formatter baseline since #1295. - Install surface is limited to the `[dev]` extra, lint CI job, and pre-commit environment. Production/runtime dependencies are unchanged. - The `uv.lock` refresh updates only Ruff; no unrelated dependency upgrades are included. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review ## Checklist - [x] My code follows the project's style guidelines - [x] I have performed a self-review of my code - [x] I have commented the non-obvious consistency checks - [x] Documentation changes are N/A; contributor commands are unchanged - [x] My changes generate no new warnings - [x] The guard fails on the real base-state mismatch and passes after the fix - [ ] New and existing unit tests pass locally — not run; no runtime code changed - [x] I did not edit `CHANGELOG.md`; release-please will use the conventional PR title ## Additional Notes No formatter-driven source changes are included. AI assistance was used to inspect configuration, implement the verifier, and run validation.
118 lines
3.9 KiB
Python
Executable file
118 lines
3.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Verify that every Ruff execution path uses the dev dependency pin."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any, cast
|
|
|
|
try:
|
|
import tomllib
|
|
except ImportError: # pragma: no cover - Python 3.10 fallback
|
|
import tomli as tomllib
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
RUFF_PRE_COMMIT_REPO = "https://github.com/astral-sh/ruff-pre-commit"
|
|
WORKFLOW_VERSION_COMMAND = "python scripts/verify-ruff-version.py --print-version"
|
|
WORKFLOW_INSTALL_REFERENCE = "steps.ruff-version.outputs.version"
|
|
|
|
|
|
def _load_toml(path: Path) -> dict[str, Any]:
|
|
with path.open("rb") as file:
|
|
return cast(dict[str, Any], tomllib.load(file))
|
|
|
|
|
|
def _authoritative_version() -> str:
|
|
dependencies = _load_toml(ROOT / "pyproject.toml")["project"]["optional-dependencies"]["dev"]
|
|
ruff_requirements = [
|
|
requirement for requirement in dependencies if requirement.startswith("ruff")
|
|
]
|
|
if len(ruff_requirements) != 1:
|
|
raise ValueError(f"expected one Ruff dev dependency, found {ruff_requirements!r}")
|
|
|
|
match = re.fullmatch(r"ruff==([^;,\s]+)", ruff_requirements[0])
|
|
if match is None:
|
|
raise ValueError(
|
|
"pyproject.toml must contain one exact Ruff pin in project.optional-dependencies.dev"
|
|
)
|
|
return match.group(1)
|
|
|
|
|
|
def _locked_version() -> str:
|
|
packages = _load_toml(ROOT / "uv.lock")["package"]
|
|
versions = [str(package["version"]) for package in packages if package["name"] == "ruff"]
|
|
if len(versions) != 1:
|
|
raise ValueError(f"expected one locked Ruff package, found {versions!r}")
|
|
return versions[0]
|
|
|
|
|
|
def _pre_commit_version() -> str:
|
|
lines = (ROOT / ".pre-commit-config.yaml").read_text(encoding="utf-8").splitlines()
|
|
for index, line in enumerate(lines):
|
|
if line.strip() != f"- repo: {RUFF_PRE_COMMIT_REPO}":
|
|
continue
|
|
for candidate in lines[index + 1 :]:
|
|
stripped = candidate.strip()
|
|
if stripped.startswith("- repo:"):
|
|
break
|
|
if stripped.startswith("rev:"):
|
|
return stripped.removeprefix("rev:").strip().removeprefix("v")
|
|
break
|
|
raise ValueError(f"could not find a rev for {RUFF_PRE_COMMIT_REPO}")
|
|
|
|
|
|
def _workflow_errors() -> list[str]:
|
|
workflow = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
|
|
errors = []
|
|
if WORKFLOW_VERSION_COMMAND not in workflow:
|
|
errors.append(f"ci.yml does not run {WORKFLOW_VERSION_COMMAND!r}")
|
|
if WORKFLOW_INSTALL_REFERENCE not in workflow:
|
|
errors.append(f"ci.yml does not install Ruff from {WORKFLOW_INSTALL_REFERENCE!r}")
|
|
return errors
|
|
|
|
|
|
def _parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--print-version",
|
|
action="store_true",
|
|
help="print the authoritative version after validating every execution path",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = _parse_args()
|
|
try:
|
|
authoritative = _authoritative_version()
|
|
versions = {
|
|
"pyproject.toml": authoritative,
|
|
"uv.lock": _locked_version(),
|
|
".pre-commit-config.yaml": _pre_commit_version(),
|
|
}
|
|
errors = [
|
|
f"{path} uses Ruff {version}, expected {authoritative}"
|
|
for path, version in versions.items()
|
|
if version != authoritative
|
|
]
|
|
errors.extend(_workflow_errors())
|
|
except (KeyError, TypeError, ValueError) as exc:
|
|
print(f"Ruff version verification failed: {exc}")
|
|
raise SystemExit(1) from exc
|
|
|
|
if errors:
|
|
print("Ruff version mismatch detected:")
|
|
for message in errors:
|
|
print(f" {message}")
|
|
raise SystemExit(1)
|
|
|
|
if args.print_version:
|
|
print(authoritative)
|
|
else:
|
|
print(f"Ruff versions aligned at {authoritative}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|