
Go Linting
Bootstrap a curated golangci-lint config and run an initial lint pass on a Go module without hand-picking every linter.
Overview
Go-linting is an agent skill for the Ship phase that generates a curated .golangci.yml and runs golangci-lint on your Go module.
Install
npx skills add https://github.com/cxuu/golang-skills --skill go-lintingWhat is this skill?
- Generates .golangci.yml with 10 enabled linters (errcheck, goimports, revive, govet, staticcheck, gosec, ineffassign, mi
- Shell entrypoint runs golangci-lint with exit codes 0 pass, 1 issues, 2 error
- Supports goimports local-prefix grouping for module paths
- CLI flags: --json, --force, --dry-run, --limit for JSON issue output
- Configurable revive exported rules and gocyclo min-complexity 15
- 10 linters enabled in the generated default config
- gocyclo min-complexity threshold set to 15
- JSON issue output default limit of 50 lines
Adoption & trust: 718 installs on skills.sh; 110 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
You have a Go repo but no consistent golangci-lint baseline, so agents and CI disagree on which static checks matter.
Who is it for?
Indie Go backends that want golangci-lint wired in one shot before opening a PR or tightening CI.
Skip if: Teams that already maintain a custom golangci-lint matrix across monorepos with per-package overrides—they should extend config manually instead of overwriting with --force.
When should I use this skill?
Starting or standardizing Go lint in a repo before review, CI, or agent-driven refactors.
What do I get? / Deliverables
You get a versioned lint config on disk and a clear pass-or-fail lint run with optional JSON issue lines for the agent to fix next.
- .golangci.yml file
- lint pass/fail result
- optional JSON issue report
Recommended Skills
Journey fit
How it compares
Use instead of asking the agent to invent linter lists from memory each session.
Common Questions / FAQ
Who is go-linting for?
Solo and indie builders using Claude Code, Cursor, or Codex on Go services who want a standard golangci-lint setup without reading every linter doc first.
When should I use go-linting?
During Ship review before merge, when bootstrapping a new Go module, or after cloning a repo that lacks .golangci.yml—especially when you need JSON output for agent-driven fixes.
Is go-linting safe to install?
It runs shell and touches the filesystem to write .golangci.yml and execute golangci-lint; review the Security Audits panel on this page and inspect the script before --force on production branches.
SKILL.md
READMESKILL.md - Go Linting
run: timeout: 5m linters: enable: # Minimum recommended - errcheck - goimports - revive - govet - staticcheck # Additional recommended - gosec - ineffassign - misspell - gocyclo - bodyclose linters-settings: goimports: local-prefixes: "" # Set to your module path revive: rules: - name: exported gocyclo: min-complexity: 15 issues: exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 #!/usr/bin/env bash set -euo pipefail VERSION="1.0.0" SCRIPT_NAME="$(basename "$0")" usage() { cat <<EOF $SCRIPT_NAME v$VERSION — Generate .golangci.yml and run initial lint USAGE bash $SCRIPT_NAME [options] [local-prefix] DESCRIPTION Creates a .golangci.yml with a curated set of linters (errcheck, goimports, revive, govet, staticcheck) and runs golangci-lint. If local-prefix is provided, configures goimports to group local imports separately. Exits 0 if lint passes, 1 if lint issues found, 2 on error. OPTIONS -h, --help Show this help message -v, --version Show version --json Output results as JSON --force Overwrite existing .golangci.yml --dry-run Print generated config to stdout without writing --limit N Max lint issue lines in JSON output (default: 50, 0 = unlimited) ARGUMENTS local-prefix Module path prefix for goimports grouping (e.g., github.com/myorg/myrepo) EXAMPLES bash $SCRIPT_NAME bash $SCRIPT_NAME github.com/myorg/myrepo bash $SCRIPT_NAME --force github.com/myorg/myrepo bash $SCRIPT_NAME --dry-run github.com/myorg/myrepo bash $SCRIPT_NAME --json bash $SCRIPT_NAME --json --limit 20 EOF } json_escape() { local s="$1" s="${s//\\/\\\\}" s="${s//\"/\\\"}" s="${s//$'\t'/\\t}" s="${s//$'\r'/}" s="${s//$'\n'/\\n}" printf '%s' "$s" } JSON_OUTPUT=false FORCE=false DRY_RUN=false LIMIT=50 LOCAL_PREFIX="" while [[ $# -gt 0 ]]; do case "$1" in -h|--help) usage; exit 0 ;; -v|--version) echo "$SCRIPT_NAME v$VERSION"; exit 0 ;; --json) JSON_OUTPUT=true; shift ;; --force) FORCE=true; shift ;; --dry-run) DRY_RUN=true; shift ;; --limit) LIMIT="${2:?error: --limit requires a number}"; shift 2 ;; -*) echo "error: unknown option: $1" >&2; usage >&2; exit 2 ;; *) LOCAL_PREFIX="$1"; shift ;; esac done generate_config() { cat <<'YAML' linters: enable: - errcheck - goimports - revive - govet - staticcheck linters-settings: YAML if [[ -n "$LOCAL_PREFIX" ]]; then cat <<YAML goimports: local-prefixes: ${LOCAL_PREFIX} YAML fi cat <<'YAML' revive: rules: - name: blank-imports - name: context-as-argument - name: error-return - name: error-strings - name: exported run: timeout: 5m YAML } if $DRY_RUN; then generate_config exit 0 fi CONFIG_PATH=".golangci.yml" if [[ -f "$CONFIG_PATH" ]] && ! $FORCE; then echo "error: $CONFIG_PATH already exists (use --force to overwrite)" >&2 exit 2 fi generate_config > "$CONFIG_PATH" LINT_OUTPUT="" LINT_EXIT=0 if ! command -v golangci-lint &>/dev/null; then echo "error: golangci-lint is not installed" >&2 exit 2 fi LINT_OUTPUT=$(golangci-lint run ./... 2>&1) || LINT_EXIT=$? if $JSON_OUTPUT; then LINT_TRUNCATED=false LINT_DISPLAY="$LINT_OUTPUT" if [[ $LIMIT -gt 0 && -n "$LINT_OUTPUT" ]]; then LINT_ARR=() while IFS= read -r line; do LINT_ARR+=("$line") done <<< "$LINT_OUTPUT" if [[ ${#LINT_ARR[@]} -gt $LIMIT ]]; then LINT_DISPLAY="" for (( i=0; i<LIMIT; i++ )); do [[ -n "$LINT_DISPLAY" ]] && LINT_DISPLAY+=$'\n' LINT_DISPLAY+="${LINT_ARR[$i]}" done LINT_TRUNCATED=true fi fi