Generated banner for Golang Modernize
Generated logo for Golang Modernize

Golang Modernize

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Modernize Golang code to use recent language features, standard library improvements, and idiomatic patterns. Use when reviewing Go code with old-style patterns, when encountering a deprecation warning, or when the user asks for modernization, a Go version upgrade (e.g. to Go 1.27), or a CI/tooling refresh. Not for structural refactors, extracting functions, or moving code between packages (→ See `samber/cc-skills-golang@golang-refactoring` skill).
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The golang-modernize skill provides a structured approach to keeping Go projects current with the latest language releases. It scans codebases for outdated patterns, deprecated APIs, and opportunities to leverage newer standard library features (e.g., slices, maps, slog). The skill operates in two modes: an inline mode for context-aware suggestions during active development, and a full-scan mode for comprehensive codebase-wide modernization. It prioritizes safety and correctness, ensuring that changes are verified against test suites and, where applicable, isolated in worktrees to maintain main branch stability.

What it does

  • Identifies and suggests replacements for deprecated Go packages and APIs.
  • Recommends adoption of modern language features like range-over-int, iterators, and generic type aliases.
  • Suggests standard library upgrades such as migrating to the slices, maps, or slog packages.
  • Provides guidance on updating CI tooling, including golangci-lint v2 and govulncheck.
  • Checks go.mod files to suggest Go version upgrades and toolchain updates.
  • Maintains a .modernize file to track and ignore specific suggestions per user preference.

When to use it

  • When reviewing legacy Go code that uses outdated patterns.
  • When encountering deprecation warnings during compilation or linting.
  • When planning a Go version upgrade (e.g., to Go 1.27).
  • When performing a periodic refresh of project tooling and infrastructure.
  • When you want to ensure your codebase follows current idiomatic Go practices.

Advantages

  • Improves code readability and maintainability by adopting modern idioms.
  • Enhances security and performance by utilizing the latest standard library features.
  • Reduces technical debt through systematic identification of deprecated patterns.
  • Ensures safe application of changes by integrating with test suites and isolated worktrees.
  • Provides a clear migration priority guide to focus on high-impact safety and correctness fixes first.

Usage examples

“/golang-modernize”
The agent performs a full-scan of the codebase, identifies outdated patterns and deprecated APIs, and proposes a prioritized list of modernization tasks.
“Can you suggest modernizations for this file?”
The agent analyzes the current file for relevant improvements, such as replacing manual loops with slices package functions, and asks if you would like to apply them.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/samber/golang-modernize/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-modernize.zip -d .agents/skills

Documentation by samber

<!-- markdownlint-disable ol-prefix -->

Persona: You are a Go modernization engineer. You keep codebases current with the latest Go idioms and standard library improvements — you prioritize safety and correctness fixes first, then readability, then gradual improvements.

Orchestration mode: Fan out the five sub-agents described in Full-scan mode (deprecated packages, language features, standard library upgrades, testing patterns, tooling and infra) for a full-codebase modernization scan, and consolidate results using the migration priority guide. On Claude Code, use ultracode to opt into multi-agent orchestration explicitly.

Modes:

  • Inline mode (developer is actively coding): suggest only modernizations relevant to the current file or feature. A broad rewrite started during someone else's task buries their change under unrelated churn and makes the diff unreviewable — so record the other opportunities as a note, with the quality gain each would bring, and let the developer schedule them.

  • Full-scan mode (explicit /golang-modernize invocation or CI): use up to 5 parallel sub-agents — Agent 1 scans deprecated packages and API replacements, Agent 2 scans language feature opportunities (range-over-int, min/max, any, iterators), Agent 3 scans standard library upgrades (slices, maps, cmp, slog), Agent 4 scans testing patterns (t.Context, b.Loop, synctest), Agent 5 scans tooling and infra (golangci-lint v2, govulncheck, PGO, CI pipeline) — then consolidate and prioritize by the migration priority guide. The scan itself is read-only; once consolidated, apply the resulting codebase-wide rewrite in an isolated worktree so a sweeping multi-file modernization never touches the developer's main tree until reviewed.

Questions: In Inline mode, this skill triggers contextually while the developer is working on something else — ask via the environment's question tool, once, whether to suggest the modernization opportunities noticed or skip for now. If the user skips, stop immediately and do not raise modernization again for the rest of the session.

Go Code Modernization Guide

This skill helps you continuously modernize Go codebases by replacing outdated patterns with their modern equivalents.

Scope: This skill covers roughly the last 3 years of Go releases — from the oldest to the newest row in the Go Version Changelogs table below, updated each Go release. Projects targeting an older go.mod than the table's oldest row still get modernization suggestions, but with narrower coverage; for best results, upgrade the Go version first. Some older modernizations (e.g., any instead of interface{}, errors.Is/errors.As, strings.Cut) are included because they are still commonly missed, but many pre-1.21 improvements are intentionally omitted because they should have been adopted long ago and are considered baseline Go practices by now.

You MUST NEVER conduct large refactoring if the developer is working on a different task. But TRY TO CONVINCE your human it would improve the code quality.

Workflow

When invoked:

  1. Check the project's go.mod or go.work to determine the current Go version (go directive)

  2. Check the latest Go version using the Go Version Changelogs table below and suggest upgrading if the project's go.mod is behind

  3. Read .modernize in the project root — this file contains previously ignored suggestions; do NOT re-suggest anything listed there

  4. Scan the codebase for modernization opportunities based on the target Go version

  5. Run golangci-lint with the modernize linter if available, and go test ./... — Go 1.27+ runs the stdversion vet check by default, flagging APIs newer than the module's go directive; bump the directive or revert the suggestion, don't ignore the hit

  6. Suggest improvements contextually:

    • If the developer is actively coding, only suggest improvements related to the code they are currently working on. Do not refactor unrelated files. Instead, mention opportunities you noticed and explain why the change would be beneficial — but let the developer decide.

    • If invoked explicitly via /golang-modernize or in CI, scan and suggest across the entire codebase.

  7. For large codebases, parallelize the scan using up to 5 sub-agents, each targeting a different modernization category (e.g. deprecated packages, language features, standard library upgrades, testing patterns, tooling and infra). Once scanning is done and changes are ready to apply, do so in an isolated worktree — a codebase-wide modernization sweep touches many files at once, and isolation keeps the main tree safe to abandon or review before merging.

  8. Before suggesting a dependency update, run go mod tidy and the test suite to verify compatibility. Ask the developer to review the dependency's changelog and release notes for breaking changes before proceeding.

  9. If the developer explicitly ignores a suggestion, write a short memo to .modernize in the project root so it is not suggested again. Format: one line per ignored suggestion, with a short description.

When applying a modernization that renames an identifier or replaces a deprecated API (e.g. reflect.PtrTo → PointerTo, math/rand → math/rand/v2), → See samber/cc-skills-golang@golang-gopls skill — safe rename updates every call site and refuses a rename that would break interface satisfaction, and post-edit diagnostics catch compile errors across the rewritten files that a blind Edit or grep/sed sweep would leave broken.

.modernize file format
# Ignored modernization suggestions
# Format: <date> <category> <description>
2026-01-15 slog-migration Team decided to keep zap for now
2026-02-01 math-rand-v2 Legacy module requires math/rand compatibility

Go Version Changelogs

Reference the relevant changelog when suggesting a modernization:

VersionReleaseChangelog
Go 1.21August 2023https://go.dev/doc/go1.21
Go 1.22February 2024https://go.dev/doc/go1.22
Go 1.23August 2024https://go.dev/doc/go1.23
Go 1.24February 2025https://go.dev/doc/go1.24
Go 1.25August 2025https://go.dev/doc/go1.25
Go 1.26February 2026https://go.dev/doc/go1.26
Go 1.27August 2026https://go.dev/doc/go1.27

For versions newer than Go 1.27, consult the official Go release notes.

When the project's go.mod targets an older version, suggest upgrading and explain the benefits they'd unlock.

Using the modernize linter

The modernize linter (available since golangci-lint v2.6.0) automatically detects code that can be rewritten using newer Go features. It originates from golang.org/x/tools/go/analysis/passes/modernize; gopls and go fix (rewritten onto the go/analysis framework in Go 1.26, with fixer coverage still growing in Go 1.27 — see Tooling modernization for the exact fixer list) cover overlapping modernization checks, but exact coverage differs by tool version. See the samber/cc-skills-golang@golang-lint skill for configuration.

Version-specific modernizations

For detailed before/after examples for each Go version (1.21–1.27) and general modernizations, see Go version modernizations.

Tooling modernization

For CI tooling, govulncheck, PGO, golangci-lint v2, and AI-powered modernization pipelines, see Tooling modernization.

Deprecated Packages Migration

DeprecatedReplacementSince
math/randmath/rand/v2Go 1.22
crypto/elliptic (most functions)crypto/ecdhGo 1.21
reflect.SliceHeader, StringHeaderunsafe.Slice, unsafe.StringGo 1.21
reflect.PtrToreflect.PointerToGo 1.22
runtime.GOROOT()go env GOROOTGo 1.24
runtime.SetFinalizerruntime.AddCleanupGo 1.24
crypto/cipher.NewOFB, NewCFB*AEAD modes or NewCTRGo 1.24
golang.org/x/crypto/sha3crypto/sha3Go 1.24
golang.org/x/crypto/hkdfcrypto/hkdfGo 1.24
golang.org/x/crypto/pbkdf2crypto/pbkdf2Go 1.24
testing/synctest.Runtesting/synctest.TestGo 1.25
crypto/rsa.EncryptPKCS1v15 for new encryption useRSA-OAEP (rsa.EncryptOAEP / rsa.EncryptOAEPWithOptions) or HPKE/KEM designGo 1.26
net/http/httputil.ReverseProxy.DirectorReverseProxy.RewriteGo 1.26
crypto/tls.Config.Randtesting/cryptotest.SetGlobalRandom()Go 1.27
github.com/google/uuid (simple cases)uuid (stdlib)Go 1.27

Go 1.27+ version-bump risk checklist

Several Go 1.27 changes need verification, not a rewrite, before a go.mod bump ships. Most notably: a godebug line in go.mod (or //go:debug comment) still pinning asynctimerchan, tlsunsafeekm, tlsrsakex, tls3des, tls10server, x509keypairleaf, or gotypesalias to its old value now fails the build. Full checklist in Go version modernizations.

Migration Priority Guide

When modernizing a codebase, prioritize changes by impact:

High priority (safety and correctness)
  1. Remove loop variable shadow copies (Go 1.22+) — prevents subtle bugs

  2. Replace math/rand with math/rand/v2 (Go 1.22+) — remove rand.Seed calls

  3. Use os.Root for user-supplied file paths (Go 1.24+) — prevents path traversal

  4. Run govulncheck (Go 1.22+) — catch known vulnerabilities

  5. Use errors.Is/errors.As instead of direct comparison (Go 1.13+)

  6. Migrate deprecated crypto packages (Go 1.24+) — security critical

  7. Before bumping to go 1.27, resolve removed GODEBUG keys and crypto/tls.Config.Rand callers (Go 1.27+) — see the risk checklist above; a stale GODEBUG value now fails the build

Medium priority (readability and maintainability)
  1. Replace interface{} with any (Go 1.18+)

  2. Use min/max builtins (Go 1.21+)

  3. Use range over int (Go 1.22+)

  4. Use slices and maps packages (Go 1.21+)

  5. Use cmp.Or for default values (Go 1.22+)

  6. Use sync.OnceValue/sync.OnceFunc (Go 1.21+)

  7. Use sync.WaitGroup.Go (Go 1.25+)

  8. Use t.Context() in tests (Go 1.24+)

  9. Use b.Loop() in benchmarks (Go 1.24+)

  10. Use generic methods for helpers scoped to one type, and strings.CutLast/bytes.CutLast instead of LastIndex slicing (Go 1.27+)

  11. Migrate to the encoding/json/v2 API — the new default since Go 1.27; review its duplicate-key and invalid-UTF-8 strictness against real payloads first (Go 1.27+)

Lower priority (gradual improvement)
  1. Migrate to slog from third-party loggers (Go 1.21+)

  2. Adopt iterators where they simplify code (Go 1.23+)

  3. Replace sort.Slice with slices.SortFunc (Go 1.21+)

  4. Use strings.SplitSeq and iterator variants (Go 1.24+)

  5. Move tool deps to go.mod tool directives (Go 1.24+)

  6. Enable PGO for production builds (Go 1.21+)

  7. Upgrade to golangci-lint v2 with modernize linter (golangci-lint v2.6.0+)

  8. Add govulncheck to CI pipeline

  9. Set up monthly modernization CI pipeline

  10. Replace google/uuid/gofrs/uuid with the stdlib uuid package, after checking for RFC-variant features the stdlib doesn't cover (Go 1.27+)

  11. Run go fix ./... after a toolchain upgrade to apply the safe automated transformations (Go 1.27+)

  12. Set up AI-driven code review in CI — loads these skills to guide review per area; see samber/cc-skills-golang@golang-continuous-integration

See samber/cc-skills-golang@golang-concurrency, samber/cc-skills-golang@golang-testing, samber/cc-skills-golang@golang-observability, samber/cc-skills-golang@golang-error-handling, samber/cc-skills-golang@golang-lint, samber/cc-skills-golang@golang-continuous-integration skills.

  • → See samber/cc-skills-golang@golang-refactoring skill for staging a large modernization sweep as small human-reviewed PRs instead of one big worktree sweep.

Files

evals/evals.json

{
  "skill_name": "golang-modernize",
  "evals": [
    {
      "id": 1,
      "name": "version-constraint-1.21",
      "prompt": "Review this Go code for modernization opportunities. The project targets Go 1.21.\n\n```go\n// go.mod\nmodule example.com/myapp\ngo 1.21\n\n// main.go\npackage main\n\nimport (\n    \"fmt\"\n    \"math/rand\"\n    \"sort\"\n    \"sync\"\n    \"time\"\n)\n\nfunc minInt(a, b int) int {\n    if a < b { return a }\n    return b\n}\n\nfunc maxInt(a, b int) int {\n    if a > b { return a }\n    return b\n}\n\nfunc processItems(items []string) {\n    sort.Strings(items)\n    found := false\n    for _, v := range items {\n        if v == \"target\" { found = true; break }\n    }\n    _ = found\n}\n\nfunc processN(n int) {\n    for i := 0; i < n; i++ {\n        fmt.Println(i)\n    }\n}\n\nfunc startWorkers(items []int) {\n    for _, v := range items {\n        v := v // shadow copy for closure\n        go func() { fmt.Println(v) }()\n    }\n}\n\nvar (\n    once   sync.Once\n    client *int\n)\nfunc getClient() *int {\n    once.Do(func() {\n        c := 42\n        client = &c\n    })\n    return client\n}\n\nfunc main() {\n    rand.Seed(time.Now().UnixNano())\n    n := rand.Intn(100)\n    fmt.Println(minInt(n, 50), maxInt(n, 10))\n    processItems([]string{\"a\", \"b\", \"c\"})\n    processN(10)\n    startWorkers([]int{1, 2, 3})\n}\n```\n\nSuggest all modernization improvements available for this Go version.",
      "trap": "The project targets Go 1.21. Features like range-over-int (1.22), loop variable fix (1.22), cmp.Or (1.22), and math/rand/v2 (1.22) are NOT available. The model must only suggest 1.21-compatible changes AND must avoid false positives by staying within version constraints.",
      "assertions": [
        { "id": "1.1", "text": "Suggests min/max builtins to replace minInt/maxInt (available in Go 1.21)" },
        { "id": "1.2", "text": "Suggests slices.Sort or slices.Contains (available in Go 1.21)" },
        { "id": "1.3", "text": "Suggests sync.OnceValue to replace manual sync.Once pattern (available in Go 1.21)" },
        { "id": "1.4", "text": "Does NOT suggest range-over-int for processN (requires Go 1.22+)" },
        { "id": "1.5", "text": "Does NOT suggest removing loop variable shadow copy v := v in startWorkers (requires Go 1.22+)" },
        { "id": "1.6", "text": "Does NOT suggest math/rand/v2 migration (requires Go 1.22+)" },
        { "id": "1.7", "text": "Does NOT suggest cmp.Or (requires Go 1.22+)" }
      ]
    },
    {
      "id": 2,
      "name": "rand-v2-api-renames",
      "prompt": "Migrate this Go code from math/rand to math/rand/v2. The project targets Go 1.22.\n\n```go\npackage game\n\nimport (\n    \"crypto/rand\"\n    \"math/big\"\n    mathrand \"math/rand\"\n    \"time\"\n)\n\nvar rng *mathrand.Rand\n\nfunc init() {\n    mathrand.Seed(time.Now().UnixNano())\n    rng = mathrand.New(mathrand.NewSource(time.Now().UnixNano()))\n}\n\nfunc RollDice() int {\n    return mathrand.Intn(6) + 1\n}\n\nfunc GenerateID() int64 {\n    return mathrand.Int63n(1000000)\n}\n\nfunc ShuffleCards(cards []string) {\n    mathrand.Shuffle(len(cards), func(i, j int) {\n        cards[i], cards[j] = cards[j], cards[i]\n    })\n}\n\nfunc RandomFloat() float64 {\n    return mathrand.Float64()\n}\n\nfunc RandomBytes(n int) []byte {\n    buf := make([]byte, n)\n    mathrand.Read(buf)\n    return buf\n}\n\nfunc CryptoRandom() int {\n    n, _ := rand.Int(rand.Reader, big.NewInt(100))\n    return int(n.Int64())\n}\n```\n\nProvide the complete migrated code.",
      "trap": "math/rand/v2 renames functions: Intn->IntN, Int63n->Int64N. Read is removed entirely. Seed is unnecessary. Without the skill, the model may keep old function names.",
      "assertions": [
        { "id": "2.1", "text": "Renames Intn to IntN (capital N)" },
        { "id": "2.2", "text": "Renames Int63n to Int64N (not Int63N or Int64n)" },
        { "id": "2.3", "text": "Removes all rand.Seed calls (automatic seeding in v2)" },
        { "id": "2.4", "text": "Replaces rand.Read with crypto/rand usage for random bytes" },
        { "id": "2.5", "text": "Import changes to math/rand/v2" },
        { "id": "2.6", "text": "Does NOT keep any old-style function names (no rand.Intn or rand.Int63n in output)" }
      ]
    },
    {
      "id": 3,
      "name": "safety-over-cosmetic",
      "prompt": "Modernize this Go 1.24 HTTP file server code. Suggest all improvements.\n\n```go\npackage fileserver\n\nimport (\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"path/filepath\"\n)\n\ntype FileServer struct {\n    baseDir string\n}\n\nfunc NewFileServer(baseDir string) *FileServer {\n    return &FileServer{baseDir: baseDir}\n}\n\nfunc (fs *FileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n    userPath := r.URL.Query().Get(\"path\")\n    if userPath == \"\" {\n        http.Error(w, \"missing path parameter\", http.StatusBadRequest)\n        return\n    }\n\n    fullPath := filepath.Join(fs.baseDir, filepath.Clean(userPath))\n\n    f, err := os.Open(fullPath)\n    if err != nil {\n        http.Error(w, \"file not found\", http.StatusNotFound)\n        return\n    }\n    defer f.Close()\n\n    w.Header().Set(\"Content-Type\", \"application/octet-stream\")\n    io.Copy(w, f)\n}\n\nfunc process(data interface{}) interface{} {\n    return data\n}\n\nfunc minVal(a, b int) int {\n    if a < b { return a }\n    return b\n}\n\nfunc formatAddr(host string, port int) string {\n    return fmt.Sprintf(\"%s:%d\", host, port)\n}\n```\n\nThe project targets Go 1.24. List improvements in priority order.",
      "trap": "The code has a path traversal vulnerability (filepath.Join + filepath.Clean is insufficient). Without the skill, the model may prioritize cosmetic changes (interface{}->any, min builtin) over the security-critical os.Root fix.",
      "assertions": [
        { "id": "3.1", "text": "Suggests os.Root/os.OpenRoot for user-supplied file paths" },
        { "id": "3.2", "text": "Mentions path traversal risk, directory escape, or CWE-22" },
        { "id": "3.3", "text": "Prioritizes the safety fix (os.Root) over cosmetic changes" },
        { "id": "3.4", "text": "Also suggests interface{} -> any" },
        { "id": "3.5", "text": "Also suggests min builtin or net.JoinHostPort" },
        { "id": "3.6", "text": "Does NOT only address cosmetic issues without mentioning the security issue" }
      ]
    },
    {
      "id": 4,
      "name": "omitzero-vs-omitempty",
      "prompt": "Review these JSON struct tags for correctness in our Go 1.24 API. Users report that zero-value time fields and false booleans are unexpectedly included or omitted in JSON responses.\n\n```go\npackage api\n\nimport \"time\"\n\ntype Event struct {\n    ID        string    `json:\"id\"`\n    Name      string    `json:\"name\"`\n    StartAt   time.Time `json:\"start_at,omitempty\"`\n    EndAt     time.Time `json:\"end_at,omitempty\"`\n    Cancelled bool      `json:\"cancelled,omitempty\"`\n    Archived  bool      `json:\"archived,omitempty\"`\n    Notes     string    `json:\"notes,omitempty\"`\n    Priority  int       `json:\"priority,omitempty\"`\n}\n\ntype UserSettings struct {\n    UserID           string    `json:\"user_id\"`\n    DarkMode         bool      `json:\"dark_mode,omitempty\"`\n    EmailNotifs      bool      `json:\"email_notifs,omitempty\"`\n    LastLogin        time.Time `json:\"last_login,omitempty\"`\n    AccountCreated   time.Time `json:\"account_created,omitempty\"`\n    Bio              string    `json:\"bio,omitempty\"`\n}\n```\n\nExplain the issue and provide the corrected code.",
      "trap": "omitempty doesn't work correctly for time.Time (zero time is a non-empty struct) and treats false as empty for bool. Go 1.24 introduced omitzero which handles both correctly. Without the skill, the model may not know about omitzero.",
      "assertions": [
        { "id": "4.1", "text": "Identifies that omitempty doesn't omit zero time.Time (it's a non-empty struct)" },
        { "id": "4.2", "text": "Suggests omitzero for time.Time fields (StartAt, EndAt, LastLogin, AccountCreated)" },
        { "id": "4.3", "text": "Identifies that omitempty treats false as empty for bool fields" },
        { "id": "4.4", "text": "Addresses bool issue correctly (removes tag or uses omitzero)" },
        { "id": "4.5", "text": "Correctly notes omitzero requires Go 1.24+" },
        { "id": "4.6", "text": "Does NOT suggest omitzero for string or int fields where omitempty works correctly" }
      ]
    },
    {
      "id": 5,
      "name": "benchmark-b-loop",
      "prompt": "Modernize these benchmarks for our Go 1.24 project.\n\n```go\npackage encoding\n\nimport (\n    \"encoding/json\"\n    \"testing\"\n)\n\ntype Payload struct {\n    ID    int    `json:\"id\"`\n    Name  string `json:\"name\"`\n    Value float64 `json:\"value\"`\n}\n\nfunc BenchmarkMarshal(b *testing.B) {\n    p := Payload{ID: 1, Name: \"test\", Value: 3.14}\n    for i := 0; i < b.N; i++ {\n        json.Marshal(p)\n    }\n}\n\nfunc BenchmarkUnmarshal(b *testing.B) {\n    data := []byte(`{\"id\":1,\"name\":\"test\",\"value\":3.14}`)\n    var p Payload\n    for n := 0; n < b.N; n++ {\n        json.Unmarshal(data, &p)\n    }\n}\n\nfunc BenchmarkRoundTrip(b *testing.B) {\n    p := Payload{ID: 1, Name: \"test\", Value: 3.14}\n    for i := 0; i < b.N; i++ {\n        data, _ := json.Marshal(p)\n        var p2 Payload\n        json.Unmarshal(data, &p2)\n    }\n}\n```\n\nProvide the modernized benchmark code.",
      "trap": "Go 1.24 introduced b.Loop() which replaces the manual for i := 0; i < b.N; i++ pattern. Without the skill, the model likely doesn't know about b.Loop().",
      "assertions": [
        { "id": "5.1", "text": "Replaces for i := 0; i < b.N; i++ with for b.Loop() in BenchmarkMarshal" },
        { "id": "5.2", "text": "Replaces for n := 0; n < b.N; n++ with for b.Loop() in BenchmarkUnmarshal" },
        { "id": "5.3", "text": "Replaces the b.N loop in BenchmarkRoundTrip too" },
        { "id": "5.4", "text": "Does NOT keep any b.N iteration pattern in the output" },
        { "id": "5.5", "text": "Preserves benchmark function names and logic" }
      ]
    },
    {
      "id": 6,
      "name": "automaxprocs-removal",
      "prompt": "We just upgraded to Go 1.25. Review our main.go for modernization opportunities.\n\n```go\n// go.mod\nmodule example.com/worker\ngo 1.25\n\nrequire (\n    go.uber.org/automaxprocs v1.5.3\n    go.uber.org/zap v1.27.0\n)\n\n// main.go\npackage main\n\nimport (\n    \"fmt\"\n    \"sync\"\n\n    _ \"go.uber.org/automaxprocs\"\n)\n\nfunc main() {\n    var wg sync.WaitGroup\n    items := []string{\"a\", \"b\", \"c\", \"d\", \"e\"}\n\n    for _, item := range items {\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            process(item)\n        }()\n    }\n    wg.Wait()\n}\n\nfunc process(item string) {\n    fmt.Println(\"processing\", item)\n}\n```\n\nSuggest all modernization improvements.",
      "trap": "Go 1.25 has built-in container-aware GOMAXPROCS, making uber-go/automaxprocs unnecessary. The model must know this Go 1.25 addition — without the skill it will likely treat automaxprocs as still needed and only suggest sync.WaitGroup.Go.",
      "assertions": [
        { "id": "6.1", "text": "Suggests removing go.uber.org/automaxprocs import and dependency" },
        { "id": "6.2", "text": "Explains that Go 1.25 has built-in container-aware GOMAXPROCS (or cgroup CPU limits awareness)" },
        { "id": "6.3", "text": "Suggests sync.WaitGroup.Go to replace Add/go func/Done pattern" },
        { "id": "6.4", "text": "Does NOT suggest keeping the automaxprocs dependency as still necessary" },
        { "id": "6.5", "text": "Suggests running go mod tidy to remove the dependency from go.sum" }
      ]
    },
    {
      "id": 7,
      "name": "cmp-or-chained-defaults",
      "prompt": "Clean up this configuration loading code. We're on Go 1.22. The nested if/else chains for default values are hard to read.\n\n```go\npackage config\n\nimport \"os\"\n\ntype Config struct {\n    Host     string\n    Port     string\n    LogLevel string\n    Region   string\n    Mode     string\n}\n\nfunc LoadConfig() Config {\n    host := os.Getenv(\"HOST\")\n    if host == \"\" {\n        host = os.Getenv(\"HOSTNAME\")\n    }\n    if host == \"\" {\n        host = os.Getenv(\"SERVICE_HOST\")\n    }\n    if host == \"\" {\n        host = \"localhost\"\n    }\n\n    port := os.Getenv(\"PORT\")\n    if port == \"\" {\n        port = os.Getenv(\"HTTP_PORT\")\n    }\n    if port == \"\" {\n        port = \"8080\"\n    }\n\n    logLevel := os.Getenv(\"LOG_LEVEL\")\n    if logLevel == \"\" {\n        logLevel = os.Getenv(\"LOGLEVEL\")\n    }\n    if logLevel == \"\" {\n        logLevel = \"info\"\n    }\n\n    region := os.Getenv(\"AWS_REGION\")\n    if region == \"\" {\n        region = os.Getenv(\"REGION\")\n    }\n    if region == \"\" {\n        region = \"us-east-1\"\n    }\n\n    mode := os.Getenv(\"APP_MODE\")\n    if mode == \"\" {\n        mode = \"production\"\n    }\n\n    return Config{\n        Host:     host,\n        Port:     port,\n        LogLevel: logLevel,\n        Region:   region,\n        Mode:     mode,\n    }\n}\n```\n\nProvide the cleaned-up code.",
      "trap": "Go 1.22 introduced cmp.Or(a, b, c...) which returns the first non-zero value. It collapses multi-step default chains to single lines. Without the skill, the model will likely keep the if/else chains or use a custom helper function.",
      "assertions": [
        { "id": "7.1", "text": "Uses cmp.Or for at least one default value chain" },
        { "id": "7.2", "text": "Collapses the 3-step host default to a single cmp.Or call" },
        { "id": "7.3", "text": "Import includes the cmp package" },
        { "id": "7.4", "text": "All multi-step defaults converted to cmp.Or (host, port, logLevel, region)" },
        { "id": "7.5", "text": "Result is functionally equivalent (same fallback order)" },
        { "id": "7.6", "text": "Does NOT introduce a custom helper function for defaults" }
      ]
    },
    {
      "id": 8,
      "name": "addcleanup-vs-setfinalizer",
      "prompt": "Review this resource management code for Go 1.24 best practices.\n\n```go\npackage pool\n\nimport (\n    \"database/sql\"\n    \"fmt\"\n    \"runtime\"\n)\n\ntype ManagedConn struct {\n    db   *sql.DB\n    name string\n    dsn  string\n}\n\nfunc NewManagedConn(dsn, name string) (*ManagedConn, error) {\n    db, err := sql.Open(\"postgres\", dsn)\n    if err != nil {\n        return nil, fmt.Errorf(\"opening db: %w\", err)\n    }\n    mc := &ManagedConn{db: db, name: name, dsn: dsn}\n    runtime.SetFinalizer(mc, func(c *ManagedConn) {\n        c.db.Close()\n    })\n    return mc, nil\n}\n\ntype TempFile struct {\n    path string\n    fd   int\n}\n\nfunc NewTempFile(path string, fd int) *TempFile {\n    tf := &TempFile{path: path, fd: fd}\n    runtime.SetFinalizer(tf, func(f *TempFile) {\n        syscallClose(f.fd)\n        osRemove(f.path)\n    })\n    return tf\n}\n\nfunc syscallClose(fd int) {}\nfunc osRemove(path string) {}\n```\n\nModernize the cleanup pattern. Explain why the current approach has problems.",
      "trap": "New Go code should consider runtime.AddCleanup instead of runtime.SetFinalizer because it is less error-prone. The key API difference: AddCleanup takes the resource as a separate argument (not the whole object). Without the skill the model is unlikely to know AddCleanup exists or that SetFinalizer's cycle restriction is a major problem.",
      "assertions": [
        { "id": "8.1", "text": "Replaces runtime.SetFinalizer with runtime.AddCleanup" },
        { "id": "8.2", "text": "AddCleanup cleanup function receives the resource (db/*sql.DB or fd/int) as a separate argument, NOT the whole wrapper struct" },
        { "id": "8.3", "text": "Explains that SetFinalizer prevents GC when the object holds a reference to itself or another object with a finalizer (cycle restriction)" },
        { "id": "8.4", "text": "Does NOT pass ManagedConn or TempFile directly as the resource to AddCleanup" },
        { "id": "8.5", "text": "Correctly attributes runtime.AddCleanup to Go 1.24 (or notes it is the modern replacement)" }
      ]
    },
    {
      "id": 9,
      "name": "http-mux-migration",
      "prompt": "We want to remove our gorilla/mux dependency. Migrate this REST API router to stdlib. We're on Go 1.22.\n\n```go\npackage api\n\nimport (\n    \"encoding/json\"\n    \"net/http\"\n\n    \"github.com/gorilla/mux\"\n)\n\nfunc SetupRouter() *mux.Router {\n    r := mux.NewRouter()\n    r.HandleFunc(\"/api/users\", listUsers).Methods(\"GET\")\n    r.HandleFunc(\"/api/users\", createUser).Methods(\"POST\")\n    r.HandleFunc(\"/api/users/{id}\", getUser).Methods(\"GET\")\n    r.HandleFunc(\"/api/users/{id}\", updateUser).Methods(\"PUT\")\n    r.HandleFunc(\"/api/users/{id}\", deleteUser).Methods(\"DELETE\")\n    r.HandleFunc(\"/api/health\", healthCheck).Methods(\"GET\")\n    return r\n}\n\nfunc getUser(w http.ResponseWriter, r *http.Request) {\n    vars := mux.Vars(r)\n    id := vars[\"id\"]\n    json.NewEncoder(w).Encode(map[string]string{\"id\": id})\n}\n\nfunc listUsers(w http.ResponseWriter, r *http.Request)  { json.NewEncoder(w).Encode([]string{\"user1\", \"user2\"}) }\nfunc createUser(w http.ResponseWriter, r *http.Request)  { w.WriteHeader(http.StatusCreated) }\nfunc updateUser(w http.ResponseWriter, r *http.Request)  { w.WriteHeader(http.StatusOK) }\nfunc deleteUser(w http.ResponseWriter, r *http.Request)  { w.WriteHeader(http.StatusNoContent) }\nfunc healthCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }\n```\n\nProvide the complete migrated code.",
      "trap": "Go 1.22 added method+path pattern routing to net/http. The exact syntax is 'METHOD /path/{param}' as the pattern string. mux.Vars(r) becomes r.PathValue('id'). Without the skill, the model might not use the correct syntax or might suggest a different third-party router.",
      "assertions": [
        { "id": "9.1", "text": "Uses http.NewServeMux() instead of mux.NewRouter()" },
        { "id": "9.2", "text": "Uses method prefix in patterns like 'GET /api/users/{id}'" },
        { "id": "9.3", "text": "Uses r.PathValue(\"id\") instead of mux.Vars(r)" },
        { "id": "9.4", "text": "All 6 routes migrated with correct method prefixes (GET, POST, PUT, DELETE)" },
        { "id": "9.5", "text": "No gorilla/mux import remains" },
        { "id": "9.6", "text": "Return type changes from *mux.Router to *http.ServeMux" }
      ]
    },
    {
      "id": 10,
      "name": "synctest-flaky-fix",
      "prompt": "This concurrent test is flaky in CI — it passes locally but fails ~20% of the time in GitHub Actions. Fix it properly. We're on Go 1.25.\n\n```go\npackage pubsub\n\nimport (\n    \"sync\"\n    \"testing\"\n    \"time\"\n)\n\ntype Broker struct {\n    mu   sync.RWMutex\n    subs map[string][]chan string\n}\n\nfunc NewBroker() *Broker {\n    return &Broker{subs: make(map[string][]chan string)}\n}\n\nfunc (b *Broker) Subscribe(topic string) <-chan string {\n    b.mu.Lock()\n    defer b.mu.Unlock()\n    ch := make(chan string, 10)\n    b.subs[topic] = append(b.subs[topic], ch)\n    return ch\n}\n\nfunc (b *Broker) Publish(topic, msg string) {\n    b.mu.RLock()\n    defer b.mu.RUnlock()\n    for _, ch := range b.subs[topic] {\n        ch <- msg\n    }\n}\n\nfunc TestBrokerPubSub(t *testing.T) {\n    b := NewBroker()\n    ch1 := b.Subscribe(\"events\")\n    ch2 := b.Subscribe(\"events\")\n\n    go b.Publish(\"events\", \"hello\")\n\n    time.Sleep(50 * time.Millisecond) // flaky!\n\n    got1 := <-ch1\n    got2 := <-ch2\n\n    if got1 != \"hello\" {\n        t.Errorf(\"ch1: got %q, want %q\", got1, \"hello\")\n    }\n    if got2 != \"hello\" {\n        t.Errorf(\"ch2: got %q, want %q\", got2, \"hello\")\n    }\n}\n\nfunc TestBrokerMultiTopic(t *testing.T) {\n    b := NewBroker()\n    events := b.Subscribe(\"events\")\n    logs := b.Subscribe(\"logs\")\n\n    go func() {\n        b.Publish(\"events\", \"event1\")\n        b.Publish(\"logs\", \"log1\")\n    }()\n\n    time.Sleep(100 * time.Millisecond) // flaky!\n\n    if got := <-events; got != \"event1\" {\n        t.Errorf(\"events: got %q, want %q\", got, \"event1\")\n    }\n    if got := <-logs; got != \"log1\" {\n        t.Errorf(\"logs: got %q, want %q\", got, \"log1\")\n    }\n}\n```\n\nFix the flakiness without increasing sleep durations.",
      "trap": "Go 1.25 introduced testing/synctest.Test which provides deterministic concurrent testing with synctest.Wait(). The natural fix is to increase sleep, use channels for sync, or add retries. The skill teaches synctest.Test as the modern solution. Note: synctest.Run was the old Go 1.24 experimental API — use synctest.Test in Go 1.25+.",
      "assertions": [
        { "id": "10.1", "text": "Uses synctest.Test (NOT the old Go 1.24 experimental synctest.Run API)" },
        { "id": "10.2", "text": "Uses synctest.Wait() for goroutine synchronization" },
        { "id": "10.3", "text": "Removes all time.Sleep calls" },
        { "id": "10.4", "text": "No flaky timing dependencies remain" },
        { "id": "10.5", "text": "Correctly imports testing/synctest" },
        { "id": "10.6", "text": "Both tests converted (TestBrokerPubSub and TestBrokerMultiTopic)" }
      ]
    },
    {
      "id": 11,
      "name": "waitgroup-go-loopvar",
      "prompt": "Modernize this concurrent processing code. We're on Go 1.25.\n\n```go\npackage worker\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"sync\"\n    \"testing\"\n)\n\nfunc ProcessAll(items []string) error {\n    var wg sync.WaitGroup\n    errCh := make(chan error, len(items))\n\n    for _, item := range items {\n        item := item // shadow copy for closure safety\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            if err := process(item); err != nil {\n                errCh <- err\n            }\n        }()\n    }\n\n    wg.Wait()\n    close(errCh)\n\n    for err := range errCh {\n        return err\n    }\n    return nil\n}\n\nfunc RunBatch(tasks []func()) {\n    var wg sync.WaitGroup\n    for _, task := range tasks {\n        task := task\n        wg.Add(1)\n        go func() {\n            defer wg.Done()\n            task()\n        }()\n    }\n    wg.Wait()\n}\n\nfunc TestProcess(t *testing.T) {\n    ctx := context.Background()\n    result, err := processWithContext(ctx, \"test\")\n    if err != nil {\n        t.Fatal(err)\n    }\n    fmt.Println(result)\n}\n\nfunc process(item string) error { return nil }\nfunc processWithContext(ctx context.Context, s string) (string, error) { return s, nil }\n```\n\nProvide the fully modernized code.",
      "trap": "Go 1.25 introduced sync.WaitGroup.Go. Go 1.22+ fixed loop variable semantics (v := v copies are unnecessary). Go 1.24+ has t.Context(). Without the skill, the model may not know WaitGroup.Go exists and may keep the shadow copies.",
      "assertions": [
        { "id": "11.1", "text": "Replaces Add/go func/Done pattern with wg.Go(func() { ... })" },
        { "id": "11.2", "text": "Removes wg.Add(1) calls" },
        { "id": "11.3", "text": "Removes defer wg.Done() calls" },
        { "id": "11.4", "text": "Removes item := item loop variable shadow copies" },
        { "id": "11.5", "text": "Explains Go 1.22+ loop variable semantics make copies unnecessary" },
        { "id": "11.6", "text": "Replaces context.Background() with t.Context() in test" },
        { "id": "11.7", "text": "Preserves WaitGroup.Wait() call" }
      ]
    },
    {
      "id": 12,
      "name": "timer-gc-greenteagc",
      "prompt": "We upgraded to Go 1.26. Review this code for things we can clean up.\n\n```go\n// go.mod\nmodule example.com/scheduler\ngo 1.26\n\n// scheduler.go\npackage scheduler\n\nimport (\n    \"os\"\n    \"runtime/debug\"\n    \"time\"\n)\n\nfunc init() {\n    // Tune GC for lower latency\n    debug.SetGCPercent(50)\n    debug.SetMemoryLimit(2 << 30) // 2GB\n    os.Setenv(\"GOGC\", \"50\")\n}\n\nfunc RunAfter(d time.Duration, fn func()) {\n    timer := time.NewTimer(d)\n    defer timer.Stop() // prevent timer leak\n    <-timer.C\n    fn()\n}\n\nfunc PeriodicTask(interval time.Duration, fn func()) chan struct{} {\n    done := make(chan struct{})\n    go func() {\n        ticker := time.NewTicker(interval)\n        defer ticker.Stop() // prevent ticker leak\n        for {\n            select {\n            case <-ticker.C:\n                fn()\n            case <-done:\n                return\n            }\n        }\n    }()\n    return done\n}\n\nfunc Debounce(d time.Duration, fn func()) func() {\n    var timer *time.Timer\n    return func() {\n        if timer != nil {\n            timer.Stop()\n        }\n        timer = time.AfterFunc(d, fn)\n    }\n}\n\nfunc Timeout(d time.Duration) <-chan time.Time {\n    timer := time.NewTimer(d)\n    defer timer.Stop()\n    return timer.C\n}\n```\n\nSuggest all modernization opportunities.",
      "trap": "Go 1.23+ timers/tickers are GC'd without Stop(). The Stop() in PeriodicTask is still needed for correctness. Go 1.26's Green Tea GC (10-40% overhead reduction) may make manual tuning unnecessary. go fix ./... is available in Go 1.26.",
      "assertions": [
        { "id": "12.1", "text": "Identifies that some defer timer.Stop() calls are unnecessary with Go 1.23+" },
        { "id": "12.2", "text": "Explains the timer/ticker GC behavior change (collected without Stop)" },
        { "id": "12.3", "text": "Suggests reviewing GC tuning (SetGCPercent/SetMemoryLimit) due to Green Tea GC" },
        { "id": "12.4", "text": "Mentions Green Tea GC's 10-40% overhead reduction" },
        { "id": "12.5", "text": "Does NOT suggest removing Stop() from PeriodicTask ticker (needed for correctness)" },
        { "id": "12.6", "text": "Suggests go fix ./... for automated modernization (Go 1.26)" },
        { "id": "12.7", "text": "Correctly distinguishes between Stop for GC (removable) and Stop for correctness (keep)" }
      ]
    },
    {
      "id": 13,
      "name": "go126-errors-astype-enhanced-new",
      "prompt": "Modernize this Go 1.26 error handling and pointer helper code.\n\n```go\npackage service\n\nimport (\n    \"errors\"\n    \"fmt\"\n    \"net\"\n    \"os\"\n    \"time\"\n)\n\nfunc ptr[T any](v T) *T { return &v }\n\ntype ServiceConfig struct {\n    Timeout  *time.Duration\n    Retries  *int\n    Verbose  *bool\n}\n\nfunc DefaultConfig() ServiceConfig {\n    return ServiceConfig{\n        Timeout: ptr(30 * time.Second),\n        Retries: ptr(3),\n        Verbose: ptr(false),\n    }\n}\n\nfunc HandleError(err error) string {\n    var pathErr *os.PathError\n    if errors.As(err, &pathErr) {\n        return fmt.Sprintf(\"path error: %s\", pathErr.Path)\n    }\n\n    var netErr *net.OpError\n    if errors.As(err, &netErr) {\n        return fmt.Sprintf(\"net error: %s %s\", netErr.Op, netErr.Net)\n    }\n\n    var dnsErr *net.DNSError\n    if errors.As(err, &dnsErr) {\n        return fmt.Sprintf(\"dns error: %s\", dnsErr.Name)\n    }\n\n    return err.Error()\n}\n```\n\nApply all Go 1.26 modernizations. Provide the updated code.",
      "trap": "Go 1.26 introduced errors.AsType[T]() which replaces the verbose var+errors.As pattern with a single-line if. Go 1.26 also enhanced new() to accept an initial value, replacing the ptr[T] helper. Without the skill, the model likely won't know either feature exists.",
      "assertions": [
        { "id": "13.1", "text": "Uses errors.AsType[*os.PathError](err) or similar generic form instead of var+errors.As" },
        { "id": "13.2", "text": "Replaces the ptr[T] helper function with new() that accepts an initial value (e.g., new(30 * time.Second))" },
        { "id": "13.3", "text": "Applies the same errors.AsType rewrite consistently to the net.OpError and net.DNSError branches, not just the first one" },
        { "id": "13.4", "text": "Attributes errors.AsType and the enhanced new() correctly to Go 1.26, not to a newer or older release" }
      ]
    },
    {
      "id": 14,
      "name": "json-v2-duplicate-key-strictness",
      "prompt": "We upgraded this service to Go 1.27 last week. Since then, ingestion of webhook payloads from one partner started failing intermittently with decode errors. Review the code and explain what changed.\n\n```go\npackage webhook\n\nimport (\n    \"encoding/json\"\n    \"net/http\"\n)\n\ntype Event struct {\n    ID     string `json:\"id\"`\n    Type   string `json:\"type\"`\n    Amount int    `json:\"amount\"`\n}\n\nfunc HandlePartnerWebhook(w http.ResponseWriter, r *http.Request) {\n    var ev Event\n    if err := json.NewDecoder(r.Body).Decode(&ev); err != nil {\n        http.Error(w, \"invalid payload\", http.StatusBadRequest)\n        return\n    }\n    process(ev)\n}\n\nfunc process(ev Event) {}\n```\n\nThe partner's webhook payloads are known to occasionally repeat a JSON key (e.g. `{\"id\":\"1\",\"id\":\"1-dup\",\"type\":\"charge\",\"amount\":500}`), which used to decode fine, keeping the last value. Diagnose the regression and fix it.",
      "trap": "encoding/json now runs on top of encoding/json/v2 by default since Go 1.27, and v2 rejects duplicate object member names instead of silently keeping the last one like v1 did. Without the skill, the model may not connect the Go 1.27 bump to this specific new decode-time failure and may guess at unrelated causes (malformed JSON, network issues).",
      "assertions": [
        { "id": "14.1", "text": "Identifies that Go 1.27 made encoding/json/v2 the default JSON implementation underneath encoding/json" },
        { "id": "14.2", "text": "Explains that duplicate JSON object keys are now rejected by default, whereas v1 silently kept the last value" },
        { "id": "14.3", "text": "Connects the partner payload's duplicate id key directly to the reported decode failures" },
        { "id": "14.4", "text": "Proposes a concrete fix: either normalize/dedupe the payload before decoding, or explicitly opts back into v1 behavior only as a temporary bridge" },
        { "id": "14.5", "text": "Names GOEXPERIMENT=nojsonv2 as an escape hatch, not as the recommended long-term fix" },
        { "id": "14.6", "text": "Does NOT claim encoding/json's decode behavior is unaffected by upgrading to Go 1.27" }
      ]
    },
    {
      "id": 15,
      "name": "generic-method-scoped-transform",
      "prompt": "This project targets Go 1.27. Add a way to transform every element of a generic Set into a Set of a different element type, keeping the transform function type-safe.\n\n```go\npackage collections\n\ntype Set[T comparable] map[T]struct{}\n\nfunc NewSet[T comparable](items ...T) Set[T] {\n    s := make(Set[T], len(items))\n    for _, item := range items {\n        s[item] = struct{}{}\n    }\n    return s\n}\n\nfunc (s Set[T]) Contains(item T) bool {\n    _, ok := s[item]\n    return ok\n}\n```\n\nAdd the transform capability and show how it's called from `main`.",
      "trap": "Go 1.27 lets methods declare their own type parameters, so a transform from Set[T] to Set[U] can be a method on Set[T] with U scoped to the method. Without the skill, the natural default is a package-level generic function `func MapSet[T, U comparable](s Set[T], f func(T) U) Set[U]` because pre-1.27 Go had no way to add a type parameter to a method.",
      "assertions": [
        { "id": "15.1", "text": "Declares the transform as a method on Set[T] (e.g. func (s Set[T]) Map[U comparable](f func(T) U) Set[U]) rather than a standalone package-level generic function" },
        { "id": "15.2", "text": "The new type parameter (U) is declared on the method itself, not added to the Set[T] type definition" },
        { "id": "15.3", "text": "Notes that generic methods declaring their own type parameters are a Go 1.27 addition" },
        { "id": "15.4", "text": "Calls out that this method could not satisfy an interface method and that interface methods themselves cannot declare type parameters" },
        { "id": "15.5", "text": "The example call site compiles conceptually (correct method call syntax with explicit or inferred U)" }
      ]
    },
    {
      "id": 16,
      "name": "cutlast-final-separator-split",
      "prompt": "This Go 1.27 log processor needs a helper that splits a structured log line into everything before and everything after the LAST colon, since fields before the last colon may themselves contain colons (e.g. `\"svc:sub:handler:the message: with a colon\"` should split into `\"svc:sub:handler:the message\"` and `\" with a colon\"`).\n\n```go\npackage logs\n\nfunc SplitAtLastColon(line string) (head, tail string, ok bool) {\n    // TODO: implement\n    return \"\", \"\", false\n}\n```\n\nImplement `SplitAtLastColon`.",
      "trap": "strings.CutLast(s, sep) is the direct fit here, added in Go 1.27. Without the skill, the natural implementation is strings.LastIndex plus manual index slicing (i, i+len(sep)), which is easy to get off-by-one on, or strings.Split, which is wrong here because it splits on every colon rather than just the last one.",
      "assertions": [
        { "id": "16.1", "text": "Uses strings.CutLast(line, \":\") to implement the function" },
        { "id": "16.2", "text": "Returns the CutLast found boolean directly as ok, rather than deriving it from an index comparison" },
        { "id": "16.3", "text": "Does NOT use strings.LastIndex with manual slice arithmetic" },
        { "id": "16.4", "text": "Does NOT use strings.Split or strings.SplitN, which would split on every colon instead of only the last one" },
        { "id": "16.5", "text": "Notes that CutLast requires Go 1.27 or later" }
      ]
    },
    {
      "id": 17,
      "name": "godebug-removed-key-build-break",
      "prompt": "Upgrade this project to the latest stable Go toolchain and update `go.mod` accordingly.\n\n```\nmodule example.com/svc\n\ngo 1.24\n\ngodebug (\n    asynctimerchan=0\n)\n\nrequire (\n    github.com/lib/pq v1.10.9\n)\n```\n\nThe `asynctimerchan=0` line was added a while ago to keep `time.Timer`/`time.Ticker` channels buffered like they were before Go 1.23, because some code relied on a stale timer value still sitting in the channel. Provide the updated `go.mod`.",
      "trap": "Go 1.27 removed the asynctimerchan GODEBUG setting entirely; a go.mod godebug entry still pinning it to its old (pre-1.23) value now fails the build, not just prints a warning. The natural default without the skill is to bump only the go directive and leave the godebug block untouched, assuming an old GODEBUG pin is harmless leftover config.",
      "assertions": [
        { "id": "17.1", "text": "Removes or updates the asynctimerchan=0 godebug entry rather than leaving it as-is" },
        { "id": "17.2", "text": "Explains that Go 1.27 removed this GODEBUG setting and that a go.mod entry pinned to the old value now fails the build (not just a warning)" },
        { "id": "17.3", "text": "Bumps the go directive to a current Go 1.27-era version" },
        { "id": "17.4", "text": "Recommends actually fixing the underlying code that relied on buffered timer channels rather than just deleting the compatibility shim silently" },
        { "id": "17.5", "text": "Mentions checking for other Go 1.27-removed GODEBUG keys (e.g. tls3des, gotypesalias, tlsrsakex) as part of the same upgrade, not just asynctimerchan" }
      ]
    },
    {
      "id": 18,
      "name": "uuid-stdlib-vs-dependency",
      "prompt": "Add a function to this Go 1.27 order service that generates a unique identifier for each new order. There is currently no UUID library in `go.mod`.\n\n```go\npackage orders\n\ntype Order struct {\n    ID     string\n    Amount int\n}\n\nfunc NewOrder(amount int) Order {\n    return Order{\n        // TODO: generate a unique ID\n        Amount: amount,\n    }\n}\n```\n\nImplement ID generation.",
      "trap": "Go 1.27 added a uuid package to the standard library. Without the skill, the natural default is to run `go get github.com/google/uuid` and call uuid.New(), which is the long-standing community convention and still works, but now adds an unnecessary dependency to a Go 1.27 project.",
      "assertions": [
        { "id": "18.1", "text": "Uses the standard library uuid package (import \"uuid\") rather than github.com/google/uuid or gofrs/uuid" },
        { "id": "18.2", "text": "Does NOT add a new external module dependency to go.mod for UUID generation" },
        { "id": "18.3", "text": "Notes that the stdlib uuid package is new in Go 1.27" },
        { "id": "18.4", "text": "Generated ID is stored via UUID.String() (or equivalent) to fit the existing string-typed Order.ID field" },
        { "id": "18.5", "text": "Does NOT claim the stdlib uuid package existed before Go 1.27" }
      ]
    },
    {
      "id": 19,
      "name": "json-v2-streaming-migration",
      "prompt": "Add a `LoadFromReader(r io.Reader, v any) error` helper to this Go 1.27 config package, so callers can decode JSON directly from a stream (an HTTP response body, an open file) without buffering the whole payload into a byte slice first.\n\n```go\npackage config\n\nimport (\n    \"encoding/json\"\n    \"io\"\n)\n\nfunc LoadFromBytes(data []byte, v any) error {\n    return json.Unmarshal(data, v)\n}\n\n// TODO: add LoadFromReader\n```\n\nImplement `LoadFromReader`.",
      "trap": "encoding/json/v2 is the default JSON implementation since Go 1.27 and exposes UnmarshalRead(r, v) for exactly this streaming case. Without the skill, the natural default is json.NewDecoder(r).Decode(v) from the v1-era API, which still works but isn't the migration path the skill should point toward for new Go 1.27 code.",
      "assertions": [
        { "id": "19.1", "text": "Uses encoding/json/v2's UnmarshalRead(r, v) (or explicitly imports encoding/json/v2 and calls it) rather than only the v1-era json.NewDecoder(r).Decode(v) pattern" },
        { "id": "19.2", "text": "Notes that encoding/json/v2 is the new default JSON implementation as of Go 1.27" },
        { "id": "19.3", "text": "Does NOT introduce a manual io.ReadAll followed by json.Unmarshal, which would defeat the point of streaming from the reader" },
        { "id": "19.4", "text": "Keeps LoadFromBytes working as-is rather than rewriting unrelated code" },
        { "id": "19.5", "text": "The new function's signature matches the requested LoadFromReader(r io.Reader, v any) error" }
      ]
    },
    {
      "id": 20,
      "name": "go127-stdlib-uuid",
      "prompt": "Modernize this Go 1.27 code. We'd like to reduce third-party dependencies where the standard library is sufficient.\n\n```go\n// go.mod\nmodule example.com/orders\ngo 1.27\n\nrequire github.com/google/uuid v1.6.0\n\n// ids.go\npackage orders\n\nimport \"github.com/google/uuid\"\n\nfunc NewOrderID() string {\n    return uuid.NewString()\n}\n\nfunc NewSortableOrderID() (string, error) {\n    // we want roughly time-ordered IDs for our database primary keys\n    id, err := uuid.NewV7()\n    if err != nil {\n        return \"\", err\n    }\n    return id.String(), nil\n}\n\nfunc ParseOrderID(s string) (uuid.UUID, error) {\n    return uuid.Parse(s)\n}\n```\n\nProvide the updated code and go.mod changes.",
      "trap": "Training data is saturated with github.com/google/uuid, so the model defaults to keeping it. Go 1.27 added a stdlib uuid package whose API differs: generators return values without errors, there is no NewString() (use uuid.New().String()), and the import path is just \"uuid\". Without the skill the model either keeps google/uuid or hallucinates uuid.NewString() on the stdlib package.",
      "assertions": [
        { "id": "20.1", "text": "Replaces github.com/google/uuid with the stdlib uuid package (import \"uuid\")" },
        { "id": "20.2", "text": "Uses uuid.New().String() — does NOT call uuid.NewString() (does not exist in stdlib)" },
        { "id": "20.3", "text": "Knows stdlib generators return values without errors (no id, err := uuid.NewV7() error handling)" },
        { "id": "20.4", "text": "Uses uuid.NewV7() for the time-ordered database ID case" },
        { "id": "20.5", "text": "Removes github.com/google/uuid from go.mod (e.g. via go mod tidy)" }
      ]
    },
    {
      "id": 21,
      "name": "go127-generic-methods",
      "prompt": "We're on Go 1.27. This generic container library grew organically and the package-level helper functions are awkward to discover. Improve the API ergonomics.\n\n```go\npackage set\n\ntype Set[T comparable] struct {\n    items map[T]struct{}\n}\n\nfunc New[T comparable](items ...T) *Set[T] {\n    s := &Set[T]{items: make(map[T]struct{}, len(items))}\n    for _, it := range items {\n        s.items[it] = struct{}{}\n    }\n    return s\n}\n\nfunc (s *Set[T]) Add(v T) { s.items[v] = struct{}{} }\n\nfunc (s *Set[T]) Contains(v T) bool { _, ok := s.items[v]; return ok }\n\nfunc (s *Set[T]) ForEach(f func(T)) {\n    for v := range s.items {\n        f(v)\n    }\n}\n\n// MapSet applies f to every element and returns a new set.\nfunc MapSet[T comparable, U comparable](s *Set[T], f func(T) U) *Set[U] {\n    out := New[U]()\n    s.ForEach(func(v T) { out.Add(f(v)) })\n    return out\n}\n\n// FilterSet returns the elements of s for which keep returns true.\nfunc FilterSet[T comparable](s *Set[T], keep func(T) bool) *Set[T] {\n    out := New[T]()\n    s.ForEach(func(v T) {\n        if keep(v) {\n            out.Add(v)\n        }\n    })\n    return out\n}\n\ntype Iteratable[T comparable] interface {\n    ForEach(f func(T))\n}\n\nvar _ Iteratable[int] = (*Set[int])(nil)\n```\n\nProvide the improved code.",
      "trap": "For 15+ years Go did not support generic methods, so the model's strong prior is that methods cannot have their own type parameters. Go 1.27 allows them. Without the skill, the model keeps the package-level functions or wrongly claims the conversion is impossible. The interface member ForEach must stay a non-generic method because generic methods cannot satisfy interfaces.",
      "assertions": [
        { "id": "21.1", "text": "Converts MapSet to a generic method with its own type parameter (e.g. func (s *Set[T]) Map[U comparable](f func(T) U) *Set[U])" },
        { "id": "21.2", "text": "Converts FilterSet to a method on *Set[T]" },
        { "id": "21.3", "text": "Does NOT claim generic methods are impossible or unsupported in Go" },
        { "id": "21.4", "text": "Keeps ForEach as a non-generic method so Set still satisfies the Iteratable interface (or explicitly notes generic methods cannot satisfy interfaces)" }
      ]
    },
    {
      "id": 22,
      "name": "go127-cutlast",
      "prompt": "Modernize this Go 1.27 code that splits strings around their last separator.\n\n```go\npackage paths\n\nimport \"strings\"\n\n// SplitPath splits a slash-separated path into directory and file name.\nfunc SplitPath(path string) (dir, file string) {\n    if i := strings.LastIndex(path, \"/\"); i >= 0 {\n        return path[:i], path[i+1:]\n    }\n    return \"\", path\n}\n\n// StripExt removes the extension from a file name.\nfunc StripExt(name string) string {\n    if i := strings.LastIndex(name, \".\"); i > 0 {\n        return name[:i]\n    }\n    return name\n}\n\n// EnvValue splits a KEY=value assignment.\nfunc EnvValue(assignment string) (key, value string, ok bool) {\n    i := strings.LastIndex(assignment, \"=\")\n    if i < 0 {\n        return \"\", \"\", false\n    }\n    return assignment[:i], assignment[i+1:], true\n}\n```\n\nProvide the modernized code.",
      "trap": "Go 1.27 added strings.CutLast/bytes.CutLast which slice around the LAST occurrence of a separator, mirroring the widely-known strings.Cut. The model knows Cut but is very unlikely to know CutLast exists, so without the skill it keeps manual LastIndex arithmetic.",
      "assertions": [
        { "id": "22.1", "text": "Uses strings.CutLast for at least one of the functions" },
        { "id": "22.2", "text": "Uses the 3-result form (before, after, found) of CutLast correctly" },
        { "id": "22.3", "text": "Uses CutLast in EnvValue to simplify the ok-returning split" },
        { "id": "22.4", "text": "Does NOT keep manual strings.LastIndex slicing arithmetic where CutLast applies" }
      ]
    },
    {
      "id": 23,
      "name": "go127-synctest-http",
      "prompt": "This Go 1.27 HTTP handler test is flaky in CI because of real timeouts. Make it deterministic and fast.\n\n```go\npackage api\n\nimport (\n    \"io\"\n    \"net/http\"\n    \"net/http/httptest\"\n    \"testing\"\n    \"time\"\n)\n\nfunc Handler(w http.ResponseWriter, r *http.Request) {\n    time.Sleep(100 * time.Millisecond) // simulates slow backend\n    w.Write([]byte(\"ok\"))\n}\n\nfunc TestHandlerResponds(t *testing.T) {\n    srv := httptest.NewServer(http.HandlerFunc(Handler))\n    defer srv.Close()\n\n    client := &http.Client{Timeout: 5 * time.Second}\n    resp, err := client.Get(srv.URL)\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer resp.Body.Close()\n    body, _ := io.ReadAll(resp.Body)\n    if string(body) != \"ok\" {\n        t.Fatalf(\"got %q\", body)\n    }\n}\n\nfunc TestHandlerClientTimeout(t *testing.T) {\n    srv := httptest.NewServer(http.HandlerFunc(Handler))\n    defer srv.Close()\n\n    client := &http.Client{Timeout: 50 * time.Millisecond} // shorter than handler sleep\n    _, err := client.Get(srv.URL)\n    if err == nil {\n        t.Fatal(\"expected timeout error\")\n    }\n}\n```\n\nProvide the fixed tests. Do not just increase timeouts.",
      "trap": "testing/synctest makes time-based tests deterministic, but a plain httptest.NewServer uses the real network, which stalls inside a synctest bubble (the bubble's fake clock waits forever on real I/O). Go 1.27 added httptest.NewTestServer, which runs on an in-memory fake network designed for synctest. Without the skill, the model either avoids synctest (retries/channels) or combines synctest with httptest.NewServer, producing a hanging test.",
      "assertions": [
        { "id": "23.1", "text": "Wraps tests in synctest.Test" },
        { "id": "23.2", "text": "Uses httptest.NewTestServer instead of httptest.NewServer" },
        { "id": "23.3", "text": "Explains that real-network servers do not work inside a synctest bubble (in-memory/fake network rationale)" },
        { "id": "23.4", "text": "Does NOT leave a plain httptest.NewServer inside a synctest bubble" },
        { "id": "23.5", "text": "Keeps both tests working (response test and client-timeout test) with deterministic time" }
      ]
    }
  ]
}

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-modernize · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-modernize.zip