From 2d418254a5e44aba1fa59e2adf4a27eefdbe745c Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 08:36:18 +0100 Subject: [PATCH 1/9] chore: raise Node baseline to >=23.6 Node's native TypeScript type-stripping only runs unflagged from 23.6 onward; CI's actions/setup-node steps and the release job now pin the current LTS (24) to satisfy that floor, and package.json declares the real minimum via "engines". This unblocks converting lint-staged's own config to a real .ts file in the next commit. --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/dependabot.yml | 2 +- package.json | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bf19eb..8013d43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci - run: npx turbo run typecheck @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci - run: npx turbo run lint @@ -91,7 +91,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci - run: npx turbo run format:check @@ -141,7 +141,7 @@ jobs: token: ${{ steps.app-token.outputs.token }} - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci - name: Release diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index c93bafb..6ffaede 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -113,7 +113,7 @@ jobs: - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "22" + node-version: "24" cache: npm - run: npm ci diff --git a/package.json b/package.json index 0e9874d..23ff6ff 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "private": true, "type": "module", "packageManager": "npm@11.19.0", + "engines": { + "node": ">=23.6.0" + }, "description": "Runs Claude Code for pull-request review, issue triage, or interactive @claude assistance, with shared org-wide prompts and per-mode tool allowlists.", "scripts": { "prepare": "husky", From 3dc2001ed13cd5af8538b121c7fc225cd3b4919e Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 08:36:49 +0100 Subject: [PATCH 2/9] chore: convert lint-staged config to TypeScript lint-staged dynamically imports its raw config file, so a .ts config only loads without a flag once Node's native type-stripping is unflagged (23.6+, now the repository's declared minimum). Matches the repository's other *.config.ts files and uses lint-staged's own defineConfig helper for a typed Configuration object instead of a bare default export. --- lint-staged.config.js => lint-staged.config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename lint-staged.config.js => lint-staged.config.ts (53%) diff --git a/lint-staged.config.js b/lint-staged.config.ts similarity index 53% rename from lint-staged.config.js rename to lint-staged.config.ts index 4ec9856..686a400 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.ts @@ -1,7 +1,9 @@ -// Plain JS, not lint-staged.config.ts like the repository's other *.config.ts files: lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping, which is only on by default from Node 23.6 onward and needs an explicit --experimental-strip-types flag on the Node 22 this repository's CI (and therefore its documented supported version) actually runs. A plain ESM config needs no flag on any supported Node version. +// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping, which is only on by default from Node 23.6 onward -- this repository's documented supported version (see the root "engines" field in package.json) was raised to match, so this can now be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out. // eslint.config.ts excludes package-lock.json from lint entirely (generated, not hand-maintained); the `!(package-lock).json` glob below applies the same exclusion to prettier, which has no ignores option of its own here. -export default { +import { defineConfig } from "lint-staged/config"; + +export default defineConfig({ "*.{ts,md}": [ "eslint --fix --cache --cache-location node_modules/.cache/eslint/.eslintcache", "prettier --write --cache", @@ -10,4 +12,4 @@ export default { "eslint --fix --cache --cache-location node_modules/.cache/eslint/.eslintcache", "!(package-lock).json": "prettier --write --cache", "*.{yml,yaml}": "prettier --write --cache", -}; +}); From 2fd2a43c95f071c1077527a52d3754a9f8ae0105 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 08:54:01 +0100 Subject: [PATCH 3/9] chore: give CI jobs headroom for a slower npm ci under Node 24 npm ci has been taking most of 5 minutes to install this repository's dependencies under npm 11 (bundled with the Node 24 baseline), even with a fully warm actions/setup-node cache -- against a prior baseline of well under a minute on Node 22's bundled npm. The existing 5-minute timeout on commitlint/typecheck/lint/format left too little margin and was intermittently cancelling jobs mid-install; raised to 10 minutes. --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8013d43..e04139e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,8 @@ jobs: # The local commit-msg hook (.husky/commit-msg) can be bypassed -- a merge via the GitHub web UI, a commit from a tool that doesn't run hooks. This checks every commit on a pull request against the same rules. Nothing to check on a plain push to main: those commits already passed this exact job on their pull request. if: github.event_name == 'pull_request' runs-on: ubuntu-latest - timeout-minutes: 5 + # 5 was enough while npm ci reliably finished in well under a minute on a warm cache; npm 11 (bundled with the Node 24 baseline below) has been observed taking most of 5 minutes for the same install even with a cache hit, so every job below that runs npm ci gets the same headroom. + timeout-minutes: 10 permissions: contents: read steps: @@ -54,7 +55,7 @@ jobs: typecheck: name: Typecheck runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: @@ -69,7 +70,7 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: @@ -84,7 +85,7 @@ jobs: format: name: Format runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: From afdfcdac68e88e98ef9c3ddb25b45c32bb451581 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:06:51 +0100 Subject: [PATCH 4/9] Revert "chore: give CI jobs headroom for a slower npm ci under Node 24" This reverts commit 2fd2a43c95f071c1077527a52d3754a9f8ae0105. --- .github/workflows/ci.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e04139e..8013d43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,7 @@ jobs: # The local commit-msg hook (.husky/commit-msg) can be bypassed -- a merge via the GitHub web UI, a commit from a tool that doesn't run hooks. This checks every commit on a pull request against the same rules. Nothing to check on a plain push to main: those commits already passed this exact job on their pull request. if: github.event_name == 'pull_request' runs-on: ubuntu-latest - # 5 was enough while npm ci reliably finished in well under a minute on a warm cache; npm 11 (bundled with the Node 24 baseline below) has been observed taking most of 5 minutes for the same install even with a cache hit, so every job below that runs npm ci gets the same headroom. - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: @@ -55,7 +54,7 @@ jobs: typecheck: name: Typecheck runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: @@ -70,7 +69,7 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: @@ -85,7 +84,7 @@ jobs: format: name: Format runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 5 permissions: contents: read steps: From 0f8d83d640f476e7df1925a67a8ca8191161bb58 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:06:57 +0100 Subject: [PATCH 5/9] Revert "chore: raise Node baseline to >=23.6" This reverts commit 2d418254a5e44aba1fa59e2adf4a27eefdbe745c. --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/dependabot.yml | 2 +- package.json | 3 --- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8013d43..7bf19eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci - run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose @@ -61,7 +61,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci - run: npx turbo run typecheck @@ -76,7 +76,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci - run: npx turbo run lint @@ -91,7 +91,7 @@ jobs: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci - run: npx turbo run format:check @@ -141,7 +141,7 @@ jobs: token: ${{ steps.app-token.outputs.token }} - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci - name: Release diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml index 6ffaede..c93bafb 100644 --- a/.github/workflows/dependabot.yml +++ b/.github/workflows/dependabot.yml @@ -113,7 +113,7 @@ jobs: - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: "24" + node-version: "22" cache: npm - run: npm ci diff --git a/package.json b/package.json index 23ff6ff..0e9874d 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,6 @@ "private": true, "type": "module", "packageManager": "npm@11.19.0", - "engines": { - "node": ">=23.6.0" - }, "description": "Runs Claude Code for pull-request review, issue triage, or interactive @claude assistance, with shared org-wide prompts and per-mode tool allowlists.", "scripts": { "prepare": "husky", From 9a1a1d99e2b3dbc1e4bf37eeaa84e1881dd70eec Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:07:36 +0100 Subject: [PATCH 6/9] fix: correct the Node version needed for unflagged type stripping Node backported "type stripping enabled by default" to both v23.6.0 and the 22.x LTS line at v22.18.0, not only to v23.6.0+ as the prior comment claimed. actions/setup-node's node-version: "22" resolves to the latest 22.x release, already past 22.18.0, so lint-staged.config.ts never needed a Node baseline bump to load as a real .ts file. Declares the actual constraint via "engines" instead of an unnecessarily high floor. --- lint-staged.config.ts | 2 +- package.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lint-staged.config.ts b/lint-staged.config.ts index 686a400..9edaf50 100644 --- a/lint-staged.config.ts +++ b/lint-staged.config.ts @@ -1,4 +1,4 @@ -// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping, which is only on by default from Node 23.6 onward -- this repository's documented supported version (see the root "engines" field in package.json) was raised to match, so this can now be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out. +// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping. That's enabled by default from v23.6.0 onward, and Node backported the same default-enable to the 22.x LTS line at v22.18.0 (see the "engines" field below and https://nodejs.org/en/blog/release/v22.18.0) -- so this can be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out, without raising the Node baseline any higher than the type-stripping backport itself already requires. // eslint.config.ts excludes package-lock.json from lint entirely (generated, not hand-maintained); the `!(package-lock).json` glob below applies the same exclusion to prettier, which has no ignores option of its own here. import { defineConfig } from "lint-staged/config"; diff --git a/package.json b/package.json index 0e9874d..bd7a209 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "private": true, "type": "module", "packageManager": "npm@11.19.0", + "engines": { + "node": ">=22.18.0 <23.0.0 || >=23.6.0" + }, "description": "Runs Claude Code for pull-request review, issue triage, or interactive @claude assistance, with shared org-wide prompts and per-mode tool allowlists.", "scripts": { "prepare": "husky", From 4d3fd285c85cf6415851a11b07fa507322e9d045 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:14:42 +0100 Subject: [PATCH 7/9] chore: give CI jobs headroom for a slow npm ci npm ci installing this repository's dependencies has intermittently taken most of 5 minutes on GitHub's shared runners even with a warm actions/setup-node cache -- observed on both Node 22 and Node 24, so it isn't tied to a specific Node version. Raised commitlint/typecheck/ lint/format's timeout-minutes from 5 to 10 for real margin. --- .github/workflows/ci.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bf19eb..34c9bcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,8 @@ jobs: # The local commit-msg hook (.husky/commit-msg) can be bypassed -- a merge via the GitHub web UI, a commit from a tool that doesn't run hooks. This checks every commit on a pull request against the same rules. Nothing to check on a plain push to main: those commits already passed this exact job on their pull request. if: github.event_name == 'pull_request' runs-on: ubuntu-latest - timeout-minutes: 5 + # 5 has been observed too tight: npm ci installing this repository's ~500 packages has intermittently taken most of 5 minutes on GitHub's shared runners even with a warm actions/setup-node cache, independent of which Node version is in use -- every job below that runs npm ci gets the same headroom. + timeout-minutes: 10 permissions: contents: read steps: @@ -54,7 +55,7 @@ jobs: typecheck: name: Typecheck runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: @@ -69,7 +70,7 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: @@ -84,7 +85,7 @@ jobs: format: name: Format runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 permissions: contents: read steps: From 659db9a7e73beb17e5ac14ae790c5703b0d24d85 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:23:15 +0100 Subject: [PATCH 8/9] docs: point the engines-field pointer at package.json by name "see the 'engines' field below" read as if the field were further down this same file, when it's actually in package.json. --- lint-staged.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint-staged.config.ts b/lint-staged.config.ts index 9edaf50..6887455 100644 --- a/lint-staged.config.ts +++ b/lint-staged.config.ts @@ -1,4 +1,4 @@ -// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping. That's enabled by default from v23.6.0 onward, and Node backported the same default-enable to the 22.x LTS line at v22.18.0 (see the "engines" field below and https://nodejs.org/en/blog/release/v22.18.0) -- so this can be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out, without raising the Node baseline any higher than the type-stripping backport itself already requires. +// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping. That's enabled by default from v23.6.0 onward, and Node backported the same default-enable to the 22.x LTS line at v22.18.0 (see the "engines" field in package.json and https://nodejs.org/en/blog/release/v22.18.0) -- so this can be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out, without raising the Node baseline any higher than the type-stripping backport itself already requires. // eslint.config.ts excludes package-lock.json from lint entirely (generated, not hand-maintained); the `!(package-lock).json` glob below applies the same exclusion to prettier, which has no ignores option of its own here. import { defineConfig } from "lint-staged/config"; From 491d8c10a7f20b5757e337c1f4f088dc0d986735 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Fri, 4 Sep 2026 09:30:06 +0100 Subject: [PATCH 9/9] fix: raise engines.node floor to match lint-staged's own requirement lint-staged@17.4.1 declares its own engines.node of >=22.22.1, higher than the >=22.18.0 this repository previously declared -- a contributor between those two versions would satisfy this repository's stated floor but still hit an EBADENGINE warning installing lint-staged itself. Bumped to match, and reworded the comment in lint-staged.config.ts so it no longer implies the "engines" floor is set purely by the type-stripping requirement. --- lint-staged.config.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lint-staged.config.ts b/lint-staged.config.ts index 6887455..9b1ee8f 100644 --- a/lint-staged.config.ts +++ b/lint-staged.config.ts @@ -1,4 +1,4 @@ -// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping. That's enabled by default from v23.6.0 onward, and Node backported the same default-enable to the 22.x LTS line at v22.18.0 (see the "engines" field in package.json and https://nodejs.org/en/blog/release/v22.18.0) -- so this can be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out, without raising the Node baseline any higher than the type-stripping backport itself already requires. +// lint-staged loads a TypeScript config via dynamic import of the raw file, relying on Node's native type-stripping. That's enabled by default from v23.6.0 onward, and Node backported the same default-enable to the 22.x LTS line at v22.18.0 (https://nodejs.org/en/blog/release/v22.18.0) -- so this can be a real .ts file like the repository's other *.config.ts files instead of needing a plain-JS carve-out. The "engines" field in package.json pins the 22.x floor higher than that, at v22.22.1: not the type-stripping requirement itself, but lint-staged's own declared minimum, which is the binding constraint for this file to load at all. // eslint.config.ts excludes package-lock.json from lint entirely (generated, not hand-maintained); the `!(package-lock).json` glob below applies the same exclusion to prettier, which has no ignores option of its own here. import { defineConfig } from "lint-staged/config"; diff --git a/package.json b/package.json index bd7a209..23c86e1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "packageManager": "npm@11.19.0", "engines": { - "node": ">=22.18.0 <23.0.0 || >=23.6.0" + "node": ">=22.22.1 <23.0.0 || >=23.6.0" }, "description": "Runs Claude Code for pull-request review, issue triage, or interactive @claude assistance, with shared org-wide prompts and per-mode tool allowlists.", "scripts": {