Files
Charles Wiltgen aa1d1ddd08 fix(gate): cover what the scan reads in the CI paths filter, and make the hook installer idempotent
The test-suite workflow's `paths` filter listed nine prefixes while its two steps
read considerably more. `node scripts/leak-scan.ts` walks every SURFACES entry
plus ROOT_FILES — `.cursor-plugin`, `.agents`, `axiom-codex`, `axiom-cursor`,
`axiom-mcp` and the root release files among them — and the unit suite covers
`axiom-pi` and `axiom-mcp`. A PR that added a home path to README.md or a real
UUID to `axiom-codex` therefore started no job at all, which is exactly the case
this workflow exists to backstop on a fresh clone or a `--no-verify` commit. The
filter now lists all twenty entries per event, and a test derives the coverage
requirement from the scanner's own SURFACES and ROOT_FILES so the two cannot
drift apart again.

`install-git-hooks.sh` grew the hook by one line on every run: the strip removed
the block but left the separator blank line on both sides of it, and the insert
added one back. The block now contributes no leading blank and the strip consumes
the trailing one, so a re-install leaves the file byte-identical.

Verified: 518/518 unit tests, 14/14 installer tests, leak scan 0 errors across
1978 shipped files.
2026-09-16 08:05:42 -07:00

88 lines
3.6 KiB
Bash
Executable File

#!/bin/sh
# Install Axiom's pre-commit validation into .git/hooks/pre-commit.
#
# Why an installer exists: .git/hooks is not tracked, so a guard that lives only
# there disappears on a fresh clone and on any machine that never ran it. The
# block is spliced between markers, so re-running moves it rather than stacking
# copies, and any other content in the hook (the beads integration, for instance)
# is left alone.
#
# sh scripts/install-git-hooks.sh
set -e
root=${AXIOM_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}
source_file="$root/scripts/git-hooks/pre-commit-axiom.sh"
hook="$root/.git/hooks/pre-commit"
test -f "$source_file" || { echo "missing $source_file" >&2; exit 1; }
test -d "$root/.git/hooks" || { echo "$root is not a git checkout" >&2; exit 1; }
if [ ! -f "$hook" ]; then
printf '#!/bin/sh\n' > "$hook"
chmod +x "$hook"
fi
# Splice only the marked block. The tracked file's shebang and preamble exist so
# it can be run directly; neither belongs in the middle of another hook.
BEGIN='# --- BEGIN AXIOM PLUGIN VALIDATION ---'
END='# --- END AXIOM PLUGIN VALIDATION ---'
awk -v begin="$BEGIN" -v end="$END" '
$0 == begin { inside = 1 }
inside { print }
$0 == end { inside = 0 }
' "$source_file" > "$root/.git/hooks/.axiom-block.tmp"
# Strip any block a previous run installed — the markers AND the one blank line
# that follows them — then insert the current one after the shebang. Unconditional,
# and in that order, because a block that is already there may be in the wrong
# place: the first revision appended, so an installed block can sit after another
# tool's `exit` and never run. Replacing it in place would preserve that position.
#
# Consuming the trailing blank is what makes a re-install byte-identical. Without
# it the block's own separator survives the strip and joins the separator the
# insert adds, so the file grows by a line on every run.
tmp_stripped="$root/.git/hooks/.pre-commit.stripped"
awk -v begin="$BEGIN" -v end="$END" '
# Compare with any trailing CR removed. A hook written with CRLF endings would
# otherwise never match its own markers: the strip would find nothing, the insert
# would add a SECOND block, and every commit would then run the content scan
# twice while the installer reported success.
{ line = $0; sub(/\r$/, "", line) }
line == begin { skip = 1; next }
skip && line == end { skip = 0; drop_blank = 1; next }
skip { next }
{
if (drop_blank) { drop_blank = 0; if (line == "") next }
print
}
' "$hook" > "$tmp_stripped"
# Insert right after the shebang — NOT at the end. An existing hook file may
# already carry another tool's block, and a block that terminates the script
# (an `exit`, an `exec`) would leave this one unreachable while the installer
# still reported success. The sibling installer
# (.claude/scripts/install-leak-prevention-hooks.sh) prepends for this reason,
# and this repo's own .git/hooks/pre-commit is exactly that case: the beads
# block carries `if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi`.
#
# No blank BEFORE the block; one after it. The strip above removes that same
# blank, so the pair is a fixed point.
awk -v block="$root/.git/hooks/.axiom-block.tmp" '
BEGIN {
while ((getline line < block) > 0) body = body line "\n"
placed = 0
}
!placed && NR == 1 && /^#!/ { print; printf "%s\n", body; placed = 1; next }
!placed { printf "%s\n", body; placed = 1 }
{ print }
END { if (!placed) printf "%s", body }
' "$tmp_stripped" > "$hook"
rm -f "$tmp_stripped"
echo " installed the axiom block after the shebang in .git/hooks/pre-commit"
rm -f "$root/.git/hooks/.axiom-block.tmp"
chmod +x "$hook"
sh -n "$hook" && echo " hook syntax ok"