From 1300bea2e3b9bcb736e9749fcbe94b4de77d8e9f Mon Sep 17 00:00:00 2001 From: Matthew Watkins Date: Thu, 30 Jul 2026 12:55:16 +0100 Subject: [PATCH] Chore: Pin ruff rule selection Without a select, ruff applies its default rule set, and that default is not stable across releases: 59 rules across 2 categories in 0.15.22, 413 across 38 in 0.16.0. A routine pre-commit autoupdate crossing that boundary changes which rules run, on code nobody has touched. That is what currently fails the open autoupdate pull request here. The selection is deliberately narrow. It is exactly the rule set this repository already satisfies, so pinning changes no behaviour today and both ruff 0.15.22 and 0.16.0 report all checks passing. Adopting the wider organisation superset is worthwhile but belongs in its own reviewed change. It currently reports 22 findings, 14 of them auto-fixable; the remainder are 6 B904 raise-without-from sites that need judgement about which exception context to preserve. Folding a refactor of error handling into a configuration chore would obscure both. The test per-file-ignores list rules that are not selected yet, so that widening the selection later does not immediately flag every test module. PLR0917 is there because tests stacking @patch decorators cannot satisfy it: unittest.mock injects each mock positionally, so a keyword-only signature is impossible rather than merely inconvenient. Co-authored-by: Claude Signed-off-by: Matthew Watkins --- pyproject.toml | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index eefffbb..75ec902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -191,3 +191,54 @@ pythonVersion = "3.10" typeCheckingMode = "standard" reportMissingImports = "warning" reportUninitializedInstanceVariable = "warning" + +# Pin the ruff rule set explicitly. +# +# Without a `select`, ruff applies its *default* rule set, and that +# default is not stable across releases: +# +# ruff 0.15.22 -> 59 rules across 2 categories (E, F) +# ruff 0.16.0 -> 413 rules across 38 categories +# +# A routine pre-commit autoupdate crossing that boundary changed which +# rules run and broke repositories across this organisation, on code +# nobody had touched. +# +# The selection below is deliberately narrow: it is exactly the rule set +# this repository already satisfies, so pinning changes no behaviour +# today. Widening it to the organisation superset +# ("E", "W", "F", "I", "B", "C4", "UP") is worthwhile but is a code +# change, not a configuration one: it currently reports 22 findings, +# 14 of them auto-fixable, with 6 B904 (raise-without-from) needing +# review by hand. That belongs in its own reviewed pull request rather +# than riding along with a version bump. +# +# Note that this pins the rule set at CATEGORY level, not rule level. +# Ruff selectors are prefixes, so a future release adding new E* or F* +# rules would enable them here automatically. Pinning every rule code +# individually would be exhaustive and unmaintainable; the aim is to +# bound the blast radius, reducing "413 rules across 38 categories" +# back to two, not to freeze the set outright. +# +# `line-length` and `target-version` are intentionally left unset so +# ruff's own defaults continue to apply unchanged. + +[tool.ruff.lint] +select = ["E", "F"] + +ignore = [ + "E501", # line-too-long: the formatter owns line length +] + +[tool.ruff.lint.per-file-ignores] +# Test suites legitimately break rules that matter in library code. +# +# None of these codes is reachable under the current `select`, and only +# B-prefixed ones would be reachable under the organisation superset. +# They are listed anyway so that a later widening beyond that superset, +# to "S", "ARG" or "PL", does not immediately flag every test module. +# PLR0917 is the concrete case already seen elsewhere: tests stacking +# @patch decorators cannot satisfy it, because unittest.mock injects +# its mocks positionally, so a keyword-only signature is impossible +# rather than merely inconvenient. +"tests/**/*.py" = ["S101", "ARG", "PLR0913", "PLR0917", "PLR2004"]