Releases: dakimura/guff
Release list
v0.7.0
What's Changed
- A cancel that never gets called, and a TLS config that skips its own check by @dakimura in #82
- SA5008 was misreading struct tags before it ever looked at the JSON by @dakimura in #83
- wsl_v5 was reading three of upstream's exemptions as violations by @dakimura in #84
- G122 followed the walk callback into closures upstream never enters by @dakimura in #85
//lint:file-ignore U1000stopped at the file; upstream's does not by @dakimura in #86- authelia's last three: wsl_v5's err check and a taint that never died by @dakimura in #87
- Two modernizers shipped their messages bare, and the case that would have caught it by @dakimura in #88
- minmax had one of upstream's two patterns, and the wrong idea of "equal" by @dakimura in #89
- forcetypeassert pointed at the
:=, and its whole fixture was six lines by @dakimura in #90 - guff type-checked syncthing's lib/model as ill-typed, and no gate could say so by @dakimura in #91
- SA4016 had one of upstream's two branches, and
^rendered as?by @dakimura in #92 - gosec's taint is four rules on one engine, and its call graph is not every function by @dakimura in #93
T[A, B](v)is a conversion, and*pis addressable whateverpwas by @dakimura in #94- Inference's "untyped" argument was tested for being a constant by @dakimura in #95
- An untyped constant does not convert to an interface by being representable as one by @dakimura in #96
- docs: the embedded-generic-instance failure narrows to one line of context by @dakimura in #97
- A generic instance one struct down still needs its methods expanded by @dakimura in #98
package p_testimports the test variant of p, not the production one by @dakimura in #99- A typed constant is rounded after every operation, not where it is used by @dakimura in #100
- errchkjson never matched
(*encoding/json.Encoder).Encodeby @dakimura in #101 - gosec G705: the sink is a method on an interface, and the writer decides by @dakimura in #102
byteandruneare their own Basic values, not spellings of uint8 and int32 by @dakimura in #103- Recovering a doc comment's position by line number threw away the column by @dakimura in #104
- godot: three defects — a missing collector, a wrong line, and a dropped blank line by @dakimura in #105
- Three linters reporting the right finding the wrong way by @dakimura in #106
- thanos no longer has an ill-typed package, so stop allowing it one by @dakimura in #107
- The seed compiled one copy of a package and ordered it by another's imports by @dakimura in #108
- 84 of 116 linters had never had a column or a severity checked by @dakimura in #109
- Widening 18 thin fixtures found 7 defects, two of them in the type checker by @dakimura in #110
- Widening 20 more fixtures: 4 defects, and one a unit test was demanding by @dakimura in #111
- 33 more fixtures widened: 2 defects, both invisible at one finding by @dakimura in #112
- Counting the Info.Defs holes instead of waiting to trip over them by @dakimura in #113
- Seven private
expr_string, three upstream renderers, seven defects by @dakimura in #114 - The last three
expr_string, and a fourth upstream renderer by @dakimura in #115 --isolate --linter Xwas overwriting the committed 116-target snapshot by @dakimura in #116--fixwas broken in three places, and fixing one moved nothing by @dakimura in #117- A tier that compares what
--fixwrites, and the one case guff wrote too much by @dakimura in #118 AddImportis one function ten checkers call, andFile.importswas a different list by @dakimura in #119- Three
--fixdefects in modernize, and the two the syntax error was hiding by @dakimura in #120 - importas renamed the alias and not the code that used it by @dakimura in #121
- QF1012 checked
*Tand passedT, and one hunk it leaves is upstream's bug by @dakimura in #122 DeleteStmtandDeleteUnusedVars, and no guff-side broken tree left by @dakimura in #123- revive's ReplacementLine, and a slot for "upstream is wrong" by @dakimura in #124
- govet's assign and unreachable, and a unit test that pinned the wrong answer by @dakimura in #125
- govet's timeformat and sigchanyzer by @dakimura in #126
- govet's stringintconv and composites by @dakimura in #127
- govet's hostport by @dakimura in #128
- godot's missing period, and an approximation not taken by @dakimura in #129
- whitespace's blank lines, and gocritic's first three fixes by @dakimura in #130
- staticcheck's S1002, S1004 and S1012, and the renderer that answers a different question by @dakimura in #131
- staticcheck's S1003 and S1021, and the case that closes is not the one worked on by @dakimura in #132
- staticcheck's S1016, S1028 and S1030, and two names for one type by @dakimura in #133
- Five more S checks, and a fixer that wrote nothing when it should have written a formatted file by @dakimura in #134
- Seven more S checks, and the three shapes a one-shape fixture was hiding by @dakimura in #135
- S1001, and the three defects a seven-shape fixture found in one run by @dakimura in #136
- goheader: the largest pending gap was two missing pieces by @dakimura in #137
- testifylint: seven checkers that rewrite the name and the arguments together by @dakimura in #138
- testifylint: the other nine checkers, and the case closes by @dakimura in #139
- dupword's comments, an upstream bug reproduced, and one half left undone by @dakimura in #140
- nlreturn and protogetter: "source not obtainable" was one command from false by @dakimura in #141
- Write down why nolint's fixes are a design decision, not a port by @dakimura in #142
- ginkgolinter: the message already carried the fix by @dakimura in #143
- A path boundary in the fixer: an edit must land in its own issue's file by @dakimura in #144
- SA1004: emitting more so that less is written by @dakimura in #145
- SA4013: two rules meeting in one hunk by @dakimura in #146
- SA4026 and SA1013: writing the rewrite that does not compile by @dakimura in #147
- SA1006 and SA6005, and three cases closing at once by @dakimura in #148
- staticcheck-sa closes: SA4029, SA1008, SA9004 by @dakimura in #149
- gocritic's extras.go: 48 lines of diff, none of them written by a linter by @dakimura in #150
- parens over-wrote by one hunk, and the ledger's refusal was one level too shallow by @dakimura in #151
- QF1005: seven defects behind four differing lines, one of them deleting a call by @dakimura in #152
- Add a Hindi README and put it in every language bar by @dakimura in #153
- dupword closes: a linter did less because of where a file sat in the crate graph by @dakimura in #154
- nolintlint gets its fixes: three cases close, and #142's dilemma was a false one by @dakimura in #155
- gocritic: 13 ruleguard suggestions, and the last 9 lines belong to another subsystem by @dakimura in #156
- wsl and wsl-v5 close, and the thin fixtures were hiding a reporting bug by @dakimura in #157
- The Fixer belongs inside the pipeline, before dedup and the caps by @dakimura in #158
- nakedret and embeddedstructfieldcheck: the inputs were already there by @dakimura in https://github.com/dakimura/guff/pull...
v0.6.0
What's Changed
- Make the gates binding: stop cancelling the tier that dates regressions by @dakimura in #16
- Bail to
go listwhen export data is asked for, instead of returning none by @dakimura in #18 - Lint the files git has not been told about yet by @dakimura in #19
- Report the integer conversions nothing has bounded (gosec G115) by @dakimura in #20
- Spare the parameters a config told revive to spare (allowRegex) by @dakimura in #21
- Peel the pointer off an elided composite literal (modernize atomictypes) by @dakimura in #22
- Leave the switch that cannot become an if-then alone (unnecessary-stmt) by @dakimura in #23
- Match gosec's call rules by syntax, the way gosec does (G404) by @dakimura in #24
- Keep the excluded files a test variant still has (modernize atomictypes) by @dakimura in #25
- Give testifylint a ground-truth gate, and fix the four things it found by @dakimura in #26
- Let errors.As take a target of any (govet errorsas) by @dakimura in #27
- Read protogetter's rules off protogetter, aliases and all by @dakimura in #28
- Read
uses the erroroff the call arguments, as nilerr does by @dakimura in #29 - Print the type parameters a generic receiver has (unused) by @dakimura in #30
- Follow the rename through the join, not just into the branches (SA5011) by @dakimura in #31
- Stay out of a kubebuilder package (modernize omitzero) by @dakimura in #32
- A test file is a filename, not a package name (revive IsTest) by @dakimura in #33
- A nil is only a swallowed error where an error is returned (nilerr) by @dakimura in #34
- Format like the gofumpt golangci-lint pins, and stop inventing a severity by @dakimura in #35
- Name the path in a dupl message, and stop dereferencing an interface (gocritic underef) by @dakimura in #36
- Honour revive's own disable directives by @dakimura in #37
- Compare signatures, not arena ids (SA5010) by @dakimura in #39
- Two places upstream is deliberately blind, and guff was not (composites, nilnil) by @dakimura in #38
- Four gates gitea found missing (gocritic badCall/mapKey, revive identical-*/if-return) by @dakimura in #40
- Three more of gitea's 33 (bodyclose, testifylint bool-compare, govet structtag) by @dakimura in #41
- Compare the printed source, not an approximation of it (SA4000) by @dakimura in #42
- Only a map read counts as a map read (SA6001) by @dakimura in #44
- Two gates jaeger found missing (gocritic stringXbytes, revive max-control-nesting) by @dakimura in #43
- httpresponse: the membership test is the signature, not the package by @dakimura in #45
- contextcheck: a capturing closure had nowhere to report, and a bare one had no business reporting by @dakimura in #46
- An import stub is not a package: fact producers were scheduled on metadata by @dakimura in #47
- A labelled break jumped to the top of the loop it was leaving by @dakimura in #48
- Two gocritic false positives argo-cd found (walker scope, regexpSimplify escapes) by @dakimura in #49
- Two argo-cd false positives (thelper synctest, formatters crossing a module boundary) by @dakimura in #50
- G122: the path stays the path after filepath.Clean by @dakimura in #51
- usetesting: the function that owns the closure owns the finding by @dakimura in #52
- Session log: the 2026-08-20 hunt pass, and what the next one should start with by @dakimura in #53
- contextcheck: a callee built for an imported package has no SSA package to ask by @dakimura in #54
- protogetter: a getter that returns a value cannot fill a pointer field by @dakimura in #55
- spancheck: report on the assignment and on the return, and only when there is one by @dakimura in #56
- unused: a generic interface draws no edge to the methods that implement it by @dakimura in #57
- bodyclose: a response is one that came out of a call by @dakimura in #58
- prealloc: the capacity is printed the way go/printer prints it by @dakimura in #59
- gocritic: a block comment is its own group by @dakimura in #60
- recvcheck: 2.12.2 excludes the encoding half, not the decoding half by @dakimura in #61
- Session log, second half: eight more, and the version the comparison is against by @dakimura in #62
- Record the bodyclose hypothesis that fit three shapes and broke on a repo by @dakimura in #63
- gosec G202: SQL string concatenation by @dakimura in #64
- One shared body for ST1023 and QF1011, and the right-hand side seen alone by @dakimura in #65
- An index binds to the position that absorbs it (govet printf) by @dakimura in #66
- A deferred call can assign to a named result, so don't lift it by @dakimura in #67
- The receiver decides whether a call aborts (SA5011) by @dakimura in #68
return f()returns f's results, not f's tuple by @dakimura in #69//lint:ignore U1000marks an object used by @dakimura in #70- Three loops: where rangeint points, what it refuses, and what
rangecosts by @dakimura in #71 - Three dapr false positives: an else, a getter, and a stack index by @dakimura in #72
- The package initializer is not a source function (gosec) by @dakimura in #73
- G101's gate is a dictionary, not a character count (port zxcvbn) by @dakimura in #74
- Three ways a response goes unclosed, and two that are not calls at all (bodyclose) by @dakimura in #75
- unparam's other three families, and the results a range-over-func
returnwas dropping by @dakimura in #76 - A response stored into a captured variable, and the closure that closes it by @dakimura in #77
os-setenvis on by golangci's defaults, and a conversion is pure by @dakimura in #78cargo testthat never starts is a file count, not a slow test by @dakimura in #79unusedcolours a graph from its roots, and go1.26'snewtakes a value by @dakimura in #80- Release 0.6.0: 64 commits of upstream parity, re-measured by @dakimura in #81
Full Changelog: v0.5.0...v0.6.0
v0.5.0
A performance release. Same linters, same findings, same config — less time and a third less memory.
Faster and lighter
Measured on prometheus ./... (Darwin arm64, 10 core), v0.4.1 → v0.5.0:
| v0.4.1 | v0.5.0 | ||
|---|---|---|---|
| cold wall | 2.33s | 1.98s | −15% |
| warm wall (one file changed) | 1.51s | 1.36s | −10% |
| peak memory | 2517 MiB | 1717 MiB | −32% |
-j 1 wall |
5.48s | 4.90s | −11% |
Most of it came from one thing. Three enums that sit in million-element arrays were each sized by one or two oversized variants — a Rust enum costs what its largest variant costs, so a 24-byte expression was paying for a 192-byte one:
| before | after | |
|---|---|---|
Expr |
192 B | 80 B |
Stmt |
656 B | 320 B |
TypeData |
112 B | 72 B |
ObjectData |
104 B | 64 B |
Boxing the outliers took every AST node, type and object in the process down with them. Details and the measurements that sized each one: docs/PERF_TASKS_V9.md.
vs golangci-lint
Real repositories, their own .golangci.yml, cold cache, golangci-lint 2.12.2:
| Repository | golangci-lint | guff | Speedup |
|---|---|---|---|
| grafana | 290.4s | 17.8s | 16× |
| consul | 39.4s | 4.7s | 8× |
| helm | 17.4s | 1.3s | 13× |
| k9s | 14.3s | 2.1s | 7× |
| caddy | 8.7s | 0.91s | 10× |
| containerd | 4.1s | 0.40s | 10× |
| gin | 3.9s | 0.37s | 11× |
| cobra | 1.4s | 0.22s | 6× |
Reproduce with ./benchmarks/run.sh --oss --tier pr,nightly. Full detail: SCOREBOARD.md.
grafana's golangci-lint number is 290.4s here against 394.8s in the v0.4.1 README, so the ratio reads 16× where it used to read 17× — a different checkout, a different golangci-lint, a different day. Both sides of every row are this run's measurements.
No behaviour changes
Findings are byte-identical to v0.4.1 across the regression corpus, at -j 1 and -j N, with node-kind masks on and off. 114/114 golangci-lint v2 linters, compat golden 81/81, 3119 tests.
Also in this release
Claude Code is now called out alongside Cursor in the agent docs — put the lint block in CLAUDE.md and it applies every session. See docs/AGENTS.md.
What's Changed
- Persist guff's analysis cache across CI runs by @dakimura in #1
- Halve the analyze phase: an inspector that skips the 97% it used to read, and a mutex ten workers queued on by @dakimura in #2
- The compat gate has never passed in CI: the runner's Go was two minors behind the baselines by @dakimura in #3
- Two fixtures were linux-only, so the golden never compared them by @dakimura in #6
- Comment linters were paying for object resolution none of them read by @dakimura in #5
- The shape ledger was a laptop measurement the runner could not reproduce by @dakimura in #4
- One question asked twenty-five times, and a speculation that had never once hit by @dakimura in #7
- Every package's results stayed alive until every other package caught up by @dakimura in #8
-j 1kept every package's results alive the way the wavefront used to by @dakimura in #9- Box the two Expr variants that were sizing every AST node (and finish PERF_TASKS_V8) by @dakimura in #10
- Box the two TypeData variants that were sizing every arena slot by @dakimura in #11
- Box the ObjectData variant that was sizing every object slot by @dakimura in #12
- Correct the round's closing numbers, measured on merged main by @dakimura in #13
- Re-lock both regress baselines on the round's numbers by @dakimura in #14
- Release 0.5.0: re-measure the OSS benchmarks and refresh the docs by @dakimura in #15
New Contributors
Full Changelog: v0.4.1...v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Full Changelog: https://github.com/dakimura/guff/commits/v0.1.0