Summary
Add a post-edit-test-runner hook template to the template library (aspens add hook) that automatically runs relevant tests after Claude edits a file.
What it does
A PostToolUse hook that fires after Edit, MultiEdit, or Write tool calls. Instead of running the full test suite (expensive), it maps the edited file to its corresponding test file and runs only that:
src/lib/scanner.js → runs tests/scanner.test.js
src/services/billing/stripe.ts → runs tests/billing.test.ts or src/services/billing/__tests__/stripe.test.ts
app/auth/middleware.py → runs tests/test_auth.py
This gives Claude immediate feedback on breakage mid-session, rather than piling up errors for 20 minutes. The hook is deterministic — Claude can forget instructions, but hooks always fire.
Design considerations
- Scoped, not full suite — only run the test file that corresponds to the edited file. Full suite on every edit is too expensive for large projects.
- Framework detection — should detect the test runner from the project (
vitest, jest, pytest, go test, etc.) and the test file naming convention (*.test.ts, test_*.py, *_test.go).
- Configurable patterns — users should be able to customize the file→test mapping (e.g., monorepo structures where tests live in a different package).
- Timeout — cap test execution at ~30s to avoid blocking Claude on slow test suites.
- Output to stderr — test results should go to stderr (not stdout) so they don't get injected into Claude's context. Or optionally inject a summary line into context on failure so Claude can self-correct.
- Graceful skip — if no matching test file is found, exit 0 silently.
Possible implementation
#!/bin/bash
# PostToolUse hook — runs matching test after file edits
tool_info=$(cat)
tool_name=$(echo "$tool_info" | jq -r '.tool_name // empty')
file_path=$(echo "$tool_info" | jq -r '.tool_input.file_path // empty')
# Only fire on edit tools
[[ "$tool_name" =~ ^(Edit|MultiEdit|Write)$ ]] || exit 0
[[ -n "$file_path" ]] || exit 0
# Skip non-source files
[[ "$file_path" =~ \.(test|spec)\. ]] && exit 0 # don't re-run tests on test edits
[[ "$file_path" =~ \.(md|json|yaml|yml)$ ]] && exit 0
# Map source file to test file (detect convention)
# ... framework-specific logic here ...
# Run scoped test with timeout
timeout 30 <runner> <test_file> >&2 2>&1
Template location
src/templates/hooks/post-edit-test-runner.sh — installable via aspens add hook post-edit-test-runner
Prior art
The existing post-tool-use-tracker.sh hook already does file→domain mapping on PostToolUse. This would follow the same pattern but trigger test execution instead of skill tracking.
Summary
Add a
post-edit-test-runnerhook template to the template library (aspens add hook) that automatically runs relevant tests after Claude edits a file.What it does
A
PostToolUsehook that fires afterEdit,MultiEdit, orWritetool calls. Instead of running the full test suite (expensive), it maps the edited file to its corresponding test file and runs only that:src/lib/scanner.js→ runstests/scanner.test.jssrc/services/billing/stripe.ts→ runstests/billing.test.tsorsrc/services/billing/__tests__/stripe.test.tsapp/auth/middleware.py→ runstests/test_auth.pyThis gives Claude immediate feedback on breakage mid-session, rather than piling up errors for 20 minutes. The hook is deterministic — Claude can forget instructions, but hooks always fire.
Design considerations
vitest,jest,pytest,go test, etc.) and the test file naming convention (*.test.ts,test_*.py,*_test.go).Possible implementation
Template location
src/templates/hooks/post-edit-test-runner.sh— installable viaaspens add hook post-edit-test-runnerPrior art
The existing
post-tool-use-tracker.shhook already does file→domain mapping onPostToolUse. This would follow the same pattern but trigger test execution instead of skill tracking.