Skip to content

feat: move the standards baseline to Go 1.27 - #15

Merged
sebastian-iancu merged 7 commits into
mainfrom
feat/go-1.27-floor
Oct 27, 2026
Merged

sebastian-iancu merged 7 commits into
mainfrom
feat/go-1.27-floor

Conversation

@sebastian-iancu

Copy link
Copy Markdown
Contributor

Summary

Moves the plugin's standards baseline from Go 1.26.4+ (with Go 1.27 as hints) to Go 1.27+ as a hard floor. A module on an older go directive skips what is marked newer. The compiler, go vet (stdversion, which go test runs by default from 1.27) and go fix all follow the directive, so one sentence in the router, the Cursor rule and go-reviewer replaces any per-rule 1.26 variant. That matches the "no fallback branch" policy in docs/authoring.md.

Sources: re-read per the refresh procedure in docs/authoring.md:

  • Release notes: the Go 1.27 release notes in full (late entries were added on 2026-10-02, after the last refresh).
  • Go blog: the release post and the posts on generic methods, goroutine leak profiles and HTTP/1 connection reuse.
  • JSON: the JSON tutorial, which now teaches encoding/json/v2, and the encoding/json "Migrating to v2" notes.
  • Style guides: Code Review Comments and the Google and Uber guides are unchanged since their recorded revisions. revive moved to revive-lint/revive.

Checked by running the tools, not by reading docs:

  • Fixer names: go1.27.3's go tool fix help registers errorsastype, atomictypes and slicesbackward, which were marked golangci-lint-only. waitgroup is now waitgroupgo, and fmtappendf is gone.
  • Withdrawn rewrites: x/tools left fmtappendf (x/tools/go/analysis/passes/modernize: remove the fmtappendf modernizer golang/go#77581) and bloop (x/tools/gopls: modernize bloop: Can't use b.Loop() if loop contains b.StopTimer/b.StartTimer golang/go#74967) out of the suite. bloop was marked as running in golangci-lint, which it no longer does.
  • CutLast: go1.27.3's stringscut rewrites Index to Cut but not LastIndex to CutLast. x/tools v0.52.0, which golangci-lint v2.15.0 pins, does both.
  • Version gating: go fix gates rewrites on the go directive (no range n under go 1.21). go vet reports strings.CutLast requires go1.27 or later (module is go1.26).
  • New checks and behaviour: go vet reports %w on a *E whose E implements error. omitzero drops a non-nil *time.Time at the zero time, where omitempty keeps it.

Changes by file:

  • go-idioms:
    • "Newer in Go 1.27" is folded into the tables, and the Fixer column follows go1.27.
    • fmt.Fprintf(w, …) leads over fmt.Appendf(nil, …); b.Loop is converted by hand.
    • New entries: CutLast, embedlit and unsafefuncs rows; generic methods; encoding/json/v2 for new code, with v1 kept in existing code; the standard-library uuid; closing an HTTP response body without draining it; url.Values.Clone; omitzero instead of omitempty on scalar and pointer fields.
  • go-errors: %w of a pointer to a value-receiver error type, and go fix converting errors.As.
  • go-concurrency: the goroutineleak profile as plain guidance, with what it misses and its cost; goroutine labels now print in crash tracebacks.
  • go-testing: synctest.Sleep, httptest.NewTestServer, go doc -ex, the Config.Rand deprecation, and the b.Loop conversion caveat.
  • go-lint-setup: a modernize disable: entry naming fmtappendf or waitgroup only warns now. Keep formatters on one Go minor, because 1.27's gofmt re-spaces some files.
  • Router and Cursor rule: the version gate, plus JSON, UUIDs and HTTP bodies routed to go-idioms.
  • go-reviewer: reviews on the 1.27 floor. By-hand modernization debt (cmp.Or chains, a body drain) is listed apart from go fix debt. Body 9,965 characters.
  • CI and validator: GO_FLOOR_MINOR is 1.27, and the matrix is 1.27.x, so the Fixer-column check runs strictly there.
  • Docs: AGENTS.md, README.md, docs/install.md (go1.27.3) and docs/authoring.md. The revision record now tracks the release notes and golangci-lint's x/tools snapshot, and step 4 asks for a go fix -diff check before a new Fixer cell.

Review: an independent plugin-dev:skill-reviewer pass checked each changed claim against the downloaded sources, and its findings are applied. One kept item: the b.Loop "seconds into a ten-minute timeout" detail comes from golang/go#74967, which the reviewer wasn't given.

The 0.8.0 version bump follows as its own release commit after merge.

Checklist

  • ./scripts/validate.sh passes (or claude plugin validate . if Python is unavailable): the Fixer column is checked strictly against go1.27.3 (22 cells)
  • claude plugin validate . passes; python3 scripts/validate.py --check-links: 73 of 73 URLs resolve
  • Skill / command / agent triggering tested locally (see docs/testing.md): not yet
  • Cursor install tested locally when manifest, rule, or hook content changed (see docs/install.md): the Cursor rule changed; not yet tested in Cursor
  • Both manifests kept in sync when metadata changed: no manifest change
  • Cursor rule files and the .cursor-plugin/plugin.json path map kept in step with the components that exist: no rule added or renamed
  • Version bumped and CHANGELOG.md updated (if component content changed): CHANGELOG under [Unreleased]; 0.8.0 at release
  • Docs synced (AGENTS.md, README.md, hooks/session-start.sh) when components were added or renamed: no component added or renamed

🤖 Generated with Claude Code

sebastian-iancu and others added 5 commits October 27, 2026 10:58
Go 1.27 is the hard floor; a module on an older go directive skips what
is marked newer, which the compiler, go vet (stdversion) and go fix
enforce. One sentence in the router and the Cursor rule says so; no
per-rule 1.26 variants.

Re-read against the Go 1.27 release notes, the 1.27 blog posts
(generic methods, goroutine leak profiles, HTTP/1 connection reuse), the
JSON tutorial and the encoding/json migration notes, with fixer names
taken from go1.27.3's `go tool fix help`:

- go-idioms: Newer in Go 1.27 folded into the tables. errorsastype,
  atomictypes and slicesbackward ship in go fix; waitgroup is
  waitgroupgo; fmtappendf (golang/go#77581) and bloop (#74967) were
  withdrawn, so fmt.Fprintf leads (staticcheck QF1012) and b.Loop is
  converted by hand. CutLast is rewritten by golangci-lint's x/tools
  snapshot, not yet by the toolchain's stringscut. Adds generic methods,
  encoding/json/v2 for new code, std uuid, close-don't-drain HTTP bodies,
  url Clone, and omitzero over omitempty on scalar and pointer fields.
- go-errors: %w on a *E whose E implements error (new vet check).
- go-concurrency: goroutineleak profile as plain guidance, its blind
  spots and cost; goroutine labels now appear in crash tracebacks.
- go-testing: synctest.Sleep, httptest.NewTestServer, go doc -ex,
  Config.Rand deprecation, b.Loop conversion caveat.
- go-lint-setup: renamed or removed modernize analyzers in disable:
  only warn; keep formatters on one Go minor after the gofmt change.
- revive citations move to revive-lint/revive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviews against Go 1.27 and skips what is marked newer under an older
go directive, matching the router. Stale modernization debt keeps go fix
for real fixers and lists by-hand debt separately: cmp.Or chains and a
drain before resp.Body.Close(), which Go 1.27's Transport does itself.
Body 9,965 characters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GO_FLOOR_MINOR is 1.27 and the CI matrix runs 1.27.x, so the go-idioms
Fixer column is checked against go1.27's `go tool fix help`. A next-minor
leg is added once that Go version is released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md, README.md and docs/install.md (go1.27.3) state the floor.
docs/authoring.md records the 2026-10-27 re-read, tracks the release
notes and golangci-lint's x/tools snapshot as mutable sources, and asks
for a go fix -diff check before a new Fixer cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@sebastian-iancu sebastian-iancu left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

The floor move is sound, and the behavior I could run on go1.27.3 matches the new guidance: fixer names, %w of a *E, omitzero on a non-nil *time.Time, uuid.New / NewV7, httptest.NewTestServer, synctest.Sleep, url.Values.Clone versus maps.Clone, and the generic-method syntax. Three instruction bugs should be fixed before merge. None of them is a wrong API.

Important

1. The b.Loop withdrawal is tied to a timeout Go 1.24.2 already fixed. skills/go-testing/SKILL.md lines 40–43 (and the same story on the go-idioms row) say the automatic rewrite was withdrawn after it turned a benchmark that pauses the timer from seconds into a ten-minute timeout (golang/go#74967).

That timeout is the opening report. The thread then says it was the runtime bug in golang/go#72922, fixed in Go 1.24.2. The analyzer was disabled in December 2025 for two other reasons: a rewrite that leaves the timer stopped, and nanosecond-scale benchmarks skewing by as much as +65%. On go1.27.3 the stopped-timer case fails immediately (testing/benchmark.go: B.Loop called with timer stopped), rather than hanging. Keep "convert by hand, restructure a loop that pauses the timer, re-check the run time." Name that fatal and the skew.

2. The † legend says no row needs †. The CutLast row is that case. skills/go-idioms/SKILL.md lines 30–31 define † as a rewrite that exists only in the newer x/tools suite, then say no row needs it. Line 44 says stringscut rewrites CutLast through golangci-lint for now, and the 1.27 toolchain rewrites Index only.

The cell matches the toolchain. go tool fix help stringscut on go1.27.3 documents Index, IndexByte, and Split / SplitN(...)[0], not LastIndex. A † on this row would also fail scripts/validate.py, because stringscut is registered and † means the backticked name must be absent. docs/authoring.md already says a registered name can still lack a rewrite. Say that in the legend, and keep the narrower rewrite in the cell. "Rewrites Index only" is the right contrast with CutLast and the wrong inventory of stringscut.

3. The version-gate sentence gives the compiler work that stdversion does, and go-reviewer does not carry the sentence. skills/go-coding/SKILL.md lines 8–12 and rules/go-context.mdc lines 9–12 say the compiler, go vet's stdversion check, and go fix all follow the go directive. CHANGELOG.md says go-reviewer says the same thing.

Checked on go1.27.3, module line go 1.26:

  • go build of strings.CutLast succeeded. go vet reported strings.CutLast requires go1.27 or later (module is go1.26).
  • The generic method func (l List[E]) Map[R any](f func(E) R) List[R] failed the compiler: generic method requires go1.27 or later.

The compiler enforces language changes. stdversion (which go test runs) enforces standard-library symbols. agents/go-reviewer.md lines 25–26 only say "under an older go directive, skip what is marked newer." It never says what the mark is (Since cell or (1.NN) note) and never names the tools. The body is 9,962 characters, so the full sentence needs a cut elsewhere.

go-reviewer shape

The description is unchanged and still matches the agent pattern: "Use this agent when," three prose triggers, a pointer to "When to invoke," the not-for clause, model: inherit, color: cyan, and a tools: allowlist with no Write or Edit. The new modernization split is in the right dimension: go fix for real fixers, and by hand for cmp.Or chains and a drain before resp.Body.Close(). The opening version clause is the part that got compressed past the point where the agent can apply it.

Suggestions

  • url.Values.Clone is in neither the go-idioms description nor the router load row. Closing an HTTP body without draining it is on the router row only. Both are no-fixer forms, so go fix will not surface them.
  • docs/install.md uses the same "the compiler, go vet and go fix" wording as the router, without naming stdversion.
  • skills/go-lint-setup/SKILL.md cites the modernize-disable handling at blob/main. The authoring record pins revisions; this URL does not.
  • The go-idioms description's "or which fixer owns a rewrite" dropped the verb. "or asks which fixer owns a rewrite" restores it. The description is 72 words, inside the 50–75 guide.
  • scripts/hooks-test.sh changing the fixture to go 1.27.0 does not pin the floor. Session start only checks that go.mod exists.

What holds up

  • go tool fix help on go1.27.3 registers errorsastype, atomictypes, slicesbackward, embedlit, unsafefuncs, waitgroupgo, stringscut, and omitzero. It does not register fmtappendf, bloop, or waitgroup.
  • go vet reports %w of a *E whose E has a value-receiver Error, and does not report a pointer-receiver error. omitzero drops a non-nil *time.Time at the zero time; omitempty keeps it. v2 marshals a nil slice and map as [] and {}.
  • uuid.New is version 4, uuid.NewV7 is version 7, and url.Values.Clone copies each slice while maps.Clone shares it.
  • The floor text matches across AGENTS.md, README.md, and docs/install.md. No skill, agent, or rule still says 1.26.4 or "flagged as hints." The revive URLs all point at revive-lint/revive.
  • GO_FLOOR_MINOR and the CI matrix are both 1.27. That pins fixer names. It cannot see that a registered fixer skips a rewrite, which is the CutLast case, and authoring step 4 already says to run go fix -diff for that.

Before merge

  1. Name the real b.Loop failure: the timer-stopped fatal, and timing skew. Drop the ten-minute timeout as the reason the rewrite was withdrawn.
  2. Make the † legend agree with the CutLast cell.
  3. Split the version gate into language changes (the compiler) and standard-library symbols (stdversion), and give go-reviewer the same marks the router uses.

@sebastian-iancu

Copy link
Copy Markdown
Contributor Author

Addressed in e141814 and a31a435. Each point was checked on go1.27.3 first, and all three must-fix items held.

1. b.Loop reason. Fixed in go-testing. #74967 confirms it: the ten-minute timeout was #72922, fixed in 1.24.2. The analyzer was disabled for two other reasons:

  • a converted loop that pauses the timer fails with B.Loop called with timer stopped (testing/benchmark.go);
  • b.Loop's overhead skewed nanosecond-scale benchmarks by up to 65%.

The text now names both, and keeps "restructure a loop that pauses the timer, compare results and run time".

2. † legend vs the CutLast cell. Fixed. The legend now says a fixer the toolchain registers can still lack a rewrite x/tools already has, and that the cell says so in words, pointing at the CutLast row. The cell reads "the 1.27 toolchain's version does not rewrite LastIndex yet" instead of "rewrites Index only". go tool fix help stringscut also covers Contains, so "Index only" was the wrong inventory.

3. Version gate. Fixed. Reproduced under go 1.26: strings.CutLast builds and only go vet reports it, while the generic method fails go build with generic method requires go1.27 or later. The sentence now says the compiler rejects a newer language feature, go vet's stdversion check (run by go test) reports a newer standard-library symbol, and go fix offers only rewrites the directive allows. It changed in:

  • the router, the Cursor rule (still word for word) and the go-idioms intro;
  • AGENTS.md, docs/install.md and the changelog;
  • go-reviewer, which now carries the marks (a Since cell or a (1.NN) note) and both tools. To fit, I cut the skill list its loaded router table already carries, an example list, and one rationale clause. Body: 9,963 characters.

Suggestions:

  • The go-idioms description restores "asks" and names url.Values.Clone and an undrained HTTP body (74 words). The router and Cursor rule load rows add a url.Values copy.
  • docs/install.md now uses the split wording.
  • go-lint-setup cites golangci-lint at commit 3ad7cb0, the last change to modernize.go (identical on main and v2.15.0). That pins the revision without naming a release in a component.
  • hooks-test.sh fixture: kept. It only keeps the fixture consistent with the floor, and nothing claims it pins anything.

Validator (Fixer column strict on go1.27.3, 22 cells), selftest, hook tests, claude plugin validate, and --check-links (73 of 73) all pass.

sebastian-iancu and others added 2 commits October 27, 2026 11:22
Review on #15, each point checked on go1.27.3:

- go-testing: the bloop rewrite was withdrawn (golang/go#74967) because
  a converted loop that pauses the timer fails with "B.Loop called with
  timer stopped" and b.Loop's overhead skewed nanosecond-scale benchmarks
  by up to 65%; the ten-minute timeout was a runtime bug fixed in 1.24.2.
- go-idioms: the dagger legend covers a registered fixer that lacks a
  newer rewrite, which the CutLast cell states in words; that cell no
  longer says stringscut rewrites Index only.
- Version gate (router, Cursor rule, go-idioms, AGENTS.md, install doc,
  changelog): the compiler rejects newer language features (generic
  methods under go 1.26 fail to build), go vet's stdversion newer
  standard-library symbols (strings.CutLast builds, vet reports it), and
  go fix offers only rewrites the directive allows.
- go-idioms description restores "asks" and names url.Values.Clone and
  an undrained HTTP body; the router and rule load rows add url.Values.
- go-lint-setup cites golangci-lint at a pinned commit, not a branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Names the marks (a Since cell or a (1.NN) note) and what enforces them:
the compiler for language features, go vet's stdversion for
standard-library symbols. Trimmed the skill list the router's table
already carries, an example list and a rationale clause to stay under
10,000 characters (9,963).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sebastian-iancu
sebastian-iancu merged commit d1cd217 into main Oct 27, 2026
2 checks passed
@sebastian-iancu
sebastian-iancu deleted the feat/go-1.27-floor branch October 27, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant