Go Refactoring Tools
SkillProductivityGo code refactoring with automated tools: gofmt -r, gopatch, and rsc.io/rf. Use this skill when performing mechanical rewrites across a codebase — renaming identifiers, migrating API calls, moving types between packages, rewriting expressions, or replacing deprecated patterns. Triggers when the task involves bulk code transformation, API migration, structural rewrite, or the user mentions gofmt -r, rf, gopatch, coccinelle-style patching, or large-scale rename/move in Go.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Go Refactoring Tools skill
What this skill tells your AI
The instructions your AI receives, as published by gotd/botapi in .agents/skills/golang-refactor-tools/SKILL.md and read by ahel’s review.
Decision table
| Situation | Tool |
|---|---|
| Swap one expression for another | gofmt -r |
| Structural multi-line or multi-statement rewrite | gopatch |
| Type-sensitive expression rewrite | rf ex |
| Rename / move identifier, type, function, package | rf mv |
| Delete declarations | rf rm |
Workflow: dry-run first (
gopatch -d/rf -diff), review the diff, then apply. After any tool run:goimports -w . && go build ./... && go test ./.... Commit before bulk rewrites.
gofmt -r — quickest, ships with Go
Syntax-aware but not type-aware. Pattern variables must be single lowercase letters; everything else is matched literally. -w . recurses naturally — ./... is a Go package pattern and will error.
gofmt -r 'a.OldMethod(b) -> a.NewMethod(b)' -w .
gofmt -r 'ioutil.ReadAll(a) -> io.ReadAll(a)' -w . # ioutil/io are literal; a is wildcard
After running: fix imports with goimports -w . (gofmt -r never touches imports).
gopatch — structural patch files
Operates on syntax, understands statements and imports. Can work on partially-invalid code.
go install github.com/uber-go/gopatch@latest
gopatch -p my.patch ./... # rewrite in place
gopatch -d -p my.patch ./... # dry-run: print diff
gopatch ./... <<'EOF' # read patch from stdin (omit -p)
...
EOF
Patch anatomy
# Description (shown in -d output)
@@
var x expression # matches any Go expression
var n identifier # matches any single identifier
@@
-old code
+new code
Metavariable types: expression (calls, field accesses, literals…), identifier (single name).
Undeclared identifiers are matched literally — a, b, c in the body match only args named exactly a, b, c. Use ... (elision) or declare var a expression explicitly.
...: matches zero or more statements/arguments. Multiple @@…@@ blocks = multiple patches run in order.
Examples
Replace call + fix import in one patch:
@@
@@
-import "io/ioutil"
+import "io"
-ioutil.ReadAll(...)
+io.ReadAll(...)
Remove boilerplate with elision:
# Delete redundant gomock.Controller.Finish()
@@
var ctrl, gomock identifier
var t expression
@@
import gomock "github.com/golang/mock/gomock"
ctrl := gomock.NewController(t)
...
-defer ctrl.Finish()
Replace expression with sub-expression wildcard:
@@
var x expression
@@
-time.Now().Sub(x)
+time.Since(x)
rsc.io/rf — rename, move, expression rewrite
⚠ Experimental: rf's README says "incredibly rough and likely to be buggy and change incompatibly." Prefer
gopatchfor structural work; userfwhen type-awareness is required (renames, moves, typed expression rewrites).
Takes a single script argument and operates on the module in the current directory — no ./....
go install rsc.io/rf@latest
rf -diff 'mv Foo Bar' # show diff without writing
mv — rename or move
rf 'mv OldType NewType'
rf 'mv pkg.OldFunc pkg.NewFunc'
rf 'mv mypkg.Foo otherpkg.Foo'
rf 'mv MyStruct.OldField MyStruct.NewField'
ex — type-aware expression rewrite
Variables inside { } are typed metavariables — only values of the declared type match, so there are no false positives on same-named identifiers from other packages. If a type doesn't precisely match the AST node, the rule is silently skipped. Multiple rules go in one block.
rf 'ex {
var s string
var r io.Reader
fmt.Sprintf("%s", s) -> s
ioutil.ReadAll(r) -> io.ReadAll(r)
}'
avoid prevents rewriting inside named method bodies:
rf 'ex {
var m myMap
var k, v int
avoid myMap.Get
m[k] -> m.Get(k)
}'
rm / add
rf 'rm OldFunc'
rf 'rm MyType.DeprecatedField'
rf 'add MyStruct.NewField int `json:"new_field"`'
Common recipes
Rename a public type
rf 'mv pkg.OldName pkg.NewName'
Signals
- GitHub stars
- 29
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
golang-refactor-tools- Source
- github.com/gotd/botapi