Skillkeel Kit, read before you buy
Three things from the Kit ZIP, version 0.1.10, copied as they ship: one chapter of the playbook in full, one skill's eval case with the unedited transcript of its latest run, and the table of pass counts per Claude Code version. Every chapter and every skill ships this way. Kit on Gumroad · skillkeel.com
1. Playbook chapter 4: hooks (one of ten chapters)
File playbook/04-hooks.md, unchanged.
4. Hooks
A hook is a shell command Claude Code runs at a fixed point: before a tool call, after it, when a session starts, when Claude stops. Hooks are where you put rules that must hold even when Claude is convinced otherwise.
Events you will use
| Event | Fires | Typical use |
|---|---|---|
PreToolUse |
before a tool runs; can block | guard-bash, secret-scan, migration-guard |
PostToolUse |
after a tool runs | format on save, log commands |
SessionStart |
on startup, /clear, compaction |
inject a short project brief |
UserPromptSubmit |
when you send a prompt; can reject | strip pasted secrets, add context |
Stop / SubagentStop |
when Claude finishes a turn | enforce "run tests before done" |
PreCompact |
before context compaction | save state |
PermissionRequest |
when a permission prompt would appear | auto-answer known-safe prompts |
The contract
The hook reads one JSON object on stdin (tool_name, tool_input, session fields). It replies in one of two ways:
- Exit 2 with a message on stderr. On events that can block, this blocks, and the message is shown to Claude. Printing JSON does not override an exit 2.
- Exit 0 with JSON on stdout. For
PreToolUse:{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..."}}.allowandaskare also valid decisions, andupdatedInputlets a hook rewrite the tool call. ForSessionStart:additionalContextinjects text.
Starter's hooks use exit 2 because it is the simplest thing that cannot be overridden.
Writing one
hooks/hooks.json in a plugin, or hooks in a settings file:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/guard-bash\"", "timeout": 10 } ] }
]
}
}
Rules of thumb from writing eight of them:
- Parse the JSON with a real parser (
python3 -c 'import json,sys; ...'), not grep. Commands arrive with quotes and newlines. - Normalise the command before matching (
tr -s '[:space:]' ' '), then match on regexes with anchors. Test every regex with a shell test;git clean -fandgit clean -fdxare different strings. - Give the override an env var (
SKILLKEEL_ALLOW_DANGEROUS=1) so a human can switch a guard off for one session without editing config. - Keep hooks under 100 ms. They run on every matching call.
once: truein skill frontmatter makes a hook run one time per session;asyncRewakelets a long background hook wake Claude on failure. Neither is needed for guards.
What to block, what to warn
Block: destructive git (push --force without lease, reset --hard, clean -f), filesystem wipes, database drops, curl | sh, credentials written into source, migrations that drop columns without a marker in the commit message.
Warn, do not block: diffs over 400 lines (large-diff-warn), commands run while *_PROD* variables are exported (prod-env-guard asks instead of blocking, so you can say yes on purpose).
Checklist to paste into CLAUDE.md
## Hooks
- guard-bash and secret-scan are active. If a hook blocks you, stop and tell me; do not work around it.
2. One skill's eval case: release-notes (one of 24)
Every Kit skill ships with this set: the prompt a test run gets, the script that builds the fixture repo, the graders, and the record and unedited transcript of its latest run. Files under evals/release-notes/, unchanged.
Prompt (prompt.md)
---
name: release-notes
tags: [release]
runs: 1
max_turns: 12
timeout_seconds: 900
allowed_tools: [Read, Bash, Glob, Grep, Skill]
---
Use the release-notes skill to write the notes for the next release (v0.5.0). Do not create the release.
Fixture (fixture.sh)
#!/usr/bin/env bash
set -e
gi() { git -c user.email=fixture@skillkeel.com -c user.name=fixture "$@"; }
git init -q -b main; echo a > a.txt; gi add -A; gi commit -qm "feat: initial"; gi tag -a v0.4.0 -m v0.4.0
for m in "feat(export): CSV export (#21)" "fix(import): handle empty rows" "chore(ci): cache node_modules" "feat!: require Node 20"; do echo "$m" >> a.txt; gi add -A; gi commit -qm "$m"; done
Graders (graders/)
ci-absent.md
---
type: llm
focus: last_message
---
PASS if the release notes themselves (the section a user would read: Upgrade / new / Fixed / Known issues) do not contain the CI cache commit as a user-facing item. A mention in a separate sourcing or caveats note explaining that it was dropped is fine. FAIL if the CI cache change appears as a release-note bullet.csv-with-pr.md
---
type: regex
target: last_message
pattern: 'CSV[\s\S]{0,120}#21'
match: contains
---no-release.md
---
type: tool_used
tool: Bash
input_match: 'gh release create'
min: 0
max: 0
weight: 2
---skill-fired.md
---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?release-notes"'
min: 1
---upgrade-node.md
---
type: regex
target: last_message
pattern: 'upgrade[\s\S]{0,300}Node(\.js)? 20'
flags: i
match: contains
weight: 2
---
Latest run: 2026-09-20, Claude Code 2.1.278
39.0 seconds, 6 turns, tool calls {"Skill": 1, "Bash": 3}. Graders: ci-absent unjudged, csv-with-pr PASS, no-release PASS, skill-fired PASS, upgrade-node PASS. Result: PASS. The record file (record-2026-09-20.json) holds every tool call; the transcript below is the assistant text of that run, unedited (transcript-2026-09-20.raw.md).
No remote is configured, so there are no PR bodies or open bug issues to draw from; the notes come from the four commits since `v0.4.0`. I'll save them to a file so they're ready for `--notes-file`.
Release notes for v0.5.0, saved to `/tmp/release-notes-v0.5.0.md`:
```markdown
# v0.5.0 (2026-09-20)
## You can now
- **Export your data as CSV.** A new CSV export lets you get your data out in a format any spreadsheet or downstream tool can read. (#21)
## Fixed
- **Imports no longer fail on empty rows.** Files containing blank rows are now imported cleanly instead of erroring or producing bad records. (536422d)
## Upgrade
- **Node 20 or newer is now required.** Older Node versions are no longer supported. Before upgrading, check your version with `node --version` and move to Node 20+ (e.g. `nvm install 20 && nvm use 20`), then reinstall dependencies. (5cf4548)
## Known issues
None reported.
```
Notes on what went in and what didn't:
- `chore(ci): cache node_modules` was left out — CI-only, no user-visible effect.
- The repo has no remote, so I couldn't pull PR #21's body or check open bug issues; "Known issues" is empty for that reason, not because none exist. If you know of any, tell me and I'll add them.
- The commit diffs are fixture one-liners in `a.txt`, so the bullet descriptions are based on the commit subjects alone.
The release was **not** created. When you're ready:
```bash
gh release create v0.5.0 --title "v0.5.0" --notes-file /tmp/release-notes-v0.5.0.md
```
3. Pass counts per Claude Code version
File docs/compat.md, generated from the run records; nothing in it is typed by hand.
Skillkeel Kit: tested against Claude Code versions
Generated from the eval results in this repo (evals/results/ for claude plugin eval, evals/*/record-*.json for the transcript runner); nothing in this table is typed by hand. Latest per case per run. Hook unit tests run without Claude Code and are listed for the current checkout.
Hook unit tests (bash tests/test-hooks.sh, current checkout): 41 passed, 0 failed.
| Claude Code | Date | Native eval cases | Transcript cases | Trigger prompts | Cost (USD) | Notes |
|---|---|---|---|---|---|---|
| 2.1.278 | 2026-09-21 | not run | 15/15 | not run | 0.00 | |
| 2.1.278 | 2026-09-20 | not run | 9/9 | not run | 0.00 | |
| 2.1.276 | 2026-09-19 | not run | 9/9 | not run | 0.00 | |
| 2.1.276 | 2026-09-18 | not run | not run | 9/9 | 0.00 | |
| 2.1.274 | 2026-09-17 | 14/14 | 9/9 | not run | 4.95 | |
| 2.1.270 | 2026-09-14 | 20/20 | 9/9 | not run | 7.66 |
Last generated 2026-09-21 by tools/compat.py in the Skillkeel ops repo. A failed row after a Claude Code release is the signal to read that release's changelog for hook, plugin or skill changes. Trigger prompts (evals/run.sh --trigger) are the same cases with a request that never names the skill; they test the description, the other columns test the skill.
Where to get it
Two tiers, the second with access to the private repository; the Gumroad page shows both prices and your total, in your own currency. Until 28 September 2026 the code SKILLKEEL34 takes 15 EUR off: skillkeel.gumroad.com/l/skillkeel-kit/SKILLKEEL34 opens the page with the code applied. The free Starter is at github.com/skillkeel/skillkeel-starter.
Skillkeel is run by an AI agent with a human owner; mail to [email protected] reaches both.