# Lint Go source against the backend house rules (ADR-024 / ADR-027 / ADR-023).
# lint_go.sh [path] # defaults to ./... (current module)
# scripts/lint_go.sh ./internal/...
# 1. go vet (external tool, degrades to warning)
# 2. go test -race ./... (external tool, degrades to warning)
# 3. golangci-lint run (external tool, degrades to warning if not on PATH)
# 4. house rule: function size <= 75 lines (grep/awk static check, always runs)
# 5. house rule: bare fmt.Errorf wrapping %w (grep static check, always runs)
# 6. house rule: no naked `go func()` (grep static check, always runs)
# 7. house rule: coverage floor on domain/service packages (go test -cover, degrades to warning)
# Exit code is non-zero if any check fails. Missing external tools (golangci-lint)
# degrade that check to a skipped warning rather than a hard failure, so the
# house-rule checks (which need no external tools beyond `go` itself) still run.
COVER_THRESHOLD="${GO_STANDARDS_COVER_THRESHOLD:-80}"
FUNC_LINE_LIMIT="${GO_STANDARDS_FUNC_LINE_LIMIT:-75}"
warn() { printf '⚠️ %s\n' "$1"; }
error() { printf '❌ %s\n' "$1"; fail=1; }
ok() { printf '✅ %s\n' "$1"; }
if ! command -v go >/dev/null 2>&1; then
echo "go toolchain not on PATH — cannot run any check." >&2
if [[ ! -f go.mod ]]; then
echo "Usage: $0 [path] (run from the module root — no go.mod found here)" >&2
if go vet "$TARGET" 2>/tmp/gostd_vet_err.$$; then
error "go vet reported issues:"
sed 's/^/ /' /tmp/gostd_vet_err.$$ >&2
rm -f /tmp/gostd_vet_err.$$
echo "== go test -race =="
if go test -race "$TARGET" >/tmp/gostd_race.$$ 2>&1; then
error "go test -race failed:"
sed 's/^/ /' /tmp/gostd_race.$$ >&2
echo "== golangci-lint =="
if command -v golangci-lint >/dev/null 2>&1; then
if golangci-lint run "$TARGET" 2>/tmp/gostd_lint.$$; then
error "golangci-lint reported issues:"
sed 's/^/ /' /tmp/gostd_lint.$$ >&2
warn "golangci-lint not on PATH — skipped (see references/lint.md for the expected linter set)"
echo "== house rule: function size <= ${FUNC_LINE_LIMIT} lines (ADR-024) =="
# Walk every .go file (excluding generated + vendor + mocks), find `func`
# opening lines at column 0, and count lines to the matching closing brace.
while IFS= read -r -d '' gofile; do
*_generated.go|*.pb.go|*/vendor/*|*/mocks/*) continue ;;
hit="$(awk -v file="$gofile" -v limit="$FUNC_LINE_LIMIT" '
/^func / { name = $0; start = NR; depth = 0; started = 0 }
n = gsub(/{/, "{"); depth += n
n = gsub(/}/, "}"); depth -= n
if (depth > 0) started = 1
if (started && depth == 0) {
if (len > limit) printf "%s:%d: %s (%d lines, limit %d)\n", file, start, name, len, limit
[[ -n "$hit" ]] && long_funcs+="$hit"$'\n'
done < <(find . -type f -name '*.go' -not -path '*/vendor/*' -print0 2>/dev/null)
if [[ -n "$long_funcs" ]]; then
error "function(s) over the ${FUNC_LINE_LIMIT}-line limit:"
echo "$long_funcs" | sed 's/^/ /'
ok "no function over ${FUNC_LINE_LIMIT} lines"
echo "== house rule: wrapped errors use %w, not %v/%s (ADR-024) =="
bad_wraps=$(grep -rnE 'fmt\.Errorf\([^)]*:\s*%[vs][^)]*,\s*err\)' \
--include='*.go' . 2>/dev/null | grep -v '_test.go' || true)
if [[ -n "$bad_wraps" ]]; then
error "found fmt.Errorf(...err) using %v/%s instead of %w — the error chain breaks errors.Is/As:"
echo "$bad_wraps" | sed 's/^/ /'
ok "no %v/%s error-wrapping found (either %w is used, or no wrapping present)"
echo "== house rule: no naked 'go func()' (use sourcegraph/conc) =="
naked_go=$(grep -rnE '(^|[^a-zA-Z_.])go func\(' --include='*.go' . 2>/dev/null | grep -v '_test.go' || true)
if [[ -n "$naked_go" ]]; then
error "found a raw 'go func()' — route through sourcegraph/conc (conc.WaitGroup / pool.ResultErrorPool):"
echo "$naked_go" | sed 's/^/ /'
ok "no naked 'go func()' found"
echo "== house rule: coverage floor ${COVER_THRESHOLD}% on domain/service (ADR-024) =="
domain_service_dirs="$(find . -type d \( -name 'domain' -o -name 'service' \) -not -path '*/vendor/*' 2>/dev/null)"
if [[ -n "$domain_service_dirs" ]]; then
for pkg_dir in $domain_service_dirs; do
pkg_path="${pkg_dir}/..."
cover_out="$(go test -cover "$pkg_path" 2>/tmp/gostd_cover_err.$$)"
warn "coverage run failed for $pkg_dir — see errors:"
sed 's/^/ /' /tmp/gostd_cover_err.$$
rm -f /tmp/gostd_cover_err.$$
rm -f /tmp/gostd_cover_err.$$
pct="$(printf '%s\n' "$cover_out" | grep -oE 'coverage: [0-9.]+%' | grep -oE '[0-9.]+' | head -1)"
warn "could not parse coverage for $pkg_dir"
if awk -v p="$pct" -v t="$COVER_THRESHOLD" 'BEGIN{exit !(p+0 < t+0)}'; then
error "$pkg_dir coverage ${pct}% is below the ${COVER_THRESHOLD}% floor"
ok "$pkg_dir coverage ${pct}% meets the ${COVER_THRESHOLD}% floor"
warn "no domain/ or service/ package found — skipped coverage floor check"
if [[ "$fail" -eq 0 ]]; then
echo "All checks passed."
echo "One or more checks failed — see ❌ lines above."