From 129b13f85ede90b50ac9e2131bddf30659fa57a9 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 19 Apr 2026 12:46:59 -0500 Subject: chore: add Claude Code ruleset via ~/code/rulesets install-elisp Installs the Elisp ruleset from the rulesets repo: - CLAUDE.md (project instructions template) - .claude/rules/ (testing, verification, elisp, elisp-testing) - .claude/hooks/validate-el.sh (check-parens + byte-compile + run matching tests on every .el edit via PostToolUse) - .claude/settings.json (permission allowlist + hook wiring) - githooks/pre-commit (secret scan + staged-file paren check) core.hooksPath set to githooks/ so the pre-commit activates automatically. Hooks use \$CLAUDE_PROJECT_DIR with a script-relative fallback, so a fresh clone works without path edits. .gitignore extended with personal-override entries (settings.local.json, .cache/) and byte-compile artifacts (*.elc, *.eln). --- .claude/hooks/validate-el.sh | 87 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 .claude/hooks/validate-el.sh (limited to '.claude/hooks/validate-el.sh') diff --git a/.claude/hooks/validate-el.sh b/.claude/hooks/validate-el.sh new file mode 100755 index 0000000..6f93d48 --- /dev/null +++ b/.claude/hooks/validate-el.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Validate and test .el files after Edit/Write/MultiEdit. +# PostToolUse hook: receives tool-call JSON on stdin. +# Silent on success; on failure, prints emacs output and exits 2 +# so Claude sees the error and can correct it. +# +# Phase 1: check-parens + byte-compile +# Phase 2: for modules/*.el, run matching tests/test-*.el + +set -u + +# 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 + printf 'PAREN CHECK FAILED: %s\n%s\n' "$f" "$output" >&2 + exit 2 + fi + ;; + *.el) + if ! output="$(emacs --batch --no-site-file --no-site-lisp \ + -L "$PROJECT_ROOT" \ + -L "$PROJECT_ROOT/modules" \ + -L "$PROJECT_ROOT/tests" \ + --eval '(package-initialize)' \ + "$f" \ + --eval '(check-parens)' \ + --eval "(or (byte-compile-file \"$f\") (kill-emacs 1))" 2>&1)"; then + printf 'VALIDATION FAILED: %s\n%s\n' "$f" "$output" >&2 + exit 2 + 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 \ + -L "$PROJECT_ROOT" \ + -L "$PROJECT_ROOT/modules" \ + -L "$PROJECT_ROOT/tests" \ + --eval '(package-initialize)' \ + -l ert "${load_args[@]}" \ + --eval "(ert-run-tests-batch-and-exit '(not (tag :slow)))" 2>&1)"; then + printf 'TESTS FAILED for %s (%d test file(s)):\n%s\n' "$f" "$count" "$output" >&2 + exit 2 + fi +fi + +exit 0 -- cgit v1.2.3