smallnest/goal-workflow1 file

Modern Go

Modernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations). Scans go.mod for the Go version, then transforms Go source files to use modern patterns—from Go 1.0 through 1.26+. Use when the user says "现代化","现代Go语言", "地道的", "idiomatic", "modernize", "modern-go", "update Go code", "gofix", or wants to upgrade Go idioms.

Specification
Skill ID
smallnest/goal-workflow/modern-go
Publisher
smallnest
Repository
goal-workflow
Installs
0
Files
1
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

smallnest/goal-workflow/modern-goInstalls these files
  • SKILL.md

What this skill tells the agent

modern-go

Modernize Go source code by applying version-appropriate idioms, APIs, and language features. Works like go fix plus additional transformations curated from the Go team's modernize analysis passes and community best practices.

Usage

Invoke this skill when the user asks to modernize Go code. By default, modernize the entire project; the user may specify a file or directory instead.

When invoked:

  1. Detect the project's Go version from go.mod (the go directive).
  2. Find all .go files in the target scope (excluding vendor/, .git/, testdata/).
  3. For each file, apply all transformations for versions ≤ the project's Go version, starting from the oldest to the newest.
  4. After all transformations, print a summary of what was changed and what was skipped.

If the user specifies a file or directory, limit the scope to that path.

Transformation Catalog

Each transformation includes a Go version gate—only apply when the project's go.mod version ≥ that version. Never apply a transformation that requires a version higher than the project declares.

Go 1.0+ — time.Since

BeforeAfter
time.Now().Sub(start)time.Since(start)
// before
elapsed := time.Now().Sub(start)
// after
elapsed := time.Since(start)

Go 1.8+ — time.Until

BeforeAfter
deadline.Sub(time.Now())time.Until(deadline)
// before
remaining := deadline.Sub(time.Now())
// after
remaining := time.Until(deadline)

Go 1.10+ — strings.Builder (loop concatenation)

BeforeAfter
s += item in a loopvar b strings.Builder; b.WriteString(item)
// before
s := ""
for _, item := range items {
    s += item
}
// after
var b strings.Builder
for _, item := range items {
    b.WriteString(item)
}
s := b.String()

Only when += concatenation happens inside a loop.

Go 1.13+ — errors.Is

BeforeAfter
err == io.EOFerrors.Is(err, io.EOF)
// before
if err == io.EOF {
    return
}
// after
if errors.Is(err, io.EOF) {
    return
}

Go 1.17+ — //go:build constraints (plusbuild)

BeforeAfter
// +build linux + //go:build linux (both present)keep only //go:build linux
// before
//go:build linux && amd64
// +build linux,amd64

package foo
// after
//go:build linux && amd64

package foo

The plusbuild modernizer removes obsolete // +build constraint lines once the equivalent //go:build line is present (the //go:build syntax landed in Go 1.17). Only strip the old line when a matching //go:build already exists — never drop the sole constraint.

Go 1.17+ — unsafe.Add / unsafe.Slice (unsafefuncs)

BeforeAfter
unsafe.Pointer(uintptr(ptr) + uintptr(n))unsafe.Add(ptr, n)
(*[n]T)(unsafe.Pointer(p))[:] slice constructionunsafe.Slice(p, n)
// before — pointer arithmetic via uintptr
p2 := unsafe.Pointer(uintptr(ptr) + uintptr(offset))
// after
p2 := unsafe.Add(ptr, offset)
// before — building a slice from a base pointer
s := (*[1 << 30]byte)(unsafe.Pointer(p))[:n:n]
// after
s := unsafe.Slice(p, n)

The unsafefuncs modernizer (gopls v0.22.0) rewrites error-prone uintptr pointer math into unsafe.Add / unsafe.Slice, which the compiler and go vet understand as GC-safe.

Go 1.18+ — any

BeforeAfter
interface{}any
// before
func decode(v interface{}) error { ... }
// after
func decode(v any) error { ... }

Go 1.18+ — strings.Cut

BeforeAfter
i := strings.Index(s, sep); ... s[:i], s[i+len(sep):]key, val, found := strings.Cut(s, sep)
// before
if i := strings.Index(s, "="); i >= 0 {
    key, val := s[:i], s[i+1:]
}
// after
if key, val, found := strings.Cut(s, "="); found {
    ...
}

Go 1.18+ — bytes.Cut

BeforeAfter
i := bytes.Index(b, sep); ... b[:i], b[i+len(sep):]before, after, found := bytes.Cut(b, sep)
// before
if i := bytes.Index(b, sep); i >= 0 {
    before, after := b[:i], b[i+len(sep):]
}
// after
before, after, found := bytes.Cut(b, sep)

Go 1.19+ — fmt.Appendf

BeforeAfter
buf = append(buf, fmt.Sprintf(...)...)buf = fmt.Appendf(buf, ...)
// before
buf = append(buf, fmt.Sprintf("x=%d", x)...)
// after
buf = fmt.Appendf(buf, "x=%d", x)

Go 1.19+ — Type-safe atomics (atomictypes)

BeforeAfter
atomic.StoreInt32(&v, 1) / atomic.LoadInt32(&v)var v atomic.Int32; v.Store(1); v.Load()
atomic.AddInt64(&v, 1)var v atomic.Int64; v.Add(1)
atomic.Value + type assertionatomic.Pointer[T]
// before
var ready int32
atomic.StoreInt32(&ready, 1)
if atomic.LoadInt32(&ready) == 1 { ... }

// after
var ready atomic.Int32
ready.Store(1)
if ready.Load() == 1 { ... }
// before
var cache atomic.Value
cache.Store(&Config{})
cfg := cache.Load().(*Config)

// after
var cache atomic.Pointer[Config]
cache.Store(&Config{})
cfg := cache.Load()

The atomictypes modernizer (gopls v0.22.0, AtomicTypesAnalyzer) rewrites both the variable declaration and every call site. Typed wrappers (atomic.Int32/Int64/Uint32/Uint64/Bool/Pointer[T]) have identical performance but prevent accidental non-atomic access and fix 64-bit alignment crashes on 32-bit architectures.

Go 1.20+ — strings.Clone

BeforeAfter
string([]byte(s))strings.Clone(s)
// before
s2 := string([]byte(s)) // force copy
// after
s2 := strings.Clone(s)

Go 1.20+ — bytes.Clone

BeforeAfter
make([]byte, len(src)); copy(dst, src)bytes.Clone(src)
// before
dst := make([]byte, len(src))
copy(dst, src)
// after
dst := bytes.Clone(src)

Go 1.20+ — strings.CutPrefix / strings.CutSuffix

BeforeAfter
if strings.HasPrefix(s, p) { s = s[len(p):] }if rest, ok := strings.CutPrefix(s, p); ok { s = rest }
if strings.HasSuffix(s, sf) { s = s[:len(s)-len(sf)] }if rest, ok := strings.CutSuffix(s, sf); ok { s = rest }
// before
if strings.HasPrefix(s, "pre_") {
    s = s[len("pre_"):]
}
// after
if rest, ok := strings.CutPrefix(s, "pre_"); ok {
    s = rest
}
// before
if strings.HasSuffix(s, ".txt") {
    s = s[:len(s)-len(".txt")]
}
// after
if rest, ok := strings.CutSuffix(s, ".txt"); ok {
    s = rest
}

Go 1.20+ — errors.Join

BeforeAfter
fmt.Errorf("...: %w: %w", err1, err2)errors.Join(err1, err2)
// before
return fmt.Errorf("load config: %w: %w", err1, err2)
// after
return errors.Join(fmt.Errorf("load config"), err1, err2)

Go 1.20+ — context.WithCancelCause

BeforeAfter
ctx, cancel := context.WithCancel(parent) + bare cancel()ctx, cancel := context.WithCancelCause(parent) + cancel(err)
// before
ctx, cancel := context.WithCancel(parent)
// ... somewhere ...
cancel()

// after
ctx, cancel := context.WithCancelCause(parent)
cancel(ErrShutdown)
// caller: context.Cause(ctx) → ErrShutdown

Go 1.21+ — min / max

BeforeAfter
if a < b { v = a } else { v = b }v = min(a, b)
if a > b { v = a } else { v = b }v = max(a, b)
if x < lo { x = lo }; if x > hi { x = hi }x = min(max(x, lo), hi)
// before
lo := a
if b < lo {
    lo = b
}
// after
lo := min(a, b)
// before
if x < 0 {
    x = 0
}
if x > 100 {
    x = 100
}
// after
x = min(max(x, 0), 100)

Go 1.21+ — clear

BeforeAfter
for k := range m { delete(m, k) }clear(m)
for i := range s { s[i] = zero }clear(s)
// before
for k := range m {
    delete(m, k)
}
// after
clear(m)
// before
for i := range s {
    s[i] = 0
}
// after
clear(s)

Go 1.21+ — slices package

Use this skill in a project

Open any RiverX project, search the Skills panel for smallnest/goal-workflow/modern-go, and install it in one click.

Use this skill