diff options
Diffstat (limited to 'working/hook-fail-open/validate-el.sh.superseded')
| -rw-r--r-- | working/hook-fail-open/validate-el.sh.superseded | 142 |
1 files changed, 0 insertions, 142 deletions
diff --git a/working/hook-fail-open/validate-el.sh.superseded b/working/hook-fail-open/validate-el.sh.superseded deleted file mode 100644 index 045c93f..0000000 --- a/working/hook-fail-open/validate-el.sh.superseded +++ /dev/null @@ -1,142 +0,0 @@ -#!/usr/bin/env bash -# Validate and test .el files after Edit/Write/MultiEdit. -# PostToolUse hook: receives tool-call JSON on stdin. -# -# On success: exit 0 silent. -# On failure: emit JSON with hookSpecificOutput.additionalContext so Claude -# sees a structured error in its context, THEN exit 2 to block the tool -# pipeline. stderr still echoes the error for terminal visibility. -# -# Phase 1: check-parens + byte-compile -# Phase 2: for non-test .el files, run matching tests/test-<stem>*.el - -set -u - -# Emit a JSON failure payload and exit 2. Arguments: -# $1 — short failure type (e.g. "PAREN CHECK FAILED") -# $2 — file path -# $3 — emacs output (error body), always sent to Claude in additionalContext -# $4 — optional compact terminal echo; when set, the terminal shows this -# instead of the full $3 (Claude still gets the full $3). Used by the -# test runner so a failing suite prints a short summary to the pane -# rather than dumping every ERT backtrace. -fail_json() { - local ctx - ctx="$(printf '%s: %s\n\n%s\n\nFix before proceeding.' "$1" "$2" "$3" \ - | jq -Rs .)" - cat <<EOF -{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": $ctx}} -EOF - printf '%s: %s\n%s\n' "$1" "$2" "${4:-$3}" >&2 - exit 2 -} - -# Report something without failing the edit. Used when the hook declines to do -# work it would normally do: a silent decline is indistinguishable from a clean -# run, which is how the over-cap skip below hid missing test runs for months. -# $1 — message for both the terminal and Claude's context -notice_json() { - local ctx - ctx="$(printf '%s' "$1" | jq -Rs .)" - cat <<EOF -{"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": $ctx}} -EOF - printf '%s\n' "$1" >&2 -} - -# Portable project root: prefer Claude Code's env var, fall back to deriving -# from this script's location ($project/.claude/hooks/validate-el.sh). -PROJECT_ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}" - -f="$(jq -r '.tool_input.file_path // .tool_response.filePath // empty')" -[ -z "$f" ] && exit 0 -[ "${f##*.}" = "el" ] || exit 0 - -MAX_AUTO_TEST_FILES=20 # skip if more matches than this (large test suites) - -# --- Phase 1: syntax + byte-compile --- -case "$f" in - */init.el|*/early-init.el) - # Byte-compile here would load the full package graph. Parens only. - if ! output="$(emacs --batch --no-site-file --no-site-lisp "$f" \ - --eval '(check-parens)' 2>&1)"; then - fail_json "PAREN CHECK FAILED" "$f" "$output" - fi - ;; - *.el) - # -L the file's own directory (and a sibling project root for files - # under a tests/ subdir) so cross-project edits compile against their - # own modules, not just this project's. - if ! output="$(emacs --batch --no-site-file --no-site-lisp \ - --eval '(setq load-prefer-newer t)' \ - -L "$(dirname "$f")" \ - -L "$(dirname "$f")/.." \ - -L "$PROJECT_ROOT" \ - -L "$PROJECT_ROOT/modules" \ - -L "$PROJECT_ROOT/tests" \ - -L "$PROJECT_ROOT/themes" \ - --eval '(package-initialize)' \ - "$f" \ - --eval '(check-parens)' \ - --eval "(or (byte-compile-file \"$f\") (kill-emacs 1))" 2>&1)"; then - fail_json "VALIDATION FAILED" "$f" "$output" - fi - ;; -esac - -# --- Phase 2: test runner --- -# Determine which tests (if any) apply to this edit. Works for projects with -# source at root, in modules/, or elsewhere — stem-based test lookup is the -# common pattern. -tests=() -case "$f" in - */init.el|*/early-init.el) - : # Phase 1 handled it; skip test runner - ;; - "$PROJECT_ROOT/tests/testutil-"*.el) - stem="$(basename "${f%.el}")" - stem="${stem#testutil-}" - mapfile -t tests < <(find "$PROJECT_ROOT/tests" -maxdepth 1 -name "test-${stem}*.el" 2>/dev/null | sort) - ;; - "$PROJECT_ROOT/tests/test-"*.el) - tests=("$f") - ;; - *.el) - # Any other .el under the project — find matching tests by stem - stem="$(basename "${f%.el}")" - mapfile -t tests < <(find "$PROJECT_ROOT/tests" -maxdepth 1 -name "test-${stem}*.el" 2>/dev/null | sort) - ;; -esac - -count="${#tests[@]}" -if [ "$count" -ge 1 ] && [ "$count" -le "$MAX_AUTO_TEST_FILES" ]; then - load_args=() - for t in "${tests[@]}"; do load_args+=("-l" "$t"); done - if ! output="$(emacs --batch --no-site-file --no-site-lisp \ - --eval '(setq load-prefer-newer t)' \ - -L "$PROJECT_ROOT" \ - -L "$PROJECT_ROOT/modules" \ - -L "$PROJECT_ROOT/tests" \ - -L "$PROJECT_ROOT/themes" \ - --eval '(package-initialize)' \ - --eval "(cd \"$PROJECT_ROOT/tests\")" \ - -l ert "${load_args[@]}" \ - --eval "(ert-run-tests-batch-and-exit '(not (tag :slow)))" 2>&1)"; then - # Terminal gets a compact summary (the run tally + the failing test names); - # Claude still gets the full backtrace via additionalContext. Keeps the - # pane from drowning in ERT stack frames on every red test. - summary="$(printf '%s\n' "$output" \ - | grep -E '^Ran [0-9]+ tests|unexpected results:|^[[:space:]]+FAILED' || true)" - [ -n "$summary" ] && summary="${summary}"$'\n'"(full backtrace in Claude's context)" - fail_json "TESTS FAILED ($count test file(s))" "$f" "$output" "$summary" - fi -elif [ "$count" -gt "$MAX_AUTO_TEST_FILES" ]; then - # Say so. Running this many files on every keystroke is not wanted, but a - # silent skip reads exactly like a passing run, so an edit to one of the big - # families looked verified when nothing had been tested. - notice_json "TESTS SKIPPED: $count test files match $(basename "$f") — over the \ -auto-run cap of $MAX_AUTO_TEST_FILES, so none ran. This is NOT a pass. Run them \ -explicitly, e.g. make test-file FILE=$(basename "${tests[0]}")" -fi - -exit 0 |
