Go Refactoring Tools

SkillProductivity

Go 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.

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

SituationTool
Swap one expression for anothergofmt -r
Structural multi-line or multi-statement rewritegopatch
Type-sensitive expression rewriterf ex
Rename / move identifier, type, function, packagerf mv
Delete declarationsrf 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 gopatch for structural work; use rf when 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