From 635d647763129c19e3721acef637a5e3250d24db Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 10:29:09 -0400 Subject: [PATCH 1/8] ci: switch from pre-commit to prek prek is a drop-in, Rust-based reimplementation of pre-commit that reads the same .pre-commit-config.yaml. Swaps the pre-commit dependency and invocations (nox session / CI action) for prek. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 4 ++-- pyproject.toml | 4 ++-- uv.lock | 57 +++++++++++++------------------------------------- 3 files changed, 19 insertions(+), 46 deletions(-) diff --git a/noxfile.py b/noxfile.py index c7da82c..82202df 100644 --- a/noxfile.py +++ b/noxfile.py @@ -25,8 +25,8 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: - """Run pre-commit.""" - s.run("pre-commit", "run", "--all-files", *s.posargs) + """Run prek.""" + s.run("prek", "run", "--all-files", *s.posargs) @session(uv_groups=["lint"], reuse_venv=True) diff --git a/pyproject.toml b/pyproject.toml index d01ae6d..34651c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dev = [ "cz-conventional-gitmoji>=0.6.1", "ipykernel>=6.29.5", - "pre-commit>=4.0.1", + "prek>=0.5.3", "uv>=0.6.4", { include-group = "build" }, { include-group = "test" }, @@ -46,7 +46,7 @@ build = [ ] lint = [ "mypy>=1.19.0", - "pre-commit>=4.2.0", + "prek>=0.5.3", "pylint>=3.3.9", ] nox = [ diff --git a/uv.lock b/uv.lock index a267a4e..fc68e4d 100644 --- a/uv.lock +++ b/uv.lock @@ -128,15 +128,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, ] -[[package]] -name = "cfgv" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, -] - [[package]] name = "charset-normalizer" version = "3.4.2" @@ -440,15 +431,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] -[[package]] -name = "identify" -version = "2.6.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, -] - [[package]] name = "importlib-metadata" version = "8.6.1" @@ -532,7 +514,7 @@ dev = [ { name = "ipykernel" }, { name = "nox" }, { name = "optional-dependencies" }, - { name = "pre-commit" }, + { name = "prek" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-github-actions-annotate-failures" }, @@ -541,7 +523,7 @@ dev = [ ] lint = [ { name = "mypy" }, - { name = "pre-commit" }, + { name = "prek" }, { name = "pylint" }, ] nox = [ @@ -568,7 +550,7 @@ dev = [ { name = "ipykernel", specifier = ">=6.29.5" }, { name = "nox" }, { name = "optional-dependencies", specifier = ">=0.3" }, - { name = "pre-commit", specifier = ">=4.0.1" }, + { name = "prek", specifier = ">=0.5.3" }, { name = "pytest", specifier = ">=6" }, { name = "pytest-cov", specifier = ">=3" }, { name = "pytest-github-actions-annotate-failures" }, @@ -577,7 +559,7 @@ dev = [ ] lint = [ { name = "mypy", specifier = ">=1.19.0" }, - { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "prek", specifier = ">=0.5.3" }, { name = "pylint", specifier = ">=3.3.9" }, ] nox = [ @@ -872,15 +854,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, ] -[[package]] -name = "nodeenv" -version = "1.9.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, -] - [[package]] name = "nox" version = "2025.5.1" @@ -981,19 +954,19 @@ wheels = [ ] [[package]] -name = "pre-commit" -version = "4.2.0" +name = "prek" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/d1e5afc996b9fde04d71c37fd28bdd404e54daf1da6c76b883c5cdbdb411/prek-0.5.3.tar.gz", hash = "sha256:06d88bed9a5b2886cd3796957e4cecf05fa9b06724b625df31dbf0a48ff2330c", size = 551929, upload-time = "2026-09-13T08:38:37.823Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/db/5d/8731dc49cb5424d37424db3ea2f3ff6a0b20caa971db72796e822ce48b24/prek-0.5.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2d7240c5e6a996ef5bebe5d57d43049803e41c225b343681c020bf0c87dab2b2", size = 6003518, upload-time = "2026-09-13T08:38:20.584Z" }, + { url = "https://files.pythonhosted.org/packages/51/45/ca413aefb3ea2411bbf0adb6eebfac1e9bfc5ac7679dbe26336fc651c347/prek-0.5.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1dd6df8235ca361dbbeec89c305ccb41a138088a2fc548484b4cb89f66a80d20", size = 5538754, upload-time = "2026-09-13T08:38:23.004Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/874d58ba40d2fe7cfec2789ae8bf415f3751666ad5c59928821fe53c3c01/prek-0.5.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:61ff791bb850ba52cc0d86576498df7e1bd1e9ed20df63dae113a4a6aa495e19", size = 5841568, upload-time = "2026-09-13T08:38:25.008Z" }, + { url = "https://files.pythonhosted.org/packages/8f/40/167037b8eef75f6f036c60ff7e93f42defebb7c3733677e2f30e5a479257/prek-0.5.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a863379a2668c4ef079d820ffd5b14073145380dfbe0e70fef498dc73c568f86", size = 6226960, upload-time = "2026-09-13T08:38:26.788Z" }, + { url = "https://files.pythonhosted.org/packages/42/9a/bfcd3c1fe1fdefee8dc144a92c4b0b93b59f31c7df60ee1720c49223a90a/prek-0.5.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:086d1b77557f0a089568dc5b93c22ccd793351524f3eeb29d36781421a350e72", size = 5854832, upload-time = "2026-09-13T08:38:28.954Z" }, + { url = "https://files.pythonhosted.org/packages/15/41/1450b995b18bbf0d8ab65016860198052baad5c79c231151fa1dcca4a2de/prek-0.5.3-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:4f09b947255124e9591a7f7ef16eb5340a36ba94a275d788d7ea804fab35f0e2", size = 6343055, upload-time = "2026-09-13T08:38:30.96Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e8/8f45950b8a8ddcf22efc7183e9d6b1d7e30373229ff7d9e56f918a55922c/prek-0.5.3-py3-none-win_amd64.whl", hash = "sha256:5b74a9742c3e8f8688d5dad1439688f112e2e1746fcb91016015dae38df3eb42", size = 5744293, upload-time = "2026-09-13T08:38:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/24/cd/ce637f4b6cc6c583516dc16b539408dab593672f7c6fe7723a37caf378ff/prek-0.5.3-py3-none-win_arm64.whl", hash = "sha256:20d92aef53a5e237f4659ecc6669c0fbc98a1b83f0e2476530400a72d42ca390", size = 5512958, upload-time = "2026-09-13T08:38:36.123Z" }, ] [[package]] From 950fc585bd4cb61bf7b2d17bd670452acd49a714 Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 11:23:00 -0400 Subject: [PATCH 2/8] fix(ci): skip no-commit-to-branch in the full-suite CI run no-commit-to-branch would otherwise fail every push to main: CI checks out a real local branch literally named `main` for push events, so the hook would always fire. It's a client-side guard for a human running `git commit`/`git push` locally (or via installed git hooks) -- not something a full "run every hook" CI invocation should re-evaluate after the fact. Skips it there via SKIP=no-commit-to-branch; the hook itself is untouched and still fully active locally. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 82202df..98fe083 100644 --- a/noxfile.py +++ b/noxfile.py @@ -26,7 +26,10 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: """Run prek.""" - s.run("prek", "run", "--all-files", *s.posargs) + # no-commit-to-branch always fails here: CI checks out the real + # `main` branch on every push, which is exactly what the hook exists to + # block for a human running `git commit`/`git push` locally. + s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": "no-commit-to-branch"}) @session(uv_groups=["lint"], reuse_venv=True) From 49334e58a46338cf29401c33cb1d7b1c50dee3b5 Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 11:40:30 -0400 Subject: [PATCH 3/8] fix(ci): don't clobber an existing SKIP when skipping no-commit-to-branch Appends no-commit-to-branch to any SKIP a developer already has set (e.g. via their shell) rather than overwriting it wholesale, matching the same fix applied in response to Copilot review feedback on GalacticDynamics/coordinax#885. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 98fe083..d3730df 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,5 +1,6 @@ """Nox setup.""" +import os import shutil from pathlib import Path @@ -28,8 +29,10 @@ def precommit(s: nox.Session, /) -> None: """Run prek.""" # no-commit-to-branch always fails here: CI checks out the real # `main` branch on every push, which is exactly what the hook exists to - # block for a human running `git commit`/`git push` locally. - s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": "no-commit-to-branch"}) + # block for a human running `git commit`/`git push` locally. Add it to + # any SKIP a caller already set, rather than clobbering it. + skip = ",".join(filter(None, [os.environ.get("SKIP"), "no-commit-to-branch"])) + s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": skip}) @session(uv_groups=["lint"], reuse_venv=True) From ea67d6b307ece87a57a1309b2d5c2b2951912c48 Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 12:31:24 -0400 Subject: [PATCH 4/8] docs(nox): clarify the no-commit-to-branch skip isn't CI-specific Addresses Copilot review feedback on GalacticDynamics/galax#847: the comment said "CI checks out the real main branch," but the skip applies unconditionally, including local `nox -s lint` runs -- which is correct (a CI-only skip would leave the same false failure for any local dev running the full suite while on `main`). Fixes the wording to match the actual, intended behavior instead of narrowing it. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/noxfile.py b/noxfile.py index d3730df..3b03383 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,10 +27,12 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: """Run prek.""" - # no-commit-to-branch always fails here: CI checks out the real - # `main` branch on every push, which is exactly what the hook exists to - # block for a human running `git commit`/`git push` locally. Add it to - # any SKIP a caller already set, rather than clobbering it. + # no-commit-to-branch guards a human's local `git commit`/`git push`, + # not a manual "run every hook over all files" invocation like this + # one -- which CI also runs on every push to `main`, where it would + # otherwise always fail. Skipped here (locally or in CI); the + # installed git hook still catches the real case. Add it to any SKIP + # a caller already set, rather than clobbering it. skip = ",".join(filter(None, [os.environ.get("SKIP"), "no-commit-to-branch"])) s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": skip}) From 150b6031097466527280f18927e7a6c2c598ab69 Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 12:37:05 -0400 Subject: [PATCH 5/8] docs(nox): clarify the precommit session still runs via prek Addresses Copilot review feedback on GalacticDynamics/dataclassish#94: the docstring "Run prek." on a session still named `precommit` could read as though the session itself was renamed. Spells out that it runs the pre-commit hooks, now via prek. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index 3b03383..1756853 100644 --- a/noxfile.py +++ b/noxfile.py @@ -26,7 +26,7 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: - """Run prek.""" + """Run the pre-commit hooks (via prek).""" # no-commit-to-branch guards a human's local `git commit`/`git push`, # not a manual "run every hook over all files" invocation like this # one -- which CI also runs on every push to `main`, where it would From 86d6bf8d09f5555b0058aa69872d3ab4213ca3a7 Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 12:41:55 -0400 Subject: [PATCH 6/8] docs(nox): fix now-inaccurate git-push mention in the SKIP comment Addresses Copilot review feedback on GalacticDynamics/galax#847: this comment still said no-commit-to-branch guards `git push`, but the earlier stages: [pre-commit] fix means it no longer runs on push at all. Clarifies that explicitly instead of leaving stale wording. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/noxfile.py b/noxfile.py index 1756853..99aa41f 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,12 +27,13 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: """Run the pre-commit hooks (via prek).""" - # no-commit-to-branch guards a human's local `git commit`/`git push`, - # not a manual "run every hook over all files" invocation like this - # one -- which CI also runs on every push to `main`, where it would - # otherwise always fail. Skipped here (locally or in CI); the - # installed git hook still catches the real case. Add it to any SKIP - # a caller already set, rather than clobbering it. + # no-commit-to-branch guards a human's local `git commit` (it's + # scoped to stages: [pre-commit] in .pre-commit-config.yaml, so it + # never runs on push) -- not a manual "run every hook over all files" + # invocation like this one, which CI also runs on every push to + # `main`, where it would otherwise always fail. Skipped here (locally + # or in CI); the installed git hook still catches the real case. Add + # it to any SKIP a caller already set, rather than clobbering it. skip = ",".join(filter(None, [os.environ.get("SKIP"), "no-commit-to-branch"])) s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": skip}) From 28f4614fe3263c3a710099c13c66239671f0d20e Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 12:56:01 -0400 Subject: [PATCH 7/8] docs: disambiguate "never fires on push" from the workflow's own push trigger Addresses Copilot review feedback on GalacticDynamics/galax#847: "it never fires on push" reads as a claim about this workflow's own `on: push:` trigger (which is false -- that's why the SKIP exists at all), when it actually means the git pre-push hook stage. Spells that out explicitly. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/noxfile.py b/noxfile.py index 99aa41f..a6402c3 100644 --- a/noxfile.py +++ b/noxfile.py @@ -29,11 +29,12 @@ def precommit(s: nox.Session, /) -> None: """Run the pre-commit hooks (via prek).""" # no-commit-to-branch guards a human's local `git commit` (it's # scoped to stages: [pre-commit] in .pre-commit-config.yaml, so it - # never runs on push) -- not a manual "run every hook over all files" - # invocation like this one, which CI also runs on every push to - # `main`, where it would otherwise always fail. Skipped here (locally - # or in CI); the installed git hook still catches the real case. Add - # it to any SKIP a caller already set, rather than clobbering it. + # never fires as a pre-push git hook) -- not a manual "run every + # hook over all files" invocation like this one, which CI also runs + # on every push to `main`, where it would otherwise always fail. + # Skipped here (locally or in CI); the installed git hook still + # catches the real case. Add it to any SKIP a caller already set, + # rather than clobbering it. skip = ",".join(filter(None, [os.environ.get("SKIP"), "no-commit-to-branch"])) s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": skip}) From 20d52c0747d52f9e05984ff6eb5a1185481f87af Mon Sep 17 00:00:00 2001 From: nstarman Date: Tue, 15 Sep 2026 13:53:31 -0400 Subject: [PATCH 8/8] docs: trim verbose inline comments down to the load-bearing WHY The archaeology (why pre-commit's nodeenv/pyyaml floors mattered, why --skip clobbers, the full CI-checkout explanation) belongs in commit history, not permanently inline. Keeps just enough to orient a future reader without re-litigating the whole investigation. Co-Authored-By: Claude Sonnet 5 --- noxfile.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/noxfile.py b/noxfile.py index a6402c3..787135d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -27,14 +27,8 @@ def lint(s: nox.Session, /) -> None: @session(uv_groups=["lint"], reuse_venv=True) def precommit(s: nox.Session, /) -> None: """Run the pre-commit hooks (via prek).""" - # no-commit-to-branch guards a human's local `git commit` (it's - # scoped to stages: [pre-commit] in .pre-commit-config.yaml, so it - # never fires as a pre-push git hook) -- not a manual "run every - # hook over all files" invocation like this one, which CI also runs - # on every push to `main`, where it would otherwise always fail. - # Skipped here (locally or in CI); the installed git hook still - # catches the real case. Add it to any SKIP a caller already set, - # rather than clobbering it. + # Not a real commit -- no-commit-to-branch would always fail here. + # Merge into any SKIP already set, rather than clobber it. skip = ",".join(filter(None, [os.environ.get("SKIP"), "no-commit-to-branch"])) s.run("prek", "run", "--all-files", *s.posargs, env={"SKIP": skip})