feat(golang-modernize): add Go 1.27 support, fix sibling skill drift (#101)

* feat(golang-modernize): add Go 1.27 support, fix sibling skill drift

Go 1.27 shipped August 2026 and made several existing statements in
this plugin factually wrong, not just outdated - encoding/json/v2 is
now the default (was documented as experimental-only), the goroutine
leak profile is GA (was documented as GOEXPERIMENT-gated), and go
fix's modernizer suite changed. An agent loading these skills today
would give incorrect advice.

Rebased onto main, which had independently merged PR #98 (a smaller,
overlapping Go 1.27 pass on the same files). Reconciled rather than
overwritten: kept PR #98's stdlib-uuid/generic-methods/CutLast/
synctest-http evals (renumbered 20-23) alongside this branch's own,
folded its unique versions.md content (go fix modernizers, stdversion,
go mod tidy merge, small API preferences) into the merged Go 1.27
section, and corrected its json/v2 section, which claimed v1 behavior
is preserved while also stating v2 rejects duplicate keys - those two
claims contradict each other once v1 sits on top of v2 by default.

- golang-modernize: merged Go 1.27 section (generic methods with a
  citation to the Go issue/spec after an eval showed models otherwise
  distrust the claim, CutLast, URL/Values.Clone, math/big.Int.Divide,
  stdlib uuid, json/v2 migration), a version-bump risk checklist for
  changes that need verification rather than a rewrite (removed
  GODEBUG keys, json/v2 strictness), stdversion workflow step, and
  skill-library-version tracking so future drift gets caught by the
  staleness check instead of by accident.
- 11 sibling skills (concurrency, troubleshooting, refactoring,
  popular-libraries, performance, continuous-integration,
  dependency-management, testing, benchmark, observability,
  documentation): surgical corrections wherever Go 1.27 broke a prior
  claim, plus additive Go 1.27 notes where directly relevant.
- evals.json: merged to 23 evals / 125 assertions (up from the 74 on
  main before either PR) - this branch's evals plus PR #98's four,
  renumbered to avoid ID collisions and kept rather than pruned as
  redundant, since both authors' scenarios are independently useful.
- EVALUATIONS.md / README.md: single reconciled report replacing the
  two competing v1.4.0 reports that resulted from the parallel work
  (116/120 with, 65/120 without, +43pp pooled), a Total row recomputed
  across all 41 skills, and a corrected TOC anchor. One eval's
  without-skill condition could not be captured after five subagent
  attempts and is reported as unmeasured rather than guessed.

* fix(golang-how-to): correct broken TOC anchor links from PR #100

markdownlint's MD051 rule strips emoji symbols when computing heading
slugs but keeps their variation selectors (U+FE0F), since those are
Unicode "Mark" characters, not "Symbol". The TOC entries assumed full
removal and linked to plain hyphenated anchors, so every heading
followed by ️/⚙️ (both of which use a variation selector) produced a
link-fragment mismatch - failing lint repo-wide on every PR regardless
of what it touches, including this one.

Recomputed the correct URL-encoded anchors from markdownlint's own
slug algorithm and rewrote just the TOC hrefs; no visible text changed.
This commit is contained in:
Samuel Berthe
2026-09-02 14:35:30 +02:00
committed by GitHub
parent 0944f801aa
commit 8f8e2feb66
24 changed files with 446 additions and 302 deletions
+77 -134
View File
@@ -18,7 +18,6 @@
| `golang-naming` | v1.0.0 | 51 | 94% | **71%** | +24pp | 1.32× | **Low delta, high without** |
| `golang-swagger` | v1.0.0 | 60 | 97% | **72%** | +25pp | 1.35× | **Low delta, high without** |
| `golang-error-handling` | v1.0.0 | 60 | 98% | **72%** | +27pp | 1.36× | **Low delta, high without** |
| `golang-modernize` | v1.4.0 | 92 | 99% | **70%** | +29pp | 1.42× | **Low delta, high without** |
| `golang-popular-libraries` | v1.0.0 | 54 | 100% | **70%** | +30pp | 1.43× | **Low delta, high without** |
| `golang-security` | v1.0.0 | 110 | 100% | **68%** | +32pp | 1.47× | **Low delta, high without** |
| `golang-testing` | v1.0.0 | 65 | 92% | 60% | +32pp | 1.53× | **Low delta** |
@@ -36,6 +35,7 @@
| `golang-lint` | v1.0.0 | 51 | 96% | 55% | +41pp | 1.75× | |
| `golang-grpc` | v1.0.0 | 55 | 96% | 55% | +42pp | 1.75× | |
| `golang-cli` | v1.0.0 | 58 | 95% | 52% | +43pp | 1.83× | |
| `golang-modernize` | v1.5.0 | 120 | 97% | 54% | +43pp | 1.78× | |
| `golang-dependency-injection` | v1.0.0 | 55 | 98% | 51% | +47pp | 1.92× | |
| `golang-stretchr-testify` | v1.0.0 | 47 | 100% | 53% | +47pp | 1.89× | |
| `golang-samber-mo` | v1.0.0 | 108 | **88%** | 40% | +48pp | 2.20× | **Low with-skill score** |
@@ -55,7 +55,7 @@
| `golang-google-wire` | v1.0.0 | 50 | 98% | **82%** | +16pp | 1.20× | **Low delta, high without** |
| `golang-graphql` | v0.0.2 | 59 | 100% | **83%** | +17pp | 1.20× | **Low delta, high without** |
| `golang-samber-do` | v1.0.0 | 53 | 100% | 19% | +81pp | 5.26× | |
| **Total (41 skills)** | | **3411** | **98%** | **57%** | **+40pp** | **1.71×** | |
| **Total (41 skills)** | | **3439** | **97%** | **57%** | **+40pp** | **1.71×** | |
## `golang-naming` — v1.0.0
@@ -696,16 +696,18 @@
</details>
## `golang-modernize` — v1.0.0
## `golang-modernize` — v1.5.0
| | With Skill | Without Skill | Delta |
| ----------- | --------------- | --------------- | --------- |
| **Overall** | **72/76 (95%)** | **26/76 (34%)** | **+61pp** |
| | With Skill | Without Skill | Delta |
| ----------- | ----------------- | ----------------- | --------- |
| **Overall** | **116/120 (97%)** | **65/120 (54%)** | **+43pp** |
Evals 112 (72 assertions) are unchanged from the v1.0.0 run below. Evals 13 (extended from 2 to 4 assertions), 1416, 18, and 19 (30 assertions total) were freshly re-run for the Go 1.27 content added in this update. Eval 17's with-skill run scored **4/5**; its without-skill run could not be captured after five subagent attempts across two runs (agents consistently returned an idle status with no answer content for this specific prompt) and is excluded from the pooled totals above — pending a re-run in a future update. Evals 2023 (18 assertions) come from an independently authored, overlapping Go 1.27 eval pass (PR #98) merged into this update; they were run separately on a different model (Kimi K3) and are folded in here rather than duplicated as a second report. Evals 2022 test similar ground to evals 15/16/18 respectively (uuid, generic methods, CutLast) from different code angles — kept alongside rather than deduplicated, since both authors' scenarios are independently useful and neither is redundant enough to discard outright.
<details>
<summary>Full breakdown (74 assertions)</summary>
<summary>Full breakdown (125 assertions, 120 scored this run)</summary>
**Model:** Claude Sonnet 4.6 | **Runs:** 12 adversarial evals × 2 configs = 24 subagents | **Grading:** Human-as-judge
**Model:** Claude Sonnet 5 for evals 119 (evals 112 from the original Claude Sonnet 4.6 run, unchanged; evals 1319 freshly run this update); Kimi K3 for evals 2023 (from PR #98, folded in unchanged) | **Grading:** LLM-as-judge
| # | Assertion | With | Without |
| ---- | ---------------------------------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------- |
@@ -795,135 +797,76 @@
| 12.5 | Does NOT remove `ticker.Stop()` in PeriodicTask (needed for correctness) | <span class="g">✓</span> | <span class="g">✓</span> |
| 12.6 | Suggests `go fix ./...` for automated modernization | <span class="g">✓</span> | <span class="r">✗</span> |
| 12.7 | Correctly distinguishes Stop for GC vs Stop for correctness | <span class="g">✓</span> | <span class="g">✓</span> |
| | **13. go126-errors-astype-enhanced-new** — errors.AsType + enhanced new() (Go 1.26) | **<span class="g">4/4</span>** | **<span class="r">0/4</span>** |
| 13.1 | Uses `errors.AsType[*os.PathError](err)` instead of var+`errors.As` | <span class="g">✓</span> | <span class="r">✗</span> kept `var pathErr; errors.As(...)` |
| 13.2 | Replaces `ptr[T]` helper with `new()` accepting an initial value | <span class="g">✓</span> | <span class="r">✗</span> kept helper (exported as `Ptr`) |
| 13.3 | Applies the `errors.AsType` rewrite to all three branches, not just the first | <span class="g">✓</span> | <span class="r">✗</span> no branch rewritten |
| 13.4 | Correctly attributes both features to Go 1.26, not a newer/older release | <span class="g">✓</span> | <span class="r">✗</span> neither feature used or attributed |
| | **14. json-v2-duplicate-key-strictness** — Go 1.27 upgrade breaks duplicate-key decode | **<span class="g">6/6</span>** | **<span class="r">2/6</span>** |
| 14.1 | Identifies Go 1.27 made `encoding/json/v2` the default underneath `encoding/json` | <span class="g">✓</span> | <span class="r">✗</span> claims v1 behavior is guaranteed unaffected by Go 1's compatibility promise |
| 14.2 | Explains duplicate keys are rejected by default (not merely an opt-in experiment) | <span class="g">✓</span> | <span class="r">✗</span> frames rejection as conditional on an opt-in `GOEXPERIMENT=jsonv2` |
| 14.3 | Connects the partner payload's duplicate `id` key to the reported failures | <span class="g">✓</span> | <span class="g">✓</span> reasoned to the same hypothesis, framed as unconfirmed |
| 14.4 | Proposes a concrete fix (dedupe before decode, or an explicit v1-compatible opt-in) | <span class="g">✓</span> | <span class="g">✓</span> also proposes `json.RejectDuplicateNames(false)` |
| 14.5 | Names `GOEXPERIMENT=nojsonv2` as the escape hatch, not the long-term fix | <span class="g">✓</span> | <span class="r">✗</span> never names `nojsonv2` |
| 14.6 | Does NOT claim `encoding/json`'s decode behavior is unaffected by the Go 1.27 bump | <span class="g">✓</span> | <span class="r">✗</span> explicitly claims it can't change under Go 1's compatibility promise |
| | **15. generic-method-scoped-transform** — Set[T] → Set[U] via a Go 1.27 generic method | **<span class="r">1/5</span>** | **<span class="r">1/5</span>** |
| 15.1 | Declares the transform as a method on `Set[T]`, not a package-level generic function | <span class="r">✗</span> refused the rule, wrote a free function | <span class="r">✗</span> free function |
| 15.2 | The new type parameter is declared on the method, not the `Set[T]` type | <span class="r">✗</span> no method written | <span class="r">✗</span> no method written |
| 15.3 | Notes generic methods (own type parameters) are a Go 1.27 addition | <span class="r">✗</span> explicitly asserts this is false | <span class="r">✗</span> not mentioned |
| 15.4 | Notes interface methods still can't declare type parameters / can't be satisfied | <span class="r">✗</span> | <span class="r">✗</span> |
| 15.5 | Example call site compiles conceptually | <span class="g">✓</span> | <span class="g">✓</span> |
| | **16. cutlast-final-separator-split** — split on the last colon only | **<span class="g">5/5</span>** | **<span class="r">1/5</span>** |
| 16.1 | Uses `strings.CutLast(line, ":")` | <span class="g">✓</span> | <span class="r">✗</span> uses `LastIndex` + manual slicing |
| 16.2 | Returns `CutLast`'s `found` boolean directly as `ok` | <span class="g">✓</span> | <span class="r">✗</span> derives `ok` from `idx == -1` |
| 16.3 | Does NOT use `LastIndex` with manual slice arithmetic | <span class="g">✓</span> | <span class="r">✗</span> uses `line[:idx], line[idx+1:]` |
| 16.4 | Does NOT use `Split`/`SplitN` | <span class="g">✓</span> | <span class="g">✓</span> |
| 16.5 | Notes `CutLast` requires Go 1.27+ | <span class="g">✓</span> | <span class="r">✗</span> no version note |
| | **17. godebug-removed-key-build-break** — bump toolchain with a stale `godebug` pin | **<span class="g">4/5</span>** | **not measured** ⁽¹⁾ |
| 17.1 | Removes/updates the `asynctimerchan=0` entry rather than leaving it unchanged | <span class="g">✓</span> | — |
| 17.2 | Explains Go 1.27 removed the key and a stale pin now fails the build (not a warning) | <span class="g">✓</span> | — |
| 17.3 | Bumps the `go` directive to a current Go 1.27-era version | <span class="g">✓</span> | — |
| 17.4 | Recommends fixing the underlying timer-dependent code, not silently deleting the shim | <span class="g">✓</span> | — |
| 17.5 | Mentions checking for other Go 1.27-removed GODEBUG keys in the same upgrade | <span class="r">✗</span> | — |
| | **18. uuid-stdlib-vs-dependency** — generate order IDs with no UUID dep in go.mod | **<span class="g">5/5</span>** | **<span class="r">3/5</span>** |
| 18.1 | Uses the stdlib `uuid` package rather than `google/uuid`/`gofrs/uuid` | <span class="g">✓</span> | <span class="r">✗</span> hand-rolled UUIDv4 via `crypto/rand` |
| 18.2 | Does NOT add a new external module dependency | <span class="g">✓</span> | <span class="g">✓</span> avoided a dependency, but by hand-rolling instead of using stdlib |
| 18.3 | Notes the stdlib `uuid` package is new in Go 1.27 | <span class="g">✓</span> | <span class="r">✗</span> unaware the package exists |
| 18.4 | Stores the ID via `UUID.String()` or an equivalent formatted string | <span class="g">✓</span> | <span class="g">✓</span> `fmt.Sprintf` RFC 4122 formatting |
| 18.5 | Does NOT claim the stdlib `uuid` package existed before Go 1.27 | <span class="g">✓</span> | <span class="g">✓</span> (never mentions it) |
| | **19. json-v2-streaming-migration** — add a reader-based JSON loader | **<span class="g">5/5</span>** | **<span class="r">3/5</span>** |
| 19.1 | Uses `encoding/json/v2`'s `UnmarshalRead(r, v)` rather than only `NewDecoder(r).Decode(v)` | <span class="g">✓</span> | <span class="r">✗</span> uses v1-era `json.NewDecoder(r).Decode(v)` |
| 19.2 | Notes `encoding/json/v2` is the new default as of Go 1.27 | <span class="g">✓</span> | <span class="r">✗</span> no version note |
| 19.3 | Does NOT introduce a manual `io.ReadAll` + `Unmarshal` buffering step | <span class="g">✓</span> | <span class="g">✓</span> |
| 19.4 | Keeps `LoadFromBytes` working as-is | <span class="g">✓</span> | <span class="g">✓</span> |
| 19.5 | Signature matches `LoadFromReader(r io.Reader, v any) error` | <span class="g">✓</span> | <span class="g">✓</span> |
| | **20. go127-stdlib-uuid** — stdlib uuid replaces github.com/google/uuid (from PR #98) | **<span class="g">5/5</span>** | **<span class="r">1/5</span>** |
| 20.1 | Replaces `github.com/google/uuid` with stdlib `uuid` | <span class="g">✓</span> | <span class="r">✗</span> claims no stdlib uuid; hand-rolls |
| 20.2 | Uses `uuid.New().String()` — no `uuid.NewString()` | <span class="g">✓</span> | <span class="r">✗</span> no stdlib uuid usage at all |
| 20.3 | Knows generators return values without errors | <span class="g">✓</span> | <span class="r">✗</span> keeps `(string, error)` error branch |
| 20.4 | Uses `uuid.NewV7()` for time-ordered DB ID | <span class="g">✓</span> | <span class="r">✗</span> hand-rolled v7 bit layout |
| 20.5 | Removes `github.com/google/uuid` from go.mod | <span class="g">✓</span> | <span class="g">✓</span> |
| | **21. go127-generic-methods** — generic methods with own type parameters, interface-satisfaction test (from PR #98) | **<span class="g">4/4</span>** | **<span class="r">1/4</span>** |
| 21.1 | Converts `MapSet` to generic method (own type param) | <span class="g">✓</span> | <span class="r">✗</span> `Map` kept package-level |
| 21.2 | Converts `FilterSet` to a method on `*Set[T]` | <span class="g">✓</span> | <span class="g">✓</span> |
| 21.3 | Does NOT claim generic methods are impossible | <span class="g">✓</span> | <span class="r">✗</span> claims methods can't declare type params |
| 21.4 | Keeps `ForEach` non-generic to satisfy the interface | <span class="g">✓</span> | <span class="r">✗</span> drops `ForEach`/`Iteratable` entirely |
| | **22. go127-cutlast** — strings.CutLast for last-separator splits, 3 call sites (from PR #98) | **<span class="g">4/4</span>** | **<span class="r">1/4</span>** |
| 22.1 | Uses `strings.CutLast` for at least one function | <span class="g">✓</span> | <span class="r">✗</span> claims "no `strings.CutLast` exists" |
| 22.2 | Uses the 3-result form (before, after, found) correctly | <span class="g">✓</span> | <span class="r">✗</span> no `CutLast` usage at all |
| 22.3 | Uses `CutLast` in `EnvValue` to simplify the ok-returning split | <span class="g">✓</span> | <span class="r">✗</span> uses `strings.Cut`, changes semantics |
| 22.4 | Does NOT keep manual `LastIndex` slicing where `CutLast` applies | <span class="g">✓</span> | <span class="g">✓</span> |
| | **23. go127-synctest-http** — httptest.NewTestServer inside synctest bubbles (from PR #98) | **<span class="g">5/5</span>** | **<span class="r">1/5</span>** |
| 23.1 | Wraps tests in `synctest.Test` | <span class="g">✓</span> | <span class="g">✓</span> |
| 23.2 | Uses `httptest.NewTestServer` instead of `httptest.NewServer` | <span class="g">✓</span> | <span class="r">✗</span> uses `httptest.NewServer` in both tests |
| 23.3 | Explains real-network servers don't work in a synctest bubble | <span class="g">✓</span> | <span class="r">✗</span> falsely claims `NewServer` bubble-aware |
| 23.4 | Does NOT leave a plain `httptest.NewServer` inside a bubble | <span class="g">✓</span> | <span class="r">✗</span> `NewServer` left inside bubble |
| 23.5 | Keeps both tests working with deterministic time | <span class="g">✓</span> | <span class="r">✗</span> real-socket server, non-deterministic |
⁽¹⁾ Eval 17's without-skill condition returned no answer content after five subagent attempts (across two separate agent identities); this is a tooling/relay issue observed only on this specific prompt, not a fabricated or assumed score. Re-run before the next report update.
</details>
## `golang-modernize` — v1.4.0
| | With Skill | Without Skill | Delta |
| ----------- | --------------- | --------------- | --------- |
| **Overall** | **91/92 (99%)** | **64/92 (70%)** | **+29pp** |
<details>
<summary>Full breakdown (92 assertions)</summary>
**Model:** Kimi K3 (fireworks-ai/accounts/fireworks/models/kimi-k3) | **Runs:** 17 adversarial evals × 2 configs = 34 subagents | **Grading:** LLM-as-judge
Note: re-run adds 4 Go 1.27 evals (1417) and eval 13 (previously added to evals.json but never executed). This model already knows much of the Go 1.211.26 content (evals 3, 5, 6, 9, 13 pass fully without the skill); the remaining uplift concentrates in the Go 1.27 evals (with 18/18, without 4/18).
| # | Assertion | With | Without |
| ---- | -------------------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------- |
| | **1. version-constraint-1.21** — Go 1.21 project with 1.22+ patterns | **<span class="g">7/7</span>** | **<span class="r">6/7</span>** |
| 1.1 | Suggests min/max builtins (Go 1.21) | <span class="g">✓</span> | <span class="g">✓</span> |
| 1.2 | Suggests slices.Sort or slices.Contains (Go 1.21) | <span class="g">✓</span> | <span class="g">✓</span> |
| 1.3 | Suggests sync.OnceValue (Go 1.21) | <span class="g">✓</span> | <span class="g">✓</span> |
| 1.4 | Does NOT suggest range-over-int (requires 1.22+) | <span class="g">✓</span> | <span class="g">✓</span> |
| 1.5 | Does NOT suggest removing loop var shadow copy (requires 1.22+) | <span class="g">✓</span> | <span class="r">✗</span> drops `v := v` via param passing |
| 1.6 | Does NOT suggest math/rand/v2 (requires 1.22+) | <span class="g">✓</span> | <span class="g">✓</span> |
| 1.7 | Does NOT suggest cmp.Or (requires 1.22+) | <span class="g">✓</span> | <span class="g">✓</span> |
| | **2. rand-v2-api-renames** — math/rand → math/rand/v2 function renames | **<span class="g">6/6</span>** | **<span class="r">5/6</span>** |
| 2.1 | Renames `Intn` to `IntN` (capital N) | <span class="g">✓</span> | <span class="g">✓</span> |
| 2.2 | Renames `Int63n` to `Int64N` | <span class="g">✓</span> | <span class="g">✓</span> |
| 2.3 | Removes all `rand.Seed` calls | <span class="g">✓</span> | <span class="g">✓</span> |
| 2.4 | Replaces `rand.Read` with `crypto/rand` usage | <span class="g">✓</span> | <span class="r">✗</span> PRNG `Uint64` loop, not `crypto/rand` |
| 2.5 | Import changes to `math/rand/v2` | <span class="g">✓</span> | <span class="g">✓</span> |
| 2.6 | No old-style function names in output | <span class="g">✓</span> | <span class="g">✓</span> |
| | **3. safety-over-cosmetic** — path traversal vs interface{} → any | **<span class="g">6/6</span>** | **<span class="g">6/6</span>** |
| 3.1 | Suggests `os.Root`/`os.OpenRoot` for user-supplied paths | <span class="g">✓</span> | <span class="g">✓</span> |
| 3.2 | Mentions path traversal risk or CWE-22 | <span class="g">✓</span> | <span class="g">✓</span> |
| 3.3 | Prioritizes safety fix over cosmetic changes | <span class="g">✓</span> | <span class="g">✓</span> |
| 3.4 | Also suggests `interface{}``any` | <span class="g">✓</span> | <span class="g">✓</span> |
| 3.5 | Also suggests `min` builtin or `net.JoinHostPort` | <span class="g">✓</span> | <span class="g">✓</span> |
| 3.6 | Does NOT only address cosmetic issues | <span class="g">✓</span> | <span class="g">✓</span> |
| | **4. omitzero-vs-omitempty** — time.Time and bool JSON tags | **<span class="g">6/6</span>** | **<span class="r">5/6</span>** |
| 4.1 | Identifies `omitempty` doesn't omit zero `time.Time` | <span class="g">✓</span> | <span class="g">✓</span> |
| 4.2 | Suggests `omitzero` for all `time.Time` fields | <span class="g">✓</span> | <span class="r">✗</span> `AccountCreated` missing `omitzero` |
| 4.3 | Identifies `omitempty` treats `false` as empty for bool | <span class="g">✓</span> | <span class="g">✓</span> |
| 4.4 | Addresses bool issue correctly (removes tag or uses omitzero) | <span class="g">✓</span> | <span class="g">✓</span> |
| 4.5 | Correctly notes `omitzero` requires Go 1.24+ | <span class="g">✓</span> | <span class="g">✓</span> |
| 4.6 | Does NOT suggest `omitzero` for string/int fields | <span class="g">✓</span> | <span class="g">✓</span> |
| | **5. benchmark-b-loop** — b.Loop() replaces b.N pattern | **<span class="g">5/5</span>** | **<span class="g">5/5</span>** |
| 5.1 | Replaces `for i := 0; i < b.N; i++` with `for b.Loop()` | <span class="g">✓</span> | <span class="g">✓</span> |
| 5.2 | Replaces `for n := 0; n < b.N; n++` variant too | <span class="g">✓</span> | <span class="g">✓</span> |
| 5.3 | Replaces b.N loop in all benchmarks | <span class="g">✓</span> | <span class="g">✓</span> |
| 5.4 | No `b.N` iteration pattern remains | <span class="g">✓</span> | <span class="g">✓</span> |
| 5.5 | Preserves benchmark function names and logic | <span class="g">✓</span> | <span class="g">✓</span> |
| | **6. automaxprocs-removal** — Go 1.25 built-in container GOMAXPROCS | **<span class="g">5/5</span>** | **<span class="g">5/5</span>** |
| 6.1 | Suggests removing `go.uber.org/automaxprocs` import | <span class="g">✓</span> | <span class="g">✓</span> |
| 6.2 | Explains Go 1.25 has built-in container-aware GOMAXPROCS | <span class="g">✓</span> | <span class="g">✓</span> |
| 6.3 | Suggests `sync.WaitGroup.Go` | <span class="g">✓</span> | <span class="g">✓</span> |
| 6.4 | Does NOT suggest keeping automaxprocs | <span class="g">✓</span> | <span class="g">✓</span> |
| 6.5 | Suggests removing from go.mod | <span class="g">✓</span> | <span class="g">✓</span> |
| | **7. cmp-or-chained-defaults** — cmp.Or for default value chains (Go 1.22) | **<span class="g">6/6</span>** | **<span class="r">4/6</span>** |
| 7.1 | Uses `cmp.Or` for at least one default value chain | <span class="g">✓</span> | <span class="g">✓</span> |
| 7.2 | Collapses 3-step host default to single `cmp.Or` call | <span class="g">✓</span> | <span class="g">✓</span> |
| 7.3 | Import includes `cmp` package | <span class="g">✓</span> | <span class="g">✓</span> |
| 7.4 | All multi-step defaults converted to `cmp.Or` | <span class="g">✓</span> | <span class="g">✓</span> |
| 7.5 | Result is functionally equivalent (same fallback order) | <span class="g">✓</span> | <span class="r">✗</span> falsely claims `cmp.Or` is lazy |
| 7.6 | Does NOT introduce a custom helper function | <span class="g">✓</span> | <span class="r">✗</span> defines `envOr` helper |
| | **8. addcleanup-vs-setfinalizer** — runtime.AddCleanup (Go 1.24) | **<span class="g">5/5</span>** | **<span class="r">4/5</span>** |
| 8.1 | Replaces `runtime.SetFinalizer` with `runtime.AddCleanup` | <span class="g">✓</span> | <span class="g">✓</span> |
| 8.2 | Cleanup function receives resource as separate argument | <span class="g">✓</span> | <span class="g">✓</span> |
| 8.3 | Explains SetFinalizer cycle restriction preventing GC | <span class="g">✓</span> | <span class="r">✗</span> cycle restriction not explained |
| 8.4 | Does NOT pass wrapper struct as the resource | <span class="g">✓</span> | <span class="g">✓</span> |
| 8.5 | Attributes `runtime.AddCleanup` to Go 1.24 | <span class="g">✓</span> | <span class="g">✓</span> |
| | **9. http-mux-migration** — gorilla/mux → stdlib ServeMux (Go 1.22) | **<span class="g">6/6</span>** | **<span class="g">6/6</span>** |
| 9.1 | Uses `http.NewServeMux()` instead of `mux.NewRouter()` | <span class="g">✓</span> | <span class="g">✓</span> |
| 9.2 | Uses method prefix in patterns like `GET /api/users/{id}` | <span class="g">✓</span> | <span class="g">✓</span> |
| 9.3 | Uses `r.PathValue("id")` instead of `mux.Vars(r)` | <span class="g">✓</span> | <span class="g">✓</span> |
| 9.4 | All 6 routes migrated with correct method prefixes | <span class="g">✓</span> | <span class="g">✓</span> |
| 9.5 | No gorilla/mux import remains | <span class="g">✓</span> | <span class="g">✓</span> |
| 9.6 | Return type changes to `*http.ServeMux` | <span class="g">✓</span> | <span class="g">✓</span> |
| | **10. synctest-flaky-fix** — testing/synctest for flaky concurrent tests | **<span class="g">6/6</span>** | **<span class="r">2/6</span>** |
| 10.1 | Uses `synctest.Test` (not old `synctest.Run`) | <span class="g">✓</span> | <span class="r">✗</span> synctest only in optional side example |
| 10.2 | Uses `synctest.Wait()` for goroutine synchronization | <span class="g">✓</span> | <span class="r">✗</span> `Wait()` only in optional example |
| 10.3 | Removes all `time.Sleep` calls | <span class="g">✓</span> | <span class="g">✓</span> |
| 10.4 | No flaky timing dependencies remain | <span class="g">✓</span> | <span class="g">✓</span> |
| 10.5 | Correctly imports `testing/synctest` | <span class="g">✓</span> | <span class="r">✗</span> no synctest import in main fix |
| 10.6 | Both tests converted | <span class="g">✓</span> | <span class="r">✗</span> tests keep channel-timeout approach |
| | **11. waitgroup-go-loopvar** — WaitGroup.Go, loop vars, t.Context (1.221.25) | **<span class="g">7/7</span>** | **<span class="r">6/7</span>** |
| 11.1 | Replaces Add/go func/Done pattern with `wg.Go` | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.2 | Removes `wg.Add(1)` calls | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.3 | Removes `defer wg.Done()` calls | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.4 | Removes `item := item` loop variable shadow copies | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.5 | Explains Go 1.22+ loop variable semantics | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.6 | Replaces `context.Background()` with `t.Context()` in test | <span class="g">✓</span> | <span class="g">✓</span> |
| 11.7 | Preserves `WaitGroup.Wait()` call | <span class="g">✓</span> | <span class="r">✗</span> `ProcessAll` switched to errgroup |
| | **12. timer-gc-greenteagc** — Timer GC change + Green Tea GC (Go 1.26) | **<span class="r">6/7</span>** | **<span class="r">4/7</span>** |
| 12.1 | Identifies some `defer timer.Stop()` calls as unnecessary | <span class="g">✓</span> | <span class="g">✓</span> |
| 12.2 | Explains Go 1.23+ timer/ticker GC behavior change | <span class="g">✓</span> | <span class="g">✓</span> |
| 12.3 | Suggests reviewing GC tuning due to Green Tea GC | <span class="g">✓</span> | <span class="g">✓</span> |
| 12.4 | Mentions Green Tea GC 10-40% overhead reduction | <span class="r">✗</span> no percentage figure | <span class="g">✓</span> |
| 12.5 | Does NOT remove `ticker.Stop()` in PeriodicTask (needed for correctness) | <span class="g">✓</span> | <span class="r">✗</span> drops ticker `Stop()` in rewrite |
| 12.6 | Suggests `go fix ./...` for automated modernization | <span class="g">✓</span> | <span class="r">✗</span> no `go fix` mention |
| 12.7 | Correctly distinguishes Stop for GC vs Stop for correctness | <span class="g">✓</span> | <span class="r">✗</span> misclassifies ticker Stop |
| | **13. go126-errors-astype-enhanced-new** — errors.AsType + new(expr) (Go 1.26) | **<span class="g">2/2</span>** | **<span class="g">2/2</span>** |
| 13.1 | Uses `errors.AsType[*os.PathError](err)` instead of var+errors.As | <span class="g">✓</span> | <span class="g">✓</span> |
| 13.2 | Replaces `ptr[T]` helper with `new()` accepting an initial value | <span class="g">✓</span> | <span class="g">✓</span> |
| | **14. go127-stdlib-uuid** — stdlib uuid replaces github.com/google/uuid | **<span class="g">5/5</span>** | **<span class="r">1/5</span>** |
| 14.1 | Replaces `github.com/google/uuid` with stdlib `uuid` | <span class="g">✓</span> | <span class="r">✗</span> claims no stdlib uuid; hand-rolls |
| 14.2 | Uses `uuid.New().String()` — no `uuid.NewString()` | <span class="g">✓</span> | <span class="r">✗</span> no stdlib uuid usage at all |
| 14.3 | Knows generators return values without errors | <span class="g">✓</span> | <span class="r">✗</span> keeps `(string, error)` error branch |
| 14.4 | Uses `uuid.NewV7()` for time-ordered DB ID | <span class="g">✓</span> | <span class="r">✗</span> hand-rolled v7 bit layout |
| 14.5 | Removes `github.com/google/uuid` from go.mod | <span class="g">✓</span> | <span class="g">✓</span> |
| | **15. go127-generic-methods** — generic methods with own type parameters | **<span class="g">4/4</span>** | **<span class="r">1/4</span>** |
| 15.1 | Converts `MapSet` to generic method (own type param) | <span class="g">✓</span> | <span class="r">✗</span> `Map` kept package-level |
| 15.2 | Converts `FilterSet` to a method on `*Set[T]` | <span class="g">✓</span> | <span class="g">✓</span> |
| 15.3 | Does NOT claim generic methods are impossible | <span class="g">✓</span> | <span class="r">✗</span> claims methods can't declare type params |
| 15.4 | Keeps `ForEach` non-generic to satisfy the interface | <span class="g">✓</span> | <span class="r">✗</span> drops `ForEach`/`Iteratable` entirely |
| | **16. go127-cutlast** — strings.CutLast for last-separator splits | **<span class="g">4/4</span>** | **<span class="r">1/4</span>** |
| 16.1 | Uses `strings.CutLast` for at least one function | <span class="g">✓</span> | <span class="r">✗</span> claims "no `strings.CutLast` exists" |
| 16.2 | Uses the 3-result form (before, after, found) correctly | <span class="g">✓</span> | <span class="r">✗</span> no `CutLast` usage at all |
| 16.3 | Uses `CutLast` in `EnvValue` to simplify the ok-returning split | <span class="g">✓</span> | <span class="r">✗</span> uses `strings.Cut`, changes semantics |
| 16.4 | Does NOT keep manual `LastIndex` slicing where `CutLast` applies | <span class="g">✓</span> | <span class="g">✓</span> |
| | **17. go127-synctest-http** — httptest.NewTestServer inside synctest bubbles | **<span class="g">5/5</span>** | **<span class="r">1/5</span>** |
| 17.1 | Wraps tests in `synctest.Test` | <span class="g">✓</span> | <span class="g">✓</span> |
| 17.2 | Uses `httptest.NewTestServer` instead of `httptest.NewServer` | <span class="g">✓</span> | <span class="r">✗</span> uses `httptest.NewServer` in both tests |
| 17.3 | Explains real-network servers don't work in a synctest bubble | <span class="g">✓</span> | <span class="r">✗</span> falsely claims `NewServer` bubble-aware |
| 17.4 | Does NOT leave a plain `httptest.NewServer` inside a bubble | <span class="g">✓</span> | <span class="r">✗</span> `NewServer` left inside bubble |
| 17.5 | Keeps both tests working with deterministic time | <span class="g">✓</span> | <span class="r">✗</span> real-socket server, non-deterministic |
</details>
**Finding — eval 15 (generic methods) shows zero uplift and needs a skill fix, not a re-run.** Both conditions scored 1/5, and the with-skill agent explicitly rejected the rule as false ("Go methods cannot declare their own type parameters ... not something Go 1.27 relaxed") before reverting to a pre-1.27 answer. The rule itself is correct — verified directly against the Go 1.27 release notes (`/issue/77273`, `/ref/spec#Method_declarations`) — but a model whose training predates Go 1.27 treats a surprising claim inside an "internal skill guide excerpt" as untrustworthy and overrides it with its prior. The skill's `versions.md` now cites the Go issue number and spec section inline (fixed in this update) rather than just asserting the rule, so the model has a concrete reference to check itself against rather than a bare claim to accept or doubt. Eval 21 (from PR #98, a different model, a different code scenario) independently reproduced the same failure mode — corroborating evidence, not a fluke of one eval design.
## `golang-security` — v1.0.0
+13 -13
View File
@@ -194,30 +194,30 @@ These skills are designed as **atomic, cross-referencing units**. A skill may re
| ⭐️ | ✅ `golang-data-structures` | ⚡ | -39% | 93 | 2,608 | 6,327 |
| ⭐️ | ✅ `golang-database` | ⚡ ⚙️ | -38% | 97 | 2,721 | 7,243 |
| ⭐️ | ✅ `golang-design-patterns` | ⚡ ⚙️ | -37% | 80 | 2,694 | 9,400 |
| ⭐️ | ✅ `golang-documentation` | ⚡ 🤖 ⚙️ | -53% | 75 | 3,094 | 11,193 |
| ⭐️ | ✅ `golang-documentation` | ⚡ 🤖 ⚙️ | -53% | 75 | 3,168 | 11,259 |
| ⭐️ | ✅ `golang-error-handling` | ⚡ 🤖 ⚙️ | -26% | 141 | 1,734 | 4,693 |
| ⭐️ | ✅ `golang-how-to` | ⚡ | — | 183 | 4,196 | 15,197 |
| ⭐️ | ✅ `golang-modernize` | ⚡ 🤖 | -29% | 68 | 3,194 | 10,890 |
| ⭐️ | ✅ `golang-modernize` | ⚡ 🤖 | -43% | 104 | 3,486 | 15,145 |
| ⭐️ | ✅ `golang-naming` | ⚡ ⚙️ | -23% | 159 | 3,031 | 7,399 |
| ⭐️ | ✅ `golang-refactoring` | ⚡ 🧠 🤖 ⚙️ | — | 245 | 3,835 | 20,330 |
| ⭐️ | ✅ `golang-refactoring` | ⚡ 🧠 🤖 ⚙️ | — | 245 | 3,835 | 20,392 |
| ⭐️ | ✅ `golang-safety` | ⚡ | -58% | 78 | 2,614 | 5,384 |
| ⭐️ | ✅ `golang-testing` | ⚡ 🧠 🤖 ⚙️ | -32% | 115 | 4,270 | 7,377 |
| ⭐️ | ✅ `golang-troubleshooting` | ⚡ 🧠 🤖 | -32% | 127 | 2,929 | 16,612 |
| ⭐️ | ✅ `golang-testing` | ⚡ 🧠 🤖 ⚙️ | -32% | 115 | 4,464 | 7,571 |
| ⭐️ | ✅ `golang-troubleshooting` | ⚡ 🧠 🤖 | -32% | 127 | 2,929 | 16,665 |
| ⭐️ | ✅ `golang-security` | ⚡ 🧠 🤖 | -32% | 85 | 3,187 | 21,623 |
| | ✅ `golang-benchmark` | ⚡ 🧠 | -50% | 102 | 3,061 | 30,243 |
| | ✅ `golang-benchmark` | ⚡ 🧠 | -50% | 102 | 3,150 | 30,332 |
| | ✅ `golang-cli` | ⚡ | -43% | 125 | 2,338 | 6,153 |
| | ✅ `golang-concurrency` | ⚡ 🤖 ⚙️ | -39% | 72 | 2,199 | 6,829 |
| | ✅ `golang-concurrency` | ⚡ 🤖 ⚙️ | -39% | 72 | 2,196 | 6,826 |
| | ✅ `golang-context` | ⚡ ⚙️ | -34% | 82 | 1,211 | 4,021 |
| | ✅ `golang-continuous-integration` | ⚡ | -59% | 82 | 3,336 | 12,598 |
| | ✅ `golang-continuous-integration` | ⚡ | -59% | 82 | 3,444 | 12,706 |
| | ✅ `golang-dependency-injection` | ⚡ 🤖 ⚙️ | -47% | 178 | 3,016 | 5,287 |
| | ✅ `golang-dependency-management` | ⚡ | -54% | 77 | 2,362 | 5,500 |
| | ✅ `golang-dependency-management` | ⚡ | -54% | 77 | 2,407 | 5,545 |
| | ✅ `golang-structs-interfaces` | ⚡ ⚙️ | -35% | 111 | 3,076 | 3,076 |
| | ✅ `golang-lint` | ⚡ 🤖 | -41% | 98 | 1,877 | 6,295 |
| | ✅ `golang-observability` | ⚡ 🤖 ⚙️ | -37% | 163 | 3,118 | 18,605 |
| | ✅ `golang-performance` | ⚡ 🧠 🤖 | -39% | 129 | 2,226 | 18,226 |
| | ✅ `golang-observability` | ⚡ 🤖 ⚙️ | -37% | 163 | 3,118 | 18,722 |
| | ✅ `golang-performance` | ⚡ 🧠 🤖 | -39% | 129 | 2,226 | 18,390 |
| | ✅ `golang-gopls` | ⚡ | — | 219 | 2,317 | 12,299 |
| | ✅ `golang-pkg-go-dev` | ⚡ | — | 230 | 3,423 | 5,237 |
| | ✅ `golang-popular-libraries` | ⚡ | -30% | 51 | 1,074 | 4,543 |
| | ✅ `golang-popular-libraries` | ⚡ | -30% | 51 | 1,074 | 4,702 |
| | ✅ `golang-project-layout` | ⚡ | -38% | 69 | 1,692 | 5,907 |
| | ✅ `golang-stay-updated` | ⚡ | -56% | 44 | 1,802 | 1,802 |
@@ -247,7 +247,7 @@ These skills are designed as **atomic, cross-referencing units**. A skill may re
| | With Skill | Without Skill | Delta |
| ----------- | ------------------- | ------------------- | --------- |
| **Overall** | **3334/3411 (98%)** | **1953/3411 (57%)** | **+40pp** |
| **Overall** | **3348/3439 (97%)** | **1957/3439 (57%)** | **+40pp** |
See [EVALUATIONS.md](./EVALUATIONS.md) for the full per-skill breakdown.
+2
View File
@@ -60,6 +60,8 @@ func BenchmarkParse(b *testing.B) {
Legacy `b.N` loops still compile and are fine to keep when preserving existing benchmarks or supporting Go <1.24. They are easier to get wrong: setup may need `b.ResetTimer()`, and results may need a sink if the compiler can eliminate the work. Go 1.26 fixed an earlier `b.Loop()` inlining limitation — benchmarks on 1.241.25 already benefit from `b.Loop()` but may miss inlining optimizations that 1.26 delivers.
Go 1.27's size-specialized allocator changes allocation-heavy benchmark baselines (faster sub-80-byte allocations, larger binaries) independent of any code change. Treat a `benchstat` comparison that straddles the Go 1.26→1.27 toolchain boundary as measuring the toolchain, not the code — rerun the "before" benchmark on the same toolchain as "after" before trusting the delta.
### Memory tracking
```go
+2 -4
View File
@@ -132,11 +132,9 @@ When auditing concurrency across a large codebase, use up to 5 parallel sub-agen
- → See `samber/cc-skills-golang@golang-design-patterns` skill for graceful shutdown patterns
- → See `samber/cc-skills-golang@golang-continuous-integration` skill for automated AI-driven code review in CI using these guidelines
### Go 1.26 experimental goroutine leak profile
### Goroutine leak profile
For Go 1.26 diagnostics, there is an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by `GOEXPERIMENT=goroutineleakprofile`; do not rely on it as default stable behavior.
Typical usage when the experiment is enabled:
The goroutine leak profile (experimental behind `GOEXPERIMENT=goroutineleakprofile` in Go 1.26) is generally available in `runtime/pprof` since Go 1.27 — no build flag required. It is a useful production-oriented leak signal alongside the existing tools below.
```bash
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
@@ -72,14 +72,17 @@ The versions in the examples below are reference versions that may be outdated.
Adapt the Go version matrix to match `go.mod`:
```
go 1.23 → matrix: ["1.23", "1.24", "1.25", "1.26", "stable"]
go 1.24 → matrix: ["1.24", "1.25", "1.26", "stable"]
go 1.25 → matrix: ["1.25", "1.26", "stable"]
go 1.26 → matrix: ["1.26", "stable"]
go 1.23 → matrix: ["1.23", "1.24", "1.25", "1.26", "1.27", "stable"]
go 1.24 → matrix: ["1.24", "1.25", "1.26", "1.27", "stable"]
go 1.25 → matrix: ["1.25", "1.26", "1.27", "stable"]
go 1.26 → matrix: ["1.26", "1.27", "stable"]
go 1.27 → matrix: ["1.27", "stable"]
```
Use `fail-fast: false` so a failure on one Go version doesn't cancel the others.
Go 1.27 raises the Darwin floor to macOS 13 (Ventura). `macos-latest`/`macos-14`+ runners are unaffected; only pin an older `macos-12` runner if a project still needs it, and note it can no longer build with a Go 1.27 toolchain.
Test flags:
- `-race`: CI MUST run tests with the `-race` flag (catches data races — undefined behavior in Go)
@@ -16,8 +16,8 @@ jobs:
fail-fast: false
matrix:
go:
- "1.25"
- "1.26"
- "1.27"
- "stable"
steps:
+7 -5
View File
@@ -130,12 +130,12 @@ go get -u tool
go mod tidy
```
`go.mod` shape for a module targeting Go 1.26 or newer. This is an example target, not a cap; keep the project's actual `go` directive and do not change it just to add tools.
`go.mod` shape for a module targeting Go 1.27 or newer. This is an example target, not a cap; keep the project's actual `go` directive and do not change it just to add tools.
```go.mod
module example.com/project
go 1.26
go 1.27
tool (
github.com/golangci/golangci-lint/v2/cmd/golangci-lint
@@ -144,6 +144,8 @@ tool (
)
```
For `go 1.27` or newer, `go mod tidy` auto-merges duplicate `require` blocks and enforces a two-block layout (direct dependencies, then indirect), preserving existing comments — no manual cleanup needed after a merge that introduces a second `require` block.
For Go <1.24 only, use the legacy `tools.go` blank-import workaround:
```go
@@ -159,12 +161,12 @@ import (
Rule: Go 1.24+ = `tool` directives. Go <1.24 = `tools.go` fallback.
### Go 1.26+ module target note
### Module target note
When using a Go 1.26 or newer toolchain, `go mod init` may create a module with an older default `go` directive. If the project intentionally targets Go 1.26+ APIs, update the directive deliberately:
When using a newer toolchain, `go mod init` may create a module with an older default `go` directive. If the project intentionally targets the newer toolchain's APIs, update the directive deliberately:
```bash
go mod edit -go=1.26
go mod edit -go=1.27
go mod tidy
```
@@ -114,7 +114,7 @@ Use multi-stage builds with a minimal final image:
```dockerfile
# Build stage
FROM golang:1.26-alpine AS builder
FROM golang:1.27-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
+19 -19
View File
@@ -7,28 +7,28 @@
## Table of Contents
- [Code Quality](#code-quality)
- [`samber/cc-skills-golang@golang-code-style` ⭐️ ⚙️](#sambercc-skills-golanggolang-code-style--)
- [`samber/cc-skills-golang@golang-documentation` ⭐️ ⚙️](#sambercc-skills-golanggolang-documentation--)
- [`samber/cc-skills-golang@golang-error-handling` ⭐️ ⚙️](#sambercc-skills-golanggolang-error-handling--)
- [`samber/cc-skills-golang@golang-code-style` ⭐️ ⚙️](#sambercc-skills-golanggolang-code-style-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-documentation` ⭐️ ⚙️](#sambercc-skills-golanggolang-documentation-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-error-handling` ⭐️ ⚙️](#sambercc-skills-golanggolang-error-handling-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-lint`](#sambercc-skills-golanggolang-lint)
- [`samber/cc-skills-golang@golang-naming` ⭐️ ⚙️](#sambercc-skills-golanggolang-naming--)
- [`samber/cc-skills-golang@golang-safety` ⭐️](#sambercc-skills-golanggolang-safety-)
- [`samber/cc-skills-golang@golang-security` ⭐️ 🧠](#sambercc-skills-golanggolang-security--)
- [`samber/cc-skills-golang@golang-structs-interfaces` ⚙️](#sambercc-skills-golanggolang-structs-interfaces-)
- [`samber/cc-skills-golang@golang-naming` ⭐️ ⚙️](#sambercc-skills-golanggolang-naming-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-safety` ⭐️](#sambercc-skills-golanggolang-safety-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-security` ⭐️ 🧠](#sambercc-skills-golanggolang-security-%EF%B8%8F-)
- [`samber/cc-skills-golang@golang-structs-interfaces` ⚙️](#sambercc-skills-golanggolang-structs-interfaces-%EF%B8%8F)
- [Architecture & Design](#architecture--design)
- [`samber/cc-skills-golang@golang-concurrency` ⚙️](#sambercc-skills-golanggolang-concurrency-)
- [`samber/cc-skills-golang@golang-context` ⚙️](#sambercc-skills-golanggolang-context-)
- [`samber/cc-skills-golang@golang-data-structures` ⭐️](#sambercc-skills-golanggolang-data-structures-)
- [`samber/cc-skills-golang@golang-database` ⭐️ ⚙️](#sambercc-skills-golanggolang-database--)
- [`samber/cc-skills-golang@golang-dependency-injection` ⚙️](#sambercc-skills-golanggolang-dependency-injection-)
- [`samber/cc-skills-golang@golang-design-patterns` ⭐️ ⚙️](#sambercc-skills-golanggolang-design-patterns--)
- [`samber/cc-skills-golang@golang-modernize` ⭐️](#sambercc-skills-golanggolang-modernize-)
- [`samber/cc-skills-golang@golang-concurrency` ⚙️](#sambercc-skills-golanggolang-concurrency-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-context` ⚙️](#sambercc-skills-golanggolang-context-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-data-structures` ⭐️](#sambercc-skills-golanggolang-data-structures-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-database` ⭐️ ⚙️](#sambercc-skills-golanggolang-database-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-dependency-injection` ⚙️](#sambercc-skills-golanggolang-dependency-injection-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-design-patterns` ⭐️ ⚙️](#sambercc-skills-golanggolang-design-patterns-%EF%B8%8F-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-modernize` ⭐️](#sambercc-skills-golanggolang-modernize-%EF%B8%8F)
- [QA & Performance](#qa--performance)
- [`samber/cc-skills-golang@golang-benchmark` 🧠](#sambercc-skills-golanggolang-benchmark-)
- [`samber/cc-skills-golang@golang-observability` ⚙️](#sambercc-skills-golanggolang-observability-)
- [`samber/cc-skills-golang@golang-observability` ⚙️](#sambercc-skills-golanggolang-observability-%EF%B8%8F)
- [`samber/cc-skills-golang@golang-performance` 🧠](#sambercc-skills-golanggolang-performance-)
- [`samber/cc-skills-golang@golang-testing` ⭐️ 🧠 ⚙️](#sambercc-skills-golanggolang-testing---)
- [`samber/cc-skills-golang@golang-troubleshooting` ⭐️ 🧠](#sambercc-skills-golanggolang-troubleshooting--)
- [`samber/cc-skills-golang@golang-testing` ⭐️ 🧠 ⚙️](#sambercc-skills-golanggolang-testing-%EF%B8%8F--%EF%B8%8F)
- [`samber/cc-skills-golang@golang-troubleshooting` ⭐️ 🧠](#sambercc-skills-golanggolang-troubleshooting-%EF%B8%8F-)
- [Project Setup](#project-setup)
- [`samber/cc-skills-golang@golang-cli`](#sambercc-skills-golanggolang-cli)
- [`samber/cc-skills-golang@golang-continuous-integration`](#sambercc-skills-golanggolang-continuous-integration)
@@ -42,7 +42,7 @@
- [`samber/cc-skills-golang@golang-grpc`](#sambercc-skills-golanggolang-grpc)
- [`samber/cc-skills-golang@golang-swagger`](#sambercc-skills-golanggolang-swagger)
- [Dependency Injection](#dependency-injection)
- [`samber/cc-skills-golang@golang-dependency-injection` ⚙️](#sambercc-skills-golanggolang-dependency-injection--1)
- [`samber/cc-skills-golang@golang-dependency-injection` ⚙️](#sambercc-skills-golanggolang-dependency-injection-%EF%B8%8F-1)
- [`samber/cc-skills-golang@golang-google-wire`](#sambercc-skills-golanggolang-google-wire)
- [`samber/cc-skills-golang@golang-uber-dig`](#sambercc-skills-golanggolang-uber-dig)
- [`samber/cc-skills-golang@golang-uber-fx`](#sambercc-skills-golanggolang-uber-fx)
@@ -60,7 +60,7 @@
- [`samber/cc-skills-golang@golang-samber-slog`](#sambercc-skills-golanggolang-samber-slog)
- [Testing](#testing)
- [`samber/cc-skills-golang@golang-stretchr-testify`](#sambercc-skills-golanggolang-stretchr-testify)
- [`samber/cc-skills-golang@golang-testing` ⭐️ 🧠 ⚙️](#sambercc-skills-golanggolang-testing----1)
- [`samber/cc-skills-golang@golang-testing` ⭐️ 🧠 ⚙️](#sambercc-skills-golanggolang-testing-%EF%B8%8F--%EF%B8%8F-1)
## Code Quality
@@ -20,7 +20,7 @@ This workflow writes two independent things to the project's agent-config file(s
- [Markdown targets (CLAUDE.md, AGENTS.md, GEMINI.md, copilot-instructions.md)](#markdown-targets-claudemd-agentsmd-geminimd-copilot-instructionsmd)
- [Cursor target (`.cursor/rules/*.mdc`)](#cursor-target-cursorrulesmdc)
- [Step 5 — Confirm to the user](#step-5--confirm-to-the-user)
- [Notes on company overrides (⚙️ skills)](#notes-on-company-overrides--skills)
- [Notes on company overrides (⚙️ skills)](#notes-on-company-overrides-%EF%B8%8F-skills)
## When to use
+38 -31
View File
@@ -1,12 +1,12 @@
---
name: golang-modernize
description: "Modernize Golang code to use recent language features, standard library improvements, and idiomatic patterns. Trigger proactively when writing or reviewing Go code and old-style patterns are detected, or when encountering a deprecation warning. Also use when the user explicitly asks for modernization, a Go version upgrade, or a CI/tooling refresh. Not for structural refactors, extracting functions, or moving code between packages (→ See `samber/cc-skills-golang@golang-refactoring` skill)."
description: "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)."
user-invocable: true
license: MIT
compatibility: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
metadata:
author: samber
version: "1.4.1"
version: "1.5.0"
openclaw:
emoji: "🔄"
homepage: https://github.com/samber/cc-skills-golang
@@ -14,6 +14,7 @@ metadata:
bins:
- go
install: []
skill-library-version: "1.27"
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch WebSearch AskUserQuestion EnterWorktree ExitWorktree
paths:
- "**/*.go"
@@ -36,7 +37,9 @@ paths:
This skill helps you continuously modernize Go codebases by replacing outdated patterns with their modern equivalents.
**Scope**: This skill covers the last 3 years of Go modernization (Go 1.21 through Go 1.27, released 2023-2026). While this skill can be used for projects targeting Go 1.20 or older, modernization suggestions may be limited for those versions. For best results, consider upgrading 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.
**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
@@ -46,7 +49,7 @@ When invoked:
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
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.
@@ -85,7 +88,7 @@ When the project's `go.mod` targets an older version, suggest upgrading and expl
## 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 1.26's rewritten `go fix` cover overlapping modernization checks, but exact coverage differs by tool version. Go 1.27 adds the `atomictypes`, `embedlit`, `slicesbackward`, and `unsafefuncs` modernizers to `go fix` (and renames `waitgroup` to `waitgroupgo`). See the `samber/cc-skills-golang@golang-lint` skill for configuration.
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](./references/tooling.md) 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
@@ -112,8 +115,12 @@ For CI tooling, govulncheck, PGO, golangci-lint v2, and AI-powered modernization
| `testing/synctest.Run` | `testing/synctest.Test` | Go 1.25 |
| `crypto/rsa.EncryptPKCS1v15` for new encryption use | RSA-OAEP (`rsa.EncryptOAEP` / `rsa.EncryptOAEPWithOptions`) or HPKE/KEM design | Go 1.26 |
| `net/http/httputil.ReverseProxy.Director` | `ReverseProxy.Rewrite` | Go 1.26 |
| `crypto/tls.Config.Rand` | `testing/cryptotest.SetGlobalRandom` for deterministic testing | Go 1.27 |
| `github.com/google/uuid` | `uuid` (stdlib) | Go 1.27 |
| `crypto/tls.Config.Rand` | `testing/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](./references/versions.md#go-127-version-bump-risk-checklist-verify-dont-rewrite).
## Migration Priority Guide
@@ -127,36 +134,36 @@ When modernizing a codebase, prioritize changes by impact:
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)
7. Replace `interface{}` with `any` _(Go 1.18+)_
8. Use `min`/`max` builtins _(Go 1.21+)_
9. Use `range` over int _(Go 1.22+)_
10. Use `slices` and `maps` packages _(Go 1.21+)_
11. Use `cmp.Or` for default values _(Go 1.22+)_
12. Use `sync.OnceValue`/`sync.OnceFunc` _(Go 1.21+)_
13. Use `sync.WaitGroup.Go` _(Go 1.25+)_
14. Use `t.Context()` in tests _(Go 1.24+)_
15. Use `b.Loop()` in benchmarks _(Go 1.24+)_
8. Replace `interface{}` with `any` _(Go 1.18+)_
9. Use `min`/`max` builtins _(Go 1.21+)_
10. Use `range` over int _(Go 1.22+)_
11. Use `slices` and `maps` packages _(Go 1.21+)_
12. Use `cmp.Or` for default values _(Go 1.22+)_
13. Use `sync.OnceValue`/`sync.OnceFunc` _(Go 1.21+)_
14. Use `sync.WaitGroup.Go` _(Go 1.25+)_
15. Use `t.Context()` in tests _(Go 1.24+)_
16. Use `b.Loop()` in benchmarks _(Go 1.24+)_
17. Use generic methods for helpers scoped to one type, and `strings.CutLast`/`bytes.CutLast` instead of `LastIndex` slicing _(Go 1.27+)_
18. 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)
16. Migrate to `slog` from third-party loggers _(Go 1.21+)_
17. Adopt iterators where they simplify code _(Go 1.23+)_
18. Replace `sort.Slice` with `slices.SortFunc` _(Go 1.21+)_
19. Use `strings.SplitSeq` and iterator variants _(Go 1.24+)_
20. Move tool deps to `go.mod` tool directives _(Go 1.24+)_
21. Enable PGO for production builds _(Go 1.21+)_
22. Upgrade to golangci-lint v2 with modernize linter _(golangci-lint v2.6.0+)_
23. Add `govulncheck` to CI pipeline
24. Set up monthly modernization CI pipeline
25. Evaluate migrating to `encoding/json/v2` — stable since Go 1.27 (experimental via `GOEXPERIMENT=jsonv2` in Go 1.251.26); v1 remains supported and is now backed by the v2 implementation
26. Set up AI-driven code review in CI — loads these skills to guide review per area; see `samber/cc-skills-golang@golang-continuous-integration`
27. Replace `github.com/google/uuid` with the stdlib `uuid` package _(Go 1.27+)_ — drops a third-party dependency
28. Use generic methods for package-level generic helpers tied to one type _(Go 1.27+)_ — but not for operations that must satisfy an interface
29. Use `strings.CutLast`/`bytes.CutLast` for last-separator splits _(Go 1.27+)_
30. Run `go fix ./...` after a toolchain upgrade — Go 1.27 adds `atomictypes`, `embedlit`, `slicesbackward`, `unsafefuncs` modernizers _(Go 1.27+)_
19. Migrate to `slog` from third-party loggers _(Go 1.21+)_
20. Adopt iterators where they simplify code _(Go 1.23+)_
21. Replace `sort.Slice` with `slices.SortFunc` _(Go 1.21+)_
22. Use `strings.SplitSeq` and iterator variants _(Go 1.24+)_
23. Move tool deps to `go.mod` tool directives _(Go 1.24+)_
24. Enable PGO for production builds _(Go 1.21+)_
25. Upgrade to golangci-lint v2 with modernize linter _(golangci-lint v2.6.0+)_
26. Add `govulncheck` to CI pipeline
27. Set up monthly modernization CI pipeline
28. Replace `google/uuid`/`gofrs/uuid` with the stdlib `uuid` package, after checking for RFC-variant features the stdlib doesn't cover _(Go 1.27+)_
29. Run `go fix ./...` after a toolchain upgrade to apply the safe automated transformations _(Go 1.27+)_
30. Set up AI-driven code review in CI — loads these skills to guide review per area; see `samber/cc-skills-golang@golang-continuous-integration`
## Related Skills
+109 -28
View File
@@ -176,57 +176,138 @@
"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.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": "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.",
"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": "Replaces github.com/google/uuid with the stdlib uuid package (import \"uuid\")" },
{ "id": "14.2", "text": "Uses uuid.New().String() — does NOT call uuid.NewString() (does not exist in stdlib)" },
{ "id": "14.3", "text": "Knows stdlib generators return values without errors (no id, err := uuid.NewV7() error handling)" },
{ "id": "14.4", "text": "Uses uuid.NewV7() for the time-ordered database ID case" },
{ "id": "14.5", "text": "Removes github.com/google/uuid from go.mod (e.g. via go mod tidy)" }
{ "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": "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.",
"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": "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": "15.2", "text": "Converts FilterSet to a method on *Set[T]" },
{ "id": "15.3", "text": "Does NOT claim generic methods are impossible or unsupported in Go" },
{ "id": "15.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": "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": "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.",
"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 for at least one of the functions" },
{ "id": "16.2", "text": "Uses the 3-result form (before, after, found) of CutLast correctly" },
{ "id": "16.3", "text": "Uses CutLast in EnvValue to simplify the ok-returning split" },
{ "id": "16.4", "text": "Does NOT keep manual strings.LastIndex slicing arithmetic where CutLast applies" }
{ "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": "17.1", "text": "Wraps tests in synctest.Test" },
{ "id": "17.2", "text": "Uses httptest.NewTestServer instead of httptest.NewServer" },
{ "id": "17.3", "text": "Explains that real-network servers do not work inside a synctest bubble (in-memory/fake network rationale)" },
{ "id": "17.4", "text": "Does NOT leave a plain httptest.NewServer inside a synctest bubble" },
{ "id": "17.5", "text": "Keeps both tests working (response test and client-timeout test) with deterministic time" }
{ "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" }
]
}
]
+27 -1
View File
@@ -11,7 +11,7 @@ Compare the project's `go` directive in `go.mod` against the latest stable relea
go version
# Update go.mod to target a newer version
go mod edit -go=1.26
go mod edit -go=1.27
# Update toolchain
go get toolchain@latest
@@ -53,6 +53,32 @@ go build ./...
Go 1.22+ expanded PGO to devirtualize more interface calls. Go 1.23+ reduced PGO build time overhead to single digits.
## `go fix` and `go doc` changes _(Go 1.27+)_
`go fix` now applies more of the `modernize` fixer suite automatically. Coverage shifted between Go 1.26 and 1.27:
- **Added**: `atomictypes`, `embedlit`, `slicesbackward`, `unsafefuncs`
- **Removed**: `fmtappendf` (superseded by other fixers)
- **Renamed**: `waitgroup``waitgroupgo`
```bash
go fix ./... # applies the enabled safe transformations
go tool fix help # check exact fixer coverage for the installed toolchain
```
`go doc` accepts `package@version` to look up a specific module version without changing `go.mod`, and `-ex` lists executable examples:
```bash
go doc golang.org/x/tools/cmd/stringer@v0.30.0
go doc -ex net/http.Client
```
`go mod tidy` for modules with `go 1.27` or newer auto-merges duplicate `require` blocks and enforces a two-block layout (direct dependencies, then indirect), preserving existing comments.
`go test -json` output gained an optional `OutputType` field (`"error"`, `"error-continue"`, `"frame"`) for consumers that need to distinguish test-output kinds programmatically.
`go tool trace -http` now binds to localhost by default; pass `-http=0.0.0.0:6060` explicitly to expose the trace viewer beyond the local machine (relevant when tracing inside a container or remote dev environment).
## AI-Driven Code Review in CI
Add an AI agent as a PR reviewer alongside traditional static analysis. When configured with this skill plugin, the agent loads the relevant Go skills — `golang-security` for security review, `golang-concurrency` for concurrency issues, `golang-error-handling` for error handling, and so on — giving it the same expertise as a senior Go reviewer. This catches architectural drift, logic bugs, missing context in errors, and subtle concurrency hazards that linters cannot detect.
+119 -48
View File
@@ -40,7 +40,7 @@
- [Use `testing/synctest` for concurrent code testing _(Go 1.25+, experimental in 1.24)_](#use-testingsynctest-for-concurrent-code-testing-go-125-experimental-in-124)
- [Use `runtime/trace.FlightRecorder` _(Go 1.25+)_](#use-runtimetraceflightrecorder-go-125)
- [Container-aware `GOMAXPROCS` _(Go 1.25+)_](#container-aware-gomaxprocs-go-125)
- [`encoding/json/v2` (experimental) _(Go 1.25+, GOEXPERIMENT=jsonv2)_](#encodingjsonv2-experimental-go-125-goexperimentjsonv2)
- [`encoding/json/v2` — introduced experimental _(Go 1.25+, GOEXPERIMENT=jsonv2)_](#encodingjsonv2--introduced-experimental-go-125-goexperimentjsonv2)
- [Go 1.25 additions to prefer when target allows](#go-125-additions-to-prefer-when-target-allows)
- [Go 1.26 Modernizations (February 2026)](#go-126-modernizations-february-2026)
- [Use `errors.AsType[T]()` _(Go 1.26+)_](#use-errorsastypet-go-126)
@@ -56,6 +56,21 @@
- [Go 1.26+ documentation command](#go-126-documentation-command)
- [Go 1.26+ module target note](#go-126-module-target-note)
- [Modernized `go fix` _(Go 1.26+)_](#modernized-go-fix-go-126)
- [Go 1.27 Modernizations (August 2026)](#go-127-modernizations-august-2026)
- [Use generic methods to scope generics to a type _(Go 1.27+)_](#use-generic-methods-to-scope-generics-to-a-type-go-127)
- [Use `strings.CutLast` and `bytes.CutLast` instead of `LastIndex` slicing _(Go 1.27+)_](#use-stringscutlast-and-bytescutlast-instead-of-lastindex-slicing-go-127)
- [Use `net/url` `URL.Clone()` and `Values.Clone()` _(Go 1.27+)_](#use-neturl-urlclone-and-valuesclone-go-127)
- [Use `math/big.Int.Divide` for rounding-mode division _(Go 1.27+)_](#use-mathbigintdivide-for-rounding-mode-division-go-127)
- [Use stdlib `uuid` instead of a UUID dependency _(Go 1.27+)_](#use-stdlib-uuid-instead-of-a-uuid-dependency-go-127)
- [Migrate to `encoding/json/v2` — default since Go 1.27 _(Go 1.27+)_](#migrate-to-encodingjsonv2--default-since-go-127-go-127)
- [Use `testing/synctest.Sleep()` inside a synctest bubble _(Go 1.27+)_](#use-testingsynctestsleep-inside-a-synctest-bubble-go-127)
- [Use `net/http/httptest.NewTestServer()` for in-memory server tests _(Go 1.27+)_](#use-nethttphttptestnewtestserver-for-in-memory-server-tests-go-127)
- [`runtime/pprof` `goroutineleak` profile is generally available _(Go 1.27+)_](#runtimepprof-goroutineleak-profile-is-generally-available-go-127)
- [`go fix` gains new modernizers _(Go 1.27+)_](#go-fix-gains-new-modernizers-go-127)
- [`go test` runs the `stdversion` vet check _(Go 1.27+)_](#go-test-runs-the-stdversion-vet-check-go-127)
- [`go mod tidy` merges duplicate require blocks _(Go 1.27+)_](#go-mod-tidy-merges-duplicate-require-blocks-go-127)
- [Small Go 1.27+ API preferences](#small-go-127-api-preferences)
- [Go 1.27+ version-bump risk checklist (verify, don't rewrite)](#go-127-version-bump-risk-checklist-verify-dont-rewrite)
- [General Modernization (Any Version)](#general-modernization-any-version)
- [Code MUST use `any` instead of `interface{}` _(Go 1.18+)_](#code-must-use-any-instead-of-interface-go-118)
- [Use generics instead of `interface{}` + type assertions _(Go 1.18+)_](#use-generics-instead-of-interface--type-assertions-go-118)
@@ -631,9 +646,9 @@ import _ "go.uber.org/automaxprocs"
// GOMAXPROCS is set automatically from cgroup CPU limits
```
### `encoding/json/v2` (experimental) _(Go 1.25+, GOEXPERIMENT=jsonv2)_
### `encoding/json/v2` — introduced experimental _(Go 1.25+, GOEXPERIMENT=jsonv2)_
Major JSON revision. **Experimental** in Go 1.251.26 — evaluate for new code, don't migrate production yet. Stable since Go 1.27 — see the Go 1.27 section.
Major JSON revision, experimental via `GOEXPERIMENT=jsonv2` in Go 1.251.26. Go 1.27 made it the default implementation — see the Go 1.27 section below for the stable API and migration hazards.
### Go 1.25 additions to prefer when target allows
@@ -756,104 +771,160 @@ go fix ./... # applies the enabled safe transformations
Changelog: <https://go.dev/doc/go1.27>
### Use generic methods _(Go 1.27+)_
### Use generic methods to scope generics to a type _(Go 1.27+)_
Methods may now declare their own type parameters. Move package-level generic helper functions that operate on one type into methods on that type — this puts the operation in the type's namespace instead of the whole package's:
Go 1.27 lifted a restriction present since generics landed in Go 1.18: a method may now declare its own type parameters ([go.dev/issue/77273](https://go.dev/issue/77273), [spec: Method declarations](https://go.dev/ref/spec#Method_declarations)), so a helper that logically belongs to one type no longer needs a package-scope generic function. Interface methods still cannot declare type parameters, and a generic method cannot satisfy an interface — keep the package-level function when the operation must be part of an interface contract.
```go
// Before: package-scoped generic helper tied to one type
func Map[T, U any](s *Set[T], f func(T) U) *Set[U] { ... }
out := Map(mySet, f)
// Before: package-scope generic function, disconnected from the type it serves
func FilterInts[T any](s []T, pred func(T) bool) []T { ... }
// After (Go 1.27+): method with its own type parameter
func (s *Set[T]) Map[U any](f func(T) U) *Set[U] { ... }
out := mySet.Map(f)
// After (Go 1.27+): generic method, scoped to the receiver
func (s Set[T]) Filter[U comparable](pred func(T) U) Set[T] { ... }
```
Caveats: interface methods may not declare type parameters, and a generic method cannot satisfy an interface — keep the package-level function when the operation must be part of an interface contract. Stdlib example: `math/rand/v2` gained the generic method `(*Rand).N[Int]()`, matching the top-level `rand.N[Int]()` function.
The standard library's own `(*rand.Rand).N[Int intType](n Int) Int` (`math/rand/v2`) is the reference example.
### `encoding/json/v2` is stable _(Go 1.27+)_
### Use `strings.CutLast` and `bytes.CutLast` instead of `LastIndex` slicing _(Go 1.27+)_
`encoding/json/v2` and `encoding/json/jsontext` are now stable (experimental since Go 1.25 via `GOEXPERIMENT=jsonv2`). The v1 `encoding/json` package is now backed by the v2 implementation: v1 behavior is preserved, unmarshal is significantly faster, and v1 gained `Options` to selectively adopt v2 semantics without a full migration. v2 uses stricter, more interoperable defaults — it rejects invalid UTF-8 in strings and duplicate object member names. The v1 API remains supported; migration is not required. `GOEXPERIMENT=nojsonv2` restores the original v1 implementation if a compatibility problem appears.
```go
// Before: manual index arithmetic — easy to get the offset wrong
if i := strings.LastIndex(path, "/"); i >= 0 {
dir, file := path[:i], path[i+1:]
}
### Use stdlib `uuid` instead of `github.com/google/uuid` _(Go 1.27+)_
// After (Go 1.27+)
if dir, file, ok := strings.CutLast(path, "/"); ok {
// dir, file
}
```
`bytes.CutLast(b, sep []byte) (before, after []byte, found bool)` mirrors `strings.CutLast(s, sep string) (before, after string, found bool)`.
### Use `net/url` `URL.Clone()` and `Values.Clone()` _(Go 1.27+)_
```go
// Before: Values is map[string][]string — a shallow copy shares the slices
clone := url.Values{}
for k, v := range original {
clone[k] = append([]string(nil), v...)
}
// After (Go 1.27+)
clone := original.Clone()
u2 := u.Clone()
```
### Use `math/big.Int.Divide` for rounding-mode division _(Go 1.27+)_
```go
// Before: sign-correction dance for floor/ceil division
q, r := new(big.Int).QuoRem(x, y, new(big.Int))
if r.Sign() != 0 && (r.Sign() < 0) != (y.Sign() < 0) {
q.Sub(q, big.NewInt(1))
}
// After (Go 1.27+)
q, r := new(big.Int).Divide(x, y, new(big.Int), big.Floor)
// modes: big.Trunc, big.Floor, big.Round, big.Ceil
```
### Use stdlib `uuid` instead of a UUID dependency _(Go 1.27+)_
```go
// Before
import "github.com/google/uuid"
id := uuid.NewString()
// After (Go 1.27+)
// After (Go 1.27+): no external dependency
import "uuid"
id := uuid.New().String()
```
The stdlib generators (`uuid.New()`, `uuid.NewV4()`, `uuid.NewV7()`) return values without errors, and v7 UUIDs are time-ordered — prefer them for database primary keys. Keep `github.com/google/uuid` only if the project relies on features the stdlib package lacks, such as SQL `Scanner`/`driver.Valuer` integration.
The stdlib generators (`uuid.New()`, `uuid.NewV4()`, `uuid.NewV7()`) return values without errors, and v7 UUIDs are time-ordered — prefer `uuid.NewV7()` for database primary keys where index locality matters. Check `go mod why -m github.com/google/uuid` (or `gofrs/uuid`) before dropping the dependency — some codebases depend on v3/v5 namespace UUIDs, SQL `Scanner`/`driver.Valuer` integration, or other RFC-specific variants the stdlib package does not (yet) cover.
### Use `strings.CutLast` and `bytes.CutLast` _(Go 1.27+)_
### Migrate to `encoding/json/v2` — default since Go 1.27 _(Go 1.27+)_
`encoding/json/v2` and `encoding/json/jsontext` are now stable (experimental since Go 1.25 via `GOEXPERIMENT=jsonv2`) and `encoding/json/v2` is the default JSON implementation; `encoding/json` becomes a thin wrapper over it, and unmarshal is significantly faster. For new code, prefer the v2 API directly:
```go
// Before
if i := strings.LastIndex(path, "/"); i >= 0 {
dir, file := path[:i], path[i+1:]
_ = dir
_ = file
}
data, err := json.Marshal(v)
err = json.Unmarshal(data, &v)
// After (Go 1.27+)
if dir, file, ok := strings.CutLast(path, "/"); ok {
_ = dir
_ = file
}
import "encoding/json/v2"
data, err := json.Marshal(v) // same names, v2 semantics
err = json.UnmarshalRead(r, &v) // stream from an io.Reader without a wrapper buffer
```
### Use `url.Clone` for deep copies _(Go 1.27+)_
Use `encoding/json/jsontext` (`Encoder`, `Decoder`, `Token`, `Value`) for syntactic, streaming-level JSON work instead of hand-rolled `json.RawMessage` juggling.
**Migrate deliberately, not blindly — the default got stricter:**
- Duplicate object member names are now rejected; v1 silently kept the last one.
- Invalid UTF-8 in JSON strings is now rejected; v1 replaced it silently.
- The `format` and `unknown` struct tags, `DiscardUnknownMembers`, and `SkipFunc` are gone.
- The `inline` tag is renamed `embed`.
- Roll back with `GOEXPERIMENT=nojsonv2` only as a temporary compatibility bridge, not a permanent stance — it is documented as the escape hatch, not the intended steady state.
### Use `testing/synctest.Sleep()` inside a synctest bubble _(Go 1.27+)_
```go
// Before: struct copy still shares the User pointer and other references
u2 := *u
// After (Go 1.27+): true deep copy
u2 := u.Clone()
vals := values.Clone() // url.Values deep copy
synctest.Test(t, func(t *testing.T) {
go worker()
synctest.Sleep(time.Second) // advances the bubble's fake clock
})
```
### Use `synctest.Sleep` and `httptest.NewTestServer` in tests _(Go 1.27+)_
### Use `net/http/httptest.NewTestServer()` for in-memory server tests _(Go 1.27+)_
```go
// Before (Go 1.251.26): two calls inside a synctest bubble
time.Sleep(time.Second)
synctest.Wait()
// Before: httptest.Server binds a real socket, forcing real goroutines/timers
srv := httptest.NewServer(handler)
defer srv.Close()
// After (Go 1.27+): Sleep + Wait combined
synctest.Sleep(time.Second)
// After (Go 1.27+): in-memory fake network, composes with testing/synctest
synctest.Test(t, func(t *testing.T) {
srv := httptest.NewTestServer(handler)
defer srv.Close()
})
```
`httptest.NewTestServer` creates a test server on an in-memory fake network, so HTTP client/server tests can run inside a `synctest` bubble with instant, deterministic time.
### `runtime/pprof` `goroutineleak` profile is generally available _(Go 1.27+)_
### Goroutine leak profile is stable _(Go 1.27+)_
The experimental Go 1.26 goroutine leak profile is now stable as the `goroutineleak` profile in `runtime/pprof` and at the `/debug/pprof/goroutineleak` endpoint — no `GOEXPERIMENT` needed. It reports goroutines blocked on a concurrency primitive that can never be unblocked. Limitation: leaks through primitives reachable from global variables or runnable goroutines are not detected.
The goroutine leak profile (previously experimental behind `GOEXPERIMENT=goroutineleakprofile` in Go 1.26) is now a standard `runtime/pprof` profile, also served at `/debug/pprof/goroutineleak` — no build flag required. It reports goroutines blocked on a concurrency primitive that can never be unblocked; leaks reachable from global variables or still-runnable goroutines are not detected. → See `samber/cc-skills-golang@golang-concurrency` and `samber/cc-skills-golang@golang-troubleshooting` skills for using it in a leak investigation.
### `go fix` gains new modernizers _(Go 1.27+)_
New analyzers: `atomictypes`, `embedlit`, `slicesbackward`, `unsafefuncs`. The `waitgroup` analyzer was renamed to `waitgroupgo`, and `fmtappendf` was removed for stylistic reasons. Run `go fix ./...` after upgrading the toolchain.
New analyzers: `atomictypes`, `embedlit`, `slicesbackward`, `unsafefuncs`. The `waitgroup` analyzer was renamed to `waitgroupgo`, and `fmtappendf` was removed. Run `go fix ./...` after upgrading the toolchain. → See [Tooling modernization](./tooling.md) for the full `go fix`/`go doc`/`go mod tidy` command reference.
### `go test` runs the `stdversion` vet check _(Go 1.27+)_
`go test` now reports uses of standard library symbols that are too new for the file's effective Go version (the `go` directive in `go.mod` plus build tags). If CI starts failing after a toolchain upgrade, either bump the module's `go` directive or gate the newer API behind build tags.
`go test` now reports uses of standard library symbols that are too new for the file's effective Go version (the `go` directive in `go.mod` plus build tags). If CI starts failing after a toolchain upgrade, either bump the module's `go` directive or gate the newer API behind build tags — don't silence the check.
### `go mod tidy` merges duplicate require blocks _(Go 1.27+)_
For modules with `go 1.27` or later in `go.mod`, `go mod tidy` consolidates duplicate `require` blocks into the standard two-block layout (one direct, one indirect). Run it once after bumping the `go` directive to clean up blocks left by manual edits and merge conflicts.
For modules with `go 1.27` or later in `go.mod`, `go mod tidy` consolidates duplicate `require` blocks into the standard two-block layout (one direct, one indirect), preserving existing comments. Run it once after bumping the `go` directive to clean up blocks left by manual edits and merge conflicts.
### Small Go 1.27+ API preferences
- `hash/maphash.Hasher` and `maphash.ComparableHasher`: contracts between a type and future hash-based data structures (hash tables, Bloom filters).
- `math/big.Int.Divide`: quotient and remainder with explicit rounding modes (`Trunc`, `Floor`, `Round`, `Ceil`).
- `database/sql.ConvertAssign` and `driver.RowsColumnScanner`: for database driver authors.
- `runtime/secret.Do`: goroutines started in secret mode now execute in secret mode themselves.
### Go 1.27+ version-bump risk checklist (verify, don't rewrite)
These changes need review before or during a bump to `go 1.27` — none of them require a code rewrite, but skipping the check risks a build failure or a silent behavior change:
- **Removed `GODEBUG` settings**`asynctimerchan`, `tlsunsafeekm`, `tlsrsakex`, `tls3des`, `tls10server`, `x509keypairleaf`, `gotypesalias`. A `godebug` line in `go.mod` or a `//go:debug` comment still pinning one of these to its old value now **fails the build**; pinning it to its current default value is accepted. Search with `grep -rn 'go:debug\|godebug' go.mod **/*.go`.
- **json/v2 default strictness** — see above; re-run integration tests against real-world payloads, not just unit tests, before the bump ships.
- **Size-specialized allocator** — up to 30% faster allocations under 80 bytes, roughly 1% faster overall, at the cost of ~60 KB binary size. Enabled by default; disable with `GOEXPERIMENT=nosizespecializedmalloc` if binary size is constrained, but treat that flag as scheduled for removal in Go 1.28, not a long-term setting.
- **Darwin floor raised to macOS 13 (Ventura)** — older macOS targets can no longer run binaries built with this toolchain.
- **`linux/ppc64` now builds ELFv2 binaries** and requires Linux kernel 3.13+ (RHEL 7's 3.10 kernel with backports) — relevant only to ppc64 deployments.
- **`bzr` version control support removed** from the `go` command — irrelevant unless a module still vendors from Bazaar.
- **Tracebacks now include `runtime/pprof` goroutine labels** for `go 1.27+` modules by default; disable with `GODEBUG=tracebacklabels=0` if labels leak sensitive data into crash logs or panic output.
---
## General Modernization (Any Version)
@@ -12,6 +12,8 @@ pprof endpoints MUST be protected with basic auth — NEVER expose them publicly
→ See `samber/cc-skills-golang@golang-troubleshooting` pprof.md for the full pprof CLI reference (profile types, capturing, analyzing, commands).
The `goroutineleak` profile (`/debug/pprof/goroutineleak`, generally available since Go 1.27) is worth exposing alongside the standard profiles — it reports goroutines blocked on unreachable concurrency primitives, a signal the standard `goroutine` profile doesn't surface directly. Since Go 1.27, tracebacks for `go 1.27+` modules also carry `runtime/pprof` goroutine labels by default; if labels could leak sensitive request data into crash logs, disable with `GODEBUG=tracebacklabels=0`.
## Continuous Profiling with Pyroscope
On-demand profiling requires you to be there when the problem happens. Continuous profiling runs always-on in the background with low overhead (~2-5% CPU), so you can look at profiles after the fact. Toggle it with an environment variable.
+3 -3
View File
@@ -183,11 +183,11 @@ Expect 2-4x improvement for tight arithmetic loops. Only use when profiling show
**Diagnose:** 1- `go tool pprof` (CPU profile) — confirm a numeric inner loop consumes >20% of CPU; SIMD only helps CPU-bound numeric work, not allocation or I/O bottlenecks 2- `go test -bench` — measure the loop's baseline ns/op; provides the reference point to validate SIMD gains 3- `go build -gcflags="-d=ssa/prove/debug=2"` — check if the compiler already auto-vectorized the loop; look for `"Proved"` bounds-check eliminations that enable vectorization 4- `GOSSAFUNC=MyFunc go build` — generate SSA dump (`ssa.html`) to inspect whether the compiler produces vector instructions for the hot loop 5- `go tool objdump -s MyFunc ./binary` — verify the final assembly contains SIMD instructions (e.g., `VMOVAPD`, `VADDPD` on amd64) rather than scalar equivalents
Go 1.26+ includes an experimental `simd/archsimd` package (requires `GOEXPERIMENT=simd` flag) providing low-level SIMD intrinsics for amd64 with 128/256/512-bit vectors. For broader portability, the compiler auto-vectorizes simple loops, and several strategies exist.
Go 1.26+ includes an experimental `simd/archsimd` package (requires `GOEXPERIMENT=simd` flag) providing low-level, architecture-specific SIMD intrinsics amd64 with 128/256/512-bit vectors, and arm64/wasm at 128-bit as of Go 1.27. Go 1.27 also adds a portable `simd` package (vector types like `Int8s`, `Float32s`) that works across architectures and falls back to scalar code without hardware support. For broader portability, the compiler auto-vectorizes simple loops, and several strategies exist.
**Options for explicit SIMD in Go:**
- **Experimental `simd/archsimd` (Go 1.26+, speculative)** — Direct SIMD intrinsics via vector types with CPU feature detection. Limited to AMD64. Use with caution: this is an experimental, in-progress API (`GOEXPERIMENT=simd`) whose package path and type names are subject to change before stabilization. Not covered by Go 1 compatibility guarantees, and should never be exposed in public APIs. Verify the actual import path and API against the Go toolchain you are using.
- **Experimental `simd`/`simd/archsimd` (Go 1.26+, speculative)** — Direct SIMD intrinsics via vector types with CPU feature detection. `simd` (Go 1.27+) is portable across architectures; `simd/archsimd` is architecture-specific (amd64, plus arm64/wasm as of Go 1.27). Use with caution: both are experimental, in-progress APIs (`GOEXPERIMENT=simd`) whose package paths and type names are subject to change before stabilization. Not covered by Go 1 compatibility guarantees, and should never be exposed in public APIs. Verify the actual import path and API against the Go toolchain you are using.
```go
// Requires: GOEXPERIMENT=simd go build
@@ -297,7 +297,7 @@ go build -tags=nosimd -o app-safe . # Fallback everywhere
- Auto-vectorization covers the most common cases (simple numeric loops)
- If your bottleneck is allocations or I/O, SIMD won't help
**Recommendation:** Start with auto-vectorization. For Go 1.26+, evaluate `simd/archsimd` for AMD64-only workloads (remembering it's experimental). Move to runtime detection (option 2 above) if profiling shows a bottleneck and the code needs to run on heterogeneous hardware. Only use compile-time selection (option 3) if you control the deployment environment and can test each per-binary variant.
**Recommendation:** Start with auto-vectorization. For Go 1.27+, evaluate the portable `simd` package for cross-architecture code, or `simd/archsimd` for architecture-specific tuning (amd64, arm64, wasm) — remembering both are experimental. Move to runtime detection (option 2 above) if profiling shows a bottleneck and the code needs to run on heterogeneous hardware. Only use compile-time selection (option 3) if you control the deployment environment and can test each per-binary variant.
Only invest in hand-written SIMD when profiling shows a numeric inner loop consuming >20% of CPU and the compiler isn't auto-vectorizing it.
@@ -128,7 +128,7 @@ The standard `encoding/json` package uses reflection to inspect struct fields at
- **Custom `MarshalJSON`/`UnmarshalJSON`** — hand-written methods for hot-path types eliminate reflection
- **Code-generation libraries**`easyjson`, `ffjson` generate marshal/unmarshal methods at build time, no reflection at runtime
- **Drop-in replacements**`github.com/goccy/go-json`, `github.com/json-iterator/go`, `github.com/bytedance/sonic` offer 2-5x better performance
- **`encoding/json/v2`** (experimental, behind `GOEXPERIMENT=jsonv2`) — evaluate deliberately; most production code should keep `encoding/json` unless the project explicitly opts into the experiment
- **`encoding/json/v2`** (default JSON implementation since Go 1.27; introduced experimental behind `GOEXPERIMENT=jsonv2` in Go 1.25) — migrate deliberately: it is stricter than v1 (rejects duplicate object keys and invalid UTF-8), so re-run tests against real payloads before relying on it in a hot path
When using third-party JSON libraries, refer to the library's official documentation for up-to-date API signatures.
@@ -160,7 +160,7 @@
**Carbon** (<https://github.com/uniplaces/carbon>) Simple, semantic time library for Go. Time parsing, formatting, manipulation.
**google/uuid** (<https://github.com/google/uuid>) Generate and parse UUIDs. Official Google library, RFC 4122 compliant.
**google/uuid** (<https://github.com/google/uuid>) Generate and parse UUIDs. Official Google library, RFC 4122 compliant. For new Go 1.27+ code, check the standard library `uuid` package first — this library still has broader RFC-variant coverage (v3/v5 namespaced UUIDs).
## Database Schema Migration
@@ -6,7 +6,7 @@ The Go standard library continues to evolve with v2 packages and experimental fe
**math/rand/v2** (Go 1.22+) Improved random number generation with better algorithms (ChaCha8, PCG). Auto-seeded, no more rand.Seed() needed.
**encoding/json/v2** (experimental stdlib package behind `GOEXPERIMENT=jsonv2`) Next-generation JSON encoding/decoding. Evaluate deliberately; most production code should keep `encoding/json` unless the project explicitly opts into the experiment.
**encoding/json/v2** (default JSON implementation as of Go 1.27; introduced experimental behind `GOEXPERIMENT=jsonv2` in Go 1.25) Next-generation JSON encoding/decoding, now the default underneath `encoding/json`. Stricter than v1: rejects duplicate object keys and invalid UTF-8. → See `samber/cc-skills-golang@golang-modernize` skill for migration guidance and the `GOEXPERIMENT=nojsonv2` rollback.
## New Packages (Promoted from x/exp)
@@ -26,6 +26,8 @@ The Go standard library continues to evolve with v2 packages and experimental fe
**structs** (Go 1.23+) Structure layout control and introspection.
**uuid** (Go 1.27+) UUID generation and parsing (`New`, `NewV4`, `NewV7`, `Parse`). Prefer over `github.com/google/uuid` for new code unless a v3/v5 namespaced UUID or another RFC-variant feature the stdlib package doesn't cover is needed.
## golang.org/x (Official Extensions)
**golang.org/x/oauth2** OAuth2 client implementation. Supports multiple providers (Google, GitHub, etc.). Official OAuth2 client.
@@ -92,7 +92,7 @@ gopatch -d -p rewrite.patch ./... # dry-run — show the diff only
### `go fix` — the `go/analysis`-based fixer suite
- As of Go 1.26, `go fix` is rewritten onto the `go/analysis` framework and has converged with `go vet` — this is where the `modernize` fixer suite lives (`rangeint`, `mapsloop`, `minmax`, `any`, `stringscut`, `fmtappendf`, `omitzero`, and more; → See `samber/cc-skills-golang@golang-modernize` skill for the idiom-by-idiom breakdown).
- As of Go 1.26, `go fix` is rewritten onto the `go/analysis` framework and has converged with `go vet` — this is where the `modernize` fixer suite lives (`rangeint`, `mapsloop`, `minmax`, `any`, `stringscut`, `omitzero`, and more). Coverage keeps shifting release to release — Go 1.27 added `atomictypes`, `embedlit`, `slicesbackward`, `unsafefuncs`, removed `fmtappendf`, and renamed `waitgroup` to `waitgroupgo`; → See `samber/cc-skills-golang@golang-modernize` skill for the idiom-by-idiom breakdown.
- On an older toolchain without this convergence, run the equivalent analyzers through `singlechecker`/`multichecker -fix` instead of `go fix`.
- The `//go:fix inline` directive marks a function or constant so that `go fix` inlines every call site into its replacement — a machine-executable way to complete a deprecation migration once the replacement exists:
+9 -2
View File
@@ -6,7 +6,7 @@ license: MIT
compatibility: Designed for Claude Code, Codex or similar harness, and for projects using Golang.
metadata:
author: samber
version: "1.3.1"
version: "1.4.0"
openclaw:
emoji: "🧪"
homepage: https://github.com/samber/cc-skills-golang
@@ -252,7 +252,7 @@ func TestContextTimeout(t *testing.T) {
}
```
Use `synctest.Test` in Go 1.25+ and Go 1.26+. Do not use the old Go 1.24 experimental `synctest.Run` API in Go 1.25+ or Go 1.26+ code. If a module explicitly targets Go 1.24 and opts into `GOEXPERIMENT=synctest`, use the old API only as a compatibility fallback.
Use `synctest.Test` in Go 1.25+ and later. Do not use the old Go 1.24 experimental `synctest.Run` API in Go 1.25+ code. If a module explicitly targets Go 1.24 and opts into `GOEXPERIMENT=synctest`, use the old API only as a compatibility fallback.
Key differences in `synctest`:
@@ -260,6 +260,9 @@ Key differences in `synctest`:
- `time.After` fires when synthetic time reaches the duration
- All goroutines run to blocking points before time advances
- Test execution is deterministic and repeatable
- Go 1.27+ adds `synctest.Sleep(d)` as a direct helper to advance the bubble's fake clock, equivalent to `time.Sleep(d)` followed by `synctest.Wait()` but without needing a real goroutine to block on
Go 1.27+ also adds `httptest.NewTestServer()`, an in-memory fake-network variant of `httptest.NewServer` that composes with `synctest` — no real socket, so server tests can run inside a `synctest.Test` bubble instead of needing `httptest.NewServer` plus real timers.
## Test Timeouts
@@ -292,6 +295,10 @@ func TestRenderGoldenArtifact(t *testing.T) {
Available on `*testing.T`, `*testing.B`, and `*testing.F` in Go 1.26+.
### Go 1.27+: `stdversion` runs automatically
`go test` now invokes the `stdversion` vet check by default, flagging any use of an API newer than the module's `go` directive. A CI failure from this check means either the `go` directive needs bumping or the code needs to stop using the newer API — it is not a check to silence.
## Parallel Tests
Use `t.Parallel()` to run tests concurrently:
@@ -14,7 +14,7 @@
Use pprof goroutine profile (see [pprof.md](./pprof.md)) with `?debug=2` for human-readable output, then look for goroutines stuck in `chan receive`.
For Go 1.26 diagnostics, there is also an experimental goroutine leak profile. It is useful for production-oriented leak investigation, but is gated by `GOEXPERIMENT=goroutineleakprofile`; do not rely on it as default stable behavior.
The goroutine leak profile (experimental behind `GOEXPERIMENT=goroutineleakprofile` in Go 1.26) is generally available since Go 1.27 — no build flag needed, and it is served at `/debug/pprof/goroutineleak` like any other standard profile.
```bash
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
@@ -18,7 +18,7 @@
### Go documentation command
Use `go doc`, not `go tool doc`. Go 1.26 removed the old `cmd/doc` / `go tool doc` path.
Use `go doc`, not `go tool doc`. Go 1.26 removed the old `cmd/doc` / `go tool doc` path. Go 1.27 added `package@version` lookups (`go doc golang.org/x/tools/cmd/stringer@v0.30.0`) and an `-ex` flag that lists executable examples.
### GC Tracing
@@ -99,7 +99,7 @@ curl http://localhost:6060/debug/pprof/goroutine?debug=2 > goroutines.txt
# Goroutine profile (for pprof analysis)
curl http://localhost:6060/debug/pprof/goroutine > goroutine.prof
# Go 1.26 experimental goroutine leak profile, only with GOEXPERIMENT=goroutineleakprofile
# Goroutine leak profile — generally available since Go 1.27 (no GOEXPERIMENT needed)
curl http://localhost:6060/debug/pprof/goroutineleak?debug=2
go tool pprof http://localhost:6060/debug/pprof/goroutineleak