Make a Surgical Change
Activated Cloud✓ Officialactivated/make-surgical-change
Free · MIT
About
Makes a small, targeted code or config change (a fix, a rename, a default, a copy change, one function) with the smallest correct diff: locate the exact site, read it first, find every caller before touching an interface, match the file's style, edit with patch, validate the syntax, run the relevant checks and show the diff. Use when the change fits in one or a few files and needs no plan. Not for multi-step features (use implement-feature-end-to-end) or behaviour-preserving restructuring (use refactor-safely).
Documentation
Make a Surgical Change
Most real edits are small, and small edits are where careless habits do the most damage: the wrong one of two similar functions gets patched, a caller is missed, a whole file is reformatted, a YAML file stops parsing. A surgical change touches exactly what it must, looks like the code around it, and is proven by a check you ran.
When to use
- "Change the timeout to 30 seconds", "rename this field", "fix the typo on the pricing page", "make this function return None instead of raising".
- A one-line bug fix whose cause is already known.
- Config, dependency pin, environment or CI tweaks.
Check the size before you start, and again if the change grows:
| Signal | Still surgical? |
|---|---|
| One to three files, internal code only | Yes |
| A default, constant or signature others call | Yes, after the caller search in step 3 |
| Touches a public API, schema, migration or another team's code | No: plan it as a feature |
| Needs new tests across several modules, or more than about 100 changed lines | No: plan it as a feature |
| The cause of the bug is not yet known | No: find the root cause first |
What you need
- The exact request. If "the timeout" could mean three timeouts, ask which with
clarify(name the candidates and their locations). - Write access to the repo (GitHub or GitLab connected app, or a git credential on your computer), or an uploaded copy if the owner wants a patch back.
- The command that checks the area you are changing: a specific test, the linter, a config validator.
Method
Locate the exact site.
search_filesfor the literal string, symbol or error text. If there are several hits, open each withread_fileand decide which one the request means. Similar code in two places (a copy-pasted validator, a duplicated constant) often needs the change in both: check, and say what you did.Read before you edit.
read_filethe whole function or block you will change, plus enough above and below to see imports, types and how the result is used. Note the file's style: indentation, quotes, naming, error handling, type hints, import order, line length. If you or anything else may have changed the file since you read it, read it again.Check the blast radius. If you change anything others depend on (a function signature, return type, default value, exported name, constant, config key, environment variable, CLI flag, route, DB column, event name, CSS class used by tests), search for every reference:
search_fileswith\bname\bacross the repo, excluding vendored and build folders;- string and dynamic uses (
"name",getattr(obj, "name"),data["name"], templates, YAML, SQL, docs, tests); - other services or repos that consume it, if it crosses a boundary. Update every reference, or keep the old form working (a default argument, an alias) and say so in your summary.
Edit with
patch, smallest unique match. Pick anold_stringthat occurs once and includes a line or two of context. Read the unified diff thatpatchreturns: it uses fuzzy matching, so confirm the change landed where you meant and nowhere else. Usereplace_allonly when you have counted the occurrences and want all of them. Do not rewrite a whole existing file withwrite_filefor a small change: it churns formatting and silently drops anything that changed since you read it.Keep the diff clean.
- No drive-by reformatting, renaming or reordering of lines you were not asked to touch.
- Keep the file's line endings, indentation and trailing newline.
- Generated files (
*.pb.go,dist/, lockfiles, compiled CSS): change the source and regenerate with the project's command; never hand-edit the output. - Lockfiles change only through the package manager (
npm install pkg@x,uv add,go get,cargo update -p). - Known language traps are listed in
references/edit-gotchas.md. Check the ones for your file type.
Validate syntax for config and data files.
python -c "import sys, yaml; yaml.safe_load(open(sys.argv[1]))" path.yml && echo ok python -m json.tool path.json > /dev/null && echo ok python -c "import sys, tomllib; tomllib.load(open(sys.argv[1], 'rb'))" path.toml && echo ok docker compose config -q && echo okUse the tool's own validator when there is one (
nginx -t,terraform validate,actionlint,promtool check config).Run the narrowest meaningful check, then one wider. The test covering the code you changed, then that test file or package; the linter and type checker on the touched files. Check the exit code of each. If no test covers the behaviour you changed and it is logic (not copy or config), add one, or say plainly that it is untested.
Show the diff.
git diff(orgit diff --statplus the relevant hunks). Read it once more as a reviewer: is every changed line necessary?Commit if the workflow calls for it. Stage the exact paths, write a message that says why (
fix(api): raise upload timeout to 30 s for large CSVs), and leave pushing to the owner's rules.
Output
A short message with: what changed and where (file:line), why that site and not the similar one, callers updated (or "none, internal only"), the checks you ran with their results, and the diff (or its key hunk) in a code block. For example:
Raised the CSV upload timeout from 10 s to 30 s in `api/uploads.py:42` (`UPLOAD_TIMEOUT_S`).
The other timeout in `api/client.py:17` is for outbound calls, so I left it alone.
Callers: the constant is used only in `api/uploads.py` (searched the repo and config files).
Checks: `pytest tests/test_uploads.py -q` passed (7 tests); `ruff check api/uploads.py` exit 0.
```diff
-UPLOAD_TIMEOUT_S = 10
+UPLOAD_TIMEOUT_S = 30 # large CSV imports exceed 10 s on the free plan
```
Checks before you finish
- You read the file right before editing it, and the returned diff shows only the intended change.
- Every reference to anything you renamed or re-typed was found and handled.
- Config and data files parse.
- The relevant test, linter and type check were run after the edit, with exit codes noted.
- No unrelated lines changed (
git diff --statshows only the files you meant).
Pitfalls
- Patching the wrong twin. Two functions with the same name in different modules, or a test fixture that mirrors production code. Confirm the call path before editing.
- Changing a default and breaking a caller that relied on it. A default is an interface. Search for calls that omit the argument.
- Fuzzy-match surprises.
patchwill find the closest match. A shortold_stringlikereturn Nonemay hit the first of five. Include context and read the diff. - YAML traps. Tabs are illegal;
on,yes,noandoffmay parse as booleans;010may parse as octal in older parsers; indentation changes meaning. Quote strings that look like other types. - Whole-file rewrites. They hide the real change inside formatting noise and can erase a teammate's edit.
- "It's only a string change." Strings are matched in tests, translations, analytics events and API clients. Search before changing them.
- No check at all. Even a copy change deserves a look in the browser or a grep of the rendered template.
Versions
Listed from the source repository.
Reviews
No reviews yet. Be the first.
