Hookify — Python Rule Engine for Custom Hooks

Event-Driven Validation with Markdown Rules

Define validation rules in human-readable markdown with YAML frontmatter. A Python rule engine evaluates patterns against tool operations in real time, enabling warn/block actions across 4 hook event types.

24
Files
4
Events
6
Operators
PY
Language

🔒 Rule Engine🐍 Python Core📝 Markdown Rules⚡ Real-time Validation

📚 What is this & why does it exist?

Hookify bridges the gap between Claude Code's hook system and custom project rules. Instead of writing raw Python for every validation, you define rules as markdown files with YAML frontmatter specifying events, patterns, and actions. The Python rule_engine.py loads these rules via config_loader.py, evaluates each condition using 6 operators (equals, contains, matches, startsWith, endsWith, not), and returns warn or block decisions through JSON stdout.

💡 Real-world analogy

Think of airport security screening. Each security rule (no liquids over 100ml, no sharp objects) is written on a simple card that any officer can read. The X-ray machine (rule engine) scans every bag (tool operation) and checks it against all the rules. If something matches a “warn” rule, the officer flags it for inspection. If it matches a “block” rule, the bag doesn't get through. Hookify works the same way — readable rules, automated enforcement.

Architecture

flowchart LR
    subgraph Events["Hook Events"]
        E1["PreToolUse"]
        E2["PostToolUse"]
        E3["Stop"]
        E4["UserPromptSubmit"]
    end
    subgraph HookScripts["Python Hook Scripts"]
        E1 --> H1["pretooluse.py"]
        E2 --> H2["posttooluse.py"]
        E3 --> H3["stop.py"]
        E4 --> H4["userpromptsubmit.py"]
    end
    subgraph Engine["Rule Engine Core"]
        H1 --> CL["config_loader.py"]
        H2 --> CL
        H3 --> CL
        H4 --> CL
        CL --> RE["rule_engine.py"]
        RE --> OP["6 Operators"]
        OP --> D{"Decision"}
    end
    subgraph Actions["Output"]
        D -->|match| W["Warn / Block"]
        D -->|no match| A["Allow"]
    end

Key Components

ComponentFileRole
config_loader.pycore/config_loader.pyParses markdown rules, loads YAML frontmatter into Rule and Condition dataclasses
rule_engine.pycore/rule_engine.pyRuleEngine class with evaluate_rules() and _check_condition() supporting 6 operators
pretooluse.pyhooks/pretooluse.pyIntercepts tool calls before execution, can block or warn
posttooluse.pyhooks/posttooluse.pyValidates tool output after execution, logs warnings
stop.pyhooks/stop.pyFires when conversation ends, triggers cleanup rules
userpromptsubmit.pyhooks/userpromptsubmit.pyValidates user prompts before they reach the model
hooks.json.claude/hooks.jsonHook registration mapping events to Python scripts
conversation-analyzeragents/conversation-analyzer.mdAnalyzes conversation patterns to suggest new rules
writing-rulesskills/writing-rules/SKILL.mdTeaches Claude how to author markdown rules with YAML frontmatter
no-sudo-rulerules/no-sudo.mdExample rule: blocks sudo commands in bash tool
file-size-rulerules/file-size-check.mdExample rule: warns when writing files over 500 lines
test-required-rulerules/test-required.mdExample rule: warns when editing src/ without matching test
no-secrets-rulerules/no-secrets.mdExample rule: blocks patterns matching API keys or tokens
plugin.json.claude-plugin/plugin.jsonPlugin manifest with hook, agent, skill, and command registration

Rule Authoring Guide

Markdown Rule Format

Rules are markdown files in the rules/ directory with YAML frontmatter defining the rule metadata and a markdown body providing the human-readable description and rationale.

---
name: no-sudo
enabled: true
event: bash
tool_matcher: Bash
conditions:
  - field: command
    operator: contains
    pattern: sudo
action: block
message: "sudo commands are not allowed in this project"
---

# No Sudo Rule

This rule prevents execution of sudo commands through the Bash tool.
Elevated privileges should not be needed for development tasks.
If you need sudo access, contact the infrastructure team.

Operator Usage Examples

equals

conditions:
  - field: tool_name
    operator: equals
    pattern: "Write"

Exact match — tool name must be exactly “Write”.

matches (regex)

conditions:
  - field: file_path
    operator: matches
    pattern: "\\.(env|secret|key)$"

Regex match — catches .env, .secret, and .key files.

startsWith

conditions:
  - field: file_path
    operator: startsWith
    pattern: "/etc/"

Prefix match — blocks writes to system directories.

not (negation)

conditions:
  - field: file_path
    operator: not
    pattern: "src/"

Negated match — triggers when path does NOT contain “src/”.

Multi-Condition Rules

Rules can specify multiple conditions. All conditions must match (AND logic) for the rule to trigger:

---
name: large-file-warning
enabled: true
event: file
tool_matcher: Write
conditions:
  - field: file_path
    operator: matches
    pattern: "\\.(ts|py|js)$"
  - field: content_lines
    operator: gt
    pattern: "500"
action: warn
message: "File exceeds 500 lines - consider splitting"
---

Hook Event Reference

EventScriptTriggerPayload Fields
PreToolUsepretooluse.pyBefore any tool executestool_name, tool_input, command, file_path
PostToolUseposttooluse.pyAfter tool completestool_name, tool_input, tool_output, exit_code
Stopstop.pyConversation endsreason, turn_count, session_id
UserPromptSubmituserpromptsubmit.pyUser sends messageprompt, session_id, turn_number

Data Flow

sequenceDiagram
    participant CC as Claude Code
    participant Hook as Python Hook Script
    participant CL as config_loader.py
    participant RE as rule_engine.py
    participant Rules as Markdown Rules

    CC->>Hook: JSON stdin (tool name, input, event)
    Hook->>CL: load_rules(rules_dir)
    CL->>Rules: Parse YAML frontmatter
    Rules-->>CL: List of Rule dataclasses
    CL-->>Hook: Loaded rules
    Hook->>RE: evaluate_rules(event, tool_data, rules)
    RE->>RE: Filter rules by event type
    RE->>RE: Match tool_matcher pattern
    RE->>RE: _check_condition() x6 operators
    RE-->>Hook: Decision (allow / warn / block)
    Hook-->>CC: JSON stdout (decision + message)

Operator Reference

OperatorPython MethodDescription
equals== patternExact string match against field value
containsin fieldSubstring match within the field value
matchesre.search()Regular expression match against field value
startsWith.startswith()Field value starts with the given prefix
endsWith.endswith()Field value ends with the given suffix
notnot matchNegates the inner condition result

Interactions

ModuleInterfaceData Exchanged
Claude Code Hook SystemJSON stdin/stdoutTool event payload in, decision JSON out
File System.claude/ directoryReads hooks.json config and markdown rule files
Python stdlibre, json, sys, dataclassesRegex matching, JSON parsing, data structures
YAML frontmatterMarkdown parsingRule metadata: event, pattern, conditions, action
conversation-analyzer agentAgent orchestrationConversation history analysis for rule suggestions

Configuration Reference

ParameterDefaultDescription
rule.name(required)Human-readable rule identifier
rule.enabledtrueToggle rule on/off without deleting
rule.eventallTarget event: bash, file, stop, prompt, or all
rule.pattern*Regex pattern for initial tool name matching
rule.conditions[][]Array of condition objects to evaluate
condition.field(required)JSON path to the field in the tool event payload
condition.operatorcontainsOne of 6 operators: equals, contains, matches, startsWith, endsWith, not
condition.pattern(required)Value or regex to match against the field
rule.actionwarnAction on match: warn (log and continue) or block (reject tool call)
rule.tool_matcher*Glob pattern for matching tool names (e.g., Bash, Write)

Directory Map

Source tree for Hookify
hookify/
├── .claude-plugin/plugin.json          ← Plugin manifest
├── .claude/hooks.json                  ← Hook event registration
├── README.md                           ← Usage documentation
├── core/
│   ├── config_loader.py                ← Rule & Condition dataclasses, load_rules()
│   └── rule_engine.py                  ← RuleEngine, evaluate_rules(), _check_condition()
├── hooks/
│   ├── pretooluse.py                   ← PreToolUse event handler
│   ├── posttooluse.py                  ← PostToolUse event handler
│   ├── stop.py                         ← Stop event handler
│   └── userpromptsubmit.py             ← UserPromptSubmit event handler
├── rules/
│   ├── no-sudo.md                      ← Example: block sudo commands
│   ├── file-size-check.md              ← Example: warn on large files
│   ├── test-required.md                ← Example: require test for src changes
│   └── no-secrets.md                   ← Example: block API key patterns
├── agents/
│   └── conversation-analyzer.md        ← Suggests rules from conversation patterns
├── skills/
│   └── writing-rules/SKILL.md          ← Guide for authoring markdown rules
└── commands/
    ├── hookify.md                      ← Main command
    ├── hookify_list.md                 ← List active rules
    ├── hookify_configure.md            ← Configure rule engine
    └── hookify_help.md                 ← Help and documentation