surgical
README.md
# surgical ✂️
**Enforce Karpathy's Rule #3. Automatically prune AI diff bloat and over-editing before you commit.**
When you ask an AI coding agent to fix a 2-line bug, research by Tongyao Zhu, Wei Hern Lim, and Min-Yen Kan shows it routinely rewrites 40–100 lines: renaming variables in unrelated files, reordering imports, and touching working code you never asked it to touch ([arXiv:2609.04061](https://arxiv.org/abs/2609.04061)). That over-editing shows up in **any** stack—Next.js, FastAPI, Go, Rust, SQL migrations—especially on generic names like `page.tsx`, `main.py`, `app.py`, `utils.ts`, and `lib.rs`.
`surgical` is a language-agnostic MCP server and CLI. It audits uncommitted Git changes against your stated intent using **Universal Lexical Topology** (sub-word tokens, an identifier graph, and cosmetic normalization)—not per-language parsers or fragile filenames—and strips unrequested drive-by refactors before you commit.
Zero network dependencies. Operates 100% locally on your Git working tree in `<5ms`. No Python runtime, no tree-sitter, no extra language tooling.
*(Currently in pre-release testing — install directly from source)*.
---
## The Rule We Enforce
> **Andrej Karpathy's Rule #3 for Coding Agents:**
> *"Surgical Changes Only — touch only what was asked, never refactor adjacent code, one diff one scope."*
---
## Why Filename Heuristics Fail
Most naive diff cleaners check filenames against keywords in your prompt. That breaks immediately on modern apps:
* In the **Next.js App Router**, almost every file is named `page.tsx`, `layout.tsx`, or `route.ts`.
* Shared logic lives in `utils.ts`, `helpers.ts`, `lib.rs`, `utils.py`, or `helpers.go`.
* In **Python** services (FastAPI, Django, Flask), the same trap is `main.py`, `app.py`, `models.py`, and `config.py`.
If your prompt is *"Fix the mobile navbar"*, a filename matcher will blindly discard `app/page.tsx` and break your build. If it is *"Add a `/healthz` endpoint"*, the same matcher will drop `main.py` or keep an unrelated `models.py` because the filename looks "important."
### How `surgical` beats filenames (Universal Lexical Topology)
1. **Sub-word tokenizer:** camelCase, PascalCase, snake_case, kebab-case, and SCREAMING_SNAKE are split into stems. Prompt *"Fix mobile navbar toggle"* → `{mobile, navbar, toggle}`. That matches `NavbarToggle`, `navbar_toggle`, and `toggle_nav()` in TypeScript, Python, Go, or Rust.
2. **Identifier graph (no import parsing):** Novel identifiers are taken from **seed hunks** that match the intent. Files that share those identifiers are **KEEP**. Files with zero overlap to the seed cluster are **BLOAT**.
3. **Cosmetic normalization:** Comments (`//`, `#`, `--`, `/* */`, `""" """`, `''' '''`) and whitespace are stripped. If before equals after, the hunk is formatter churn (black / isort / prettier / gofmt / rustfmt) and is pruned even if it contains identifiers.
Generic-file examples (`main.py`, `app.py`, `page.tsx`, `utils.ts`) are scored by hunk content and identifier overlap—not by path.
### FastAPI example (same engine, any language)
**Intent:** *Add a `/healthz` endpoint.*
| Change | Verdict | Why |
| :--- | :--- | :--- |
| `main.py` — `@app.get("/healthz") def health_check(): return {"status": "ok"}` | **Keep (surgical)** | Sub-word tokens match `healthz`, even though the filename is generic. |
| `utils/db.py` — helper `health_check` uses (`check_db_connection`) | **Keep (connected)** | Identifier graph: the endpoint and helper share identifiers. |
| `routers/auth.py` — unsolicited JWT validation refactor + import reorder | **Prune (bloat)** | Isolated island; zero identifier overlap with the `/healthz` seed cluster. |
| `config.py` — Black formatting-only cosmetic changes | **Prune (cosmetic)** | After comment/whitespace strip, before == after. |
A polyglot commit is the same: Python `navbar_toggle`, Go `NavbarToggle`, and TypeScript `NavbarToggle` stay; an unrelated SQL index and prettier-only CSS are pruned.
---
## 1. Quick Setup (From Source)
Requires **Node.js 18+** and Git.
```bash
git clone https://github.com/preprint-labs/surgical.git
cd surgical
npm install
npm run build
```
Verify that `dist/index.js` was created.
---
## 2. Editor Setup (Cursor, Claude Code, GitHub Copilot)
Because you built from source, point your editor to your local `dist/index.js` binary.
### Find your full script path:
* **Windows (PowerShell):** `(Resolve-Path dist\index.js).Path`
* **macOS / Linux:** `pwd`/`dist/index.js`
---
### A. Cursor Setup
#### Option 1: Workspace Config (Recommended)
Add to your project's `.cursor/mcp.json`:
```json
{
"mcpServers": {
"surgical": {
"command": "node",
"args": ["<FULL_PATH_TO_SURGICAL>/dist/index.js"]
}
}
}
```
*(On Windows, use escaped backslashes: `"C:\\path\\to\\surgical\\dist\\index.js"`)*.
#### Option 2: Cursor GUI
1. Open **Settings** (`Ctrl + Shift + J` or `Cmd + Shift + J`) -> **Features** -> **MCP**.
2. Click **+ Add New MCP Server**.
3. Set:
* **Name:** `surgical`
* **Type:** `command`
* **Command:** `node <FULL_PATH_TO_SURGICAL>/dist/index.js`
---
### B. Claude Code (Terminal CLI)
Run this single command in your terminal:
```bash
# macOS/Linux:
claude mcp add surgical -- node /absolute/path/to/surgical/dist/index.js
# Windows:
claude mcp add surgical -- node "C:\path\to\surgical\dist\index.js"
```
---
### C. GitHub Copilot (VS Code)
Add to your workspace `.vscode/settings.json`:
```json
{
"github.copilot.chat.mcpServers": {
"surgical": {
"command": "node",
"args": ["<FULL_PATH_TO_SURGICAL>/dist/index.js"]
}
}
}
```
---
## 3. Automated Agent Enforcement (Zero Human Friction)
You do not need to run terminal commands manually while coding.
Add the snippet below to your project's `.cursorrules`, `CLAUDE.md`, or `.github/copilot-instructions.md` to force your AI assistant to audit and clean up its own diff before finishing. The same instruction works for Next.js, FastAPI, Go, Rust, and everything else.
```markdown
# Karpathy Rule #3: Surgical Changes Only
Before declaring a task complete, you MUST call the `prune_diff_bloat` MCP tool passing the user's original request as the `intent`.
Generic files (`page.tsx`, `main.py`, `app.py`, `utils.ts`, `lib.rs`) are scored by hunk tokens and identifier overlap—not filenames.
Keep intent-matching and identifier-connected changes. Confirm reversion of isolated drive-by refactors and cosmetic-only diffs (formatter churn).
```
---
## 4. CLI Usage (For Manual Inspection)
You can also run `surgical` directly from the command line outside an editor:
```bash
# Explain what belongs to your intent without touching the working tree
node dist/index.js explain --intent "add navbar to homepage"
# Same scoring on any language (generic app.py / main.py / page.tsx is not discarded)
node dist/index.js explain --intent "add /healthz endpoint"
node dist/index.js explain --intent "fix mobile navbar toggle"
# Plan and non-destructively stage only the matching surgical files
node dist/index.js prune --intent "add navbar to homepage"
# Revert confirmed unrequested drive-by refactors
node dist/index.js prune --intent "add navbar to homepage" --auto-discard
```
| Command | Behavior |
| :--- | :--- |
| `explain --intent <text>` | Prints a dry-run explanation of why each file/hunk is marked surgical vs. bloat. |
| `prune --intent <text>` | Non-destructively runs `git add` on verified surgical files, leaving bloat unstaged. |
| `prune --auto-discard` | Reverts unrequested bloat files after safety snapshotting. |
MCP tools: `prune_diff_bloat`, `explain_diff`, `undo_pruning`.
---
## 5. Safety & Zero Data Loss Guarantee
`surgical` is designed to be **safe by default**:
1. **Automatic Safety Snapshot:** Before any mutation or discard, `surgical` runs `git stash create` / `git write-tree` in `<5ms`. Your entire pre-pruned state is saved as an immutable Git tree object.
2. **Non-Destructive Staging:** By default, `prune` only **stages** the surgical files (`git add`), leaving questionable files unstaged for human review.
3. **Instant Undo:** If a file was pruned that you wanted to keep, run:
```bash
node dist/index.js undo
```
*(or call the MCP tool `undo_pruning`)* to instantly restore your working tree.
---
## 6. Running Tests
The test suite uses a **polyglot** fixture (Python + Go + TypeScript + unrelated SQL + cosmetic CSS) and a FastAPI `/healthz` story (`main.py` + connected `utils/db.py` vs. isolated JWT churn in `routers/auth.py` and Black-only `config.py`):
```bash
npm test
```
---
## Citation
If you reference this tool or the over-editing evaluation methodology, please cite the underlying research:
```bibtex
@article{overediting2026,
title={When Models Edit Too Much: On the Fidelity of Minimal Code Edits},
author={Zhu, Tongyao and Lim, Wei Hern and Kan, Min-Yen},
journal={arXiv preprint arXiv:2609.04061},
year={2026}
}
```
---
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues