From b10884d0de0bbde379938c5baa8a29218a49a88c Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 10 Jun 2026 18:16:46 +0200 Subject: [PATCH 01/26] Swap pre-commit for prek (#20305) Replace pre-commit with prek as the local Git hook runner and in the testAndPublish CI step. prek is a faster, drop-in compatible alternative that reads the existing .pre-commit-config.yaml unchanged. - pyproject.toml: pre-commit==4.2.0 -> prek==0.4.4 (lint group); relock - testAndPublish.yml: uv run pre-commit -> uv run prek - automated.md: update setup/run docs; add "Switching from pre-commit" note (prek install -f to overwrite the legacy hook) - changes.md: developer changelog entry The .pre-commit-config.yaml and pre-commit.ci integration are kept as-is. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/testAndPublish.yml | 12 ++-- projectDocs/testing/automated.md | 26 +++++---- pyproject.toml | 2 +- user_docs/en/changes.md | 3 + uv.lock | 85 +++------------------------- 5 files changed, 34 insertions(+), 94 deletions(-) diff --git a/.github/workflows/testAndPublish.yml b/.github/workflows/testAndPublish.yml index 5abce8d2329..01962d37529 100644 --- a/.github/workflows/testAndPublish.yml +++ b/.github/workflows/testAndPublish.yml @@ -184,14 +184,14 @@ jobs: architecture: ${{ env.defaultArch }} - name: Install the latest version of uv uses: astral-sh/setup-uv@v7 - - name: Run pre-commit + - name: Run prek run: | # Ensure uv environment is up to date. - # If the uv environment is outdated, running pre-commit will trigger uv to generate a uv.lock file, - # which causes checkPo to fail without clear errors because pre-commit fails when files are changed during its run. - uv run pre-commit run uv-lock --all-files - # Run pre-commit on the translations - uv run pre-commit run checkPo --all-files + # If the uv environment is outdated, running prek will trigger uv to generate a uv.lock file, + # which causes checkPo to fail without clear errors because prek fails when files are changed during its run. + uv run prek run uv-lock --all-files + # Run prek on the translations + uv run prek run checkPo --all-files - name: Add job summary if: ${{ failure() }} shell: bash diff --git a/projectDocs/testing/automated.md b/projectDocs/testing/automated.md index cc470c3510d..c4c896f95d5 100644 --- a/projectDocs/testing/automated.md +++ b/projectDocs/testing/automated.md @@ -5,28 +5,34 @@ These tests help to ensure that code changes do not unintentionally break functi ## Pre-commit hooks -[Pre-commit hooks](https://pre-commit.com/) can be used to automatically run linting, translatable string checks and unit tests on files staged for commit. +Pre-commit hooks can be used to automatically run linting, translatable string checks and unit tests on files staged for commit. This will automatically apply lint fixes where possible, and will cancel the commit on lint issues and other test failures. +NVDA uses [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative to [pre-commit](https://pre-commit.com/) that reads the same `.pre-commit-config.yaml`. -From a shell, [set up pre-commit scripts](https://pre-commit.com/#pre-commit-install) for your NVDA python environment: +From a shell, [set up the Git hooks](https://prek.j178.dev/reference/cli/#prek-install) for your NVDA python environment: -`uv run pre-commit install` +`uv run prek install` -Alternatively, set up pre-commit scripts globally: +Alternatively, set up the Git hooks globally: -1. `pip install pre-commit` -1. `pre-commit install --allow-missing-config` +1. `uv tool install prek` +1. `prek install --allow-missing-config` -To skip pre-commit hooks from triggering, use the `--no-verify` CLI option. +To skip the hooks from triggering, use the `--no-verify` CLI option. Example: `git commit -m "message" --no-verify`. -### Manually running pre-commit hooks +### Switching from pre-commit -You can run pre-commit hooks manually with [`pre commit run`](https://pre-commit.com/#pre-commit-run). +If you previously ran `pre-commit install`, an old `pre-commit` Git hook is still installed. +Run `uv run prek install -f` once to overwrite it with the prek hook. + +### Manually running hooks + +You can run the hooks manually with [`prek run`](https://prek.j178.dev/reference/cli/#prek-run). * You can filter files with `--files` and `--all-files` * You can also compare two revisions: -`uv run pre-commit run --from-ref origin/master --to-ref HEAD` +`uv run prek run --from-ref origin/master --to-ref HEAD` ## Translatable string checks diff --git a/pyproject.toml b/pyproject.toml index a5c53f94f5f..ff94c765ecd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -334,7 +334,7 @@ dev = [ ] lint = [ "ruff==0.15.9", - "pre-commit==4.2.0", + "prek==0.4.4", "pyright[nodejs]==1.1.407", ] license-check = [ diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index f2fdc854eb3..b832ce22b2b 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -33,6 +33,9 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentation/developerGuide.html#API) for information on NVDA's API deprecation and removal process. +* The local Git hook runner has been switched from [pre-commit](https://pre-commit.com/) to [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative. (#20305, @LeonarddeR) + * The existing `.pre-commit-config.yaml` and the [pre-commit.ci](https://pre-commit.ci/) integration are unchanged. + * Developers who previously ran `pre-commit install` should run `uv run prek install -f` once to replace the installed Git hook. * `braille.BrailleDisplayGesture` now exposes a `cellIndexes` list attribute, replacing the single-valued `routingIndex`. (#20001, @LeonarddeR) * Drivers should set `cellIndexes` directly instead of `routingIndex`. * When a gesture addresses more than one cell, its `id` should be set to `"multiRouting"` (or be built via the new `BrailleDisplayGesture.idForCellCount(n)` helper). diff --git a/uv.lock b/uv.lock index 8cc16063c4c..7de7d823814 100644 --- a/uv.lock +++ b/uv.lock @@ -124,15 +124,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, ] -[[package]] -name = "cfgv" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, -] - [[package]] name = "charset-normalizer" version = "3.4.4" @@ -221,15 +212,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/5e/4f5fe4b89fde1dc3ed0eb51bd4ce4c0bca406246673d370ea2ad0c58d747/detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060", size = 120341, upload-time = "2024-05-06T17:46:16.628Z" }, ] -[[package]] -name = "distlib" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, -] - [[package]] name = "docutils" version = "0.21.2" @@ -258,15 +240,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/2b/fd360e1b65ba44179424aa0a8c227c17d7df384f20bb8d38a5cbe23e3ba2/fhconfparser-2024.1-py3-none-any.whl", hash = "sha256:f6048cb646e69a3422a581bc0102150c2b79fe7ff26b82233e5ef52f72820e3e", size = 9221, upload-time = "2024-01-24T21:48:54.81Z" }, ] -[[package]] -name = "filelock" -version = "3.28.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474, upload-time = "2026-04-14T22:54:33.625Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189, upload-time = "2026-04-14T22:54:32.037Z" }, -] - [[package]] name = "fuzzysearch" version = "0.8.1" @@ -276,15 +249,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e9/b4/d92b429ba4bfe5461faa358f84092eceb5ea8985cd2e448daf38bba17737/fuzzysearch-0.8.1.tar.gz", hash = "sha256:e5f50962c6b1c3dfc6c8cdfd5e2604838c95cb10118e4cab518e5292e9e0b1c6", size = 39510, upload-time = "2025-11-11T08:47:10.321Z" } -[[package]] -name = "identify" -version = "2.6.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -601,7 +565,7 @@ license-check = [ { name = "licensecheck", marker = "sys_platform == 'win32'" }, ] lint = [ - { name = "pre-commit", marker = "sys_platform == 'win32'" }, + { name = "prek", marker = "sys_platform == 'win32'" }, { name = "pyright", extra = ["nodejs"], marker = "sys_platform == 'win32'" }, { name = "ruff", marker = "sys_platform == 'win32'" }, ] @@ -661,7 +625,7 @@ dev-docs = [ ] license-check = [{ name = "licensecheck", specifier = "==2025.1" }] lint = [ - { name = "pre-commit", specifier = "==4.2.0" }, + { name = "prek", specifier = "==0.4.4" }, { name = "pyright", extras = ["nodejs"], specifier = "==1.1.407" }, { name = "ruff", specifier = "==0.15.9" }, ] @@ -763,19 +727,14 @@ wheels = [ ] [[package]] -name = "pre-commit" -version = "4.2.0" +name = "prek" +version = "0.4.4" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv", marker = "sys_platform == 'win32'" }, - { name = "identify", marker = "sys_platform == 'win32'" }, - { name = "nodeenv", marker = "sys_platform == 'win32'" }, - { name = "pyyaml", marker = "sys_platform == 'win32'" }, - { name = "virtualenv", marker = "sys_platform == 'win32'" }, -] -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/f6/13/3d71b3adbf385f7dc7fb6e16d6e25421fd8398b45d8f8410a328bf22bd3f/prek-0.4.4.tar.gz", hash = "sha256:4ec5771153d158a0e4473933b7fd9b51e1b1f57f2df50aeb7560ea6812226dc5", size = 470641, upload-time = "2026-06-04T07:26:07.199Z" } 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/46/c0/a4ddbf38034afe67cfa97c4bd81c86429ada098e7c323218d9f9fd061566/prek-0.4.4-py3-none-win32.whl", hash = "sha256:143154b329c05b2f9fa3230e604d02d9c4297dd43f96135a8ba166772e8ecd60", size = 5240317, upload-time = "2026-06-04T07:26:08.726Z" }, + { url = "https://files.pythonhosted.org/packages/bc/8c/fe97b5b095187bb2f93bbe406bccf108c879e5e4c83f165809b0d16ce0fb/prek-0.4.4-py3-none-win_amd64.whl", hash = "sha256:c38c5140ae2ea55ebb02e6ca590a416664ea1af287cdd21f54daeec53a81015a", size = 5626104, upload-time = "2026-06-04T07:26:14.81Z" }, + { url = "https://files.pythonhosted.org/packages/25/63/3586226d536796e65f8e725b531d6104e55caaa18659bdcb512661629586/prek-0.4.4-py3-none-win_arm64.whl", hash = "sha256:3efa28fb37b9ddbafb7759da8d497f0d36cf02a05816e15d6541f5669d5d2114", size = 5470399, upload-time = "2026-06-04T07:26:13.231Z" }, ] [[package]] @@ -935,19 +894,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, ] -[[package]] -name = "python-discovery" -version = "1.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock", marker = "sys_platform == 'win32'" }, - { name = "platformdirs", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, -] - [[package]] name = "pytweening" version = "1.2.0" @@ -1309,21 +1255,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1b/bb/e6bfdea92ed270f3445a5a3c17599d041b3f2dbc5026c09e02830a03bbaf/uv-0.11.7-py3-none-win_arm64.whl", hash = "sha256:6158b7e39464f1aa1e040daa0186cae4749a78b5cd80ac769f32ca711b8976b1", size = 23941816, upload-time = "2026-04-15T21:43:06.732Z" }, ] -[[package]] -name = "virtualenv" -version = "21.2.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib", marker = "sys_platform == 'win32'" }, - { name = "filelock", marker = "sys_platform == 'win32'" }, - { name = "platformdirs", marker = "sys_platform == 'win32'" }, - { name = "python-discovery", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, -] - [[package]] name = "win32-setctime" version = "1.2.0" From 3a3944ea37b74f8456b6a0422dc20a5e5d734868 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:20:45 +0200 Subject: [PATCH 02/26] Convert to native prek.toml, drop pre-commit.ci config Convert .pre-commit-config.yaml to prek's native prek.toml via `prek util yaml-to-toml`. Drop the pre-commit.ci `ci:` block and the check-pre-commit-ci-config hook (no longer relevant). Tag the eight auto-fixing hooks with `groups = ["autofix"]` so a CI autofix job can run only those via `prek run --group autofix`. Co-Authored-By: Claude Opus 4.8 (1M context) --- .pre-commit-config.yaml | 172 ------------------------------- prek.toml | 223 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 172 deletions(-) delete mode 100644 .pre-commit-config.yaml create mode 100644 prek.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 19a482f33ea..00000000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,172 +0,0 @@ -exclude: ^user_docs/(?!en/).+/.+\.md$ - -# https://pre-commit.ci/ -# Configuration for Continuous Integration service -ci: - # Can't run Windows scons scripts on Linux. - # unit testing requires our python environment, - # which cannot be configured with pre-commit.ci: - # https://stackoverflow.com/questions/70778806/pre-commit-not-using-virtual-environment . - # Can't run licenseCheck as it relies on telemetry, - # which CI blocks. - # Pyright does not seem to work in pre-commit CI - skip: [checkPo, scons-source, checkPot, unitTest, licenseCheck, pyright] - autoupdate_schedule: monthly - autoupdate_commit_msg: "Pre-commit auto-update" - autofix_commit_msg: "Pre-commit auto-fix" - submodules: true - -default_language_version: - python: python3.13 - -repos: -- repo: https://github.com/pre-commit-ci/pre-commit-ci-config - rev: v1.6.1 - hooks: - - id: check-pre-commit-ci-config - -- repo: meta - hooks: - # ensures that exclude directives apply to any file in the repository. - - id: check-useless-excludes - # ensures that the configured hooks apply to at least one file in the repository. - - id: check-hooks-apply - -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - # Prevents commits to certain branches - - id: no-commit-to-branch - args: ["--branch", "master", "--branch", "beta", "--branch", "rc"] - # Checks that large files have not been added. Default cut-off for "large" files is 500kb. - - id: check-added-large-files - # POFiles and TTF fonts can't be made smaller - # XLIFF files can be moved here from exclude when pre-commit/identify#558 is released. - exclude_types: ["pofile", "ttf"] - # Same applies for XLIFF, NVDA dictionary (.dic) files and Spline Font Database (.SFD) files, but these aren't recognised by the Identify library. - exclude: "\\.(dic|sfd|xliff)$" - # Checks python syntax - - id: check-ast - # Checks for filenames that will conflict on case insensitive filesystems (the majority of Windows filesystems, most of the time) - - id: check-case-conflict - # Checks for artifacts from resolving merge conflicts. - - id: check-merge-conflict - # Checks Python files for debug statements, such as python's breakpoint function, or those inserted by some IDEs. - - id: debug-statements - # Removes trailing whitespace. - - id: trailing-whitespace - types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] - # Ensures all files end in 1 (and only 1) newline. - - id: end-of-file-fixer - types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] - # Removes the UTF-8 BOM from files that have it. - # See https://github.com/nvaccess/nvda/blob/master/projectDocs/dev/codingStandards.md#encoding - - id: fix-byte-order-marker - types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] - # Validates TOML files. - - id: check-toml - # Validates YAML files. - - id: check-yaml - # Validates XML files. - - id: check-xml - # Ensures that links to lines in files under version control point to a particular commit. - - id: check-vcs-permalinks - # Avoids using reserved Windows filenames. - - id: check-illegal-windows-names - # Checks that tests are named test_*.py. - - id: name-tests-test - args: ["--unittest"] - # Exclude Python files under `tests/` that aren't unittest files. - # This is a Python verbose regular expression. - # See https://docs.python.org/3/library/re.html#re.VERBOSE - exclude: | - (?x)^tests/( - checkPot.py | # Doesn't use unittest - system | # Uses robot - unit/ ( - # Test helpers - textProvider.py | - extensionPointTestHelpers.py | - objectProvider.py | - test_speechManager/speechManagerTestHarness.py - ) - ) - -- repo: https://github.com/rhysd/actionlint - rev: v1.7.12 - hooks: - # Lint GitHub Actions workflow files. - - id: actionlint - -- repo: https://github.com/asottile/add-trailing-comma - rev: v4.0.0 - hooks: - # Ruff preserves indent/new-line formatting of function arguments, list items, and similar iterables, - # if a trailing comma is added. - # This adds a trailing comma to args/iterable items in case it was missed. - - id: add-trailing-comma - -- repo: https://github.com/astral-sh/ruff-pre-commit - # Matches Ruff version in pyproject. - rev: v0.15.9 - hooks: - - id: ruff - name: lint with ruff - args: [ --fix ] - - id: ruff-format - name: format with ruff - -- repo: https://github.com/astral-sh/uv-pre-commit - rev: 0.11.7 - hooks: - - id: uv-lock - name: Verify uv lock file - # Override python interpreter from .python-versions as that is too strict for pre-commit.ci - args: ["-p3.13"] - -- repo: https://github.com/DavidAnson/markdownlint-cli2 - rev: v0.22.0 - hooks: - - id: markdownlint-cli2 - name: Lint markdown files - args: ["--fix"] - -- repo: local - hooks: - - id: checkPo - name: Check po files - entry: uv run source/l10nUtil.py checkPo - language: python - types: [pofile] - - id: scons-source - name: build c/c++ files - entry: cmd.exe /c "scons source --all-cores" - language: system - pass_filenames: false - types_or: [c, c++] - - id: checkPot - name: translation string check - entry: ./runcheckpot.bat --all-cores - language: script - pass_filenames: false - types: [python] - files: ^source/.*$ - exclude: "(sconscript|sconstruct)$" - - id: unitTest - name: unit tests - entry: ./rununittests.bat - language: script - pass_filenames: false - types_or: [python, c, c++, batch] - - id: licenseCheck - name: Check license compatibility of pip dependencies - files: ^(runlicensecheck\.bat|pyproject\.toml)$ - entry: ./runlicensecheck.bat - language: script - pass_filenames: false - - id: pyright - name: type check with pyright - entry: uv run pyright - language: system - types: [python] - exclude: "(sconscript|sconstruct)$" diff --git a/prek.toml b/prek.toml new file mode 100644 index 00000000000..96d57f83b42 --- /dev/null +++ b/prek.toml @@ -0,0 +1,223 @@ +# Configuration file for `prek`, a git hook framework written in Rust. +# See https://prek.j178.dev for more information. +#:schema https://www.schemastore.org/prek.json + +exclude = '^user_docs/(?!en/).+/.+\.md$' +default_language_version = { python = "python3.13" } + +[[repos]] +repo = "meta" +hooks = [ + { id = "check-useless-excludes" }, + { id = "check-hooks-apply" } +] + +[[repos]] +repo = "https://github.com/pre-commit/pre-commit-hooks" +rev = "v6.0.0" +hooks = [ + { + id = "no-commit-to-branch", + args = [ + "--branch", + "master", + "--branch", + "beta", + "--branch", + "rc" + ] + }, + { + id = "check-added-large-files", + exclude_types = [ + "pofile", + "ttf" + ], + exclude = '\.(dic|sfd|xliff)$' + }, + { id = "check-ast" }, + { id = "check-case-conflict" }, + { id = "check-merge-conflict" }, + { id = "debug-statements" }, + { + id = "trailing-whitespace", + groups = ["autofix"], + types_or = [ + "python", + "c", + "c++", + "batch", + "markdown", + "toml", + "yaml", + "powershell" + ] + }, + { + id = "end-of-file-fixer", + groups = ["autofix"], + types_or = [ + "python", + "c", + "c++", + "batch", + "markdown", + "toml", + "yaml", + "powershell" + ] + }, + { + id = "fix-byte-order-marker", + groups = ["autofix"], + types_or = [ + "python", + "c", + "c++", + "batch", + "markdown", + "toml", + "yaml", + "powershell" + ] + }, + { id = "check-toml" }, + { id = "check-yaml" }, + { id = "check-xml" }, + { id = "check-vcs-permalinks" }, + { id = "check-illegal-windows-names" }, + { + id = "name-tests-test", + args = ["--unittest"], + exclude = """ +(?x)^tests/( + checkPot.py | # Doesn't use unittest + system | # Uses robot + unit/ ( + # Test helpers + textProvider.py | + extensionPointTestHelpers.py | + objectProvider.py | + test_speechManager/speechManagerTestHarness.py + ) +) +""" + } +] + +[[repos]] +repo = "https://github.com/rhysd/actionlint" +rev = "v1.7.12" +hooks = [ + { id = "actionlint" } +] + +[[repos]] +repo = "https://github.com/asottile/add-trailing-comma" +rev = "v4.0.0" +hooks = [ + { id = "add-trailing-comma", groups = ["autofix"] } +] + +[[repos]] +repo = "https://github.com/astral-sh/ruff-pre-commit" +rev = "v0.15.9" +hooks = [ + { + id = "ruff", + name = "lint with ruff", + groups = ["autofix"], + args = ["--fix"] + }, + { + id = "ruff-format", + name = "format with ruff", + groups = ["autofix"] + } +] + +[[repos]] +repo = "https://github.com/astral-sh/uv-pre-commit" +rev = "0.11.7" +hooks = [ + { + id = "uv-lock", + name = "Verify uv lock file", + groups = ["autofix"], + args = ["-p3.13"] + } +] + +[[repos]] +repo = "https://github.com/DavidAnson/markdownlint-cli2" +rev = "v0.22.0" +hooks = [ + { + id = "markdownlint-cli2", + name = "Lint markdown files", + groups = ["autofix"], + args = ["--fix"] + } +] + +[[repos]] +repo = "local" +hooks = [ + { + id = "checkPo", + name = "Check po files", + entry = "uv run source/l10nUtil.py checkPo", + language = "python", + types = ["pofile"] + }, + { + id = "scons-source", + name = "build c/c++ files", + entry = 'cmd.exe /c "scons source --all-cores"', + language = "system", + pass_filenames = false, + types_or = [ + "c", + "c++" + ] + }, + { + id = "checkPot", + name = "translation string check", + entry = "./runcheckpot.bat --all-cores", + language = "script", + pass_filenames = false, + types = ["python"], + files = "^source/.*$", + exclude = "(sconscript|sconstruct)$" + }, + { + id = "unitTest", + name = "unit tests", + entry = "./rununittests.bat", + language = "script", + pass_filenames = false, + types_or = [ + "python", + "c", + "c++", + "batch" + ] + }, + { + id = "licenseCheck", + name = "Check license compatibility of pip dependencies", + files = '^(runlicensecheck\.bat|pyproject\.toml)$', + entry = "./runlicensecheck.bat", + language = "script", + pass_filenames = false + }, + { + id = "pyright", + name = "type check with pyright", + entry = "uv run pyright", + language = "system", + types = ["python"], + exclude = "(sconscript|sconstruct)$" + } +] From d14b8220a67569844bc119b84747cc1a5ca3724b Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:27:22 +0200 Subject: [PATCH 03/26] Add autofix-or-fail GitHub Actions workflow Replaces pre-commit.ci with native prek: a single job that auto-fixes on push events and runs a blocking lint gate on every event. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/autofix.yml | 86 +++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/autofix.yml diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml new file mode 100644 index 00000000000..78c8356c1ce --- /dev/null +++ b/.github/workflows/autofix.yml @@ -0,0 +1,86 @@ +# Autofix-or-fail workflow. +# +# This replaces pre-commit.ci with native prek. It is a single job with two roles: +# - Autofix: on push events, run the fixer hooks (the "autofix" group), commit +# any fixes back to the branch, and push them. +# - Gate: on every event, run all hooks (minus the heavy/env-bound ones that +# have their own dedicated jobs in testAndPublish.yml) as a blocking check. +# +# The autofix step needs write access. On fork `pull_request` events GitHub +# downgrades the token to read-only, so autofix is guarded to push events only +# (it runs in a contributor's fork when they enable Actions, and on NV Access +# `try-**` branches). On a fork PR with Actions disabled nothing gets fixed, so +# any lint issue fails the gate, forcing a manual fix or enabling fork Actions. + +name: Autofix or fail + +on: + push: + # Never auto-fix or push to the protected branches; PRs targeting them are + # handled by the pull_request trigger below. + branches-ignore: + - master + - beta + - rc + pull_request: + branches: + - master + - beta + - rc + +# contents: write is needed so the autofix step can push fixes back. +# GitHub automatically downgrades this to read-only on fork pull_request events. +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + autofix: + name: Auto-fix and lint gate + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Set up python + uses: actions/setup-python@v6 + with: + python-version: '3.13.13' + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v7 + # Autofix only on push events (write token), and never react to the bot's + # own auto-fix commit (loop safety; GITHUB_TOKEN-pushed commits do not + # re-trigger workflows, but this guards the rare manual re-run case too). + - name: Apply prek auto-fixes + if: github.event_name == 'push' && github.actor != 'github-actions[bot]' + shell: bash + env: + REF_NAME: ${{ github.ref_name }} + run: | + # Fixers exit non-zero when they change files; that is expected here, so + # do not let it fail the job. + uv run prek run --group autofix --all-files || true + if ! git diff --quiet; then + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add -A + git commit -m "Auto-fix: apply prek fixes" + # Push via an explicit refspec so this works even if checkout left us + # in a detached HEAD state. + git push origin HEAD:"$REF_NAME" + fi + # Blocking lint gate: runs on every event. The 6 skipped hooks are the + # heavy / environment-bound checks that have their own dedicated jobs in + # testAndPublish.yml. A non-zero exit here fails the job. + - name: Lint gate + shell: bash + run: | + uv run prek run --all-files \ + --skip checkPo \ + --skip scons-source \ + --skip checkPot \ + --skip unitTest \ + --skip licenseCheck \ + --skip pyright From 213d8ce638ebd3491e11dda377ba5fa19ab44a74 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:32:32 +0200 Subject: [PATCH 04/26] Settle uv env before prek gate and cross-reference python version in autofix.yml Add a step after setup-uv that runs `uv run prek run uv-lock --all-files`, mirroring the checkPo job in testAndPublish.yml. On pull_request events the lint gate is the first uv run, so settling the environment here avoids the spurious hook failure caused by uv regenerating uv.lock mid-run. Also add a comment noting the hardcoded python-version must be kept in sync with defaultPythonVersion in testAndPublish.yml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/autofix.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 78c8356c1ce..bb357c54704 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -47,9 +47,16 @@ jobs: - name: Set up python uses: actions/setup-python@v6 with: + # Keep in sync with defaultPythonVersion in testAndPublish.yml. python-version: '3.13.13' - name: Install the latest version of uv uses: astral-sh/setup-uv@v7 + - name: Settle the uv environment + # Ensure the uv environment is up to date before any other prek run. + # If the uv environment is outdated, running prek will trigger uv to generate a uv.lock file, + # which causes hooks to fail without clear errors because prek fails when files are changed during its run. + # On pull_request events the gate below is the first uv run, so settle here to avoid that spurious failure. + run: uv run prek run uv-lock --all-files # Autofix only on push events (write token), and never react to the bot's # own auto-fix commit (loop safety; GITHUB_TOKEN-pushed commits do not # re-trigger workflows, but this guards the rare manual re-run case too). From 48c1aeca91c740fadcc0d5f0324c40278ec73db4 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:34:16 +0200 Subject: [PATCH 05/26] Add scheduled prek auto-update workflow Replaces the monthly pre-commit.ci autoupdate with a GitHub Actions workflow that runs `prek auto-update` on a monthly schedule (and on manual dispatch) and opens a pull request with the updated hook revisions via peter-evans/create-pull-request. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/prekAutoUpdate.yml | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/prekAutoUpdate.yml diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml new file mode 100644 index 00000000000..5a5f28d9dcd --- /dev/null +++ b/.github/workflows/prekAutoUpdate.yml @@ -0,0 +1,37 @@ +name: Prek auto-update + +on: + schedule: + # Monthly, at midnight on the first day of the month. + - cron: '0 0 1 * *' + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + prekAutoUpdate: + name: Prek auto-update + runs-on: ubuntu-latest + steps: + # Auto-update only rewrites hook revisions in prek.toml, + # so submodule content is not needed. + - name: Checkout code + uses: actions/checkout@v6 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v7 + # Run prek as an ephemeral tool; it only edits prek.toml + # and does not need the project environment. + - name: Run prek auto-update + run: uvx prek auto-update + - name: Create pull request + uses: peter-evans/create-pull-request@v7 + with: + branch: prek-autoupdate + delete-branch: true + title: Pre-commit auto-update + commit-message: Pre-commit auto-update + author: github-actions + body: | + Automated update of prek hook revisions in `prek.toml`. From 313f715259116c9917b75f24af9d688277992380 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:35:14 +0200 Subject: [PATCH 06/26] Use consistent "Prek auto-update" title for auto-update PR Align the auto-update PR title and commit message with the workflow name now that the project uses prek instead of pre-commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/prekAutoUpdate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index 5a5f28d9dcd..ffc6b01c4a6 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -30,8 +30,8 @@ jobs: with: branch: prek-autoupdate delete-branch: true - title: Pre-commit auto-update - commit-message: Pre-commit auto-update + title: Prek auto-update + commit-message: Prek auto-update author: github-actions body: | Automated update of prek hook revisions in `prek.toml`. From 4c22e9620df5f8eb0db698c41629f6695dc1e610 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:39:34 +0200 Subject: [PATCH 07/26] Docs and workflows: complete pre-commit.ci to prek + GitHub Actions migration Update documentation, changelog, and the remaining raw pre-commit usage to reflect the full migration away from pre-commit.ci to native prek with GitHub Actions: - readme.md: remove the obsolete pre-commit.ci status badge. - projectDocs/dev/contributing.md: replace pre-commit.ci instructions with the GitHub Actions lint-gate / fork-autofix model. - projectDocs/testing/automated.md: rename the hooks section to "Git hooks (prek)", state hooks are configured in native prek.toml (not .pre-commit-config.yaml), and clarify the uv run vs global invocation styles. - user_docs/en/changes.md: expand the developer changelog entry to cover dropping pre-commit.ci, native prek.toml config, and the new GitHub Actions autofix-or-fail and monthly prek auto-update workflows. - .github/workflows/add-new-language.yml: install prek hooks via uv run prek install instead of raw pip install pre-commit. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/add-new-language.yml | 9 +++------ projectDocs/dev/contributing.md | 6 +++--- projectDocs/testing/automated.md | 18 ++++++++++++------ readme.md | 1 - user_docs/en/changes.md | 3 ++- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/workflows/add-new-language.yml b/.github/workflows/add-new-language.yml index 9133d9c2225..84687cdd38d 100644 --- a/.github/workflows/add-new-language.yml +++ b/.github/workflows/add-new-language.yml @@ -44,13 +44,10 @@ jobs: crowdinProjectID: ${{ vars.CROWDIN_PROJECT_ID }} crowdinAuthToken: ${{ secrets.CROWDIN_AUTH_TOKEN }} - - name: Install pre-commit - run: | - python -m pip install --upgrade pip - pip install pre-commit - pre-commit install + - name: Install prek Git hooks + run: uv run prek install - # Job will fail if pre-commit checks fail + # Job will fail if prek checks fail - name: Commit language files only id: commit shell: bash diff --git a/projectDocs/dev/contributing.md b/projectDocs/dev/contributing.md index 08931594f6c..75878fb58e0 100644 --- a/projectDocs/dev/contributing.md +++ b/projectDocs/dev/contributing.md @@ -94,9 +94,9 @@ Refer to ["Proposing Major Changes"](./proposingMajorChanges.md) before opening * Consider if the PR should be made against `beta` or `rc` in the case of addressing bugs introduced in the current release cycle. 1. CI/CD testing: * Every time a PR has a commit pushed to it, CI/CD checks will be run - * [pre-commit.ci](https://pre-commit.ci/) will apply linting fixes. - * re-run pre-commit on a pull request by commenting `pre-commit.ci run`. - * prevent pre-commit from pushing by putting `[skip ci]`, `[ci skip]`, `[skip pre-commit.ci]`, or `[pre-commit.ci skip]` in the commit message. + * GitHub Actions runs a lint check on every PR, which fails until any lint issues are fixed. + * To have lint fixes applied and pushed automatically, enable GitHub Actions on your fork so the autofix workflow runs on your branch pushes. + * Otherwise, fix the reported lint issues manually. * GitHub Actions will build a copy of NVDA when changes are pushed to your PR. A build artifact will be created for a successful build to allow for testing the PR. * GitHub Actions will run system tests and other tests. diff --git a/projectDocs/testing/automated.md b/projectDocs/testing/automated.md index c4c896f95d5..8a32ea6d318 100644 --- a/projectDocs/testing/automated.md +++ b/projectDocs/testing/automated.md @@ -3,20 +3,25 @@ If you make a change to the NVDA code, you should run NVDA's automated tests. These tests help to ensure that code changes do not unintentionally break functionality that was previously working. -## Pre-commit hooks +## Git hooks (prek) -Pre-commit hooks can be used to automatically run linting, translatable string checks and unit tests on files staged for commit. +Git hooks can be used to automatically run linting, translatable string checks and unit tests on files staged for commit. This will automatically apply lint fixes where possible, and will cancel the commit on lint issues and other test failures. -NVDA uses [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative to [pre-commit](https://pre-commit.com/) that reads the same `.pre-commit-config.yaml`. +NVDA uses [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative to [pre-commit](https://pre-commit.com/). +The hooks are configured in prek's native `prek.toml`. + +There are two ways to run prek, and the examples below use both: + +* Via the project's uv environment, prefixing commands with `uv run` (e.g. `uv run prek install`). This needs no separate installation. +* Via a global install, calling `prek` directly (e.g. `prek install`). Install it once with `uv tool install prek`. From a shell, [set up the Git hooks](https://prek.j178.dev/reference/cli/#prek-install) for your NVDA python environment: `uv run prek install` -Alternatively, set up the Git hooks globally: +Alternatively, if you installed prek globally, set up the Git hooks with: -1. `uv tool install prek` -1. `prek install --allow-missing-config` +`prek install --allow-missing-config` To skip the hooks from triggering, use the `--no-verify` CLI option. Example: `git commit -m "message" --no-verify`. @@ -29,6 +34,7 @@ Run `uv run prek install -f` once to overwrite it with the prek hook. ### Manually running hooks You can run the hooks manually with [`prek run`](https://prek.j178.dev/reference/cli/#prek-run). +The examples below use the project's uv environment (`uv run prek run …`); if you installed prek globally, drop the `uv run` prefix and call `prek run …` directly. * You can filter files with `--files` and `--all-files` * You can also compare two revisions: diff --git a/readme.md b/readme.md index defd75e247d..e44cc0ee6d5 100644 --- a/readme.md +++ b/readme.md @@ -35,5 +35,4 @@ This includes information on reporting issues, triaging issues, testing, transla * Alpha build status: [![view latest alpha builds](https://github.com/nvaccess/nvda/actions/workflows/testAndPublish.yml/badge.svg?branch=master)](https://github.com/nvaccess/nvda/actions/workflows/testAndPublish.yml?query=branch%3Amaster+event%3Apush) * Beta build status: [![view latest beta builds](https://github.com/nvaccess/nvda/actions/workflows/testAndPublish.yml/badge.svg?branch=beta)](https://github.com/nvaccess/nvda/actions/workflows/testAndPublish.yml?query=branch%3Abeta+event%3Apush) -* Pre-commit status (master): [![view pre-commit checks on master](https://results.pre-commit.ci/badge/github/nvaccess/nvda/master.svg)](https://results.pre-commit.ci/latest/github/nvaccess/nvda/master) * CodeQL security analysis status (master): [![view CodeQL security analysis checks on master](https://github.com/nvaccess/nvda/actions/workflows/codeql.yml/badge.svg)](https://github.com/nvaccess/nvda/actions/workflows/codeql.yml) diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index b832ce22b2b..d41a6e69c40 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -34,7 +34,8 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentation/developerGuide.html#API) for information on NVDA's API deprecation and removal process. * The local Git hook runner has been switched from [pre-commit](https://pre-commit.com/) to [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative. (#20305, @LeonarddeR) - * The existing `.pre-commit-config.yaml` and the [pre-commit.ci](https://pre-commit.ci/) integration are unchanged. + * Hooks are now configured in prek's native `prek.toml` instead of `.pre-commit-config.yaml`. + * The [pre-commit.ci](https://pre-commit.ci/) integration has been dropped entirely; linting and autofixing now run via GitHub Actions, using an autofix-or-fail workflow plus a monthly `prek auto-update` workflow. * Developers who previously ran `pre-commit install` should run `uv run prek install -f` once to replace the installed Git hook. * `braille.BrailleDisplayGesture` now exposes a `cellIndexes` list attribute, replacing the single-valued `routingIndex`. (#20001, @LeonarddeR) * Drivers should set `cellIndexes` directly instead of `routingIndex`. From 723f63aa320acbcffc5dcdb6cb1dbfd5567c1c5d Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:55:21 +0200 Subject: [PATCH 08/26] Run prek via uvx instead of uv run in autofix workflow Use `uvx prek` (an ephemeral, isolated tool run) for both the autofix and lint-gate steps. `uv run` syncs the project environment and can silently regenerate uv.lock as a side effect, which both masks a genuinely stale lock and risks spurious mid-run failures. Running prek via uvx touches no project state, so the uv-lock hook itself properly gates lock staleness. This also removes the now-unnecessary "settle the uv environment" step. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/autofix.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index bb357c54704..9d5ba60e5f0 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -51,12 +51,11 @@ jobs: python-version: '3.13.13' - name: Install the latest version of uv uses: astral-sh/setup-uv@v7 - - name: Settle the uv environment - # Ensure the uv environment is up to date before any other prek run. - # If the uv environment is outdated, running prek will trigger uv to generate a uv.lock file, - # which causes hooks to fail without clear errors because prek fails when files are changed during its run. - # On pull_request events the gate below is the first uv run, so settle here to avoid that spurious failure. - run: uv run prek run uv-lock --all-files + # prek is run via `uvx` (an ephemeral, isolated tool run) rather than + # `uv run`. `uv run` would sync the project environment and could silently + # regenerate uv.lock as a side effect, masking a genuinely stale lock and + # causing spurious mid-run failures. `uvx` touches no project state, so the + # uv-lock hook itself is what gates lock staleness. # Autofix only on push events (write token), and never react to the bot's # own auto-fix commit (loop safety; GITHUB_TOKEN-pushed commits do not # re-trigger workflows, but this guards the rare manual re-run case too). @@ -68,7 +67,7 @@ jobs: run: | # Fixers exit non-zero when they change files; that is expected here, so # do not let it fail the job. - uv run prek run --group autofix --all-files || true + uvx prek run --group autofix --all-files || true if ! git diff --quiet; then git config user.name "github-actions" git config user.email "github-actions@github.com" @@ -84,7 +83,7 @@ jobs: - name: Lint gate shell: bash run: | - uv run prek run --all-files \ + uvx prek run --all-files \ --skip checkPo \ --skip scons-source \ --skip checkPot \ From 8e0b7c44f876d8f636d7b867deeb70e8e0b9b0e8 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 18:57:23 +0200 Subject: [PATCH 09/26] Drop redundant prek hook install from add-new-language workflow The addLanguage branch push triggers the autofix-or-fail workflow (it is a non-protected branch), which runs the lint gate and autofix. Installing a local prek Git hook here was redundant and was the last remaining use of `uv run prek` in CI. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/add-new-language.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/add-new-language.yml b/.github/workflows/add-new-language.yml index 84687cdd38d..b10759bc7bd 100644 --- a/.github/workflows/add-new-language.yml +++ b/.github/workflows/add-new-language.yml @@ -44,10 +44,9 @@ jobs: crowdinProjectID: ${{ vars.CROWDIN_PROJECT_ID }} crowdinAuthToken: ${{ secrets.CROWDIN_AUTH_TOKEN }} - - name: Install prek Git hooks - run: uv run prek install - - # Job will fail if prek checks fail + # Linting is handled by the autofix-or-fail workflow, which runs on the push + # of the addLanguage branch below (a non-protected branch); no local hook is + # installed here. - name: Commit language files only id: commit shell: bash From 8716980f8776c1b65b0ce6cdfd64f9cad56a5a6c Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 19:35:26 +0200 Subject: [PATCH 10/26] Add NVDA license headers to the new prek workflows Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/autofix.yml | 14 +++++++------- .github/workflows/prekAutoUpdate.yml | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 9d5ba60e5f0..7eca598ec0e 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -1,6 +1,9 @@ -# Autofix-or-fail workflow. -# -# This replaces pre-commit.ci with native prek. It is a single job with two roles: +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2026 NV Access Limited, Leonard de Ruijter +# This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. +# For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt + +# This flow has a single job with two roles: # - Autofix: on push events, run the fixer hooks (the "autofix" group), commit # any fixes back to the branch, and push them. # - Gate: on every event, run all hooks (minus the heavy/env-bound ones that @@ -9,7 +12,7 @@ # The autofix step needs write access. On fork `pull_request` events GitHub # downgrades the token to read-only, so autofix is guarded to push events only # (it runs in a contributor's fork when they enable Actions, and on NV Access -# `try-**` branches). On a fork PR with Actions disabled nothing gets fixed, so +# branches). On a fork PR with Actions disabled nothing gets fixed, so # any lint issue fails the gate, forcing a manual fix or enabling fork Actions. name: Autofix or fail @@ -27,9 +30,6 @@ on: - master - beta - rc - -# contents: write is needed so the autofix step can push fixes back. -# GitHub automatically downgrades this to read-only on fork pull_request events. permissions: contents: write diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index ffc6b01c4a6..bf8b931d539 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -1,3 +1,8 @@ +# A part of NonVisual Desktop Access (NVDA) +# Copyright (C) 2026 NV Access Limited, Leonard de Ruijter +# This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. +# For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt + name: Prek auto-update on: From 34a64e78bdbf0feb4780304303b84bf10444da04 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 19:44:58 +0200 Subject: [PATCH 11/26] One sentence per line in the prek hooks doc Split the two-sentence list items in the prek invocation-styles list so each sentence is on its own line, matching the repo's semantic-line-break style. Co-Authored-By: Claude Opus 4.8 (1M context) --- projectDocs/testing/automated.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projectDocs/testing/automated.md b/projectDocs/testing/automated.md index 8a32ea6d318..97938d75f81 100644 --- a/projectDocs/testing/automated.md +++ b/projectDocs/testing/automated.md @@ -12,8 +12,10 @@ The hooks are configured in prek's native `prek.toml`. There are two ways to run prek, and the examples below use both: -* Via the project's uv environment, prefixing commands with `uv run` (e.g. `uv run prek install`). This needs no separate installation. -* Via a global install, calling `prek` directly (e.g. `prek install`). Install it once with `uv tool install prek`. +* Via the project's uv environment, prefixing commands with `uv run` (e.g. `uv run prek install`). + This needs no separate installation. +* Via a global install, calling `prek` directly (e.g. `prek install`). + Install it once with `uv tool install prek`. From a shell, [set up the Git hooks](https://prek.j178.dev/reference/cli/#prek-install) for your NVDA python environment: From 4b26a96ba24921980bdd2ca1bbf3182cfefb3b3d Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 19:52:16 +0200 Subject: [PATCH 12/26] Don't activate uv venv --- .github/workflows/autofix.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 7eca598ec0e..6b0754b1e66 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -44,6 +44,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 + with: + submodules: true - name: Set up python uses: actions/setup-python@v6 with: @@ -51,6 +53,8 @@ jobs: python-version: '3.13.13' - name: Install the latest version of uv uses: astral-sh/setup-uv@v7 + with: + activate-environment: "false" # prek is run via `uvx` (an ephemeral, isolated tool run) rather than # `uv run`. `uv run` would sync the project environment and could silently # regenerate uv.lock as a side effect, masking a genuinely stale lock and From e35c97437b9f33abf32e30e064dad273cb0cf8a2 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 19:56:51 +0200 Subject: [PATCH 13/26] Run autofix on windows --- .github/workflows/autofix.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 6b0754b1e66..af3f766da45 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -40,12 +40,10 @@ concurrency: jobs: autofix: name: Auto-fix and lint gate - runs-on: ubuntu-latest + runs-on: windows-2025-vs2026 steps: - name: Checkout repository uses: actions/checkout@v6 - with: - submodules: true - name: Set up python uses: actions/setup-python@v6 with: From dc817a3b53c8952f0813d5129275099f85366e58 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 20:01:21 +0200 Subject: [PATCH 14/26] Definitely need submodules --- .github/workflows/autofix.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index af3f766da45..3342035b7db 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -44,6 +44,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v6 + with: + submodules: true - name: Set up python uses: actions/setup-python@v6 with: From d01ee9a0416c6ffb359f06cdca6e04763d9f7337 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 21:34:24 +0200 Subject: [PATCH 15/26] Create a linter error on purpose so autofix will pick it up on fork --- source/gui/settingsDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 0f6198523d7..23fd1062f9b 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -453,7 +453,7 @@ def _validationErrorMessageBox( self, message: str, option: str, - category: Optional[str] = None, + category: Optional[str] = None ): if category is None: category = self.title From 360e5236bb01bb3844b53eae93df2e6c950510de Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 11 Jun 2026 19:36:30 +0000 Subject: [PATCH 16/26] Auto-fix: apply prek fixes --- source/gui/settingsDialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/gui/settingsDialogs.py b/source/gui/settingsDialogs.py index 23fd1062f9b..0f6198523d7 100644 --- a/source/gui/settingsDialogs.py +++ b/source/gui/settingsDialogs.py @@ -453,7 +453,7 @@ def _validationErrorMessageBox( self, message: str, option: str, - category: Optional[str] = None + category: Optional[str] = None, ): if category is None: category = self.title From 5c2768e7c889a058a22323205431564023cdea03 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 21:55:25 +0200 Subject: [PATCH 17/26] Use prek-action for lint gate and standalone prek for auto-update Run the autofix.yml lint gate via j178/prek-action@v2 instead of `uvx prek` so prek's hook environments are cached across runs. The action installs prek standalone, so it never touches uv.lock. The autofix step keeps using `uvx prek` because it needs the commit/push logic the action does not provide. Install prek as a standalone binary in prekAutoUpdate.yml via the cargo-dist installer instead of uv. auto-update only rewrites hook revisions in prek.toml, so neither uv nor the project environment is needed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/autofix.yml | 21 +++++++++++++-------- .github/workflows/prekAutoUpdate.yml | 13 ++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 3342035b7db..5b1d8d042d8 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -84,13 +84,18 @@ jobs: # Blocking lint gate: runs on every event. The 6 skipped hooks are the # heavy / environment-bound checks that have their own dedicated jobs in # testAndPublish.yml. A non-zero exit here fails the job. + # Run via prek-action (not `uvx prek`) so prek's hook environments are + # cached across runs. The action installs prek standalone, so it does not + # touch uv.lock; on push events the gate runs against the tree already + # fixed by the autofix step above. - name: Lint gate - shell: bash - run: | - uvx prek run --all-files \ - --skip checkPo \ - --skip scons-source \ - --skip checkPot \ - --skip unitTest \ - --skip licenseCheck \ + uses: j178/prek-action@v2 + with: + extra-args: >- + --all-files + --skip checkPo + --skip scons-source + --skip checkPot + --skip unitTest + --skip licenseCheck --skip pyright diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index bf8b931d539..5debd4c5287 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -24,12 +24,15 @@ jobs: # so submodule content is not needed. - name: Checkout code uses: actions/checkout@v6 - - name: Install the latest version of uv - uses: astral-sh/setup-uv@v7 - # Run prek as an ephemeral tool; it only edits prek.toml - # and does not need the project environment. + # Install prek as a standalone binary; auto-update only rewrites hook + # revisions in prek.toml, so neither uv nor the project environment is + # needed. The cargo-dist installer adds prek to $GITHUB_PATH. + - name: Install prek + run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/latest/download/prek-installer.sh | sh + # --cooldown-days skips releases younger than 3 days, avoiding pinning to + # a freshly published (and potentially yanked/broken) hook revision. - name: Run prek auto-update - run: uvx prek auto-update + run: prek auto-update --cooldown-days 3 - name: Create pull request uses: peter-evans/create-pull-request@v7 with: From b2aa55c731b33441c732329f8e7ccce7ac08e263 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 22:21:14 +0200 Subject: [PATCH 18/26] Update docs --- .github/workflows/autofix.yml | 9 +++++++-- projectDocs/dev/contributing.md | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 5b1d8d042d8..9ebdeec67cf 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -61,8 +61,13 @@ jobs: # causing spurious mid-run failures. `uvx` touches no project state, so the # uv-lock hook itself is what gates lock staleness. # Autofix only on push events (write token), and never react to the bot's - # own auto-fix commit (loop safety; GITHUB_TOKEN-pushed commits do not - # re-trigger workflows, but this guards the rare manual re-run case too). + # own auto-fix commit. A commit pushed with this repo's GITHUB_TOKEN does + # not re-trigger this repo's own push workflow, so the fix commit cannot + # loop back into another autofix run here; the actor guard below just + # covers the rare manual re-run case too. (Note: on a fork PR the fix + # commit DOES start a fresh pull_request run on the base repo, since that + # is a different repository - but it lands as "action required" pending + # maintainer approval because the bot is the triggering actor.) - name: Apply prek auto-fixes if: github.event_name == 'push' && github.actor != 'github-actions[bot]' shell: bash diff --git a/projectDocs/dev/contributing.md b/projectDocs/dev/contributing.md index 75878fb58e0..f7a8c53ddd2 100644 --- a/projectDocs/dev/contributing.md +++ b/projectDocs/dev/contributing.md @@ -96,6 +96,8 @@ Refer to ["Proposing Major Changes"](./proposingMajorChanges.md) before opening * Every time a PR has a commit pushed to it, CI/CD checks will be run * GitHub Actions runs a lint check on every PR, which fails until any lint issues are fixed. * To have lint fixes applied and pushed automatically, enable GitHub Actions on your fork so the autofix workflow runs on your branch pushes. + * When the autofix workflow pushes a fix commit, your PR's checks do re-run, but since that commit is authored by the GitHub Actions bot the run is held for maintainer approval (it shows as "action required" / "approve and run"). + To get the checks running without waiting for approval, push any follow-up commit yourself. * Otherwise, fix the reported lint issues manually. * GitHub Actions will build a copy of NVDA when changes are pushed to your PR. A build artifact will be created for a successful build to allow for testing the PR. From fa7beab5ed18a8b1a39f516cb2111aea1dfae1e4 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 11 Jun 2026 22:40:10 +0200 Subject: [PATCH 19/26] Guard prek auto-update to the upstream repo The auto-update schedule is an upstream-only maintenance task. Forks enable Actions for the autofix workflow; without this guard they would also run this schedule and open spurious auto-update PRs against their own fork. Restrict the job to github.repository == nvaccess/nvda. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/prekAutoUpdate.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index 5debd4c5287..53cc2af5bea 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -18,6 +18,10 @@ permissions: jobs: prekAutoUpdate: name: Prek auto-update + # Upstream-only maintenance task. Forks enable Actions for the autofix + # workflow; without this guard they would also run this schedule and open + # spurious auto-update PRs against their own fork. + if: github.repository == 'nvaccess/nvda' runs-on: ubuntu-latest steps: # Auto-update only rewrites hook revisions in prek.toml, From 4f97e6da48b6afe6737ca98047629ed8708b812d Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 22 Jun 2026 08:31:38 +0200 Subject: [PATCH 20/26] Review actions --- .github/workflows/add-new-language.yml | 7 +++--- .github/workflows/prekAutoUpdate.yml | 32 +++++++++++++++----------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/.github/workflows/add-new-language.yml b/.github/workflows/add-new-language.yml index b10759bc7bd..83d99ed3ebb 100644 --- a/.github/workflows/add-new-language.yml +++ b/.github/workflows/add-new-language.yml @@ -44,9 +44,6 @@ jobs: crowdinProjectID: ${{ vars.CROWDIN_PROJECT_ID }} crowdinAuthToken: ${{ secrets.CROWDIN_AUTH_TOKEN }} - # Linting is handled by the autofix-or-fail workflow, which runs on the push - # of the addLanguage branch below (a non-protected branch); no local hook is - # installed here. - name: Commit language files only id: commit shell: bash @@ -57,6 +54,10 @@ jobs: git add source/locale/${{ matrix.newLanguage }} git add user_docs/${{ matrix.newLanguage }} + # Lint the staged translation files. If a hook fails or rewrites a file, + # this step fails, so the job stops here and no pull request is opened. + uv run prek run + # Check if there are any changes to commit if git diff --staged --quiet; then echo "No changes to commit" diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index 53cc2af5bea..ea1e0be02d8 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -18,10 +18,6 @@ permissions: jobs: prekAutoUpdate: name: Prek auto-update - # Upstream-only maintenance task. Forks enable Actions for the autofix - # workflow; without this guard they would also run this schedule and open - # spurious auto-update PRs against their own fork. - if: github.repository == 'nvaccess/nvda' runs-on: ubuntu-latest steps: # Auto-update only rewrites hook revisions in prek.toml, @@ -38,12 +34,22 @@ jobs: - name: Run prek auto-update run: prek auto-update --cooldown-days 3 - name: Create pull request - uses: peter-evans/create-pull-request@v7 - with: - branch: prek-autoupdate - delete-branch: true - title: Prek auto-update - commit-message: Prek auto-update - author: github-actions - body: | - Automated update of prek hook revisions in `prek.toml`. + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + if git diff --quiet; then + echo "No hook updates available." + exit 0 + fi + git config --local user.name "github-actions" + git config --local user.email "github-actions@github.com" + git checkout -b prek-autoupdate + git commit -am "Prek auto-update" + # Force push so a re-run replaces a stale auto-update branch. + git push --force --set-upstream origin prek-autoupdate + # Reuse the existing PR if one is already open from a previous run. + gh pr create --base master --head prek-autoupdate \ + --title "Prek auto-update" \ + --body "Automated update of prek hook revisions in \`prek.toml\`." \ + || echo "A pull request for prek-autoupdate already exists." From 9ef3c9007e9fb84ce2a7a0c16b73876fed135404 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 22 Jun 2026 10:09:19 +0200 Subject: [PATCH 21/26] Bump action deps --- .github/workflows/add-new-language.yml | 2 +- .github/workflows/autofix.yml | 2 +- .github/workflows/prekAutoUpdate.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/add-new-language.yml b/.github/workflows/add-new-language.yml index 83d99ed3ebb..897b39f4711 100644 --- a/.github/workflows/add-new-language.yml +++ b/.github/workflows/add-new-language.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: beta fetch-depth: 1 diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 9ebdeec67cf..29df5e1d9cf 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -43,7 +43,7 @@ jobs: runs-on: windows-2025-vs2026 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: true - name: Set up python diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index ea1e0be02d8..ae56dba8783 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -23,7 +23,7 @@ jobs: # Auto-update only rewrites hook revisions in prek.toml, # so submodule content is not needed. - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 # Install prek as a standalone binary; auto-update only rewrites hook # revisions in prek.toml, so neither uv nor the project environment is # needed. The cargo-dist installer adds prek to $GITHUB_PATH. From 6af4c059f05eae2fb8372cea9fc45a16b5bf1647 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Tue, 23 Jun 2026 07:22:08 +0200 Subject: [PATCH 22/26] Back to .pre-commit-config.yaml --- .pre-commit-config.yaml | 158 ++++++++++++++++++++++++++++ prek.toml | 223 ---------------------------------------- 2 files changed, 158 insertions(+), 223 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 prek.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000000..e15b330a0cb --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,158 @@ +exclude: ^user_docs/(?!en/).+/.+\.md$ + +default_language_version: + python: python3.13 + +repos: +- repo: meta + hooks: + # ensures that exclude directives apply to any file in the repository. + - id: check-useless-excludes + # ensures that the configured hooks apply to at least one file in the repository. + - id: check-hooks-apply + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + # Prevents commits to certain branches + - id: no-commit-to-branch + args: ["--branch", "master", "--branch", "beta", "--branch", "rc"] + # Checks that large files have not been added. Default cut-off for "large" files is 500kb. + - id: check-added-large-files + # POFiles and TTF fonts can't be made smaller + # XLIFF files can be moved here from exclude when pre-commit/identify#558 is released. + exclude_types: ["pofile", "ttf"] + # Same applies for XLIFF, NVDA dictionary (.dic) files and Spline Font Database (.SFD) files, but these aren't recognised by the Identify library. + exclude: "\\.(dic|sfd|xliff)$" + # Checks python syntax + - id: check-ast + # Checks for filenames that will conflict on case insensitive filesystems (the majority of Windows filesystems, most of the time) + - id: check-case-conflict + # Checks for artifacts from resolving merge conflicts. + - id: check-merge-conflict + # Checks Python files for debug statements, such as python's breakpoint function, or those inserted by some IDEs. + - id: debug-statements + # Removes trailing whitespace. + - id: trailing-whitespace + groups: [autofix] + types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] + # Ensures all files end in 1 (and only 1) newline. + - id: end-of-file-fixer + groups: [autofix] + types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] + # Removes the UTF-8 BOM from files that have it. + # See https://github.com/nvaccess/nvda/blob/master/projectDocs/dev/codingStandards.md#encoding + - id: fix-byte-order-marker + groups: [autofix] + types_or: [python, c, c++, batch, markdown, toml, yaml, powershell] + # Validates TOML files. + - id: check-toml + # Validates YAML files. + - id: check-yaml + # Validates XML files. + - id: check-xml + # Ensures that links to lines in files under version control point to a particular commit. + - id: check-vcs-permalinks + # Avoids using reserved Windows filenames. + - id: check-illegal-windows-names + # Checks that tests are named test_*.py. + - id: name-tests-test + args: ["--unittest"] + # Exclude Python files under `tests/` that aren't unittest files. + # This is a Python verbose regular expression. + # See https://docs.python.org/3/library/re.html#re.VERBOSE + exclude: | + (?x)^tests/( + checkPot.py | # Doesn't use unittest + system | # Uses robot + unit/ ( + # Test helpers + textProvider.py | + extensionPointTestHelpers.py | + objectProvider.py | + test_speechManager/speechManagerTestHarness.py + ) + ) + +- repo: https://github.com/rhysd/actionlint + rev: v1.7.12 + hooks: + # Lint GitHub Actions workflow files. + - id: actionlint + +- repo: https://github.com/asottile/add-trailing-comma + rev: v4.0.0 + hooks: + # Ruff preserves indent/new-line formatting of function arguments, list items, and similar iterables, + # if a trailing comma is added. + # This adds a trailing comma to args/iterable items in case it was missed. + - id: add-trailing-comma + groups: [autofix] + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Matches Ruff version in pyproject. + rev: v0.15.9 + hooks: + - id: ruff + name: lint with ruff + groups: [autofix] + args: [ --fix ] + - id: ruff-format + name: format with ruff + groups: [autofix] + +- repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.11.7 + hooks: + - id: uv-lock + name: Verify uv lock file + groups: [autofix] + args: ["-p3.13"] + +- repo: https://github.com/DavidAnson/markdownlint-cli2 + rev: v0.22.0 + hooks: + - id: markdownlint-cli2 + name: Lint markdown files + groups: [autofix] + args: ["--fix"] + +- repo: local + hooks: + - id: checkPo + name: Check po files + entry: uv run source/l10nUtil.py checkPo + language: python + types: [pofile] + - id: scons-source + name: build c/c++ files + entry: cmd.exe /c "scons source --all-cores" + language: system + pass_filenames: false + types_or: [c, c++] + - id: checkPot + name: translation string check + entry: ./runcheckpot.bat --all-cores + language: script + pass_filenames: false + types: [python] + files: ^source/.*$ + exclude: "(sconscript|sconstruct)$" + - id: unitTest + name: unit tests + entry: ./rununittests.bat + language: script + pass_filenames: false + types_or: [python, c, c++, batch] + - id: licenseCheck + name: Check license compatibility of pip dependencies + files: ^(runlicensecheck\.bat|pyproject\.toml)$ + entry: ./runlicensecheck.bat + language: script + pass_filenames: false + - id: pyright + name: type check with pyright + entry: uv run pyright + language: system + types: [python] + exclude: "(sconscript|sconstruct)$" diff --git a/prek.toml b/prek.toml deleted file mode 100644 index 96d57f83b42..00000000000 --- a/prek.toml +++ /dev/null @@ -1,223 +0,0 @@ -# Configuration file for `prek`, a git hook framework written in Rust. -# See https://prek.j178.dev for more information. -#:schema https://www.schemastore.org/prek.json - -exclude = '^user_docs/(?!en/).+/.+\.md$' -default_language_version = { python = "python3.13" } - -[[repos]] -repo = "meta" -hooks = [ - { id = "check-useless-excludes" }, - { id = "check-hooks-apply" } -] - -[[repos]] -repo = "https://github.com/pre-commit/pre-commit-hooks" -rev = "v6.0.0" -hooks = [ - { - id = "no-commit-to-branch", - args = [ - "--branch", - "master", - "--branch", - "beta", - "--branch", - "rc" - ] - }, - { - id = "check-added-large-files", - exclude_types = [ - "pofile", - "ttf" - ], - exclude = '\.(dic|sfd|xliff)$' - }, - { id = "check-ast" }, - { id = "check-case-conflict" }, - { id = "check-merge-conflict" }, - { id = "debug-statements" }, - { - id = "trailing-whitespace", - groups = ["autofix"], - types_or = [ - "python", - "c", - "c++", - "batch", - "markdown", - "toml", - "yaml", - "powershell" - ] - }, - { - id = "end-of-file-fixer", - groups = ["autofix"], - types_or = [ - "python", - "c", - "c++", - "batch", - "markdown", - "toml", - "yaml", - "powershell" - ] - }, - { - id = "fix-byte-order-marker", - groups = ["autofix"], - types_or = [ - "python", - "c", - "c++", - "batch", - "markdown", - "toml", - "yaml", - "powershell" - ] - }, - { id = "check-toml" }, - { id = "check-yaml" }, - { id = "check-xml" }, - { id = "check-vcs-permalinks" }, - { id = "check-illegal-windows-names" }, - { - id = "name-tests-test", - args = ["--unittest"], - exclude = """ -(?x)^tests/( - checkPot.py | # Doesn't use unittest - system | # Uses robot - unit/ ( - # Test helpers - textProvider.py | - extensionPointTestHelpers.py | - objectProvider.py | - test_speechManager/speechManagerTestHarness.py - ) -) -""" - } -] - -[[repos]] -repo = "https://github.com/rhysd/actionlint" -rev = "v1.7.12" -hooks = [ - { id = "actionlint" } -] - -[[repos]] -repo = "https://github.com/asottile/add-trailing-comma" -rev = "v4.0.0" -hooks = [ - { id = "add-trailing-comma", groups = ["autofix"] } -] - -[[repos]] -repo = "https://github.com/astral-sh/ruff-pre-commit" -rev = "v0.15.9" -hooks = [ - { - id = "ruff", - name = "lint with ruff", - groups = ["autofix"], - args = ["--fix"] - }, - { - id = "ruff-format", - name = "format with ruff", - groups = ["autofix"] - } -] - -[[repos]] -repo = "https://github.com/astral-sh/uv-pre-commit" -rev = "0.11.7" -hooks = [ - { - id = "uv-lock", - name = "Verify uv lock file", - groups = ["autofix"], - args = ["-p3.13"] - } -] - -[[repos]] -repo = "https://github.com/DavidAnson/markdownlint-cli2" -rev = "v0.22.0" -hooks = [ - { - id = "markdownlint-cli2", - name = "Lint markdown files", - groups = ["autofix"], - args = ["--fix"] - } -] - -[[repos]] -repo = "local" -hooks = [ - { - id = "checkPo", - name = "Check po files", - entry = "uv run source/l10nUtil.py checkPo", - language = "python", - types = ["pofile"] - }, - { - id = "scons-source", - name = "build c/c++ files", - entry = 'cmd.exe /c "scons source --all-cores"', - language = "system", - pass_filenames = false, - types_or = [ - "c", - "c++" - ] - }, - { - id = "checkPot", - name = "translation string check", - entry = "./runcheckpot.bat --all-cores", - language = "script", - pass_filenames = false, - types = ["python"], - files = "^source/.*$", - exclude = "(sconscript|sconstruct)$" - }, - { - id = "unitTest", - name = "unit tests", - entry = "./rununittests.bat", - language = "script", - pass_filenames = false, - types_or = [ - "python", - "c", - "c++", - "batch" - ] - }, - { - id = "licenseCheck", - name = "Check license compatibility of pip dependencies", - files = '^(runlicensecheck\.bat|pyproject\.toml)$', - entry = "./runlicensecheck.bat", - language = "script", - pass_filenames = false - }, - { - id = "pyright", - name = "type check with pyright", - entry = "uv run pyright", - language = "system", - types = ["python"], - exclude = "(sconscript|sconstruct)$" - } -] From 433dccecc11c20b679a45d92eefb4010c058f928 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Wed, 24 Jun 2026 09:46:25 +0200 Subject: [PATCH 23/26] Remove stale mentions of prek.toml --- .github/workflows/prekAutoUpdate.yml | 8 ++------ .pre-commit-config.yaml | 1 - projectDocs/testing/automated.md | 1 - user_docs/en/changes.md | 3 +-- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.github/workflows/prekAutoUpdate.yml b/.github/workflows/prekAutoUpdate.yml index ae56dba8783..be0901bac53 100644 --- a/.github/workflows/prekAutoUpdate.yml +++ b/.github/workflows/prekAutoUpdate.yml @@ -20,13 +20,9 @@ jobs: name: Prek auto-update runs-on: ubuntu-latest steps: - # Auto-update only rewrites hook revisions in prek.toml, - # so submodule content is not needed. - name: Checkout code uses: actions/checkout@v7 - # Install prek as a standalone binary; auto-update only rewrites hook - # revisions in prek.toml, so neither uv nor the project environment is - # needed. The cargo-dist installer adds prek to $GITHUB_PATH. + # Install prek as a standalone binary - name: Install prek run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/j178/prek/releases/latest/download/prek-installer.sh | sh # --cooldown-days skips releases younger than 3 days, avoiding pinning to @@ -51,5 +47,5 @@ jobs: # Reuse the existing PR if one is already open from a previous run. gh pr create --base master --head prek-autoupdate \ --title "Prek auto-update" \ - --body "Automated update of prek hook revisions in \`prek.toml\`." \ + --body "Automated update of prek hook revisions." \ || echo "A pull request for prek-autoupdate already exists." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e15b330a0cb..1fffaf0b2ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -107,7 +107,6 @@ repos: - id: uv-lock name: Verify uv lock file groups: [autofix] - args: ["-p3.13"] - repo: https://github.com/DavidAnson/markdownlint-cli2 rev: v0.22.0 diff --git a/projectDocs/testing/automated.md b/projectDocs/testing/automated.md index 97938d75f81..815a118db8b 100644 --- a/projectDocs/testing/automated.md +++ b/projectDocs/testing/automated.md @@ -8,7 +8,6 @@ These tests help to ensure that code changes do not unintentionally break functi Git hooks can be used to automatically run linting, translatable string checks and unit tests on files staged for commit. This will automatically apply lint fixes where possible, and will cancel the commit on lint issues and other test failures. NVDA uses [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative to [pre-commit](https://pre-commit.com/). -The hooks are configured in prek's native `prek.toml`. There are two ways to run prek, and the examples below use both: diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index bb97e008b9c..d1f3fd9601e 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -47,8 +47,7 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentation/developerGuide.html#API) for information on NVDA's API deprecation and removal process. * The local Git hook runner has been switched from [pre-commit](https://pre-commit.com/) to [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative. (#20305, @LeonarddeR) - * Hooks are now configured in prek's native `prek.toml` instead of `.pre-commit-config.yaml`. - * The [pre-commit.ci](https://pre-commit.ci/) integration has been dropped entirely; linting and autofixing now run via GitHub Actions, using an autofix-or-fail workflow plus a monthly `prek auto-update` workflow. + * The [pre-commit.ci](https://pre-commit.ci/) integration has been dropped entirely; linting and autofixing now run via GitHub Actions, using an autofix-or-fail workflow plus an automatic `prek auto-update` workflow. * Developers who previously ran `pre-commit install` should run `uv run prek install -f` once to replace the installed Git hook. * The `braille` module is now a package. The public API is unchanged: every symbol previously accessed as `braille.X` remains available. (#12772, @LeonarddeR) From 20a40eaab220333d58929f511791637632e8cea3 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Thu, 25 Jun 2026 16:33:34 +0200 Subject: [PATCH 24/26] Review actions --- .github/workflows/autofix.yml | 13 ++++++++++--- .pre-commit-config.yaml | 24 ++++++++++++++++++++++++ user_docs/en/changes.md | 3 ++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 29df5e1d9cf..371b3eec320 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -30,6 +30,8 @@ on: - master - beta - rc + - 'try-**' + workflow_dispatch: permissions: contents: write @@ -74,9 +76,14 @@ jobs: env: REF_NAME: ${{ github.ref_name }} run: | - # Fixers exit non-zero when they change files; that is expected here, so - # do not let it fail the job. - uvx prek run --group autofix --all-files || true + # Fixers exit non-zero when they change files. Some fixers' output feeds + # another fixer, so a single pass may not reach a stable tree. + # Re-run on the fixed tree until prek exits clean or we hit the cap. + for attempt in 1 2 3; do + if uvx prek run --group autofix --all-files; then + break + fi + done if ! git diff --quiet; then git config user.name "github-actions" git config user.email "github-actions@github.com" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1fffaf0b2ff..4a36b372f73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,31 @@ exclude: ^user_docs/(?!en/).+/.+\.md$ +# https://pre-commit.ci/ +# Configuration for Continuous Integration service +ci: + # Can't run Windows scons scripts on Linux. + # unit testing requires our python environment, + # which cannot be configured with pre-commit.ci: + # https://stackoverflow.com/questions/70778806/pre-commit-not-using-virtual-environment . + # Can't run licenseCheck as it relies on telemetry, + # which CI blocks. + # Pyright does not seem to work in pre-commit CI + skip: [checkPo, scons-source, checkPot, unitTest, licenseCheck, pyright] + autofix_prs: false + autoupdate_schedule: quarterly + autoupdate_commit_msg: "Pre-commit auto-update" + autofix_commit_msg: "Pre-commit auto-fix" + submodules: true + default_language_version: python: python3.13 repos: +- repo: https://github.com/pre-commit-ci/pre-commit-ci-config + rev: v1.6.1 + hooks: + - id: check-pre-commit-ci-config + - repo: meta hooks: # ensures that exclude directives apply to any file in the repository. @@ -107,6 +129,8 @@ repos: - id: uv-lock name: Verify uv lock file groups: [autofix] + # Override python interpreter from .python-versions as that is too strict for pre-commit.ci + args: ["-p3.13"] - repo: https://github.com/DavidAnson/markdownlint-cli2 rev: v0.22.0 diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index d1f3fd9601e..3ee1f99f235 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -47,7 +47,8 @@ Please refer to [the developer guide](https://download.nvaccess.org/documentation/developerGuide.html#API) for information on NVDA's API deprecation and removal process. * The local Git hook runner has been switched from [pre-commit](https://pre-commit.com/) to [prek](https://prek.j178.dev/), a faster, drop-in compatible alternative. (#20305, @LeonarddeR) - * The [pre-commit.ci](https://pre-commit.ci/) integration has been dropped entirely; linting and autofixing now run via GitHub Actions, using an autofix-or-fail workflow plus an automatic `prek auto-update` workflow. + * The [pre-commit.ci](https://pre-commit.ci/) integration will be dropped entirely;. + Linting and autofixing now run via GitHub Actions, using an autofix-or-fail workflow plus an automatic `prek auto-update` workflow. * Developers who previously ran `pre-commit install` should run `uv run prek install -f` once to replace the installed Git hook. * The `braille` module is now a package. The public API is unchanged: every symbol previously accessed as `braille.X` remains available. (#12772, @LeonarddeR) From 10a28c7696486b2c1f179851401e9bd616aae5dc Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter <3049216+LeonarddeR@users.noreply.github.com> Date: Fri, 26 Jun 2026 07:16:54 +0200 Subject: [PATCH 25/26] Update .github/workflows/autofix.yml --- .github/workflows/autofix.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 371b3eec320..852c8f91022 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -71,7 +71,6 @@ jobs: # is a different repository - but it lands as "action required" pending # maintainer approval because the bot is the triggering actor.) - name: Apply prek auto-fixes - if: github.event_name == 'push' && github.actor != 'github-actions[bot]' shell: bash env: REF_NAME: ${{ github.ref_name }} From 5004afa0900e4474a65043d92acd076a9105a370 Mon Sep 17 00:00:00 2001 From: Leonard de Ruijter Date: Mon, 29 Jun 2026 08:53:19 +0200 Subject: [PATCH 26/26] Update job --- .github/workflows/autofix.yml | 43 ++++++++--------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 852c8f91022..8149f60bd32 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -3,24 +3,11 @@ # This file may be used under the terms of the GNU General Public License, version 2 or later, as modified by the NVDA license. # For full terms and any additional permissions, see the NVDA license file: https://github.com/nvaccess/nvda/blob/master/copying.txt -# This flow has a single job with two roles: -# - Autofix: on push events, run the fixer hooks (the "autofix" group), commit -# any fixes back to the branch, and push them. -# - Gate: on every event, run all hooks (minus the heavy/env-bound ones that -# have their own dedicated jobs in testAndPublish.yml) as a blocking check. -# -# The autofix step needs write access. On fork `pull_request` events GitHub -# downgrades the token to read-only, so autofix is guarded to push events only -# (it runs in a contributor's fork when they enable Actions, and on NV Access -# branches). On a fork PR with Actions disabled nothing gets fixed, so -# any lint issue fails the gate, forcing a manual fix or enabling fork Actions. - name: Autofix or fail on: push: - # Never auto-fix or push to the protected branches; PRs targeting them are - # handled by the pull_request trigger below. + # Never auto-fix the protected branches; PRs to them use pull_request below. branches-ignore: - master - beta @@ -42,6 +29,9 @@ concurrency: jobs: autofix: name: Auto-fix and lint gate + # On the base repo the pull_request run is the gate; skip its push run on non-Fork pushes to + # avoid a duplicate. Fork pushes still run for autofix. + if: github.event_name != 'push' || github.repository != 'nvaccess/nvda' runs-on: windows-2025-vs2026 steps: - name: Checkout repository @@ -57,20 +47,9 @@ jobs: uses: astral-sh/setup-uv@v7 with: activate-environment: "false" - # prek is run via `uvx` (an ephemeral, isolated tool run) rather than - # `uv run`. `uv run` would sync the project environment and could silently - # regenerate uv.lock as a side effect, masking a genuinely stale lock and - # causing spurious mid-run failures. `uvx` touches no project state, so the - # uv-lock hook itself is what gates lock staleness. - # Autofix only on push events (write token), and never react to the bot's - # own auto-fix commit. A commit pushed with this repo's GITHUB_TOKEN does - # not re-trigger this repo's own push workflow, so the fix commit cannot - # loop back into another autofix run here; the actor guard below just - # covers the rare manual re-run case too. (Note: on a fork PR the fix - # commit DOES start a fresh pull_request run on the base repo, since that - # is a different repository - but it lands as "action required" pending - # maintainer approval because the bot is the triggering actor.) + # Autofix role: run the fixer hooks (the "autofix" group) and push the fixes. - name: Apply prek auto-fixes + if: github.event_name == 'push' shell: bash env: REF_NAME: ${{ github.ref_name }} @@ -92,13 +71,9 @@ jobs: # in a detached HEAD state. git push origin HEAD:"$REF_NAME" fi - # Blocking lint gate: runs on every event. The 6 skipped hooks are the - # heavy / environment-bound checks that have their own dedicated jobs in - # testAndPublish.yml. A non-zero exit here fails the job. - # Run via prek-action (not `uvx prek`) so prek's hook environments are - # cached across runs. The action installs prek standalone, so it does not - # touch uv.lock; on push events the gate runs against the tree already - # fixed by the autofix step above. + # Gate role: the merge-required blocking check (non-zero exit fails the job). + # The 6 skipped hooks are the heavy/env-bound ones with dedicated jobs in + # testAndPublish.yml. - name: Lint gate uses: j178/prek-action@v2 with: