diff --git a/plugins/README.md b/plugins/README.md index cf4a21ec..9aba71fd 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -12,6 +12,7 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do | Name | Description | Contents | |------|-------------|----------| +| [accept-with-feedback](./accept-with-feedback/) | Accept permission requests with feedback - provide guidance to Claude when approving operations | **Commands:** `/accept-feedback`, `/configure-feedback` - Set one-time or persistent feedback
**Hook:** PermissionRequest - Intercepts permissions to add user guidance | | [agent-sdk-dev](./agent-sdk-dev/) | Development kit for working with the Claude Agent SDK | **Command:** `/new-sdk-app` - Interactive setup for new Agent SDK projects
**Agents:** `agent-sdk-verifier-py`, `agent-sdk-verifier-ts` - Validate SDK applications against best practices | | [claude-opus-4-5-migration](./claude-opus-4-5-migration/) | Migrate code and prompts from Sonnet 4.x and Opus 4.1 to Opus 4.5 | **Skill:** `claude-opus-4-5-migration` - Automated migration of model strings, beta headers, and prompt adjustments | | [code-review](./code-review/) | Automated PR code review using multiple specialized agents with confidence-based scoring to filter false positives | **Command:** `/code-review` - Automated PR review workflow
**Agents:** 5 parallel Sonnet agents for CLAUDE.md compliance, bug detection, historical context, PR history, and code comments | diff --git a/plugins/accept-with-feedback/.claude-plugin/plugin.json b/plugins/accept-with-feedback/.claude-plugin/plugin.json new file mode 100644 index 00000000..3177405b --- /dev/null +++ b/plugins/accept-with-feedback/.claude-plugin/plugin.json @@ -0,0 +1,9 @@ +{ + "name": "accept-with-feedback", + "version": "1.0.0", + "description": "Accept permission requests with feedback - provide guidance to Claude when approving operations", + "author": { + "name": "Anthropic", + "email": "support@anthropic.com" + } +} diff --git a/plugins/accept-with-feedback/README.md b/plugins/accept-with-feedback/README.md new file mode 100644 index 00000000..ac4abefe --- /dev/null +++ b/plugins/accept-with-feedback/README.md @@ -0,0 +1,151 @@ +# Accept with Feedback + +A Claude Code plugin that lets you approve operations while providing guidance to Claude. Instead of just accepting or rejecting permission requests, you can accept *with feedback* - approving the operation while giving Claude additional context or instructions. + +## Why? + +Sometimes you want to approve an operation but also want to guide Claude's behavior: + +- "Yes, edit that file, but make sure to add error handling" +- "Okay to run those tests, but skip the slow integration tests" +- "Go ahead and commit, but use conventional commit format" + +This plugin bridges the gap between simple approval and rejection-with-feedback. + +## Installation + +This plugin is included in the Claude Code repository. Enable it in your settings or use: + +```bash +claude /plugin install accept-with-feedback +``` + +## Usage + +### One-time feedback + +Use the `/accept-feedback` command to queue feedback for the next permission request: + +``` +/accept-feedback Make sure to preserve backwards compatibility +``` + +When Claude next asks for permission (e.g., to edit a file), the operation will be automatically approved and Claude will receive your guidance as a system message. + +### Persistent feedback rules + +Create rules that automatically provide feedback for certain types of operations. Add configuration to `.claude/accept-feedback.json`: + +```json +{ + "rules": [ + { + "matcher": "Edit|Write", + "conditions": { + "file_path": ".py" + }, + "feedback": "Follow PEP 8 style and add type hints to all functions." + }, + { + "matcher": "Bash", + "conditions": { + "command": "npm" + }, + "feedback": "Use --legacy-peer-deps if you encounter peer dependency issues." + } + ] +} +``` + +Use `/configure-feedback` for an interactive configuration experience. + +## Configuration + +### Rule properties + +| Property | Description | Example | +|----------|-------------|---------| +| `matcher` | Tool name pattern | `"Edit"`, `"Write\|Edit"`, `"*"` | +| `conditions` | Optional filters on tool input | `{"file_path": ".ts"}` | +| `feedback` | Guidance message for Claude | `"Add JSDoc comments"` | + +### Configuration locations + +- **User-level**: `~/.claude/accept-feedback.json` +- **Project-level**: `.claude/accept-feedback.json` (takes precedence) + +## Commands + +| Command | Description | +|---------|-------------| +| `/accept-feedback ` | Queue feedback for the next permission request | +| `/configure-feedback` | Interactive configuration of persistent rules | + +## How it works + +1. The plugin uses a `PermissionRequest` hook to intercept permission requests +2. When a permission request occurs: + - If there's pending feedback (from `/accept-feedback`), approve with that feedback + - If a configured rule matches, approve with the rule's feedback + - Otherwise, let the normal permission flow proceed +3. Feedback is sent to Claude as a system message, providing guidance for the operation + +## Examples + +### Example 1: One-time guidance + +``` +You: /accept-feedback Please add comprehensive error handling + +Claude: I'll edit src/api.ts to add the new endpoint... +[Permission automatically approved with your feedback] + +Claude: I've added the endpoint with try-catch blocks and proper error responses... +``` + +### Example 2: Persistent Python style rules + +`.claude/accept-feedback.json`: +```json +{ + "rules": [ + { + "matcher": "Edit|Write", + "conditions": { + "file_path": ".py" + }, + "feedback": "Use Google-style docstrings and add type hints to all function signatures." + } + ] +} +``` + +Every time Claude edits a Python file, it receives this style guidance. + +### Example 3: Git workflow rules + +```json +{ + "rules": [ + { + "matcher": "Bash", + "conditions": { + "command": "git commit" + }, + "feedback": "Use conventional commit format: type(scope): description" + } + ] +} +``` + +## Tips + +- Use specific matchers to avoid unnecessary approvals +- Conditions use substring matching - `".py"` matches any path containing `.py` +- Combine with other permission management for a comprehensive workflow +- Feedback is visible in the conversation, so Claude can reference it + +## Related + +- Rejecting with feedback: Built into Claude Code's plan rejection flow +- Permission hooks: See the [hook development guide](../plugin-dev/skills/hook-development/SKILL.md) diff --git a/plugins/accept-with-feedback/commands/accept-feedback.md b/plugins/accept-with-feedback/commands/accept-feedback.md new file mode 100644 index 00000000..3618eed5 --- /dev/null +++ b/plugins/accept-with-feedback/commands/accept-feedback.md @@ -0,0 +1,63 @@ +--- +description: Set feedback to provide when accepting the next permission request +argument_name: feedback +--- + +# Accept with Feedback + +You are helping the user set feedback that will be automatically provided to Claude when the next permission request is approved. + +## What the user wants + +The user wants to approve an upcoming operation but also provide guidance or feedback to Claude. This feedback will: +1. Automatically approve the next permission request +2. Send the feedback message to Claude as guidance + +## Instructions + +1. Parse the user's feedback from the argument: `$ARGUMENTS` + +2. If feedback was provided, save it for the next permission request: + - Create/update the file `~/.claude/pending-accept-feedback.json` + - Store the feedback with the current session ID + +3. Confirm to the user that their feedback has been queued + +## Saving the feedback + +Use this Python code to save the pending feedback: + +```python +import json +import os +from pathlib import Path + +feedback = """$ARGUMENTS""" +session_id = os.environ.get("CLAUDE_SESSION_ID", "default") + +pending_file = Path.home() / ".claude" / "pending-accept-feedback.json" +pending_file.parent.mkdir(parents=True, exist_ok=True) + +try: + existing = json.loads(pending_file.read_text()) if pending_file.exists() else {} +except: + existing = {} + +existing[session_id] = { + "message": feedback, + "one_time": True +} + +pending_file.write_text(json.dumps(existing, indent=2)) +print(f"Feedback queued for next permission request.") +``` + +## Example usage + +User runs: `/accept-feedback Make sure to add error handling` + +Then when Claude asks for permission to edit a file, the operation is automatically approved and Claude receives the guidance: "Make sure to add error handling" + +## Response + +After saving, confirm: "Your feedback has been queued. The next permission request will be automatically approved, and Claude will receive your guidance." diff --git a/plugins/accept-with-feedback/commands/configure-feedback.md b/plugins/accept-with-feedback/commands/configure-feedback.md new file mode 100644 index 00000000..f34acff8 --- /dev/null +++ b/plugins/accept-with-feedback/commands/configure-feedback.md @@ -0,0 +1,80 @@ +--- +description: Configure persistent feedback rules for accept-with-feedback +--- + +# Configure Accept-with-Feedback Rules + +You are helping the user configure persistent feedback rules that automatically provide guidance to Claude when certain operations are approved. + +## Configuration file location + +Rules are stored in `.claude/accept-feedback.json` in either: +- User's home directory (`~/.claude/accept-feedback.json`) for global rules +- Project directory (`.claude/accept-feedback.json`) for project-specific rules + +Project rules take precedence over user rules. + +## Configuration format + +```json +{ + "rules": [ + { + "matcher": "Edit|Write", + "conditions": { + "file_path": ".py" + }, + "feedback": "Ensure all Python code follows PEP 8 style guidelines and includes type hints." + }, + { + "matcher": "Bash", + "conditions": { + "command": "git" + }, + "feedback": "Use conventional commit format for commit messages." + }, + { + "matcher": "*", + "feedback": "Please explain your reasoning before making changes." + } + ] +} +``` + +## Rule properties + +- **matcher**: Tool name pattern (e.g., "Edit", "Write|Edit", "*" for all) +- **conditions**: Optional key-value pairs to match against tool input +- **feedback**: The guidance message to send to Claude when this rule matches + +## Instructions + +1. Ask the user what kind of feedback rules they want to configure +2. Help them create appropriate rules based on their needs +3. Save the configuration to the appropriate location + +## Common use cases + +1. **Code style guidance**: Provide style guidelines when editing specific file types +2. **Git workflow**: Add commit message format requirements for git operations +3. **Safety reminders**: Add warnings when working with sensitive files +4. **Project conventions**: Enforce project-specific patterns and practices + +## Example interaction + +User: "I want to always remind Claude to add tests when editing Python files" + +You would create: +```json +{ + "rules": [ + { + "matcher": "Edit|Write", + "conditions": { + "file_path": ".py" + }, + "feedback": "Remember to add or update tests for any code changes." + } + ] +} +``` diff --git a/plugins/accept-with-feedback/hooks/accept_with_feedback.py b/plugins/accept-with-feedback/hooks/accept_with_feedback.py new file mode 100644 index 00000000..f9da1ab6 --- /dev/null +++ b/plugins/accept-with-feedback/hooks/accept_with_feedback.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +""" +Accept with Feedback Hook for Claude Code + +This hook intercepts permission requests and allows users to provide feedback +when accepting operations. The feedback is passed to Claude as a system message, +giving users a way to provide guidance while still approving the operation. + +Usage: +1. Set pending feedback: /accept-feedback "your guidance here" +2. When a permission prompt appears, if pending feedback exists: + - The operation is automatically approved + - Your feedback is sent to Claude as guidance +3. Or configure always-on feedback rules in .claude/accept-feedback.json +""" + +import json +import os +import sys +from datetime import datetime +from pathlib import Path + + +def get_config_paths(): + """Get paths for config and state files.""" + claude_dir = Path.home() / ".claude" + project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", ".")) + + return { + "user_config": claude_dir / "accept-feedback.json", + "project_config": project_dir / ".claude" / "accept-feedback.json", + "pending_feedback": claude_dir / "pending-accept-feedback.json", + } + + +def load_json_file(path): + """Load JSON from file, return empty dict if not found.""" + try: + if path.exists(): + with open(path, "r") as f: + return json.load(f) + except (json.JSONDecodeError, IOError): + pass + return {} + + +def get_pending_feedback(session_id): + """Get pending feedback for this session and clear it.""" + paths = get_config_paths() + pending_file = paths["pending_feedback"] + + pending = load_json_file(pending_file) + + # Check for session-specific pending feedback + if session_id in pending: + feedback = pending[session_id] + # Clear the pending feedback after use + del pending[session_id] + try: + if pending: + with open(pending_file, "w") as f: + json.dump(pending, f) + else: + pending_file.unlink(missing_ok=True) + except IOError: + pass + return feedback.get("message"), feedback.get("one_time", True) + + return None, True + + +def get_configured_feedback(tool_name, tool_input): + """Get feedback configured for this tool type.""" + paths = get_config_paths() + + # Load both user and project configs + user_config = load_json_file(paths["user_config"]) + project_config = load_json_file(paths["project_config"]) + + # Project config takes precedence + config = {**user_config, **project_config} + + rules = config.get("rules", []) + + for rule in rules: + # Check if rule matches this tool + matcher = rule.get("matcher", "*") + if matcher == "*" or tool_name in matcher.split("|"): + # Check conditions if specified + conditions = rule.get("conditions", {}) + matches = True + + for key, pattern in conditions.items(): + value = str(tool_input.get(key, "")) + if pattern not in value: + matches = False + break + + if matches: + return rule.get("feedback") + + return None + + +def main(): + """Main hook function.""" + # Read input from stdin + try: + raw_input = sys.stdin.read() + input_data = json.loads(raw_input) + except json.JSONDecodeError: + # Can't parse input, don't interfere + sys.exit(0) + + session_id = input_data.get("session_id", "default") + tool_name = input_data.get("tool_name", "") + tool_input = input_data.get("tool_input", {}) + + # Check for pending feedback first (from /accept-feedback command) + pending_message, _ = get_pending_feedback(session_id) + + if pending_message: + # We have pending feedback - approve with the feedback as system message + result = { + "hookSpecificOutput": { + "permissionDecision": "allow" + }, + "systemMessage": f"[User Feedback on Approval]: {pending_message}" + } + print(json.dumps(result)) + sys.exit(0) + + # Check for configured feedback rules + configured_feedback = get_configured_feedback(tool_name, tool_input) + + if configured_feedback: + # We have configured feedback for this tool type + result = { + "hookSpecificOutput": { + "permissionDecision": "allow" + }, + "systemMessage": f"[User Guidance]: {configured_feedback}" + } + print(json.dumps(result)) + sys.exit(0) + + # No feedback configured - don't interfere with the normal permission flow + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/plugins/accept-with-feedback/hooks/hooks.json b/plugins/accept-with-feedback/hooks/hooks.json new file mode 100644 index 00000000..ca154607 --- /dev/null +++ b/plugins/accept-with-feedback/hooks/hooks.json @@ -0,0 +1,17 @@ +{ + "description": "Accept with feedback - allows users to provide guidance when approving tool permissions", + "hooks": { + "PermissionRequest": [ + { + "matcher": "*", + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/accept_with_feedback.py", + "timeout": 300 + } + ] + } + ] + } +}