mirror of
https://github.com/anthropics/claude-code.git
synced 2026-02-19 04:27:33 -08:00
Compare commits
60 Commits
claude/cre
...
daisy/ralp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59372c0921 | ||
|
|
68f90e05dd | ||
|
|
5dd91a9fe2 | ||
|
|
17945ae3d5 | ||
|
|
d594fd24b9 | ||
|
|
2b535344fc | ||
|
|
c91a6b660d | ||
|
|
0b4215d637 | ||
|
|
b85cc4474f | ||
|
|
fc3c2c26e0 | ||
|
|
dd65bc4d16 | ||
|
|
dfd715012f | ||
|
|
62c3cbc471 | ||
|
|
556d296786 | ||
|
|
8a0bfd3687 | ||
|
|
5d66745e78 | ||
|
|
18043d7474 | ||
|
|
d38bde5087 | ||
|
|
970fff49e2 | ||
|
|
2d0fcacc05 | ||
|
|
f09b24c49a | ||
|
|
1fe9e369a7 | ||
|
|
b95fa46499 | ||
|
|
7a05427a4b | ||
|
|
3af8ef29be | ||
|
|
84b97165dd | ||
|
|
07dcea57ee | ||
|
|
1e95326e12 | ||
|
|
b42fd9928c | ||
|
|
128de2a75d | ||
|
|
b8a98a8df7 | ||
|
|
ba49573fe1 | ||
|
|
015808d89c | ||
|
|
ae411f8461 | ||
|
|
4310085cb5 | ||
|
|
c509821adc | ||
|
|
d9aa4cf649 | ||
|
|
b935da77db | ||
|
|
0c7d02b56f | ||
|
|
8b47e224a0 | ||
|
|
21bbc9f250 | ||
|
|
7add6863a0 | ||
|
|
5484a86d28 | ||
|
|
10e1d3fe77 | ||
|
|
4dc23d0275 | ||
|
|
8077cdc68c | ||
|
|
207b22de65 | ||
|
|
52fea66ba5 | ||
|
|
4e417747c5 | ||
|
|
1b41969c71 | ||
|
|
e9af4d7c1d | ||
|
|
48a8bfc2b1 | ||
|
|
546f0b46ac | ||
|
|
3be7215354 | ||
|
|
5d0e5cf15f | ||
|
|
71bb75e3b5 | ||
|
|
1b827ad951 | ||
|
|
113ea425ac | ||
|
|
70cb0d1016 | ||
|
|
ff0aafa946 |
@@ -57,6 +57,72 @@
|
||||
},
|
||||
"source": "./plugins/security-guidance",
|
||||
"category": "security"
|
||||
},
|
||||
{
|
||||
"name": "code-review",
|
||||
"description": "Automated code review for pull requests using multiple specialized agents with confidence-based scoring to filter false positives",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Boris Cherny",
|
||||
"email": "boris@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/code-review",
|
||||
"category": "productivity"
|
||||
},
|
||||
{
|
||||
"name": "explanatory-output-style",
|
||||
"description": "Adds educational insights about implementation choices and codebase patterns (mimics the deprecated Explanatory output style)",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Dickson Tsai",
|
||||
"email": "dickson@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/explanatory-output-style",
|
||||
"category": "learning"
|
||||
},
|
||||
{
|
||||
"name": "learning-output-style",
|
||||
"description": "Interactive learning mode that requests meaningful code contributions at decision points (mimics the unshipped Learning output style)",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Boris Cherny",
|
||||
"email": "boris@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/learning-output-style",
|
||||
"category": "learning"
|
||||
},
|
||||
{
|
||||
"name": "frontend-design",
|
||||
"description": "Create distinctive, production-grade frontend interfaces with high design quality. Generates creative, polished code that avoids generic AI aesthetics.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Prithvi Rajasekaran & Alexander Bricken",
|
||||
"email": "prithvi@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/frontend-design",
|
||||
"category": "development"
|
||||
},
|
||||
{
|
||||
"name": "ralph-wiggum",
|
||||
"description": "Interactive self-referential AI loops for iterative development. Claude works on the same task repeatedly, seeing its previous work, until completion.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Daisy Hollman",
|
||||
"email": "daisy@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/ralph-wiggum",
|
||||
"category": "development"
|
||||
},
|
||||
{
|
||||
"name": "hookify",
|
||||
"description": "Easily create custom hooks to prevent unwanted behaviors by analyzing conversation patterns or from explicit instructions. Define rules via simple markdown files.",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Daisy Hollman",
|
||||
"email": "daisy@anthropic.com"
|
||||
},
|
||||
"source": "./plugins/hookify",
|
||||
"category": "productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
40
.claude/commands/oncall-triage.md
Normal file
40
.claude/commands/oncall-triage.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue list:*), Bash(gh issue view:*), Bash(gh issue edit:*), TodoWrite
|
||||
description: Triage GitHub issues and label critical ones for oncall
|
||||
---
|
||||
|
||||
You're an oncall triage assistant for GitHub issues. Your task is to identify critical issues that require immediate oncall attention and apply the "oncall" label.
|
||||
|
||||
Repository: anthropics/claude-code
|
||||
|
||||
Task overview:
|
||||
|
||||
1. First, get all open bugs updated in the last 3 days with at least 50 engagements:
|
||||
```bash
|
||||
gh issue list --repo anthropics/claude-code --state open --label bug --limit 1000 --json number,title,updatedAt,comments,reactions | jq -r '.[] | select((.updatedAt >= (now - 259200 | strftime("%Y-%m-%dT%H:%M:%SZ"))) and ((.comments | length) + ([.reactions[].content] | length) >= 50)) | "\(.number)"'
|
||||
```
|
||||
|
||||
2. Save the list of issue numbers and create a TODO list with ALL of them. This ensures you process every single one.
|
||||
|
||||
3. For each issue in your TODO list:
|
||||
- Use `gh issue view <number> --repo anthropics/claude-code --json title,body,labels,comments` to get full details
|
||||
- Read and understand the full issue content and comments to determine actual user impact
|
||||
- Evaluate: Is this truly blocking users from using Claude Code?
|
||||
- Consider: "crash", "stuck", "frozen", "hang", "unresponsive", "cannot use", "blocked", "broken"
|
||||
- Does it prevent core functionality? Can users work around it?
|
||||
- Be conservative - only flag issues that truly prevent users from getting work done
|
||||
|
||||
4. For issues that are truly blocking and don't already have the "oncall" label:
|
||||
- Use `gh issue edit <number> --repo anthropics/claude-code --add-label "oncall"`
|
||||
- Mark the issue as complete in your TODO list
|
||||
|
||||
5. After processing all issues, provide a summary:
|
||||
- List each issue number that received the "oncall" label
|
||||
- Include the issue title and brief reason why it qualified
|
||||
- If no issues qualified, state that clearly
|
||||
|
||||
Important:
|
||||
- Process ALL issues in your TODO list systematically
|
||||
- Don't post any comments to issues
|
||||
- Only add the "oncall" label, never remove it
|
||||
- Use individual `gh issue view` commands instead of bash for loops to avoid approval prompts
|
||||
1
.github/workflows/claude-dedupe-issues.yml
vendored
1
.github/workflows/claude-dedupe-issues.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
||||
with:
|
||||
prompt: "/dedupe ${{ github.repository }}/issues/${{ github.event.issue.number || inputs.issue_number }}"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_args: "--model claude-sonnet-4-5-20250929"
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
1
.github/workflows/claude-issue-triage.yml
vendored
1
.github/workflows/claude-issue-triage.yml
vendored
@@ -102,5 +102,6 @@ jobs:
|
||||
timeout_minutes: "5"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
mcp_config: /tmp/mcp-config/mcp-servers.json
|
||||
claude_args: "--model claude-sonnet-4-5-20250929"
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
1
.github/workflows/claude.yml
vendored
1
.github/workflows/claude.yml
vendored
@@ -34,4 +34,5 @@ jobs:
|
||||
uses: anthropics/claude-code-action@beta
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
claude_args: "--model claude-sonnet-4-5-20250929"
|
||||
|
||||
|
||||
120
.github/workflows/oncall-triage.yml
vendored
Normal file
120
.github/workflows/oncall-triage.yml
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
name: Oncall Issue Triage
|
||||
description: Automatically identify and label critical blocking issues requiring oncall attention
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- add-oncall-triage-workflow # Temporary: for testing only
|
||||
schedule:
|
||||
# Run every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
oncall-triage:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create oncall triage prompt
|
||||
run: |
|
||||
mkdir -p /tmp/claude-prompts
|
||||
cat > /tmp/claude-prompts/oncall-triage-prompt.txt << 'EOF'
|
||||
You're an oncall triage assistant for GitHub issues. Your task is to identify critical issues that require immediate oncall attention.
|
||||
|
||||
Important: Don't post any comments or messages to the issues. Your only action should be to apply the "oncall" label to qualifying issues.
|
||||
|
||||
Repository: ${{ github.repository }}
|
||||
|
||||
Task overview:
|
||||
1. Fetch all open issues updated in the last 3 days:
|
||||
- Use mcp__github__list_issues with:
|
||||
- state="open"
|
||||
- first=5 (fetch only 5 issues per page)
|
||||
- orderBy="UPDATED_AT"
|
||||
- direction="DESC"
|
||||
- This will give you the most recently updated issues first
|
||||
- For each page of results, check the updatedAt timestamp of each issue
|
||||
- Add issues updated within the last 3 days (72 hours) to your TODO list as you go
|
||||
- Keep paginating using the 'after' parameter until you encounter issues older than 3 days
|
||||
- Once you hit issues older than 3 days, you can stop fetching (no need to fetch all open issues)
|
||||
|
||||
2. Build your TODO list incrementally as you fetch:
|
||||
- As you fetch each page, immediately add qualifying issues to your TODO list
|
||||
- One TODO item per issue number (e.g., "Evaluate issue #123")
|
||||
- This allows you to start processing while still fetching more pages
|
||||
|
||||
3. For each issue in your TODO list:
|
||||
- Use mcp__github__get_issue to read the issue details (title, body, labels)
|
||||
- Use mcp__github__get_issue_comments to read all comments
|
||||
- Evaluate whether this issue needs the oncall label:
|
||||
a) Is it a bug? (has "bug" label or describes bug behavior)
|
||||
b) Does it have at least 50 engagements? (count comments + reactions)
|
||||
c) Is it truly blocking? Read and understand the full content to determine:
|
||||
- Does this prevent core functionality from working?
|
||||
- Can users work around it?
|
||||
- Consider severity indicators: "crash", "stuck", "frozen", "hang", "unresponsive", "cannot use", "blocked", "broken"
|
||||
- Be conservative - only flag issues that truly prevent users from getting work done
|
||||
|
||||
4. For issues that meet all criteria and do not already have the "oncall" label:
|
||||
- Use mcp__github__update_issue to add the "oncall" label
|
||||
- Do not post any comments
|
||||
- Do not remove any existing labels
|
||||
- Do not remove the "oncall" label from issues that already have it
|
||||
|
||||
Important guidelines:
|
||||
- Use the TODO list to track your progress through ALL candidate issues
|
||||
- Process issues efficiently - don't read every single issue upfront, work through your TODO list systematically
|
||||
- Be conservative in your assessment - only flag truly critical blocking issues
|
||||
- Do not post any comments to issues
|
||||
- Your only action should be to add the "oncall" label using mcp__github__update_issue
|
||||
- Mark each issue as complete in your TODO list as you process it
|
||||
|
||||
7. After processing all issues in your TODO list, provide a summary of your actions:
|
||||
- Total number of issues processed (candidate issues evaluated)
|
||||
- Number of issues that received the "oncall" label
|
||||
- For each issue that got the label: list issue number, title, and brief reason why it qualified
|
||||
- Close calls: List any issues that almost qualified but didn't quite meet the criteria (e.g., borderline blocking, had workarounds)
|
||||
- If no issues qualified, state that clearly
|
||||
- Format the summary clearly for easy reading
|
||||
EOF
|
||||
|
||||
- name: Setup GitHub MCP Server
|
||||
run: |
|
||||
mkdir -p /tmp/mcp-config
|
||||
cat > /tmp/mcp-config/mcp-servers.json << 'EOF'
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"-i",
|
||||
"--rm",
|
||||
"-e",
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||
"ghcr.io/github/github-mcp-server:sha-7aced2b"
|
||||
],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.GITHUB_TOKEN }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Run Claude Code for Oncall Triage
|
||||
uses: anthropics/claude-code-base-action@beta
|
||||
with:
|
||||
prompt_file: /tmp/claude-prompts/oncall-triage-prompt.txt
|
||||
allowed_tools: "mcp__github__list_issues,mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue"
|
||||
timeout_minutes: "10"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
mcp_config: /tmp/mcp-config/mcp-servers.json
|
||||
claude_env: |
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
|
||||
118
CHANGELOG.md
118
CHANGELOG.md
@@ -1,5 +1,123 @@
|
||||
# Changelog
|
||||
|
||||
## 2.0.42
|
||||
|
||||
- Added `agent_id` and `agent_transcript_path` fields to `SubagentStop` hooks.
|
||||
|
||||
## 2.0.41
|
||||
|
||||
- Added `model` parameter to prompt-based stop hooks, allowing users to specify a custom model for hook evaluation
|
||||
- Fixed slash commands from user settings being loaded twice, which could cause rendering issues
|
||||
- Fixed incorrect labeling of user settings vs project settings in command descriptions
|
||||
- Fixed crash when plugin command hooks timeout during execution
|
||||
- Fixed: Bedrock users no longer see duplicate Opus entries in the /model picker when using `--model haiku`
|
||||
- Fixed broken security documentation links in trust dialogs and onboarding
|
||||
- Fixed issue where pressing ESC to close the diff modal would also interrupt the model
|
||||
- ctrl-r history search landing on a slash command no longer cancels the search
|
||||
- SDK: Support custom timeouts for hooks
|
||||
- Allow more safe git commands to run without approval
|
||||
- Plugins: Added support for sharing and installing output styles
|
||||
- Teleporting a session from web will automatically set the upstream branch
|
||||
|
||||
## 2.0.37
|
||||
|
||||
- Fixed how idleness is computed for notifications
|
||||
- Hooks: Added matcher values for Notification hook events
|
||||
- Output Styles: Added `keep-coding-instructions` option to frontmatter
|
||||
|
||||
## 2.0.36
|
||||
|
||||
- Fixed: DISABLE_AUTOUPDATER environment variable now properly disables package manager update notifications
|
||||
- Fixed queued messages being incorrectly executed as bash commands
|
||||
- Fixed input being lost when typing while a queued message is processed
|
||||
|
||||
## 2.0.35
|
||||
|
||||
- Improve fuzzy search results when searching commands
|
||||
- Improved VS Code extension to respect `chat.fontSize` and `chat.fontFamily` settings throughout the entire UI, and apply font changes immediately without requiring reload
|
||||
- Added `CLAUDE_CODE_EXIT_AFTER_STOP_DELAY` environment variable to automatically exit SDK mode after a specified idle duration, useful for automated workflows and scripts
|
||||
- Migrated `ignorePatterns` from project config to deny permissions in the localSettings.
|
||||
- Fixed messages returning null `stop_reason` and `stop_sequence` values
|
||||
- Fixed menu navigation getting stuck on items with empty string or other falsy values (e.g., in the `/hooks` menu)
|
||||
|
||||
## 2.0.34
|
||||
|
||||
- VSCode Extension: Added setting to configure the initial permission mode for new conversations
|
||||
- Improved file path suggestion performance with native Rust-based fuzzy finder
|
||||
- Fixed infinite token refresh loop that caused MCP servers with OAuth (e.g., Slack) to hang during connection
|
||||
- Fixed memory crash when reading or writing large files (especially base64-encoded images)
|
||||
|
||||
## 2.0.33
|
||||
|
||||
- Native binary installs now launch quicker.
|
||||
- Fixed `claude doctor` incorrectly detecting Homebrew vs npm-global installations by properly resolving symlinks
|
||||
- Fixed `claude mcp serve` exposing tools with incompatible outputSchemas
|
||||
|
||||
## 2.0.32
|
||||
|
||||
- Un-deprecate output styles based on community feedback
|
||||
- Added `companyAnnouncements` setting for displaying announcements on startup
|
||||
- Fixed hook progress messages not updating correctly during PostToolUse hook execution
|
||||
|
||||
## 2.0.31
|
||||
|
||||
- Windows: native installation uses shift+tab as shortcut for mode switching, instead of alt+m
|
||||
- Vertex: add support for Web Search on supported models
|
||||
- VSCode: Adding the respectGitIgnore configuration to include .gitignored files in file searches (defaults to true)
|
||||
- Fixed a bug with subagents and MCP servers related to "Tool names must be unique" error
|
||||
- Fixed issue causing `/compact` to fail with `prompt_too_long` by making it respect existing compact boundaries
|
||||
- Fixed plugin uninstall not removing plugins
|
||||
|
||||
## 2.0.30
|
||||
|
||||
- Added helpful hint to run `security unlock-keychain` when encountering API key errors on macOS with locked keychain
|
||||
- Added `allowUnsandboxedCommands` sandbox setting to disable the dangerouslyDisableSandbox escape hatch at policy level
|
||||
- Added `disallowedTools` field to custom agent definitions for explicit tool blocking
|
||||
- Added prompt-based stop hooks
|
||||
- VSCode: Added respectGitIgnore configuration to include .gitignored files in file searches (defaults to true)
|
||||
- Enabled SSE MCP servers on native build
|
||||
- Deprecated output styles. Review options in `/output-style` and use --system-prompt-file, --system-prompt, --append-system-prompt, CLAUDE.md, or plugins instead
|
||||
- Removed support for custom ripgrep configuration, resolving an issue where Search returns no results and config discovery fails
|
||||
- Fixed Explore agent creating unwanted .md investigation files during codebase exploration
|
||||
- Fixed a bug where `/context` would sometimes fail with "max_tokens must be greater than thinking.budget_tokens" error message
|
||||
- Fixed `--mcp-config` flag to correctly override file-based MCP configurations
|
||||
- Fixed bug that saved session permissions to local settings
|
||||
- Fixed MCP tools not being available to sub-agents
|
||||
- Fixed hooks and plugins not executing when using --dangerously-skip-permissions flag
|
||||
- Fixed delay when navigating through typeahead suggestions with arrow keys
|
||||
- VSCode: Restored selection indicator in input footer showing current file or code selection status
|
||||
|
||||
## 2.0.28
|
||||
|
||||
- Plan mode: introduced new Plan subagent
|
||||
- Subagents: claude can now choose to resume subagents
|
||||
- Subagents: claude can dynamically choose the model used by its subagents
|
||||
- SDK: added --max-budget-usd flag
|
||||
- Discovery of custom slash commands, subagents, and output styles no longer respects .gitignore
|
||||
- Stop `/terminal-setup` from adding backslash to `Shift + Enter` in VS Code
|
||||
- Add branch and tag support for git-based plugins and marketplaces using fragment syntax (e.g., `owner/repo#branch`)
|
||||
- Fixed a bug where macOS permission prompts would show up upon initial launch when launching from home directory
|
||||
- Various other bug fixes
|
||||
|
||||
## 2.0.27
|
||||
|
||||
- New UI for permission prompts
|
||||
- Added current branch filtering and search to session resume screen for easier navigation
|
||||
- Fixed directory @-mention causing "No assistant message found" error
|
||||
- VSCode Extension: Add config setting to include .gitignored files in file searches
|
||||
- VSCode Extension: Bug fixes for unrelated 'Warmup' conversations, and configuration/settings occasionally being reset to defaults
|
||||
|
||||
## 2.0.25
|
||||
|
||||
- Removed legacy SDK entrypoint. Please migrate to @anthropic-ai/claude-agent-sdk for future SDK updates: https://docs.claude.com/en/docs/claude-code/sdk/migration-guide
|
||||
|
||||
## 2.0.24
|
||||
|
||||
- Fixed a bug where project-level skills were not loading when --setting-sources 'project' was specified
|
||||
- Claude Code Web: Support for Web -> CLI teleport
|
||||
- Sandbox: Releasing a sandbox mode for the BashTool on Linux & Mac
|
||||
- Bedrock: Display awsAuthRefresh output when auth is required
|
||||
|
||||
## 2.0.22
|
||||
|
||||
- Fixed content layout shift when scrolling through slash commands
|
||||
|
||||
20
README.md
20
README.md
@@ -14,10 +14,28 @@ Claude Code is an agentic coding tool that lives in your terminal, understands y
|
||||
|
||||
1. Install Claude Code:
|
||||
|
||||
```sh
|
||||
**MacOS/Linux:**
|
||||
```bash
|
||||
curl -fsSL https://claude.ai/install.sh | bash
|
||||
```
|
||||
|
||||
**Homebrew (MacOS):**
|
||||
```bash
|
||||
brew install --cask claude-code
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```powershell
|
||||
irm https://claude.ai/install.ps1 | iex
|
||||
```
|
||||
|
||||
**NPM:**
|
||||
```bash
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
```
|
||||
|
||||
NOTE: If installing with NPM, you also need to install [Node.js 18+](https://nodejs.org/en/download/)
|
||||
|
||||
2. Navigate to your project directory and run `claude`.
|
||||
|
||||
## Plugins
|
||||
|
||||
@@ -32,6 +32,16 @@ Simplifies common git operations with streamlined commands for committing, pushi
|
||||
- `/clean_gone` - Clean up stale local branches marked as [gone]
|
||||
- **Use case**: Faster git workflows with less context switching
|
||||
|
||||
### [code-review](./code-review/)
|
||||
|
||||
**Automated Pull Request Code Review Plugin**
|
||||
|
||||
Provides automated code review for pull requests using multiple specialized agents with confidence-based scoring to filter false positives.
|
||||
|
||||
- **Command**:
|
||||
- `/code-review` - Automated PR review workflow
|
||||
- **Use case**: Automated code review on pull requests with high-confidence issue detection (threshold ≥80)
|
||||
|
||||
### [feature-dev](./feature-dev/)
|
||||
|
||||
**Comprehensive Feature Development Workflow Plugin**
|
||||
|
||||
10
plugins/code-review/.claude-plugin/plugin.json
Normal file
10
plugins/code-review/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "code-review",
|
||||
"description": "Automated code review for pull requests using multiple specialized agents with confidence-based scoring",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Boris Cherny",
|
||||
"email": "boris@anthropic.com"
|
||||
}
|
||||
}
|
||||
|
||||
246
plugins/code-review/README.md
Normal file
246
plugins/code-review/README.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Code Review Plugin
|
||||
|
||||
Automated code review for pull requests using multiple specialized agents with confidence-based scoring to filter false positives.
|
||||
|
||||
## Overview
|
||||
|
||||
The Code Review Plugin automates pull request review by launching multiple agents in parallel to independently audit changes from different perspectives. It uses confidence scoring to filter out false positives, ensuring only high-quality, actionable feedback is posted.
|
||||
|
||||
## Commands
|
||||
|
||||
### `/code-review`
|
||||
|
||||
Performs automated code review on a pull request using multiple specialized agents.
|
||||
|
||||
**What it does:**
|
||||
1. Checks if review is needed (skips closed, draft, trivial, or already-reviewed PRs)
|
||||
2. Gathers relevant CLAUDE.md guideline files from the repository
|
||||
3. Summarizes the pull request changes
|
||||
4. Launches 4 parallel agents to independently review:
|
||||
- **Agents #1 & #2**: Audit for CLAUDE.md compliance
|
||||
- **Agent #3**: Scan for obvious bugs in changes
|
||||
- **Agent #4**: Analyze git blame/history for context-based issues
|
||||
5. Scores each issue 0-100 for confidence level
|
||||
6. Filters out issues below 80 confidence threshold
|
||||
7. Posts review comment with high-confidence issues only
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
/code-review
|
||||
```
|
||||
|
||||
**Example workflow:**
|
||||
```bash
|
||||
# On a PR branch, run:
|
||||
/code-review
|
||||
|
||||
# Claude will:
|
||||
# - Launch 4 review agents in parallel
|
||||
# - Score each issue for confidence
|
||||
# - Post comment with issues ≥80 confidence
|
||||
# - Skip posting if no high-confidence issues found
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Multiple independent agents for comprehensive review
|
||||
- Confidence-based scoring reduces false positives (threshold: 80)
|
||||
- CLAUDE.md compliance checking with explicit guideline verification
|
||||
- Bug detection focused on changes (not pre-existing issues)
|
||||
- Historical context analysis via git blame
|
||||
- Automatic skipping of closed, draft, or already-reviewed PRs
|
||||
- Links directly to code with full SHA and line ranges
|
||||
|
||||
**Review comment format:**
|
||||
```markdown
|
||||
## Code review
|
||||
|
||||
Found 3 issues:
|
||||
|
||||
1. Missing error handling for OAuth callback (CLAUDE.md says "Always handle OAuth errors")
|
||||
|
||||
https://github.com/owner/repo/blob/abc123.../src/auth.ts#L67-L72
|
||||
|
||||
2. Memory leak: OAuth state not cleaned up (bug due to missing cleanup in finally block)
|
||||
|
||||
https://github.com/owner/repo/blob/abc123.../src/auth.ts#L88-L95
|
||||
|
||||
3. Inconsistent naming pattern (src/conventions/CLAUDE.md says "Use camelCase for functions")
|
||||
|
||||
https://github.com/owner/repo/blob/abc123.../src/utils.ts#L23-L28
|
||||
```
|
||||
|
||||
**Confidence scoring:**
|
||||
- **0**: Not confident, false positive
|
||||
- **25**: Somewhat confident, might be real
|
||||
- **50**: Moderately confident, real but minor
|
||||
- **75**: Highly confident, real and important
|
||||
- **100**: Absolutely certain, definitely real
|
||||
|
||||
**False positives filtered:**
|
||||
- Pre-existing issues not introduced in PR
|
||||
- Code that looks like a bug but isn't
|
||||
- Pedantic nitpicks
|
||||
- Issues linters will catch
|
||||
- General quality issues (unless in CLAUDE.md)
|
||||
- Issues with lint ignore comments
|
||||
|
||||
## Installation
|
||||
|
||||
This plugin is included in the Claude Code repository. The command is automatically available when using Claude Code.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Using `/code-review`
|
||||
- Maintain clear CLAUDE.md files for better compliance checking
|
||||
- Trust the 80+ confidence threshold - false positives are filtered
|
||||
- Run on all non-trivial pull requests
|
||||
- Review agent findings as a starting point for human review
|
||||
- Update CLAUDE.md based on recurring review patterns
|
||||
|
||||
### When to use
|
||||
- All pull requests with meaningful changes
|
||||
- PRs touching critical code paths
|
||||
- PRs from multiple contributors
|
||||
- PRs where guideline compliance matters
|
||||
|
||||
### When not to use
|
||||
- Closed or draft PRs (automatically skipped anyway)
|
||||
- Trivial automated PRs (automatically skipped)
|
||||
- Urgent hotfixes requiring immediate merge
|
||||
- PRs already reviewed (automatically skipped)
|
||||
|
||||
## Workflow Integration
|
||||
|
||||
### Standard PR review workflow:
|
||||
```bash
|
||||
# Create PR with changes
|
||||
/code-review
|
||||
|
||||
# Review the automated feedback
|
||||
# Make any necessary fixes
|
||||
# Merge when ready
|
||||
```
|
||||
|
||||
### As part of CI/CD:
|
||||
```bash
|
||||
# Trigger on PR creation or update
|
||||
# Automatically posts review comments
|
||||
# Skip if review already exists
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Git repository with GitHub integration
|
||||
- GitHub CLI (`gh`) installed and authenticated
|
||||
- CLAUDE.md files (optional but recommended for guideline checking)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Review takes too long
|
||||
|
||||
**Issue**: Agents are slow on large PRs
|
||||
|
||||
**Solution**:
|
||||
- Normal for large changes - agents run in parallel
|
||||
- 4 independent agents ensure thoroughness
|
||||
- Consider splitting large PRs into smaller ones
|
||||
|
||||
### Too many false positives
|
||||
|
||||
**Issue**: Review flags issues that aren't real
|
||||
|
||||
**Solution**:
|
||||
- Default threshold is 80 (already filters most false positives)
|
||||
- Make CLAUDE.md more specific about what matters
|
||||
- Consider if the flagged issue is actually valid
|
||||
|
||||
### No review comment posted
|
||||
|
||||
**Issue**: `/code-review` runs but no comment appears
|
||||
|
||||
**Solution**:
|
||||
Check if:
|
||||
- PR is closed (reviews skipped)
|
||||
- PR is draft (reviews skipped)
|
||||
- PR is trivial/automated (reviews skipped)
|
||||
- PR already has review (reviews skipped)
|
||||
- No issues scored ≥80 (no comment needed)
|
||||
|
||||
### Link formatting broken
|
||||
|
||||
**Issue**: Code links don't render correctly in GitHub
|
||||
|
||||
**Solution**:
|
||||
Links must follow this exact format:
|
||||
```
|
||||
https://github.com/owner/repo/blob/[full-sha]/path/file.ext#L[start]-L[end]
|
||||
```
|
||||
- Must use full SHA (not abbreviated)
|
||||
- Must use `#L` notation
|
||||
- Must include line range with at least 1 line of context
|
||||
|
||||
### GitHub CLI not working
|
||||
|
||||
**Issue**: `gh` commands fail
|
||||
|
||||
**Solution**:
|
||||
- Install GitHub CLI: `brew install gh` (macOS) or see [GitHub CLI installation](https://cli.github.com/)
|
||||
- Authenticate: `gh auth login`
|
||||
- Verify repository has GitHub remote
|
||||
|
||||
## Tips
|
||||
|
||||
- **Write specific CLAUDE.md files**: Clear guidelines = better reviews
|
||||
- **Include context in PRs**: Helps agents understand intent
|
||||
- **Use confidence scores**: Issues ≥80 are usually correct
|
||||
- **Iterate on guidelines**: Update CLAUDE.md based on patterns
|
||||
- **Review automatically**: Set up as part of PR workflow
|
||||
- **Trust the filtering**: Threshold prevents noise
|
||||
|
||||
## Configuration
|
||||
|
||||
### Adjusting confidence threshold
|
||||
|
||||
The default threshold is 80. To adjust, modify the command file at `commands/code-review.md`:
|
||||
```markdown
|
||||
Filter out any issues with a score less than 80.
|
||||
```
|
||||
|
||||
Change `80` to your preferred threshold (0-100).
|
||||
|
||||
### Customizing review focus
|
||||
|
||||
Edit `commands/code-review.md` to add or modify agent tasks:
|
||||
- Add security-focused agents
|
||||
- Add performance analysis agents
|
||||
- Add accessibility checking agents
|
||||
- Add documentation quality checks
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Agent architecture
|
||||
- **2x CLAUDE.md compliance agents**: Redundancy for guideline checks
|
||||
- **1x bug detector**: Focused on obvious bugs in changes only
|
||||
- **1x history analyzer**: Context from git blame and history
|
||||
- **Nx confidence scorers**: One per issue for independent scoring
|
||||
|
||||
### Scoring system
|
||||
- Each issue independently scored 0-100
|
||||
- Scoring considers evidence strength and verification
|
||||
- Threshold (default 80) filters low-confidence issues
|
||||
- For CLAUDE.md issues: verifies guideline explicitly mentions it
|
||||
|
||||
### GitHub integration
|
||||
Uses `gh` CLI for:
|
||||
- Viewing PR details and diffs
|
||||
- Fetching repository data
|
||||
- Reading git blame and history
|
||||
- Posting review comments
|
||||
|
||||
## Author
|
||||
|
||||
Boris Cherny (boris@anthropic.com)
|
||||
|
||||
## Version
|
||||
|
||||
1.0.0
|
||||
92
plugins/code-review/commands/code-review.md
Normal file
92
plugins/code-review/commands/code-review.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)
|
||||
description: Code review a pull request
|
||||
disable-model-invocation: false
|
||||
---
|
||||
|
||||
Provide a code review for the given pull request.
|
||||
|
||||
To do this, follow these steps precisely:
|
||||
|
||||
1. Use a Haiku agent to check if the pull request (a) is closed, (b) is a draft, (c) does not need a code review (eg. because it is an automated pull request, or is very simple and obviously ok), or (d) already has a code review from you from earlier. If so, do not proceed.
|
||||
2. Use another Haiku agent to give you a list of file paths to (but not the contents of) any relevant CLAUDE.md files from the codebase: the root CLAUDE.md file (if one exists), as well as any CLAUDE.md files in the directories whose files the pull request modified
|
||||
3. Use a Haiku agent to view the pull request, and ask the agent to return a summary of the change
|
||||
4. Then, launch 5 parallel Sonnet agents to independently code review the change. The agents should do the following, then return a list of issues and the reason each issue was flagged (eg. CLAUDE.md adherence, bug, historical git context, etc.):
|
||||
a. Agent #1: Audit the changes to make sure they compily with the CLAUDE.md. Note that CLAUDE.md is guidance for Claude as it writes code, so not all instructions will be applicable during code review.
|
||||
b. Agent #2: Read the file changes in the pull request, then do a shallow scan for obvious bugs. Avoid reading extra context beyond the changes, focusing just on the changes themselves. Focus on large bugs, and avoid small issues and nitpicks. Ignore likely false positives.
|
||||
c. Agent #3: Read the git blame and history of the code modified, to identify any bugs in light of that historical context
|
||||
d. Agent #4: Read previous pull requests that touched these files, and check for any comments on those pull requests that may also apply to the current pull request.
|
||||
e. Agent #5: Read code comments in the modified files, and make sure the changes in the pull request comply with any guidance in the comments.
|
||||
5. For each issue found in #4, launch a parallel Haiku agent that takes the PR, issue description, and list of CLAUDE.md files (from step 2), and returns a score to indicate the agent's level of confidence for whether the issue is real or false positive. To do that, the agent should score each issue on a scale from 0-100, indicating its level of confidence. For issues that were flagged due to CLAUDE.md instructions, the agent should double check that the CLAUDE.md actually calls out that issue specifically. The scale is (give this rubric to the agent verbatim):
|
||||
a. 0: Not confident at all. This is a false positive that doesn't stand up to light scrutiny, or is a pre-existing issue.
|
||||
b. 25: Somewhat confident. This might be a real issue, but may also be a false positive. The agent wasn't able to verify that it's a real issue. If the issue is stylistic, it is one that was not explicitly called out in the relevant CLAUDE.md.
|
||||
c. 50: Moderately confident. The agent was able to verify this is a real issue, but it might be a nitpick or not happen very often in practice. Relative to the rest of the PR, it's not very important.
|
||||
d. 75: Highly confident. The agent double checked the issue, and verified that it is very likely it is a real issue that will be hit in practice. The existing approach in the PR is insufficient. The issue is very important and will directly impact the code's functionality, or it is an issue that is directly mentioned in the relevant CLAUDE.md.
|
||||
e. 100: Absolutely certain. The agent double checked the issue, and confirmed that it is definitely a real issue, that will happen frequently in practice. The evidence directly confirms this.
|
||||
6. Filter out any issues with a score less than 80. If there are no issues that meet this criteria, do not proceed.
|
||||
7. Use a Haiku agent to repeat the eligibility check from #1, to make sure that the pull request is still eligible for code review.
|
||||
8. Finally, use the gh bash command to comment back on the pull request with the result. When writing your comment, keep in mind to:
|
||||
a. Keep your output brief
|
||||
b. Avoid emojis
|
||||
c. Link and cite relevant code, files, and URLs
|
||||
|
||||
Examples of false positives, for steps 4 and 5:
|
||||
|
||||
- Pre-existing issues
|
||||
- Something that looks like a bug but is not actually a bug
|
||||
- Pedantic nitpicks that a senior engineer wouldn't call out
|
||||
- Issues that a linter, typechecker, or compiler would catch (eg. missing or incorrect imports, type errors, broken tests, formatting issues, pedantic style issues like newlines). No need to run these build steps yourself -- it is safe to assume that they will be run separately as part of CI.
|
||||
- General code quality issues (eg. lack of test coverage, general security issues, poor documentation), unless explicitly required in CLAUDE.md
|
||||
- Issues that are called out in CLAUDE.md, but explicitly silenced in the code (eg. due to a lint ignore comment)
|
||||
- Changes in functionality that are likely intentional or are directly related to the broader change
|
||||
- Real issues, but on lines that the user did not modify in their pull request
|
||||
|
||||
Notes:
|
||||
|
||||
- Do not check build signal or attempt to build or typecheck the app. These will run separately, and are not relevant to your code review.
|
||||
- Use `gh` to interact with Github (eg. to fetch a pull request, or to create inline comments), rather than web fetch
|
||||
- Make a todo list first
|
||||
- You must cite and link each bug (eg. if referring to a CLAUDE.md, you must link it)
|
||||
- For your final comment, follow the following format precisely (assuming for this example that you found 3 issues):
|
||||
|
||||
---
|
||||
|
||||
### Code review
|
||||
|
||||
Found 3 issues:
|
||||
|
||||
1. <brief description of bug> (CLAUDE.md says "<...>")
|
||||
|
||||
<link to file and line with full sha1 + line range for context, note that you MUST provide the full sha and not use bash here, eg. https://github.com/anthropics/claude-code/blob/1d54823877c4de72b2316a64032a54afc404e619/README.md#L13-L17>
|
||||
|
||||
2. <brief description of bug> (some/other/CLAUDE.md says "<...>")
|
||||
|
||||
<link to file and line with full sha1 + line range for context>
|
||||
|
||||
3. <brief description of bug> (bug due to <file and code snippet>)
|
||||
|
||||
<link to file and line with full sha1 + line range for context>
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
<sub>- If this code review was useful, please react with 👍. Otherwise, react with 👎.</sub>
|
||||
|
||||
---
|
||||
|
||||
- Or, if you found no issues:
|
||||
|
||||
---
|
||||
|
||||
### Code review
|
||||
|
||||
No issues found. Checked for bugs and CLAUDE.md compliance.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.ai/code)
|
||||
|
||||
- When linking to code, follow the following format precisely, otherwise the Markdown preview won't render correctly: https://github.com/anthropics/claude-cli-internal/blob/c21d3c10bc8e898b7ac1a2d745bdc9bc4e423afe/package.json#L10-L15
|
||||
- Requires full git sha
|
||||
- You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.
|
||||
- Repo name must match the repo you're code reviewing
|
||||
- # sign after the file name
|
||||
- Line range format is L[start]-L[end]
|
||||
- Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)
|
||||
10
plugins/commit-commands/.claude-plugin/plugin.json
Normal file
10
plugins/commit-commands/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "commit-commands",
|
||||
"description": "Streamline your git workflow with simple commands for committing, pushing, and creating pull requests",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "Anthropic",
|
||||
"email": "support@anthropic.com"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "explanatory-output-style",
|
||||
"version": "1.0.0",
|
||||
"description": "Adds educational insights about implementation choices and codebase patterns (mimics the deprecated Explanatory output style)",
|
||||
"author": {
|
||||
"name": "Dickson Tsai",
|
||||
"email": "dickson@anthropic.com"
|
||||
}
|
||||
}
|
||||
72
plugins/explanatory-output-style/README.md
Normal file
72
plugins/explanatory-output-style/README.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Explanatory Output Style Plugin
|
||||
|
||||
This plugin recreates the deprecated Explanatory output style as a SessionStart
|
||||
hook.
|
||||
|
||||
WARNING: Do not install this plugin unless you are fine with incurring the token
|
||||
cost of this plugin's additional instructions and output.
|
||||
|
||||
## What it does
|
||||
|
||||
When enabled, this plugin automatically adds instructions at the start of each
|
||||
session that encourage Claude to:
|
||||
|
||||
1. Provide educational insights about implementation choices
|
||||
2. Explain codebase patterns and decisions
|
||||
3. Balance task completion with learning opportunities
|
||||
|
||||
## How it works
|
||||
|
||||
The plugin uses a SessionStart hook to inject additional context into every
|
||||
session. This context instructs Claude to provide brief educational explanations
|
||||
before and after writing code, formatted as:
|
||||
|
||||
```
|
||||
`★ Insight ─────────────────────────────────────`
|
||||
[2-3 key educational points]
|
||||
`─────────────────────────────────────────────────`
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, the plugin activates automatically at the start of every
|
||||
session. No additional configuration is needed.
|
||||
|
||||
The insights focus on:
|
||||
|
||||
- Specific implementation choices for your codebase
|
||||
- Patterns and conventions in your code
|
||||
- Trade-offs and design decisions
|
||||
- Codebase-specific details rather than general programming concepts
|
||||
|
||||
## Migration from Output Styles
|
||||
|
||||
This plugin replaces the deprecated "Explanatory" output style setting. If you
|
||||
previously used:
|
||||
|
||||
```json
|
||||
{
|
||||
"outputStyle": "Explanatory"
|
||||
}
|
||||
```
|
||||
|
||||
You can now achieve the same behavior by installing this plugin instead.
|
||||
|
||||
More generally, this SessionStart hook pattern is roughly equivalent to
|
||||
CLAUDE.md, but it is more flexible and allows for distribution through plugins.
|
||||
|
||||
Note: Output styles that involve tasks besides software development, are better
|
||||
expressed as
|
||||
[subagents](https://docs.claude.com/en/docs/claude-code/sub-agents), not as
|
||||
SessionStart hooks. Subagents change the system prompt while SessionStart hooks
|
||||
add to the default system prompt.
|
||||
|
||||
## Managing changes
|
||||
|
||||
- Disable the plugin - keep the code installed on your device
|
||||
- Uninstall the plugin - remove the code from your device
|
||||
- Update the plugin - create a local copy of this plugin to personalize this
|
||||
plugin
|
||||
- Hint: Ask Claude to read
|
||||
https://docs.claude.com/en/docs/claude-code/plugins.md and set it up for
|
||||
you!
|
||||
15
plugins/explanatory-output-style/hooks-handlers/session-start.sh
Executable file
15
plugins/explanatory-output-style/hooks-handlers/session-start.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Output the explanatory mode instructions as additionalContext
|
||||
# This mimics the deprecated Explanatory output style
|
||||
|
||||
cat << 'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "You are in 'explanatory' output style mode, where you should provide educational insights about the codebase as you help with the user's task.\n\nYou should be clear and educational, providing helpful explanations while remaining focused on the task. Balance educational content with task completion. When providing insights, you may exceed typical length constraints, but remain focused and relevant.\n\n## Insights\nIn order to encourage learning, before and after writing code, always provide brief educational explanations about implementation choices using (with backticks):\n\"`★ Insight ─────────────────────────────────────`\n[2-3 key educational points]\n`─────────────────────────────────────────────────`\"\n\nThese insights should be included in the conversation, not in the codebase. You should generally focus on interesting insights that are specific to the codebase or the code you just wrote, rather than general programming concepts. Do not wait until the end to provide insights. Provide them as you write code."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
15
plugins/explanatory-output-style/hooks/hooks.json
Normal file
15
plugins/explanatory-output-style/hooks/hooks.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"description": "Explanatory mode hook that adds educational insights instructions",
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks-handlers/session-start.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
9
plugins/frontend-design/.claude-plugin/plugin.json
Normal file
9
plugins/frontend-design/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "frontend-design",
|
||||
"version": "1.0.0",
|
||||
"description": "Frontend design skill for UI/UX implementation",
|
||||
"author": {
|
||||
"name": "Prithvi Rajasekaran, Alexander Bricken",
|
||||
"email": "prithvi@anthropic.com, alexander@anthropic.com"
|
||||
}
|
||||
}
|
||||
31
plugins/frontend-design/README.md
Normal file
31
plugins/frontend-design/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Frontend Design Plugin
|
||||
|
||||
Generates distinctive, production-grade frontend interfaces that avoid generic AI aesthetics.
|
||||
|
||||
## What It Does
|
||||
|
||||
Claude automatically uses this skill for frontend work. Creates production-ready code with:
|
||||
|
||||
- Bold aesthetic choices
|
||||
- Distinctive typography and color palettes
|
||||
- High-impact animations and visual details
|
||||
- Context-aware implementation
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
"Create a dashboard for a music streaming app"
|
||||
"Build a landing page for an AI security startup"
|
||||
"Design a settings panel with dark mode"
|
||||
```
|
||||
|
||||
Claude will choose a clear aesthetic direction and implement production code with meticulous attention to detail.
|
||||
|
||||
## Learn More
|
||||
|
||||
See the [Frontend Aesthetics Cookbook](https://github.com/anthropics/claude-cookbooks/blob/main/coding/prompting_for_frontend_aesthetics.ipynb) for detailed guidance on prompting for high-quality frontend design.
|
||||
|
||||
## Authors
|
||||
|
||||
Prithvi Rajasekaran (prithvi@anthropic.com)
|
||||
Alexander Bricken (alexander@anthropic.com)
|
||||
42
plugins/frontend-design/skills/frontend-design/SKILL.md
Normal file
42
plugins/frontend-design/skills/frontend-design/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
|
||||
license: Complete terms in LICENSE.txt
|
||||
---
|
||||
|
||||
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
|
||||
|
||||
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
|
||||
|
||||
## Design Thinking
|
||||
|
||||
Before coding, understand the context and commit to a BOLD aesthetic direction:
|
||||
- **Purpose**: What problem does this interface solve? Who uses it?
|
||||
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
|
||||
- **Constraints**: Technical requirements (framework, performance, accessibility).
|
||||
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
|
||||
|
||||
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
|
||||
|
||||
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
|
||||
- Production-grade and functional
|
||||
- Visually striking and memorable
|
||||
- Cohesive with a clear aesthetic point-of-view
|
||||
- Meticulously refined in every detail
|
||||
|
||||
## Frontend Aesthetics Guidelines
|
||||
|
||||
Focus on:
|
||||
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
|
||||
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
|
||||
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
|
||||
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
||||
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
|
||||
|
||||
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
|
||||
|
||||
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
|
||||
|
||||
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
|
||||
|
||||
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
|
||||
9
plugins/hookify/.claude-plugin/plugin.json
Normal file
9
plugins/hookify/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "hookify",
|
||||
"version": "0.1.0",
|
||||
"description": "Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns",
|
||||
"author": {
|
||||
"name": "Daisy Hollman",
|
||||
"email": "daisy@anthropic.com"
|
||||
}
|
||||
}
|
||||
30
plugins/hookify/.gitignore
vendored
Normal file
30
plugins/hookify/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Local configuration (should not be committed)
|
||||
.claude/*.local.md
|
||||
.claude/*.local.json
|
||||
340
plugins/hookify/README.md
Normal file
340
plugins/hookify/README.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Hookify Plugin
|
||||
|
||||
Easily create custom hooks to prevent unwanted behaviors by analyzing conversation patterns or from explicit instructions.
|
||||
|
||||
## Overview
|
||||
|
||||
The hookify plugin makes it simple to create hooks without editing complex `hooks.json` files. Instead, you create lightweight markdown configuration files that define patterns to watch for and messages to show when those patterns match.
|
||||
|
||||
**Key features:**
|
||||
- 🎯 Analyze conversations to find unwanted behaviors automatically
|
||||
- 📝 Simple markdown configuration files with YAML frontmatter
|
||||
- 🔍 Regex pattern matching for powerful rules
|
||||
- 🚀 No coding required - just describe the behavior
|
||||
- 🔄 Easy enable/disable without restarting
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create Your First Rule
|
||||
|
||||
```bash
|
||||
/hookify Warn me when I use rm -rf commands
|
||||
```
|
||||
|
||||
This analyzes your request and creates `.claude/hookify.warn-rm.local.md`.
|
||||
|
||||
### 2. Test It Immediately
|
||||
|
||||
**No restart needed!** Rules take effect on the very next tool use.
|
||||
|
||||
Ask Claude to run a command that should trigger the rule:
|
||||
```
|
||||
Run rm -rf /tmp/test
|
||||
```
|
||||
|
||||
You should see the warning message immediately!
|
||||
|
||||
## Usage
|
||||
|
||||
### Main Command: /hookify
|
||||
|
||||
**With arguments:**
|
||||
```
|
||||
/hookify Don't use console.log in TypeScript files
|
||||
```
|
||||
Creates a rule from your explicit instructions.
|
||||
|
||||
**Without arguments:**
|
||||
```
|
||||
/hookify
|
||||
```
|
||||
Analyzes recent conversation to find behaviors you've corrected or been frustrated by.
|
||||
|
||||
### Helper Commands
|
||||
|
||||
**List all rules:**
|
||||
```
|
||||
/hookify:list
|
||||
```
|
||||
|
||||
**Configure rules interactively:**
|
||||
```
|
||||
/hookify:configure
|
||||
```
|
||||
Enable/disable existing rules through an interactive interface.
|
||||
|
||||
**Get help:**
|
||||
```
|
||||
/hookify:help
|
||||
```
|
||||
|
||||
## Rule Configuration Format
|
||||
|
||||
### Simple Rule (Single Pattern)
|
||||
|
||||
`.claude/hookify.dangerous-rm.local.md`:
|
||||
```markdown
|
||||
---
|
||||
name: block-dangerous-rm
|
||||
enabled: true
|
||||
event: bash
|
||||
pattern: rm\s+-rf
|
||||
action: block
|
||||
---
|
||||
|
||||
⚠️ **Dangerous rm command detected!**
|
||||
|
||||
This command could delete important files. Please:
|
||||
- Verify the path is correct
|
||||
- Consider using a safer approach
|
||||
- Make sure you have backups
|
||||
```
|
||||
|
||||
**Action field:**
|
||||
- `warn`: Shows warning but allows operation (default)
|
||||
- `block`: Prevents operation from executing (PreToolUse) or stops session (Stop events)
|
||||
|
||||
### Advanced Rule (Multiple Conditions)
|
||||
|
||||
`.claude/hookify.sensitive-files.local.md`:
|
||||
```markdown
|
||||
---
|
||||
name: warn-sensitive-files
|
||||
enabled: true
|
||||
event: file
|
||||
action: warn
|
||||
conditions:
|
||||
- field: file_path
|
||||
operator: regex_match
|
||||
pattern: \.env$|credentials|secrets
|
||||
- field: new_text
|
||||
operator: contains
|
||||
pattern: KEY
|
||||
---
|
||||
|
||||
🔐 **Sensitive file edit detected!**
|
||||
|
||||
Ensure credentials are not hardcoded and file is in .gitignore.
|
||||
```
|
||||
|
||||
**All conditions must match** for the rule to trigger.
|
||||
|
||||
## Event Types
|
||||
|
||||
- **`bash`**: Triggers on Bash tool commands
|
||||
- **`file`**: Triggers on Edit, Write, MultiEdit tools
|
||||
- **`stop`**: Triggers when Claude wants to stop (for completion checks)
|
||||
- **`prompt`**: Triggers on user prompt submission
|
||||
- **`all`**: Triggers on all events
|
||||
|
||||
## Pattern Syntax
|
||||
|
||||
Use Python regex syntax:
|
||||
|
||||
| Pattern | Matches | Example |
|
||||
|---------|---------|---------|
|
||||
| `rm\s+-rf` | rm -rf | rm -rf /tmp |
|
||||
| `console\.log\(` | console.log( | console.log("test") |
|
||||
| `(eval\|exec)\(` | eval( or exec( | eval("code") |
|
||||
| `\.env$` | files ending in .env | .env, .env.local |
|
||||
| `chmod\s+777` | chmod 777 | chmod 777 file.txt |
|
||||
|
||||
**Tips:**
|
||||
- Use `\s` for whitespace
|
||||
- Escape special chars: `\.` for literal dot
|
||||
- Use `|` for OR: `(foo|bar)`
|
||||
- Use `.*` to match anything
|
||||
- Set `action: block` for dangerous operations
|
||||
- Set `action: warn` (or omit) for informational warnings
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Block Dangerous Commands
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: block-destructive-ops
|
||||
enabled: true
|
||||
event: bash
|
||||
pattern: rm\s+-rf|dd\s+if=|mkfs|format
|
||||
action: block
|
||||
---
|
||||
|
||||
🛑 **Destructive operation detected!**
|
||||
|
||||
This command can cause data loss. Operation blocked for safety.
|
||||
Please verify the exact path and use a safer approach.
|
||||
```
|
||||
|
||||
**This rule blocks the operation** - Claude will not be allowed to execute these commands.
|
||||
|
||||
### Example 2: Warn About Debug Code
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: warn-debug-code
|
||||
enabled: true
|
||||
event: file
|
||||
pattern: console\.log\(|debugger;|print\(
|
||||
action: warn
|
||||
---
|
||||
|
||||
🐛 **Debug code detected**
|
||||
|
||||
Remember to remove debugging statements before committing.
|
||||
```
|
||||
|
||||
**This rule warns but allows** - Claude sees the message but can still proceed.
|
||||
|
||||
### Example 3: Require Tests Before Stopping
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: require-tests-run
|
||||
enabled: false
|
||||
event: stop
|
||||
action: block
|
||||
conditions:
|
||||
- field: transcript
|
||||
operator: not_contains
|
||||
pattern: npm test|pytest|cargo test
|
||||
---
|
||||
|
||||
**Tests not detected in transcript!**
|
||||
|
||||
Before stopping, please run tests to verify your changes work correctly.
|
||||
```
|
||||
|
||||
**This blocks Claude from stopping** if no test commands appear in the session transcript. Enable only when you want strict enforcement.
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Multiple Conditions
|
||||
|
||||
Check multiple fields simultaneously:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: api-key-in-typescript
|
||||
enabled: true
|
||||
event: file
|
||||
conditions:
|
||||
- field: file_path
|
||||
operator: regex_match
|
||||
pattern: \.tsx?$
|
||||
- field: new_text
|
||||
operator: regex_match
|
||||
pattern: (API_KEY|SECRET|TOKEN)\s*=\s*["']
|
||||
---
|
||||
|
||||
🔐 **Hardcoded credential in TypeScript!**
|
||||
|
||||
Use environment variables instead of hardcoded values.
|
||||
```
|
||||
|
||||
### Operators Reference
|
||||
|
||||
- `regex_match`: Pattern must match (most common)
|
||||
- `contains`: String must contain pattern
|
||||
- `equals`: Exact string match
|
||||
- `not_contains`: String must NOT contain pattern
|
||||
- `starts_with`: String starts with pattern
|
||||
- `ends_with`: String ends with pattern
|
||||
|
||||
### Field Reference
|
||||
|
||||
**For bash events:**
|
||||
- `command`: The bash command string
|
||||
|
||||
**For file events:**
|
||||
- `file_path`: Path to file being edited
|
||||
- `new_text`: New content being added (Edit, Write)
|
||||
- `old_text`: Old content being replaced (Edit only)
|
||||
- `content`: File content (Write only)
|
||||
|
||||
**For prompt events:**
|
||||
- `user_prompt`: The user's submitted prompt text
|
||||
|
||||
**For stop events:**
|
||||
- Use general matching on session state
|
||||
|
||||
## Management
|
||||
|
||||
### Enable/Disable Rules
|
||||
|
||||
**Temporarily disable:**
|
||||
Edit the `.local.md` file and set `enabled: false`
|
||||
|
||||
**Re-enable:**
|
||||
Set `enabled: true`
|
||||
|
||||
**Or use interactive tool:**
|
||||
```
|
||||
/hookify:configure
|
||||
```
|
||||
|
||||
### Delete Rules
|
||||
|
||||
Simply delete the `.local.md` file:
|
||||
```bash
|
||||
rm .claude/hookify.my-rule.local.md
|
||||
```
|
||||
|
||||
### View All Rules
|
||||
|
||||
```
|
||||
/hookify:list
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
This plugin is part of the Claude Code Marketplace. It should be auto-discovered when the marketplace is installed.
|
||||
|
||||
**Manual testing:**
|
||||
```bash
|
||||
cc --plugin-dir /path/to/hookify
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.7+
|
||||
- No external dependencies (uses stdlib only)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Rule not triggering:**
|
||||
1. Check rule file exists in `.claude/` directory (in project root, not plugin directory)
|
||||
2. Verify `enabled: true` in frontmatter
|
||||
3. Test regex pattern separately
|
||||
4. Rules should work immediately - no restart needed
|
||||
5. Try `/hookify:list` to see if rule is loaded
|
||||
|
||||
**Import errors:**
|
||||
- Ensure Python 3 is available: `python3 --version`
|
||||
- Check hookify plugin is installed
|
||||
|
||||
**Pattern not matching:**
|
||||
- Test regex: `python3 -c "import re; print(re.search(r'pattern', 'text'))"`
|
||||
- Use unquoted patterns in YAML to avoid escaping issues
|
||||
- Start simple, then add complexity
|
||||
|
||||
**Hook seems slow:**
|
||||
- Keep patterns simple (avoid complex regex)
|
||||
- Use specific event types (bash, file) instead of "all"
|
||||
- Limit number of active rules
|
||||
|
||||
## Contributing
|
||||
|
||||
Found a useful rule pattern? Consider sharing example files via PR!
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- Severity levels (error/warning/info distinctions)
|
||||
- Rule templates library
|
||||
- Interactive pattern builder
|
||||
- Hook testing utilities
|
||||
- JSON format support (in addition to markdown)
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
176
plugins/hookify/agents/conversation-analyzer.md
Normal file
176
plugins/hookify/agents/conversation-analyzer.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
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>
|
||||
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:
|
||||
|
||||
```
|
||||
## 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
|
||||
128
plugins/hookify/commands/configure.md
Normal file
128
plugins/hookify/commands/configure.md
Normal file
@@ -0,0 +1,128 @@
|
||||
---
|
||||
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:
|
||||
```
|
||||
pattern: ".claude/hookify.*.local.md"
|
||||
```
|
||||
|
||||
If no rules found, inform user:
|
||||
```
|
||||
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:**
|
||||
```
|
||||
old_string: "enabled: false"
|
||||
new_string: "enabled: true"
|
||||
```
|
||||
|
||||
**Edit pattern for disabling:**
|
||||
```
|
||||
old_string: "enabled: true"
|
||||
new_string: "enabled: false"
|
||||
```
|
||||
|
||||
### 6. Confirm Changes
|
||||
|
||||
Show user what was changed:
|
||||
|
||||
```
|
||||
## 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
|
||||
175
plugins/hookify/commands/help.md
Normal file
175
plugins/hookify/commands/help.md
Normal file
@@ -0,0 +1,175 @@
|
||||
---
|
||||
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**
|
||||
```
|
||||
/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**
|
||||
```
|
||||
/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:
|
||||
```
|
||||
/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
|
||||
|
||||
4. Refine the rule by editing `.claude/hookify.warn-rm.local.md`
|
||||
|
||||
5. Create more rules as you encounter unwanted behaviors
|
||||
|
||||
For more examples, check the `${CLAUDE_PLUGIN_ROOT}/examples/` directory.
|
||||
231
plugins/hookify/commands/hookify.md
Normal file
231
plugins/hookify/commands/hookify.md
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
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:
|
||||
```
|
||||
{
|
||||
"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:
|
||||
```
|
||||
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.
|
||||
82
plugins/hookify/commands/list.md
Normal file
82
plugins/hookify/commands/list.md
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
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:
|
||||
```
|
||||
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:
|
||||
|
||||
```
|
||||
## 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)
|
||||
```
|
||||
|
||||
4. For each rule, show a brief preview:
|
||||
```
|
||||
### 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
|
||||
```
|
||||
|
||||
5. Add helpful footer:
|
||||
```
|
||||
---
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
## 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
|
||||
```
|
||||
|
||||
Check `${CLAUDE_PLUGIN_ROOT}/examples/` for example rule files.
|
||||
```
|
||||
0
plugins/hookify/core/__init__.py
Normal file
0
plugins/hookify/core/__init__.py
Normal file
297
plugins/hookify/core/config_loader.py
Normal file
297
plugins/hookify/core/config_loader.py
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Configuration loader for hookify plugin.
|
||||
|
||||
Loads and parses .claude/hookify.*.local.md files.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import glob
|
||||
import re
|
||||
from typing import List, Optional, Dict, Any
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass
|
||||
class Condition:
|
||||
"""A single condition for matching."""
|
||||
field: str # "command", "new_text", "old_text", "file_path", etc.
|
||||
operator: str # "regex_match", "contains", "equals", etc.
|
||||
pattern: str # Pattern to match
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'Condition':
|
||||
"""Create Condition from dict."""
|
||||
return cls(
|
||||
field=data.get('field', ''),
|
||||
operator=data.get('operator', 'regex_match'),
|
||||
pattern=data.get('pattern', '')
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Rule:
|
||||
"""A hookify rule."""
|
||||
name: str
|
||||
enabled: bool
|
||||
event: str # "bash", "file", "stop", "all", etc.
|
||||
pattern: Optional[str] = None # Simple pattern (legacy)
|
||||
conditions: List[Condition] = field(default_factory=list)
|
||||
action: str = "warn" # "warn" or "block" (future)
|
||||
tool_matcher: Optional[str] = None # Override tool matching
|
||||
message: str = "" # Message body from markdown
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, frontmatter: Dict[str, Any], message: str) -> 'Rule':
|
||||
"""Create Rule from frontmatter dict and message body."""
|
||||
# Handle both simple pattern and complex conditions
|
||||
conditions = []
|
||||
|
||||
# New style: explicit conditions list
|
||||
if 'conditions' in frontmatter:
|
||||
cond_list = frontmatter['conditions']
|
||||
if isinstance(cond_list, list):
|
||||
conditions = [Condition.from_dict(c) for c in cond_list]
|
||||
|
||||
# Legacy style: simple pattern field
|
||||
simple_pattern = frontmatter.get('pattern')
|
||||
if simple_pattern and not conditions:
|
||||
# Convert simple pattern to condition
|
||||
# Infer field from event
|
||||
event = frontmatter.get('event', 'all')
|
||||
if event == 'bash':
|
||||
field = 'command'
|
||||
elif event == 'file':
|
||||
field = 'new_text'
|
||||
else:
|
||||
field = 'content'
|
||||
|
||||
conditions = [Condition(
|
||||
field=field,
|
||||
operator='regex_match',
|
||||
pattern=simple_pattern
|
||||
)]
|
||||
|
||||
return cls(
|
||||
name=frontmatter.get('name', 'unnamed'),
|
||||
enabled=frontmatter.get('enabled', True),
|
||||
event=frontmatter.get('event', 'all'),
|
||||
pattern=simple_pattern,
|
||||
conditions=conditions,
|
||||
action=frontmatter.get('action', 'warn'),
|
||||
tool_matcher=frontmatter.get('tool_matcher'),
|
||||
message=message.strip()
|
||||
)
|
||||
|
||||
|
||||
def extract_frontmatter(content: str) -> tuple[Dict[str, Any], str]:
|
||||
"""Extract YAML frontmatter and message body from markdown.
|
||||
|
||||
Returns (frontmatter_dict, message_body).
|
||||
|
||||
Supports multi-line dictionary items in lists by preserving indentation.
|
||||
"""
|
||||
if not content.startswith('---'):
|
||||
return {}, content
|
||||
|
||||
# Split on --- markers
|
||||
parts = content.split('---', 2)
|
||||
if len(parts) < 3:
|
||||
return {}, content
|
||||
|
||||
frontmatter_text = parts[1]
|
||||
message = parts[2].strip()
|
||||
|
||||
# Simple YAML parser that handles indented list items
|
||||
frontmatter = {}
|
||||
lines = frontmatter_text.split('\n')
|
||||
|
||||
current_key = None
|
||||
current_list = []
|
||||
current_dict = {}
|
||||
in_list = False
|
||||
in_dict_item = False
|
||||
|
||||
for line in lines:
|
||||
# Skip empty lines and comments
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith('#'):
|
||||
continue
|
||||
|
||||
# Check indentation level
|
||||
indent = len(line) - len(line.lstrip())
|
||||
|
||||
# Top-level key (no indentation or minimal)
|
||||
if indent == 0 and ':' in line and not line.strip().startswith('-'):
|
||||
# Save previous list/dict if any
|
||||
if in_list and current_key:
|
||||
if in_dict_item and current_dict:
|
||||
current_list.append(current_dict)
|
||||
current_dict = {}
|
||||
frontmatter[current_key] = current_list
|
||||
in_list = False
|
||||
in_dict_item = False
|
||||
current_list = []
|
||||
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
if not value:
|
||||
# Empty value - list or nested structure follows
|
||||
current_key = key
|
||||
in_list = True
|
||||
current_list = []
|
||||
else:
|
||||
# Simple key-value pair
|
||||
value = value.strip('"').strip("'")
|
||||
if value.lower() == 'true':
|
||||
value = True
|
||||
elif value.lower() == 'false':
|
||||
value = False
|
||||
frontmatter[key] = value
|
||||
|
||||
# List item (starts with -)
|
||||
elif stripped.startswith('-') and in_list:
|
||||
# Save previous dict item if any
|
||||
if in_dict_item and current_dict:
|
||||
current_list.append(current_dict)
|
||||
current_dict = {}
|
||||
|
||||
item_text = stripped[1:].strip()
|
||||
|
||||
# Check if this is an inline dict (key: value on same line)
|
||||
if ':' in item_text and ',' in item_text:
|
||||
# Inline comma-separated dict: "- field: command, operator: regex_match"
|
||||
item_dict = {}
|
||||
for part in item_text.split(','):
|
||||
if ':' in part:
|
||||
k, v = part.split(':', 1)
|
||||
item_dict[k.strip()] = v.strip().strip('"').strip("'")
|
||||
current_list.append(item_dict)
|
||||
in_dict_item = False
|
||||
elif ':' in item_text:
|
||||
# Start of multi-line dict item: "- field: command"
|
||||
in_dict_item = True
|
||||
k, v = item_text.split(':', 1)
|
||||
current_dict = {k.strip(): v.strip().strip('"').strip("'")}
|
||||
else:
|
||||
# Simple list item
|
||||
current_list.append(item_text.strip('"').strip("'"))
|
||||
in_dict_item = False
|
||||
|
||||
# Continuation of dict item (indented under list item)
|
||||
elif indent > 2 and in_dict_item and ':' in line:
|
||||
# This is a field of the current dict item
|
||||
k, v = stripped.split(':', 1)
|
||||
current_dict[k.strip()] = v.strip().strip('"').strip("'")
|
||||
|
||||
# Save final list/dict if any
|
||||
if in_list and current_key:
|
||||
if in_dict_item and current_dict:
|
||||
current_list.append(current_dict)
|
||||
frontmatter[current_key] = current_list
|
||||
|
||||
return frontmatter, message
|
||||
|
||||
|
||||
def load_rules(event: Optional[str] = None) -> List[Rule]:
|
||||
"""Load all hookify rules from .claude directory.
|
||||
|
||||
Args:
|
||||
event: Optional event filter ("bash", "file", "stop", etc.)
|
||||
|
||||
Returns:
|
||||
List of enabled Rule objects matching the event.
|
||||
"""
|
||||
rules = []
|
||||
|
||||
# Find all hookify.*.local.md files
|
||||
pattern = os.path.join('.claude', 'hookify.*.local.md')
|
||||
files = glob.glob(pattern)
|
||||
|
||||
for file_path in files:
|
||||
try:
|
||||
rule = load_rule_file(file_path)
|
||||
if not rule:
|
||||
continue
|
||||
|
||||
# Filter by event if specified
|
||||
if event:
|
||||
if rule.event != 'all' and rule.event != event:
|
||||
continue
|
||||
|
||||
# Only include enabled rules
|
||||
if rule.enabled:
|
||||
rules.append(rule)
|
||||
|
||||
except (IOError, OSError, PermissionError) as e:
|
||||
# File I/O errors - log and continue
|
||||
print(f"Warning: Failed to read {file_path}: {e}", file=sys.stderr)
|
||||
continue
|
||||
except (ValueError, KeyError, AttributeError, TypeError) as e:
|
||||
# Parsing errors - log and continue
|
||||
print(f"Warning: Failed to parse {file_path}: {e}", file=sys.stderr)
|
||||
continue
|
||||
except Exception as e:
|
||||
# Unexpected errors - log with type details
|
||||
print(f"Warning: Unexpected error loading {file_path} ({type(e).__name__}): {e}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
return rules
|
||||
|
||||
|
||||
def load_rule_file(file_path: str) -> Optional[Rule]:
|
||||
"""Load a single rule file.
|
||||
|
||||
Returns:
|
||||
Rule object or None if file is invalid.
|
||||
"""
|
||||
try:
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
frontmatter, message = extract_frontmatter(content)
|
||||
|
||||
if not frontmatter:
|
||||
print(f"Warning: {file_path} missing YAML frontmatter (must start with ---)", file=sys.stderr)
|
||||
return None
|
||||
|
||||
rule = Rule.from_dict(frontmatter, message)
|
||||
return rule
|
||||
|
||||
except (IOError, OSError, PermissionError) as e:
|
||||
print(f"Error: Cannot read {file_path}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except (ValueError, KeyError, AttributeError, TypeError) as e:
|
||||
print(f"Error: Malformed rule file {file_path}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except UnicodeDecodeError as e:
|
||||
print(f"Error: Invalid encoding in {file_path}: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error: Unexpected error parsing {file_path} ({type(e).__name__}): {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
# For testing
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
# Test frontmatter parsing
|
||||
test_content = """---
|
||||
name: test-rule
|
||||
enabled: true
|
||||
event: bash
|
||||
pattern: "rm -rf"
|
||||
---
|
||||
|
||||
⚠️ Dangerous command detected!
|
||||
"""
|
||||
|
||||
fm, msg = extract_frontmatter(test_content)
|
||||
print("Frontmatter:", fm)
|
||||
print("Message:", msg)
|
||||
|
||||
rule = Rule.from_dict(fm, msg)
|
||||
print("Rule:", rule)
|
||||
313
plugins/hookify/core/rule_engine.py
Normal file
313
plugins/hookify/core/rule_engine.py
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rule evaluation engine for hookify plugin."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
# Import from local module
|
||||
from hookify.core.config_loader import Rule, Condition
|
||||
|
||||
|
||||
# Cache compiled regexes (max 128 patterns)
|
||||
@lru_cache(maxsize=128)
|
||||
def compile_regex(pattern: str) -> re.Pattern:
|
||||
"""Compile regex pattern with caching.
|
||||
|
||||
Args:
|
||||
pattern: Regex pattern string
|
||||
|
||||
Returns:
|
||||
Compiled regex pattern
|
||||
"""
|
||||
return re.compile(pattern, re.IGNORECASE)
|
||||
|
||||
|
||||
class RuleEngine:
|
||||
"""Evaluates rules against hook input data."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize rule engine."""
|
||||
# No need for instance cache anymore - using global lru_cache
|
||||
pass
|
||||
|
||||
def evaluate_rules(self, rules: List[Rule], input_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Evaluate all rules and return combined results.
|
||||
|
||||
Checks all rules and accumulates matches. Blocking rules take priority
|
||||
over warning rules. All matching rule messages are combined.
|
||||
|
||||
Args:
|
||||
rules: List of Rule objects to evaluate
|
||||
input_data: Hook input JSON (tool_name, tool_input, etc.)
|
||||
|
||||
Returns:
|
||||
Response dict with systemMessage, hookSpecificOutput, etc.
|
||||
Empty dict {} if no rules match.
|
||||
"""
|
||||
hook_event = input_data.get('hook_event_name', '')
|
||||
blocking_rules = []
|
||||
warning_rules = []
|
||||
|
||||
for rule in rules:
|
||||
if self._rule_matches(rule, input_data):
|
||||
if rule.action == 'block':
|
||||
blocking_rules.append(rule)
|
||||
else:
|
||||
warning_rules.append(rule)
|
||||
|
||||
# If any blocking rules matched, block the operation
|
||||
if blocking_rules:
|
||||
messages = [f"**[{r.name}]**\n{r.message}" for r in blocking_rules]
|
||||
combined_message = "\n\n".join(messages)
|
||||
|
||||
# Use appropriate blocking format based on event type
|
||||
if hook_event == 'Stop':
|
||||
return {
|
||||
"decision": "block",
|
||||
"reason": combined_message,
|
||||
"systemMessage": combined_message
|
||||
}
|
||||
elif hook_event in ['PreToolUse', 'PostToolUse']:
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": hook_event,
|
||||
"permissionDecision": "deny"
|
||||
},
|
||||
"systemMessage": combined_message
|
||||
}
|
||||
else:
|
||||
# For other events, just show message
|
||||
return {
|
||||
"systemMessage": combined_message
|
||||
}
|
||||
|
||||
# If only warnings, show them but allow operation
|
||||
if warning_rules:
|
||||
messages = [f"**[{r.name}]**\n{r.message}" for r in warning_rules]
|
||||
return {
|
||||
"systemMessage": "\n\n".join(messages)
|
||||
}
|
||||
|
||||
# No matches - allow operation
|
||||
return {}
|
||||
|
||||
def _rule_matches(self, rule: Rule, input_data: Dict[str, Any]) -> bool:
|
||||
"""Check if rule matches input data.
|
||||
|
||||
Args:
|
||||
rule: Rule to evaluate
|
||||
input_data: Hook input data
|
||||
|
||||
Returns:
|
||||
True if rule matches, False otherwise
|
||||
"""
|
||||
# Extract tool information
|
||||
tool_name = input_data.get('tool_name', '')
|
||||
tool_input = input_data.get('tool_input', {})
|
||||
|
||||
# Check tool matcher if specified
|
||||
if rule.tool_matcher:
|
||||
if not self._matches_tool(rule.tool_matcher, tool_name):
|
||||
return False
|
||||
|
||||
# If no conditions, don't match
|
||||
# (Rules must have at least one condition to be valid)
|
||||
if not rule.conditions:
|
||||
return False
|
||||
|
||||
# All conditions must match
|
||||
for condition in rule.conditions:
|
||||
if not self._check_condition(condition, tool_name, tool_input, input_data):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _matches_tool(self, matcher: str, tool_name: str) -> bool:
|
||||
"""Check if tool_name matches the matcher pattern.
|
||||
|
||||
Args:
|
||||
matcher: Pattern like "Bash", "Edit|Write", "*"
|
||||
tool_name: Actual tool name
|
||||
|
||||
Returns:
|
||||
True if matches
|
||||
"""
|
||||
if matcher == '*':
|
||||
return True
|
||||
|
||||
# Split on | for OR matching
|
||||
patterns = matcher.split('|')
|
||||
return tool_name in patterns
|
||||
|
||||
def _check_condition(self, condition: Condition, tool_name: str,
|
||||
tool_input: Dict[str, Any], input_data: Dict[str, Any] = None) -> bool:
|
||||
"""Check if a single condition matches.
|
||||
|
||||
Args:
|
||||
condition: Condition to check
|
||||
tool_name: Tool being used
|
||||
tool_input: Tool input dict
|
||||
input_data: Full hook input data (for Stop events, etc.)
|
||||
|
||||
Returns:
|
||||
True if condition matches
|
||||
"""
|
||||
# Extract the field value to check
|
||||
field_value = self._extract_field(condition.field, tool_name, tool_input, input_data)
|
||||
if field_value is None:
|
||||
return False
|
||||
|
||||
# Apply operator
|
||||
operator = condition.operator
|
||||
pattern = condition.pattern
|
||||
|
||||
if operator == 'regex_match':
|
||||
return self._regex_match(pattern, field_value)
|
||||
elif operator == 'contains':
|
||||
return pattern in field_value
|
||||
elif operator == 'equals':
|
||||
return pattern == field_value
|
||||
elif operator == 'not_contains':
|
||||
return pattern not in field_value
|
||||
elif operator == 'starts_with':
|
||||
return field_value.startswith(pattern)
|
||||
elif operator == 'ends_with':
|
||||
return field_value.endswith(pattern)
|
||||
else:
|
||||
# Unknown operator
|
||||
return False
|
||||
|
||||
def _extract_field(self, field: str, tool_name: str,
|
||||
tool_input: Dict[str, Any], input_data: Dict[str, Any] = None) -> Optional[str]:
|
||||
"""Extract field value from tool input or hook input data.
|
||||
|
||||
Args:
|
||||
field: Field name like "command", "new_text", "file_path", "reason", "transcript"
|
||||
tool_name: Tool being used (may be empty for Stop events)
|
||||
tool_input: Tool input dict
|
||||
input_data: Full hook input (for accessing transcript_path, reason, etc.)
|
||||
|
||||
Returns:
|
||||
Field value as string, or None if not found
|
||||
"""
|
||||
# Direct tool_input fields
|
||||
if field in tool_input:
|
||||
value = tool_input[field]
|
||||
if isinstance(value, str):
|
||||
return value
|
||||
return str(value)
|
||||
|
||||
# For Stop events and other non-tool events, check input_data
|
||||
if input_data:
|
||||
# Stop event specific fields
|
||||
if field == 'reason':
|
||||
return input_data.get('reason', '')
|
||||
elif field == 'transcript':
|
||||
# Read transcript file if path provided
|
||||
transcript_path = input_data.get('transcript_path')
|
||||
if transcript_path:
|
||||
try:
|
||||
with open(transcript_path, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"Warning: Transcript file not found: {transcript_path}", file=sys.stderr)
|
||||
return ''
|
||||
except PermissionError:
|
||||
print(f"Warning: Permission denied reading transcript: {transcript_path}", file=sys.stderr)
|
||||
return ''
|
||||
except (IOError, OSError) as e:
|
||||
print(f"Warning: Error reading transcript {transcript_path}: {e}", file=sys.stderr)
|
||||
return ''
|
||||
except UnicodeDecodeError as e:
|
||||
print(f"Warning: Encoding error in transcript {transcript_path}: {e}", file=sys.stderr)
|
||||
return ''
|
||||
elif field == 'user_prompt':
|
||||
# For UserPromptSubmit events
|
||||
return input_data.get('user_prompt', '')
|
||||
|
||||
# Handle special cases by tool type
|
||||
if tool_name == 'Bash':
|
||||
if field == 'command':
|
||||
return tool_input.get('command', '')
|
||||
|
||||
elif tool_name in ['Write', 'Edit']:
|
||||
if field == 'content':
|
||||
# Write uses 'content', Edit has 'new_string'
|
||||
return tool_input.get('content') or tool_input.get('new_string', '')
|
||||
elif field == 'new_text' or field == 'new_string':
|
||||
return tool_input.get('new_string', '')
|
||||
elif field == 'old_text' or field == 'old_string':
|
||||
return tool_input.get('old_string', '')
|
||||
elif field == 'file_path':
|
||||
return tool_input.get('file_path', '')
|
||||
|
||||
elif tool_name == 'MultiEdit':
|
||||
if field == 'file_path':
|
||||
return tool_input.get('file_path', '')
|
||||
elif field in ['new_text', 'content']:
|
||||
# Concatenate all edits
|
||||
edits = tool_input.get('edits', [])
|
||||
return ' '.join(e.get('new_string', '') for e in edits)
|
||||
|
||||
return None
|
||||
|
||||
def _regex_match(self, pattern: str, text: str) -> bool:
|
||||
"""Check if pattern matches text using regex.
|
||||
|
||||
Args:
|
||||
pattern: Regex pattern
|
||||
text: Text to match against
|
||||
|
||||
Returns:
|
||||
True if pattern matches
|
||||
"""
|
||||
try:
|
||||
# Use cached compiled regex (LRU cache with max 128 patterns)
|
||||
regex = compile_regex(pattern)
|
||||
return bool(regex.search(text))
|
||||
|
||||
except re.error as e:
|
||||
print(f"Invalid regex pattern '{pattern}': {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
# For testing
|
||||
if __name__ == '__main__':
|
||||
from hookify.core.config_loader import Condition, Rule
|
||||
|
||||
# Test rule evaluation
|
||||
rule = Rule(
|
||||
name="test-rm",
|
||||
enabled=True,
|
||||
event="bash",
|
||||
conditions=[
|
||||
Condition(field="command", operator="regex_match", pattern=r"rm\s+-rf")
|
||||
],
|
||||
message="Dangerous rm command!"
|
||||
)
|
||||
|
||||
engine = RuleEngine()
|
||||
|
||||
# Test matching input
|
||||
test_input = {
|
||||
"tool_name": "Bash",
|
||||
"tool_input": {
|
||||
"command": "rm -rf /tmp/test"
|
||||
}
|
||||
}
|
||||
|
||||
result = engine.evaluate_rules([rule], test_input)
|
||||
print("Match result:", result)
|
||||
|
||||
# Test non-matching input
|
||||
test_input2 = {
|
||||
"tool_name": "Bash",
|
||||
"tool_input": {
|
||||
"command": "ls -la"
|
||||
}
|
||||
}
|
||||
|
||||
result2 = engine.evaluate_rules([rule], test_input2)
|
||||
print("Non-match result:", result2)
|
||||
14
plugins/hookify/examples/console-log-warning.local.md
Normal file
14
plugins/hookify/examples/console-log-warning.local.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: warn-console-log
|
||||
enabled: true
|
||||
event: file
|
||||
pattern: console\.log\(
|
||||
action: warn
|
||||
---
|
||||
|
||||
🔍 **Console.log detected**
|
||||
|
||||
You're adding a console.log statement. Please consider:
|
||||
- Is this for debugging or should it be proper logging?
|
||||
- Will this ship to production?
|
||||
- Should this use a logging library instead?
|
||||
14
plugins/hookify/examples/dangerous-rm.local.md
Normal file
14
plugins/hookify/examples/dangerous-rm.local.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: block-dangerous-rm
|
||||
enabled: true
|
||||
event: bash
|
||||
pattern: rm\s+-rf
|
||||
action: block
|
||||
---
|
||||
|
||||
⚠️ **Dangerous rm command detected!**
|
||||
|
||||
This command could delete important files. Please:
|
||||
- Verify the path is correct
|
||||
- Consider using a safer approach
|
||||
- Make sure you have backups
|
||||
22
plugins/hookify/examples/require-tests-stop.local.md
Normal file
22
plugins/hookify/examples/require-tests-stop.local.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: require-tests-run
|
||||
enabled: false
|
||||
event: stop
|
||||
action: block
|
||||
conditions:
|
||||
- field: transcript
|
||||
operator: not_contains
|
||||
pattern: npm test|pytest|cargo test
|
||||
---
|
||||
|
||||
**Tests not detected in transcript!**
|
||||
|
||||
Before stopping, please run tests to verify your changes work correctly.
|
||||
|
||||
Look for test commands like:
|
||||
- `npm test`
|
||||
- `pytest`
|
||||
- `cargo test`
|
||||
|
||||
**Note:** This rule blocks stopping if no test commands appear in the transcript.
|
||||
Enable this rule only when you want strict test enforcement.
|
||||
18
plugins/hookify/examples/sensitive-files-warning.local.md
Normal file
18
plugins/hookify/examples/sensitive-files-warning.local.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: warn-sensitive-files
|
||||
enabled: true
|
||||
event: file
|
||||
action: warn
|
||||
conditions:
|
||||
- field: file_path
|
||||
operator: regex_match
|
||||
pattern: \.env$|\.env\.|credentials|secrets
|
||||
---
|
||||
|
||||
🔐 **Sensitive file detected**
|
||||
|
||||
You're editing a file that may contain sensitive data:
|
||||
- Ensure credentials are not hardcoded
|
||||
- Use environment variables for secrets
|
||||
- Verify this file is in .gitignore
|
||||
- Consider using a secrets manager
|
||||
0
plugins/hookify/hooks/__init__.py
Executable file
0
plugins/hookify/hooks/__init__.py
Executable file
49
plugins/hookify/hooks/hooks.json
Normal file
49
plugins/hookify/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
plugins/hookify/hooks/posttooluse.py
Executable file
66
plugins/hookify/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
plugins/hookify/hooks/pretooluse.py
Executable file
74
plugins/hookify/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
plugins/hookify/hooks/stop.py
Executable file
59
plugins/hookify/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
plugins/hookify/hooks/userpromptsubmit.py
Executable file
58
plugins/hookify/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()
|
||||
0
plugins/hookify/matchers/__init__.py
Normal file
0
plugins/hookify/matchers/__init__.py
Normal file
374
plugins/hookify/skills/writing-rules/SKILL.md
Normal file
374
plugins/hookify/skills/writing-rules/SKILL.md
Normal file
@@ -0,0 +1,374 @@
|
||||
---
|
||||
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:**
|
||||
```
|
||||
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`
|
||||
0
plugins/hookify/utils/__init__.py
Normal file
0
plugins/hookify/utils/__init__.py
Normal file
9
plugins/learning-output-style/.claude-plugin/plugin.json
Normal file
9
plugins/learning-output-style/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "learning-output-style",
|
||||
"version": "1.0.0",
|
||||
"description": "Interactive learning mode that requests meaningful code contributions at decision points (mimics the unshipped Learning output style)",
|
||||
"author": {
|
||||
"name": "Boris Cherny",
|
||||
"email": "boris@anthropic.com"
|
||||
}
|
||||
}
|
||||
93
plugins/learning-output-style/README.md
Normal file
93
plugins/learning-output-style/README.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Learning Style Plugin
|
||||
|
||||
This plugin combines the unshipped Learning output style with explanatory functionality as a SessionStart hook.
|
||||
|
||||
**Note:** This plugin differs from the original unshipped Learning output style by also incorporating all functionality from the [explanatory-output-style plugin](https://github.com/anthropics/claude-code/tree/main/plugins/explanatory-output-style), providing both interactive learning and educational insights.
|
||||
|
||||
WARNING: Do not install this plugin unless you are fine with incurring the token cost of this plugin's additional instructions and the interactive nature of learning mode.
|
||||
|
||||
## What it does
|
||||
|
||||
When enabled, this plugin automatically adds instructions at the start of each session that encourage Claude to:
|
||||
|
||||
1. **Learning Mode:** Engage you in active learning by requesting meaningful code contributions at decision points
|
||||
2. **Explanatory Mode:** Provide educational insights about implementation choices and codebase patterns
|
||||
|
||||
Instead of implementing everything automatically, Claude will:
|
||||
|
||||
1. Identify opportunities where you can write 5-10 lines of meaningful code
|
||||
2. Focus on business logic and design choices where your input truly matters
|
||||
3. Prepare the context and location for your contribution
|
||||
4. Explain trade-offs and guide your implementation
|
||||
5. Provide educational insights before and after writing code
|
||||
|
||||
## How it works
|
||||
|
||||
The plugin uses a SessionStart hook to inject additional context into every session. This context instructs Claude to adopt an interactive teaching approach where you actively participate in writing key parts of the code.
|
||||
|
||||
## When Claude requests contributions
|
||||
|
||||
Claude will ask you to write code for:
|
||||
- Business logic with multiple valid approaches
|
||||
- Error handling strategies
|
||||
- Algorithm implementation choices
|
||||
- Data structure decisions
|
||||
- User experience decisions
|
||||
- Design patterns and architecture choices
|
||||
|
||||
## When Claude won't request contributions
|
||||
|
||||
Claude will implement directly:
|
||||
- Boilerplate or repetitive code
|
||||
- Obvious implementations with no meaningful choices
|
||||
- Configuration or setup code
|
||||
- Simple CRUD operations
|
||||
|
||||
## Example interaction
|
||||
|
||||
**Claude:** I've set up the authentication middleware. The session timeout behavior is a security vs. UX trade-off - should sessions auto-extend on activity, or have a hard timeout?
|
||||
|
||||
In `auth/middleware.ts`, implement the `handleSessionTimeout()` function to define the timeout behavior.
|
||||
|
||||
Consider: auto-extending improves UX but may leave sessions open longer; hard timeouts are more secure but might frustrate active users.
|
||||
|
||||
**You:** [Write 5-10 lines implementing your preferred approach]
|
||||
|
||||
## Educational insights
|
||||
|
||||
In addition to interactive learning, Claude will provide educational insights about implementation choices using this format:
|
||||
|
||||
```
|
||||
`★ Insight ─────────────────────────────────────`
|
||||
[2-3 key educational points about the codebase or implementation]
|
||||
`─────────────────────────────────────────────────`
|
||||
```
|
||||
|
||||
These insights focus on:
|
||||
- Specific implementation choices for your codebase
|
||||
- Patterns and conventions in your code
|
||||
- Trade-offs and design decisions
|
||||
- Codebase-specific details rather than general programming concepts
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, the plugin activates automatically at the start of every session. No additional configuration is needed.
|
||||
|
||||
## Migration from Output Styles
|
||||
|
||||
This plugin combines the unshipped "Learning" output style with the deprecated "Explanatory" output style. It provides an interactive learning experience where you actively contribute code at meaningful decision points, while also receiving educational insights about implementation choices.
|
||||
|
||||
If you previously used the explanatory-output-style plugin, this learning plugin includes all of that functionality plus interactive learning features.
|
||||
|
||||
This SessionStart hook pattern is roughly equivalent to CLAUDE.md, but it is more flexible and allows for distribution through plugins.
|
||||
|
||||
## Managing changes
|
||||
|
||||
- Disable the plugin - keep the code installed on your device
|
||||
- Uninstall the plugin - remove the code from your device
|
||||
- Update the plugin - create a local copy of this plugin to personalize it
|
||||
- Hint: Ask Claude to read https://docs.claude.com/en/docs/claude-code/plugins.md and set it up for you!
|
||||
|
||||
## Philosophy
|
||||
|
||||
Learning by doing is more effective than passive observation. This plugin transforms your interaction with Claude from "watch and learn" to "build and understand," ensuring you develop practical skills through hands-on coding of meaningful logic.
|
||||
15
plugins/learning-output-style/hooks-handlers/session-start.sh
Executable file
15
plugins/learning-output-style/hooks-handlers/session-start.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Output the learning mode instructions as additionalContext
|
||||
# This combines the unshipped Learning output style with explanatory functionality
|
||||
|
||||
cat << 'EOF'
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "You are in 'learning' output style mode, which combines interactive learning with educational explanations. This mode differs from the original unshipped Learning output style by also incorporating explanatory functionality.\n\n## Learning Mode Philosophy\n\nInstead of implementing everything yourself, identify opportunities where the user can write 5-10 lines of meaningful code that shapes the solution. Focus on business logic, design choices, and implementation strategies where their input truly matters.\n\n## When to Request User Contributions\n\nRequest code contributions for:\n- Business logic with multiple valid approaches\n- Error handling strategies\n- Algorithm implementation choices\n- Data structure decisions\n- User experience decisions\n- Design patterns and architecture choices\n\n## How to Request Contributions\n\nBefore requesting code:\n1. Create the file with surrounding context\n2. Add function signature with clear parameters/return type\n3. Include comments explaining the purpose\n4. Mark the location with TODO or clear placeholder\n\nWhen requesting:\n- Explain what you've built and WHY this decision matters\n- Reference the exact file and prepared location\n- Describe trade-offs to consider, constraints, or approaches\n- Frame it as valuable input that shapes the feature, not busy work\n- Keep requests focused (5-10 lines of code)\n\n## Example Request Pattern\n\nContext: I've set up the authentication middleware. The session timeout behavior is a security vs. UX trade-off - should sessions auto-extend on activity, or have a hard timeout? This affects both security posture and user experience.\n\nRequest: In auth/middleware.ts, implement the handleSessionTimeout() function to define the timeout behavior.\n\nGuidance: Consider: auto-extending improves UX but may leave sessions open longer; hard timeouts are more secure but might frustrate active users.\n\n## Balance\n\nDon't request contributions for:\n- Boilerplate or repetitive code\n- Obvious implementations with no meaningful choices\n- Configuration or setup code\n- Simple CRUD operations\n\nDo request contributions when:\n- There are meaningful trade-offs to consider\n- The decision shapes the feature's behavior\n- Multiple valid approaches exist\n- The user's domain knowledge would improve the solution\n\n## Explanatory Mode\n\nAdditionally, provide educational insights about the codebase as you help with tasks. Be clear and educational, providing helpful explanations while remaining focused on the task. Balance educational content with task completion.\n\n### Insights\nBefore and after writing code, provide brief educational explanations about implementation choices using:\n\n\"`★ Insight ─────────────────────────────────────`\n[2-3 key educational points]\n`─────────────────────────────────────────────────`\"\n\nThese insights should be included in the conversation, not in the codebase. Focus on interesting insights specific to the codebase or the code you just wrote, rather than general programming concepts. Provide insights as you write code, not just at the end."
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
exit 0
|
||||
15
plugins/learning-output-style/hooks/hooks.json
Normal file
15
plugins/learning-output-style/hooks/hooks.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"description": "Learning mode hook that adds interactive learning instructions",
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks-handlers/session-start.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
9
plugins/ralph-wiggum/.claude-plugin/plugin.json
Normal file
9
plugins/ralph-wiggum/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "ralph-wiggum",
|
||||
"version": "1.0.0",
|
||||
"description": "Implementation of the Ralph Wiggum technique - continuous self-referential AI loops for interactive iterative development. Run Claude in a while-true loop with the same prompt until task completion.",
|
||||
"author": {
|
||||
"name": "Daisy Hollman",
|
||||
"email": "daisy@anthropic.com"
|
||||
}
|
||||
}
|
||||
179
plugins/ralph-wiggum/README.md
Normal file
179
plugins/ralph-wiggum/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Ralph Wiggum Plugin
|
||||
|
||||
Implementation of the Ralph Wiggum technique for iterative, self-referential AI development loops in Claude Code.
|
||||
|
||||
## What is Ralph?
|
||||
|
||||
Ralph is a development methodology based on continuous AI agent loops. As Geoffrey Huntley describes it: **"Ralph is a Bash loop"** - a simple `while true` that repeatedly feeds an AI agent a prompt file, allowing it to iteratively improve its work until completion.
|
||||
|
||||
The technique is named after Ralph Wiggum from The Simpsons, embodying the philosophy of persistent iteration despite setbacks.
|
||||
|
||||
### Core Concept
|
||||
|
||||
This plugin implements Ralph using a **Stop hook** that intercepts Claude's exit attempts:
|
||||
|
||||
```bash
|
||||
# You run ONCE:
|
||||
/ralph-loop "Your task description" --completion-promise "DONE"
|
||||
|
||||
# Then Claude Code automatically:
|
||||
# 1. Works on the task
|
||||
# 2. Tries to exit
|
||||
# 3. Stop hook blocks exit
|
||||
# 4. Stop hook feeds the SAME prompt back
|
||||
# 5. Repeat until completion
|
||||
```
|
||||
|
||||
The loop happens **inside your current session** - you don't need external bash loops. The Stop hook in `hooks/stop-hook.sh` creates the self-referential feedback loop by blocking normal session exit.
|
||||
|
||||
This creates a **self-referential feedback loop** where:
|
||||
- The prompt never changes between iterations
|
||||
- Claude's previous work persists in files
|
||||
- Each iteration sees modified files and git history
|
||||
- Claude autonomously improves by reading its own past work in files
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
/ralph-loop "Build a REST API for todos. Requirements: CRUD operations, input validation, tests. Output <promise>COMPLETE</promise> when done." --completion-promise "COMPLETE" --max-iterations 50
|
||||
```
|
||||
|
||||
Claude will:
|
||||
- Implement the API iteratively
|
||||
- Run tests and see failures
|
||||
- Fix bugs based on test output
|
||||
- Iterate until all requirements met
|
||||
- Output the completion promise when done
|
||||
|
||||
## Commands
|
||||
|
||||
### /ralph-loop
|
||||
|
||||
Start a Ralph loop in your current session.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
/ralph-loop "<prompt>" --max-iterations <n> --completion-promise "<text>"
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--max-iterations <n>` - Stop after N iterations (default: unlimited)
|
||||
- `--completion-promise <text>` - Phrase that signals completion
|
||||
|
||||
### /cancel-ralph
|
||||
|
||||
Cancel the active Ralph loop.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
/cancel-ralph
|
||||
```
|
||||
|
||||
## Prompt Writing Best Practices
|
||||
|
||||
### 1. Clear Completion Criteria
|
||||
|
||||
❌ Bad: "Build a todo API and make it good."
|
||||
|
||||
✅ Good:
|
||||
```markdown
|
||||
Build a REST API for todos.
|
||||
|
||||
When complete:
|
||||
- All CRUD endpoints working
|
||||
- Input validation in place
|
||||
- Tests passing (coverage > 80%)
|
||||
- README with API docs
|
||||
- Output: <promise>COMPLETE</promise>
|
||||
```
|
||||
|
||||
### 2. Incremental Goals
|
||||
|
||||
❌ Bad: "Create a complete e-commerce platform."
|
||||
|
||||
✅ Good:
|
||||
```markdown
|
||||
Phase 1: User authentication (JWT, tests)
|
||||
Phase 2: Product catalog (list/search, tests)
|
||||
Phase 3: Shopping cart (add/remove, tests)
|
||||
|
||||
Output <promise>COMPLETE</promise> when all phases done.
|
||||
```
|
||||
|
||||
### 3. Self-Correction
|
||||
|
||||
❌ Bad: "Write code for feature X."
|
||||
|
||||
✅ Good:
|
||||
```markdown
|
||||
Implement feature X following TDD:
|
||||
1. Write failing tests
|
||||
2. Implement feature
|
||||
3. Run tests
|
||||
4. If any fail, debug and fix
|
||||
5. Refactor if needed
|
||||
6. Repeat until all green
|
||||
7. Output: <promise>COMPLETE</promise>
|
||||
```
|
||||
|
||||
### 4. Escape Hatches
|
||||
|
||||
Always use `--max-iterations` as a safety net to prevent infinite loops on impossible tasks:
|
||||
|
||||
```bash
|
||||
# Recommended: Always set a reasonable iteration limit
|
||||
/ralph-loop "Try to implement feature X" --max-iterations 20
|
||||
|
||||
# In your prompt, include what to do if stuck:
|
||||
# "After 15 iterations, if not complete:
|
||||
# - Document what's blocking progress
|
||||
# - List what was attempted
|
||||
# - Suggest alternative approaches"
|
||||
```
|
||||
|
||||
**Note**: The `--completion-promise` uses exact string matching, so you cannot use it for multiple completion conditions (like "SUCCESS" vs "BLOCKED"). Always rely on `--max-iterations` as your primary safety mechanism.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Ralph embodies several key principles:
|
||||
|
||||
### 1. Iteration > Perfection
|
||||
Don't aim for perfect on first try. Let the loop refine the work.
|
||||
|
||||
### 2. Failures Are Data
|
||||
"Deterministically bad" means failures are predictable and informative. Use them to tune prompts.
|
||||
|
||||
### 3. Operator Skill Matters
|
||||
Success depends on writing good prompts, not just having a good model.
|
||||
|
||||
### 4. Persistence Wins
|
||||
Keep trying until success. The loop handles retry logic automatically.
|
||||
|
||||
## When to Use Ralph
|
||||
|
||||
**Good for:**
|
||||
- Well-defined tasks with clear success criteria
|
||||
- Tasks requiring iteration and refinement (e.g., getting tests to pass)
|
||||
- Greenfield projects where you can walk away
|
||||
- Tasks with automatic verification (tests, linters)
|
||||
|
||||
**Not good for:**
|
||||
- Tasks requiring human judgment or design decisions
|
||||
- One-shot operations
|
||||
- Tasks with unclear success criteria
|
||||
- Production debugging (use targeted debugging instead)
|
||||
|
||||
## Real-World Results
|
||||
|
||||
- Successfully generated 6 repositories overnight in Y Combinator hackathon testing
|
||||
- One $50k contract completed for $297 in API costs
|
||||
- Created entire programming language ("cursed") over 3 months using this approach
|
||||
|
||||
## Learn More
|
||||
|
||||
- Original technique: https://ghuntley.com/ralph/
|
||||
- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator
|
||||
|
||||
## For Help
|
||||
|
||||
Run `/help` in Claude Code for detailed command reference and examples.
|
||||
26
plugins/ralph-wiggum/commands/cancel-ralph.md
Normal file
26
plugins/ralph-wiggum/commands/cancel-ralph.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
description: "Cancel active Ralph Wiggum loop"
|
||||
allowed-tools: ["Bash"]
|
||||
hide-from-slash-command-tool: "true"
|
||||
---
|
||||
|
||||
# Cancel Ralph
|
||||
|
||||
```!
|
||||
if [[ -f .claude/ralph-loop.local.md ]]; then
|
||||
ITERATION=$(grep '^iteration:' .claude/ralph-loop.local.md | sed 's/iteration: *//')
|
||||
echo "FOUND_LOOP=true"
|
||||
echo "ITERATION=$ITERATION"
|
||||
else
|
||||
echo "FOUND_LOOP=false"
|
||||
fi
|
||||
```
|
||||
|
||||
Check the output above:
|
||||
|
||||
1. **If FOUND_LOOP=false**:
|
||||
- Say "No active Ralph loop found."
|
||||
|
||||
2. **If FOUND_LOOP=true**:
|
||||
- Use Bash: `rm .claude/ralph-loop.local.md`
|
||||
- Report: "Cancelled Ralph loop (was at iteration N)" where N is the ITERATION value from above.
|
||||
126
plugins/ralph-wiggum/commands/help.md
Normal file
126
plugins/ralph-wiggum/commands/help.md
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
description: "Explain Ralph Wiggum technique and available commands"
|
||||
---
|
||||
|
||||
# Ralph Wiggum Plugin Help
|
||||
|
||||
Please explain the following to the user:
|
||||
|
||||
## What is the Ralph Wiggum Technique?
|
||||
|
||||
The Ralph Wiggum technique is an iterative development methodology based on continuous AI loops, pioneered by Geoffrey Huntley.
|
||||
|
||||
**Core concept:**
|
||||
```bash
|
||||
while :; do
|
||||
cat PROMPT.md | claude-code --continue
|
||||
done
|
||||
```
|
||||
|
||||
The same prompt is fed to Claude repeatedly. The "self-referential" aspect comes from Claude seeing its own previous work in the files and git history, not from feeding output back as input.
|
||||
|
||||
**Each iteration:**
|
||||
1. Claude receives the SAME prompt
|
||||
2. Works on the task, modifying files
|
||||
3. Tries to exit
|
||||
4. Stop hook intercepts and feeds the same prompt again
|
||||
5. Claude sees its previous work in the files
|
||||
6. Iteratively improves until completion
|
||||
|
||||
The technique is described as "deterministically bad in an undeterministic world" - failures are predictable, enabling systematic improvement through prompt tuning.
|
||||
|
||||
## Available Commands
|
||||
|
||||
### /ralph-loop <PROMPT> [OPTIONS]
|
||||
|
||||
Start a Ralph loop in your current session.
|
||||
|
||||
**Usage:**
|
||||
```
|
||||
/ralph-loop "Refactor the cache layer" --max-iterations 20
|
||||
/ralph-loop "Add tests" --completion-promise "TESTS COMPLETE"
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--max-iterations <n>` - Max iterations before auto-stop
|
||||
- `--completion-promise <text>` - Promise phrase to signal completion
|
||||
|
||||
**How it works:**
|
||||
1. Creates `.claude/.ralph-loop.local.md` state file
|
||||
2. You work on the task
|
||||
3. When you try to exit, stop hook intercepts
|
||||
4. Same prompt fed back
|
||||
5. You see your previous work
|
||||
6. Continues until promise detected or max iterations
|
||||
|
||||
---
|
||||
|
||||
### /cancel-ralph
|
||||
|
||||
Cancel an active Ralph loop (removes the loop state file).
|
||||
|
||||
**Usage:**
|
||||
```
|
||||
/cancel-ralph
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Checks for active loop state file
|
||||
- Removes `.claude/.ralph-loop.local.md`
|
||||
- Reports cancellation with iteration count
|
||||
|
||||
---
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### Completion Promises
|
||||
|
||||
To signal completion, Claude must output a `<promise>` tag:
|
||||
|
||||
```
|
||||
<promise>TASK COMPLETE</promise>
|
||||
```
|
||||
|
||||
The stop hook looks for this specific tag. Without it (or `--max-iterations`), Ralph runs infinitely.
|
||||
|
||||
### Self-Reference Mechanism
|
||||
|
||||
The "loop" doesn't mean Claude talks to itself. It means:
|
||||
- Same prompt repeated
|
||||
- Claude's work persists in files
|
||||
- Each iteration sees previous attempts
|
||||
- Builds incrementally toward goal
|
||||
|
||||
## Example
|
||||
|
||||
### Interactive Bug Fix
|
||||
|
||||
```
|
||||
/ralph-loop "Fix the token refresh logic in auth.ts. Output <promise>FIXED</promise> when all tests pass." --completion-promise "FIXED" --max-iterations 10
|
||||
```
|
||||
|
||||
You'll see Ralph:
|
||||
- Attempt fixes
|
||||
- Run tests
|
||||
- See failures
|
||||
- Iterate on solution
|
||||
- In your current session
|
||||
|
||||
## When to Use Ralph
|
||||
|
||||
**Good for:**
|
||||
- Well-defined tasks with clear success criteria
|
||||
- Tasks requiring iteration and refinement
|
||||
- Iterative development with self-correction
|
||||
- Greenfield projects
|
||||
|
||||
**Not good for:**
|
||||
- Tasks requiring human judgment or design decisions
|
||||
- One-shot operations
|
||||
- Tasks with unclear success criteria
|
||||
- Debugging production issues (use targeted debugging instead)
|
||||
|
||||
## Learn More
|
||||
|
||||
- Original technique: https://ghuntley.com/ralph/
|
||||
- Ralph Orchestrator: https://github.com/mikeyobrien/ralph-orchestrator
|
||||
48
plugins/ralph-wiggum/commands/ralph-loop.md
Normal file
48
plugins/ralph-wiggum/commands/ralph-loop.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: "Start Ralph Wiggum loop in current session"
|
||||
argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
|
||||
allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh)"]
|
||||
hide-from-slash-command-tool: "true"
|
||||
---
|
||||
|
||||
# Ralph Loop Command
|
||||
|
||||
Execute the setup script to initialize the Ralph loop:
|
||||
|
||||
```!
|
||||
"${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh" $ARGUMENTS
|
||||
|
||||
# Extract and display completion promise if set
|
||||
if [ -f .claude/ralph-loop.local.md ]; then
|
||||
PROMISE=$(grep '^completion_promise:' .claude/ralph-loop.local.md | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
if [ -n "$PROMISE" ] && [ "$PROMISE" != "null" ]; then
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo "CRITICAL - Ralph Loop Completion Promise"
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo "To complete this loop, output this EXACT text:"
|
||||
echo " <promise>$PROMISE</promise>"
|
||||
echo ""
|
||||
echo "STRICT REQUIREMENTS (DO NOT VIOLATE):"
|
||||
echo " ✓ Use <promise> XML tags EXACTLY as shown above"
|
||||
echo " ✓ The statement MUST be completely and unequivocally TRUE"
|
||||
echo " ✓ Do NOT output false statements to exit the loop"
|
||||
echo " ✓ Do NOT lie even if you think you should exit"
|
||||
echo ""
|
||||
echo "IMPORTANT - Do not circumvent the loop:"
|
||||
echo " Even if you believe you're stuck, the task is impossible,"
|
||||
echo " or you've been running too long - you MUST NOT output a"
|
||||
echo " false promise statement. The loop is designed to continue"
|
||||
echo " until the promise is GENUINELY TRUE. Trust the process."
|
||||
echo ""
|
||||
echo " If the loop should stop, the promise statement will become"
|
||||
echo " true naturally. Do not force it by lying."
|
||||
echo "═══════════════════════════════════════════════════════════"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
Please work on the task. When you try to exit, the Ralph loop will feed the SAME PROMPT back to you for the next iteration. You'll see your previous work in files and git history, allowing you to iterate and improve.
|
||||
|
||||
CRITICAL RULE: If a completion promise is set, you may ONLY output it when the statement is completely and unequivocally TRUE. Do not output false promises to escape the loop, even if you think you're stuck or should exit for other reasons. The loop is designed to continue until genuine completion.
|
||||
15
plugins/ralph-wiggum/hooks/hooks.json
Normal file
15
plugins/ralph-wiggum/hooks/hooks.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"description": "Ralph Wiggum plugin stop hook for self-referential loops",
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
177
plugins/ralph-wiggum/hooks/stop-hook.sh
Executable file
177
plugins/ralph-wiggum/hooks/stop-hook.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Ralph Wiggum Stop Hook
|
||||
# Prevents session exit when a ralph-loop is active
|
||||
# Feeds Claude's output back as input to continue the loop
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Read hook input from stdin (advanced stop hook API)
|
||||
HOOK_INPUT=$(cat)
|
||||
|
||||
# Check if ralph-loop is active
|
||||
RALPH_STATE_FILE=".claude/ralph-loop.local.md"
|
||||
|
||||
if [[ ! -f "$RALPH_STATE_FILE" ]]; then
|
||||
# No active loop - allow exit
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse markdown frontmatter (YAML between ---) and extract values
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$RALPH_STATE_FILE")
|
||||
ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
|
||||
MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')
|
||||
# Extract completion_promise and strip surrounding quotes if present
|
||||
COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
|
||||
# Validate numeric fields before arithmetic operations
|
||||
if [[ ! "$ITERATION" =~ ^[0-9]+$ ]]; then
|
||||
echo "⚠️ Ralph loop: State file corrupted" >&2
|
||||
echo " File: $RALPH_STATE_FILE" >&2
|
||||
echo " Problem: 'iteration' field is not a valid number (got: '$ITERATION')" >&2
|
||||
echo "" >&2
|
||||
echo " This usually means the state file was manually edited or corrupted." >&2
|
||||
echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
|
||||
echo "⚠️ Ralph loop: State file corrupted" >&2
|
||||
echo " File: $RALPH_STATE_FILE" >&2
|
||||
echo " Problem: 'max_iterations' field is not a valid number (got: '$MAX_ITERATIONS')" >&2
|
||||
echo "" >&2
|
||||
echo " This usually means the state file was manually edited or corrupted." >&2
|
||||
echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if max iterations reached
|
||||
if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
|
||||
echo "🛑 Ralph loop: Max iterations ($MAX_ITERATIONS) reached."
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get transcript path from hook input
|
||||
TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path')
|
||||
|
||||
if [[ ! -f "$TRANSCRIPT_PATH" ]]; then
|
||||
echo "⚠️ Ralph loop: Transcript file not found" >&2
|
||||
echo " Expected: $TRANSCRIPT_PATH" >&2
|
||||
echo " This is unusual and may indicate a Claude Code internal issue." >&2
|
||||
echo " Ralph loop is stopping." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read last assistant message from transcript (JSONL format - one JSON per line)
|
||||
# First check if there are any assistant messages
|
||||
if ! grep -q '"role":"assistant"' "$TRANSCRIPT_PATH"; then
|
||||
echo "⚠️ Ralph loop: No assistant messages found in transcript" >&2
|
||||
echo " Transcript: $TRANSCRIPT_PATH" >&2
|
||||
echo " This is unusual and may indicate a transcript format issue" >&2
|
||||
echo " Ralph loop is stopping." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract last assistant message with explicit error handling
|
||||
LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1)
|
||||
if [[ -z "$LAST_LINE" ]]; then
|
||||
echo "⚠️ Ralph loop: Failed to extract last assistant message" >&2
|
||||
echo " Ralph loop is stopping." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse JSON with error handling
|
||||
LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r '
|
||||
.message.content |
|
||||
map(select(.type == "text")) |
|
||||
map(.text) |
|
||||
join("\n")
|
||||
' 2>&1)
|
||||
|
||||
# Check if jq succeeded
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "⚠️ Ralph loop: Failed to parse assistant message JSON" >&2
|
||||
echo " Error: $LAST_OUTPUT" >&2
|
||||
echo " This may indicate a transcript format issue" >&2
|
||||
echo " Ralph loop is stopping." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -z "$LAST_OUTPUT" ]]; then
|
||||
echo "⚠️ Ralph loop: Assistant message contained no text content" >&2
|
||||
echo " Ralph loop is stopping." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for completion promise (only if set)
|
||||
if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then
|
||||
# Extract text from <promise> tags using Perl for multiline support
|
||||
# -0777 slurps entire input, s flag makes . match newlines
|
||||
# .*? is non-greedy (takes FIRST tag), whitespace normalized
|
||||
PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
|
||||
|
||||
# Use = for literal string comparison (not pattern matching)
|
||||
# == in [[ ]] does glob pattern matching which breaks with *, ?, [ characters
|
||||
if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
|
||||
echo "✅ Ralph loop: Detected <promise>$COMPLETION_PROMISE</promise>"
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Not complete - continue loop with SAME PROMPT
|
||||
NEXT_ITERATION=$((ITERATION + 1))
|
||||
|
||||
# Extract prompt (everything after the closing ---)
|
||||
# Skip first --- line, skip until second --- line, then print everything after
|
||||
# Use i>=2 instead of i==2 to handle --- in prompt content
|
||||
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$RALPH_STATE_FILE")
|
||||
|
||||
if [[ -z "$PROMPT_TEXT" ]]; then
|
||||
echo "⚠️ Ralph loop: State file corrupted or incomplete" >&2
|
||||
echo " File: $RALPH_STATE_FILE" >&2
|
||||
echo " Problem: No prompt text found" >&2
|
||||
echo "" >&2
|
||||
echo " This usually means:" >&2
|
||||
echo " • State file was manually edited" >&2
|
||||
echo " • File was corrupted during writing" >&2
|
||||
echo "" >&2
|
||||
echo " Ralph loop is stopping. Run /ralph-loop again to start fresh." >&2
|
||||
rm "$RALPH_STATE_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Update iteration in frontmatter (portable across macOS and Linux)
|
||||
# Create temp file, then atomically replace
|
||||
TEMP_FILE="${RALPH_STATE_FILE}.tmp.$$"
|
||||
sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$RALPH_STATE_FILE" > "$TEMP_FILE"
|
||||
mv "$TEMP_FILE" "$RALPH_STATE_FILE"
|
||||
|
||||
# Build system message with iteration count and completion promise info
|
||||
if [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$COMPLETION_PROMISE" ]]; then
|
||||
SYSTEM_MSG="🔄 Ralph iteration $NEXT_ITERATION | To stop: output <promise>$COMPLETION_PROMISE</promise> (ONLY when statement is TRUE - do not lie to exit!)"
|
||||
else
|
||||
SYSTEM_MSG="🔄 Ralph iteration $NEXT_ITERATION | No completion promise set - loop runs infinitely"
|
||||
fi
|
||||
|
||||
# Output JSON to block the stop and feed prompt back
|
||||
# The "reason" field contains the prompt that will be sent back to Claude
|
||||
jq -n \
|
||||
--arg prompt "$PROMPT_TEXT" \
|
||||
--arg msg "$SYSTEM_MSG" \
|
||||
'{
|
||||
"decision": "block",
|
||||
"reason": $prompt,
|
||||
"systemMessage": $msg
|
||||
}'
|
||||
|
||||
# Exit 0 for successful hook execution
|
||||
exit 0
|
||||
176
plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
Executable file
176
plugins/ralph-wiggum/scripts/setup-ralph-loop.sh
Executable file
@@ -0,0 +1,176 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Ralph Loop Setup Script
|
||||
# Creates state file for in-session Ralph loop
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Parse arguments
|
||||
PROMPT_PARTS=()
|
||||
MAX_ITERATIONS=0
|
||||
COMPLETION_PROMISE="null"
|
||||
|
||||
# Parse options and positional arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-h|--help)
|
||||
cat << 'HELP_EOF'
|
||||
Ralph Loop - Interactive self-referential development loop
|
||||
|
||||
USAGE:
|
||||
/ralph-loop [PROMPT...] [OPTIONS]
|
||||
|
||||
ARGUMENTS:
|
||||
PROMPT... Initial prompt to start the loop (can be multiple words without quotes)
|
||||
|
||||
OPTIONS:
|
||||
--max-iterations <n> Maximum iterations before auto-stop (default: unlimited)
|
||||
--completion-promise '<text>' Promise phrase (USE QUOTES for multi-word)
|
||||
-h, --help Show this help message
|
||||
|
||||
DESCRIPTION:
|
||||
Starts a Ralph Wiggum loop in your CURRENT session. The stop hook prevents
|
||||
exit and feeds your output back as input until completion or iteration limit.
|
||||
|
||||
To signal completion, you must output: <promise>YOUR_PHRASE</promise>
|
||||
|
||||
Use this for:
|
||||
- Interactive iteration where you want to see progress
|
||||
- Tasks requiring self-correction and refinement
|
||||
- Learning how Ralph works
|
||||
|
||||
EXAMPLES:
|
||||
/ralph-loop Build a todo API --completion-promise 'DONE' --max-iterations 20
|
||||
/ralph-loop --max-iterations 10 Fix the auth bug
|
||||
/ralph-loop Refactor cache layer (runs forever)
|
||||
/ralph-loop --completion-promise 'TASK COMPLETE' Create a REST API
|
||||
|
||||
STOPPING:
|
||||
Only by reaching --max-iterations or detecting --completion-promise
|
||||
No manual stop - Ralph runs infinitely by default!
|
||||
|
||||
MONITORING:
|
||||
# View current iteration:
|
||||
grep '^iteration:' .claude/ralph-loop.local.md
|
||||
|
||||
# View full state:
|
||||
head -10 .claude/ralph-loop.local.md
|
||||
HELP_EOF
|
||||
exit 0
|
||||
;;
|
||||
--max-iterations)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "❌ Error: --max-iterations requires a number argument" >&2
|
||||
echo "" >&2
|
||||
echo " Valid examples:" >&2
|
||||
echo " --max-iterations 10" >&2
|
||||
echo " --max-iterations 50" >&2
|
||||
echo " --max-iterations 0 (unlimited)" >&2
|
||||
echo "" >&2
|
||||
echo " You provided: --max-iterations (with no number)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
|
||||
echo "❌ Error: --max-iterations must be a positive integer or 0, got: $2" >&2
|
||||
echo "" >&2
|
||||
echo " Valid examples:" >&2
|
||||
echo " --max-iterations 10" >&2
|
||||
echo " --max-iterations 50" >&2
|
||||
echo " --max-iterations 0 (unlimited)" >&2
|
||||
echo "" >&2
|
||||
echo " Invalid: decimals (10.5), negative numbers (-5), text" >&2
|
||||
exit 1
|
||||
fi
|
||||
MAX_ITERATIONS="$2"
|
||||
shift 2
|
||||
;;
|
||||
--completion-promise)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "❌ Error: --completion-promise requires a text argument" >&2
|
||||
echo "" >&2
|
||||
echo " Valid examples:" >&2
|
||||
echo " --completion-promise 'DONE'" >&2
|
||||
echo " --completion-promise 'TASK COMPLETE'" >&2
|
||||
echo " --completion-promise 'All tests passing'" >&2
|
||||
echo "" >&2
|
||||
echo " You provided: --completion-promise (with no text)" >&2
|
||||
echo "" >&2
|
||||
echo " Note: Multi-word promises must be quoted!" >&2
|
||||
exit 1
|
||||
fi
|
||||
COMPLETION_PROMISE="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
# Non-option argument - collect all as prompt parts
|
||||
PROMPT_PARTS+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Join all prompt parts with spaces
|
||||
PROMPT="${PROMPT_PARTS[*]}"
|
||||
|
||||
# Validate prompt is non-empty
|
||||
if [[ -z "$PROMPT" ]]; then
|
||||
echo "❌ Error: No prompt provided" >&2
|
||||
echo "" >&2
|
||||
echo " Ralph needs a task description to work on." >&2
|
||||
echo "" >&2
|
||||
echo " Examples:" >&2
|
||||
echo " /ralph-loop Build a REST API for todos" >&2
|
||||
echo " /ralph-loop Fix the auth bug --max-iterations 20" >&2
|
||||
echo " /ralph-loop --completion-promise 'DONE' Refactor code" >&2
|
||||
echo "" >&2
|
||||
echo " For all options: /ralph-loop --help" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create state file for stop hook (markdown with YAML frontmatter)
|
||||
mkdir -p .claude
|
||||
|
||||
# Quote completion promise for YAML if it contains special chars or is not null
|
||||
if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then
|
||||
COMPLETION_PROMISE_YAML="\"$COMPLETION_PROMISE\""
|
||||
else
|
||||
COMPLETION_PROMISE_YAML="null"
|
||||
fi
|
||||
|
||||
cat > .claude/ralph-loop.local.md <<EOF
|
||||
---
|
||||
active: true
|
||||
iteration: 1
|
||||
max_iterations: $MAX_ITERATIONS
|
||||
completion_promise: $COMPLETION_PROMISE_YAML
|
||||
started_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
---
|
||||
|
||||
$PROMPT
|
||||
EOF
|
||||
|
||||
# Output setup message
|
||||
cat <<EOF
|
||||
🔄 Ralph loop activated in this session!
|
||||
|
||||
Iteration: 1
|
||||
Max iterations: $(if [[ $MAX_ITERATIONS -gt 0 ]]; then echo $MAX_ITERATIONS; else echo "unlimited"; fi)
|
||||
Completion promise: $(if [[ "$COMPLETION_PROMISE" != "null" ]]; then echo "${COMPLETION_PROMISE//\"/} (ONLY output when TRUE - do not lie!)"; else echo "none (runs forever)"; fi)
|
||||
|
||||
The stop hook is now active. When you try to exit, the SAME PROMPT will be
|
||||
fed back to you. You'll see your previous work in files, creating a
|
||||
self-referential loop where you iteratively improve on the same task.
|
||||
|
||||
To monitor: head -10 .claude/ralph-loop.local.md
|
||||
|
||||
⚠️ WARNING: This loop cannot be stopped manually! It will run infinitely
|
||||
unless you set --max-iterations or --completion-promise.
|
||||
|
||||
🔄
|
||||
EOF
|
||||
|
||||
# Output the initial prompt if provided
|
||||
if [[ -n "$PROMPT" ]]; then
|
||||
echo ""
|
||||
echo "$PROMPT"
|
||||
fi
|
||||
Reference in New Issue
Block a user