lintmax-go
Maximum-strictness Go quality gate. One command, always-latest, never stale.
The Go counterpart to lintmax (TypeScript). Designed for coding agents, not humans.
Why
Go's compiler is already stricter than tsc on some axes (unused vars/imports are hard errors). lintmax-go closes the rest of the gap and pushes past it: every linter golangci-lint bundles, maxed internal configs, plus the tools golangci deliberately doesn't wrap — nil-safety, whole-program dead code, vulnerability scanning.
Never stale
No linter version is ever pinned. Every tool is fetched @latest on each run, and the embedded golangci config uses default: all — so the moment golangci ships a new linter, your build runs it. You are always on the bleeding edge; you fix-forward, you never accumulate lint debt.
This extends to your own dependencies. A realtime staleness scan checks go.mod deps and GitHub-Action pins against upstream the instant a newer release publishes (tolerance 0; opt into a buffer via LINTMAX_STALENESS_TOLERANCE_DAYS). A stale direct dep — or a stale used indirect one — hard-fails the gate; graph-only modules the build never compiles are ignored. Indirect deps that are MVS-capped (a transitive can't exceed what its requiring module pins) are reported as a non-blocking advisory rather than failing on the unfixable. Set LINTMAX_SKIP_STALENESS=1 to disable.
Install
go install github.com/1qh/lintmax-go@latest # Go users
brew tap 1qh/tap # macOS / Linux
brew install lintmax-go
# or prebuilt binary (no Go, no brew):
curl -fsSL https://raw.githubusercontent.com/1qh/lintmax-go/main/install.sh | sh
Use
Exactly four commands — the lean agent-first surface:
lintmax-go fix # format + autofix + full gate (every scanner) — the default action
lintmax-go check # verify only, no writes (CI mode) — same exhaustive scanner set
lintmax-go version # print version
lintmax-go rules # list every enabled linter under the maxed config
Prints ok on a single line on success, exit 0 = clean. Tool output is shown only on failure (verbose). The ok is the explicit success signal an agent needs to confirm the gate ran and passed; a clean run that is cached prints ok (cached).
Self-evolving (automatic, never a command)
The tool maintains itself — you never type a maintenance command. Every gate run, internally:
- Linter @latest refresh — every child tool is reinstalled
@latest on a refresh cadence (fast local loop caches the last green run for <24h; CI always forces @latest).
- Self-update — in CI the running binary refreshes itself to
@latest before gating.
- Green-tree-hash cache — a clean
check is skipped when the working tree hash is unchanged (ok (cached)).
- Staleness scan —
go.mod deps + GitHub-Action pins are checked against upstream every run.
What runs
| Layer |
Tool |
Catches |
| comments + compact |
native (go/scanner + go/ast) |
deletes all comments except directives; removes blank lines inside function bodies |
| format |
gofumpt → gci → golines |
strict format, deterministic imports, line length 123 |
| lint |
golangci-lint default: all |
~115 linters, all internal checks maxed, error-or-off |
| nil-safety |
nilaway |
nil panics (the TS-strict-null gap) |
| dead code |
deadcode |
whole-program unreachable funcs |
| tests |
go test -race -shuffle=on |
data races, test-order coupling |
| deep |
govulncheck + osv-scanner |
reachability + lockfile CVEs |
Parity with lintmax
Mirrors lintmax (TypeScript): every rule error or off (never warn), default: all (auto-inherits new linters), comment deletion + compaction, 2-wide indent (.editorconfig tab_width=2), line width 123.
Two hard limits Go's gofmt imposes that lintmax doesn't hit (cannot be overridden without abandoning gofmt):
- Tabs, not spaces — gofmt mandates tabs;
tab_width=2 makes them render identically to lintmax's 2-space.
- Top-level blank lines — gofmt forces a blank after
package, around imports, and between top-level declarations. Blank lines inside function bodies are removed (true "no empty lines between statements"); the top-level ones are gofmt law.
Earned disables
The disable list starts empty. Each entry is earned by a concrete conflict found on real code, never anticipated — feature-forced (comment-strip/compact removing what a rule demands) or lintmax-parity (matching lintmax's own OFF-list). See internal/config/golangci.yml for the documented reason on each.
Configless by default
The bundled config is generic only and runs with zero per-project config — no consumer file is read, no override is merged. It carries no project/ecosystem opinion (import paths, in-house ban patterns, first-party struct scope); those live in the consuming project's own enforcement layer, never in lintmax. exhaustruct ships disabled (with no include it would flag every third-party T{}); forbidigo ships generic stdlib-discipline bans only (log.*, bare fmt.Print*, context.Background in tests).
Strictness policy
default: all — opt out of nothing by default; new linters auto-enabled.
- Only physical conflicts disabled (
nlreturn vs wsl_v5) — every other linter and rule stays on.
- Maxed internal checks: staticcheck all categories, gocritic all checks, revive all rules, gosec low/low, errcheck type-assertions + blank.
- golangci's silent default exclusions are off — nothing is hidden.
- Every suppression requires
//nolint:rule // reason (nolintlint enforces it).
License
MIT