Skip to content

Add a ruff-config hook and publish ops as a two-hook repository - #6

Merged
jonnyspicer merged 4 commits into
mainfrom
feat/ruff-config-hook
Aug 12, 2026
Merged

Add a ruff-config hook and publish ops as a two-hook repository#6
jonnyspicer merged 4 commits into
mainfrom
feat/ruff-config-hook

Conversation

@jonnyspicer

Copy link
Copy Markdown
Contributor

First half of 86cb43xya — the deferred half of 86cb417ty. The six consumer PRs follow once hooks-v1.0 is tagged.

Why

Every consumer's pyproject.toml carries this:

# Shared ruff standard for offworldlabs Python repos.
# Keep in sync across repos; see offworldlabs/ops for the canonical copy.

ops has never held a ruff config. It is the same false canonical-copy claim as the dead-code script, in a different file — and "keep in sync across repos" is an instruction to humans with no mechanism behind it.

Why a checker rather than a shared file

Sharing by reference is impossible here: ruff's extend accepts only a local filesystem path (verified — given a URL it treats it as a relative filename and fails), and there is no rev:-style sharing for pyproject.toml.

But pre-commit clones the hook repo locally before running it. So a checker shipped from ops can compare a consumer's config against a canonical file sitting beside it in the hook checkout — one rev: pins the standard and the checker together.

What it enforces

Key Rule
line-length must equal canonical
lint.select, lint.ignore canonical entries must all be present
lint.per-file-ignores canonical patterns must be present with equal values
target-version ignored — tracks each package's requires-python, legitimately per-repo
anything else permitted — local additions are not drift

Comparison is semantic, not textual: values compare as sets. That is load-bearing — Tower-Finder is semantically identical to the baseline but pads its comments differently, so a text diff would falsely fail the largest repo in the fleet.

All six repos pass today, so adoption requires reconciling nobody's config.

Tag scheme changes

Per-hook tags stop making sense with two hooks: pre-commit pins the repo at one rev, so dead-code-v1.1 also silently decided which ruff-config you got. Moving to repo-level hooks-v<MAJOR>.<MINOR>. The older dead-code-v1.0 / v1.1 remain valid.

The dot is mandatory, not cosmetic — pre-commit warns "appears to be a mutable reference" for any rev containing neither a . nor pure hex.

Verification

  • check-ruff-config.py: 10 tests. check-dead-code.sh: 14 tests. Both green.
  • CI now runs pre-commit try-repo positive controls for both hooks: each plants a deliberate failure, requires the hook to catch it, then requires a clean version to pass. Review independently proved these are load-bearing by replacing the hooks with /bin/true and with a checker that unconditionally exits 0 — both produced FAIL: hook did not catch....
  • One test originally asserted only rc -eq 2 for a missing pyproject.toml. Python exits 2 when it cannot open the script at all, so it passed whether or not the checker existed. Now asserts the message too: with the script hidden the suite goes from 1 passing to 0 passing, 10 failing.

🤖 Generated with Claude Code

https://claude.ai/code/session_013ZcazeseXVpu8XrYA2tE1V

Every pyproject.toml claims 'see offworldlabs/ops for the canonical copy'.
ops has never held one. Same false claim as 86cb417ty's dead-code script.

ruff's extend takes only a local path, so there is no rev:-style sharing for
pyproject.toml. But pre-commit clones the hook repo locally before running it,
so a checker shipped from ops can compare against a canonical file sitting
beside it — one rev pins the standard and the checker together.

Comparison is semantic: select, ignore and per-file-ignores compare as sets, so
comment whitespace and ordering do not matter. target-version is ignored
because it tracks requires-python and is legitimately per-repo. Verified: all
six repos pass today.
python exits 2 when it cannot open the script, the same code the checker
uses for a missing pyproject.toml, so the bare rc check passed even with
check-ruff-config.py absent — it would have kept passing if the script were
later renamed or deleted.
Adds the ruff-config hook to the manifest, renames the workflow now that it
covers more than one hook, and gives both hooks a try-repo positive control.

Switches to repo-level hooks-v<MAJOR>.<MINOR> tags: pre-commit pins the whole
repo at one rev, so per-hook tags were incoherent — pinning dead-code-v1.1 also
silently decided which ruff-config you got.

Also reworks the drift message printed by check-ruff-config.py: it now leads
with "copy the entries above into this repo's pyproject.toml" (the common
case) and lists editing ruff-shared.toml as the secondary path, since the
prior wording read as though the standard itself should usually change.
The quickstart still showed rev: dead-code-v1.1 with a single hook, sitting
above the paragraph that explains per-hook tags are superseded. A reader
skimming top-to-bottom would copy the old scheme and get only one of the two
hooks.

Also updates check-dead-code.sh's header comment, which pointed the same
old dead-code-v*.* scheme, to hooks-v*.* — kept version-agnostic (a
placeholder, not a literal MAJOR.MINOR), since this file ships frozen
inside whatever tag a consumer pins.
@jonnyspicer
jonnyspicer merged commit 72cfe49 into main Aug 12, 2026
3 checks passed
@jonnyspicer
jonnyspicer deleted the feat/ruff-config-hook branch August 12, 2026 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant