agentsclimarketplace

Generate hooks

Skill Zpankz/create-workflow/skills/generate-hooks

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.From its SKILL.md

Install
npx -y skills add Zpankz/create-workflow --skill generate-hooks

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

12.8 KB, as published. Nobody here has run it

Generate Hooks

Produce hooks in the format native to the active AI coding tool. The workflow is the product — hooks enforce rules automatically as reflexes.

Role in the System

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.

Tool Detection

Detect which tool is running and emit native hook format:

SignalToolHook Format
.claude-plugin/ exists or CLAUDE_CODE envClaude CodeShell commands in .claude/settings.json
plugin.json at root with gemini fieldsAntigravity/GeminiShell commands in .claude/settings.json (shared format)
plugin.yaml + __init__.py existHermesPython callbacks via register(ctx)
.codex-plugin/ exists or CODEX envCodexShell commands in hooks.json
package.json with "pi" keyPiTypeScript pi.on() in extension file

When the tool cannot be detected, default to Claude Code format and warn.

Cross-Tool Event Mapping

Use this table to translate hook intent across tools:

IntentClaude CodeAntigravity/GeminiHermesCodexPi
Before tool runsPreToolUseBeforeTool / PreToolUsepre_tool_callPreToolUsetool_execution_start
After tool runsPostToolUsePostToolUsepost_tool_callPostToolUsetool_result
Session starts(none)SessionStarton_session_startSessionStartsession_start
Session ends / turn doneStopSessionEndon_session_endStopsession_shutdown
Before LLM call(none)BeforeAgentpre_llm_call(none)before_provider_request
After LLM call(none)AfterAgentpost_llm_call(none)after_provider_response
User submits prompt(none)(none)(none)UserPromptSubmitinput
Transform output(none)(none)transform_llm_output(none)message_update

Quality Tool Hooks

Common Quality Patterns

Map quality tools to hook events. These are the most frequently generated hooks:

Quality ToolEventMatcherCommandWhen
ruff (Python lint+format)PostToolUseWrite|Editruff 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
pytestStop(none)python -m pytest tests/ --tb=short -qEnd of every turn
eslintPostToolUseWrite|Editnpx eslint --fix "$CLAUDE_FILE_PATH"After every JS/TS file edit
prettierPostToolUseWrite|Editnpx prettier --write "$CLAUDE_FILE_PATH"After every file edit
biomePostToolUseWrite|Editnpx biome check --write "$CLAUDE_FILE_PATH"After every file edit
black (Python format)PostToolUseWrite|Editblack "$CLAUDE_FILE_PATH"After every Python file edit
mypy (Python types)Stop(none)mypy .End of every turn
tsc (TypeScript)Stop(none)npx tsc --noEmitEnd of every turn
cargo clippy (Rust)PostToolUseWrite|Editcargo clippy --fix --allow-dirtyAfter every Rust file edit
go vetStop(none)go vet ./...End of every turn
secret scanStop(none)grep -rn "sk-|AKIA|password=" --include="*.py" . || trueEnd of every turn

Detection and Auto-Configuration

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.

Hook Ordering

When multiple quality tools run on the same event:

  1. PostToolUse: formatter first, then linter (format → lint, not lint → format)
  2. Stop: type checker first, then tests (types catch errors faster than test runs)

Hookify Integration

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.

When to Use Hookify vs Raw Hooks

Use CaseFormatReason
Run a CLI tool (ruff, pytest, pyright)Raw hookDeterministic shell command, no pattern matching needed
Warn about code patterns (console.log, hardcoded secrets)Hookify rulePattern matching on file content
Block behaviors (rm -rf, force push)Hookify ruleTranscript/command pattern matching
Enforce test coverage before stopHookify ruleTranscript analysis (did tests run?)
Format on every editRaw hookFast shell command, no analysis

Hookify Rule Format

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.

Common Hookify Rules to Generate

RuleEventActionPattern
Warn on console.logfilewarnconsole\.log\(
Warn on hardcoded secretsfilewarnsk-[a-zA-Z0-9]{20,}|AKIA[A-Z0-9]{16}
Block rm -rftoolblockrm\s+-rf\s+/
Block force pushtoolblockgit\s+push\s+.*--force
Require tests before stopstopblockcondition: transcript not_contains pytest|npm test|cargo test
Warn on TODO in codefilewarnTODO|FIXME|HACK
Warn on Any type (Python)filewarn:\s*Any[^a-zA-Z]
Warn on sync I/O in asyncfilewarndef\s+(?!async\s).*(?:open|read|write)\(

Hybrid Generation

When hookify is available, generate BOTH:

  1. Raw hooks in settings.json for CLI tool execution (ruff, pyright, pytest)
  2. Hookify rules for pattern-based warnings and blocks

This gives the best of both worlds: deterministic CLI enforcement via hooks + intelligent pattern matching via hookify.

Native Output Formats

Claude Code / Antigravity (shell commands)

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"
          }
        ]
      }
    ]
  }
}

Hermes (Python callbacks)

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"])

Codex (hooks.json)

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"
    }
  ]
}

Pi (TypeScript extension)

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");
  });
}

Input

Read .claude/scaffold-decisions.md if it exists — primary source for resolved grill decisions. Map decisions to hooks using the intent table above.

Hook Design from Workflow Decisions

Map grill decisions to hooks by intent, then translate to the target tool's native event:

Decision PatternIntentHook Type
"Format after every edit"After tool + file matcherRaw hook → formatter CLI
"Lint changed files"After tool + file matcherRaw hook → linter CLI
"Type check on stop"Session endsRaw hook → type checker CLI
"Run tests on stop"Session endsRaw hook → test runner CLI
"Warn on console.log"File content patternHookify rule → warn
"Block force push"Command patternHookify rule → block
"Enforce tests ran"Transcript analysisHookify rule → block on stop

What Hooks CANNOT Do (Any Tool)

  • Filter Bash by command content. No tool's matcher can see what shell command runs. "Before git commit" requires a git pre-commit hook, not a tool hook.
  • Access conversation context. Hooks are deterministic — shell commands (Claude Code/Antigravity/Codex) or event callbacks (Hermes/Pi). Hookify rules CAN match transcripts.
  • Be skipped by the LLM. That's the point.

Target File Selection

ToolTeam-sharedPersonal
Claude Code.claude/settings.json.claude/settings.local.json
Antigravity.claude/settings.json.claude/settings.local.json
Hermes__init__.py(not supported)
Codexhooks.json(not supported)
Pipi/extensions/hooks.ts(not supported)
Hookify.claude/hookify.<name>.local.mdSame (always local)

Generation Rules

  • Detect installed quality tools first — only generate hooks for tools that exist
  • Test the command locally before writing the hook (pipe-test with sample input)
  • Use $CLAUDE_FILE_PATH (Claude Code) or $FILE_PATH for shell-based hooks
  • Keep commands fast — hooks run on every trigger
  • Pipe output through tail -N for Stop hooks to keep feedback concise
  • Add || true to PostToolUse formatters so non-matching files don't block
  • For shell hooks: validate resulting JSON with jq -e
  • For Python hooks: validate syntax with python -c "import ast; ast.parse(open('file').read())"
  • For TypeScript hooks: validate with npx tsc --noEmit if available

Multi-Tool Generation

When --all-tools is passed, generate hooks for ALL detected tools simultaneously.

Standalone Invocation

/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 manifests

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.