Initial commit
This commit is contained in:
21
.claude-plugin/plugin.json
Normal file
21
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "hookify",
|
||||||
|
"description": "Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"author": {
|
||||||
|
"name": "Daisy Hollman",
|
||||||
|
"email": "daisy@anthropic.com"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./skills"
|
||||||
|
],
|
||||||
|
"agents": [
|
||||||
|
"./agents"
|
||||||
|
],
|
||||||
|
"commands": [
|
||||||
|
"./commands"
|
||||||
|
],
|
||||||
|
"hooks": [
|
||||||
|
"./hooks"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# hookify
|
||||||
|
|
||||||
|
Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns
|
||||||
200
agents/conversation-analyzer.md
Normal file
200
agents/conversation-analyzer.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
---
|
||||||
|
name: conversation-analyzer
|
||||||
|
description: "Use this agent when analyzing conversation transcripts to find behaviors worth preventing with hooks. Examples: <example>Context: User is running /hookify command without arguments\\nuser: \"/hookify\"\\nassistant: \"I'll analyze the conversation to find behaviors you want to prevent\"\\n<commentary>The /hookify command without arguments triggers conversation analysis to find unwanted behaviors.</commentary></example><example>Context: User wants to create hooks from recent frustrations\\nuser: \"Can you look back at this conversation and help me create hooks for the mistakes you made?\"\\nassistant: \"I'll use the conversation-analyzer agent to identify the issues and suggest hooks.\"\\n<commentary>User explicitly asks to analyze conversation for mistakes that should be prevented.</commentary></example>"
|
||||||
|
capabilities:
|
||||||
|
- Analyze conversation transcripts for problematic patterns
|
||||||
|
- Identify tool usage that caused issues
|
||||||
|
- Extract regex patterns for hook rules
|
||||||
|
- Categorize issues by severity
|
||||||
|
model: inherit
|
||||||
|
color: yellow
|
||||||
|
tools: ["Read", "Grep"]
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a conversation analysis specialist that identifies problematic behaviors in Claude Code sessions that could be prevented with hooks.
|
||||||
|
|
||||||
|
**Your Core Responsibilities:**
|
||||||
|
|
||||||
|
1. Read and analyze user messages to find frustration signals
|
||||||
|
2. Identify specific tool usage patterns that caused issues
|
||||||
|
3. Extract actionable patterns that can be matched with regex
|
||||||
|
4. Categorize issues by severity and type
|
||||||
|
5. Provide structured findings for hook rule generation
|
||||||
|
|
||||||
|
**Analysis Process:**
|
||||||
|
|
||||||
|
### 1. Search for User Messages Indicating Issues
|
||||||
|
|
||||||
|
Read through user messages in reverse chronological order (most recent first). Look for:
|
||||||
|
|
||||||
|
**Explicit correction requests:**
|
||||||
|
|
||||||
|
- "Don't use X"
|
||||||
|
- "Stop doing Y"
|
||||||
|
- "Please don't Z"
|
||||||
|
- "Avoid..."
|
||||||
|
- "Never..."
|
||||||
|
|
||||||
|
**Frustrated reactions:**
|
||||||
|
|
||||||
|
- "Why did you do X?"
|
||||||
|
- "I didn't ask for that"
|
||||||
|
- "That's not what I meant"
|
||||||
|
- "That was wrong"
|
||||||
|
|
||||||
|
**Corrections and reversions:**
|
||||||
|
|
||||||
|
- User reverting changes Claude made
|
||||||
|
- User fixing issues Claude created
|
||||||
|
- User providing step-by-step corrections
|
||||||
|
|
||||||
|
**Repeated issues:**
|
||||||
|
|
||||||
|
- Same type of mistake multiple times
|
||||||
|
- User having to remind multiple times
|
||||||
|
- Pattern of similar problems
|
||||||
|
|
||||||
|
### 2. Identify Tool Usage Patterns
|
||||||
|
|
||||||
|
For each issue, determine:
|
||||||
|
|
||||||
|
- **Which tool**: Bash, Edit, Write, MultiEdit
|
||||||
|
- **What action**: Specific command or code pattern
|
||||||
|
- **When it happened**: During what task/phase
|
||||||
|
- **Why problematic**: User's stated reason or implicit concern
|
||||||
|
|
||||||
|
**Extract concrete examples:**
|
||||||
|
|
||||||
|
- For Bash: Actual command that was problematic
|
||||||
|
- For Edit/Write: Code pattern that was added
|
||||||
|
- For Stop: What was missing before stopping
|
||||||
|
|
||||||
|
### 3. Create Regex Patterns
|
||||||
|
|
||||||
|
Convert behaviors into matchable patterns:
|
||||||
|
|
||||||
|
**Bash command patterns:**
|
||||||
|
|
||||||
|
- `rm\s+-rf` for dangerous deletes
|
||||||
|
- `sudo\s+` for privilege escalation
|
||||||
|
- `chmod\s+777` for permission issues
|
||||||
|
|
||||||
|
**Code patterns (Edit/Write):**
|
||||||
|
|
||||||
|
- `console\.log\(` for debug logging
|
||||||
|
- `eval\(|new Function\(` for dangerous eval
|
||||||
|
- `innerHTML\s*=` for XSS risks
|
||||||
|
|
||||||
|
**File path patterns:**
|
||||||
|
|
||||||
|
- `\.env$` for environment files
|
||||||
|
- `/node_modules/` for dependency files
|
||||||
|
- `dist/|build/` for generated files
|
||||||
|
|
||||||
|
### 4. Categorize Severity
|
||||||
|
|
||||||
|
**High severity (should block in future):**
|
||||||
|
|
||||||
|
- Dangerous commands (rm -rf, chmod 777)
|
||||||
|
- Security issues (hardcoded secrets, eval)
|
||||||
|
- Data loss risks
|
||||||
|
|
||||||
|
**Medium severity (warn):**
|
||||||
|
|
||||||
|
- Style violations (console.log in production)
|
||||||
|
- Wrong file types (editing generated files)
|
||||||
|
- Missing best practices
|
||||||
|
|
||||||
|
**Low severity (optional):**
|
||||||
|
|
||||||
|
- Preferences (coding style)
|
||||||
|
- Non-critical patterns
|
||||||
|
|
||||||
|
### 5. Output Format
|
||||||
|
|
||||||
|
Return your findings as structured text in this format:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
## Hookify Analysis Results
|
||||||
|
|
||||||
|
### Issue 1: Dangerous rm Commands
|
||||||
|
**Severity**: High
|
||||||
|
**Tool**: Bash
|
||||||
|
**Pattern**: `rm\s+-rf`
|
||||||
|
**Occurrences**: 3 times
|
||||||
|
**Context**: Used rm -rf on /tmp directories without verification
|
||||||
|
**User Reaction**: "Please be more careful with rm commands"
|
||||||
|
|
||||||
|
**Suggested Rule:**
|
||||||
|
- Name: warn-dangerous-rm
|
||||||
|
- Event: bash
|
||||||
|
- Pattern: rm\s+-rf
|
||||||
|
- Message: "Dangerous rm command detected. Verify path before proceeding."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue 2: Console.log in TypeScript
|
||||||
|
**Severity**: Medium
|
||||||
|
**Tool**: Edit/Write
|
||||||
|
**Pattern**: `console\.log\(`
|
||||||
|
**Occurrences**: 2 times
|
||||||
|
**Context**: Added console.log statements to production TypeScript files
|
||||||
|
**User Reaction**: "Don't use console.log in production code"
|
||||||
|
|
||||||
|
**Suggested Rule:**
|
||||||
|
- Name: warn-console-log
|
||||||
|
- Event: file
|
||||||
|
- Pattern: console\.log\(
|
||||||
|
- Message: "Console.log detected. Use proper logging library instead."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Continue for each issue found...]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Found {N} behaviors worth preventing:
|
||||||
|
- {N} high severity
|
||||||
|
- {N} medium severity
|
||||||
|
- {N} low severity
|
||||||
|
|
||||||
|
Recommend creating rules for high and medium severity issues.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quality Standards:**
|
||||||
|
|
||||||
|
- Be specific about patterns (don't be overly broad)
|
||||||
|
- Include actual examples from conversation
|
||||||
|
- Explain why each issue matters
|
||||||
|
- Provide ready-to-use regex patterns
|
||||||
|
- Don't false-positive on discussions about what NOT to do
|
||||||
|
|
||||||
|
**Edge Cases:**
|
||||||
|
|
||||||
|
**User discussing hypotheticals:**
|
||||||
|
|
||||||
|
- "What would happen if I used rm -rf?"
|
||||||
|
- Don't treat as problematic behavior
|
||||||
|
|
||||||
|
**Teaching moments:**
|
||||||
|
|
||||||
|
- "Here's what you shouldn't do: ..."
|
||||||
|
- Context indicates explanation, not actual problem
|
||||||
|
|
||||||
|
**One-time accidents:**
|
||||||
|
|
||||||
|
- Single occurrence, already fixed
|
||||||
|
- Mention but mark as low priority
|
||||||
|
|
||||||
|
**Subjective preferences:**
|
||||||
|
|
||||||
|
- "I prefer X over Y"
|
||||||
|
- Mark as low severity, let user decide
|
||||||
|
|
||||||
|
**Return Results:**
|
||||||
|
Provide your analysis in the structured format above. The /hookify command will use this to:
|
||||||
|
|
||||||
|
1. Present findings to user
|
||||||
|
2. Ask which rules to create
|
||||||
|
3. Generate .local.md configuration files
|
||||||
|
4. Save rules to .claude directory
|
||||||
139
commands/configure.md
Normal file
139
commands/configure.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
---
|
||||||
|
description: Enable or disable hookify rules interactively
|
||||||
|
allowed-tools: ["Glob", "Read", "Edit", "AskUserQuestion", "Skill"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Configure Hookify Rules
|
||||||
|
|
||||||
|
**Load hookify:writing-rules skill first** to understand rule format.
|
||||||
|
|
||||||
|
Enable or disable existing hookify rules using an interactive interface.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### 1. Find Existing Rules
|
||||||
|
|
||||||
|
Use Glob tool to find all hookify rule files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
pattern: ".claude/hookify.*.local.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
If no rules found, inform user:
|
||||||
|
|
||||||
|
```text
|
||||||
|
No hookify rules configured yet. Use `/hookify` to create your first rule.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Read Current State
|
||||||
|
|
||||||
|
For each rule file:
|
||||||
|
|
||||||
|
- Read the file
|
||||||
|
- Extract `name` and `enabled` fields from frontmatter
|
||||||
|
- Build list of rules with current state
|
||||||
|
|
||||||
|
### 3. Ask User Which Rules to Toggle
|
||||||
|
|
||||||
|
Use AskUserQuestion to let user select rules:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"questions": [
|
||||||
|
{
|
||||||
|
"question": "Which rules would you like to enable or disable?",
|
||||||
|
"header": "Configure",
|
||||||
|
"multiSelect": true,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "warn-dangerous-rm (currently enabled)",
|
||||||
|
"description": "Warns about rm -rf commands"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "warn-console-log (currently disabled)",
|
||||||
|
"description": "Warns about console.log in code"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "require-tests (currently enabled)",
|
||||||
|
"description": "Requires tests before stopping"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option format:**
|
||||||
|
|
||||||
|
- Label: `{rule-name} (currently {enabled|disabled})`
|
||||||
|
- Description: Brief description from rule's message or pattern
|
||||||
|
|
||||||
|
### 4. Parse User Selection
|
||||||
|
|
||||||
|
For each selected rule:
|
||||||
|
|
||||||
|
- Determine current state from label (enabled/disabled)
|
||||||
|
- Toggle state: enabled → disabled, disabled → enabled
|
||||||
|
|
||||||
|
### 5. Update Rule Files
|
||||||
|
|
||||||
|
For each rule to toggle:
|
||||||
|
|
||||||
|
- Use Read tool to read current content
|
||||||
|
- Use Edit tool to change `enabled: true` to `enabled: false` (or vice versa)
|
||||||
|
- Handle both with and without quotes
|
||||||
|
|
||||||
|
**Edit pattern for enabling:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
old_string: "enabled: false"
|
||||||
|
new_string: "enabled: true"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Edit pattern for disabling:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
old_string: "enabled: true"
|
||||||
|
new_string: "enabled: false"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Confirm Changes
|
||||||
|
|
||||||
|
Show user what was changed:
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Hookify Rules Updated
|
||||||
|
|
||||||
|
**Enabled:**
|
||||||
|
- warn-console-log
|
||||||
|
|
||||||
|
**Disabled:**
|
||||||
|
- warn-dangerous-rm
|
||||||
|
|
||||||
|
**Unchanged:**
|
||||||
|
- require-tests
|
||||||
|
|
||||||
|
Changes apply immediately - no restart needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Changes take effect immediately on next tool use
|
||||||
|
- You can also manually edit .claude/hookify.*.local.md files
|
||||||
|
- To permanently remove a rule, delete its .local.md file
|
||||||
|
- Use `/hookify:list` to see all configured rules
|
||||||
|
|
||||||
|
## Edge Cases
|
||||||
|
|
||||||
|
**No rules to configure:**
|
||||||
|
|
||||||
|
- Show message about using `/hookify` to create rules first
|
||||||
|
|
||||||
|
**User selects no rules:**
|
||||||
|
|
||||||
|
- Inform that no changes were made
|
||||||
|
|
||||||
|
**File read/write errors:**
|
||||||
|
|
||||||
|
- Inform user of specific error
|
||||||
|
- Suggest manual editing as fallback
|
||||||
194
commands/help.md
Normal file
194
commands/help.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
description: Get help with the hookify plugin
|
||||||
|
allowed-tools: ["Read"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hookify Plugin Help
|
||||||
|
|
||||||
|
Explain how the hookify plugin works and how to use it.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The hookify plugin makes it easy to create custom hooks that prevent unwanted behaviors.
|
||||||
|
Instead of editing `hooks.json` files, users create simple markdown configuration files
|
||||||
|
that define patterns to watch for.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. Hook System
|
||||||
|
|
||||||
|
Hookify installs generic hooks that run on these events:
|
||||||
|
|
||||||
|
- **PreToolUse**: Before any tool executes (Bash, Edit, Write, etc.)
|
||||||
|
- **PostToolUse**: After a tool executes
|
||||||
|
- **Stop**: When Claude wants to stop working
|
||||||
|
- **UserPromptSubmit**: When user submits a prompt
|
||||||
|
|
||||||
|
These hooks read configuration files from `.claude/hookify.*.local.md` and check if any
|
||||||
|
rules match the current operation.
|
||||||
|
|
||||||
|
### 2. Configuration Files
|
||||||
|
|
||||||
|
Users create rules in `.claude/hookify.{rule-name}.local.md` files:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: warn-dangerous-rm
|
||||||
|
enabled: true
|
||||||
|
event: bash
|
||||||
|
pattern: rm\s+-rf
|
||||||
|
---
|
||||||
|
|
||||||
|
⚠️ **Dangerous rm command detected!**
|
||||||
|
|
||||||
|
This command could delete important files. Please verify the path.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key fields:**
|
||||||
|
|
||||||
|
- `name`: Unique identifier for the rule
|
||||||
|
- `enabled`: true/false to activate/deactivate
|
||||||
|
- `event`: bash, file, stop, prompt, or all
|
||||||
|
- `pattern`: Regex pattern to match
|
||||||
|
|
||||||
|
The message body is what Claude sees when the rule triggers.
|
||||||
|
|
||||||
|
### 3. Creating Rules
|
||||||
|
|
||||||
|
#### Option A: Use /hookify command
|
||||||
|
|
||||||
|
```text
|
||||||
|
/hookify Don't use console.log in production files
|
||||||
|
```
|
||||||
|
|
||||||
|
This analyzes your request and creates the appropriate rule file.
|
||||||
|
|
||||||
|
#### Option B: Create manually
|
||||||
|
|
||||||
|
Create `.claude/hookify.my-rule.local.md` with the format above.
|
||||||
|
|
||||||
|
#### Option C: Analyze conversation
|
||||||
|
|
||||||
|
```text
|
||||||
|
/hookify
|
||||||
|
```
|
||||||
|
|
||||||
|
Without arguments, hookify analyzes recent conversation to find behaviors you want to prevent.
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
- **`/hookify`** - Create hooks from conversation analysis or explicit instructions
|
||||||
|
- **`/hookify:help`** - Show this help (what you're reading now)
|
||||||
|
- **`/hookify:list`** - List all configured hooks
|
||||||
|
- **`/hookify:configure`** - Enable/disable existing hooks interactively
|
||||||
|
|
||||||
|
## Example Use Cases
|
||||||
|
|
||||||
|
**Prevent dangerous commands:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: block-chmod-777
|
||||||
|
enabled: true
|
||||||
|
event: bash
|
||||||
|
pattern: chmod\s+777
|
||||||
|
---
|
||||||
|
|
||||||
|
Don't use chmod 777 - it's a security risk. Use specific permissions instead.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warn about debugging code:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: warn-console-log
|
||||||
|
enabled: true
|
||||||
|
event: file
|
||||||
|
pattern: console\.log\(
|
||||||
|
---
|
||||||
|
|
||||||
|
Console.log detected. Remember to remove debug logging before committing.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Require tests before stopping:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: require-tests
|
||||||
|
enabled: true
|
||||||
|
event: stop
|
||||||
|
pattern: .*
|
||||||
|
---
|
||||||
|
|
||||||
|
Did you run tests before finishing? Make sure `npm test` or equivalent was executed.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern Syntax
|
||||||
|
|
||||||
|
Use Python regex syntax:
|
||||||
|
|
||||||
|
- `\s` - whitespace
|
||||||
|
- `\.` - literal dot
|
||||||
|
- `|` - OR
|
||||||
|
- `+` - one or more
|
||||||
|
- `*` - zero or more
|
||||||
|
- `\d` - digit
|
||||||
|
- `[abc]` - character class
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
- `rm\s+-rf` - matches "rm -rf"
|
||||||
|
- `console\.log\(` - matches "console.log("
|
||||||
|
- `(eval|exec)\(` - matches "eval(" or "exec("
|
||||||
|
- `\.env$` - matches files ending in .env
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
**No Restart Needed**: Hookify rules (`.local.md` files) take effect immediately on
|
||||||
|
the next tool use. The hookify hooks are already loaded and read your rules dynamically.
|
||||||
|
|
||||||
|
**Block or Warn**: Rules can either `block` operations (prevent execution) or `warn`
|
||||||
|
(show message but allow). Set `action: block` or `action: warn` in the rule's frontmatter.
|
||||||
|
|
||||||
|
**Rule Files**: Keep rules in `.claude/hookify.*.local.md` - they should be git-ignored (add to .gitignore if needed).
|
||||||
|
|
||||||
|
**Disable Rules**: Set `enabled: false` in frontmatter or delete the file.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Hook not triggering:**
|
||||||
|
|
||||||
|
- Check rule file is in `.claude/` directory
|
||||||
|
- Verify `enabled: true` in frontmatter
|
||||||
|
- Confirm pattern is valid regex
|
||||||
|
- Test pattern: `python3 -c "import re; print(re.search('your_pattern', 'test_text'))"`
|
||||||
|
- Rules take effect immediately - no restart needed
|
||||||
|
|
||||||
|
**Import errors:**
|
||||||
|
|
||||||
|
- Check Python 3 is available: `python3 --version`
|
||||||
|
- Verify hookify plugin is installed correctly
|
||||||
|
|
||||||
|
**Pattern not matching:**
|
||||||
|
|
||||||
|
- Test regex separately
|
||||||
|
- Check for escaping issues (use unquoted patterns in YAML)
|
||||||
|
- Try simpler pattern first, then refine
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Create your first rule:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/hookify Warn me when I try to use rm -rf
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Try to trigger it:
|
||||||
|
- Ask Claude to run `rm -rf /tmp/test`
|
||||||
|
- You should see the warning
|
||||||
|
|
||||||
|
3. Refine the rule by editing `.claude/hookify.warn-rm.local.md`
|
||||||
|
|
||||||
|
4. Create more rules as you encounter unwanted behaviors
|
||||||
|
|
||||||
|
For more examples, check the `${CLAUDE_PLUGIN_ROOT}/examples/` directory.
|
||||||
253
commands/hookify.md
Normal file
253
commands/hookify.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
---
|
||||||
|
description: Create hooks to prevent unwanted behaviors from conversation analysis or explicit instructions
|
||||||
|
argument-hint: Optional specific behavior to address
|
||||||
|
allowed-tools: ["Read", "Write", "AskUserQuestion", "Task", "Grep", "TodoWrite", "Skill"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hookify - Create Hooks from Unwanted Behaviors
|
||||||
|
|
||||||
|
**FIRST: Load the hookify:writing-rules skill** using the Skill tool to understand rule file format and syntax.
|
||||||
|
|
||||||
|
Create hook rules to prevent problematic behaviors by analyzing the conversation or from explicit user instructions.
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
You will help the user create hookify rules to prevent unwanted behaviors. Follow these steps:
|
||||||
|
|
||||||
|
### Step 1: Gather Behavior Information
|
||||||
|
|
||||||
|
**If $ARGUMENTS is provided:**
|
||||||
|
|
||||||
|
- User has given specific instructions: `$ARGUMENTS`
|
||||||
|
- Still analyze recent conversation (last 10-15 user messages) for additional context
|
||||||
|
- Look for examples of the behavior happening
|
||||||
|
|
||||||
|
**If $ARGUMENTS is empty:**
|
||||||
|
|
||||||
|
- Launch the conversation-analyzer agent to find problematic behaviors
|
||||||
|
- Agent will scan user prompts for frustration signals
|
||||||
|
- Agent will return structured findings
|
||||||
|
|
||||||
|
**To analyze conversation:**
|
||||||
|
Use the Task tool to launch conversation-analyzer agent:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{
|
||||||
|
"subagent_type": "general-purpose",
|
||||||
|
"description": "Analyze conversation for unwanted behaviors",
|
||||||
|
"prompt": "You are analyzing a Claude Code conversation to find behaviors the user wants to prevent.
|
||||||
|
|
||||||
|
Read user messages in the current conversation and identify:
|
||||||
|
1. Explicit requests to avoid something (\"don't do X\", \"stop doing Y\")
|
||||||
|
2. Corrections or reversions (user fixing Claude's actions)
|
||||||
|
3. Frustrated reactions (\"why did you do X?\", \"I didn't ask for that\")
|
||||||
|
4. Repeated issues (same problem multiple times)
|
||||||
|
|
||||||
|
For each issue found, extract:
|
||||||
|
- What tool was used (Bash, Edit, Write, etc.)
|
||||||
|
- Specific pattern or command
|
||||||
|
- Why it was problematic
|
||||||
|
- User's stated reason
|
||||||
|
|
||||||
|
Return findings as a structured list with:
|
||||||
|
- category: Type of issue
|
||||||
|
- tool: Which tool was involved
|
||||||
|
- pattern: Regex or literal pattern to match
|
||||||
|
- context: What happened
|
||||||
|
- severity: high/medium/low
|
||||||
|
|
||||||
|
Focus on the most recent issues (last 20-30 messages). Don't go back further unless explicitly asked."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Present Findings to User
|
||||||
|
|
||||||
|
After gathering behaviors (from arguments or agent), present to user using AskUserQuestion:
|
||||||
|
|
||||||
|
**Question 1: Which behaviors to hookify?**
|
||||||
|
|
||||||
|
- Header: "Create Rules"
|
||||||
|
- multiSelect: true
|
||||||
|
- Options: List each detected behavior (max 4)
|
||||||
|
- Label: Short description (e.g., "Block rm -rf")
|
||||||
|
- Description: Why it's problematic
|
||||||
|
|
||||||
|
**Question 2: For each selected behavior, ask about action:**
|
||||||
|
|
||||||
|
- "Should this block the operation or just warn?"
|
||||||
|
- Options:
|
||||||
|
- "Just warn" (action: warn - shows message but allows)
|
||||||
|
- "Block operation" (action: block - prevents execution)
|
||||||
|
|
||||||
|
**Question 3: Ask for example patterns:**
|
||||||
|
|
||||||
|
- "What patterns should trigger this rule?"
|
||||||
|
- Show detected patterns
|
||||||
|
- Allow user to refine or add more
|
||||||
|
|
||||||
|
### Step 3: Generate Rule Files
|
||||||
|
|
||||||
|
For each confirmed behavior, create a `.claude/hookify.{rule-name}.local.md` file:
|
||||||
|
|
||||||
|
**Rule naming convention:**
|
||||||
|
|
||||||
|
- Use kebab-case
|
||||||
|
- Be descriptive: `block-dangerous-rm`, `warn-console-log`, `require-tests-before-stop`
|
||||||
|
- Start with action verb: block, warn, prevent, require
|
||||||
|
|
||||||
|
**File format:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {rule-name}
|
||||||
|
enabled: true
|
||||||
|
event: {bash|file|stop|prompt|all}
|
||||||
|
pattern: {regex pattern}
|
||||||
|
action: {warn|block}
|
||||||
|
---
|
||||||
|
|
||||||
|
{Message to show Claude when rule triggers}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Action values:**
|
||||||
|
|
||||||
|
- `warn`: Show message but allow operation (default)
|
||||||
|
- `block`: Prevent operation or stop session
|
||||||
|
|
||||||
|
**For more complex rules (multiple conditions):**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {rule-name}
|
||||||
|
enabled: true
|
||||||
|
event: file
|
||||||
|
conditions:
|
||||||
|
- field: file_path
|
||||||
|
operator: regex_match
|
||||||
|
pattern: \.env$
|
||||||
|
- field: new_text
|
||||||
|
operator: contains
|
||||||
|
pattern: API_KEY
|
||||||
|
---
|
||||||
|
|
||||||
|
{Warning message}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Create Files and Confirm
|
||||||
|
|
||||||
|
**IMPORTANT**: Rule files must be created in the current working directory's `.claude/`
|
||||||
|
folder, NOT the plugin directory.
|
||||||
|
|
||||||
|
Use the current working directory (where Claude Code was started) as the base path.
|
||||||
|
|
||||||
|
1. Check if `.claude/` directory exists in current working directory
|
||||||
|
- If not, create it first with: `mkdir -p .claude`
|
||||||
|
|
||||||
|
2. Use Write tool to create each `.claude/hookify.{name}.local.md` file
|
||||||
|
- Use relative path from current working directory: `.claude/hookify.{name}.local.md`
|
||||||
|
- The path should resolve to the project's .claude directory, not the plugin's
|
||||||
|
|
||||||
|
3. Show user what was created:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Created 3 hookify rules:
|
||||||
|
- .claude/hookify.dangerous-rm.local.md
|
||||||
|
- .claude/hookify.console-log.local.md
|
||||||
|
- .claude/hookify.sensitive-files.local.md
|
||||||
|
|
||||||
|
These rules will trigger on:
|
||||||
|
- dangerous-rm: Bash commands matching "rm -rf"
|
||||||
|
- console-log: Edits adding console.log statements
|
||||||
|
- sensitive-files: Edits to .env or credentials files
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Verify files were created in the correct location by listing them
|
||||||
|
|
||||||
|
5. Inform user: **"Rules are active immediately - no restart needed!"**
|
||||||
|
|
||||||
|
The hookify hooks are already loaded and will read your new rules on the next tool use.
|
||||||
|
|
||||||
|
## Event Types Reference
|
||||||
|
|
||||||
|
- **bash**: Matches Bash tool commands
|
||||||
|
- **file**: Matches Edit, Write, MultiEdit tools
|
||||||
|
- **stop**: Matches when agent wants to stop (use for completion checks)
|
||||||
|
- **prompt**: Matches when user submits prompts
|
||||||
|
- **all**: Matches all events
|
||||||
|
|
||||||
|
## Pattern Writing Tips
|
||||||
|
|
||||||
|
**Bash patterns:**
|
||||||
|
|
||||||
|
- Match dangerous commands: `rm\s+-rf|chmod\s+777|dd\s+if=`
|
||||||
|
- Match specific tools: `npm\s+install\s+|pip\s+install`
|
||||||
|
|
||||||
|
**File patterns:**
|
||||||
|
|
||||||
|
- Match code patterns: `console\.log\(|eval\(|innerHTML\s*=`
|
||||||
|
- Match file paths: `\.env$|\.git/|node_modules/`
|
||||||
|
|
||||||
|
**Stop patterns:**
|
||||||
|
|
||||||
|
- Check for missing steps: (check transcript or completion criteria)
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
**User says**: "/hookify Don't use rm -rf without asking me first"
|
||||||
|
|
||||||
|
**Your response**:
|
||||||
|
|
||||||
|
1. Analyze: User wants to prevent rm -rf commands
|
||||||
|
2. Ask: "Should I block this command or just warn you?"
|
||||||
|
3. User selects: "Just warn"
|
||||||
|
4. Create `.claude/hookify.dangerous-rm.local.md`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: warn-dangerous-rm
|
||||||
|
enabled: true
|
||||||
|
event: bash
|
||||||
|
pattern: rm\s+-rf
|
||||||
|
---
|
||||||
|
|
||||||
|
⚠️ **Dangerous rm command detected**
|
||||||
|
|
||||||
|
You requested to be warned before using rm -rf.
|
||||||
|
Please verify the path is correct.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Confirm: "Created hookify rule. It's active immediately - try triggering it!"
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **No restart needed**: Rules take effect immediately on the next tool use
|
||||||
|
- **File location**: Create files in project's `.claude/` directory (current working
|
||||||
|
directory), NOT the plugin's .claude/
|
||||||
|
- **Regex syntax**: Use Python regex syntax (raw strings, no need to escape in YAML)
|
||||||
|
- **Action types**: Rules can `warn` (default) or `block` operations
|
||||||
|
- **Testing**: Test rules immediately after creating them
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**If rule file creation fails:**
|
||||||
|
|
||||||
|
1. Check current working directory with pwd
|
||||||
|
2. Ensure `.claude/` directory exists (create with mkdir if needed)
|
||||||
|
3. Use absolute path if needed: `{cwd}/.claude/hookify.{name}.local.md`
|
||||||
|
4. Verify file was created with Glob or ls
|
||||||
|
|
||||||
|
**If rule doesn't trigger after creation:**
|
||||||
|
|
||||||
|
1. Verify file is in project `.claude/` not plugin `.claude/`
|
||||||
|
2. Check file with Read tool to ensure pattern is correct
|
||||||
|
3. Test pattern with: `python3 -c "import re; print(re.search(r'pattern', 'test text'))"`
|
||||||
|
4. Verify `enabled: true` in frontmatter
|
||||||
|
5. Remember: Rules work immediately, no restart needed
|
||||||
|
|
||||||
|
**If blocking seems too strict:**
|
||||||
|
|
||||||
|
1. Change `action: block` to `action: warn` in the rule file
|
||||||
|
2. Or adjust the pattern to be more specific
|
||||||
|
3. Changes take effect on next tool use
|
||||||
|
|
||||||
|
Use TodoWrite to track your progress through the steps.
|
||||||
87
commands/list.md
Normal file
87
commands/list.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
description: List all configured hookify rules
|
||||||
|
allowed-tools: ["Glob", "Read", "Skill"]
|
||||||
|
---
|
||||||
|
|
||||||
|
# List Hookify Rules
|
||||||
|
|
||||||
|
**Load hookify:writing-rules skill first** to understand rule format.
|
||||||
|
|
||||||
|
Show all configured hookify rules in the project.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Use Glob tool to find all hookify rule files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
pattern: ".claude/hookify.*.local.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. For each file found:
|
||||||
|
- Use Read tool to read the file
|
||||||
|
- Extract frontmatter fields: name, enabled, event, pattern
|
||||||
|
- Extract message preview (first 100 chars)
|
||||||
|
|
||||||
|
3. Present results in a table:
|
||||||
|
|
||||||
|
```text
|
||||||
|
## Configured Hookify Rules
|
||||||
|
|
||||||
|
| Name | Enabled | Event | Pattern | File |
|
||||||
|
|------|---------|-------|---------|------|
|
||||||
|
| warn-dangerous-rm | ✅ Yes | bash | rm\s+-rf | hookify.dangerous-rm.local.md |
|
||||||
|
| warn-console-log | ✅ Yes | file | console\.log\( | hookify.console-log.local.md |
|
||||||
|
| check-tests | ❌ No | stop | .* | hookify.require-tests.local.md |
|
||||||
|
|
||||||
|
**Total**: 3 rules (2 enabled, 1 disabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. For each rule, show a brief preview:
|
||||||
|
|
||||||
|
```text
|
||||||
|
### warn-dangerous-rm
|
||||||
|
**Event**: bash
|
||||||
|
**Pattern**: `rm\s+-rf`
|
||||||
|
**Message**: "⚠️ **Dangerous rm command detected!** This command could delete..."
|
||||||
|
|
||||||
|
**Status**: ✅ Active
|
||||||
|
**File**: .claude/hookify.dangerous-rm.local.md
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add helpful footer:
|
||||||
|
|
||||||
|
```text
|
||||||
|
---
|
||||||
|
|
||||||
|
To modify a rule: Edit the .local.md file directly
|
||||||
|
To disable a rule: Set `enabled: false` in frontmatter
|
||||||
|
To enable a rule: Set `enabled: true` in frontmatter
|
||||||
|
To delete a rule: Remove the .local.md file
|
||||||
|
To create a rule: Use `/hookify` command
|
||||||
|
|
||||||
|
**Remember**: Changes take effect immediately - no restart needed
|
||||||
|
```
|
||||||
|
|
||||||
|
## If No Rules Found
|
||||||
|
|
||||||
|
If no hookify rules exist:
|
||||||
|
|
||||||
|
```text
|
||||||
|
## No Hookify Rules Configured
|
||||||
|
|
||||||
|
You haven't created any hookify rules yet.
|
||||||
|
|
||||||
|
To get started:
|
||||||
|
1. Use `/hookify` to analyze conversation and create rules
|
||||||
|
2. Or manually create `.claude/hookify.my-rule.local.md` files
|
||||||
|
3. See `/hookify:help` for documentation
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
|
||||||
|
/hookify Warn me when I use console.log
|
||||||
|
|
||||||
|
```text
|
||||||
|
|
||||||
|
Check `${CLAUDE_PLUGIN_ROOT}/examples/` for example rule files.
|
||||||
|
```
|
||||||
0
hooks/__init__.py
Normal file
0
hooks/__init__.py
Normal file
49
hooks/hooks.json
Normal file
49
hooks/hooks.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"description": "Hookify plugin - User-configurable hooks from .local.md files",
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse.py",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse.py",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Stop": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/stop.py",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"UserPromptSubmit": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/userpromptsubmit.py",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
66
hooks/posttooluse.py
Executable file
66
hooks/posttooluse.py
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PostToolUse hook executor for hookify plugin.
|
||||||
|
|
||||||
|
This script is called by Claude Code after a tool executes.
|
||||||
|
It reads .claude/hookify.*.local.md files and evaluates rules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
# CRITICAL: Add plugin root to Python path for imports
|
||||||
|
PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
|
||||||
|
if PLUGIN_ROOT:
|
||||||
|
parent_dir = os.path.dirname(PLUGIN_ROOT)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
if PLUGIN_ROOT not in sys.path:
|
||||||
|
sys.path.insert(0, PLUGIN_ROOT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hookify.core.config_loader import load_rules
|
||||||
|
from hookify.core.rule_engine import RuleEngine
|
||||||
|
except ImportError as e:
|
||||||
|
error_msg = {"systemMessage": f"Hookify import error: {e}"}
|
||||||
|
print(json.dumps(error_msg), file=sys.stdout)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for PostToolUse hook."""
|
||||||
|
try:
|
||||||
|
# Read input from stdin
|
||||||
|
input_data = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Determine event type based on tool
|
||||||
|
tool_name = input_data.get('tool_name', '')
|
||||||
|
event = None
|
||||||
|
if tool_name == 'Bash':
|
||||||
|
event = 'bash'
|
||||||
|
elif tool_name in ['Edit', 'Write', 'MultiEdit']:
|
||||||
|
event = 'file'
|
||||||
|
|
||||||
|
# Load rules
|
||||||
|
rules = load_rules(event=event)
|
||||||
|
|
||||||
|
# Evaluate rules
|
||||||
|
engine = RuleEngine()
|
||||||
|
result = engine.evaluate_rules(rules, input_data)
|
||||||
|
|
||||||
|
# Always output JSON (even if empty)
|
||||||
|
print(json.dumps(result), file=sys.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_output = {
|
||||||
|
"systemMessage": f"Hookify error: {str(e)}"
|
||||||
|
}
|
||||||
|
print(json.dumps(error_output), file=sys.stdout)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# ALWAYS exit 0
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
74
hooks/pretooluse.py
Executable file
74
hooks/pretooluse.py
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PreToolUse hook executor for hookify plugin.
|
||||||
|
|
||||||
|
This script is called by Claude Code before any tool executes.
|
||||||
|
It reads .claude/hookify.*.local.md files and evaluates rules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
# CRITICAL: Add plugin root to Python path for imports
|
||||||
|
# We need to add the parent of the plugin directory so Python can find "hookify" package
|
||||||
|
PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
|
||||||
|
if PLUGIN_ROOT:
|
||||||
|
# Add the parent directory of the plugin
|
||||||
|
parent_dir = os.path.dirname(PLUGIN_ROOT)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
|
||||||
|
# Also add PLUGIN_ROOT itself in case we have other scripts
|
||||||
|
if PLUGIN_ROOT not in sys.path:
|
||||||
|
sys.path.insert(0, PLUGIN_ROOT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hookify.core.config_loader import load_rules
|
||||||
|
from hookify.core.rule_engine import RuleEngine
|
||||||
|
except ImportError as e:
|
||||||
|
# If imports fail, allow operation and log error
|
||||||
|
error_msg = {"systemMessage": f"Hookify import error: {e}"}
|
||||||
|
print(json.dumps(error_msg), file=sys.stdout)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for PreToolUse hook."""
|
||||||
|
try:
|
||||||
|
# Read input from stdin
|
||||||
|
input_data = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Determine event type for filtering
|
||||||
|
# For PreToolUse, we use tool_name to determine "bash" vs "file" event
|
||||||
|
tool_name = input_data.get('tool_name', '')
|
||||||
|
|
||||||
|
event = None
|
||||||
|
if tool_name == 'Bash':
|
||||||
|
event = 'bash'
|
||||||
|
elif tool_name in ['Edit', 'Write', 'MultiEdit']:
|
||||||
|
event = 'file'
|
||||||
|
|
||||||
|
# Load rules
|
||||||
|
rules = load_rules(event=event)
|
||||||
|
|
||||||
|
# Evaluate rules
|
||||||
|
engine = RuleEngine()
|
||||||
|
result = engine.evaluate_rules(rules, input_data)
|
||||||
|
|
||||||
|
# Always output JSON (even if empty)
|
||||||
|
print(json.dumps(result), file=sys.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# On any error, allow the operation and log
|
||||||
|
error_output = {
|
||||||
|
"systemMessage": f"Hookify error: {str(e)}"
|
||||||
|
}
|
||||||
|
print(json.dumps(error_output), file=sys.stdout)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# ALWAYS exit 0 - never block operations due to hook errors
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
59
hooks/stop.py
Executable file
59
hooks/stop.py
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Stop hook executor for hookify plugin.
|
||||||
|
|
||||||
|
This script is called by Claude Code when agent wants to stop.
|
||||||
|
It reads .claude/hookify.*.local.md files and evaluates stop rules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
# CRITICAL: Add plugin root to Python path for imports
|
||||||
|
PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
|
||||||
|
if PLUGIN_ROOT:
|
||||||
|
parent_dir = os.path.dirname(PLUGIN_ROOT)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
if PLUGIN_ROOT not in sys.path:
|
||||||
|
sys.path.insert(0, PLUGIN_ROOT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hookify.core.config_loader import load_rules
|
||||||
|
from hookify.core.rule_engine import RuleEngine
|
||||||
|
except ImportError as e:
|
||||||
|
error_msg = {"systemMessage": f"Hookify import error: {e}"}
|
||||||
|
print(json.dumps(error_msg), file=sys.stdout)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for Stop hook."""
|
||||||
|
try:
|
||||||
|
# Read input from stdin
|
||||||
|
input_data = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Load stop rules
|
||||||
|
rules = load_rules(event='stop')
|
||||||
|
|
||||||
|
# Evaluate rules
|
||||||
|
engine = RuleEngine()
|
||||||
|
result = engine.evaluate_rules(rules, input_data)
|
||||||
|
|
||||||
|
# Always output JSON (even if empty)
|
||||||
|
print(json.dumps(result), file=sys.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# On any error, allow the operation
|
||||||
|
error_output = {
|
||||||
|
"systemMessage": f"Hookify error: {str(e)}"
|
||||||
|
}
|
||||||
|
print(json.dumps(error_output), file=sys.stdout)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# ALWAYS exit 0
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
58
hooks/userpromptsubmit.py
Executable file
58
hooks/userpromptsubmit.py
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""UserPromptSubmit hook executor for hookify plugin.
|
||||||
|
|
||||||
|
This script is called by Claude Code when user submits a prompt.
|
||||||
|
It reads .claude/hookify.*.local.md files and evaluates rules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
|
||||||
|
# CRITICAL: Add plugin root to Python path for imports
|
||||||
|
PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
|
||||||
|
if PLUGIN_ROOT:
|
||||||
|
parent_dir = os.path.dirname(PLUGIN_ROOT)
|
||||||
|
if parent_dir not in sys.path:
|
||||||
|
sys.path.insert(0, parent_dir)
|
||||||
|
if PLUGIN_ROOT not in sys.path:
|
||||||
|
sys.path.insert(0, PLUGIN_ROOT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from hookify.core.config_loader import load_rules
|
||||||
|
from hookify.core.rule_engine import RuleEngine
|
||||||
|
except ImportError as e:
|
||||||
|
error_msg = {"systemMessage": f"Hookify import error: {e}"}
|
||||||
|
print(json.dumps(error_msg), file=sys.stdout)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for UserPromptSubmit hook."""
|
||||||
|
try:
|
||||||
|
# Read input from stdin
|
||||||
|
input_data = json.load(sys.stdin)
|
||||||
|
|
||||||
|
# Load user prompt rules
|
||||||
|
rules = load_rules(event='prompt')
|
||||||
|
|
||||||
|
# Evaluate rules
|
||||||
|
engine = RuleEngine()
|
||||||
|
result = engine.evaluate_rules(rules, input_data)
|
||||||
|
|
||||||
|
# Always output JSON (even if empty)
|
||||||
|
print(json.dumps(result), file=sys.stdout)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_output = {
|
||||||
|
"systemMessage": f"Hookify error: {str(e)}"
|
||||||
|
}
|
||||||
|
print(json.dumps(error_output), file=sys.stdout)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# ALWAYS exit 0
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
89
plugin.lock.json
Normal file
89
plugin.lock.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:basher83/lunar-claude:plugins/meta/hookify",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "0de1696757187783f1a85e3edb5eb96693149d1a",
|
||||||
|
"treeHash": "39c1f8749e71654475f66610e548f9df8e90cb0dc094f44ff57f87aadeb2a2c4",
|
||||||
|
"generatedAt": "2025-11-28T10:14:12.998507Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "hookify",
|
||||||
|
"description": "Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns",
|
||||||
|
"version": "0.1.1"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "c39476bf2e66bab0c771b3c461ff44ea2a27e3cf6d9747e89244963666142234"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "agents/conversation-analyzer.md",
|
||||||
|
"sha256": "3cd096438cd35c08703b674d141d870ebed165f1e3edf948a97b36891cf06b84"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/userpromptsubmit.py",
|
||||||
|
"sha256": "82c8784355826e9ecfa01c5b357c27d1246c21f0d26400d32ea962c7db4e3d01"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/stop.py",
|
||||||
|
"sha256": "c37678c66db0d9ca0c2d4004ee073de88617c70c6def11bfd110d5610ddfb24c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/__init__.py",
|
||||||
|
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/pretooluse.py",
|
||||||
|
"sha256": "cf3f718f26f57979686ca00d9cccbe09bc7920943bdc35cd689ba5163a777aac"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/hooks.json",
|
||||||
|
"sha256": "838dd2ffbe325307cc23a02ef541fd45c25fd42492d64ccbe9ec11233a3a04f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "hooks/posttooluse.py",
|
||||||
|
"sha256": "b440f17f3a6c962d87f094ce8974cb6090881adec2e495eb2356aa89b945f741"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "a45b938c335e627ef46df719ff0e9efca48f314b66ba0288e173abdaab071f83"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/help.md",
|
||||||
|
"sha256": "d38090734252d8aaa44a02429df3e6897c2a6870dd5873cadda827d75c4592b8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/list.md",
|
||||||
|
"sha256": "23bcbc165f0bd0bce5d1cca2d5d6608a6c0ffffdfd4277b80e273f574d9e3cbc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/configure.md",
|
||||||
|
"sha256": "15bc56a5971962174d535a31f575887ef61b3e500a49854188683c1ea02bbaed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "commands/hookify.md",
|
||||||
|
"sha256": "07d972fc9fc6e2639b9e59a72535bf4994916f33404e4f2d6e6ed59aebfa711d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "skills/writing-rules/SKILL.md",
|
||||||
|
"sha256": "d5c04a7b5a6972d47bf87f808ee0556f733117df47f57d6eca1b8fdab407f765"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "39c1f8749e71654475f66610e548f9df8e90cb0dc094f44ff57f87aadeb2a2c4"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
408
skills/writing-rules/SKILL.md
Normal file
408
skills/writing-rules/SKILL.md
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
---
|
||||||
|
name: Writing Hookify Rules
|
||||||
|
description: >
|
||||||
|
This skill should be used when the user asks to "create a hookify rule", "write a hook rule",
|
||||||
|
"configure hookify", "add a hookify rule", or needs guidance on hookify rule syntax and patterns.
|
||||||
|
version: 0.1.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Writing Hookify Rules
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Hookify rules are markdown files with YAML frontmatter that define patterns to watch for
|
||||||
|
and messages to show when those patterns match. Rules are stored in
|
||||||
|
`.claude/hookify.{rule-name}.local.md` files.
|
||||||
|
|
||||||
|
## Rule File Format
|
||||||
|
|
||||||
|
### Basic Structure
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: rule-identifier
|
||||||
|
enabled: true
|
||||||
|
event: bash|file|stop|prompt|all
|
||||||
|
pattern: regex-pattern-here
|
||||||
|
---
|
||||||
|
|
||||||
|
Message to show Claude when this rule triggers.
|
||||||
|
Can include markdown formatting, warnings, suggestions, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontmatter Fields
|
||||||
|
|
||||||
|
**name** (required): Unique identifier for the rule
|
||||||
|
|
||||||
|
- Use kebab-case: `warn-dangerous-rm`, `block-console-log`
|
||||||
|
- Be descriptive and action-oriented
|
||||||
|
- Start with verb: warn, prevent, block, require, check
|
||||||
|
|
||||||
|
**enabled** (required): Boolean to activate/deactivate
|
||||||
|
|
||||||
|
- `true`: Rule is active
|
||||||
|
- `false`: Rule is disabled (won't trigger)
|
||||||
|
- Can toggle without deleting rule
|
||||||
|
|
||||||
|
**event** (required): Which hook event to trigger on
|
||||||
|
|
||||||
|
- `bash`: Bash tool commands
|
||||||
|
- `file`: Edit, Write, MultiEdit tools
|
||||||
|
- `stop`: When agent wants to stop
|
||||||
|
- `prompt`: When user submits a prompt
|
||||||
|
- `all`: All events
|
||||||
|
|
||||||
|
**action** (optional): What to do when rule matches
|
||||||
|
|
||||||
|
- `warn`: Show message but allow operation (default)
|
||||||
|
- `block`: Prevent operation (PreToolUse) or stop session (Stop events)
|
||||||
|
- If omitted, defaults to `warn`
|
||||||
|
|
||||||
|
**pattern** (simple format): Regex pattern to match
|
||||||
|
|
||||||
|
- Used for simple single-condition rules
|
||||||
|
- Matches against command (bash) or new_text (file)
|
||||||
|
- Python regex syntax
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
event: bash
|
||||||
|
pattern: rm\s+-rf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Format (Multiple Conditions)
|
||||||
|
|
||||||
|
For complex rules with multiple conditions:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: warn-env-file-edits
|
||||||
|
enabled: true
|
||||||
|
event: file
|
||||||
|
conditions:
|
||||||
|
- field: file_path
|
||||||
|
operator: regex_match
|
||||||
|
pattern: \.env$
|
||||||
|
- field: new_text
|
||||||
|
operator: contains
|
||||||
|
pattern: API_KEY
|
||||||
|
---
|
||||||
|
|
||||||
|
You're adding an API key to a .env file. Ensure this file is in .gitignore!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Condition fields:**
|
||||||
|
|
||||||
|
- `field`: Which field to check
|
||||||
|
- For bash: `command`
|
||||||
|
- For file: `file_path`, `new_text`, `old_text`, `content`
|
||||||
|
- `operator`: How to match
|
||||||
|
- `regex_match`: Regex pattern matching
|
||||||
|
- `contains`: Substring check
|
||||||
|
- `equals`: Exact match
|
||||||
|
- `not_contains`: Substring must NOT be present
|
||||||
|
- `starts_with`: Prefix check
|
||||||
|
- `ends_with`: Suffix check
|
||||||
|
- `pattern`: Pattern or string to match
|
||||||
|
|
||||||
|
**All conditions must match for rule to trigger.**
|
||||||
|
|
||||||
|
## Message Body
|
||||||
|
|
||||||
|
The markdown content after frontmatter is shown to Claude when the rule triggers.
|
||||||
|
|
||||||
|
**Good messages:**
|
||||||
|
|
||||||
|
- Explain what was detected
|
||||||
|
- Explain why it's problematic
|
||||||
|
- Suggest alternatives or best practices
|
||||||
|
- Use formatting for clarity (bold, lists, etc.)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
⚠️ **Console.log detected!**
|
||||||
|
|
||||||
|
You're adding console.log to production code.
|
||||||
|
|
||||||
|
**Why this matters:**
|
||||||
|
- Debug logs shouldn't ship to production
|
||||||
|
- Console.log can expose sensitive data
|
||||||
|
- Impacts browser performance
|
||||||
|
|
||||||
|
**Alternatives:**
|
||||||
|
- Use a proper logging library
|
||||||
|
- Remove before committing
|
||||||
|
- Use conditional debug builds
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Type Guide
|
||||||
|
|
||||||
|
### bash Events
|
||||||
|
|
||||||
|
Match Bash command patterns:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
event: bash
|
||||||
|
pattern: sudo\s+|rm\s+-rf|chmod\s+777
|
||||||
|
---
|
||||||
|
|
||||||
|
Dangerous command detected!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common patterns:**
|
||||||
|
|
||||||
|
- Dangerous commands: `rm\s+-rf`, `dd\s+if=`, `mkfs`
|
||||||
|
- Privilege escalation: `sudo\s+`, `su\s+`
|
||||||
|
- Permission issues: `chmod\s+777`, `chown\s+root`
|
||||||
|
|
||||||
|
### file Events
|
||||||
|
|
||||||
|
Match Edit/Write/MultiEdit operations:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
event: file
|
||||||
|
pattern: console\.log\(|eval\(|innerHTML\s*=
|
||||||
|
---
|
||||||
|
|
||||||
|
Potentially problematic code pattern detected!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Match on different fields:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
event: file
|
||||||
|
conditions:
|
||||||
|
- field: file_path
|
||||||
|
operator: regex_match
|
||||||
|
pattern: \.tsx?$
|
||||||
|
- field: new_text
|
||||||
|
operator: regex_match
|
||||||
|
pattern: console\.log\(
|
||||||
|
---
|
||||||
|
|
||||||
|
Console.log in TypeScript file!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common patterns:**
|
||||||
|
|
||||||
|
- Debug code: `console\.log\(`, `debugger`, `print\(`
|
||||||
|
- Security risks: `eval\(`, `innerHTML\s*=`, `dangerouslySetInnerHTML`
|
||||||
|
- Sensitive files: `\.env$`, `credentials`, `\.pem$`
|
||||||
|
- Generated files: `node_modules/`, `dist/`, `build/`
|
||||||
|
|
||||||
|
### stop Events
|
||||||
|
|
||||||
|
Match when agent wants to stop (completion checks):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
event: stop
|
||||||
|
pattern: .*
|
||||||
|
---
|
||||||
|
|
||||||
|
Before stopping, verify:
|
||||||
|
- [ ] Tests were run
|
||||||
|
- [ ] Build succeeded
|
||||||
|
- [ ] Documentation updated
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use for:**
|
||||||
|
|
||||||
|
- Reminders about required steps
|
||||||
|
- Completion checklists
|
||||||
|
- Process enforcement
|
||||||
|
|
||||||
|
### prompt Events
|
||||||
|
|
||||||
|
Match user prompt content (advanced):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
event: prompt
|
||||||
|
conditions:
|
||||||
|
- field: user_prompt
|
||||||
|
operator: contains
|
||||||
|
pattern: deploy to production
|
||||||
|
---
|
||||||
|
|
||||||
|
Production deployment checklist:
|
||||||
|
- [ ] Tests passing?
|
||||||
|
- [ ] Reviewed by team?
|
||||||
|
- [ ] Monitoring ready?
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern Writing Tips
|
||||||
|
|
||||||
|
### Regex Basics
|
||||||
|
|
||||||
|
**Literal characters:** Most characters match themselves
|
||||||
|
|
||||||
|
- `rm` matches "rm"
|
||||||
|
- `console.log` matches "console.log"
|
||||||
|
|
||||||
|
**Special characters need escaping:**
|
||||||
|
|
||||||
|
- `.` (any char) → `\.` (literal dot)
|
||||||
|
- `(` `)` → `\(` `\)` (literal parens)
|
||||||
|
- `[` `]` → `\[` `\]` (literal brackets)
|
||||||
|
|
||||||
|
**Common metacharacters:**
|
||||||
|
|
||||||
|
- `\s` - whitespace (space, tab, newline)
|
||||||
|
- `\d` - digit (0-9)
|
||||||
|
- `\w` - word character (a-z, A-Z, 0-9, _)
|
||||||
|
- `.` - any character
|
||||||
|
- `+` - one or more
|
||||||
|
- `*` - zero or more
|
||||||
|
- `?` - zero or one
|
||||||
|
- `|` - OR
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
|
```text
|
||||||
|
rm\s+-rf Matches: rm -rf, rm -rf
|
||||||
|
console\.log\( Matches: console.log(
|
||||||
|
(eval|exec)\( Matches: eval( or exec(
|
||||||
|
chmod\s+777 Matches: chmod 777, chmod 777
|
||||||
|
API_KEY\s*= Matches: API_KEY=, API_KEY =
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Patterns
|
||||||
|
|
||||||
|
Test regex patterns before using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "import re; print(re.search(r'your_pattern', 'test text'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use online regex testers (regex101.com with Python flavor).
|
||||||
|
|
||||||
|
### Common Pitfalls
|
||||||
|
|
||||||
|
**Too broad:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pattern: log # Matches "log", "login", "dialog", "catalog"
|
||||||
|
```
|
||||||
|
|
||||||
|
Better: `console\.log\(|logger\.`
|
||||||
|
|
||||||
|
**Too specific:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
pattern: rm -rf /tmp # Only matches exact path
|
||||||
|
```
|
||||||
|
|
||||||
|
Better: `rm\s+-rf`
|
||||||
|
|
||||||
|
**Escaping issues:**
|
||||||
|
|
||||||
|
- YAML quoted strings: `"pattern"` requires double backslashes `\\s`
|
||||||
|
- YAML unquoted: `pattern: \s` works as-is
|
||||||
|
- **Recommendation**: Use unquoted patterns in YAML
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
**Location:** All rules in `.claude/` directory
|
||||||
|
**Naming:** `.claude/hookify.{descriptive-name}.local.md`
|
||||||
|
**Gitignore:** Add `.claude/*.local.md` to `.gitignore`
|
||||||
|
|
||||||
|
**Good names:**
|
||||||
|
|
||||||
|
- `hookify.dangerous-rm.local.md`
|
||||||
|
- `hookify.console-log.local.md`
|
||||||
|
- `hookify.require-tests.local.md`
|
||||||
|
- `hookify.sensitive-files.local.md`
|
||||||
|
|
||||||
|
**Bad names:**
|
||||||
|
|
||||||
|
- `hookify.rule1.local.md` (not descriptive)
|
||||||
|
- `hookify.md` (missing .local)
|
||||||
|
- `danger.local.md` (missing hookify prefix)
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Creating a Rule
|
||||||
|
|
||||||
|
1. Identify unwanted behavior
|
||||||
|
2. Determine which tool is involved (Bash, Edit, etc.)
|
||||||
|
3. Choose event type (bash, file, stop, etc.)
|
||||||
|
4. Write regex pattern
|
||||||
|
5. Create `.claude/hookify.{name}.local.md` file in project root
|
||||||
|
6. Test immediately - rules are read dynamically on next tool use
|
||||||
|
|
||||||
|
### Refining a Rule
|
||||||
|
|
||||||
|
1. Edit the `.local.md` file
|
||||||
|
2. Adjust pattern or message
|
||||||
|
3. Test immediately - changes take effect on next tool use
|
||||||
|
|
||||||
|
### Disabling a Rule
|
||||||
|
|
||||||
|
**Temporary:** Set `enabled: false` in frontmatter
|
||||||
|
**Permanent:** Delete the `.local.md` file
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See `${CLAUDE_PLUGIN_ROOT}/examples/` for complete examples:
|
||||||
|
|
||||||
|
- `dangerous-rm.local.md` - Block dangerous rm commands
|
||||||
|
- `console-log-warning.local.md` - Warn about console.log
|
||||||
|
- `sensitive-files-warning.local.md` - Warn about editing .env files
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
**Minimum viable rule:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: my-rule
|
||||||
|
enabled: true
|
||||||
|
event: bash
|
||||||
|
pattern: dangerous_command
|
||||||
|
---
|
||||||
|
|
||||||
|
Warning message here
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule with conditions:**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: my-rule
|
||||||
|
enabled: true
|
||||||
|
event: file
|
||||||
|
conditions:
|
||||||
|
- field: file_path
|
||||||
|
operator: regex_match
|
||||||
|
pattern: \.ts$
|
||||||
|
- field: new_text
|
||||||
|
operator: contains
|
||||||
|
pattern: any
|
||||||
|
---
|
||||||
|
|
||||||
|
Warning message
|
||||||
|
```
|
||||||
|
|
||||||
|
**Event types:**
|
||||||
|
|
||||||
|
- `bash` - Bash commands
|
||||||
|
- `file` - File edits
|
||||||
|
- `stop` - Completion checks
|
||||||
|
- `prompt` - User input
|
||||||
|
- `all` - All events
|
||||||
|
|
||||||
|
**Field options:**
|
||||||
|
|
||||||
|
- Bash: `command`
|
||||||
|
- File: `file_path`, `new_text`, `old_text`, `content`
|
||||||
|
- Prompt: `user_prompt`
|
||||||
|
|
||||||
|
**Operators:**
|
||||||
|
|
||||||
|
- `regex_match`, `contains`, `equals`, `not_contains`, `starts_with`, `ends_with`
|
||||||
Reference in New Issue
Block a user