Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Learn more in the [official plugins documentation](https://docs.claude.com/en/do
| [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<br>**Agents:** 5 parallel Sonnet agents for CLAUDE.md compliance, bug detection, historical context, PR history, and code comments |
| [commit-commands](./commit-commands/) | Git workflow automation for committing, pushing, and creating pull requests | **Commands:** `/commit`, `/commit-push-pr`, `/clean_gone` - Streamlined git operations |
| [edit-verifier](./edit-verifier/) | Verifies file edits applied correctly by reading the file back after Edit operations | **Hook:** PostToolUse - Checks that new content exists in the file after each Edit, warns if not found |
| [explanatory-output-style](./explanatory-output-style/) | Adds educational insights about implementation choices and codebase patterns (mimics the deprecated Explanatory output style) | **Hook:** SessionStart - Injects educational context at the start of each session |
| [feature-dev](./feature-dev/) | Comprehensive feature development workflow with a structured 7-phase approach | **Command:** `/feature-dev` - Guided feature development workflow<br>**Agents:** `code-explorer`, `code-architect`, `code-reviewer` - For codebase analysis, architecture design, and quality review |
| [frontend-design](./frontend-design/) | Create distinctive, production-grade frontend interfaces that avoid generic AI aesthetics | **Skill:** `frontend-design` - Auto-invoked for frontend work, providing guidance on bold design choices, typography, animations, and visual details |
Expand Down
5 changes: 5 additions & 0 deletions plugins/edit-verifier/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "edit-verifier",
"version": "1.0.0",
"description": "PostToolUse hook that verifies file edits applied correctly by reading the file back after Edit operations"
}
40 changes: 40 additions & 0 deletions plugins/edit-verifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Edit Verifier

A PostToolUse hook plugin that verifies file edits applied correctly by reading the file back after Edit operations.

## Problem

When Claude uses the Edit tool, it assumes the edit succeeded and proceeds without verifying. If the target text wasn't found (whitespace mismatch, code already modified, partial match), the edit silently fails and Claude continues with incorrect assumptions.

## How it works

After each Edit tool operation, this plugin:

1. Reads the edited file back from disk
2. Checks that `new_string` from the edit is present in the file
3. If **not found**, sends a warning to Claude suggesting it read the file to verify
4. If **found**, stays silent (no unnecessary output)

The plugin is non-blocking - it warns but never denies operations.

## Installation

```bash
claude plugin install edit-verifier
```

Or add to your project's `.claude/settings.json`:

```json
{
"plugins": ["edit-verifier"]
}
```

## Configuration

No configuration needed. The hook runs automatically after every Edit operation.

**Skipped cases:**
- Empty edits or replacements shorter than 5 characters (avoids false positives on whitespace)
- Files that can't be read (warns instead of blocking)
16 changes: 16 additions & 0 deletions plugins/edit-verifier/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"description": "Verifies file edits applied correctly by checking the new content exists in the file after an Edit operation",
"hooks": {
"PostToolUse": [
{
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/post_edit_verify.py"
}
],
"matcher": "Edit"
}
]
}
}
63 changes: 63 additions & 0 deletions plugins/edit-verifier/hooks/post_edit_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Post-edit verification hook for Claude Code.

After an Edit tool operation, reads the file back and checks that the
new_string is present. If not found, sends a systemMessage warning
Claude that the edit may not have applied correctly.
"""

import json
import sys


def main():
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError:
sys.exit(0)

tool_name = input_data.get("tool_name", "")
if tool_name != "Edit":
json.dump({}, sys.stdout)
sys.exit(0)

tool_input = input_data.get("tool_input", {})
file_path = tool_input.get("file_path", "")
new_string = tool_input.get("new_string", "")

# Skip verification for empty edits or very short replacements
if not file_path or not new_string or len(new_string.strip()) < 5:
json.dump({}, sys.stdout)
sys.exit(0)

# Read the file and check if new_string is present
try:
with open(file_path, "r") as f:
content = f.read()
except (FileNotFoundError, PermissionError, OSError):
# Can't read file - don't block, just warn
result = {
"systemMessage": (
f"Edit verification: Could not read {file_path} to verify "
f"the edit applied correctly. Please verify manually."
)
}
json.dump(result, sys.stdout)
sys.exit(0)

if new_string not in content:
result = {
"systemMessage": (
f"Edit verification warning: The expected new content was not "
f"found in {file_path} after the Edit operation. The edit may "
f"not have applied correctly. Please read the file to verify."
)
}
json.dump(result, sys.stdout)
else:
# Edit verified successfully - silent
json.dump({}, sys.stdout)


if __name__ == "__main__":
main()