Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
66298bf
feat(guardrails): serve the git hooks to lefthook repositories as a r…
kyle-sexton Oct 23, 2026
994f69c
feat(guardrails): refuse the remote under a custom lefthook source_dir
kyle-sexton Oct 23, 2026
65f4de6
feat(guardrails): pin the lefthook remote to a guardrails-release branch
kyle-sexton Oct 24, 2026
1c24fe3
ci(guardrails): test the lefthook remote end to end before merge and …
kyle-sexton Oct 24, 2026
71050d1
Merge remote-tracking branch 'origin/main' into feat/guardrails-lefth…
kyle-sexton Oct 24, 2026
40458b4
fix(guardrails): match the lefthook remote by URL and ref, and refuse…
kyle-sexton Oct 24, 2026
5529a1c
fix(guardrails): read the lefthook remotes entry in any key order and…
kyle-sexton Oct 24, 2026
c76fdde
fix(guardrails): read source_dir in TOML and JSON lefthook configs, a…
kyle-sexton Oct 24, 2026
70f2fd9
fix(guardrails): read flow-style YAML remotes entries
kyle-sexton Oct 24, 2026
ab4c635
feat(guardrails): track main in the lefthook remote and drop the rele…
kyle-sexton Oct 24, 2026
3c085d3
docs(guardrails): say that merging to main runs code in every remote …
kyle-sexton Oct 24, 2026
0267d35
fix(guardrails): ignore trailing comments in remotes entries and find…
kyle-sexton Oct 24, 2026
b52b15f
fix(guardrails): match only this ref's lefthook cache in verify
kyle-sexton Oct 24, 2026
7132cf3
Merge remote-tracking branch 'origin/main' into feat/guardrails-lefth…
kyle-sexton Oct 24, 2026
4381c64
fix(guardrails): read remotes entries past a column-0 comment
kyle-sexton Oct 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: minor
---

### Added

- `apply install-git-hooks scope=shared` in a lefthook repository, or one with no hook manager yet, now adds one `remotes:` entry to the committed `lefthook.yml` instead of committing copies under `.githooks/guardrails/`: lefthook fetches `git-hooks/lefthook-remote.yml` and its scripts from this marketplace repository's `main` branch and refetches them every 24 hours, so every clone runs the hooks as merged to `main`, with no refresh pull request and no lint exclusion. `install.mjs plan --scope remote` returns the snippet; `verify` reports each entry `ok` once the config carries the remote, `lefthook install` has written the event hook and the remote is fetched, and `missing` with "run lefthook install" otherwise, so the session-start notice also catches a first install made offline. A repository holding the earlier shared copies gets a `migrate` decision (`from: shared`) naming `.githooks/guardrails/` and the old `guardrails-<entry>` jobs to remove. Husky and plain hooks keep the shared copies, and so does a lefthook config that sets its own `source_dir` (a `remote-source-dir` decision), because lefthook then looks for the remote's scripts in a directory the fetched copy does not have.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
bump: patch
---

### Changed

- The vendored git-hook installer (`lib/git-hooks/install.mjs`) accepts an optional `remote` block in `git-hooks/entries.json` and a `plan --scope remote` that returns a lefthook `remotes:` entry for it; a plugin without the block behaves as before, and `copy --scope remote` is a usage error.
23 changes: 23 additions & 0 deletions .github/scripts/pr-require-checks/install-lefthook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Install the pinned lefthook on a Linux x86_64 runner, checksum-verified, into ~/.local/bin and
# onto $GITHUB_PATH: the lefthook the guardrails remote's end-to-end test
# (plugins/guardrails/git-hooks/lefthook-remote.test.sh) runs in pr-require-checks.yml's
# test-bash lane.
# The version is the one the fleet runs; the hash is the release's published SHA-256 for
# lefthook_<version>_Linux_x86_64. A cached download under ~/.cache/ci-tools is reused and
# checked the same way.
set -euo pipefail

LEFTHOOK_VERSION=2.2.2
LEFTHOOK_SHA256=4fbf89eb7748c6aed84d833ee1a29edac48fae614468ca9a82896ec32f2800b6

asset="lefthook_${LEFTHOOK_VERSION}_Linux_x86_64"
mkdir -p "$HOME/.local/bin" "$HOME/.cache/ci-tools"
cached="$HOME/.cache/ci-tools/$asset"
[[ -f "$cached" ]] || curl -fSL --no-progress-meter --retry 3 --retry-all-errors -o "$cached" \
"https://github.com/evilmartians/lefthook/releases/download/v$LEFTHOOK_VERSION/$asset"
cp "$cached" "$HOME/.local/bin/lefthook"
echo "$LEFTHOOK_SHA256 $HOME/.local/bin/lefthook" | sha256sum -c -
chmod +x "$HOME/.local/bin/lefthook"
[[ -z "${GITHUB_PATH:-}" ]] || echo "$HOME/.local/bin" >>"$GITHUB_PATH"
test "$("$HOME/.local/bin/lefthook" version)" = "$LEFTHOOK_VERSION"
17 changes: 15 additions & 2 deletions .github/workflows/pr-require-checks.yml
Comment thread
kyle-sexton marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2589,7 +2589,7 @@ jobs:
rcfile: .shellcheckrc
# The eval-coverage gate's suite reads `shfmt --to-json` and exits 2 when
# shfmt is missing, so this job installs the same v3.14.1 pin as
# check-skills. The shfmt and DuckDB downloads are cached by version (four
# check-skills. The shfmt, DuckDB and lefthook downloads are cached by version (four
# red main runs in two weeks were these downloads failing), and a miss
# retries transient failures. `--no-progress-meter` instead of `-s` keeps
# curl's "Will retry" warning in the log. The SHA-256 check runs on a hit
Expand All @@ -2599,7 +2599,7 @@ jobs:
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ci-tools
key: ci-tools-${{ runner.os }}-shfmt-v3.14.1-duckdb-v1.5.5
key: ci-tools-${{ runner.os }}-shfmt-v3.14.1-duckdb-v1.5.5-lefthook-2.2.2
- name: Install pinned shfmt
env:
SHFMT_VERSION: v3.14.1
Expand Down Expand Up @@ -2689,6 +2689,17 @@ jobs:
gunzip "$RUNNER_TEMP/duckdb/duckdb.gz"
chmod +x "$RUNNER_TEMP/duckdb/duckdb"
echo "$RUNNER_TEMP/duckdb" >> "$GITHUB_PATH"
# The guardrails remote's end-to-end suite runs real lefthook against a
# stand-in remote; a broken lefthook-remote.yml would block every
# consumer's commits, so it fails here instead of skipping
# (GUARDRAILS_REQUIRE_LEFTHOOK).
- name: Install pinned lefthook
run: |
if [[ "$(jq -r --arg leg "$LEG" '.[$leg] // ""' <<<"$LEG_PLAN")" != *lefthook* ]]; then
echo "No suite on this leg runs lefthook."
exit 0
fi
bash .github/scripts/pr-require-checks/install-lefthook.sh
- name: Save the pinned tool downloads
if: github.ref == 'refs/heads/main' && steps.tools_cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand All @@ -2710,6 +2721,8 @@ jobs:
# The check-visual-parity pixel cases fail on a missing pixelmatch or pngjs here instead of skipping green.
VISUAL_COMPARE_REQUIRE: '1'
VISUAL_COMPARE_DEPS_DIR: .work
# The guardrails remote's end-to-end cases fail on a missing lefthook here instead of skipping green.
GUARDRAILS_REQUIRE_LEFTHOOK: '1'
# On main (schedule, dispatch) a failing suite in
# scripts/run-plugin-tests-advisory.txt warns instead of failing the
# leg; pull requests and merge groups still fail on it.
Expand Down
5 changes: 5 additions & 0 deletions .lefthook/commit-msg/guardrails-commit-msg-convention.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# The guardrails commit-msg-convention entry for consumers of the plugin's lefthook remote
# (plugins/guardrails/git-hooks/lefthook-remote.yml). Lefthook looks up a remote's scripts
# under this repository's root .lefthook/, so this runs the plugin's own script, which finds its libs beside it.
exec bash "${BASH_SOURCE[0]%/*}/../../plugins/guardrails/git-hooks/commit-msg-convention.sh" "$@"
5 changes: 5 additions & 0 deletions .lefthook/pre-commit/guardrails-pre-commit-content.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# The guardrails pre-commit-content entry for consumers of the plugin's lefthook remote
# (plugins/guardrails/git-hooks/lefthook-remote.yml). Lefthook looks up a remote's scripts
# under this repository's root .lefthook/, so this runs the plugin's own script, which finds its libs beside it.
exec bash "${BASH_SOURCE[0]%/*}/../../plugins/guardrails/git-hooks/pre-commit-content.sh" "$@"
5 changes: 5 additions & 0 deletions .lefthook/pre-push/guardrails-reference-verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
# The guardrails reference-verify entry for consumers of the plugin's lefthook remote
# (plugins/guardrails/git-hooks/lefthook-remote.yml). Lefthook looks up a remote's scripts
# under this repository's root .lefthook/, so this runs the plugin's own script, which finds its libs beside it.
exec bash "${BASH_SOURCE[0]%/*}/../../plugins/guardrails/git-hooks/reference-verify.sh" "$@"
Loading
Loading