Skip to content

ci: expand the lint config, make make fix converge - #60

Merged
min0625 merged 1 commit into
mainfrom
ci/lint-config-and-fix-target
Sep 1, 2026
Merged

min0625 merged 1 commit into
mainfrom
ci/lint-config-and-fix-target

Conversation

@min0625

@min0625 min0625 commented Aug 31, 2026 •

Copy link
Copy Markdown
Owner

What

Second pass over the lint setup after #59, plus a make fix that actually
converges.

Formatters — re-add gci. #59 dropped it as covered by
gofumpt+goimports, but those two only sort within the groups a file
already has. Checked on cmd/mint/main.go with cobra moved into the stdlib
block:

result
gofumpt+goimports cobra ends up in a third group of its own
+ gci merged back into the existing third-party group

So it earns its slot; #59's reasoning was wrong on that point.

replace guard — swap gomodguard_v2.local-replace-directives for the
gomoddirectives linter. gomodguard only sees directly imported modules, so
an indirect dep's replace slipped through. Verified gomoddirectives rejects
a resolvable non-local replace by default, not just => ../local:

go.mod:27:1: replacement are not allowed: github.com/spf13/cobra (gomoddirectives)

depguard retired — its single rule (github.com/pkg/errors) moves into
the gomodguard_v2 blocklist, next to the other deprecated modules.
mitchellh/mapstructure and gopkg.in/yaml (prefix match, both majors) join
it.

New linters — bidichk, makezero, reassign. reassign gets
patterns: [".*"] per upstream's own recommendation; the default only guards
EOF and Err*, leaving os.Args and http.DefaultClient open. Tests are
excluded from it — they borrow os.Stdin/os.Args and restore them.

Settings — errcheck.check-type-assertions on;
prealloc.for-loops on (modernize rewrites 3-clause loops into the range
form prealloc polices, so with the default the finding only surfaces after
--fix); perfsprint.concat-loop off (modernize rewrites the same pattern
better, reusing the variable instead of inventing one).

gosec G104 excluded — it fires on bare call statements only (never
defer/go) and honors a whitelist, so it is a strict subset of errcheck,
which also names the offending function. With uniq-by-line: false both would
print on the same line. Narrowing errcheck (exclude-functions,
std-error-handling) would reopen the gap — noted in the config.

make fix — now tidy → --fix → tidy → lint. --fix is not a fixpoint:
its own edits can trip a linter the fixing run never saw, and can add or remove
imports.

check-rev rationale corrected — #58 documented golangci-lint as only
warning and exiting 0 on an unresolvable --new-from-rev, which would let CI
pass having linted nothing. It does not: it warns, reports every issue in the
repo, and exits 1. The guard stays, but the Makefile comment and AGENTS.md
now say what it actually buys — a readable error message, not safety.

Docs — AGENTS.md also gains a note that .golangci.yaml is not
self-contained: whole-files: true is silently inert without one of the
--new* modes, so the file only works alongside the Makefile's
--new-from-rev harness. And that formatter findings are the exception to the
ratchet — apply golangci-lint fmt ./... repo-wide rather than leave a
half-formatted tree contradicting its own config.

Verification

  • golangci-lint config verify — clean
  • golangci-lint run ./... (no --new-from-rev) — 0 issues repo-wide, so
    the whole-files ratchet takes on no new debt
  • make check (full prek suite, the CI gate) — all hooks pass
  • make fix — exits 0, leaves the tree unchanged

Note on scope: the make fix run above exercised the new
tidy → --fix → tidy → lint plumbing on a clean tree. It confirms the ordering
runs, not that the second pass caught a real fixpoint miss.

🤖 Generated with Claude Code

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@min0625
min0625 force-pushed the ci/lint-config-and-fix-target branch from 84b4ec8 to 8569431 Compare September 1, 2026 11:21
Re-add gci. #59 dropped it as covered by gofumpt+goimports, but those two
only sort within the groups a file already has: a third-party import
stranded in the stdlib block is moved out into a group of its own rather
than merged into the existing third-party block. gci is what collapses it
back to the canonical two groups.

Replace gomodguard_v2's local-replace-directives with the gomoddirectives
linter. gomodguard only sees directly imported modules, so an indirect
dep's replace slipped through; gomoddirectives forbids every replace by
default -- checked against a resolvable non-local one, not just a
`=> ../local`.

Retire depguard. Its one rule (github.com/pkg/errors) moves into the
gomodguard_v2 blocklist so the recommendation text sits next to the other
deprecated modules. mitchellh/mapstructure and gopkg.in/yaml (prefix
match, so both majors) join it.

Enable bidichk, makezero and reassign. reassign gets patterns: ".*", which
is upstream's own recommendation -- the default only guards EOF and Err*,
leaving os.Args and http.DefaultClient open. Tests are excluded from it:
they borrow os.Stdin/os.Args and restore them afterwards.

Turn on errcheck.check-type-assertions and prealloc.for-loops (modernize
rewrites 3-clause loops into the range form prealloc polices, so with the
default the finding only surfaces after --fix), and turn off
perfsprint.concat-loop, which modernize rewrites better by reusing the
variable instead of inventing one.

Exclude gosec's G104. It fires on bare call statements only, never on
defer/go, and honors a whitelist -- a strict subset of errcheck, which
also names the offending function; with uniq-by-line: false both would
print on the same line. Narrowing errcheck (exclude-functions,
std-error-handling) would reopen the gap.

Correct the check-rev rationale in the Makefile and AGENTS.md. #58
documented golangci-lint as only warning and exiting 0 on an unresolvable
--new-from-rev, which would let CI pass having linted nothing. It does
not: it warns, reports every issue in the repo, and exits 1. The guard
stays, but it buys a readable error message, not safety.

`make fix` now runs tidy -> --fix -> tidy -> lint. --fix is not a
fixpoint: its edits can trip a linter the fixing run never saw, and can
add or remove imports. The new ordering was exercised on a clean tree, so
it confirms the plumbing runs, not that the second pass caught a real
fixpoint miss.

Also document in AGENTS.md that .golangci.yaml is not self-contained --
whole-files: true is silently inert without one of the --new* modes, so
the file only works alongside the Makefile's --new-from-rev harness --
and that formatter findings are the exception to the ratchet: apply
`golangci-lint fmt ./...` repo-wide rather than letting a half-formatted
tree contradict its own config.

`golangci-lint run ./...` reports 0 issues repo-wide under this config, so
the whole-files ratchet takes on no new debt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@min0625
min0625 force-pushed the ci/lint-config-and-fix-target branch from 8569431 to 6132aa6 Compare September 1, 2026 11:35
@min0625
min0625 merged commit 7a27d43 into main Sep 1, 2026
6 checks passed
@min0625
min0625 deleted the ci/lint-config-and-fix-target branch September 1, 2026 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant