Releasing Marchat
Cod-e-Codes/marchat
Prepares marchat releases: version bumps, CHANGELOG, packaging checksums, GitHub Actions release workflow, and Docker tags.
Modernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations).
$ npx skills add smallnest/goal-workflow --skill modern-go -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install smallnest/goal-workflow modern-go --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/modern-go .claude/skills/modern-go && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .claude/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/smallnest/goal-workflow/tree/master/skills/modern-goType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add smallnest/goal-workflow --skill modern-go -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install smallnest/goal-workflow modern-go --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/modern-go .agents/skills/modern-go && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .agents/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add smallnest/goal-workflow --skill modern-go -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install smallnest/goal-workflow modern-go --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/modern-go .cursor/skills/modern-go && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .cursor/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/smallnest/goal-workflow.git --path skills/modern-go--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add smallnest/goal-workflow --skill modern-go -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install smallnest/goal-workflow modern-go --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/modern-go .gemini/skills/modern-go && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .gemini/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install smallnest/goal-workflow modern-goInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add smallnest/goal-workflow --skill modern-go -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/modern-go .github/skills/modern-go && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .github/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add smallnest/goal-workflow --skill modern-go -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install smallnest/goal-workflow modern-go --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/smallnest/goal-workflow.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/modern-go .opencode/skills/modern-go && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "modern-go" agent skill from https://github.com/smallnest/goal-workflow/tree/master/skills/modern-go into .opencode/skills/modern-go/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "modern-go", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
modern-goModernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations).
Modern Go is an agent skill from smallnest/goal-workflow. Modernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations). Scans go.mod for the Go version, then transforms Go source files to use modern patterns—from Go 1.0 through 1.26+. Use when the user says "现代化","现代Go语言", "地道的", "idiomatic", "modernize", "modern-go", "update Go code", "gofix", or wants to upgrade Go idioms.
Its SKILL.md is about 7.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Development. It works with Go and Linux. The repository describes itself as: AI-driven development workflow with /prd, /goal, /review-it and /ship-it skills. The licence is MIT.
4 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit b06ab3c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
goFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Modern Go loads about 7.2k tokens when it runs. Until then it costs about 91 tokens; SKILL.md has 1,960 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from smallnest/goal-workflow at commit b06ab3c, republished under its MIT licence (© smallnest). 1,960 words, ~7,233 tokens.
.claude/skills/modern-go/SKILL.md (or your agent's skills folder).Modernize Go source code by applying version-appropriate idioms, APIs, and language features. Works like go fix plus additional transformations curated from the Go team's modernize analysis passes and community best practices.
Invoke this skill when the user asks to modernize Go code. By default, modernize the entire project; the user may specify a file or directory instead.
When invoked:
go.mod (the go directive)..go files in the target scope (excluding vendor/, .git/, testdata/).If the user specifies a file or directory, limit the scope to that path.
Each transformation includes a Go version gate—only apply when the project's go.mod version ≥ that version. Never apply a transformation that requires a version higher than the project declares.
time.Since| Before | After |
|---|---|
time.Now().Sub(start) | time.Since(start) |
// before
elapsed := time.Now().Sub(start)
// after
elapsed := time.Since(start)time.Until| Before | After |
|---|---|
deadline.Sub(time.Now()) | time.Until(deadline) |
// before
remaining := deadline.Sub(time.Now())
// after
remaining := time.Until(deadline)strings.Builder (loop concatenation)| Before | After |
|---|---|
s += item in a loop | var b strings.Builder; b.WriteString(item) |
// before
s := ""
for _, item := range items {
s += item
}
// after
var b strings.Builder
for _, item := range items {
b.WriteString(item)
}
s := b.String()Only when += concatenation happens inside a loop.
errors.Is| Before | After |
|---|---|
err == io.EOF | errors.Is(err, io.EOF) |
// before
if err == io.EOF {
return
}
// after
if errors.Is(err, io.EOF) {
return
}//go:build constraints (plusbuild)| Before | After |
|---|---|
// +build linux + //go:build linux (both present) | keep only //go:build linux |
// before
//go:build linux && amd64
// +build linux,amd64
package foo
// after
//go:build linux && amd64
package fooThe plusbuild modernizer removes obsolete // +build constraint lines once the equivalent //go:build line is present (the //go:build syntax landed in Go 1.17). Only strip the old line when a matching //go:build already exists — never drop the sole constraint.
unsafe.Add / unsafe.Slice (unsafefuncs)| Before | After |
|---|---|
unsafe.Pointer(uintptr(ptr) + uintptr(n)) | unsafe.Add(ptr, n) |
(*[n]T)(unsafe.Pointer(p))[:] slice construction | unsafe.Slice(p, n) |
// before — pointer arithmetic via uintptr
p2 := unsafe.Pointer(uintptr(ptr) + uintptr(offset))
// after
p2 := unsafe.Add(ptr, offset)// before — building a slice from a base pointer
s := (*[1 << 30]byte)(unsafe.Pointer(p))[:n:n]
// after
s := unsafe.Slice(p, n)The unsafefuncs modernizer (gopls v0.22.0) rewrites error-prone uintptr pointer math into unsafe.Add / unsafe.Slice, which the compiler and go vet understand as GC-safe.
any| Before | After |
|---|---|
interface{} | any |
// before
func decode(v interface{}) error { ... }
// after
func decode(v any) error { ... }strings.Cut| Before | After |
|---|---|
i := strings.Index(s, sep); ... s[:i], s[i+len(sep):] | key, val, found := strings.Cut(s, sep) |
// before
if i := strings.Index(s, "="); i >= 0 {
key, val := s[:i], s[i+1:]
}
// after
if key, val, found := strings.Cut(s, "="); found {
...
}bytes.Cut| Before | After |
|---|---|
i := bytes.Index(b, sep); ... b[:i], b[i+len(sep):] | before, after, found := bytes.Cut(b, sep) |
// before
if i := bytes.Index(b, sep); i >= 0 {
before, after := b[:i], b[i+len(sep):]
}
// after
before, after, found := bytes.Cut(b, sep)fmt.Appendf| Before | After |
|---|---|
buf = append(buf, fmt.Sprintf(...)...) | buf = fmt.Appendf(buf, ...) |
// before
buf = append(buf, fmt.Sprintf("x=%d", x)...)
// after
buf = fmt.Appendf(buf, "x=%d", x)| Before | After |
|---|---|
atomic.StoreInt32(&v, 1) / atomic.LoadInt32(&v) | var v atomic.Int32; v.Store(1); v.Load() |
atomic.AddInt64(&v, 1) | var v atomic.Int64; v.Add(1) |
atomic.Value + type assertion | atomic.Pointer[T] |
// before
var ready int32
atomic.StoreInt32(&ready, 1)
if atomic.LoadInt32(&ready) == 1 { ... }
// after
var ready atomic.Int32
ready.Store(1)
if ready.Load() == 1 { ... }// before
var cache atomic.Value
cache.Store(&Config{})
cfg := cache.Load().(*Config)
// after
var cache atomic.Pointer[Config]
cache.Store(&Config{})
cfg := cache.Load()The atomictypes modernizer (gopls v0.22.0, AtomicTypesAnalyzer) rewrites both the variable declaration and every call site. Typed wrappers (atomic.Int32/Int64/Uint32/Uint64/Bool/Pointer[T]) have identical performance but prevent accidental non-atomic access and fix 64-bit alignment crashes on 32-bit architectures.
strings.Clone| Before | After |
|---|---|
string([]byte(s)) | strings.Clone(s) |
// before
s2 := string([]byte(s)) // force copy
// after
s2 := strings.Clone(s)bytes.Clone| Before | After |
|---|---|
make([]byte, len(src)); copy(dst, src) | bytes.Clone(src) |
// before
dst := make([]byte, len(src))
copy(dst, src)
// after
dst := bytes.Clone(src)strings.CutPrefix / strings.CutSuffix| Before | After |
|---|---|
if strings.HasPrefix(s, p) { s = s[len(p):] } | if rest, ok := strings.CutPrefix(s, p); ok { s = rest } |
if strings.HasSuffix(s, sf) { s = s[:len(s)-len(sf)] } | if rest, ok := strings.CutSuffix(s, sf); ok { s = rest } |
// before
if strings.HasPrefix(s, "pre_") {
s = s[len("pre_"):]
}
// after
if rest, ok := strings.CutPrefix(s, "pre_"); ok {
s = rest
}// before
if strings.HasSuffix(s, ".txt") {
s = s[:len(s)-len(".txt")]
}
// after
if rest, ok := strings.CutSuffix(s, ".txt"); ok {
s = rest
}errors.Join| Before | After |
|---|---|
fmt.Errorf("...: %w: %w", err1, err2) | errors.Join(err1, err2) |
// before
return fmt.Errorf("load config: %w: %w", err1, err2)
// after
return errors.Join(fmt.Errorf("load config"), err1, err2)context.WithCancelCause| Before | After |
|---|---|
ctx, cancel := context.WithCancel(parent) + bare cancel() | ctx, cancel := context.WithCancelCause(parent) + cancel(err) |
// before
ctx, cancel := context.WithCancel(parent)
// ... somewhere ...
cancel()
// after
ctx, cancel := context.WithCancelCause(parent)
cancel(ErrShutdown)
// caller: context.Cause(ctx) → ErrShutdownmin / max| Before | After |
|---|---|
if a < b { v = a } else { v = b } | v = min(a, b) |
if a > b { v = a } else { v = b } | v = max(a, b) |
if x < lo { x = lo }; if x > hi { x = hi } | x = min(max(x, lo), hi) |
// before
lo := a
if b < lo {
lo = b
}
// after
lo := min(a, b)// before
if x < 0 {
x = 0
}
if x > 100 {
x = 100
}
// after
x = min(max(x, 0), 100)clear| Before | After |
|---|---|
for k := range m { delete(m, k) } | clear(m) |
for i := range s { s[i] = zero } | clear(s) |
// before
for k := range m {
delete(m, k)
}
// after
clear(m)// before
for i := range s {
s[i] = 0
}
// after
clear(s)slices package| Before | After |
|---|---|
| Manual loop to find element | slices.Contains(items, target) |
| Loop returning index or -1 | slices.Index(items, target) |
sort.Slice(items, func(i,j int) bool { return items[i] < items[j] }) | slices.SortFunc(items, cmp.Compare) |
| Max/min finding loop | slices.Max(items) / slices.Min(items) |
| Reverse swap loop | slices.Reverse(s) |
| Remove consecutive duplicates loop | slices.Compact(s) |
s[:len(s):len(s)] | slices.Clip(s) |
make([]T, len(src)); copy(dst, src) | slices.Clone(src) |
// before → after: slices.Contains(items, target)
found := false
for _, x := range items {
if x == target {
found = true
break
}
}// before → after: slices.Index(items, target)
for i, x := range items {
if x == target {
return i
}
}
return -1// before → after: slices.SortFunc(items, cmp.Compare)
sort.Slice(items, func(i, j int) bool { return items[i] < items[j] })// before → after: slices.Max(items) / slices.Min(items)
max := items[0]
for _, v := range items[1:] {
if v > max {
max = v
}
}// before → after: slices.Reverse(s)
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}// before → after: slices.Compact(s)
i := 0
for j := 1; j < len(s); j++ {
if s[j] != s[i] {
i++
s[i] = s[j]
}
}
s = s[:i+1]// before → after: slices.Clip(s)
s = s[:len(s):len(s)]// before → after: slices.Clone(src)
dst := make([]T, len(src))
copy(dst, src)Requires importing "slices" and "cmp" (for SortFunc).
slices.Delete / slices.Insert| Before | After |
|---|---|
append(s[:i], s[i+1:]...) | slices.Delete(s, i, i+1) |
append(s[:i:i], append([]T{x}, s[i:]...)...) | slices.Insert(s, i, x) |
// before — element removal (classic aliasing/leak footgun)
s = append(s[:i], s[i+1:]...)
// after
s = slices.Delete(s, i, i+1)// before — insert at index i
s = append(s[:i], append([]T{v}, s[i:]...)...)
// after
s = slices.Insert(s, i, v)slices.Delete zeroes the tail elements to avoid retaining pointers (the manual append form leaks). Requires importing "slices".
slices.Equal / maps.Equal| Before | After |
|---|---|
reflect.DeepEqual(a, b) for comparable slices | slices.Equal(a, b) |
reflect.DeepEqual(m1, m2) for comparable maps | maps.Equal(m1, m2) |
// before
if reflect.DeepEqual(got, want) { ... } // got, want are []string
// after
if slices.Equal(got, want) { ... }Faster and type-safe, with no reflection. Only for element types that are directly comparable (use slices.EqualFunc / maps.EqualFunc otherwise). Requires importing "slices" or "maps".
maps package| Before | After |
|---|---|
| Manual loop to copy a map | maps.Clone(m) |
for k, v := range src { dst[k] = v } | maps.Copy(dst, src) |
| Loop + conditional delete | maps.DeleteFunc(m, predicate) |
// before → after: maps.Clone(m)
dst := make(map[K]V)
for k, v := range src {
dst[k] = v
}// before → after: maps.Copy(dst, src)
for k, v := range src {
dst[k] = v
}// before → after: maps.DeleteFunc(m, func(k K, v V) bool { return v == 0 })
for k, v := range m {
if v == 0 {
delete(m, k)
}
}Requires importing "maps".
slices.Concat (appendclipped)| Before | After |
|---|---|
append(append([]T(nil), s1...), s2...) | slices.Concat(s1, s2) |
append(slices.Clip(s1), s2...) for a fresh result | slices.Concat(s1, s2) |
// before
all := append(append([]int(nil), a...), b...)
// after
all := slices.Concat(a, b)// before — three-way concat
merged := append(append(append([]string(nil), x...), y...), z...)
// after
merged := slices.Concat(x, y, z)The appendclipped modernizer replaces nested append concatenation of multiple slices with slices.Concat, which allocates a fresh, correctly-sized result. slices.Concat was added in Go 1.22. Requires importing "slices". Only apply when the pattern builds a new slice (starts from []T(nil) or a clipped base) — not when it appends in place to an existing slice.
sync.OnceFunc / sync.OnceValue| Before | After |
|---|---|
var once sync.Once; once.Do(func() { ... }) | f := sync.OnceFunc(func() { ... }); f() |
sync.Once + stored result variable | sync.OnceValue(func() T { return val }) |
// before
var once sync.Once
func init() { once.Do(func() { setup() }) }
// after
var initOnce = sync.OnceFunc(func() { setup() })// before
var once sync.Once
var cfg *Config
func getConfig() *Config {
once.Do(func() { cfg = loadConfig() })
return cfg
}
// after
var getConfig = sync.OnceValue(func() *Config { return loadConfig() })context.AfterFunc| Before | After |
|---|---|
go func() { <-ctx.Done(); cleanup() }() | stop := context.AfterFunc(ctx, cleanup) |
// before
go func() {
<-ctx.Done()
conn.Close()
}()
// after
stop := context.AfterFunc(ctx, func() { conn.Close() })context.WithTimeoutCause / WithDeadlineCause| Before | After |
|---|---|
context.WithTimeout(parent, d) | context.WithTimeoutCause(parent, d, err) |
// before
ctx, cancel := context.WithTimeout(parent, 5*time.Second)
// after
ctx, cancel := context.WithTimeoutCause(parent, 5*time.Second, ErrTimeout)Only apply when a meaningful cause error is available.
| Before | After |
|---|---|
for i := 0; i < n; i++ { ... } | for i := range n { ... } |
for i := 0; i < n; i++ { ... } (i unused) | for range n { ... } |
// before
for i := 0; i < len(items); i++ {
process(i, items[i])
}
// after
for i := range len(items) {
process(i, items[i])
}// before
for i := 0; i < n; i++ {
doWork()
}
// after
for range n {
doWork()
}| Before | After |
|---|---|
for _, x := range items { x := x; ... } | for _, x := range items { ... } |
// before
for _, x := range items {
x := x // capture for goroutine
go func() { use(x) }()
}
// after
for _, x := range items {
go func() { use(x) }()
}The x := x capture idiom is redundant since Go 1.22.
cmp.Or| Before | After |
|---|---|
Chain of if v == "" { v = fallback } | v := cmp.Or(val, fallback1, fallback2, ...) |
// before
name := os.Getenv("NAME")
if name == "" {
name = os.Getenv("USER")
}
if name == "" {
name = "anonymous"
}
// after
name := cmp.Or(os.Getenv("NAME"), os.Getenv("USER"), "anonymous")Requires importing "cmp".
reflect.TypeFor| Before | After |
|---|---|
reflect.TypeOf((*T)(nil)).Elem() | reflect.TypeFor[T]() |
// before
t := reflect.TypeOf((*MyType)(nil)).Elem()
// after
t := reflect.TypeFor[MyType]()http.ServeMux| Before | After |
|---|---|
mux.HandleFunc("/api/", h) + manual path parsing | mux.HandleFunc("GET /api/{id}", h) + r.PathValue("id") |
// before
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/api/")
...
})
// after
mux.HandleFunc("GET /api/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
...
})math/rand/v2| Before | After |
|---|---|
rand.Intn(n) | rand.IntN(n) |
rand.Int63n(n) / rand.Int31n(n) | rand.Int64N(n) / rand.Int32N(n) |
rand.Seed(...) + global funcs | drop Seed; use auto-seeded top-level funcs or rand.N[T] |
// before
import "math/rand"
n := rand.Intn(100)
// after
import "math/rand/v2"
n := rand.IntN(100)// before — type-specific bound
d := time.Duration(rand.Int63n(int64(max)))
// after — generic N works for any integer type
d := rand.N(max)math/rand/v2 (Go 1.22) drops the deprecated global Seed (top-level funcs are auto-seeded) and adds generic rand.N[T]. Semantic migration: the random stream differs from math/rand, so do not apply where reproducibility from a fixed seed matters. Flag as a suggestion, not auto-apply.
| Before | After |
|---|---|
Custom func Walk(yield func(T) bool) callback traversal | func Walk() iter.Seq[T] returning an iterator |
// before — callback-based iteration
func (t *Tree) Each(fn func(v int)) {
for _, v := range t.values {
fn(v)
}
}
// caller: t.Each(func(v int) { use(v) })
// after — standard iterator, usable with range
func (t *Tree) All() iter.Seq[int] {
return func(yield func(int) bool) {
for _, v := range t.values {
if !yield(v) { return }
}
}
}
// caller: for v := range t.All() { use(v) }Adopt the iter.Seq[T] / iter.Seq2[K,V] protocol (Go 1.23) so custom containers compose with range, slices.Collect, maps.Keys, etc. Requires importing "iter". Flag as a suggestion — it reshapes the API surface.
| Before | After |
|---|---|
var keys []K; for k := range m { keys = append(keys, k) } | slices.Collect(maps.Keys(m)) |
var vals []V; for _, v := range m { vals = append(vals, v) } | slices.Collect(maps.Values(m)) |
// before
var keys []string
for k := range m {
keys = append(keys, k)
}
// after
keys := slices.Collect(maps.Keys(m))// before
var vals []int
for _, v := range m {
vals = append(vals, v)
}
// after
vals := slices.Collect(maps.Values(m))Requires importing "slices" and "maps".
strings.SplitSeq / strings.FieldsSeq| Before | After |
|---|---|
for _, part := range strings.Split(s, sep) | for part := range strings.SplitSeq(s, sep) |
for _, field := range strings.Fields(s) | for field := range strings.FieldsSeq(s) |
// before
for _, part := range strings.Split(line, ",") {
process(part)
}
// after
for part := range strings.SplitSeq(line, ",") {
process(part)
}Only when the loop body does not need the index or the full slice.
bytes.SplitSeq / bytes.FieldsSeq| Before | After |
|---|---|
for _, part := range bytes.Split(b, sep) | for part := range bytes.SplitSeq(b, sep) |
// before
for _, part := range bytes.Split(data, sep) {
process(part)
}
// after
for part := range bytes.SplitSeq(data, sep) {
process(part)
}slices.Backward (slicesbackward)| Before | After |
|---|---|
for i := len(s) - 1; i >= 0; i-- { use(s[i]) } | for _, v := range slices.Backward(s) { use(v) } |
| reverse loop needing the index too | for i, v := range slices.Backward(s) { ... } |
// before
for i := len(items) - 1; i >= 0; i-- {
process(items[i])
}
// after
for _, v := range slices.Backward(items) {
process(v)
}// before — index still needed
for i := len(items) - 1; i >= 0; i-- {
fmt.Println(i, items[i])
}
// after
for i, v := range slices.Backward(items) {
fmt.Println(i, v)
}The slicesbackward modernizer (gopls v0.22.0) replaces manual descending-index loops with the slices.Backward iterator. Requires importing "slices". Caveat: the rewrite preserves exact semantics in normal cases, but do not apply it when the loop body mutates the slice length or the index is used for out-of-band arithmetic — those edge cases can become unsound.
t.Context() in tests| Before | After |
|---|---|
ctx, cancel := context.WithCancel(context.Background()); defer cancel() | ctx := t.Context() |
// before
func TestFetch(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
result := fetch(ctx)
}
// after
func TestFetch(t *testing.T) {
result := fetch(t.Context())
}strings.Lines / bytes.Lines| Before | After |
|---|---|
bufio.Scanner line loop over a string/buffer | for line := range strings.Lines(s) |
// before
sc := bufio.NewScanner(strings.NewReader(s))
for sc.Scan() {
process(sc.Text())
}
// after
for line := range strings.Lines(s) {
process(strings.TrimSuffix(line, "\n"))
}strings.Lines / bytes.Lines (Go 1.24) return line iterators — no Scanner setup, no default 64KB token-size limit. Note the yielded line retains its trailing \n, unlike Scanner.Text(); trim it if the old code relied on stripped lines. Only apply for in-memory strings/buffers, not streaming io.Readers.
os.Root (directory-scoped filesystem access)| Before | After |
|---|---|
manual filepath.Clean + prefix check to block traversal | root, _ := os.OpenRoot(dir); root.Open(name) |
// before — hand-rolled path-traversal guard
p := filepath.Join(base, name)
if !strings.HasPrefix(filepath.Clean(p), filepath.Clean(base)+string(os.PathSeparator)) {
return errUnsafePath
}
f, err := os.Open(p)
// after — the OS enforces the boundary
root, err := os.OpenRoot(base)
if err != nil { return err }
defer root.Close()
f, err := root.Open(name) // symlinks/".." escaping base are rejectedos.Root (Go 1.24) confines all operations to a directory tree, rejecting .. and symlink escapes at the syscall layer — far more robust than string prefix checks. Security hardening: flag as a strong suggestion wherever user-controlled paths are joined to a base directory.
omitzero struct tag| Before | After |
|---|---|
json:"field,omitempty" (for time.Time, time.Duration, structs, slices, maps) | json:"field,omitzero" |
// before
type Config struct {
Timeout time.Duration `json:"timeout,omitempty"`
Labels []string `json:"labels,omitempty"`
}
// after
type Config struct {
Timeout time.Duration `json:"timeout,omitzero"`
Labels []string `json:"labels,omitzero"`
}Only for types where omitempty fails: time.Time, time.Duration, structs, slices, maps. Flag as suggestion, not auto-apply.
b.Loop() in benchmarks| Before | After |
|---|---|
for i := 0; i < b.N; i++ { ... } | for b.Loop() { ... } |
// before
func BenchmarkHash(b *testing.B) {
for i := 0; i < b.N; i++ {
hash(input)
}
}
// after
func BenchmarkHash(b *testing.B) {
for b.Loop() {
hash(input)
}
}sync.WaitGroup.Go| Before | After |
|---|---|
wg.Add(1); go func() { defer wg.Done(); fn() }() | wg.Go(fn) |
// before
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item Item) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait()
// after
var wg sync.WaitGroup
for _, item := range items {
wg.Go(func() { process(item) })
}
wg.Wait()new with expressions| Before | After |
|---|---|
v := val; &v | new(val) |
Helper func ptr[T any](v T) *T { return &v } | new(val) directly |
// before
timeout := 30
debug := true
cfg := Config{
Timeout: &timeout,
Debug: &debug,
}
// after
cfg := Config{
Timeout: new(30),
Debug: new(true),
}// before
func ptr[T any](v T) *T { return &v }
cfg := Config{Count: ptr(10)}
// after
cfg := Config{Count: new(10)}errors.AsType| Before | After |
|---|---|
var t *T; errors.As(err, &t) | t, ok := errors.AsType[*T](err) |
// before
var pathErr *os.PathError
if errors.As(err, &pathErr) {
log.Println(pathErr.Path)
}
// after
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
log.Println(pathErr.Path)
}| Before | After |
|---|---|
T{U: U{x: 1}} (redundant embedded-type specifier) | T{x: 1} |
type Base struct {
ID int
Name string
}
type User struct {
Base
Age int
}
// before
u := User{
Base: Base{ID: 1, Name: "alice"},
Age: 30,
}
// after
u := User{
ID: 1,
Name: "alice",
Age: 30,
}The embedlit modernizer (gopls v0.22.0, EmbedLitAnalyzer) strips redundant embedded-struct field-type specifiers from composite literals. Go 1.27 lets you initialize promoted fields directly without the nested literal. Only apply when the promoted field names don't collide with the outer struct's own fields.
Read go.mod to extract the Go version (go 1.xx line). If no go.mod is found, default to go 1.21.
Find all .go files in the target scope (project root, or user-specified file/directory). Exclude vendor/, .git/, and testdata/ directories.
For each .go file, apply all transformations for versions ≤ the detected Go version. Process files sequentially. For each file:
goimports -w (or gofmt -w) on the file after all edits.Never apply a transformation that requires a version higher than the project's Go version.
Print a summary table showing:
## Modernization Summary
| File | Transformations |
|---|---|
| main.go | any, strings.Cut, min/max (2 occurrences) |
| pkg/handler.go | range over int (3), slices.Contains, t.Context() |
| pkg/util.go | new(expr) (1), errors.AsType → errors.Is |
**3 files modified, 10 transformations applied**
Skipped (requires higher Go version):
- new(expr): requires go 1.26 (project is go 1.24)
- WaitGroup.Go: requires go 1.25 (project is go 1.24)omitzero blindly—it changes JSON serialization behavior; flag it as a suggestion instead.math/rand/v2 (changes the random stream), iter.Seq iterators (reshapes the API), os.Root (behavior/error-path change). Point them out and let the user opt in.bufio.Scanner loop with strings.Lines/bytes.Lines, remember the yielded line keeps its trailing \n; add a TrimSuffix if the old code relied on Scanner.Text() semantics.strings.SplitSeq or bytes.SplitSeq when the loop body references the index or the full slice elsewhere.strings.Builder if the concatenation happens outside a loop (single += is fine).goimports -w on each modified file to clean up imports.goimports is not available, fall back to gofmt -w.go.mod, ask the user for the target Go version before proceeding.Many of these transformations are now shipped as official modernizers in gopls/go fix. On Go 1.27+ the whole set can be applied across a module with:
go fix ./...gopls v0.22.0 added four notable passes covered above:
| Modernizer | Min Go | Transformation |
|---|---|---|
unsafefuncs | 1.17 | uintptr pointer math → unsafe.Add / unsafe.Slice |
atomictypes | 1.19 | primitive atomic.* funcs → typed atomic.Int32/Pointer[T] wrappers |
slicesbackward | 1.23 | descending-index loops → slices.Backward iterator |
embedlit | 1.27 | redundant embedded-field literals → promoted-field init |
Other modernizers in the same suite that this catalog covers: minmax, efaceany, fmtappendf, stringscut, stringsseq, sortslice/slicescontains, mapsloop, stditerators, forvar, rangeint, testingcontext, bloop, waitgroup, newexpr, errorsastype, appendclipped (→ slices.Concat), and plusbuild (→ drop obsolete // +build).
To disable an over-eager pass (e.g. slicesbackward rewriting loops that mutate the slice), scope the run with -fixes or exclude that analyzer in your editor's gopls settings.
© smallnest, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/modern-go of smallnest/goal-workflow.
Open the folder on GitHubat commit b06ab3c
We found 1 copy of this SKILL.md (exact, near-identical or edited) in other folders. This page covers the copy in smallnest/goal-workflow, which our catalogue first saw on October 7, 2026.
Modern Go next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Modern Go this skillsmallnest/goal-workflow | 290 | — | ~7.2k | Automated safety check: Pass | MIT | |
| Releasing MarchatCod-e-Codes/marchat | 137 | — | ~801 | Automated safety check: Pass | MIT | |
| Temporal Developerlatitude-dev/latitude-llm | 4.7k | — | ~1.5k | Automated safety check: Pass | MIT | |
| Install Mimi Remotegaixianggeng/mimi-remote | 104 | — | ~2.8k | Automated safety check: Pass | GPL-3.0 | |
| Add Redis Command to go-redisredis/go-redis | 22k | — | ~1.4k | Automated safety check: Pass | BSD-2-Clause | |
| Go Pedantrychromedp/chromedp | 13k | — | ~3.7k | Automated safety check: Pass | MIT |
Cod-e-Codes/marchat
Prepares marchat releases: version bumps, CHANGELOG, packaging checksums, GitHub Actions release workflow, and Docker tags.
latitude-dev/latitude-llm
This skill should be used when the user asks to "create a Temporal workflow", "write a Temporal activity", "debug stuck workflow", "fix non-determinism error", "Temporal Python", "Temporal…
gaixianggeng/mimi-remote
安装、配置、配对、迁移、升级、诊断、回滚或卸载 Mimi Remote;在 macOS 上安装和维护 Mimi Remote Mac 菜单栏 App / DMG,或通过 Homebrew、Linux user-systemd 部署 agentd;从源码构建 iPhone/iPad App;配置 Codex 主通道和可选 Claude Code 实验 Runtime。用户提出“安装 Mimi…
redis/go-redis
Walks through adding a new Redis or Redis module command to the go-redis client, from fetching the spec and docs to the type, interface wiring, parsing and tests.
chromedp/chromedp
This skill should be used when the user is writing Go code and needs guidance on Go-specific pedantry: error wrapping with fmt.Errorf and %w, interface design (accept interfaces return structs)…
dotnet/core
Audits and updates os-packages.json files listing the Linux packages each .NET release needs per distro, then regenerates the Markdown from the JSON.
smallnest/goal-workflow
Illustrate an article (Markdown, HTML, etc.) with animated-style icons from itshover.com/icons.
smallnest/goal-workflow
Graph engineering for parallel task execution: convert a task, PRD, SPEC, or issue set into a dependency graph (DAG), layer it into supersteps, then implement each independent node concurrently with…
smallnest/goal-workflow
Generate a Phase-2 Walkthrough artifact (walkthrough.md) once implementation and verification are complete.
smallnest/goal-workflow
为任意项目生成 UML 图、架构图和流程图。分析代码库后让用户选择要生成的图表类型,使用 architecture-diagram skill 渲染为 HTML+SVG,保存到 docs/ 目录。适用于任何软件项目的文档可视化。
smallnest/goal-workflow
Reverse-engineer a SPEC document from an existing project. An agent skill from smallnest/goal-workflow.
smallnest/goal-workflow
A skill your agent uses when turning a requirement, spec, or feature brief into a single self-contained HTML design document in a fixed house style — one styled HTML page with a table-of-contents…
Categories
Modernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations). Modern Go is an agent skill from smallnest/goal-workflow. Modernize Go code by applying version-appropriate idioms and APIs (gofix-style transformations).
Modern Go fits situations like: the user says 现代化; wants to upgrade Go idioms.
Run `npx skills add smallnest/goal-workflow --skill modern-go -a claude-code`. Or copy the skill folder (skills/modern-go in smallnest/goal-workflow) into .claude/skills/modern-go in your project. Claude Code loads it when a task matches its description.
Run `npx skills add smallnest/goal-workflow --skill modern-go -a codex`. Or copy the skill folder (skills/modern-go in smallnest/goal-workflow) into .agents/skills/modern-go in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add smallnest/goal-workflow --skill modern-go -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/modern-go, .gemini/skills/modern-go, .github/skills/modern-go and .opencode/skills/modern-go in your project.
Going by SKILL.md and its folder, Modern Go needs the command-line tools its instructions call (go).
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Modern Go is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.2k tokens (SKILL.md is roughly 29k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Modern Go: Releasing Marchat (Cod-e-Codes/marchat, 137 stars), Temporal Developer (latitude-dev/latitude-llm, 4.7k stars), Install Mimi Remote (gaixianggeng/mimi-remote, 104 stars) and Add Redis Command to go-redis (redis/go-redis, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
smallnest (a GitHub user) maintains it in smallnest/goal-workflow, which has 290 GitHub stars. The repository holds 20 skills in this directory. The repository was last updated on September 13, 2026.
Source: smallnest/goal-workflow on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.