tenequm/go-dev

Configuração opinativa de Go com golangci-lint v2, gofumpt, gotestsum, golang-migrate e just. Use ao iniciar um projeto Go, configurar lint, formatação, teste, cobertura ou CI, escrever um Justfile, criar migrações ou abandonar um workflow com Makefile.

O que é go-dev?

Esta skill fornece uma configuração Go moderna e opinativa, consolidando ferramentas como golangci-lint v2, gofumpt, gotestsum e just em um único stack coeso. Ela é ideal para desenvolvedores que desejam iniciar um novo projeto Go com linting, formatação rigorosa, testes com saída legível e automação de tarefas via Justfile, substituindo workflows baseados em Makefile. A skill suporta integração com golang-migrate para migrações de banco de dados e lefthook para hooks Git, priorizando simplicidade e reprodutibilidade. Inclui modelos de configuração detalhados para .golangci.yml, Justfile e lefthook.yml, com explicações sobre como evitar armadilhas comuns, como a instalação correta do golangci-lint (via binário, não como ferramenta Go) e o gerenciamento de versões mínimas (floors) para compatibilidade.

Funciona com✓Claude Code✓Codex CLI✓Cursor
npx skills add https://github.com/tenequm/skills/tree/HEAD/skills/go-dev

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Stack de Desenvolvimento Go

Configuração opinativa e moderna para desenvolvimento Go. Uma ferramenta por responsabilidade, zero sobreposição.

Quando Usar

  • Iniciar um novo projeto Go do zero
  • Adicionar infraestrutura de linting, formatação ou testes
  • Configurar CI/CD para um serviço ou biblioteca Go
  • Criar um Justfile para substituir um Makefile
  • Adicionar ferramentas de migração de banco de dados
  • Migrar de invocações dispersas de gofmt/govet/staticcheck para uma configuração unificada

Não use para forks que fazem merge regular do upstream: substituir o Makefile e reformatar a árvore com gofumpt causa conflitos em cada merge e enterra mudanças reais em um diff de reformatação. Mantenha o gofmt e as ferramentas de build do upstream e adicione apenas verificações.

O Stack

FerramentaVersãoFunçãoSubstitui
Go1.27+Linguagem, toolchain, go mod, go fix-
golangci-lintv2.14+Meta-linter (100+ linters + formatadores + comando fmt)gofmt, govet, staticcheck, errcheck executados separadamente
gofumptv0.12+Formatador rigoroso (superconjunto de gofmt, 19 regras padrão)gofmt
gotestsumv1.13+Executor de testes com saída legível, modo watch, XML JUnitgo test puro
just1.58+Executor de tarefasMakefile
golang-migratev4.20+Migrações de BD (CLI + biblioteca + embed.FS)Scripts SQL manuais
lefthookv2.1+Hooks Git (binário único, paralelo)pre-commit (Python)

Versões mínimas (floors) são importantes. golangci-lint "suporta versões do Go iguais ou inferiores à versão do Go usada para compilá-lo" - uma versão mais antiga que seu toolchain Go falha diretamente. O suporte a Go 1.27 chegou no golangci-lint v2.13.0, então v2.13 é o mínimo para um projeto Go 1.27; esta skill usa v2.14.0, a primeira versão que inclui gofumpt v0.12.0. Mais dois floors foram movidos recentemente: gofumpt v0.12.0 "é baseado no gofmt do Go 1.27 e requer Go 1.26 ou superior", e o caminho go install do lefthook agora pede Go 1.26+.

Início Rápido: Novo Projeto

# 1. Criar módulo
mkdir myapp && cd myapp
go mod init github.com/yourorg/myapp

# 2. Criar diretórios
mkdir -p cmd/myapp internal migrations

# 3. Instalar golangci-lint como binário, não como ferramenta de módulo (veja nota abaixo)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.14.0

# 4. Rastrear o resto no go.mod (diretiva tool Go 1.24+). Fixe versões - nunca @latest,
#    que recompila a ferramenta a cada execução de CI e varia entre máquinas.
go get -tool mvdan.cc/[email protected]
go get -tool gotest.tools/[email protected]

# golang-migrate precisa de uma build tag, então instale diretamente
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/[email protected]

# 5. Criar arquivos de configuração (modelos abaixo)
# 6. Executar: just check

Não instale o golangci-lint através do padrão de ferramentas. O upstream é explícito: "Usar go install/go get, "tools pattern" e comando/diretivas tool não são garantidos de funcionar. Recomendamos a instalação binária." O motivo que importa em um repositório compartilhado é o vazamento de dependências - "as dependências de uma ferramenta podem modificar as dependências de outra ferramenta ou do seu projeto". Se você precisar ter no go.mod, isole-o atrás de seu próprio -modfile - veja a Referência golangci-lint.

go get -tool rastreia; go tool executa. A diretiva tool registra a dependência no go.mod mas não coloca nada no seu PATH. Invoque através do toolchain - go tool gofumpt -l ., go tool gotestsum --format testname - ou execute go install tool uma vez para popular $(go env GOPATH)/bin. O Justfile abaixo chama os binários diretamente, então assume o caminho go install tool (ou uma instalação de sistema via Homebrew - mas o "Homebrew pode usar uma versão inesperada do Go para construir o binário" para golangci-lint, e não pode fixar uma versão). Note que go tool resolve em relação ao módulo no diretório atual - "ferramentas adicionais podem ser definidas no go.mod do módulo atual" - então em um monorepo ele falha com go: no such tool "..." a menos que a receita defina [working-directory(...)].

Dois comportamentos do comando Go que vale a pena conhecer antes do primeiro commit:

  • go mod init sob um toolchain 1.N escreve go 1.(N-1).0, não 1.N - "Executar go mod init usando um toolchain de versão 1.N.X criará um arquivo go.mod especificando a versão do Go go 1.(N-1).0." Aumente a diretiva deliberadamente se quiser recursos de linguagem 1.N.
  • Defina uma linha toolchain go1.27.1 no go.mod, mas saiba que é um mínimo, não uma fixação: sob o padrão GOTOOLCHAIN=auto, o comando go muda apenas "se ... <tname> for mais novo que o toolchain Go padrão", então um go local mais novo vence. Para um toolchain exato, defina GOTOOLCHAIN=go1.27.1 (CI ou go env -w); GODEBUG=toolchaintrace=1 go version mostra qual foi selecionado. Mantenha a linha no patch atual, não no .0: ele seleciona o go contra o qual govulncheck verifica os avisos da stdlib em qualquer máquina cujo próprio go seja mais antigo, então um patch desatualizado acende o alerta vermelho no CI por conta própria - veja Armadilhas abaixo.

.golangci.yml

version: "2"

run:
  timeout: 5m
  build-tags:
    - integration   # caso contrário, arquivos atrás da tag integration do Justfile nunca são lintados

linters:
  default: standard
  enable:
    - bodyclose
    - copyloopvar
    - dupl
    - durationcheck
    - err113
    - errname
    - errorlint
    - exhaustive
    - exptostd
    - fatcontext
    - goconst
    - gocritic
    - gosec
    - intrange
    - misspell
    - modernize
    - musttag
    - nakedret
    - nestif
    - nilerr
    - noctx
    - nolintlint
    - nonamedreturns
    - perfsprint
    - prealloc
    - revive
    - sqlclosecheck
    - testifylint
    - thelper
    - unconvert
    - unparam
    - usestdlibvars
    - usetesting
    - wastedassign
    - whitespace
    - wrapcheck
  settings:
    govet:
      enable:
        - shadow
    gocritic:
      enabled-checks:
        - nestingReduce
    revive:
      enable-all-rules: true
      rules:
        # enable-all-rules ativa `unhandled-error`, que sinaliza `fmt.Println` no main.
        # Sob enable-all-rules, os `arguments` de uma regra são ignorados (a regra registra
        # duas vezes), então uma lista de permissões não funciona aqui - apenas `disabled` tem efeito.
        - name: unhandled-error
          disabled: true
    errcheck:
      check-type-assertions: true
  exclusions:
    generated: strict
    presets:
      - comments
      - std-error-handling
      - common-false-positives
    rules:
      - path: _test\.go
        linters:
          - errcheck
          - dupl
          - gosec
          - wrapcheck

formatters:
  enable:
    - gofumpt
    - goimports
  settings:
    gofumpt:
      # Selecione as regras individualmente. `extra-rules: true` está obsoleto e também
      # ativa `balance_calls`, que o próprio gofumpt rebaixou como controverso.
      extra:
        group-params: true
        clothe-returns: true
        balance-calls: false
  exclusions:
    generated: strict
    paths:
      - vendor/

output:
  formats:
    text:
      path: stdout
      print-linter-name: true
      colors: true
  sort-order:
    - linter
    - file
  show-stats: true

Justfile

set shell := ["bash", "-euo", "pipefail", "-c"]
set dotenv-load

binary := "myapp"

[private]
default:
    @just --list --unsorted

# ── Qualidade de Código ────────────────────────────────────

# Formatar todo o código Go
[group('quality')]
fmt:
    golangci-lint fmt ./...

# Verificar formatação sem modificar (seguro para CI)
[group('quality')]
fmt-check:
    golangci-lint fmt --diff ./...

# Executar linter
[group('quality')]
lint:
    golangci-lint run ./...

# Executar linter com correção automática
[group('quality')]
lint-fix:
    golangci-lint run --fix ./...

# Executar verificação de vulnerabilidades
[group('quality')]
vuln:
    govulncheck ./...

# ── Testes ─────────────────────────────────────────────────

# Executar todos os testes com detecção de race condition
[group('test')]
test *args="./...":
    gotestsum --format testname -- -race {{ args }}

# Executar testes com cobertura
[group('test')]
test-cov:
    gotestsum --format testname -- -race -coverprofile=coverage.out -covermode=atomic ./...
    go tool cover -func=coverage.out

# Abrir relatório de cobertura no navegador
[group('test')]
coverage: test-cov
    go tool cover -html=coverage.out

# Executar testes de integração
[group('test')]
test-integration:
    gotestsum --format testname -- -race -tags=integration ./...

# Observar testes durante o desenvolvimento
[group('test')]
test-watch:
    gotestsum --watch --watch-clear --format testname

# Executar benchmarks
[group('test')]
bench:
    go test -bench=. -benchmem ./...

# ── Build ──────────────────────────────────────────────────

# Compilar o binário
[group('build')]
build:
    go build -o {{ binary }} ./cmd/{{ binary }}

# Compilar binário de release otimizado
[group('build')]
build-release:
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o {{ binary }} ./cmd/{{ binary }}

# ── Dependências ───────────────────────────────────────────

# Organizar e verificar módulos
[group('deps')]
tidy:
    go mod tidy
    go mod verify

# Falhar se go.mod/go.sum estiverem desorganizados, sem tocá-los (seguro para CI)
[group('deps')]
tidy-check:
    go mod tidy -diff

# Executar geradores de código
[group('deps')]
generate:
    go generate ./...

# ── Banco de Dados ─────────────────────────────────────────

# Aplicar todas as migrações pendentes
[group('db')]
migrate-up:
    migrate -path migrations -database "$DATABASE_URL" up

# Reverter a última migração
[group('db')]
migrate-down:
    migrate -path migrations -database "$DATABASE_URL" down 1

# Criar uma nova migração
[group('db')]
migrate-create name:
    migrate create -ext sql -dir migrations -seq {{ name }}

# ── CI ─────────────────────────────────────────────────────

# Portão completo de CI (verificação de formatação + lint + testes)
[group('ci')]
check: fmt-check lint test
    @echo "Todas as verificações passaram"

# Limpar artefatos de build
[group('ci')]
clean:
    go clean
    rm -f {{ binary }} coverage.out

check deliberadamente deixa de fora vuln: govulncheck chama a rede, e uma atualização do banco de vulnerabilidades pode tornar o portão vermelho em uma alteração que não tocou em nada. Execute-o como seu próprio job de CI. golangci-lint run também executa os formatadores habilitados e relata seus problemas, então fmt-check duplica essa verificação - mantenha-o mesmo assim pelo diff legível.

Configuração do Lefthook

Lefthook é preferido ao pre-commit para projetos Go - é um único binário Go, executa hooks em paralelo e não precisa de Python.

go install github.com/evilmartians/lefthook/[email protected]   # precisa de Go 1.26+
lefthook install
# lefthook.yml
assert_lefthook_installed: true   # falhar alto em vez de pular todas as regras

pre-commit:
  piped: true   # falhe rápido - pare no primeiro job com falha
  commands:
    fmt:
      glob: "*.go"
      run: golangci-lint fmt {staged_files}
      stage_fixed: true
    lint:
      glob: "*.go"
      # Nunca passe uma lista de arquivos nua para `golangci-lint run`: uma lista abrangendo
      # dois diretórios é rejeitada diretamente, e um arquivo de um pacote com vários arquivos
      # relata erros de typecheck fantasma `undefined:`. Lint os pacotes.
      run: printf '%s\n' {staged_files} | xargs -n1 dirname | sort -u | xargs golangci-lint run --fix
      stage_fixed: true
    mod-tidy:
      glob: "*.{go,mod,sum}"
      run: go mod tidy

pre-push:
  commands:
    test:
      run: go test -race ./...

piped: true é falha rápida, não ordenação - lefthook "executa comandos e scripts sequencialmente por padrão", e piped adiciona "Pare de executar comandos e scripts se um deles falhar." Não pode ser combinado com parallel: true.

jobs: (adicionado no lefthook 1.10.0) é a primitiva mais nova ao lado da divisão commands:/scripts: - "Jobs fornecem uma maneira flexível de definir tarefas, suportando tanto comandos quanto scripts. Jobs podem ser agrupados para controle de fluxo avançado." commands: não está obsoleto e permanece totalmente documentado; recorra a jobs: quando precisar de agrupamento, controle de fluxo aninhado ou uma mistura de comandos e scripts em um único hook.

Mais quatro que valem a pena configurar:

  • assert_lefthook_installed: true, acima, é o antídoto para a armadilha de inatividade abaixo: "falha (com código de saída 1) se o executável lefthook não for encontrado no $PATH".
  • lefthook validate no CI detecta um lefthook.yml malformado antes que ele desabilite silenciosamente os hooks; lefthook dump imprime a configuração mesclada efetiva quando um hook não se comporta como escrito.
  • Um lefthook-local.yml ignorado pelo git permite que um desenvolvedor adicione ou pule jobs sem impô-los aos colegas - "Isso é útil quando você quer usar lefthook localmente sem impô-lo aos seus colegas."
  • Em um monorepo, dê a cada job um root: apontando para seu diretório de módulo; sem ele, go mod tidy e go tool executam contra a raiz do repositório e falham.
  • skip: [merge, rebase] nos jobs mod-tidy e lint os mantém fora de commits feitos durante um merge ou rebase em andamento, para que resolver conflitos não também reestage uma reorganização tidy ou edições --fix.

A suíte pre-push repete o que o CI já executa. Quando levar minutos, remova-a ou escopá-a para pacotes alterados - caso contrário, os desenvolvedores aprendem a fazer push com LEFTHOOK=0. Testes executados a partir de um hook também herdam o ambiente de hook do git (GIT_DIR, GIT_INDEX_FILE, GIT_WORK_TREE), então um helper de teste que executa git init ou git commit em t.TempDir() age no repositório real - limpe essas variáveis em cmd.Env.

Um git merge sem conflitos nunca executa pre-commit: o git invoca pre-merge-commit em vez disso, e executa pre-commit apenas quando você finaliza um merge com conflitos com git commit. Espelhe as verificações sob uma chave pre-merge-commit:, ou merges chegarão sem lint.

Comandos de hook resolvem ferramentas a partir do PATH do chamador, então um binário ausente falha o commit com um exit 127 puro. Executá-los como go tool <nome> (diretiva tool) torna o hook tão reproduzível quanto o build.

Beta, mas vale a pena saber: ai: declara hooks de agente LLM no mesmo arquivo - "Durante lefthook install, o lefthook gera o arquivo de configurações específico do provedor para que o agente chame lefthook run <hook> quando necessário."

[truncado para tradução]

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

Habilidades Relacionadas