From create-workflow
Generate hooks native to the target tool's runtime — shell hooks, Python register(ctx), TypeScript pi.on(), hookify rules, or quality tool integrations (ruff, pyright, eslint, pytest). Detects the active tool and emits the correct format. Invoked by scaffold or standalone via /generate-hooks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/create-workflow:generate-hooksThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Produce hooks in the format native to the active AI coding tool. The workflow is the product — hooks enforce rules automatically as reflexes.
Produce hooks in the format native to the active AI coding tool. The workflow is the product — hooks enforce rules automatically as reflexes.
Hooks are reflexes — automatic responses to tool events. They enforce rules without requiring the LLM's cooperation. Unlike skills (on-demand), hooks fire every time their trigger condition is met.
Detect which tool is running and emit native hook format:
| Signal | Tool | Hook Format |
|---|---|---|
.claude-plugin/ exists or CLAUDE_CODE env | Claude Code | Shell commands in .claude/settings.json |
plugin.json at root with gemini fields | Antigravity/Gemini | Shell commands in .claude/settings.json (shared format) |
plugin.yaml + __init__.py exist | Hermes | Python callbacks via register(ctx) |
.codex-plugin/ exists or CODEX env | Codex | Shell commands in hooks.json |
package.json with "pi" key | Pi | TypeScript pi.on() in extension file |
When the tool cannot be detected, default to Claude Code format and warn.
Use this table to translate hook intent across tools:
| Intent | Claude Code | Antigravity/Gemini | Hermes | Codex | Pi |
|---|---|---|---|---|---|
| Before tool runs | PreToolUse | BeforeTool / PreToolUse | pre_tool_call | PreToolUse | tool_execution_start |
| After tool runs | PostToolUse | PostToolUse | post_tool_call | PostToolUse | tool_result |
| Session starts | (none) | SessionStart | on_session_start | SessionStart | session_start |
| Session ends / turn done | Stop | SessionEnd | on_session_end | Stop | session_shutdown |
| Before LLM call | (none) | BeforeAgent | pre_llm_call | (none) | before_provider_request |
| After LLM call | (none) | AfterAgent | post_llm_call | (none) | after_provider_response |
| User submits prompt | (none) | (none) | (none) | UserPromptSubmit | input |
| Transform output | (none) | (none) | transform_llm_output | (none) | message_update |
Map quality tools to hook events. These are the most frequently generated hooks:
| Quality Tool | Event | Matcher | Command | When |
|---|---|---|---|---|
| ruff (Python lint+format) | PostToolUse | Write|Edit | ruff check --fix "$CLAUDE_FILE_PATH" && ruff format "$CLAUDE_FILE_PATH" | After every Python file edit |
| pyright (Python types) | Stop | (none) | pyright --pythonpath .venv/bin/python . | End of every turn |
| pytest | Stop | (none) | python -m pytest tests/ --tb=short -q | End of every turn |
| eslint | PostToolUse | Write|Edit | npx eslint --fix "$CLAUDE_FILE_PATH" | After every JS/TS file edit |
| prettier | PostToolUse | Write|Edit | npx prettier --write "$CLAUDE_FILE_PATH" | After every file edit |
| biome | PostToolUse | Write|Edit | npx biome check --write "$CLAUDE_FILE_PATH" | After every file edit |
| black (Python format) | PostToolUse | Write|Edit | black "$CLAUDE_FILE_PATH" | After every Python file edit |
| mypy (Python types) | Stop | (none) | mypy . | End of every turn |
| tsc (TypeScript) | Stop | (none) | npx tsc --noEmit | End of every turn |
| cargo clippy (Rust) | PostToolUse | Write|Edit | cargo clippy --fix --allow-dirty | After every Rust file edit |
| go vet | Stop | (none) | go vet ./... | End of every turn |
| secret scan | Stop | (none) | grep -rn "sk-|AKIA|password=" --include="*.py" . || true | End of every turn |
When generating hooks, scan the project for installed quality tools:
# Python
which ruff && echo "ruff"
which pyright && echo "pyright"
which black && echo "black"
which mypy && echo "mypy"
python -m pytest --version 2>/dev/null && echo "pytest"
# JavaScript/TypeScript
[ -f node_modules/.bin/eslint ] && echo "eslint"
[ -f node_modules/.bin/prettier ] && echo "prettier"
[ -f node_modules/.bin/biome ] && echo "biome"
[ -f node_modules/.bin/tsc ] && echo "tsc"
# Rust
which cargo && echo "cargo"
# Go
which go && echo "go"
For each detected tool, generate the corresponding hook entry. Group by event type: all PostToolUse/Write|Edit hooks together, all Stop hooks together.
When multiple quality tools run on the same event:
When the hookify plugin is installed (check for .claude/plugins/cache/hookify/ or /hookify skill availability), generate hookify rules alongside or instead of raw hooks for pattern-based enforcement.
| Use Case | Format | Reason |
|---|---|---|
| Run a CLI tool (ruff, pytest, pyright) | Raw hook | Deterministic shell command, no pattern matching needed |
| Warn about code patterns (console.log, hardcoded secrets) | Hookify rule | Pattern matching on file content |
| Block behaviors (rm -rf, force push) | Hookify rule | Transcript/command pattern matching |
| Enforce test coverage before stop | Hookify rule | Transcript analysis (did tests run?) |
| Format on every edit | Raw hook | Fast shell command, no analysis |
Write to .claude/hookify.<rule-name>.local.md:
---
name: <rule-name>
enabled: true
event: file|stop|tool
pattern: <regex> # for file/tool events
action: warn|block
conditions: # for stop events
- field: transcript
operator: contains|not_contains
pattern: <regex>
---
**Message Title**
Explanation of why this rule exists and what to do about it.
| Rule | Event | Action | Pattern |
|---|---|---|---|
| Warn on console.log | file | warn | console\.log\( |
| Warn on hardcoded secrets | file | warn | sk-[a-zA-Z0-9]{20,}|AKIA[A-Z0-9]{16} |
| Block rm -rf | tool | block | rm\s+-rf\s+/ |
| Block force push | tool | block | git\s+push\s+.*--force |
| Require tests before stop | stop | block | condition: transcript not_contains pytest|npm test|cargo test |
| Warn on TODO in code | file | warn | TODO|FIXME|HACK |
| Warn on Any type (Python) | file | warn | :\s*Any[^a-zA-Z] |
| Warn on sync I/O in async | file | warn | def\s+(?!async\s).*(?:open|read|write)\( |
When hookify is available, generate BOTH:
This gives the best of both worlds: deterministic CLI enforcement via hooks + intelligent pattern matching via hookify.
Target: .claude/settings.json (team) or .claude/settings.local.json (personal)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "ruff check --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null && ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
},
{
"type": "command",
"command": "pyright \"$CLAUDE_FILE_PATH\" 2>&1 | tail -3"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "pyright --pythonpath .venv/bin/python . 2>&1 | tail -5"
},
{
"type": "command",
"command": "python -m pytest tests/ --tb=short -q 2>&1 | tail -5"
}
]
}
]
}
}
Target: project's __init__.py or standalone hooks.py
def register(ctx):
ctx.register_hook("post_tool_call", format_on_edit)
ctx.register_hook("on_session_end", run_quality_gates)
def format_on_edit(event):
if event.tool_name in ("Write", "Edit"):
import subprocess
subprocess.run(["ruff", "check", "--fix", event.file_path], capture_output=True)
subprocess.run(["ruff", "format", event.file_path], capture_output=True)
def run_quality_gates(event):
import subprocess
subprocess.run(["pyright", "."], capture_output=True)
subprocess.run(["python", "-m", "pytest", "tests/", "--tb=short", "-q"])
Target: project's hooks.json
{
"hooks": [
{
"event": "PostToolUse",
"matcher": "Write|Edit",
"command": "ruff check --fix \"$CLAUDE_FILE_PATH\" && ruff format \"$CLAUDE_FILE_PATH\""
},
{
"event": "Stop",
"command": "pyright . 2>&1 | tail -5 && python -m pytest tests/ --tb=short -q 2>&1 | tail -5"
}
]
}
Target: project's pi/extensions/hooks.ts
import type { ExtensionAPI } from "pi";
export default function projectHooks(pi: ExtensionAPI) {
pi.on("tool_result", (event) => {
if (["Write", "Edit"].includes(event.toolName)) {
pi.exec(`ruff check --fix "${event.filePath}" && ruff format "${event.filePath}"`);
}
});
pi.on("session_shutdown", (_event) => {
pi.exec("pyright .");
pi.exec("python -m pytest tests/ --tb=short -q");
});
}
Read .claude/scaffold-decisions.md if it exists — primary source for resolved grill decisions. Map decisions to hooks using the intent table above.
Map grill decisions to hooks by intent, then translate to the target tool's native event:
| Decision Pattern | Intent | Hook Type |
|---|---|---|
| "Format after every edit" | After tool + file matcher | Raw hook → formatter CLI |
| "Lint changed files" | After tool + file matcher | Raw hook → linter CLI |
| "Type check on stop" | Session ends | Raw hook → type checker CLI |
| "Run tests on stop" | Session ends | Raw hook → test runner CLI |
| "Warn on console.log" | File content pattern | Hookify rule → warn |
| "Block force push" | Command pattern | Hookify rule → block |
| "Enforce tests ran" | Transcript analysis | Hookify rule → block on stop |
| Tool | Team-shared | Personal |
|---|---|---|
| Claude Code | .claude/settings.json | .claude/settings.local.json |
| Antigravity | .claude/settings.json | .claude/settings.local.json |
| Hermes | __init__.py | (not supported) |
| Codex | hooks.json | (not supported) |
| Pi | pi/extensions/hooks.ts | (not supported) |
| Hookify | .claude/hookify.<name>.local.md | Same (always local) |
$CLAUDE_FILE_PATH (Claude Code) or $FILE_PATH for shell-based hookstail -N for Stop hooks to keep feedback concise|| true to PostToolUse formatters so non-matching files don't blockjq -epython -c "import ast; ast.parse(open('file').read())"npx tsc --noEmit if availableWhen --all-tools is passed, generate hooks for ALL detected tools simultaneously.
/generate-hooks
$ARGUMENTS:
--format-only — Generate only formatter hooks--quality — Auto-detect and generate all quality tool hooks (ruff, pyright, pytest, eslint, etc.)--hookify — Generate hookify rules instead of/alongside raw hooks--personal — Target personal/local config--quick-grill — Abbreviated interrogation (3-5 questions)--tool=claude|antigravity|hermes|codex|pi — Force specific tool output--all-tools — Generate hooks for all detected tool manifestsCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
npx claudepluginhub zpankz/create-workflow --plugin create-workflow