From go-agent-skills
Modernizes Go code to use generics (1.18+), slog (1.21+), errors.Join, slices/maps packages, range-over-func, and iterators (1.23+). Use for upgrading to modern Go patterns.
npx claudepluginhub eduardo-sl/go-agent-skills --plugin go-agent-skillsThis skill uses the workspace's default tool permissions.
Go evolves. Code written for Go 1.16 should not look the same as code targeting
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Go evolves. Code written for Go 1.16 should not look the same as code targeting
Go 1.22+. Modernize incrementally — update go.mod, then adopt new patterns.
interface{} / any with type parameters where appropriate:// ❌ Before — loses type safety
func Contains(slice []interface{}, target interface{}) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// ✅ After — type-safe generic
func Contains[T comparable](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
// Built-in constraints
func Sum[T int | int64 | float64](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
// Or use golang.org/x/exp/constraints (or define your own)
type Number interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~float32 | ~float64
}
func Sum[T Number](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
// ❌ Don't use generics when a single concrete type works
func PrintUser[T User](u T) { fmt.Println(u.Name) }
// → Just use: func PrintUser(u User) { fmt.Println(u.Name) }
// ❌ Don't use generics to avoid interfaces for behavior polymorphism
// Interfaces are still the right tool for runtime polymorphism
// ✅ Use generics for:
// - Container types (Set[T], Stack[T], Result[T])
// - Utility functions operating on multiple types (Map, Filter, Reduce)
// - Type-safe wrappers (sync pool, atomic values)
type Set[T comparable] struct {
items map[T]struct{}
}
func NewSet[T comparable](items ...T) Set[T] {
s := Set[T]{items: make(map[T]struct{}, len(items))}
for _, item := range items {
s.items[item] = struct{}{}
}
return s
}
func (s Set[T]) Contains(item T) bool {
_, ok := s.items[item]
return ok
}
func (s Set[T]) Add(item T) {
s.items[item] = struct{}{}
}
// ❌ Before
log.Printf("processing order %s for user %s", orderID, userID)
// ✅ After
slog.Info("processing order",
slog.String("order_id", orderID),
slog.String("user_id", userID),
)
// Before — zap
logger.Info("request completed",
zap.String("method", method),
zap.Int("status", status),
zap.Duration("latency", elapsed),
)
// After — slog (if you don't need zap-specific features)
slog.Info("request completed",
slog.String("method", method),
slog.Int("status", status),
slog.Duration("latency", elapsed),
)
Keep zap/zerolog if you need their performance characteristics for high-throughput logging. For most services, slog is sufficient.
// ❌ Before — manual error accumulation
var errMsgs []string
for _, item := range items {
if err := validate(item); err != nil {
errMsgs = append(errMsgs, err.Error())
}
}
if len(errMsgs) > 0 {
return fmt.Errorf("validation: %s", strings.Join(errMsgs, "; "))
}
// ✅ After — errors.Join preserves the error chain
var errs []error
for _, item := range items {
if err := validate(item); err != nil {
errs = append(errs, err)
}
}
if err := errors.Join(errs...); err != nil {
return fmt.Errorf("validation: %w", err)
}
errors.Join preserves the full error chain — errors.Is and errors.As
work on each individual error.
// ❌ Before — manual sort
sort.Slice(users, func(i, j int) bool {
return users[i].Name < users[j].Name
})
// ✅ After — slices.SortFunc
slices.SortFunc(users, func(a, b User) int {
return cmp.Compare(a.Name, b.Name)
})
// ❌ Before — manual contains check
found := false
for _, v := range items {
if v == target {
found = true
break
}
}
// ✅ After
found := slices.Contains(items, target)
// ❌ Before — manual index search
idx := -1
for i, v := range items {
if v.ID == targetID {
idx = i
break
}
}
// ✅ After
idx := slices.IndexFunc(items, func(item Item) bool {
return item.ID == targetID
})
// ❌ Before — manual key collection
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
// ✅ After
keys := slices.Collect(maps.Keys(m))
// ❌ Before — manual map clone
clone := make(map[string]int, len(m))
for k, v := range m {
clone[k] = v
}
// ✅ After
clone := maps.Clone(m)
// ❌ Before
for i := 0; i < n; i++ {
process(i)
}
// ✅ After
for i := range n {
process(i)
}
// ✅ Iterator that yields filtered results
func (db *DB) ActiveUsers(ctx context.Context) iter.Seq2[User, error] {
return func(yield func(User, error) bool) {
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE active = true")
if err != nil {
yield(User{}, fmt.Errorf("query active users: %w", err))
return
}
defer rows.Close()
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Name); err != nil {
if !yield(User{}, fmt.Errorf("scan user: %w", err)) {
return
}
continue
}
if !yield(u, nil) {
return
}
}
if err := rows.Err(); err != nil {
yield(User{}, fmt.Errorf("iterate users: %w", err))
}
}
}
// Usage — clean range loop
for user, err := range db.ActiveUsers(ctx) {
if err != nil {
return fmt.Errorf("active users: %w", err)
}
process(user)
}
// maps.Keys, maps.Values return iterators (Go 1.23+)
for key := range maps.Keys(m) {
fmt.Println(key)
}
// slices.All, slices.Values, slices.Backward
for i, v := range slices.Backward(items) {
fmt.Printf("%d: %v\n", i, v)
}
// ❌ Before — request without context
req, err := http.NewRequest(http.MethodGet, url, nil)
// ✅ After — context propagated
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
| Go Version | Feature | Action |
|---|---|---|
| 1.13+ | errors.Is, errors.As | Replace == error comparisons |
| 1.13+ | http.NewRequestWithContext | Replace http.NewRequest |
| 1.16+ | embed | Replace go-bindata / packr |
| 1.18+ | Generics | Replace interface{} utility functions |
| 1.20+ | errors.Join | Replace manual error accumulation |
| 1.21+ | log/slog | Replace log for structured logging |
| 1.21+ | slices, maps | Replace hand-written slice/map utilities |
| 1.21+ | min, max builtins | Replace math.Min/math.Max (float64-only) |
| 1.22+ | Range over int | Replace for i := 0; i < n; i++ |
| 1.23+ | Range over func | Replace callback-based iteration |
go.mod version matches the features used in the codebaseinterface{} where any or type parameters would be clearerlog/slog used instead of log.Printf for structured loggingerrors.Join used instead of manual error string concatenationslices.Contains, slices.SortFunc, maps.Clone replace hand-written loopsfor i := range n) used where applicablehttp.NewRequestWithContext used instead of http.NewRequestsort.Slice — use slices.SortFunc with cmp.Compare