From 04c41dc5574d2474af158245daf562b55dc8c00c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 21:11:20 -0400 Subject: [PATCH 1/3] chore(migration): step 08 root pre-commit hooks Merge the pre-commit configs of api, federation-api, bagel-cli and the docs into one root .pre-commit-config.yaml, scoped per folder and using each tool's own config files; replace the frontends' husky hooks with local hooks that run their lint-staged configs; run the hooks in CI, with the docs link check as a non-blocking job; update the READMEs. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 37 +++ .pre-commit-config.yaml | 228 +++++++++++++++++++ README.md | 14 +- docs/.pre-commit-config.yaml | 45 ---- docs/README.md | 16 +- tools/annotation-tool/.husky/post-merge | 4 - tools/annotation-tool/.husky/pre-commit | 5 - tools/annotation-tool/README.md | 2 +- tools/annotation-tool/package.json | 3 +- tools/api/.pre-commit-config.yaml | 60 ----- tools/api/README.md | 6 + tools/bagel-cli/.pre-commit-config.yaml | 45 ---- tools/bagel-cli/README.md | 6 + tools/federation-api/.pre-commit-config.yaml | 38 ---- tools/federation-api/README.md | 6 + tools/query-tool/.husky/post-merge | 3 - tools/query-tool/.husky/pre-commit | 3 - tools/query-tool/README.md | 2 +- tools/query-tool/package.json | 1 - 19 files changed, 312 insertions(+), 212 deletions(-) create mode 100644 .pre-commit-config.yaml delete mode 100644 docs/.pre-commit-config.yaml delete mode 100755 tools/annotation-tool/.husky/post-merge delete mode 100755 tools/annotation-tool/.husky/pre-commit delete mode 100644 tools/api/.pre-commit-config.yaml delete mode 100644 tools/bagel-cli/.pre-commit-config.yaml delete mode 100644 tools/federation-api/.pre-commit-config.yaml delete mode 100755 tools/query-tool/.husky/post-merge delete mode 100755 tools/query-tool/.husky/pre-commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6b0198c..34ce88e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,43 @@ jobs: - name: Run shellcheck run: shellcheck scripts/*.sh + pre-commit: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.12" + + # markdown-link-check runs in its own job below. The frontends' lint-staged + # hooks only check staged files; test-node.yml runs the same checks. + - name: Run pre-commit + uses: pre-commit/action@v3.0.1 + env: + SKIP: markdown-link-check-docs,lint-staged-query-tool,lint-staged-annotation-tool + + markdown-link-check: + name: Docs link check (non-blocking) + runs-on: ubuntu-latest + # Already fails upstream on an external link (see migration/baseline.md) + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.12" + + - name: Run markdown-link-check + uses: pre-commit/action@v3.0.1 + with: + extra_args: markdown-link-check-docs --all-files + changes: runs-on: ubuntu-latest permissions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..78885ddc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,228 @@ +# Root pre-commit configuration for the Neurobagel monorepo. +# +# Merged from the upstream configs of api, federation-api, bagel-cli and the +# docs, keeping each repository's hook versions and arguments. Hooks run from +# the repository root, so every hook is scoped to its folder with `files:` and +# is given that folder's own config file explicitly: tools such as flake8, +# isort, codespell and markdownlint otherwise look for config files in the +# current directory only. Each hook has an alias `-` for SKIP=. +# +# The frontends keep their lint-staged configs; the local hooks at the end run +# lint-staged in the tool folder on staged files (this replaces husky). + +ci: + autofix_prs: false + skip: [docker-compose-check-api] + +repos: + # ------------------------------------------------- Python tools and docs + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 26.10.1 + hooks: + - id: black + alias: black-api + files: ^tools/api/ + args: [--config=tools/api/pyproject.toml] + - id: black + alias: black-federation-api + files: ^tools/federation-api/ + args: [--config=tools/federation-api/pyproject.toml] + - id: black + alias: black-bagel-cli + files: ^tools/bagel-cli/ + args: [--config=tools/bagel-cli/pyproject.toml] + + - repo: https://github.com/PyCQA/flake8 + rev: 7.4.1 + hooks: + - id: flake8 + alias: flake8-api + files: ^tools/api/ + language_version: python3 + # TODO: replace this with ruff to bring all config into pyproject.toml + args: [--config=tools/api/.flake8] + - id: flake8 + alias: flake8-federation-api + files: ^tools/federation-api/ + language_version: python3 + args: + - --extend-ignore=E501 + - --extend-select=B950 + - --per-file-ignores=tools/federation-api/app/api/models.py:F722 + - id: flake8 + alias: flake8-bagel-cli + files: ^tools/bagel-cli/ + language_version: python3 + args: [--config=tools/bagel-cli/.flake8] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-yaml + alias: check-yaml-api + files: ^tools/api/ + - id: check-toml + alias: check-toml-api + files: ^tools/api/ + - id: pretty-format-json + alias: pretty-format-json-api + files: ^tools/api/ + args: ["--autofix", "--no-sort-keys"] + - id: check-yaml + alias: check-yaml-federation-api + files: ^tools/federation-api/ + - id: check-yaml + alias: check-yaml-bagel-cli + files: ^tools/bagel-cli/ + - id: check-toml + alias: check-toml-bagel-cli + files: ^tools/bagel-cli/ + # docs + - id: check-ast + alias: check-ast-docs + files: ^docs/ + - id: check-case-conflict + alias: check-case-conflict-docs + files: ^docs/ + - id: check-json + alias: check-json-docs + files: ^docs/ + - id: check-merge-conflict + alias: check-merge-conflict-docs + files: ^docs/ + - id: check-yaml + alias: check-yaml-docs + files: ^docs/ + exclude: ^docs/mkdocs\.yml$ + - id: end-of-file-fixer + alias: end-of-file-fixer-docs + files: ^docs/ + - id: mixed-line-ending + alias: mixed-line-ending-docs + files: ^docs/ + args: [--fix=lf] + - id: trailing-whitespace + alias: trailing-whitespace-docs + files: ^docs/ + + - repo: https://github.com/pycqa/isort + rev: 9.0.2 + hooks: + # src_paths default to the settings file's folder, so each tool's package + # (app, bagel) stays first-party, as when upstream ran isort in the tool folder + - id: isort + alias: isort-api + files: ^tools/api/ + args: [--settings-path=tools/api/pyproject.toml] + - id: isort + alias: isort-federation-api + files: ^tools/federation-api/ + args: [--settings-path=tools/federation-api/pyproject.toml] + - id: isort + alias: isort-bagel-cli + files: ^tools/bagel-cli/ + args: [--settings-path=tools/bagel-cli/pyproject.toml] + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.3 + hooks: + - id: codespell + alias: codespell-api + files: ^tools/api/ + additional_dependencies: [tomli] + args: [--config=tools/api/.codespellrc, --toml=tools/api/pyproject.toml] + - id: codespell + alias: codespell-federation-api + files: ^tools/federation-api/ + - id: codespell + alias: codespell-bagel-cli + files: ^tools/bagel-cli/ + additional_dependencies: [tomli] + args: [--toml=tools/bagel-cli/pyproject.toml] + + - repo: https://github.com/iamthefij/docker-pre-commit + rev: v3.0.1 + hooks: + - id: docker-compose-check + alias: docker-compose-check-api + files: ^tools/api/(.*/)?(docker-)?compose\.ya?ml$ + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v2.4.0 + hooks: + - id: mypy + alias: mypy-api + files: ^tools/api/ + additional_dependencies: [pydantic] + args: [--config-file=tools/api/pyproject.toml] + - id: mypy + alias: mypy-bagel-cli + files: ^tools/bagel-cli/ + args: [--config-file=tools/bagel-cli/pyproject.toml] + + # federation-api validated its own GitHub workflows; in the monorepo those are the root workflows + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.38.2 + hooks: + - id: check-github-workflows + files: ^\.github/workflows/ + + # --------------------------------------------------------------- docs + - repo: https://github.com/tcort/markdown-link-check + rev: v3.14.2 + hooks: + - id: markdown-link-check + alias: markdown-link-check-docs + files: ^docs/ + args: [--quiet, --config=docs/md_link_check_config.json] + + # Check formatting of CSS, JSON (prettier: https://prettier.io/) + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.8.1 + hooks: + - id: prettier + alias: prettier-docs + files: ^docs/ + types_or: [css, json] + + - repo: https://github.com/codespell-project/codespell + rev: v2.4.1 + hooks: + - id: codespell + alias: codespell-docs + files: ^docs/ + args: [--config=docs/.codespellrc] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.47.0 + hooks: + - id: markdownlint + alias: markdownlint-docs + files: ^docs/ + args: [-c, docs/.markdownlint.jsonc] + + - repo: https://github.com/psf/black-pre-commit-mirror + rev: 26.1.0 + hooks: + - id: black + alias: black-docs + files: ^docs/ + args: [--safe] + + # ---------------------------------------------------------- frontends + # Same checks as the tools' lint-staged configs (typecheck, eslint, prettier), + # run by lint-staged on staged files. Needs `npm ci` in the tool folder. + - repo: local + hooks: + - id: lint-staged-query-tool + name: lint-staged (query-tool) + language: system + files: ^tools/query-tool/ + pass_filenames: false + entry: bash -c 'cd tools/query-tool && npx --no-install lint-staged' + - id: lint-staged-annotation-tool + name: lint-staged (annotation-tool) + language: system + files: ^tools/annotation-tool/ + pass_filenames: false + entry: bash -c 'cd tools/annotation-tool && npx --no-install lint-staged' diff --git a/README.md b/README.md index 25e4e888..1d036018 100644 --- a/README.md +++ b/README.md @@ -33,11 +33,22 @@ just check-unchanged # check that imported tools' source and tests are unchange `just lint` needs [actionlint](https://github.com/rhysd/actionlint) and [shellcheck](https://www.shellcheck.net/). +## Commit hooks + +One [pre-commit](https://pre-commit.com/) configuration at the root, `.pre-commit-config.yaml`, holds every tool's hooks, each scoped to its folder and using the tool's own config files. Install it once: + +```bash +pip install pre-commit # or: uv tool install pre-commit +pre-commit install +``` + +The frontends' hooks run their `lint-staged` configs and need `npm ci` in `tools/query-tool/` and `tools/annotation-tool/`. Run all hooks on all files with `SKIP=markdown-link-check-docs pre-commit run --all-files`. + ## Repository-wide workflows | Workflow | What it does | | --- | --- | -| `ci.yml` | Lints workflows and scripts, detects which tools a change touches, runs those tools' tests, and checks that imported source and tests are unchanged | +| `ci.yml` | Runs the pre-commit hooks (and the docs link check, non-blocking), lints workflows and scripts, detects which tools a change touches, runs those tools' tests, and checks that imported source and tests are unchanged | | `docs.yml` | Builds the docs on pull requests and deploys them to GitHub Pages on merge | | `linkspector.yml` | Checks the links in api's `README.md` (configured by `tools/api/.linkspector.yml`) on changes to api's Markdown | @@ -61,3 +72,4 @@ just check-unchanged # check that imported tools' source and tests are unchange | R2 (Step 7b) | api, federation-api and query-tool images are built from the repository root (`docker build -f tools//Dockerfile .`, or `just build `), with an allowlist `tools//Dockerfile.dockerignore` per image; query-tool's Dockerfile copies `examples/` directly; the temporary `scripts/materialize-context.sh` is removed | Docker does not follow symlinks out of a tool folder; one build context for all images, with each image's files listed explicitly | api, federation-api, query-tool | [api](tools/api/README.md#docker-image), [federation-api](tools/federation-api/README.md#docker-image), [query-tool](tools/query-tool/README.md#docker-image) | | R3 (Step 7c) | The bagel-cli image installs one pre-built wheel (`tools/bagel-cli/dist/*.whl`), the same file that is published to PyPI, instead of running `pip install bagel` from PyPI; `Dockerfile.nightly` is removed and nightly and release images share one Dockerfile | The image always contains exactly the released (or nightly) artifact, and the build no longer depends on PyPI having the new release yet | bagel-cli | [bagel-cli README: Docker image](tools/bagel-cli/README.md#docker-image) | | Step 7d | api's link check runs from the root workflow `linkspector.yml`, in `tools/api`; the other upstream repository-automation workflows are not carried over (`handle_external_pr`, `add_iss2project`, `stale`, `scorecard`, query-tool's example-diff workflows) or are replaced later | GitHub only runs root workflows | api | [Repository-wide workflows](#repository-wide-workflows) | +| Step 8 | One root `.pre-commit-config.yaml` replaces the per-tool configs of api, federation-api, bagel-cli and docs, and the frontends' husky hooks; CI runs it on every pull request | pre-commit only reads a config at the repository root, and husky only installs hooks there | all | [Commit hooks](#commit-hooks) | diff --git a/docs/.pre-commit-config.yaml b/docs/.pre-commit-config.yaml deleted file mode 100644 index e9a00848..00000000 --- a/docs/.pre-commit-config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-ast - - id: check-case-conflict - - id: check-json - - id: check-merge-conflict - - id: check-yaml - exclude: | - (?x)^( - mkdocs.yml - )$ - - id: end-of-file-fixer - - id: mixed-line-ending - args: [--fix=lf] - - id: trailing-whitespace - - repo: https://github.com/tcort/markdown-link-check - rev: v3.14.2 - hooks: - - id: markdown-link-check - args: - - --quiet - - --config=md_link_check_config.json - # Check formatting of CSS, JSON - # prettier: https://prettier.io/ - - repo: https://github.com/rbubley/mirrors-prettier - rev: v3.8.1 - hooks: - - id: prettier - types_or: [css, json] - - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 - hooks: - - id: codespell - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.47.0 - hooks: - - id: markdownlint - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.1.0 - hooks: - - id: black - args: - - --safe diff --git a/docs/README.md b/docs/README.md index 19be1bd7..547d8f32 100644 --- a/docs/README.md +++ b/docs/README.md @@ -108,9 +108,19 @@ The docs are not versioned or released; every merge to `main` that changes `docs We use several tools in this repository to enforce our style guide (codespell, prettier, markdownlint...). They can all be run in an orchestrated manner using [pre-commit](https://pre-commit.com/) python package. -The hooks are configured in `.pre-commit-config.yaml` in this folder. -In the monorepo, pre-commit only reads a configuration at the repository root, -so these hooks do not run on commit and are not checked in CI at the moment. +The hooks for the docs are part of the repository's root `.pre-commit-config.yaml` +(scoped to `docs/`, with the docs' own `.markdownlint.jsonc`, `.codespellrc` and +`md_link_check_config.json`). Install them from the repository root: + +```bash +pip install pre-commit +pre-commit install +``` + +After this the style guide will be enforced every time you commit: +some pre-commit 'hooks' will try to fix some of the errors they find, +so you may have to restage your file before committing. +CI runs the same hooks on every pull request; the link check runs as a separate, non-blocking job. For markdownlint, you can also use the [VS-code extension](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) diff --git a/tools/annotation-tool/.husky/post-merge b/tools/annotation-tool/.husky/post-merge deleted file mode 100755 index 3aaff098..00000000 --- a/tools/annotation-tool/.husky/post-merge +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -git diff HEAD^ HEAD --exit-code -- ./package.json || npm ci \ No newline at end of file diff --git a/tools/annotation-tool/.husky/pre-commit b/tools/annotation-tool/.husky/pre-commit deleted file mode 100755 index 7d5c4c84..00000000 --- a/tools/annotation-tool/.husky/pre-commit +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" - -npx lint-staged - diff --git a/tools/annotation-tool/README.md b/tools/annotation-tool/README.md index c2a9fa13..1be7deec 100644 --- a/tools/annotation-tool/README.md +++ b/tools/annotation-tool/README.md @@ -75,7 +75,7 @@ cd develop/tools/annotation-tool npm ci ``` -Commit hooks (husky and lint-staged) do not run in the monorepo: husky only installs hooks at the root of a git repository. Run the checks below before committing. +The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/annotation-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. ### Tests diff --git a/tools/annotation-tool/package.json b/tools/annotation-tool/package.json index a2df7130..00aa8a76 100644 --- a/tools/annotation-tool/package.json +++ b/tools/annotation-tool/package.json @@ -14,8 +14,7 @@ "test:e2e": "cypress run --e2e", "test:component": "cypress run --component", "test:unit": "vitest --run --coverage", - "typecheck": "tsc --noEmit --pretty", - "prepare": "husky install" + "typecheck": "tsc --noEmit --pretty" }, "dependencies": { "@emotion/react": "^11.14.0", diff --git a/tools/api/.pre-commit-config.yaml b/tools/api/.pre-commit-config.yaml deleted file mode 100644 index a0a24225..00000000 --- a/tools/api/.pre-commit-config.yaml +++ /dev/null @@ -1,60 +0,0 @@ -ci: - autofix_prs: false - skip: [docker-compose-check] - -repos: - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.10.1 - hooks: - - id: black - args: - - --config=pyproject.toml - - - repo: https://github.com/PyCQA/flake8 - rev: 7.4.1 - hooks: - - id: flake8 - language_version: python3 - args: - # TODO: replace this with ruff to bring all config into pyproject.toml - - --config=.flake8 - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-yaml - - id: check-toml - - id: pretty-format-json - args: - - "--autofix" - - "--no-sort-keys" - - - repo: https://github.com/pycqa/isort - rev: 9.0.2 - hooks: - - id: isort - args: - - --settings-path=pyproject.toml - - - repo: https://github.com/codespell-project/codespell - rev: v2.4.3 - hooks: - - id: codespell - additional_dependencies: - - tomli - args: - - --toml=pyproject.toml - - - repo: https://github.com/iamthefij/docker-pre-commit - rev: v3.0.1 - hooks: - - id: docker-compose-check - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.4.0 - hooks: - - id: mypy - additional_dependencies: - - pydantic - args: - - --config-file=pyproject.toml diff --git a/tools/api/README.md b/tools/api/README.md index 13634dc0..322da57f 100644 --- a/tools/api/README.md +++ b/tools/api/README.md @@ -184,6 +184,12 @@ uv sync --group dev The version is derived from the repository's `api-v*` tags, so the clone needs its full history and tags (the default for `git clone`). +To check your changes on every commit, install the repository's commit hooks (configured in the root `.pre-commit-config.yaml`; the api hooks are black, flake8, isort, codespell and the other checks api used before): + +```bash +uv run pre-commit install +``` + The test graph and its example data come from the monorepo's shared folders: `docker-compose.yml` in `tools/api/` builds on the development and test stack in `stack/` and loads example data from `examples/`, referring to both by relative path (`../../stack`, `../../examples`). The tests read example files through the symlink `neurobagel_examples -> ../../examples`. No submodules are needed. ### Tests diff --git a/tools/bagel-cli/.pre-commit-config.yaml b/tools/bagel-cli/.pre-commit-config.yaml deleted file mode 100644 index ef230a6f..00000000 --- a/tools/bagel-cli/.pre-commit-config.yaml +++ /dev/null @@ -1,45 +0,0 @@ -ci: - autofix_prs: false - -repos: - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.10.1 - hooks: - - id: black - args: - - --config=pyproject.toml - - - repo: https://github.com/PyCQA/flake8 - rev: 7.4.1 - hooks: - - id: flake8 - language_version: python3 - args: - - --config=.flake8 - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-yaml - - id: check-toml - - - repo: https://github.com/pycqa/isort - rev: 9.0.2 - hooks: - - id: isort - args: - - --settings-path=pyproject.toml - - - repo: https://github.com/codespell-project/codespell - rev: v2.4.3 - hooks: - - id: codespell - additional_dependencies: - - tomli - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.4.0 - hooks: - - id: mypy - args: - - --config-file=pyproject.toml diff --git a/tools/bagel-cli/README.md b/tools/bagel-cli/README.md index 6c2e07ff..a7a73e85 100644 --- a/tools/bagel-cli/README.md +++ b/tools/bagel-cli/README.md @@ -58,6 +58,12 @@ The Neurobagel example data used by the tests comes from the monorepo's `example The version is derived from the repository's `bagel-cli-v*` tags, so the clone needs its full history and tags (the default for `git clone`). +To check your changes on every commit, install the repository's commit hooks (configured in the root `.pre-commit-config.yaml`; the bagel-cli hooks are black, flake8, isort, codespell and the other checks bagel-cli used before): + +```bash +uv run pre-commit install +``` + To run the CLI during local development, from `tools/bagel-cli/`: ```bash diff --git a/tools/federation-api/.pre-commit-config.yaml b/tools/federation-api/.pre-commit-config.yaml deleted file mode 100644 index ddaf8ee1..00000000 --- a/tools/federation-api/.pre-commit-config.yaml +++ /dev/null @@ -1,38 +0,0 @@ -ci: - autofix_prs: false - -repos: - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.10.1 - hooks: - - id: black - - - repo: https://github.com/PyCQA/flake8 - rev: 7.4.1 - hooks: - - id: flake8 - language_version: python3 - args: - - --extend-ignore=E501 - - --extend-select=B950 - - --per-file-ignores=./app/api/models.py:F722 - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: check-yaml - - - repo: https://github.com/pycqa/isort - rev: 9.0.2 - hooks: - - id: isort - - - repo: https://github.com/codespell-project/codespell - rev: v2.4.3 - hooks: - - id: codespell - - - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.38.2 - hooks: - - id: check-github-workflows diff --git a/tools/federation-api/README.md b/tools/federation-api/README.md index b5c92c08..82bf02a3 100644 --- a/tools/federation-api/README.md +++ b/tools/federation-api/README.md @@ -84,6 +84,12 @@ uv run uvicorn app.main:app The version is derived from the repository's `federation-api-v*` tags, so the clone needs its full history and tags (the default for `git clone`). +To check your changes on every commit, install the repository's commit hooks (configured in the root `.pre-commit-config.yaml`; the federation-api hooks are black, flake8, isort, codespell and the other checks federation-api used before): + +```bash +uv run pre-commit install +``` + ### Tests From `tools/federation-api/`: diff --git a/tools/query-tool/.husky/post-merge b/tools/query-tool/.husky/post-merge deleted file mode 100755 index af60ae01..00000000 --- a/tools/query-tool/.husky/post-merge +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -git diff HEAD^ HEAD --exit-code -- ./package.json || npm ci \ No newline at end of file diff --git a/tools/query-tool/.husky/pre-commit b/tools/query-tool/.husky/pre-commit deleted file mode 100755 index 2f453ac2..00000000 --- a/tools/query-tool/.husky/pre-commit +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env sh - -npx lint-staged \ No newline at end of file diff --git a/tools/query-tool/README.md b/tools/query-tool/README.md index c4646dd2..2c6e6d6f 100644 --- a/tools/query-tool/README.md +++ b/tools/query-tool/README.md @@ -155,7 +155,7 @@ You can verify the tool is running by watching for the` info messages from Vite #### Developer setup -Commit hooks (husky and lint-staged) do not run in the monorepo: husky only installs hooks at the root of a git repository. Run the lint and format checks listed under [Tests](#tests) before committing. +The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/query-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. ##### Docker compose testing environment for development diff --git a/tools/query-tool/package.json b/tools/query-tool/package.json index 8b7e606e..2985dfa7 100644 --- a/tools/query-tool/package.json +++ b/tools/query-tool/package.json @@ -12,7 +12,6 @@ "preview": "vite preview", "format:fix": "prettier --write .", "format:check": "prettier --check .", - "prepare": "husky", "test:component": "cypress run --component", "test:e2e": "cypress run --e2e" }, From 50f49dad9b0653278be473ad5a60a9faa5ee7cda Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 21:12:58 -0400 Subject: [PATCH 2/3] chore(migration): step 08 explain the npm ci prerequisite of the frontend hooks Co-Authored-By: Claude Opus 5.5 --- .pre-commit-config.yaml | 4 ++-- README.md | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 78885ddc..78a61741 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -219,10 +219,10 @@ repos: language: system files: ^tools/query-tool/ pass_filenames: false - entry: bash -c 'cd tools/query-tool && npx --no-install lint-staged' + entry: bash -c 'cd tools/query-tool && if [ ! -d node_modules ]; then echo "Run npm ci in tools/query-tool first"; exit 1; fi && npx --no-install lint-staged' - id: lint-staged-annotation-tool name: lint-staged (annotation-tool) language: system files: ^tools/annotation-tool/ pass_filenames: false - entry: bash -c 'cd tools/annotation-tool && npx --no-install lint-staged' + entry: bash -c 'cd tools/annotation-tool && if [ ! -d node_modules ]; then echo "Run npm ci in tools/annotation-tool first"; exit 1; fi && npx --no-install lint-staged' diff --git a/README.md b/README.md index 1d036018..0357d96c 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,12 @@ pip install pre-commit # or: uv tool install pre-commit pre-commit install ``` -The frontends' hooks run their `lint-staged` configs and need `npm ci` in `tools/query-tool/` and `tools/annotation-tool/`. Run all hooks on all files with `SKIP=markdown-link-check-docs pre-commit run --all-files`. +The frontends' hooks run their `lint-staged` configs and need `npm ci` in `tools/query-tool/` and `tools/annotation-tool/` (they fail with that instruction otherwise). To run all hooks on all files: + +```bash +(cd tools/query-tool && npm ci) && (cd tools/annotation-tool && npm ci) +SKIP=markdown-link-check-docs pre-commit run --all-files +``` ## Repository-wide workflows From 60dd208dbdd68b317c1663b9e7a42a7e7644d2ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 17 Oct 2026 21:58:36 -0400 Subject: [PATCH 3/3] chore(migration): step 08 rerun npm ci after merges that change frontend dependencies Replace the frontends' husky post-merge hooks with pre-commit post-merge hooks, installed by default, that compare ORIG_HEAD with HEAD for package.json and package-lock.json. Co-Authored-By: Claude Opus 5.5 --- .pre-commit-config.yaml | 25 +++++++++++++++++++++++++ README.md | 2 ++ tools/annotation-tool/README.md | 2 +- tools/query-tool/README.md | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 78a61741..30209dfa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,11 @@ # The frontends keep their lint-staged configs; the local hooks at the end run # lint-staged in the tool folder on staged files (this replaces husky). +# `pre-commit install` sets up both hook types. Hooks run at commit time unless +# they set `stages:` (the frontends' post-merge hooks below). +default_install_hook_types: [pre-commit, post-merge] +default_stages: [pre-commit] + ci: autofix_prs: false skip: [docker-compose-check-api] @@ -226,3 +231,23 @@ repos: files: ^tools/annotation-tool/ pass_filenames: false entry: bash -c 'cd tools/annotation-tool && if [ ! -d node_modules ]; then echo "Run npm ci in tools/annotation-tool first"; exit 1; fi && npx --no-install lint-staged' + + # After a merge or pull (git sets ORIG_HEAD to the previous HEAD), reinstall a + # frontend's dependencies if its package.json or package-lock.json changed. + # Replaces the frontends' husky post-merge hooks. + - repo: local + hooks: + - id: npm-ci-on-merge-query-tool + name: npm ci after merge (query-tool) + language: system + stages: [post-merge] + always_run: true + pass_filenames: false + entry: bash -c 'git diff --quiet ORIG_HEAD HEAD -- tools/query-tool/package.json tools/query-tool/package-lock.json || (cd tools/query-tool && npm ci)' + - id: npm-ci-on-merge-annotation-tool + name: npm ci after merge (annotation-tool) + language: system + stages: [post-merge] + always_run: true + pass_filenames: false + entry: bash -c 'git diff --quiet ORIG_HEAD HEAD -- tools/annotation-tool/package.json tools/annotation-tool/package-lock.json || (cd tools/annotation-tool && npm ci)' diff --git a/README.md b/README.md index 0357d96c..3a16c421 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ pip install pre-commit # or: uv tool install pre-commit pre-commit install ``` +This installs two git hooks: `pre-commit` runs the checks on every commit, and `post-merge` runs `npm ci` in a frontend after a merge or pull that changed its `package.json` or `package-lock.json`, so local `node_modules` stay in sync. + The frontends' hooks run their `lint-staged` configs and need `npm ci` in `tools/query-tool/` and `tools/annotation-tool/` (they fail with that instruction otherwise). To run all hooks on all files: ```bash diff --git a/tools/annotation-tool/README.md b/tools/annotation-tool/README.md index 1be7deec..fa571f57 100644 --- a/tools/annotation-tool/README.md +++ b/tools/annotation-tool/README.md @@ -75,7 +75,7 @@ cd develop/tools/annotation-tool npm ci ``` -The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/annotation-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. +The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/annotation-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. The same install adds a `post-merge` hook that reruns `npm ci` here after a merge or pull that changed `package.json` or `package-lock.json`. ### Tests diff --git a/tools/query-tool/README.md b/tools/query-tool/README.md index 2c6e6d6f..07d1cfec 100644 --- a/tools/query-tool/README.md +++ b/tools/query-tool/README.md @@ -155,7 +155,7 @@ You can verify the tool is running by watching for the` info messages from Vite #### Developer setup -The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/query-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. +The repository's commit hooks (root `.pre-commit-config.yaml`) run this tool's `lint-staged.config.js` (typecheck, eslint, prettier) on staged files in `tools/query-tool/`. Install them once with `pre-commit install` from the repository root (for example after `pip install pre-commit`); they need `npm ci` to have been run here. The same install adds a `post-merge` hook that reruns `npm ci` here after a merge or pull that changed `package.json` or `package-lock.json`. ##### Docker compose testing environment for development