<< All versions

Skill v1.0.1

currentAutomated scan100/100
gregoryg/aipihkal/emacs-lisp-dev

2 files

──Details
PublishedOctober 26, 2026 at 07:46 AM
Content Hashsha256:e8b7af4ccd95fba9...
Git SHAa573b679f60a
Bump Typepatch
Compare with v1.0.0
──Files
Files (1 file, 6.0 KB)
SKILL.md6.0 KBactive
SKILL.md · 143 lines · 6.0 KB

version: "1.0.1" name: emacs-lisp-dev description: Write, review, and test Emacs Lisp code following idiomatic conventions and hard-won local best practices. Use when writing, editing, debugging, or reviewing .el files, Emacs packages, or gptel tools. Enforces a mandatory verify-before-deliver rule - all written code must be batch byte-compiled and smoke-tested before presenting to the user. Covers load-path management, buffer-local variable safety, HTML/XML parsing, file I/O patterns, and batch testing workflows. license: Complete terms in LICENSE.txt


Emacs Lisp Development

Prime Directive: Verify Before You Deliver

Every piece of Emacs Lisp code you write or edit MUST be tested before presenting it to the user.

After writing or modifying a .el file:

  1. If emacs is unavailable, or required dependencies cannot be loaded in batch, tell the user clearly and treat verification as incomplete.
  1. Syntax check (byte-compile as linter — do NOT leave .elc artifacts behind):

``bash emacs --batch -L <deps> -L . --eval '(byte-compile-file "FILE.el")' rm -f FILE.elc # clean up — stale .elc is a trap without (setq load-prefer-newer t) ` This catches unbalanced parens, unbound variables, and missing require`s.

  1. Load check: emacs --batch -L <deps> -L . --eval '(require (quote FEATURE))'
  2. Smoke test (when practical): emacs --batch -L <deps> --eval '(progn (require ...) (message "result: %s" (my-function "arg")))'

If any step fails, fix the code and re-test before responding. You have shell access—use it.

Load-Path Reality

Bare emacs --batch does NOT load the user's .emacs.d config or package paths. You must explicitly pass -L flags for every dependency directory. Ask the user for needed load-path extensions early in the session if they are not obvious from context.

Common pattern:

bash
emacs --batch \
-L ~/projects/emacs/ai/gptel/ \
-L ~/emacs-gregoryg/ \
-f batch-byte-compile target.el

Tip: Use emacs --batch --eval '(message "%s" load-path)' to inspect defaults, then add missing paths.

Critical Safety Patterns

Buffer-Local Variable Capture (incl. "shadows global")

Buffer-local variables can surprise you in two ways:

1) Scope switch: buffer-locals are tied to the current buffer. If you switch buffers with with-current-buffer / with-temp-buffer, you are now reading the other buffer's local value (often nil). Capture them BEFORE switching:

emacs-lisp
;; WRONG - my-local-var is nil in temp buffer
(with-temp-buffer
(insert (format "%s" my-local-var)))
;; RIGHT - capture first
(let ((val my-local-var))
(with-temp-buffer
(insert (format "%s" val))))

2) Shadowing: when a variable is buffer-local, the local value shadows the global value in that buffer. This means you do not get an automatic "merge" of global + local values unless you implement it.

If you want "effective" values that combine a global list with a buffer-local extension, do something like:

emacs-lisp
;; Example: combine a global allowlist with a buffer-local allowlist.
(let* ((global (default-value 'my-allowed-directories))
(local (and (local-variable-p 'my-allowed-directories)
my-allowed-directories)))
(seq-uniq (append local global) #'equal))

HTML/XML Parsing

Never use regex for HTML/XML. It is too fragile for namespaces (<html:title>), attributes, and nesting. Use:

  • libxml-parse-html-region + dom.el for HTML
  • libxml-parse-xml-region for XML
emacs-lisp
(let* ((dom (with-temp-buffer
(insert html-string)
(libxml-parse-html-region (point-min) (point-max))))
(title (dom-text (dom-by-tag dom 'title))))
title)

File I/O & Open Buffers

When writing auxiliary/sidecar files, always check `find-buffer-visiting` first. If the user has the file open with unsaved changes, modifying the buffer (and saving) is safer than blindly overwriting the file on disk:

emacs-lisp
(let ((buf (find-buffer-visiting filepath)))
(if buf
(with-current-buffer buf
(erase-buffer)
(insert new-content)
(save-buffer))
(with-temp-file filepath
(insert new-content))))

Style & Conventions

  • Prefix all public symbols with the package name: mypackage-do-thing, mypackage--internal-helper (double-dash for private).
  • Docstrings: First line is a complete sentence. Mention argument names in CAPS.
  • `defcustom` for user-facing options; defvar for internal state.
  • Prefer `when`/`unless` over single-branch if.
  • Prefer `pcase` for destructuring and multi-branch matching.
  • Autoloads: Add ;;;###autoload cookies to entry-point commands and major modes.

Testing Approach

  • Prefer batch-mode probes over interactive runs.
  • Capture message output via *Messages* buffer reads when needed.
  • Keep tests minimal and terminal-friendly; TTY behavior matters (e.g., cursor-sensor echoes).
  • For gptel tools specifically: byte-compile the file, then require it in batch to confirm clean loading.
  • Use ert for structured test suites when the project warrants it.

Generated Org artifacts

When Emacs Lisp creates or substantially rewrites Org files, parse a produced artifact with vanilla Org as a separate smoke test:

bash
emacs -Q --batch --eval \
'(progn (require '\''org)
(with-temp-buffer
(insert-file-contents "OUTPUT.org")
(org-mode)
(org-element-parse-buffer)))'

This proves that Org can read the structure; it does not prove Agenda semantics or preservation behavior. Also assert important headings, properties, and tags, rerun the generator to check idempotence, and test configuration-dependent Agenda views in the user's normal Emacs when relevant.

Discovery & Exploration

  • Avoid broad filesystem pokes inside Emacs batch; rely on shell tools (rg, grep, find, sed) for discovery and then use targeted Elisp for processing.
  • Set load-prefer-newer t or delete stale .elc files to ensure fresh code is loaded during development.
← v1.0.0All versions