Merge PR #824 into main

fix(dev): preserve pre-push hooks across worktree installs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
AkitaOnRails
2026-09-24 15:42:45 -03:00
4 changed files with 1154 additions and 38 deletions
+9
View File
@@ -17,6 +17,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
release's `hooks/` bundle beside the client, so auto-wire no longer fails
with "could not locate hooks directory" for script-based harnesses on a host
where `install-hooks` never ran. (#874)
- Fixed pre-push installation from linked worktrees and preserved the managed
block's position during reinstallation. Configured `core.hooksPath` overrides
and ambiguous markers are rejected without replacing the existing hook. The
block now keeps its shell options and `SSL_CERT_FILE` inside its subshell and
propagates a failure explicitly, so user hook commands after it keep their
own semantics and a failing test run still blocks the push. (#824)
- Isolated the pre-push test process from Git's repository environment and
global/system configuration so fixture commands use their own repositories.
Existing installations need to run `scripts/install-git-hooks.sh` again. (#824)
- Auto-improve review no longer stages a proposal whose LLM-produced page
path contains a Windows-illegal character (e.g. a `:` copied from a
conventional-commit subject). That path passed the deliberately tolerant
+15 -3
View File
@@ -77,9 +77,21 @@ Skipped tests still count as "skipped" in the summary, never hidden, and two
independent things run them anyway: the pre-push hook and CI.
Install the hook once per clone with `scripts/install-git-hooks.sh` (from Git
Bash on Windows). It appends or updates only ai-memory's managed block in
`.git/hooks/pre-push`, preserving any existing hook body. Bypass it on a
work-in-progress branch with `git push --no-verify`.
Bash on Windows). It can run from the main checkout or a linked worktree;
both use the shared repository hook directory. Reinstallation replaces
ai-memory's managed block in place, preserving surrounding user commands and
their order. If `core.hooksPath` is set, the installer stops before writing;
integrate the block through your existing hook manager instead. Incomplete or
duplicate managed markers also stop installation and leave the hook unchanged.
Bypass it on a work-in-progress branch with `git push --no-verify`.
The managed test block clears Git's repository environment and disables global
and system Git configuration for Cargo and its children. Fixture commands can
then use their own repositories without inheriting the checkout being pushed.
The publishing Git process and other hook code retain their configuration, and
the block's shell options stay inside it; a failing test run still fails the
hook even when your own commands follow the block without `set -e`.
Run the installer again to update an existing installation.
Integration tests live in `tests/suite/` per crate and compile into the
crate's own test harness (declare a new file with `mod name;` in
File diff suppressed because it is too large Load Diff
+108 -35
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Installs this repo's pre-push hook into .git/hooks without discarding an
# existing user hook. Run once per clone (from Git Bash on Windows):
# Installs this repo's pre-push hook without moving existing user commands.
# Run once per clone (from Git Bash on Windows):
#
# scripts/install-git-hooks.sh
#
@@ -12,48 +12,121 @@
set -euo pipefail
repo_root=$(git rev-parse --show-toplevel)
hook="$repo_root/.git/hooks/pre-push"
cd "$repo_root"
if git config --get core.hooksPath >/dev/null; then
echo 'core.hooksPath is set; this installer only manages the shared repository hook directory' >&2
exit 1
else
# `$?` is still the condition's status here: 1 means the key is unset, and
# anything else is a Git failure the installer must not guess past.
config_status=$?
if [[ "$config_status" -ne 1 ]]; then
exit "$config_status"
fi
fi
hook=$(git rev-parse --git-path hooks/pre-push)
begin="# >>> ai-memory pre-push >>>"
end="# <<< ai-memory pre-push <<<"
tmp=$(mktemp "${hook}.XXXXXX")
trap 'rm -f "$tmp"' EXIT
if [[ -f "$hook" ]]; then
awk -v begin="$begin" -v end="$end" '
$0 == begin { skip = 1; next }
$0 == end { skip = 0; next }
!skip { print }
' "$hook" > "$tmp"
if grep -q '[^[:space:]]' "$tmp"; then
printf '\n' >> "$tmp"
managed_block=$(cat <<'HOOK'
# >>> ai-memory pre-push >>>
# Runs the full test tier before a push. See scripts/install-git-hooks.sh.
# Git's hook environment would redirect fixture commands into this checkout.
# Isolate Cargo and its children, and keep this block's shell options and
# exports away from user hook code around it.
(
set -euo pipefail
# macOS: stop reqwest re-reading the Keychain in every test process.
if [ "$(uname -s 2>/dev/null || true)" = "Darwin" ] && [ -z "${SSL_CERT_FILE:-}" ] && [ -f /etc/ssl/cert.pem ]; then
export SSL_CERT_FILE=/etc/ssl/cert.pem
fi
# A plain assignment, so `set -e` still aborts if `git rev-parse` fails.
git_local_env_vars=$(git rev-parse --local-env-vars)
# A surrounding user hook may have narrowed IFS; the list below is split on
# newlines, so fall back to the default before splitting it. Unset rather
# than assigned: Bash 3.2 expands ANSI-C quoting inside this heredoc.
unset IFS
# Unquoted on purpose: the output is one variable name per line.
for git_local_env_var in $git_local_env_vars; do
unset "$git_local_env_var"
done
# Fixture repositories must not inherit machine settings such as signing.
# Git for Windows maps /dev/null to nul.
export GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=/dev/null
if command -v cargo-nextest >/dev/null 2>&1; then
echo "pre-push: cargo nextest run --workspace -P full"
cargo nextest run --workspace -P full
else
printf '%s\n\n' '#!/usr/bin/env bash' '# Installed by scripts/install-git-hooks.sh.' > "$tmp"
echo "pre-push: cargo test --workspace --all-targets (nextest not installed)"
cargo test --workspace --all-targets
fi
)
# Bash ignores `set -e` inside a subshell tested by `||`, `&&`, `!` or `if`, so
# the subshell stays a plain command and its status is propagated here. A user
# hook without `set -e` would otherwise run on and let the push through.
ai_memory_pre_push_status=$?
if [ "$ai_memory_pre_push_status" -ne 0 ]; then
exit "$ai_memory_pre_push_status"
fi
unset ai_memory_pre_push_status
# <<< ai-memory pre-push <<<
HOOK
)
has_content=0
if [[ -f "$hook" ]]; then
if grep -q '[^[:space:]]' "$hook"; then
has_content=1
else
# As above, `$?` is grep's status: 1 is a blank hook, 2 a read error.
read_status=$?
if [[ "$read_status" -ne 1 ]]; then
exit "$read_status"
fi
fi
fi
mkdir -p "${hook%/*}"
tmp=$(mktemp "${hook}.XXXXXX")
if [[ "$has_content" -eq 1 ]]; then
# ENVIRON preserves backslashes; BINMODE prevents Windows CRLF translation.
if ! AI_MEMORY_PRE_PUSH_BLOCK="$managed_block" awk -v BINMODE=3 -v begin="$begin" -v end="$end" '
{
marker = $0
sub(/\r$/, "", marker)
if (marker == begin) {
if (inside || seen) { invalid = 1; exit 1 }
inside = seen = 1
print ENVIRON["AI_MEMORY_PRE_PUSH_BLOCK"]
next
}
if (marker == end) {
if (!inside) { invalid = 1; exit 1 }
inside = 0
next
}
if (!inside) print
}
END {
if (invalid || inside) exit 1
if (!seen) {
print ""
print ENVIRON["AI_MEMORY_PRE_PUSH_BLOCK"]
}
}
' "$hook" > "$tmp"; then
printf 'invalid managed markers in %s; original unchanged, temporary file retained at %s\n' "$hook" "$tmp" >&2
exit 1
fi
else
printf '%s\n\n' '#!/usr/bin/env bash' '# Installed by scripts/install-git-hooks.sh.' > "$tmp"
printf '%s\n' "$managed_block" >> "$tmp"
fi
cat >> "$tmp" <<'HOOK'
# >>> ai-memory pre-push >>>
# Runs the full test tier before a push. See scripts/install-git-hooks.sh.
set -euo pipefail
# macOS: stop reqwest re-reading the Keychain in every test process.
if [ "$(uname -s 2>/dev/null || true)" = "Darwin" ] && [ -z "${SSL_CERT_FILE:-}" ] && [ -f /etc/ssl/cert.pem ]; then
export SSL_CERT_FILE=/etc/ssl/cert.pem
fi
if command -v cargo-nextest >/dev/null 2>&1; then
echo "pre-push: cargo nextest run --workspace -P full"
cargo nextest run --workspace -P full
else
echo "pre-push: cargo test --workspace --all-targets (nextest not installed)"
cargo test --workspace --all-targets
fi
# <<< ai-memory pre-push <<<
HOOK
mv "$tmp" "$hook"
trap - EXIT
chmod +x "$hook"
echo "installed $hook"