Go Script Developer
Production-ready Go. Modules layout + flag/cobra + log/slog + error wrapping with %w + context-aware cancellation + signal.NotifyContext + typed exit errors + errgroup concurrency + cross-compile single binaries.
When to use
- The user asks for any
.go program, Go CLI, automation, single-binary tool, or batch processor.
- The user wants to harden, refactor, or review an existing Go program.
- A task chain ends in "and put it in a Go program".
Skip this skill for: web servers (different shape — frameworks like net/http + chi), full applications with persistent storage (database-backed apps need their own architecture), or non-Go scripts (use the matching language skill).
Required structure
Every program you write starts from this skeleton. Do not omit the typed exit error, signal.NotifyContext, or the separation between main and run(ctx, args).
// Package main implements <one-line description>.
//
// Usage: my-tool [options] <input>
// Exit: 0 ok, 1 generic, 2 usage, 3 input, 130 interrupt
package main
import (
"context"
"errors"
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
)
const (
exitOK = 0; exitGeneric = 1; exitUsage = 2; exitInput = 3
exitInterrupt = 130
)
type exitError struct{ code int; err error }
func (e *exitError) Error() string { return e.err.Error() }
func (e *exitError) Unwrap() error { return e.err }
type args struct {
input string
verbose bool
}
func parseArgs() (args, error) {
fs := flag.NewFlagSet("my-tool", flag.ContinueOnError)
fs.SetOutput(os.Stderr)
var a args
fs.BoolVar(&a.verbose, "v", false, "enable debug logging")
if err := fs.Parse(os.Args[1:]); err != nil { return a, err }
if fs.NArg() != 1 {
return a, &exitError{code: exitUsage, err: fmt.Errorf("expected one positional argument")}
}
a.input = fs.Arg(0)
return a, nil
}
func run(ctx context.Context, a args) error {
slog.Info("processing", "input", a.input)
// ... real work, respecting ctx
return nil
}
func main() {
a, err := parseArgs()
if err != nil {
if errors.Is(err, flag.ErrHelp) { os.Exit(exitOK) }
var ee *exitError
if errors.As(err, &ee) { fmt.Fprintln(os.Stderr, "error:", err); os.Exit(ee.code) }
fmt.Fprintln(os.Stderr, "error:", err); os.Exit(exitUsage)
}
level := slog.LevelInfo
if a.verbose { level = slog.LevelDebug }
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})))
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := run(ctx, a); err != nil {
var ee *exitError
if errors.As(err, &ee) { slog.Error(ee.Error()); os.Exit(ee.code) }
if errors.Is(err, context.Canceled) { slog.Info("interrupted"); os.Exit(exitInterrupt) }
slog.Error("unexpected", "err", err); os.Exit(exitGeneric)
}
}
Workflow
- Confirm the Go version. Default floor: Go 1.22. Bump to 1.23 if you need iterators or
range over func. Set the floor in go.mod's go directive.
- Generate / verify
go.mod using assets/templates/go.mod.template. Module path is github.com/<owner>/<repo> by default.
- Pick a starting template from
assets/templates/:
main.template.go — single-file program.
cli-tool.template.go — multi-subcommand CLI, dispatch by hand (no third-party dep).
file-processor.template.go — batch processing with errgroup + bounded concurrency.
- Apply patterns from
references/patterns.md for module layout, flag parsing, slog, error wrapping with %w, sentinel + typed errors, distinct exit codes, context cancellation, signal.NotifyContext, errgroup concurrency, //go:embed, atomic writes, env-var configs.
- Validate the result. Run
bash scripts/validate-script.sh <your-main.go> — checks package doc, log/slog, %w wrapping, errors.Is/errors.As, signal.NotifyContext, context-as-parameter, exit-code constants, path/filepath, no ioutil, no panic, flag-based argv, main+run separation, stderr logging, no cgo. Aim for ≥ 90%.
- Type-check + vet. Run
go vet ./... and go build ./... after writing. Add golangci-lint run for production code.
- Cross-check
references/anti-patterns.md — especially panic for non-programmer errors, ignoring errors with _, fmt.Errorf("...: %s", err) instead of %w, missing defer cancel(), goroutine leaks, path vs path/filepath.
- For cross-platform programs, load
references/cross-platform.md and apply the rules (build constraints, runtime.GOOS, os.UserHomeDir, signal differences).
- For shipping, load
references/build-and-distribution.md — cross-compile matrix, goreleaser, embedded version info, multi-stage Dockerfiles, static binaries.
- Generate man-page-style docs using
references/documentation.md — GoDoc on every export, plus a README.md reference.
Available resources
assets/templates/main.template.go — single-file program with full main+run split.
assets/templates/cli-tool.template.go — multi-subcommand dispatch (no third-party dep).
assets/templates/file-processor.template.go — batch processing with errgroup.
assets/templates/go.mod.template — module file with go 1.23 and the minimal dep set.
assets/examples/csv-analyzer/main.go — full reference implementation (CSV streaming, gzip, custom error types, Welford running stats, JSON/text output).
scripts/validate-script.sh — score a Go program against the checklist.
references/patterns.md — load when implementing flag parsing, slog, error wrapping, sentinel errors, exit codes, context, signals, errgroup, embed, atomic writes.
references/anti-patterns.md — load when reviewing or rewriting an existing Go program.
references/cross-platform.md — load when targeting Windows or macOS alongside Linux, or cross-compiling.
references/build-and-distribution.md — load when shipping (cross-compile, goreleaser, container images, static binaries).
references/documentation.md — load when generating man-page-style README + GoDoc.
Top gotchas (always inline — do not skip)
- Wrap errors with
%w, never %s or %v. fmt.Errorf("read %q: %w", path, err). The %w keeps the chain unwrappable. errors.Is / errors.As only work on chains.
signal.NotifyContext is the right shutdown primitive. Don't roll your own signal.Notify channel. The context-based version cancels every ctx-aware operation in your program for free.
context.Context as the first parameter, never on a struct field. Functions take ctx; structs hold long-lived state. Holding a context on a struct is a Go-vet warning and a sign of a design problem.
path/filepath for filesystem paths, path for URL/embed paths. Different packages. filepath.Join uses \\ on Windows.
defer cancel() after every context.WithCancel / WithTimeout. go vet flags missing ones. Without it, the parent context leaks resources.
- Define typed errors with
Unwrap(). Subclass with a struct that holds an exit code; in main, use errors.As to extract the code. Sentinel errors (var ErrNotFound = errors.New("not found")) for shape-less cases.
slog, not log. Stdlib has structured logging since 1.21. The plain log is for tiny scripts and stdlib internals.
- Goroutine leaks are bugs. Every long-running goroutine respects
ctx.Done(). No exceptions.
errgroup for bounded concurrency, not raw sync.WaitGroup. errgroup.WithContext cancels siblings on first error; g.SetLimit(N) caps concurrency.
//go:embed files are paths relative to the source file, with forward slashes. They don't follow OS conventions.
CGO_ENABLED=0 for portable static binaries. Avoid cgo unless you really need it; it complicates cross-compile, increases binary size, and adds C-toolchain dependencies.
go.mod's go directive sets the language version. Features like range over func (1.23) need the right version there. Don't assume — check.
What you DO
- Start every program from
assets/templates/.
- Use
flag.NewFlagSet (not flag.Parse directly on the global). Easier to test and reset.
- Use
log/slog for structured logging. Set the level from --verbose.
- Wrap errors with
%w at every layer boundary.
- Define typed
*exitError with Unwrap and a code int field. Map distinct failures to distinct codes.
- Take
context.Context as the first parameter on any function that does I/O or could block.
- Use
signal.NotifyContext to wire SIGINT/SIGTERM into the context.
- Use
errgroup for parallel work over a slice; cap with SetLimit(N).
- Use
path/filepath for filesystem paths; path only for URL/embed-style.
- Use
os.UserHomeDir, os.UserCacheDir, os.TempDir instead of hardcoding paths.
- Run
go vet ./... and go build ./... against every change.
- Pin the Go version floor in
go.mod's go directive.
What you do NOT do
- Use
panic for non-programmer errors. Return error and let main decide.
- Use
_ to ignore errors. Handle them or document why with a comment.
- Use
fmt.Errorf("...: %s", err) to wrap — drops the chain.
- Use
path.Join for filesystem paths — breaks on Windows.
- Use
init() for fallible setup. Be explicit and call from main.
- Use
interface{}. Use any (since Go 1.18).
- Use
ioutil (deprecated since 1.16). Use os.ReadFile / os.WriteFile / io.ReadAll.
- Skip
defer cancel() after context.WithTimeout. go vet flags this.
- Use
time.Sleep to wait for async work in tests. Use channels or eventually patterns.
- Embed
context.Context on struct fields. Pass as the first parameter.
- Use
os.Exit from inside any function except main. Skips defer.
- Add cgo dependencies casually. They wreck cross-compile and add C toolchain requirements.
Source: MKAbuMattar/skills — distributed by TomeVault.
1---2name: golang-script-developer3description: Write production-ready Go CLI tools, automation programs, and batch file processors with idiomatic Go patterns — Go modules layout, the standard library flag package by default (or cobra/urfave-cli for complex CLIs), structured logging via log/slog (Go 1.21+), error wrapping with fmt.Errorf %w plus errors.Is/errors.As, context.Context for cancellation and timeouts, signal.NotifyContext for graceful SIGINT/SIGTERM, distinct exit codes per failure mode, embedded assets via //go:embed, race-detector-clean concurrency with errgroup, and cross-platform / cross-compile support (GOOS/GOARCH) for Linux, macOS, Windows. Targets Go 1.22 and above. Use this skill whenever the user asks to create a Go / Golang script, .go program, single-binary CLI tool, automation, batch processor, or data pipeline — including casual phrasings like 'write a go program that ...', 'automate this in golang', 'make me a go CLI', or 'I need a single-binary tool'. Also use when reviewing or hardening an existing Go program. Use when this capa4---56# Go Script Developer78Production-ready Go. Modules layout + flag/cobra + log/slog + error wrapping with %w + context-aware cancellation + signal.NotifyContext + typed exit errors + errgroup concurrency + cross-compile single binaries.910## When to use1112- The user asks for any `.go` program, Go CLI, automation, single-binary tool, or batch processor.13- The user wants to harden, refactor, or review an existing Go program.14- A task chain ends in "and put it in a Go program".1516Skip this skill for: web servers (different shape — frameworks like `net/http` + `chi`), full applications with persistent storage (database-backed apps need their own architecture), or non-Go scripts (use the matching language skill).1718## Required structure1920Every program you write starts from this skeleton. Do not omit the typed exit error, `signal.NotifyContext`, or the separation between `main` and `run(ctx, args)`.2122```go23// Package main implements <one-line description>.24//25// Usage: my-tool [options] <input>26// Exit: 0 ok, 1 generic, 2 usage, 3 input, 130 interrupt27package main2829import (30 "context"31 "errors"32 "flag"33 "fmt"34 "log/slog"35 "os"36 "os/signal"37 "syscall"38)3940const (41 exitOK = 0; exitGeneric = 1; exitUsage = 2; exitInput = 342 exitInterrupt = 13043)4445type exitError struct{ code int; err error }46func (e *exitError) Error() string { return e.err.Error() }47func (e *exitError) Unwrap() error { return e.err }4849type args struct {50 input string51 verbose bool52}5354func parseArgs() (args, error) {55 fs := flag.NewFlagSet("my-tool", flag.ContinueOnError)56 fs.SetOutput(os.Stderr)57 var a args58 fs.BoolVar(&a.verbose, "v", false, "enable debug logging")59 if err := fs.Parse(os.Args[1:]); err != nil { return a, err }60 if fs.NArg() != 1 {61 return a, &exitError{code: exitUsage, err: fmt.Errorf("expected one positional argument")}62 }63 a.input = fs.Arg(0)64 return a, nil65}6667func run(ctx context.Context, a args) error {68 slog.Info("processing", "input", a.input)69 // ... real work, respecting ctx70 return nil71}7273func main() {74 a, err := parseArgs()75 if err != nil {76 if errors.Is(err, flag.ErrHelp) { os.Exit(exitOK) }77 var ee *exitError78 if errors.As(err, &ee) { fmt.Fprintln(os.Stderr, "error:", err); os.Exit(ee.code) }79 fmt.Fprintln(os.Stderr, "error:", err); os.Exit(exitUsage)80 }8182 level := slog.LevelInfo83 if a.verbose { level = slog.LevelDebug }84 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: level})))8586 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)87 defer stop()8889 if err := run(ctx, a); err != nil {90 var ee *exitError91 if errors.As(err, &ee) { slog.Error(ee.Error()); os.Exit(ee.code) }92 if errors.Is(err, context.Canceled) { slog.Info("interrupted"); os.Exit(exitInterrupt) }93 slog.Error("unexpected", "err", err); os.Exit(exitGeneric)94 }95}96```9798## Workflow991001. **Confirm the Go version.** Default floor: Go 1.22. Bump to 1.23 if you need iterators or `range over func`. Set the floor in `go.mod`'s `go` directive.1012. **Generate / verify `go.mod`** using `assets/templates/go.mod.template`. Module path is `github.com/<owner>/<repo>` by default.1023. **Pick a starting template** from `assets/templates/`:103 - `main.template.go` — single-file program.104 - `cli-tool.template.go` — multi-subcommand CLI, dispatch by hand (no third-party dep).105 - `file-processor.template.go` — batch processing with `errgroup` + bounded concurrency.1064. **Apply patterns** from `references/patterns.md` for module layout, flag parsing, slog, error wrapping with `%w`, sentinel + typed errors, distinct exit codes, context cancellation, `signal.NotifyContext`, errgroup concurrency, `//go:embed`, atomic writes, env-var configs.1075. **Validate the result.** Run `bash scripts/validate-script.sh <your-main.go>` — checks package doc, log/slog, `%w` wrapping, `errors.Is`/`errors.As`, `signal.NotifyContext`, context-as-parameter, exit-code constants, `path/filepath`, no `ioutil`, no `panic`, flag-based argv, `main`+`run` separation, stderr logging, no cgo. Aim for ≥ 90%.1086. **Type-check + vet.** Run `go vet ./...` and `go build ./...` after writing. Add `golangci-lint run` for production code.1097. **Cross-check `references/anti-patterns.md`** — especially `panic` for non-programmer errors, ignoring errors with `_`, `fmt.Errorf("...: %s", err)` instead of `%w`, missing `defer cancel()`, goroutine leaks, `path` vs `path/filepath`.1108. **For cross-platform programs**, load `references/cross-platform.md` and apply the rules (build constraints, `runtime.GOOS`, `os.UserHomeDir`, signal differences).1119. **For shipping**, load `references/build-and-distribution.md` — cross-compile matrix, `goreleaser`, embedded version info, multi-stage Dockerfiles, static binaries.11210. **Generate man-page-style docs** using `references/documentation.md` — GoDoc on every export, plus a `README.md` reference.113114## Available resources115116- `assets/templates/main.template.go` — single-file program with full main+run split.117- `assets/templates/cli-tool.template.go` — multi-subcommand dispatch (no third-party dep).118- `assets/templates/file-processor.template.go` — batch processing with `errgroup`.119- `assets/templates/go.mod.template` — module file with `go 1.23` and the minimal dep set.120- `assets/examples/csv-analyzer/main.go` — full reference implementation (CSV streaming, gzip, custom error types, Welford running stats, JSON/text output).121- `scripts/validate-script.sh` — score a Go program against the checklist.122- `references/patterns.md` — load when implementing flag parsing, slog, error wrapping, sentinel errors, exit codes, context, signals, errgroup, embed, atomic writes.123- `references/anti-patterns.md` — load when reviewing or rewriting an existing Go program.124- `references/cross-platform.md` — load when targeting Windows or macOS alongside Linux, or cross-compiling.125- `references/build-and-distribution.md` — load when shipping (cross-compile, goreleaser, container images, static binaries).126- `references/documentation.md` — load when generating man-page-style README + GoDoc.127128## Top gotchas (always inline — do not skip)129130- **Wrap errors with `%w`, never `%s` or `%v`.** `fmt.Errorf("read %q: %w", path, err)`. The `%w` keeps the chain unwrappable. `errors.Is` / `errors.As` only work on chains.131- **`signal.NotifyContext` is the right shutdown primitive.** Don't roll your own `signal.Notify` channel. The context-based version cancels every `ctx`-aware operation in your program for free.132- **`context.Context` as the first parameter, never on a struct field.** Functions take `ctx`; structs hold long-lived state. Holding a context on a struct is a Go-vet warning and a sign of a design problem.133- **`path/filepath` for filesystem paths, `path` for URL/embed paths.** Different packages. `filepath.Join` uses `\\` on Windows.134- **`defer cancel()` after every `context.WithCancel` / `WithTimeout`.** `go vet` flags missing ones. Without it, the parent context leaks resources.135- **Define typed errors with `Unwrap()`.** Subclass with a struct that holds an exit code; in `main`, use `errors.As` to extract the code. Sentinel errors (`var ErrNotFound = errors.New("not found")`) for shape-less cases.136- **`slog`, not `log`.** Stdlib has structured logging since 1.21. The plain `log` is for tiny scripts and stdlib internals.137- **Goroutine leaks are bugs.** Every long-running goroutine respects `ctx.Done()`. No exceptions.138- **`errgroup` for bounded concurrency**, not raw `sync.WaitGroup`. `errgroup.WithContext` cancels siblings on first error; `g.SetLimit(N)` caps concurrency.139- **`//go:embed` files are paths relative to the source file**, with forward slashes. They don't follow OS conventions.140- **`CGO_ENABLED=0` for portable static binaries.** Avoid cgo unless you really need it; it complicates cross-compile, increases binary size, and adds C-toolchain dependencies.141- **`go.mod`'s `go` directive sets the language version.** Features like `range over func` (1.23) need the right version there. Don't assume — check.142143## What you DO1441451. Start every program from `assets/templates/`.1462. Use `flag.NewFlagSet` (not `flag.Parse` directly on the global). Easier to test and reset.1473. Use `log/slog` for structured logging. Set the level from `--verbose`.1484. Wrap errors with `%w` at every layer boundary.1495. Define typed `*exitError` with `Unwrap` and a `code int` field. Map distinct failures to distinct codes.1506. Take `context.Context` as the first parameter on any function that does I/O or could block.1517. Use `signal.NotifyContext` to wire SIGINT/SIGTERM into the context.1528. Use `errgroup` for parallel work over a slice; cap with `SetLimit(N)`.1539. Use `path/filepath` for filesystem paths; `path` only for URL/embed-style.15410. Use `os.UserHomeDir`, `os.UserCacheDir`, `os.TempDir` instead of hardcoding paths.15511. Run `go vet ./...` and `go build ./...` against every change.15612. Pin the Go version floor in `go.mod`'s `go` directive.157158## What you do NOT do159160- Use `panic` for non-programmer errors. Return `error` and let `main` decide.161- Use `_` to ignore errors. Handle them or document why with a comment.162- Use `fmt.Errorf("...: %s", err)` to wrap — drops the chain.163- Use `path.Join` for filesystem paths — breaks on Windows.164- Use `init()` for fallible setup. Be explicit and call from `main`.165- Use `interface{}`. Use `any` (since Go 1.18).166- Use `ioutil` (deprecated since 1.16). Use `os.ReadFile` / `os.WriteFile` / `io.ReadAll`.167- Skip `defer cancel()` after `context.WithTimeout`. `go vet` flags this.168- Use `time.Sleep` to wait for async work in tests. Use channels or `eventually` patterns.169- Embed `context.Context` on struct fields. Pass as the first parameter.170- Use `os.Exit` from inside any function except `main`. Skips `defer`.171- Add cgo dependencies casually. They wreck cross-compile and add C toolchain requirements.172173---174> Source: [MKAbuMattar/skills](https://github.com/MKAbuMattar/skills) — distributed by [TomeVault](https://tomevault.io).175<!-- tomevault:4.0:skill_md:2026-05-23 -->