<feed xmlns='http://www.w3.org/2005/Atom'>
<title>rulesets/languages/elisp, branch main</title>
<subtitle>Claude Code skills, rules, and language bundles
</subtitle>
<id>https://git.cjennings.net/rulesets/atom?h=main</id>
<link rel='self' href='https://git.cjennings.net/rulesets/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/'/>
<updated>2026-04-19T22:06:10+00:00</updated>
<entry>
<title>feat(hooks): add global hooks — PreCompact priorities + git/gh confirm modals</title>
<updated>2026-04-19T22:06:10+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T22:06:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=4957c60c9ee985628ad59344e593d20a18ca8fdb'/>
<id>urn:sha1:4957c60c9ee985628ad59344e593d20a18ca8fdb</id>
<content type='text'>
Three new machine-wide hooks installed via `make install-hooks`:

- `precompact-priorities.sh` (PreCompact) — injects a priority block into
  the compaction prompt so the generated summary retains information most
  expensive to reconstruct: unanswered questions, root causes with
  file:line, subagent findings as primary evidence, exact numbers/IDs,
  A-vs-B decisions, open TODOs, classified-data handling.

- `git-commit-confirm.py` (PreToolUse/Bash) — gates `git commit` behind a
  confirmation modal showing parsed message, staged files, diff stats,
  author. Parses both HEREDOC and `-m`/`--message` forms.

- `gh-pr-create-confirm.py` (PreToolUse/Bash) — gates `gh pr create`
  behind a modal showing title, base ← head, reviewers, labels,
  assignees, milestone, draft flag, body (HEREDOC or quoted).

Makefile: adds `install-hooks` / `uninstall-hooks` targets and extends
`list` with a Hooks section. Install prints the settings.json snippet
(in `hooks/settings-snippet.json`) to merge into `~/.claude/settings.json`.

Also: `languages/elisp/claude/hooks/validate-el.sh` now emits JSON with
`hookSpecificOutput.additionalContext` on failure (via new `fail_json()`
helper) so Claude sees a structured error in context, in addition to
the existing stderr output and exit 2.

Patterns synthesized clean-room from fcakyon/claude-codex-settings
(Apache-2.0). Each hook is original content.
</content>
</entry>
<entry>
<title>feat(rules): port key testing principles from quality-engineer prompt</title>
<updated>2026-04-19T18:16:07+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T18:16:07+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=e50c732d7138c18749b96b57004a3e23f31bbaef'/>
<id>urn:sha1:e50c732d7138c18749b96b57004a3e23f31bbaef</id>
<content type='text'>
Additions to claude-rules/testing.md:
- Testing pyramid proportions (70-80% unit / 15-25% integration / 5-10% e2e)
- Integration Tests section: docstring must name 'Components integrated:'
  and mark real vs mocked; when-to-write heuristics
- Signs of Overmocking: 'would the test pass if the function body was
  NotImplementedError?' plus three more sharp questions
- Testing Code That Uses Frameworks: test your integration, not the
  framework itself
- Test Real Code, Not Copies: never inline prod code into tests
- Error Behavior, Not Error Text: test type + key values, not exact prose
- If Tests Are Hard to Write, Refactor the Code: hard-to-test is a code
  signal, not a test signal; extract focused helpers
- Anti-patterns list extended

Addition to languages/elisp/claude/rules/elisp-testing.md:
- Interactive vs Internal split pattern: cj/foo wraps cj/--foo; test the
  internal directly, skip UI mocks

Source: ~/.emacs.d/ai-prompts/quality-engineer.org (personal reference,
kept as an extended prompt separate from these rules).
</content>
</entry>
<entry>
<title>chore: remove project-specific references</title>
<updated>2026-04-19T18:09:45+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T18:09:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=2d026369b616e51199579ff039cc34be4d5c2ef9'/>
<id>urn:sha1:2d026369b616e51199579ff039cc34be4d5c2ef9</id>
<content type='text'>
- elisp-testing.md: generalized testutil description. The specific files
  testutil-general.el / testutil-filesystem.el / testutil-org.el only
  exist in one project; bundle should describe the pattern, not name
  specific files.
- README.org: install examples use ~/code/ path to match actual layout.
</content>
</entry>
<entry>
<title>feat: add commits.md rule — no AI attribution anywhere</title>
<updated>2026-04-19T18:06:09+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T18:06:09+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=1e65828d45d2209b23610111e03d088a5bd4b080'/>
<id>urn:sha1:1e65828d45d2209b23610111e03d088a5bd4b080</id>
<content type='text'>
New generic rule at claude-rules/commits.md covers author identity,
absence of Claude/Anthropic/LLM/AI attribution (messages, PRs,
comments, trailers, emojis), and conventional commit format. Applies
to all repos.

Bundle settings.json now sets attribution.commit: "" and
attribution.pr: "" so Claude Code's default attribution is suppressed
belt-and-suspenders with the written rule.

Elisp CLAUDE.md template trimmed to reference commits.md instead of
inlining the rules.
</content>
</entry>
<entry>
<title>chore(elisp): add *.elc and *.eln to gitignore template</title>
<updated>2026-04-19T17:45:33+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T17:45:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=217079ec84c8c7081fe4c27494915fe873d99f68'/>
<id>urn:sha1:217079ec84c8c7081fe4c27494915fe873d99f68</id>
<content type='text'>
The validate-el.sh hook byte-compiles .el files, which produces .elc
(and on Emacs 28+ with native-comp, .eln) artifacts. Projects using
the bundle almost always want these ignored.
</content>
</entry>
<entry>
<title>fix(elisp): call package-initialize so byte-compile finds deps</title>
<updated>2026-04-19T17:44:29+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T17:44:29+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=42086f187958249afc61c7eaa1c10e4ebbfd9238'/>
<id>urn:sha1:42086f187958249afc61c7eaa1c10e4ebbfd9238</id>
<content type='text'>
Byte-compile needs external packages on the load path to resolve
(require ...) forms in project files. Without this, any project using
MELPA/ELPA packages (dash, s, etc.) failed Phase 1 with "Cannot open
load file".

package-initialize reads package-user-dir (default ~/.emacs.d/elpa)
and exposes installed package autoloads. Small latency cost (~100ms)
but makes the hook work on real projects.

Verified:
  - chime.el (requires dash) now byte-compiles cleanly; was failing.
  - emacs.d modules still pass; no regression.
</content>
</entry>
<entry>
<title>refactor(elisp): generalize validate-el.sh test-runner for flat layouts</title>
<updated>2026-04-19T17:42:10+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T17:42:10+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=bd6881adbe25272278031b419bedd5811ef3828c'/>
<id>urn:sha1:bd6881adbe25272278031b419bedd5811ef3828c</id>
<content type='text'>
Phase 2 test lookup now triggers for any .el file outside tests/, not
just modules/*.el. Stem-based test matching works the same way; this
just broadens the case pattern.

Before: only modules/foo.el → tests/test-foo*.el triggered Phase 2.
After: foo.el, lib/foo.el, modules/foo.el all do.

init.el and early-init.el are still Phase-1-only (byte-compile would
load the full package graph).

Verified on:
  - emacs.d (modules/-based): modules/browser-config.el still runs
    its matching test, exit 0
  - flat layout (scratch /tmp): source.el at project root successfully
    finds and runs tests/test-source.el
</content>
</entry>
<entry>
<title>refactor: generalize testing.md, split Python specifics, DRY install</title>
<updated>2026-04-19T17:36:04+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T17:36:04+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=019db5f9677902ba02d703a8554667d1b6e88f6b'/>
<id>urn:sha1:019db5f9677902ba02d703a8554667d1b6e88f6b</id>
<content type='text'>
claude-rules/testing.md is now language-agnostic (TDD principles, test
categories, coverage targets, anti-patterns). Scope header widened to
**/*. Python-specific content (pytest, fixtures, parametrize, anyio,
Django DB testing) moved to languages/python/claude/rules/python-testing.md.

Added languages/python/ bundle (rules only so far; no CLAUDE.md template
or hooks yet — Python validation tooling differs from Elisp). Added
install-python shortcut to the Makefile.

Updated scripts/install-lang.sh to copy claude-rules/*.md into each
target project's .claude/rules/. Bundles no longer need to carry their
own verification.md copy — deleted languages/elisp/claude/rules/verification.md.
Single source of truth in claude-rules/, fans out via install.

Elisp-testing.md now references testing.md as its base (matches the
python-testing.md pattern).
</content>
</entry>
<entry>
<title>feat: add per-project language bundles + elisp ruleset</title>
<updated>2026-04-19T16:57:23+00:00</updated>
<author>
<name>Craig Jennings</name>
<email>c@cjennings.net</email>
</author>
<published>2026-04-19T16:57:23+00:00</published>
<link rel='alternate' type='text/html' href='https://git.cjennings.net/rulesets/commit/?id=18fcaf9f27d03849487078b30f667c3b574e6554'/>
<id>urn:sha1:18fcaf9f27d03849487078b30f667c3b574e6554</id>
<content type='text'>
Introduces a second install mode alongside the existing global symlinks:
per-project language bundles that copy a language-specific Claude Code
setup (rules, hooks, settings, pre-commit) into a target project.

Layout additions:
  languages/elisp/       - Emacs Lisp bundle (rules, hooks, settings, CLAUDE.md)
  scripts/install-lang.sh - shared install logic

Makefile additions:
  make help              - unified help text
  make install-lang LANG=&lt;lang&gt; PROJECT=&lt;path&gt; [FORCE=1]
  make install-elisp PROJECT=&lt;path&gt; [FORCE=1]   (shortcut)
  make list-languages    - show available bundles

Elisp bundle contents:
  - CLAUDE.md template (seed on first install, preserved on update)
  - .claude/rules/elisp.md, elisp-testing.md, verification.md
  - .claude/hooks/validate-el.sh (check-parens, byte-compile, run matching tests)
  - .claude/settings.json (permission allowlist, hook wiring)
  - githooks/pre-commit (secret scan + staged-file paren check)
  - gitignore-add.txt (append .claude/settings.local.json)

Hooks use \$CLAUDE_PROJECT_DIR with a script-relative fallback, so the
same bundle works on any machine or clone path. Install activates git
hooks via core.hooksPath=githooks automatically. Re-running install is
idempotent; CLAUDE.md is never overwritten without FORCE=1.
</content>
</entry>
</feed>
