CommunityCodierung & Entwicklunggithub.com

tenequm/go-dev

Meinungsstarkes Go-Setup mit golangci-lint v2, gofumpt, gotestsum, golang-migrate und just. Verwende es beim Start eines Go-Projekts, Konfiguration von Lint, Format, Test, Coverage oder CI, Schreiben einer Justfile, Anbinden von Migrationen oder Verlassen eines Makefile-Workflows.

Was ist go-dev?

Diese Skill definiert ein vollständiges, modernes Go-Entwicklungssetup, das bewährte Tools für jeden Bereich bereitstellt: golangci-lint v2 als Meta-Linter, gofumpt als strengen Formatierer, gotestsum als Testrunner mit lesbarer Ausgabe und Watch-Modus, golang-migrate für Datenbankmigrationen und just als Aufgaben-Runner. Sie enthält eine konfigurierte .golangci.yml mit einer ausgewählten Liste von Linters und Formattern, eine Justfile mit Aufgaben für Codequalität, Tests, Build, Abhängigkeiten und CI, sowie eine Lefthook-Konfiguration für Git-Hooks. Die Skill ist für Cursor optimiert und bietet eine klare Anleitung zum schnellen Starten neuer Go-Projekte unter Vermeidung typischer Fehler wie der Verwendung von @latest oder der Installation von golangci-lint über go install.

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

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Go Entwicklungsstack

Meinungsstarkes, modernes Go-Entwicklungssetup. Ein Werkzeug pro Aufgabe, null Überschneidungen.

Wann verwenden

  • Starten eines neuen Go-Projekts von Grund auf
  • Hinzufügen von Linting-, Formatierungs- oder Testinfrastruktur
  • Einrichten von CI/CD für einen Go-Dienst oder eine Bibliothek
  • Erstellen einer Justfile, um ein Makefile zu ersetzen
  • Hinzufügen von Datenbank-Migrationstooling
  • Migration von verstreuten gofmt/govet/staticcheck-Aufrufen zu einer einheitlichen Einrichtung

Nicht für einen Fork, der regelmäßig von Upstream gemerged wird: Das Ersetzen seines Makefiles und das Neuformatieren des Baums mit gofumpt führt bei jedem Merge zu Konflikten und vergräbt echte Änderungen in einem Reformats-Diff. Behalte dort das gofmt und Build-Tooling von Upstream bei und füge nur Checks hinzu.

Der Stack

ToolVersionRolleErsetzt
Go1.27+Sprache, Toolchain, go mod, go fix-
golangci-lintv2.14+Meta-Linter (100+ Linter + Formatierer + fmt-Befehl)gofmt, govet, staticcheck, errcheck separat ausführen
gofumptv0.12+Strenger Formatierer (Obermenge von gofmt, 19 Standardregeln)gofmt
gotestsumv1.13+Test-Runner mit lesbarer Ausgabe, Watch-Modus, JUnit-XMLReines go test
just1.58+Aufgaben-RunnerMakefile
golang-migratev4.20+DB-Migrationen (CLI + Bibliothek + embed.FS)Manuelle SQL-Skripte
lefthookv2.1+Git-Hooks (einzelnes Binary, parallel)pre-commit (Python)

Versionsuntergrenzen sind tragend. golangci-lint "unterstützt Go-Versionen, die niedriger oder gleich der Go-Version sind, die für die Kompilierung verwendet wurde" - eine Untergrenze, die älter als deine Go-Toolchain ist, schlägt sofort fehl. Go 1.27-Unterstützung wurde in golangci-lint v2.13.0 eingeführt, daher ist v2.13 die Untergrenze für ein Go 1.27-Projekt; diese Skill legt v2.14.0 fest, die erste Version, die gofumpt v0.12.0 bündelt. Zwei weitere Untergrenzen wurden kürzlich verschoben: gofumpt v0.12.0 "basiert auf Go 1.27s gofmt und erfordert Go 1.26 oder neuer", und der go install-Pfad von lefthook verlangt jetzt Go 1.26+.

Schnellstart: Neues Projekt

# 1. Modul erstellen
mkdir myapp && cd myapp
go mod init github.com/yourorg/myapp

# 2. Verzeichnisse anlegen
mkdir -p cmd/myapp internal migrations

# 3. golangci-lint als Binary installieren, nicht als Modul-Tool (siehe Hinweis unten)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.14.0

# 4. Den Rest in go.mod nachverfolgen (Go 1.24+ tool-Direktive). Versionen festlegen - niemals @latest,
#    da dies das Tool bei jedem CI-Lauf neu kompiliert und zwischen Maschinen abweicht.
go get -tool mvdan.cc/[email protected]
go get -tool gotest.tools/[email protected]

# golang-migrate benötigt ein Build-Tag, daher direkt installieren
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/[email protected]

# 5. Konfigurationsdateien erstellen (Vorlagen unten)
# 6. Ausführen: just check

Installiere golangci-lint nicht über das Tools-Muster. Upstream ist eindeutig: "Die Verwendung von go install/go get, "tools pattern" und tool-Befehl/Direktive-Installationen sind nicht garantiert funktionsfähig. Wir empfehlen die Binary-Installation." Der Grund, warum das in einem gemeinsam genutzten Repository wichtig ist, ist Dependency-Bleeding - "die Abhängigkeiten eines Tools können die Abhängigkeiten eines anderen Tools oder deines Projekts ändern". Wenn es unbedingt in go.mod sein muss, isoliere es mit einem eigenen -modfile - siehe die golangci-lint Referenz.

go get -tool verfolgt; go tool führt aus. Die tool-Direktive zeichnet die Abhängigkeit in go.mod auf, legt aber nichts auf deinem PATH ab. Entweder über die Toolchain aufrufen - go tool gofumpt -l ., go tool gotestsum --format testname - oder einmal go install tool ausführen, um $(go env GOPATH)/bin zu befüllen. Die Justfile unten ruft die reinen Binaries auf, geht also vom go install tool-Weg aus (oder einer Systeminstallation via Homebrew - aber "Homebrew kann eine unerwartete Go-Version zum Erstellen des Binaries verwenden" für golangci-lint, und es kann keine Version festlegen). Beachte, dass go tool gegen das Modul im aktuellen Verzeichnis auflöst - "zusätzliche Tools können in der go.mod des aktuellen Moduls definiert sein" - daher schlägt es in einem Monorepo mit go: no such tool "..." fehl, es sei denn, das Rezept setzt [working-directory(...)].

Zwei Go-Befehlsverhaltensweisen, die vor dem ersten Commit wissenswert sind:

  • go mod init unter einer 1.N-Toolchain schreibt go 1.(N-1).0, nicht 1.N - "Das Ausführen von go mod init mit einer Toolchain der Version 1.N.X erstellt eine go.mod-Datei, die die Go-Version go 1.(N-1).0 angibt." Erhöhe die Direktive bewusst, wenn du 1.N-Sprachfunktionen möchtest.
  • Setze eine toolchain go1.27.1-Zeile in go.mod, aber wisse, dass es eine Untergrenze und keine Festlegung ist: Unter dem Standard GOTOOLCHAIN=auto wechselt der go-Befehl nur "wenn ... <tname> neuer als die Standard-Go-Toolchain ist", daher gewinnt eine neuere lokale go. Für eine exakte Toolchain setze GOTOOLCHAIN=go1.27.1 (CI oder go env -w); GODEBUG=toolchaintrace=1 go version zeigt, welche ausgewählt wurde. Halte die Zeile auf dem aktuellen Patch, nicht auf .0: Sie wählt das go aus, gegen das govulncheck stdlib-Advisories auf jeder Maschine mit einer älteren eigenen go scannt. Ein veralteter Patch macht CI von selbst rot - siehe Footguns unten.

.golangci.yml

version: "2"

run:
  timeout: 5m
  build-tags:
    - integration   # sonst werden Dateien hinter dem Integration-Tag der Justfile nie gelintet

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 aktiviert `unhandled-error`, das `fmt.Println` in main meldet.
        # Unter enable-all-rules werden die `arguments` einer Regel ignoriert (die Regel
        # registriert sich zweimal), daher funktioniert hier keine Whitelist - nur `disabled` wirkt.
        - 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:
      # Regeln einzeln auswählen. `extra-rules: true` ist veraltet und schaltet auch
      # `balance_calls` ein, das gofumpt selbst als kontrovers herabgestuft hat.
      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

# ── Code-Qualität ──────────────────────────────────────────

# Den gesamten Go-Code formatieren
[group('quality')]
fmt:
    golangci-lint fmt ./...

# Formatierung prüfen ohne Änderung (CI-sicher)
[group('quality')]
fmt-check:
    golangci-lint fmt --diff ./...

# Linter ausführen
[group('quality')]
lint:
    golangci-lint run ./...

# Linter mit automatischer Korrektur ausführen
[group('quality')]
lint-fix:
    golangci-lint run --fix ./...

# Schwachstellenprüfung durchführen
[group('quality')]
vuln:
    govulncheck ./...

# ── Tests ───────────────────────────────────────────────

# Alle Tests mit Race-Erkennung ausführen
[group('test')]
test *args="./...":
    gotestsum --format testname -- -race {{ args }}

# Tests mit Coverage ausführen
[group('test')]
test-cov:
    gotestsum --format testname -- -race -coverprofile=coverage.out -covermode=atomic ./...
    go tool cover -func=coverage.out

# Coverage-Bericht im Browser öffnen
[group('test')]
coverage: test-cov
    go tool cover -html=coverage.out

# Integrationstests ausführen
[group('test')]
test-integration:
    gotestsum --format testname -- -race -tags=integration ./...

# Tests während der Entwicklung beobachten
[group('test')]
test-watch:
    gotestsum --watch --watch-clear --format testname

# Benchmarks ausführen
[group('test')]
bench:
    go test -bench=. -benchmem ./...

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

# Das Binary bauen
[group('build')]
build:
    go build -o {{ binary }} ./cmd/{{ binary }}

# Optimiertes Release-Binary bauen
[group('build')]
build-release:
    CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o {{ binary }} ./cmd/{{ binary }}

# ── Abhängigkeiten ──────────────────────────────────────────

# Module aufräumen und verifizieren
[group('deps')]
tidy:
    go mod tidy
    go mod verify

# Fehlschlagen, wenn go.mod/go.sum unordentlich sind, ohne sie zu berühren (CI-sicher)
[group('deps')]
tidy-check:
    go mod tidy -diff

# Code-Generatoren ausführen
[group('deps')]
generate:
    go generate ./...

# ── Datenbank ──────────────────────────────────────────────

# Alle ausstehenden Migrationen anwenden
[group('db')]
migrate-up:
    migrate -path migrations -database "$DATABASE_URL" up

# Letzte Migration rückgängig machen
[group('db')]
migrate-down:
    migrate -path migrations -database "$DATABASE_URL" down 1

# Neue Migration erstellen
[group('db')]
migrate-create name:
    migrate create -ext sql -dir migrations -seq {{ name }}

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

# Vollständiges CI-Gate (Format-Prüfung + Lint + Test)
[group('ci')]
check: fmt-check lint test
    @echo "Alle Prüfungen bestanden"

# Build-Artefakte bereinigen
[group('ci')]
clean:
    go clean
    rm -f {{ binary }} coverage.out

check lässt bewusst vuln aus: govulncheck ruft das Netzwerk auf, und ein Update der Verwundbarkeitsdatenbank kann das Gate bei einer Änderung rot färben, die nichts berührt. Führe es stattdessen als eigenen CI-Job aus. golangci-lint run führt auch die aktivierten Formatierer aus und meldet deren Probleme, daher dupliziert fmt-check diese Prüfung - behalte es trotzdem wegen des lesbaren Diffs.

Lefthook-Konfiguration

Lefthook wird pre-commit für Go-Projekte vorgezogen - es ist ein einzelnes Go-Binary, führt Hooks parallel aus und benötigt kein Python.

go install github.com/evilmartians/lefthook/[email protected]   # benötigt Go 1.26+
lefthook install
# lefthook.yml
assert_lefthook_installed: true   # laut fehlschlagen, anstatt jede Regel zu überspringen

pre-commit:
  piped: true   # fail fast - beim ersten fehlschlagenden Job anhalten
  commands:
    fmt:
      glob: "*.go"
      run: golangci-lint fmt {staged_files}
      stage_fixed: true
    lint:
      glob: "*.go"
      # Niemals eine reine Dateiliste an `golangci-lint run` übergeben: Eine Liste über zwei
      # Verzeichnisse hinweg wird sofort abgelehnt, und eine Datei eines Pakets mit mehreren
      # Dateien meldet phantomhafte `undefined:`-Typcheck-Fehler. Lint stattdessen die Pakete.
      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 ist fail-fast, keine Reihenfolge - lefthook "führt Befehle und Skripte standardmäßig sequenziell aus", und piped fügt hinzu: "Stoppe die Ausführung von Befehlen und Skripten, wenn einer von ihnen fehlschlägt." Es kann nicht mit parallel: true kombiniert werden.

jobs: (hinzugefügt in lefthook 1.10.0) ist das neuere Primitiv neben der commands:/scripts:-Aufteilung - "Jobs bieten eine flexible Möglichkeit, Aufgaben zu definieren, die sowohl Befehle als auch Skripte unterstützen. Jobs können für eine erweiterte Flusssteuerung gruppiert werden." commands: ist nicht veraltet und bleibt vollständig dokumentiert; greife zu jobs:, wenn du Gruppierung, verschachtelte Flusssteuerung oder eine Mischung aus Inline-Befehlen und Skripten in einem Hook benötigst.

Vier weitere, die es wert sind, verdrahtet zu werden:

  • assert_lefthook_installed: true, oben, ist das Gegenmittel zur Dormanz-Footgun unten: "fehlschlagen (mit Exit-Status 1), wenn die ausführbare Datei lefthook nicht im $PATH gefunden werden kann".
  • lefthook validate in CI fängt eine fehlerhafte lefthook.yml ab, bevor sie Hooks stillschweigend deaktiviert; lefthook dump gibt die zusammengeführte wirksame Konfiguration aus, wenn sich ein Hook nicht wie geschrieben verhält.
  • Eine git-vergessene lefthook-local.yml ermöglicht es einem Entwickler, Jobs hinzuzufügen oder zu überspringen, ohne sie Teamkollegen aufzuzwingen - "Dies ist nützlich, wenn du lefthook lokal verwenden möchtest, ohne es deinen Teamkollegen aufzuzwingen."
  • Gib in einem Monorepo jedem Job ein root:, das auf sein Modulverzeichnis zeigt; ohne dieses laufen go mod tidy und go tool gegen das Repository-Root und schlagen fehl.
  • skip: [merge, rebase] bei den mod-tidy- und lint-Jobs hält sie von Commits fern, die während eines Merges oder Rebase erstellt werden, sodass das Auflösen von Konflikten nicht auch ein tidy-rewrite oder --fix-Bearbeitungen neu einstaged.

Der pre-push-Testsuite wiederholt das, was CI bereits ausführt. Sobald es Minuten dauert, entferne es oder beschränke es auf geänderte Pakete - andernfalls lernen Entwickler, mit LEFTHOOK=0 zu pushen. Tests, die von einem Hook ausgeführt werden, erben auch die Hook-Umgebung von git (GIT_DIR, GIT_INDEX_FILE, GIT_WORK_TREE), sodass ein Test-Helfer, der git init oder git commit in t.TempDir() ausführt, auf dem eigentlichen Repository arbeitet - lösche diese Variablen in cmd.Env.

Ein konfliktfreier git merge führt niemals pre-commit aus: git ruft stattdessen pre-merge-commit auf und führt pre-commit nur aus, wenn du einen Konflikt-Merge mit git commit abschließt. Spiegle die Prüfungen unter einem pre-merge-commit:-Schlüssel, oder Merges landen ungelintet.

Hook-Befehle lösen Tools aus dem PATH des Aufrufers auf, daher schlägt ein fehlendes Binary den Commit mit einem bloßen exit 127 fehl. Die Ausführung als go tool <name> (tool-Direktive) macht den Hook so reproduzierbar wie den Build.

Beta, aber wissenswert: ai: deklariert LLM-Agent-Hooks in derselben Datei - "Während lefthook install generiert lefthook die anbieterspezifische Einstellungsdatei, sodass der Agent lefthook run <hook> aufruft, wenn ...

[truncated for translation]

Individual skills in this repo

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

Verwandte Skills