aboutsummaryrefslogtreecommitdiff
path: root/languages/elisp/claude
diff options
context:
space:
mode:
Diffstat (limited to 'languages/elisp/claude')
-rwxr-xr-xlanguages/elisp/claude/hooks/validate-el.sh7
-rw-r--r--languages/elisp/claude/rules/elisp-testing.md2
-rw-r--r--languages/elisp/claude/scripts/coverage-summary.el21
3 files changed, 22 insertions, 8 deletions
diff --git a/languages/elisp/claude/hooks/validate-el.sh b/languages/elisp/claude/hooks/validate-el.sh
index 2529fcc..870eefe 100755
--- a/languages/elisp/claude/hooks/validate-el.sh
+++ b/languages/elisp/claude/hooks/validate-el.sh
@@ -39,8 +39,6 @@ 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)
@@ -55,6 +53,7 @@ case "$f" in
# 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" \
@@ -95,15 +94,17 @@ case "$f" in
esac
count="${#tests[@]}"
-if [ "$count" -ge 1 ] && [ "$count" -le "$MAX_AUTO_TEST_FILES" ]; then
+if [ "$count" -ge 1 ]; 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);
diff --git a/languages/elisp/claude/rules/elisp-testing.md b/languages/elisp/claude/rules/elisp-testing.md
index 7c3a9ef..1ac76a0 100644
--- a/languages/elisp/claude/rules/elisp-testing.md
+++ b/languages/elisp/claude/rules/elisp-testing.md
@@ -43,6 +43,8 @@ The bundle ships a coverage summary at `.claude/scripts/coverage-summary.el` and
The number to watch is the missing-file count. A module no test loads never appears in the SimpleCov report, so a line-weighted total skips it silently — the suite looks healthier than it is. The summary counts every `modules/*.el` on disk that's absent from the report as 0%, so an untested module drags the project number down where you can see it. Copy the fragment's targets into your own Makefile to adopt it; the bundle never edits your Makefile.
+This is a local-only helper by design. `.claude/scripts/` is gitignored in code projects, so `coverage-summary.el` is untracked and CI never runs `make coverage-summary` against it — it's a developer-run check, not a CI gate. A gitignored install is intentional, not a coverage gap; don't move the script to a tracked `scripts/` dir to make CI pick it up.
+
## TDD Workflow
Write the failing test first. A failing test proves you understand the change. Assume the bug is in production code until the test proves otherwise — never fix the test before proving the test is wrong.
diff --git a/languages/elisp/claude/scripts/coverage-summary.el b/languages/elisp/claude/scripts/coverage-summary.el
index eb30c66..ed7ecfc 100644
--- a/languages/elisp/claude/scripts/coverage-summary.el
+++ b/languages/elisp/claude/scripts/coverage-summary.el
@@ -11,8 +11,15 @@
;; by file rather than by line, so untested modules are visible.
;;
;; Self-contained on purpose — it ships into a project's =.claude/scripts/= and
-;; must run with nothing but stock Emacs (`json' is built in). The SimpleCov
-;; JSON shape it parses is:
+;; must run with nothing but stock Emacs (`json' is built in).
+;;
+;; Local-only helper (Craig, 2026-06-28). =.claude/scripts/= is gitignored in
+;; code projects, so this file is not tracked and CI cannot run
+;; `make coverage-summary' against it. That is intentional: it stays a
+;; developer-run helper, not shipped to a tracked =scripts/= dir and not a CI
+;; gate. A gitignored install here is the design, not a coverage gap.
+;;
+;; The SimpleCov JSON shape it parses is:
;; { <suite>: { "coverage": { <abs-path>: [null | 0 | int, ...] } } }
;; where a null entry is a non-executable line, 0 is executable-but-unhit, and
;; any positive integer is a hit. Data unions across multiple suite keys.
@@ -91,11 +98,15 @@ missing or malformed."
(defun cj/coverage-summary--source-files (source-dir project-root)
"Return *.el files directly under SOURCE-DIR, relative to PROJECT-ROOT.
-Sorted; compiled files and subdirectories are out of scope."
+Sorted. Compiled files and subdirectories are out of scope, as are generated
+package files (`*-autoloads.el', `*-pkg.el') -- a build tool writes those, no
+test covers them, and counting them as untested source skews the number."
(let ((source-dir (file-name-as-directory (expand-file-name source-dir)))
(project-root (file-name-as-directory (expand-file-name project-root))))
- (sort (mapcar (lambda (p) (file-relative-name p project-root))
- (directory-files source-dir t "\\.el\\'"))
+ (sort (seq-remove
+ (lambda (p) (string-match-p "\\(?:-autoloads\\|-pkg\\)\\.el\\'" p))
+ (mapcar (lambda (p) (file-relative-name p project-root))
+ (directory-files source-dir t "\\.el\\'")))
#'string<)))
(defun cj/coverage-summary--missing (tracked source-dir project-root)