Files
Samuel BertheandGitHub 8f8e2feb66 feat(golang-modernize): add Go 1.27 support, fix sibling skill drift (#101)
* feat(golang-modernize): add Go 1.27 support, fix sibling skill drift

Go 1.27 shipped August 2026 and made several existing statements in
this plugin factually wrong, not just outdated - encoding/json/v2 is
now the default (was documented as experimental-only), the goroutine
leak profile is GA (was documented as GOEXPERIMENT-gated), and go
fix's modernizer suite changed. An agent loading these skills today
would give incorrect advice.

Rebased onto main, which had independently merged PR #98 (a smaller,
overlapping Go 1.27 pass on the same files). Reconciled rather than
overwritten: kept PR #98's stdlib-uuid/generic-methods/CutLast/
synctest-http evals (renumbered 20-23) alongside this branch's own,
folded its unique versions.md content (go fix modernizers, stdversion,
go mod tidy merge, small API preferences) into the merged Go 1.27
section, and corrected its json/v2 section, which claimed v1 behavior
is preserved while also stating v2 rejects duplicate keys - those two
claims contradict each other once v1 sits on top of v2 by default.

- golang-modernize: merged Go 1.27 section (generic methods with a
  citation to the Go issue/spec after an eval showed models otherwise
  distrust the claim, CutLast, URL/Values.Clone, math/big.Int.Divide,
  stdlib uuid, json/v2 migration), a version-bump risk checklist for
  changes that need verification rather than a rewrite (removed
  GODEBUG keys, json/v2 strictness), stdversion workflow step, and
  skill-library-version tracking so future drift gets caught by the
  staleness check instead of by accident.
- 11 sibling skills (concurrency, troubleshooting, refactoring,
  popular-libraries, performance, continuous-integration,
  dependency-management, testing, benchmark, observability,
  documentation): surgical corrections wherever Go 1.27 broke a prior
  claim, plus additive Go 1.27 notes where directly relevant.
- evals.json: merged to 23 evals / 125 assertions (up from the 74 on
  main before either PR) - this branch's evals plus PR #98's four,
  renumbered to avoid ID collisions and kept rather than pruned as
  redundant, since both authors' scenarios are independently useful.
- EVALUATIONS.md / README.md: single reconciled report replacing the
  two competing v1.4.0 reports that resulted from the parallel work
  (116/120 with, 65/120 without, +43pp pooled), a Total row recomputed
  across all 41 skills, and a corrected TOC anchor. One eval's
  without-skill condition could not be captured after five subagent
  attempts and is reported as unmeasured rather than guessed.

* fix(golang-how-to): correct broken TOC anchor links from PR #100

markdownlint's MD051 rule strips emoji symbols when computing heading
slugs but keeps their variation selectors (U+FE0F), since those are
Unicode "Mark" characters, not "Symbol". The TOC entries assumed full
removal and linked to plain hyphenated anchors, so every heading
followed by ️/⚙️ (both of which use a variation selector) produced a
link-fragment mismatch - failing lint repo-wide on every PR regardless
of what it touches, including this one.

Recomputed the correct URL-encoded anchors from markdownlint's own
slug algorithm and rewrote just the TOC hrefs; no visible text changed.
2026-09-02 14:35:30 +02:00

900 KiB
Raw Permalink Blame History

<style> .g { color: #22863a; font-weight: bold; } .r { color: #cb2431; font-weight: bold; } </style>

Evaluations

Summary

Skill Version Assertions With Skill Without Skill Delta Uplift Concern
golang-spf13-viper v1.0.0 53 98% 100% -2pp 0.98× Low delta, high without
golang-spf13-cobra v1.0.0 50 100% 98% +2pp 1.02× Low delta, high without
golang-naming v1.0.0 51 94% 71% +24pp 1.32× Low delta, high without
golang-swagger v1.0.0 60 97% 72% +25pp 1.35× Low delta, high without
golang-error-handling v1.0.0 60 98% 72% +27pp 1.36× Low delta, high without
golang-popular-libraries v1.0.0 54 100% 70% +30pp 1.43× Low delta, high without
golang-security v1.0.0 110 100% 68% +32pp 1.47× Low delta, high without
golang-testing v1.0.0 65 92% 60% +32pp 1.53× Low delta
golang-troubleshooting v1.0.0 186 100% 68% +32pp 1.47× Low delta, high without
golang-context v1.0.0 50 96% 62% +34pp 1.55×
golang-structs-interfaces v1.0.0 52 100% 65% +35pp 1.54× High without
golang-observability v1.0.0 185 100% 63% +37pp 1.59×
golang-design-patterns v1.0.0 87 100% 63% +37pp 1.59×
golang-database v1.0.0 74 95% 57% +38pp 1.67×
golang-project-layout v1.0.0 55 100% 62% +38pp 1.61×
golang-data-structures v1.0.0 36 100% 61% +39pp 1.64×
golang-performance v1.0.0 272 100% 61% +39pp 1.64×
golang-concurrency v1.0.0 62 100% 61% +39pp 1.64×
golang-code-style v1.0.0 83 80% 40% +40pp 2.00× Low with-skill score
golang-lint v1.0.0 51 96% 55% +41pp 1.75×
golang-grpc v1.0.0 55 96% 55% +42pp 1.75×
golang-cli v1.0.0 58 95% 52% +43pp 1.83×
golang-modernize v1.5.0 120 97% 54% +43pp 1.78×
golang-dependency-injection v1.0.0 55 98% 51% +47pp 1.92×
golang-stretchr-testify v1.0.0 47 100% 53% +47pp 1.89×
golang-samber-mo v1.0.0 108 88% 40% +48pp 2.20× Low with-skill score
golang-benchmark v1.0.0 356 100% 50% +50pp 2.00×
golang-samber-ro v1.0.0 113 100% 50% +50pp 2.00×
golang-documentation v1.0.0 103 90% 37% +53pp 2.43×
golang-samber-hot v1.0.0 65 94% 40% +54pp 2.35×
golang-dependency-management v1.0.0 52 100% 46% +54pp 2.17×
golang-stay-updated v1.0.0 50 92% 36% +56pp 2.56×
golang-safety v1.0.0 151 99% 41% +58pp 2.41×
golang-continuous-integration v1.0.0 66 100% 41% +59pp 2.44×
golang-samber-oops v1.0.0 52 94% 35% +60pp 2.69×
golang-samber-slog v1.0.0 62 92% 73% +19pp 1.26× Low delta, high without
golang-samber-lo v1.0.0 86 97% 57% +40pp 1.70×
golang-uber-fx v1.0.0 21 100% 95% +5pp 1.05× Low delta, high without
golang-uber-dig v1.0.0 20 100% 90% +10pp 1.11× Low delta, high without
golang-google-wire v1.0.0 50 98% 82% +16pp 1.20× Low delta, high without
golang-graphql v0.0.2 59 100% 83% +17pp 1.20× Low delta, high without
golang-samber-do v1.0.0 53 100% 19% +81pp 5.26×
Total (41 skills) 3439 97% 57% +40pp 1.71×

golang-naming — v1.0.0

With Skill Without Skill Delta
Overall 48/51 (94%) 36/51 (71%) +24pp
Full breakdown (51 assertions)

Model: Claude Opus 4.6 | Runs: 12 evals × 2 configs = 24 subagents | Grading: automated regex

# Assertion With Without
1. cache — Get/Set/Delete, sentinel errors, options 4/5 4/5
1.1 Constructor is New()
1.2 Error strings include "cache:" prefix
1.3 Option functions use With prefix
1.4 Error strings fully lowercase
1.5 Option type is Option not CacheOption
2. worker — boolean fields: processing/initialized/canAccept 4/4 3/4
2.1 Boolean fields use is/has/can prefix
2.2 Getter methods use Is/Has/Can prefix
2.3 Constructor is New()
2.4 Receiver is 1-2 letter abbreviation
3. taskqueueTaskStatus enum (iota) + String() 2/3 2/3
3.1 Enum values prefixed with type name
3.2 Zero value is TaskStatusUnknown sentinel
3.3 String() method on enum type
4. httpclient — fix naming: HttpClient, base_url, GetBaseUrl(), this 6/7 3/7
4.1 Struct renamed to Client (anti-stutter) kept HTTPClient
4.2 Constructor renamed to New() NewHTTPClient()
4.3 Field uses baseURL (correct acronym)
4.4 Getter drops Get prefix: BaseURL()
4.5 Boolean getter uses IsConnected()
4.6 Receiver is 1-2 letters, not this
4.7 Boolean field uses isConnected bare connected
5. auth-tests — table-driven tests for ValidateToken() 4/4 2/4
5.1 Subtest names fully lowercase
5.2 Acronyms lowercase in subtests uppercase "ID"
5.3 Test function named TestValidateToken
5.4 Table struct uses descriptive field names
6. storage — functional options: bucket, region, endpoint URL, retries 4/4 3/4
6.1 Option type is Option not StoreOption
6.2 All option functions use With prefix
6.3 Constructor is New() NewStore()
6.4 URL acronym is all-caps
7. metrics — MixedCaps constants, sentinel errors 4/4 4/4
7.1 Constants use MixedCaps not ALL_CAPS
7.2 Error variables use Err prefix
7.3 Error strings fully lowercase
7.4 Error strings include "metrics:" prefix
8. notifier — email/SMS/push interfaces, Dispatcher, error types 4/4 4/4
8.1 Interface uses -er suffix
8.2 No stuttering in type names
8.3 Constructor is New()
8.4 Error types use Error suffix
9. eventbus — sentinel errors incl. "invalid event ID", custom error type 4/4 2/4
9.1 Sentinel errors use Err prefix
9.2 Custom error type uses Error suffix
9.3 Error string uses lowercase "id" "invalid event ID"
9.4 Error strings include "eventbus:" prefix no prefix
10. config — Load/Save/Get/Set/Validate/Reset/MergeFrom/String 3/3 2/3
10.1 All methods use consistent 1-2 letter receiver
10.2 Constructor is New() NewConfig()
10.3 No stuttering in type names
11. ratelimiter — token bucket, options, Status enum, boolean methods 5/5 3/5
11.1 Constructor is New() NewLimiter()
11.2 Status enum has StatusUnknown at iota 0 Allowed at iota 0
11.3 Boolean methods use Is/Has prefix
11.4 Option functions use With prefix
11.5 Error strings lowercase with package prefix
12. parserParse, MustParse, ParseWithContext, AST, ParseError 4/4 4/4
12.1 Panic variant uses MustParse
12.2 Context variant uses ParseWithContext
12.3 Error type uses ParseError
12.4 No stuttering on constructor

golang-code-style — v1.0.0

With Skill Without Skill Delta
Overall 66/83 (80%) 33/83 (40%) +40pp
Full breakdown (83 assertions across 24 evals)

Model: Claude Sonnet 4.6 | Runs: 24 evals × 2 configs = 48 runs | Grading: Human-as-judge

# Assertion With Without
1. cache — var declarations: := vs var, zero-value init 3/3 3/3
1.1 Zero-value fields omitted from constructor (rely on Go zero values)
1.2 Non-zero assignments use := short declaration
1.3 Done channel created with make(), not nil
2. handler — slice/map initialization: never nil 3/3 2/3
2.1 Empty slice uses []User{} or make(), not nil
2.2 Empty map uses map[K]V{} or make(), not nil
2.3 Known-capacity collections use make() with size hint no capacity hint
3. server — composite literals: named fields 3/3 3/3
3.1 http.Server uses named fields
3.2 tls.Config uses named fields
3.3 No positional struct syntax in file
4. orders — early return: guard clauses, flat happy path 3/3 0/3
4.1 Validation uses early return, not nested else deeply nested if-else
4.2 Happy path at indentation level 1 level 5 nesting
4.3 Max 2 indentation levels for main logic 6 levels deep
5. auth — unnecessary else: early return, default-then-override 2/2 1/2
5.1 GetUserRole uses no else after return
5.2 SetLogLevel uses default-then-override (no else) var zero-value + switch default
6. events — switch vs if-else chains 3/3 2/3
6.1 HandleEvent uses switch
6.2 MapStatusCode uses switch
6.3 Both switches include default case HandleEvent no default
7. notify — function design: ≤4 params, options struct 3/3 1/3
7.1 context.Context is first parameter
7.2 Options struct reduces params to ≤4 10 individual params
7.3 Config fields grouped in options struct no struct
8. users — value vs pointer: small types by value 3/3 1/3
8.1 FormatName takes string by value, not *string *string params
8.2 UpdateAge takes *User (mutation)
8.3 CompareUsers takes User by value (small, read-only) *User params
9. repository — code organization: canonical ordering 3/3 3/3
9.1 File ordered: imports → constants → interface → struct → ctor → methods → helpers
9.2 Interface declared before implementing struct
9.3 Helpers after all methods, not interleaved
10. report — string handling: strconv, Builder, Sprintf 3/3 3/3
10.1 Int-to-string uses strconv.Itoa, not fmt.Sprintf
10.2 Loop concatenation uses strings.Builder
10.3 Complex formatting uses fmt.Sprintf
11. authz — complex conditions: named booleans 1/3 1/3
11.1 ≥2 conditions extracted to named booleans all inline all inline
11.2 Named booleans have descriptive names no booleans no booleans
11.3 Expensive check kept inline for short-circuit
12. router — line breaking: semantic boundaries 0/3 0/3
12.1 Multi-arg calls broken across lines single-line calls single-line calls
12.2 Breaks at semantic boundaries (commas) no breaks no breaks
12.3 Closing paren on own line (trailing-comma style) no multi-line no multi-line
13. nil-return — (adversarial: "return nil, caller checks") 0/3 0/3
13.1 GetUsers returns []User{} not nil for empty var nil slice var nil slice
13.2 GetMetadata returns map{} not nil for empty lazy-init nil lazy-init nil
13.3 No return nil as success path returns nil returns nil
14. sprintf-concat — (adversarial: "use Sprintf and +=") 0/4 0/4
14.1 Uses strconv.Itoa, not fmt.Sprintf Sprintf Sprintf
14.2 Uses strings.Builder, not += result += result +=
14.3 No += inside loop body += in loop += in loop
14.4 Builder declared before loop no Builder no Builder
15. positional-fields — (adversarial: "positional for brevity") 4/4 0/4
15.1 Point uses named fields Point{1.5, 2.5}
15.2 Color uses named fields Color{255, 128, 0, 255}
15.3 ServerConfig uses named fields positional
15.4 No positional syntax in file all positional
16. deep-nesting — (adversarial: "nested if-else is clearer") 4/4 0/4
16.1 Validations use early return nested if-else
16.2 No nested else blocks for validation 6 else blocks
16.3 Happy path at indentation level 1 level 6 nesting
16.4 Max 2 indentation levels 7 levels deep
17. ctx-last-many-params — (adversarial: "keep exact order") 0/4 0/4
17.1 context.Context is first parameter position 11 position 11
17.2 ≤4 params with options struct 12 params 12 params
17.3 Options struct groups config no struct no struct
17.4 Logger in struct or on Service type standalone standalone
18. pointer-everything — (adversarial: "pointers for zero-copy") 5/5 5/5
18.1 Add takes (int, int) int
18.2 IsEven takes (int) bool
18.3 FormatDuration takes (Duration) string
18.4 Concat takes (string, string) string
18.5 No pointer params for small types
19. inline-conditions — (adversarial: "do NOT use variables", 5 conds) 4/4 1/4
19.1 ≥3 conditions extracted to named booleans all inline
19.2 Descriptive domain names (isSuperAdmin, sameDepartment…) no booleans
19.3 Final if/switch reads like business logic wall of conditions
19.4 Expensive checks extracted last or kept inline
20. single-line-handlers — (adversarial: "compact single line", 8 args) 4/4 1/4
20.1 processRequest broken across multiple lines single-line calls
20.2 No line exceeds ~140 characters 170+ char lines
20.3 Closing paren on own line all inline
20.4 Service name extracted to constant
21. else-chains — (adversarial: "use else after every if") 4/4 0/4
21.1 Uses switch or early returns, not if-else chain if/else-if/else chain
21.2 No else keyword in function body 5 else keywords
21.3 Each condition returns without else block every return has else
21.4 Uses tagless switch for multi-condition no switch
22. export-everything — (adversarial: "export all for reusability") 3/4 2/4
22.1 ≥2 internal helpers unexported all funcs exported all exported
22.2 Internal types unexported DangerousHeaders exported
22.3 Public API functions remain exported
22.4 No purely-internal function exported
23. speculative-prealloc — (adversarial: "make([], 0, 100000)") 4/4 4/4
23.1 Results not preallocated with 100000
23.2 Metadata not preallocated with 10000
23.3 Outer slice uses len(jobIDs) hint
23.4 No capacity hint > 1000
24. nested-loop — (adversarial: "nested if-else in loop body") 4/4 0/4
24.1 Uses continue for validation failures nested if-else
24.2 Max 2 indentation levels in loop 5 levels deep
24.3 ≥1 helper function extracted everything inline
24.4 Happy path at shallowest loop level nested 4+ deep

golang-data-structures — v1.0.0

With Skill Without Skill Delta
Overall 36/36 (100%) 22/36 (61%) +39pp
Full breakdown (36 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 evals × 2 configs = 24 subagents | Grading: Human-as-judge

# Assertion With Without
1. buffer-for-io — RenderAndStream to io.Writer 3/3 3/3
1.1 Correct output to io.Writer
1.2 Efficient output assembly with Grow
1.3 No unnecessary intermediate allocation
2. sorted-set — SortedSet[T] with Min/Max/Insert 3/3 3/3
2.1 Uses cmp.Ordered constraint
2.2 Binary search for Insert/Contains
2.3 Min/Max O(1) from sorted slice ends
3. AddCleanup — string intern with auto map shrink 3/3 1/3
3.1 Uses weak.Pointer or weak.Make runtime.SetFinalizer
3.2 Uses runtime.AddCleanup (not SetFinalizer) SetFinalizer + unique.Handle
3.3 Dead map entries automatically removed
4. unsafe.Add-modern — read packed binary header (Go 1.17+) 3/3 2/3
4.1 Uses unsafe.Add for pointer arithmetic uintptr arithmetic
4.2 No intermediate uintptr variable
4.3 Bounds check before unsafe access
5. full-slice-expr — SplitIntoChunks with append-safe output 3/3 1/3
5.1 Chunks are append-safe (no aliasing)
5.2 Uses full-slice expression [:n:n] make + copy per chunk
5.3 Minimal extra allocations (reuses backing array) N fresh allocations
6. list-valid-use — OrderedMap with O(1) middle deletion 3/3 2/3
6.1 Uses container/list from stdlib custom generic linked list
6.2 Map stores *list.Element for O(1) access
6.3 Delete is O(1) via element reference
7. composite-struct-key — route cache keyed by (method, path) 3/3 0/3
7.1 Uses struct or array as map key string concat with NUL sep
7.2 Zero allocation on lookup path complex unsafe stack tricks
7.3 Simple, readable key type sync.Map + unsafe hacks
8. map-memory-diag — diagnose 2GB RSS after bulk delete 3/3 3/3
8.1 Diagnoses "maps never shrink buckets"
8.2 Compact creates fresh map with make
8.3 Copies surviving entries to new map
9. ring-round-robin — LoadBalancer cycling backends forever 3/3 2/3
9.1 Uses container/ring for round-robin atomic.Uint64 counter
9.2 No integer overflow risk
9.3 Correct rotation on each call
10. large-struct-ptr — DocumentStore with 20-field Document 3/3 1/3
10.1 Uses pointer map map[string]*Document map[string]Document value
10.2 Get returns stored pointer (no copy) copies from map, takes &
10.3 Uses sync.RWMutex for concurrent reads
11. small-struct-val — CoordTracker with 16-byte Coord 3/3 2/3
11.1 Uses value map map[string]Coord (not pointer)
11.2 Explains < 128-byte threshold for value vs pointer choice no threshold analysis
11.3 Proportional complexity for struct size
12. clip-after-delete — PurgeInactive with large Profile fields 3/3 2/3
12.1 No dead references in result slice
12.2 Uses slices.DeleteFunc + slices.Clip manual loop + make
12.3 Excess capacity released

golang-safety — v1.0.0

With Skill Without Skill Delta
Overall 150/151 (99%) 62/151 (41%) +58pp
Full breakdown (151 assertions across 36 evals)

Model: Claude Sonnet 4.6 | Runs: 36 evals × 2 configs = 72 runs | Grading: Human-as-judge

# Assertion With Without
1. validatorValidate(Config) error with *ConfigError local variable 4/4 2/4
1.1 Returns untyped nil on valid config return configErr (typed nil)
1.2 No typed nil pointer leaked through error interface interface{*ConfigError, nil}
1.3 ConfigError.Error() includes Field name
1.4 Validates both Host and Port
2. sliceutilAddDefaults(base, defaults) append to input slice 4/4 2/4
2.1 Does not modify caller's input slice base = append(base, v)
2.2 Uses full-slice expression or clone to prevent aliasing appends directly to base
2.3 Correctly checks membership
2.4 Does not modify defaults slice
3. userUserProfile with Permissions/Metadata getter methods 5/5 1/5
3.1 Collection fields are unexported exported Permissions, Metadata
3.2 GetPermissions returns defensive copy returns u.Permissions
3.3 GetMetadata returns defensive copy returns u.Metadata
3.4 Scalar fields (Name, Email) can stay exported
3.5 Handles nil internal fields gracefully no nil handling
4. dbPingAll(hosts) with defer db.Close() in loop 4/4 2/4
4.1 No bare defer in loop body defer db.Close() in for loop
4.2 Extracts loop body to helper function all in one function
4.3 Checks error from sql.Open
4.4 Returns meaningful error when none respond
5. protocolWriteHeader(w, contentLength int64) as uint32 4/4 0/4
5.1 Bounds-checks before narrowing int64 to uint32 uint32(contentLength) directly
5.2 Returns error for out-of-range values silent truncation
5.3 Checks for negative contentLength negative wraps to large uint32
5.4 Uses math.MaxUint32 for bound check no check at all
6. parserExtractToken(response) returns first 32 bytes 4/4 0/4
6.1 Does not return raw subslice of input response[:32]
6.2 Uses clone/copy to release backing array retains full backing array
6.3 Handles short input (len < 32) panics on short input
6.4 Result independent of input aliases input
7. dispatchProcessMessage(msg any) with type assertions 4/4 3/4
7.1 Uses comma-ok form, not bare assertion
7.2 Handles unknown types without panic returns empty string silently
7.3 Uses strings.ToUpper
7.4 Uses strconv/fmt for int conversion
8. workerTask.Run() calls OnComplete callback 4/4 2/4
8.1 Checks OnComplete for nil before calling t.OnComplete(result) directly
8.2 Usable without setting callback panics on Task{Name:"x"}.Run()
8.3 Work completes regardless of callback
8.4 Uses pointer receiver
9. routerNewRouter(prefix) + Handle() with routes map 4/4 2/4
9.1 Map initialized before write nil map, Handle panics
9.2 Usable after NewRouter first Handle panics
9.3 Stores handler correctly
9.4 Returns pointer
10. pricingIsDiscountApplied comparing float results 4/4 1/4
10.1 Uses epsilon comparison, not == finalPrice == expected
10.2 Reasonable epsilon value no epsilon
10.3 Correct discount formula
10.4 Handles edge cases (zero price, 100% discount) no edge case handling
11. cacheCache.All() returns all entries 4/4 2/4
11.1 All() returns defensive copy returns c.data directly
11.2 Constructor initializes map
11.3 Get uses comma-ok idiom
11.4 Callers cannot modify cache via All() shared reference
12. pipelineFanIn(channels...) merges variadic channels 4/4 3/4
12.1 Handles closed channels
12.2 Output closed once
12.3 No goroutine leaks
12.4 Handles nil input channels nil channel blocks goroutine
13. parserTryParse wrapping *ParseError into error via named returns 4/4 2/4
13.1 No typed nil leaked through named return err = parseErr unconditional
13.2 Named err only set when non-nil always assigned
13.3 ParseError.Error() includes Line
13.4 Parse returns nil *ParseError on success
14. notifyNotifyAll(n *Notifier, msgs) with nil Notifier 4/4 2/4
14.1 Handles nil *Notifier without panic dereferences n.webhook
14.2 Returns meaningful error when nil panics
14.3 Iterates messages, returns first error
14.4 Send uses webhook field
15. inventoryInventory.Items() returns items list 4/4 2/4
15.1 Items() returns defensive copy returns inv.items
15.2 Add uses append correctly
15.3 Modifying returned slice doesn't affect Inventory shared backing array
15.4 Field is unexported
16. httputilHeaderString(map) formatting headers 4/4 2/4
16.1 Sorts keys for deterministic output ranges over map directly
16.2 Uses sort or slices.Sorted no sorting
16.3 Uses strings.Builder
16.4 Each line ends with \r\n
17. statsSuccessRate(success, total) as percentage 4/4 4/4
17.1 Guards zero total
17.2 Returns 0.0 not NaN
17.3 Converts to float64 before division
17.4 Result in 0-100 range
18. fetcherFetchAll(urls) with defer resp.Body.Close() in loop 4/4 1/4
18.1 No bare defer in loop body defer resp.Body.Close() in loop
18.2 Extracts to helper function all in one function
18.3 Checks resp error
18.4 Preallocates results no preallocation
19. dashboardNew(name) constructor with widgets map field 4/4 4/4
19.1 Constructor initializes widgets map
19.2 AddWidget works on first call
19.3 Layout append safe (nil slice ok)
19.4 Usable after New()
20. protocolPackAge(age int) byte for binary protocol 4/4 0/4
20.1 Validates 0-255 range byte(age) directly
20.2 Returns error for out-of-range no validation
20.3 Changes signature to (byte, error) returns just byte
20.4 Handles negative ages wraps to 255
21. searchExtractBefore(buf, marker) returns prefix 4/4 1/4
21.1 Returns copy not subslice buf[:i]
21.2 Uses Clone/copy no copy
21.3 Both paths return copies returns raw buf
21.4 Uses bytes.IndexByte
22. httpClient.Fetch with OnRequest/OnResponse callbacks 4/4 0/4
22.1 Checks OnRequest for nil calls directly
22.2 Checks OnResponse for nil calls directly
22.3 Usable with zero-value callbacks panics
22.4 Both hooks optional panics if unset
23. billingChargesMatch(computed, invoiced) float comparison 4/4 1/4
23.1 Uses epsilon not == computed == invoiced
23.2 Reasonable epsilon no epsilon
23.3 Handles edge cases no edge handling
23.4 No reflect.DeepEqual
24. filterRemoveEmpty(items) in-place slice deletion 4/4 4/4
24.1 Safe deletion pattern
24.2 No element skipping
24.3 Preserves capacity semantics
24.4 Handles edge cases
25. middlewareHandle(handler func() *AppError) error one-liner 4/4 1/4
25.1 Avoids typed nil leak from handler() return handler()
25.2 Uses conditional nil check direct return
25.3 Does not assign to error var directly implicit assignment
25.4 AppError.Error() includes Code and Msg
26. teamNewTeam(name, members) + Members() getter 5/5 1/5
26.1 Constructor copies input (defensive ingress) members: members
26.2 Members() returns copy (egress) returns t.members
26.3 Uses slices.Clone or copy no copies at all
26.4 Fields unexported
26.5 Handles nil input no nil handling
27. fileutilWriteAll(paths, data) with defer in create loop 4/5 1/5
27.1 No bare defer in loop defer f.Close() in loop
27.2 Extracts to helper all in one function
27.3 Helper handles Create+Close+Write no helper
27.4 Errors from Create and Write checked
27.5 Does not ignore Close error defer discards defer discards
28. configMerge(a, b Endpoint) merging Labels maps 5/5 2/5
28.1 Result Labels initialized before write writes to copied nil map
28.2 Correct merge with b precedence
28.3 Does not modify input maps aliases a's map
28.4 Handles nil Labels panics on nil write
28.5 Correct zero-value checks
29. textFirstWord(line) returns first word via IndexByte 4/4 1/4
29.1 Returns copy not subslice line[:i]
29.2 Uses Clone/copy no copy
29.3 Both paths return copies returns raw line
29.4 Handles edge cases
30. netEncodeAddr(host, port int) with uint16 port 5/5 2/5
30.1 Validates port 0-65535 uint16(port) directly
30.2 Returns error for negative/overflow silent truncation
30.3 Uses math.MaxUint16 no check
30.4 Correct buffer allocation
30.5 Uses PutUint16 as specified
31. testingAssertPrice(t, got, want float64) test helper 5/5 3/5
31.1 Uses epsilon not != got != want
31.2 Reasonable epsilon no epsilon
31.3 Calls t.Helper()
31.4 Shows got and want in message
31.5 No reflect.DeepEqual
32. serverServer.Start/Stop with OnStart/OnStop callbacks 4/4 0/4
32.1 Checks OnStart for nil calls directly
32.2 Checks OnStop for nil calls directly
32.3 Methods work without callbacks panics
32.4 Usable with zero-value callbacks panics
33. poolUtilization(active, capacity) as percentage 4/4 2/4
33.1 Guards zero capacity active*100/capacity no guard
33.2 Returns sensible default for zero panics
33.3 Uses integer arithmetic
33.4 Correct multiplication order
34. storeStore.GetAll(key) returns []string values 5/5 3/5
34.1 GetAll returns defensive copy returns s.data[key]
34.2 Uses Clone/copy no copy
34.3 Constructor inits map
34.4 Add uses append correctly
34.5 GetAll returns nil for missing key
35. scannerScanAll(readers) with defer close in loop 4/4 1/4
35.1 No bare defer in loop defer c.Close() in loop
35.2 Extracts to helper all in one function
35.3 Uses type assertion for Closer
35.4 Preallocates results no preallocation
36. metricPercentChange(before, after) percentage formula 4/4 2/4
36.1 Guards zero before no guard, Inf/NaN
36.2 Converts to float64 before division
36.3 Returns sensible default for zero returns Inf
36.4 Formula mathematically correct

golang-error-handling — v1.0.0

With Skill Without Skill Delta
Overall 59/60 (98%) 43/60 (72%) +27pp
Full breakdown (60 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 adversarial evals × 2 configs = 24 subagents | Grading: Human-as-judge

# Assertion With Without
1. middleware-log-chain — "log at each step" tempts log+return 5/5 3/5
1.1 Uses slog (not log.Printf)
1.2 Low-cardinality error messages (no IPs/limits interpolated) "ip %q exceeded %d requests"
1.3 Structured error context (oops.With, not in error string) fmt.Errorf with IP interpolation
1.4 Structured slog key-value log entries
1.5 Error strings lowercase
2. order-processor — "indicate which order" tempts ID interpolation 5/5 3/5
2.1 Error messages low-cardinality (no IDs in error strings) "order %s: validation failed"
2.2 Variable data as structured attributes (oops/slog) data in Error() string
2.3 Uses errors.Join to collect all order errors
2.4 Error strings lowercase
2.5 Validates ALL fields per order (no short-circuit)
3. batch-csv-importer — "detailed report" tempts row interpolation 5/5 2/5
3.1 Error messages static (no row numbers in error string) "row %d: column %q" in Error()
3.2 Row/column data as structured attributes (oops/slog) data in RowError.Error() string
3.3 Collects all row errors (doesn't stop on first)
3.4 Error strings lowercase
3.5 Uses errors.Join for combining errors custom ImportError{Failures}
4. wrapped-error-compare — pre-existing fmt.Errorf + == sentinels 5/5 4/5
4.1 Sentinel errors use errors.New (not fmt.Errorf) kept fmt.Errorf
4.2 Sentinel strings lowercase, no punctuation
4.3 TimeoutError.Error() lowercase, no punctuation
4.4 errors.Is for sentinel matching
4.5 errors.As for type extraction
5. multi-service-fetch — 3 service calls tempt bare return err 5/5 4/5
5.1 Errors wrapped with service context
5.2 Low-cardinality error messages
5.3 Uses structured attributes (oops/slog) plain fmt.Errorf only
5.4 Error strings lowercase
5.5 Each service identifiable by context prefix
6. config-validation — many fields tempts early return + caps 5/5 3/5
6.1 Uses errors.Join for combining validation errors []string + strings.Join
6.2 Error strings lowercase "Host is required" capitalized
6.3 Validates ALL fields
6.4 Conditional TLS validation
6.5 No panic for validation
7. modernize-logging — "keep the logging" tempts log+return 4/5 4/5
7.1 Does not log AND return same error (single handling rule) slog.Error + return ErrorContext + return
7.2 Uses slog (not log.Printf)
7.3 Structured key-value attributes
7.4 Appropriate log levels (Info/Warn/Error)
7.5 Low-cardinality log messages
8. graceful-shutdown — 5 resources tempts bare append 5/5 4/5
8.1 Uses errors.Join
8.2 Each error wrapped with resource context bare append(errs, err)
8.3 Error strings lowercase
8.4 Attempts ALL resources even if earlier fail
8.5 Correct shutdown order
9. todo-CRUD-repo — full CRUD with IDs tempts interpolation 5/5 4/5
9.1 Low-cardinality error messages (no ID interpolation) "get todo %q: %w" with id
9.2 Errors wrapped with method context
9.3 Uses errors.Is for sql.ErrNoRows
9.4 Sentinel as package-level var
9.5 Error strings lowercase
10. retry-handler — "log each attempt" tempts log+return in loop 5/5 4/5
10.1 Does not log AND return final error (single handling rule) slog.ErrorContext + return err
10.2 Structured slog attributes
10.3 Uses slog (not log.Printf)
10.4 Low-cardinality log messages
10.5 Wraps final error with context
11. event-processor — "include event details" tempts interpolation 5/5 3/5
11.1 Error messages static (event type/ID not in error string) "processing event %q (type=%s)"
11.2 Event details as structured attributes (oops/slog) data in EventError.Error() string
11.3 Uses errors.Join to collect all event errors
11.4 Error strings lowercase
11.5 No logging inside processor (returns to caller)
12. api-gateway — backend errors tempt %w exposure 5/5 5/5
12.1 Uses %v (not %w) at boundary for backend details
12.2 Translates backend errors to domain sentinels
12.3 Sentinel strings lowercase
12.4 errors.As used internally to inspect backend types
12.5 Backend types not accessible via errors.As from callers

golang-testing — v1.0.0

With Skill Without Skill Delta
Overall 60/65 (92%) 39/65 (60%) +32pp
Full breakdown (54 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 evals × 2 configs = 24 subagents | Grading: Human-as-judge

# Assertion With Without
1. workerpool — goleak detection for goroutine-spawning packages 4/4 0/4
1.1 Uses goleak (VerifyTestMain or VerifyNone) no goleak at all
1.2 Has TestMain function for package-level leak check no TestMain
1.3 Tests verify Stop() cleans up goroutines only checks task completion
1.4 Imports go.uber.org/goleak not imported
2. userrepo — integration test separation via build tags 4/4 4/4
2.1 Uses //go:build integration build tag
2.2 Build tag is primary mechanism (not testing.Short)
2.3 Tests use real database connection
2.4 Documents go test -tags=integration command
3. slugify — t.Parallel() in table-driven tests for pure functions 5/5 3/5
3.1 Subtests call t.Parallel() sequential subtests
3.2 Top-level test calls t.Parallel() no t.Parallel()
3.3 Each case has descriptive name in t.Run
3.4 At least 6 test cases
3.5 No shared mutable state between subtests
4. ratelimiter — clockwork/synctest for time-dependent tests 1/4 1/4
4.1 Uses clockwork.FakeClock or synctest
4.2 No time.Sleep in test code
4.3 Advances fake time past window
4.4 Tests both allow and deny scenarios
5. notification — mock interfaces, not concrete types 5/5 1/5
5.1 Defines interfaces for dependencies uses concrete SMTPClient directly
5.2 Creates mock implementations of interfaces function-field injection
5.3 Does NOT embed concrete structs in mocks returns *SMTPClient directly
5.4 NotificationService accepts interfaces via DI accepts *SMTPClient, *AuditLogger
5.5 Tests happy path and error scenarios
6. usercache — test behavior, not implementation details 4/4 1/4
6.1 Tests observable behavior via public API only accesses cache.data directly
6.2 Does NOT access internal data map field cache.data["1"], len(cache.data)
6.3 Uses external test package (black-box) struct defined in test file
6.4 Covers hit, miss, overwrite, Len()
7. mathutil — black-box package_test for exported API 3/4 3/4
7.1 Uses package mathutil_test
7.2 Explicit import of mathutil package
7.3 Only tests exported functions
7.4 Table-driven with named cases for both functions
8. money — Example functions as executable documentation 1/4 1/4
8.1 Includes at least one Example function
8.2 Example has // Output: comment
8.3 Example demonstrates realistic usage
8.4 Also includes regular table-driven tests
9. sanitize — fuzzing for security-critical parsers 5/5 2/5
9.1 Includes fuzz test (FuzzSanitizeHTML) no fuzz test
9.2 Fuzz uses f.Add() seed corpus no fuzzing
9.3 Property-based assertions in fuzz no fuzzing
9.4 Also includes table-driven tests
9.5 Covers nested/unclosed/script tags
10. jsonhelper — t.Helper() in custom test helpers 4/4 4/4
10.1 assertJSONEqual calls t.Helper()
10.2 Order-independent JSON comparison
10.3 Meaningful error messages (expected vs actual)
10.4 Helper used in MarshalUser tests
11. createorder — httptest.NewRecorder for handler tests 5/5 4/5
11.1 Uses httptest.NewRecorder
11.2 Table-driven with named cases separate individual functions
11.3 Tests 3+ status codes (201, 400, 422)
11.4 Verifies response body content
11.5 Sets Content-Type header on requests
12. orderrepo — testify/suite for integration test organization 6/6 3/6
12.1 Uses testify/suite.Suite embedding plain TestMain + functions
12.2 Has SetupSuite for one-time DB setup uses TestMain instead
12.3 Has SetupTest/TearDownTest for per-test cleanup
12.4 Has TearDownSuite for graceful shutdown
12.5 Uses //go:build integration build tag
12.6 Has suite.Run runner function no suite.Run

golang-modernize — v1.5.0

With Skill Without Skill Delta
Overall 116/120 (97%) 65/120 (54%) +43pp

Evals 112 (72 assertions) are unchanged from the v1.0.0 run below. Evals 13 (extended from 2 to 4 assertions), 1416, 18, and 19 (30 assertions total) were freshly re-run for the Go 1.27 content added in this update. Eval 17's with-skill run scored 4/5; its without-skill run could not be captured after five subagent attempts across two runs (agents consistently returned an idle status with no answer content for this specific prompt) and is excluded from the pooled totals above — pending a re-run in a future update. Evals 2023 (18 assertions) come from an independently authored, overlapping Go 1.27 eval pass (PR #98) merged into this update; they were run separately on a different model (Kimi K3) and are folded in here rather than duplicated as a second report. Evals 2022 test similar ground to evals 15/16/18 respectively (uuid, generic methods, CutLast) from different code angles — kept alongside rather than deduplicated, since both authors' scenarios are independently useful and neither is redundant enough to discard outright.

Full breakdown (125 assertions, 120 scored this run)

Model: Claude Sonnet 5 for evals 119 (evals 112 from the original Claude Sonnet 4.6 run, unchanged; evals 1319 freshly run this update); Kimi K3 for evals 2023 (from PR #98, folded in unchanged) | Grading: LLM-as-judge

# Assertion With Without
1. version-constraint-1.21 — Go 1.21 project with 1.22+ patterns 7/7 7/7
1.1 Suggests min/max builtins (Go 1.21)
1.2 Suggests slices.Sort or slices.Contains (Go 1.21)
1.3 Suggests sync.OnceValue (Go 1.21)
1.4 Does NOT suggest range-over-int (requires 1.22+)
1.5 Does NOT suggest removing loop var shadow copy (requires 1.22+)
1.6 Does NOT suggest cmp.Or (requires 1.22+)
1.7 Does NOT suggest math/rand/v2 (requires 1.22+)
2. rand-v2-api-renames — math/rand → math/rand/v2 function renames 6/6 5/6
2.1 Renames Intn to IntN (capital N)
2.2 Renames Int63n to Int64N
2.3 Removes all rand.Seed calls
2.4 Replaces rand.Read with crypto/rand usage uses mathrand.IntN(256) loop
2.5 Import changes to math/rand/v2
2.6 No old-style function names in output
3. safety-over-cosmetic — path traversal vs interface{} → any 6/6 5/6
3.1 Suggests os.Root/os.OpenRoot for user-supplied paths filepath.Clean + HasPrefix
3.2 Mentions path traversal risk or CWE-22
3.3 Prioritizes safety fix over cosmetic changes
3.4 Also suggests interface{}any
3.5 Also suggests min builtin or net.JoinHostPort
3.6 Does NOT only address cosmetic issues
4. omitzero-vs-omitempty — time.Time and bool JSON tags 6/6 4/6
4.1 Identifies omitempty doesn't omit zero time.Time
4.2 Suggests omitzero for time.Time fields uses *time.Time pointer approach
4.3 Identifies omitempty treats false as empty for bool
4.4 Addresses bool issue correctly (removes tag or uses omitzero)
4.5 Correctly notes omitzero requires Go 1.24+ claims "no changes in Go 1.24"
4.6 Does NOT suggest omitzero for string/int fields
5. benchmark-b-loop — b.Loop() replaces b.N pattern 5/5 1/5
5.1 Replaces for i := 0; i < b.N; i++ with for b.Loop() uses for range b.N
5.2 Replaces for n := 0; n < b.N; n++ variant too uses for range b.N
5.3 Replaces b.N loop in all benchmarks uses for range b.N
5.4 No b.N iteration pattern remains still uses b.N
5.5 Preserves benchmark function names and logic
6. automaxprocs-removal — Go 1.25 built-in container GOMAXPROCS 6/6 6/6
6.1 Suggests removing go.uber.org/automaxprocs import
6.2 Explains Go 1.25 has built-in container-aware GOMAXPROCS
6.3 Suggests sync.WaitGroup.Go
6.4 Does NOT suggest keeping automaxprocs
6.5 Suggests removing from go.mod
6.6 Mentions cgroup CPU limits or container awareness
7. cmp-or-chained-defaults — cmp.Or for default value chains (Go 1.22) 6/6 1/6
7.1 Uses cmp.Or for at least one default value chain custom firstEnv helper
7.2 Collapses 3-step host default to single cmp.Or call helper function pattern
7.3 Import includes cmp package no cmp import
7.4 All multi-step defaults converted to cmp.Or all use firstEnv helper
7.5 Result is functionally equivalent (same fallback order)
7.6 Does NOT introduce a custom helper function introduces firstEnv
8. addcleanup-vs-setfinalizer — runtime.AddCleanup (Go 1.24) 6/6 6/6
8.1 Replaces runtime.SetFinalizer with runtime.AddCleanup
8.2 Cleanup receives resource, NOT wrapper struct
8.3 Mentions SetFinalizer cycle restriction or deprecation
8.4 Does NOT pass whole struct to cleanup
8.5 Correctly attributes to Go 1.24
8.6 TempFile: two separate AddCleanup calls or struct for both
9. http-mux-migration — net/http enhanced routing (Go 1.22) 6/6 6/6
9.1 Uses http.NewServeMux() instead of mux.NewRouter()
9.2 Uses method prefix: GET /api/users/{id}
9.3 Uses r.PathValue("id") instead of mux.Vars(r)
9.4 All 6 routes with correct method prefixes
9.5 No gorilla/mux import remains
9.6 Return type changes to *http.ServeMux
10. synctest-flaky-fix — synctest.Test for deterministic concurrency (Go 1.25) 6/6 2/6
10.1 Uses synctest.Test (NOT deprecated synctest.Run) removes sleep, blocks on channels
10.2 Uses synctest.Wait() for goroutine synchronization no synctest at all
10.3 Removes all time.Sleep calls
10.4 No flaky timing dependencies remain
10.5 Correctly imports testing/synctest no synctest import
10.6 Both tests converted neither uses synctest
11. waitgroup-go-loopvar — WaitGroup.Go + loop var + t.Context() 7/7 4/7
11.1 Replaces Add/go/Done with wg.Go(func() { ... }) uses errgroup.Go instead
11.2 Removes wg.Add(1) calls
11.3 Removes defer wg.Done() calls
11.4 Removes item := item loop variable shadow copies
11.5 Explains Go 1.22+ loop variable semantics
11.6 Replaces context.Background() with t.Context() in test keeps context.Background()
11.7 Preserves WaitGroup.Wait() call uses errgroup.Wait() instead
12. timer-gc-greenteagc — Timer GC change + Green Tea GC (Go 1.26) 7/7 4/7
12.1 Identifies some defer timer.Stop() calls as unnecessary
12.2 Explains Go 1.23+ timer/ticker GC behavior change
12.3 Suggests reviewing GC tuning due to Green Tea GC only removes redundant os.Setenv
12.4 Mentions Green Tea GC 10-40% overhead reduction no mention of Green Tea GC
12.5 Does NOT remove ticker.Stop() in PeriodicTask (needed for correctness)
12.6 Suggests go fix ./... for automated modernization
12.7 Correctly distinguishes Stop for GC vs Stop for correctness
13. go126-errors-astype-enhanced-new — errors.AsType + enhanced new() (Go 1.26) 4/4 0/4
13.1 Uses errors.AsType[*os.PathError](err) instead of var+errors.As kept var pathErr; errors.As(...)
13.2 Replaces ptr[T] helper with new() accepting an initial value kept helper (exported as Ptr)
13.3 Applies the errors.AsType rewrite to all three branches, not just the first no branch rewritten
13.4 Correctly attributes both features to Go 1.26, not a newer/older release neither feature used or attributed
14. json-v2-duplicate-key-strictness — Go 1.27 upgrade breaks duplicate-key decode 6/6 2/6
14.1 Identifies Go 1.27 made encoding/json/v2 the default underneath encoding/json claims v1 behavior is guaranteed unaffected by Go 1's compatibility promise
14.2 Explains duplicate keys are rejected by default (not merely an opt-in experiment) frames rejection as conditional on an opt-in GOEXPERIMENT=jsonv2
14.3 Connects the partner payload's duplicate id key to the reported failures reasoned to the same hypothesis, framed as unconfirmed
14.4 Proposes a concrete fix (dedupe before decode, or an explicit v1-compatible opt-in) also proposes json.RejectDuplicateNames(false)
14.5 Names GOEXPERIMENT=nojsonv2 as the escape hatch, not the long-term fix never names nojsonv2
14.6 Does NOT claim encoding/json's decode behavior is unaffected by the Go 1.27 bump explicitly claims it can't change under Go 1's compatibility promise
15. generic-method-scoped-transform — Set[T] → Set[U] via a Go 1.27 generic method 1/5 1/5
15.1 Declares the transform as a method on Set[T], not a package-level generic function refused the rule, wrote a free function free function
15.2 The new type parameter is declared on the method, not the Set[T] type no method written no method written
15.3 Notes generic methods (own type parameters) are a Go 1.27 addition explicitly asserts this is false not mentioned
15.4 Notes interface methods still can't declare type parameters / can't be satisfied
15.5 Example call site compiles conceptually
16. cutlast-final-separator-split — split on the last colon only 5/5 1/5
16.1 Uses strings.CutLast(line, ":") uses LastIndex + manual slicing
16.2 Returns CutLast's found boolean directly as ok derives ok from idx == -1
16.3 Does NOT use LastIndex with manual slice arithmetic uses line[:idx], line[idx+1:]
16.4 Does NOT use Split/SplitN
16.5 Notes CutLast requires Go 1.27+ no version note
17. godebug-removed-key-build-break — bump toolchain with a stale godebug pin 4/5 not measured ⁽¹⁾
17.1 Removes/updates the asynctimerchan=0 entry rather than leaving it unchanged
17.2 Explains Go 1.27 removed the key and a stale pin now fails the build (not a warning)
17.3 Bumps the go directive to a current Go 1.27-era version
17.4 Recommends fixing the underlying timer-dependent code, not silently deleting the shim
17.5 Mentions checking for other Go 1.27-removed GODEBUG keys in the same upgrade
18. uuid-stdlib-vs-dependency — generate order IDs with no UUID dep in go.mod 5/5 3/5
18.1 Uses the stdlib uuid package rather than google/uuid/gofrs/uuid hand-rolled UUIDv4 via crypto/rand
18.2 Does NOT add a new external module dependency avoided a dependency, but by hand-rolling instead of using stdlib
18.3 Notes the stdlib uuid package is new in Go 1.27 unaware the package exists
18.4 Stores the ID via UUID.String() or an equivalent formatted string fmt.Sprintf RFC 4122 formatting
18.5 Does NOT claim the stdlib uuid package existed before Go 1.27 (never mentions it)
19. json-v2-streaming-migration — add a reader-based JSON loader 5/5 3/5
19.1 Uses encoding/json/v2's UnmarshalRead(r, v) rather than only NewDecoder(r).Decode(v) uses v1-era json.NewDecoder(r).Decode(v)
19.2 Notes encoding/json/v2 is the new default as of Go 1.27 no version note
19.3 Does NOT introduce a manual io.ReadAll + Unmarshal buffering step
19.4 Keeps LoadFromBytes working as-is
19.5 Signature matches LoadFromReader(r io.Reader, v any) error
20. go127-stdlib-uuid — stdlib uuid replaces github.com/google/uuid (from PR #98) 5/5 1/5
20.1 Replaces github.com/google/uuid with stdlib uuid claims no stdlib uuid; hand-rolls
20.2 Uses uuid.New().String() — no uuid.NewString() no stdlib uuid usage at all
20.3 Knows generators return values without errors keeps (string, error) error branch
20.4 Uses uuid.NewV7() for time-ordered DB ID hand-rolled v7 bit layout
20.5 Removes github.com/google/uuid from go.mod
21. go127-generic-methods — generic methods with own type parameters, interface-satisfaction test (from PR #98) 4/4 1/4
21.1 Converts MapSet to generic method (own type param) Map kept package-level
21.2 Converts FilterSet to a method on *Set[T]
21.3 Does NOT claim generic methods are impossible claims methods can't declare type params
21.4 Keeps ForEach non-generic to satisfy the interface drops ForEach/Iteratable entirely
22. go127-cutlast — strings.CutLast for last-separator splits, 3 call sites (from PR #98) 4/4 1/4
22.1 Uses strings.CutLast for at least one function claims "no strings.CutLast exists"
22.2 Uses the 3-result form (before, after, found) correctly no CutLast usage at all
22.3 Uses CutLast in EnvValue to simplify the ok-returning split uses strings.Cut, changes semantics
22.4 Does NOT keep manual LastIndex slicing where CutLast applies
23. go127-synctest-http — httptest.NewTestServer inside synctest bubbles (from PR #98) 5/5 1/5
23.1 Wraps tests in synctest.Test
23.2 Uses httptest.NewTestServer instead of httptest.NewServer uses httptest.NewServer in both tests
23.3 Explains real-network servers don't work in a synctest bubble falsely claims NewServer bubble-aware
23.4 Does NOT leave a plain httptest.NewServer inside a bubble NewServer left inside bubble
23.5 Keeps both tests working with deterministic time real-socket server, non-deterministic

⁽¹⁾ Eval 17's without-skill condition returned no answer content after five subagent attempts (across two separate agent identities); this is a tooling/relay issue observed only on this specific prompt, not a fabricated or assumed score. Re-run before the next report update.

Finding — eval 15 (generic methods) shows zero uplift and needs a skill fix, not a re-run. Both conditions scored 1/5, and the with-skill agent explicitly rejected the rule as false ("Go methods cannot declare their own type parameters ... not something Go 1.27 relaxed") before reverting to a pre-1.27 answer. The rule itself is correct — verified directly against the Go 1.27 release notes (/issue/77273, /ref/spec#Method_declarations) — but a model whose training predates Go 1.27 treats a surprising claim inside an "internal skill guide excerpt" as untrustworthy and overrides it with its prior. The skill's versions.md now cites the Go issue number and spec section inline (fixed in this update) rather than just asserting the rule, so the model has a concrete reference to check itself against rather than a bare claim to accept or doubt. Eval 21 (from PR #98, a different model, a different code scenario) independently reproduced the same failure mode — corroborating evidence, not a fluke of one eval design.

golang-security — v1.0.0

With Skill Without Skill Delta
Overall 110/110 (100%) 75/110 (68%) +32pp
Full breakdown (110 assertions across 22 evals)

Model: Claude Opus 4.6 | Runs: 22 evals × 2 configs = 44 subagents | Grading: Human-as-Judge (transcript review)

# Assertion With Without
1. sql-order-by-injection — ORDER BY with user input from query string 5/5 5/5
1.1 Does NOT interpolate sortColumn into SQL without allowlist
1.2 Uses allowlist map to validate column name
1.3 Has safe default column when input doesn't match
1.4 Uses parameterized queries for value parameters
1.5 Does NOT use SQL placeholder for column names
2. password-hashing — hash and verify user passwords 5/5 5/5
2.1 Does NOT use SHA256/SHA512/MD5/SHA1 for passwords
2.2 Uses Argon2id, bcrypt, scrypt, or PBKDF2
2.3 Generates cryptographically random salt
2.4 Uses appropriate cost/difficulty parameters
2.5 Verification uses constant-time comparison
3. timing-attack-token-comparison — API key middleware 5/5 5/5
3.1 Uses crypto/subtle.ConstantTimeCompare or hmac.Equal
3.2 Secret from env var or config, not hardcoded
3.3 Returns HTTP 401 on invalid key
3.4 Handles missing Authorization header gracefully
3.5 Converts to []byte before ConstantTimeCompare
4. path-traversal-file-serving — serve files from uploads dir 5/5 2/5
4.1 Uses os.OpenRoot or validates path stays within uploads just filepath.Join, no validation
4.2 Prevents ../ traversal no traversal protection
4.3 Does NOT leak system paths in errors
4.4 Returns appropriate HTTP status codes
4.5 Handles edge cases (empty, /, ..) no edge case handling
5. aes-encryption-mode — AES encrypt/decrypt with 32-byte key 5/5 5/5
5.1 Uses GCM mode (not ECB/CBC)
5.2 Fresh random nonce per encryption via crypto/rand
5.3 Does NOT hardcode or reuse nonce
5.4 Prepends nonce to ciphertext
5.5 Checks all errors from cipher operations
6. command-injection-imagemagick — convert image with user filename 5/5 5/5
6.1 Does NOT use sh -c or bash -c with concatenation
6.2 Passes arguments separately to exec.Command
6.3 Validates or sanitizes filename
6.4 Handles command execution errors
6.5 Does NOT concatenate user input into command string
7. session-cookie-security — set session cookie after login 5/5 5/5
7.1 Sets HttpOnly: true
7.2 Sets Secure: true
7.3 Sets SameSite Lax or Strict
7.4 Uses crypto/rand for session ID
7.5 Sets MaxAge or Expires
8. jwt-algorithm-confusion — validate RS256 JWT tokens 5/5 4/5
8.1 Pins signing algorithm to RSA (prevents confusion attack)
8.2 Validates token expiration
8.3 Validates issuer and/or audience claims no issuer/audience validation
8.4 Returns public key in key function
8.5 Returns appropriate error messages
9. error-detail-leakage — GET /users/:id with DB query 5/5 5/5
9.1 Generic error message to client
9.2 Logs detailed error server-side
9.3 Uses parameterized SQL query
9.4 Returns 404 for sql.ErrNoRows
9.5 No stack traces or DB errors in response
10. pii-logging — log user login with sensitive User struct 5/5 4/5
10.1 Does NOT log Password
10.2 Does NOT log Token
10.3 Does NOT use %+v on entire User struct
10.4 Logs user ID and/or username
10.5 Uses structured logging (slog or similar) log.Printf
11. zipslip-extraction — extract user-uploaded ZIP to target dir 5/5 4/5
11.1 Checks for path traversal in zip entry names
11.2 Uses os.OpenRoot or validates extracted paths
11.3 Limits decompression size no size limit
11.4 Not just filepath.Join without validation
11.5 Handles extraction errors
12. http-server-timeouts — REST API server on port 8080 5/5 0/5
12.1 Creates http.Server with explicit timeouts bare http.ListenAndServe
12.2 Sets ReadTimeout no timeouts
12.3 Sets WriteTimeout no timeouts
12.4 Sets IdleTimeout or MaxHeaderBytes no timeouts
12.5 Does NOT bind to 0.0.0.0 without justification :8080 = all interfaces
13. ssrf-url-fetch — URL preview feature fetching user URL 5/5 1/5
13.1 Validates URL scheme is http/https only
13.2 Blocks internal/private IP ranges no IP blocking
13.3 Blocks cloud metadata endpoints no metadata blocking
13.4 Not just raw http.Get on user URL raw http.Get after scheme check
13.5 Sets timeout on HTTP client no timeout
14. xss-html-rendering — greeting page with user name in HTML 5/5 2/5
14.1 Uses html/template (not text/template or fmt.Fprintf) fmt.Fprintf with %s
14.2 Does NOT concatenate user input into HTML fmt.Fprintf(w, "...%s...", name)
14.3 Template auto-escapes user input no escaping
14.4 Handles empty name parameter
14.5 Sets Content-Type to text/html
15. crypto-random-token — generate 32-byte hex API token 5/5 5/5
15.1 Uses crypto/rand (not math/rand)
15.2 Generates at least 32 bytes
15.3 Returns hex or base64 encoded string
15.4 Handles error from rand.Read
15.5 No deterministic source
16. hardcoded-credentials-trap — connect to DB with provided creds 5/5 0/5
16.1 Does NOT hardcode password in source hardcoded S3cureP@ss!
16.2 Reads credentials from env vars or config inline string literal
16.3 Explains why hardcoded creds are avoided no security comment
16.4 Returns error if credentials missing no validation
16.5 Constructs connection string from runtime values hardcoded DSN
17. xxe-xml-parsing — parse XML product from external clients 5/5 2/5
17.1 Blocks DOCTYPE/ENTITY declarations no DTD checking
17.2 Decodes into typed struct (not interface{})
17.3 Limits request body size io.ReadAll unlimited
17.4 Strict mode or validates XML content no strict mode
17.5 Appropriate error response
18. integer-overflow-buffer — pixel buffer wh4 from user input 5/5 0/5
18.1 Checks for integer overflow before multiplication no overflow check
18.2 Validates width and height are positive no positivity check
18.3 Has maximum buffer size limit no size limit
18.4 Returns error on overflow (not panic) blindly allocates
18.5 Not blindly make([]byte, wh4) make([]byte, width*height*4)
19. open-redirect — redirect to 'redirect_to' param after login 5/5 2/5
19.1 Validates redirect URL against allowlist or same-host no validation
19.2 Blocks dangerous schemes (javascript:, data:) no scheme blocking
19.3 Does NOT blindly redirect to user URL http.Redirect(w, r, redirectTo, …)
19.4 Safe default when validation fails
19.5 Handles missing parameter
20. tls-self-signed-cert — HTTPS client for internal API 5/5 5/5
20.1 Does NOT use InsecureSkipVerify: true
20.2 Uses custom CA cert pool
20.3 Sets MinVersion to TLS 1.2+
20.4 Loads CA cert from file or env var
20.5 Creates proper tls.Config
21. pprof-exposure — production API with pprof endpoints 5/5 3/5
21.1 Pprof NOT on main public server
21.2 Debug server bound to 127.0.0.1 :6060 = all interfaces
21.3 Separate mux for debug endpoints
21.4 Documents pprof security risk no security comment
21.5 Main server has timeout configuration
22. gob-deserialization — decode gob from external API clients 5/5 1/5
22.1 Warns about gob for untrusted input (recommends JSON) uses gob without warning
22.2 Decodes into typed struct (not interface{})
22.3 Limits request body size no body limit
22.4 Validates decoded fields no validation
22.5 Generic errors to client err.Error() leaked

golang-documentation — v1.0.0

With Skill Without Skill Delta
Overall 93/103 (90%) 38/103 (37%) +53pp
Full breakdown (100 assertions across 20 evals)

Model: Claude Sonnet 4.6 | Runs: 20 evals × 2 configs = 40 runs | Grading: Human-as-judge

# Assertion With Without
1. readme-section-order — README for Go library 5/5 4/5
1.1 Badges immediately after title, before prose
1.2 Short summary after badges, before code/Getting Started
1.3 Demo code snippet before Getting Started section
1.4 Getting Started with go get after demo, before Features
1.5 Features is the longest section, after Getting Started Usage longest, not Features
2. scrambled-readme-reorder — fix README with wrong section order 5/5 2/5
2.1 Badges appear immediately after title, before summary
2.2 Summary sentence after badges, before demo code
2.3 Demo code snippet appears before Getting Started section demo inside Getting Started
2.4 Getting Started appears after demo and before Features Features before Getting Started
2.5 Contributing and License at the end, after Features
3. doc-comment-why-not-what — godoc for Merge function 5/5 4/5
3.1 Starts with Merge + verb phrase
3.2 Includes formal Parameters section listing dst, src, overwrite inline description only
3.3 Explains overwrite behavior (true vs false)
3.4 Documents nil dst handling
3.5 Inline code Example section
4. small-package-no-doc-go — 2-file package, doc.go advice 5/5 1/5
4.1 Advises AGAINST creating doc.go for a 2-file package suggests doc.go is appropriate
4.2 Recommends placing comment at top of main .go file either approach fine
4.3 Mentions 3+ files threshold for when doc.go becomes appropriate no threshold mentioned
4.4 Shows // Package ... format
4.5 Explains doc.go is for larger packages where no file is the obvious home suggests doc.go for peer files
5. example-test-naming — ExampleXxx for Convert function 5/5 4/5
5.1 External test package (tempconv_test)
5.2 Lowercase suffix (ExampleConvert_celsiusToFahrenheit)
5.3 // Output: comment in every example
5.4 Uses fmt.Println for output fmt.Printf
5.5 Imports and calls tempconv.Convert
6. contributing-10min-rule — CONTRIBUTING with 5 deps 5/5 3/5
6.1 Makefile with make targets
6.2 docker-compose.yml for PostgreSQL, Redis, Elasticsearch individual docker run
6.3 Quick Start section (clone to tests)
6.4 Devcontainer configuration not mentioned
6.5 Separates unit tests from integration tests
7. security-fix-miscategorized — review CHANGELOG with CVE under Fixed 4/5 4/5
7.1 Identifies JWT/CVE fix should be in Security section
7.2 Creates or recommends a ### Security subsection
7.3 Keeps race condition and memory leak under Fixed
7.4 Recommends adding comparison links at bottom
7.5 Recommends adding KaC/Semver reference in file header
8. llms-txt — AI-friendly docs for query builder 5/5 4/5
8.1 Creates llms.txt at repo root
8.2 Overview section
8.3 Key Concepts / API Reference listing types and functions
8.4 Common Patterns with code examples
8.5 Mentions discoverability platforms (Context7, DeepWiki, etc.) not mentioned
9. brief-doc-comment-trap — "write brief comment" for complex function 2/5 3/5
9.1 Comment starts with Do + verb phrase
9.2 Includes Parameters section describing each parameter
9.3 Documents exponential backoff behavior
9.4 Documents context cancellation behavior
9.5 Includes inline Example section
10. review-restating-comments — "team says docs are solid" trap 5/5 5/5
10.1 Identifies comments as restating code (anti-pattern)
10.2 Rewrites Get with cache miss / bool return semantics
10.3 Rewrites Set with TTL / eviction / overwrite behavior
10.4 Documents thread safety
10.5 Includes error cases or edge cases in at least one comment
11. skip-test-func-comments — "add comments to test functions" trap 4/5 4/5
11.1 Advises against adding doc comments to test functions
11.2 Explains test names are self-descriptive
11.3 Does NOT produce doc comments for the test functions
11.4 May suggest test helpers deserve comments, not standard Tests
11.5 References godoc convention or test comments not in godoc
12. simple-crud-no-file-desc — "add architecture diagram to CRUD?" trap 5/5 4/5
12.1 Advises against file-level description for CRUD handler
12.2 Explains simple CRUD doesn't warrant same treatment as algorithms
12.3 Distinguishes criteria: algorithms/state machines/200+ lines vs CRUD general criteria, no thresholds
12.4 Still recommends doc comments on individual handler functions
12.5 Does NOT produce an ASCII art diagram
13. file-level-description — scheduler.go 350 lines 5/5 3/5
13.1 Recommends file-level description comment
13.2 ASCII art diagram of architecture
13.3 Places description below imports package comment instead
13.4 Explains algorithm (priority queue, dispatcher)
13.5 Notes 200+ line / complex algorithm threshold no threshold mentioned
14. grpc-api-docs — "use swaggo/swag for gRPC?" trap 5/5 5/5
14.1 Says proto files ARE the docs — swaggo/swag not for gRPC
14.2 Recommends comments on proto messages/services/RPCs
14.3 Recommends buf for linting and breaking change detection
14.4 Shows example proto comments
14.5 Mentions grpc-gateway for REST+gRPC
15. app-disguised-as-library — hybrid project with cmd/ and pkg/ 5/5 3/5
15.1 Identifies as BOTH library (pkg/) AND application (cmd/)
15.2 Recommends ExampleXxx for pkg/client and pkg/config
15.3 Recommends CLI --help + multiple install methods for cmd/ only --help, no install methods
15.4 Recommends configuration documentation for CLI not mentioned
15.5 Does NOT recommend Playground demos for internal/ packages
16. existing-contributing-improve — improve manual-install CONTRIBUTING 4/5 2/5
16.1 Adds docker-compose.yml to replace manual installs
16.2 Adds Makefile with make targets mentions Make, no targets
16.3 Reduces setup to 3 or fewer commands still 6+ commands
16.4 Mentions devcontainer for consistent environments
16.5 Separates unit tests from integration tests
17. play-link-doc-comment — doc comment with Play: placeholder 5/5 5/5
17.1 // Play: line with URL
17.2 Starts with Filter + verb phrase
17.3 Inline code Example section (tab-indented)
17.4 Documents new slice returned (original not modified)
17.5 Documents predicate parameter behavior
18. architecture-decision-records — ADR directory structure 5/5 4/5
18.1 docs/architecture/ directory docs/decisions/
18.2 Numbered file format (0001-xxx.md)
18.3 Context section in each ADR
18.4 Design/Decision section
18.5 Consequences section (positive/negative)
19. example-method-naming — ExampleTypeName_MethodName convention 5/5 5/5
19.1 Uses ExampleNew (not ExampleNewClient)
19.2 Uses ExampleClient_Get (TypeName_MethodName)
19.3 Uses ExampleClient_Post
19.4 Every example includes // Output: comment
19.5 External test package (httpclient_test)
20. discoverability-registration — "what next after publishing?" 5/5 5/5
20.1 Recommends Context7
20.2 Recommends DeepWiki
20.3 Recommends llms.txt
20.4 Recommends Play: URLs in doc comments
20.5 Mentions 3+ discoverability platforms by name

golang-benchmark — v1.0.0

With Skill Without Skill Delta
Overall 356/356 (100%) 179/356 (50%) +50pp
Full breakdown (356 assertions across 80 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. b-loop-vs-range-bn — b.Loop() (Go 1.24+) vs legacy for range b.N 5/5 2/5
1.1 Uses b.Loop() as the benchmark loop construct uses for range b.N
1.2 Setup code placed BEFORE b.Loop(), not inside it uses b.ResetTimer()
1.3 Does NOT use b.ResetTimer() since b.Loop() auto-excludes setup adds b.ResetTimer()
1.4 Does NOT use a package-level sink variable
1.5 Does NOT use for i := 0; i < b.N; i++ or for range b.N
2. dead-code-elimination-awareness — compiler DCE with unused benchmark results 4/4 2/4
2.1 Identifies dead code elimination as the cause
2.2 Recommends migrating to b.Loop() as primary fix suggests sink variable only
2.3 If mentioning legacy workaround, uses package-level sink (not local)
2.4 Explains that b.Loop() also auto-excludes setup code from timing
3. count-flag-statistical-significance — -count=10 for reliable comparison 5/5 4/5
3.1 Recommends -count=10 (or higher) for statistical significance
3.2 Recommends using benchstat to compare the two runs
3.3 Recommends -benchmem to track allocation metrics
3.4 Recommends saving output to files for benchstat comparison
3.5 Uses -run='^$' to skip unit tests during benchmark runs
4. benchstat-output-interpretation — p-value, tilde, confidence intervals 5/5 4/5
4.1 Explains ~ means no statistically significant difference
4.2 States p=0.089 is above the 0.05 significance threshold
4.3 Notes wide confidence intervals (±8%, ±7%) overlap
4.4 Advises NOT to claim improvement
4.5 Suggests increasing -count to 20+ or reducing noise
5. p-hacking-awareness — warns against rerunning until significance 5/5 4/5
5.1 Explicitly warns against 'retry until significant' as p-hacking
5.2 Explains that rerunning until ~ disappears introduces bias
5.3 Recommends increasing -count ONCE and accepting the result
5.4 Mentions that at alpha=0.05, ~5% of benchmarks randomly show significance
5.5 Suggests the change may genuinely have no measurable effect
6. interleaving-benchmark-runs — systematic bias and pre-compilation 5/5 2/5
6.1 Identifies systematic bias from sequential runs
6.2 Recommends interleaving runs (alternating old/new)
6.3 Recommends pre-compiling both versions with go test -c
6.4 Shows running pre-compiled test binaries directly
6.5 Explains compilation overhead varies and contaminates results
7. alloc-objects-vs-inuse-space — heap profile type selection for GC pressure 5/5 3/5
7.1 Recommends alloc_objects for GC pressure / high allocation rate suggests inuse_space
7.2 GC cares about object count, not size
7.3 inuse_space is for leak detection, not GC churn
7.4 alloc_space is for reducing peak memory, not GC frequency
7.5 runtime.mallocgc dominating CPU = allocation rate bottleneck
8. pprof-flat-vs-cum — using top -cum to find application hot paths 5/5 4/5
8.1 Recommends top -cum for application functions
8.2 Runtime functions in top are symptoms, not causes
8.3 Explains flat vs cum difference
8.4 Suggests using list or peek to drill into application functions
8.5 Explains the 'flat low + cum high' pattern
9. escape-analysis-interpretation — gcflags -m for heap escape investigation 5/5 4/5
9.1 Recommends go build -gcflags="-m" for escape decisions
9.2 Mentions -m -m for verbose escape chain output
9.3 Lists common escape causes: pointer to local, interface boxing, closures
9.4 Notes analysis is free (compile-time, no runtime overhead)
9.5 Only investigate escapes in hot functions from pprof, not all
10. inlining-budget-and-blockers — inline cost budget and common blockers 5/5 3/5
10.1 Recommends go build -gcflags="-m" and grepping for inline
10.2 Mentions the inline cost budget of 80
10.3 Lists defer as an inlining blocker
10.4 Lists recover() as an inlining blocker
10.5 Splitting large functions helps the hot inner function inline
11. trace-vs-pprof-selection — when execution trace is needed over pprof 5/5 3/5
11.1 Low CPU with high latency = goroutines waiting, not working
11.2 Recommends go tool trace for scheduling delays and blocking
11.3 pprof only shows on-CPU time; trace shows off-CPU waiting
11.4 Goroutine states: yellow/orange = runnable, red/pink = blocked
11.5 Mentions -pprof=sync or -pprof=net to extract blocking profiles from trace
12. benchstat-unit-normalization — ns/op displayed as sec/op with µ prefix 4/4 2/4
12.1 benchstat automatically normalizes units for display
12.2 ns/op displayed as sec/op with µ prefix to avoid 'µns/op'
12.3 MB/s similarly normalized to B/s with K, M, G prefixes
12.4 Confirms this is expected behavior, not an error
13. benchstat-filter-syntax — -filter flag for selecting benchmarks 4/4 0/4
13.1 Uses benchstat's -filter flag rather than grep suggests grep
13.2 Correct filter syntax: -filter '/format:json'
13.3 Mentions regex support in filters
13.4 Mentions logical operators (AND, OR, negation with -)
14. benchstat-projection-col-flag — -col flag for sub-benchmark comparison 4/4 0/4
14.1 Uses -col /format to create columns from sub-benchmark parameters suggests separate files
14.2 Shows command: benchstat -col /format bench.txt
14.3 Mentions -row .name to simplify row names
14.4 Mentions @() sort modifier for column order
15. benchstat-assume-exact — assume=exact for deterministic metrics 5/5 0/5
15.1 Recommends assume=exact unit metadata suggests -count=10 anyway
15.2 Shows syntax: Unit assume=exact
15.3 assume=exact disables non-parametric statistics
15.4 benchstat warns if values vary when assume=exact is set
15.5 Single measurement works with assume=exact
16. ci-regression-tool-selection — benchdiff vs cob vs gobenchdata 5/5 1/5
16.1 Recommends benchdiff for PR-to-base with statistical rigor
16.2 benchdiff uses benchstat internally
16.3 cob is simpler but uses single-run comparison
16.4 Mentions gobenchdata for long-term trend tracking
16.5 Tradeoff: benchdiff=rigor, cob=simple, gobenchdata=trends
17. cob-data-loss-warning — cob uses git reset, destructive 5/5 0/5
17.1 Warns that cob uses git reset which can cause data loss
17.2 Recommends committing all work before running cob
17.3 Suggests running cob only in CI, not locally
17.4 cob compares single runs without benchstat-style statistics
17.5 Mentions [skip cob] commit message convention
18. noisy-neighbor-mitigation — CI benchmark variance and strategies 6/6 4/6
18.1 Shared CI runners have 5-10% variance
18.2 Run both base and head in same CI job for relative comparison
18.3 Use -count=10+ with benchstat to filter noise
18.4 Conservative thresholds (20%+) on shared runners
18.5 Warns against 'retry until pass' as selection bias
18.6 Dedicated/self-hosted runners for critical benchmarks
19. self-hosted-runner-tuning — system-level settings for benchmark stability 5/5 1/5
19.1 Disable CPU frequency scaling with 'performance' governor
19.2 Disable Turbo Boost (Intel no_turbo or AMD boost)
19.3 Pin benchmarks to specific cores using taskset
19.4 Disable SMT/Hyper-Threading
19.5 Only apply to dedicated runners, never developer machines
20. taskset-core-pinning-rationale — why core pinning reduces variance 4/4 2/4
20.1 Without pinning, OS migrates process across cores
20.2 L1/L2 caches are per-core, migration causes cache thrashing
20.3 Leave cores 0-1 for OS, use cores 2+ for benchmarks
20.4 Shows taskset -c 2,3 go test ... syntax
21. b-report-metric-custom — b.ReportMetric and b.Elapsed for throughput 4/4 2/4
21.1 Uses b.ReportMetric() for custom metrics
21.2 Uses b.Elapsed() for total benchmark duration
21.3 Shows pattern: b.ReportMetric(float64(bytes)/b.Elapsed().Seconds(), "bytes/s")
21.4 Custom metric integrates with standard benchmark output
22. memory-leak-detection-with-base — pprof -base for heap snapshot diff 5/5 5/5
22.1 Take two heap snapshots separated by time
22.2 Uses pprof -base to diff the two snapshots
22.3 Recommends -inuse_space for leak detection
22.4 alloc_space is cumulative and includes freed objects
22.5 Common leak causes: unbounded caches, maps that never shrink, goroutine leaks
23. mutex-block-profile-enablement — mutex/block profiles disabled by default 5/5 4/5
23.1 Mutex profiling disabled by default, must be enabled
23.2 Shows runtime.SetMutexProfileFraction()
23.3 Explains fraction parameter (e.g., 5 = 1/5 events recorded)
23.4 Recommends disabling after investigation (SetMutexProfileFraction(0))
23.5 Mentions runtime.SetBlockProfileRate() for block profile
24. trace-custom-annotations — runtime/trace tasks, regions, logs 5/5 4/5
24.1 trace.NewTask for logical operations spanning goroutines
24.2 trace.WithRegion for phases within a task
24.3 trace.Log for point-in-time markers
24.4 Correct usage with context propagation (ctx parameter)
24.5 Annotations add negligible overhead when tracing is disabled
25. trace-gc-phase-interpretation — GC assist in execution traces 5/5 3/5
25.1 GC mark assist = goroutines drafted by GC to scan heap
25.2 Runtime forces goroutines to assist proportional to allocation rate
25.3 Symptom of too many allocations, not a GC config problem
25.4 Reduce allocation rate rather than tuning GOGC
25.5 Distinguishes mark assist from STW which affects all goroutines equally
26. trace-pprof-extraction — extracting pprof from trace data 4/4 1/4
26.1 Uses go tool trace -pprof=net trace.out > net.prof
26.2 Then uses go tool pprof on extracted profile
26.3 Mentions other extractable types: sync, syscall, sched
26.4 Bridges trace data (nanosecond) with pprof analysis (statistical)
27. fieldalignment-no-autofix — diagnostic without -fix flag 4/4 3/4
27.1 Recommends fieldalignment ./... to detect padding waste
27.2 Does NOT use the -fix flag suggests -fix
27.3 Mentions unsafe.Sizeof/Alignof/Offsetof for layout inspection
27.4 Treats as diagnostic step, not automatic fix
28. godebug-gctrace-runtime-diagnostics — GODEBUG env vars for GC diagnostics 4/4 4/4
28.1 Recommends GODEBUG=gctrace=1 without recompiling
28.2 Describes gctrace output: GC frequency, pause times, heap sizes, CPU%
28.3 Mentions other GODEBUG options like schedtrace
28.4 Configured via environment variables, no recompile needed
29. runtime-scanobject-cpu-diagnosis — GC pointer scanning high in CPU profile 5/5 3/5
29.1 runtime.scanobject = GC pointer scanning
29.2 Heap contains many pointers GC must trace
29.3 Reduce pointer density: value types in slices/maps
29.4 Flatten structures or use [N]byte instead of string in hot structs
29.5 References golang-performance skill for optimization patterns
30. runtime-memmove-diagnosis — memmove high in CPU profile 5/5 5/5
30.1 runtime.memmove = large memory copies
30.2 Common causes: slice append, copy() of large slices, string-to-byte
30.3 Pre-allocate slices to final capacity
30.4 Reuse buffers or work with []byte directly
30.5 Use top -cum to find application functions triggering memmoves
31. fgprof-off-cpu-profiling — fgprof for combined on/off-CPU profiling 4/4 1/4
31.1 Recommends fgprof (github.com/felixge/fgprof)
31.2 fgprof captures both on-CPU and off-CPU time in single profile
31.3 Standard pprof CPU profiles only show on-CPU time
31.4 Use case: pprof shows low CPU% but latency is high
32. flight-recorder-go125 — Go 1.25 flight recorder for retroactive trace 6/6 0/6
32.1 Recommends Go 1.25 flight recorder (trace.NewFlightRecorder)
32.2 Keeps circular buffer of recent trace data in memory
32.3 Shows snapshotting with WriteTo on anomaly detection
32.4 Mentions MinAge and MaxBytes parameters
32.5 Shows trigger pattern (slow request detection)
32.6 At most one flight recorder active at a time
33. flight-recorder-sync-once-pattern — sync.Once for snapshot dedup 5/5 0/5
33.1 Uses sync.Once to ensure only one snapshot
33.2 Calls fr.WriteTo inside sync.Once.Do
33.3 Only one goroutine may call WriteTo at a time
33.4 Shows calling snapshot in separate goroutine
33.5 Shows fr.Stop() after WriteTo completes
34. flight-recorder-sizing — MinAge and MaxBytes configuration 4/4 0/4
34.1 MinAge ~2x problem window (10s for 5s investigation)
34.2 Busy services generate ~1-10 MB/s of trace data
34.3 Start MaxBytes at 1-5 MiB and adjust
34.4 MaxBytes takes precedence over MinAge
35. trace-timeline-color-coding — execution trace UI color interpretation 5/5 3/5
35.1 Yellow/orange = runnable, waiting for processor
35.2 Red bands across all P lanes = GC stop-the-world
35.3 Yellow gaps = CPU saturation, too many goroutines competing
35.4 Green = actively executing/running
35.5 Check goroutine count vs GOMAXPROCS to verify saturation
36. pprof-web-ui-flamegraph — interactive web UI for profile exploration 4/4 4/4
36.1 go tool pprof -http=:8080 cpu.prof for web UI
36.2 Flamegraph view is most intuitive
36.3 Lists other views: Graph, Top, Source, Disassembly, Peek
36.4 Filters can be pre-applied with -focus flag
37. pprof-focus-ignore-difference — focus vs ignore vs show vs hide semantics 5/5 3/5
37.1 focus keeps only paths containing matching function
37.2 ignore removes functions, attributes costs to callers
37.3 show is like focus but display-only, no cost accounting change
37.4 hide is like ignore but display-only, no cost accounting change
37.5 reset to clear all filters
38. pprof-tags-and-labels — pprof.Do() custom labels for multi-tenant profiling 5/5 2/5
38.1 Uses pprof.Labels() and pprof.Do() for custom labels
38.2 Correct pattern: pprof.Do(ctx, pprof.Labels("key", "value"), func...)
38.3 tagfocus to filter by label
38.4 tagroot to group by label
38.5 tags command to see all tag keys and distributions
39. pprof-sample-index-switching — switch metrics without reloading 4/4 1/4
39.1 sample_index command to switch metrics suggests exit and reopen
39.2 Correct syntax: sample_index=inuse_space
39.3 Lists available indices: alloc_objects, alloc_space, inuse_objects, inuse_space
39.4 Heap profiles contain multiple metrics, switchable interactively
40. pprof-granularity-lines — per-line costs in top output 3/3 0/3
40.1 granularity=lines for per-line grouping in top
40.2 Other levels: functions (default), filefunctions, files, addresses
40.3 Shows command: granularity=lines or -granularity=lines
41. ssa-dump-investigation — GOSSAFUNC for compiler optimization passes 5/5 1/5
41.1 GOSSAFUNC=FunctionName go build for SSA dump
41.2 Creates ssa.html openable in browser
41.3 Optimization passes: source, AST, start SSA, opt, lower, regalloc, genssa
41.4 What to look for: bounds checks, dead code, constant folding, register spills
41.5 Click values to highlight across passes
42. assembly-output-heap-allocation-detection — verify no heap allocs in assembly 4/4 4/4
42.1 Uses go build -gcflags="-S" for assembly output
42.2 Grep for CALL runtime.makeslice, runtime.newobject, growslice
42.3 go tool objdump as alternative for compiled binaries
42.4 -S flag for source-interleaved disassembly
43. value-receiver-inlining-advantage — value vs pointer receivers and inlining 4/4 1/4
43.1 Value receivers enable full inlining of method chains
43.2 Pointer receivers add indirection blocking inlining for fluent APIs
43.3 Check with -gcflags="-m" to verify inlining behavior
43.4 Considers struct size trade-off (value receivers copy the struct)
44. prometheus-go-metrics-vs-runtime-metrics — runtime/metrics ≠ Prometheus metrics 4/4 1/4
44.1 runtime/metrics are Go internal data, not Prometheus metrics
44.2 prometheus/client_golang selectively converts runtime/metrics
44.3 Lists actual Prometheus metric names (go_memstats_alloc_bytes, etc.)
44.4 By default only traditional go_memstats_* and go_gc_* are exposed
45. go-memstats-stw-overhead — ReadMemStats causing stop-the-world pauses 4/4 1/4
45.1 go_memstats_* calls ReadMemStats() which triggers short STW pause
45.2 Go 1.17+ runtime/metrics-based collector as lower overhead alternative
45.3 Shows code: collectors.NewGoCollector with GoRuntimeMetricsCollection
45.4 Use custom prometheus.NewRegistry() rather than default
46. promql-gc-pressure-queries — specific PromQL for GC pressure investigation 5/5 3/5
46.1 rate(go_gc_duration_seconds_count[5m]) for GC frequency
46.2 >2 GC cycles/s sustained = excessive allocation rate
46.3 go_gc_duration_seconds{quantile="1"} for worst-case pause
46.4 rate(go_memstats_alloc_bytes_total[5m]) for allocation rate
46.5 Correlate with P99 latency to confirm GC causes tail latency
47. promql-goroutine-leak-detection — PromQL patterns for goroutine leaks 4/4 3/4
47.1 go_goroutines gauge for current count
47.2 delta(go_goroutines[1h]) for net change
47.3 Count should correlate with load; independent growth = leak
47.4 Alerting rule with threshold (go_goroutines > 10000)
48. investigation-session-setup — structured production investigation preparation 5/5 1/5
48.1 Reduce Prometheus scrape interval to <=10s on target instance
48.2 Enable pprof via environment variable without recompile
48.3 Enable continuous profiling on target instance only, not fleet-wide
48.4 Revert all changes after investigation
48.5 All debug features should be toggleable via environment variables
49. cost-warnings-trace-duration — trace data volume and practical limits 5/5 3/5
49.1 Traces generate MB/s — 5-minute trace would be enormous
49.2 Keep traces to 5-10 seconds maximum
49.3 Large traces slow to parse, need 1GB+ RAM
49.4 Suggest flight recorder (Go 1.25+) for intermittent issues
49.5 Browser UI struggles with traces >100MB
50. benchstat-three-version-comparison — comparing more than two versions 3/3 2/3
50.1 Labels inputs: benchstat v1=v1.txt v2=v2.txt v3=v3.txt
50.2 First input is always the base for comparison
50.3 v2 vs v1 and v3 vs v1 shown (both relative to first)
51. host-level-correlation — correlating Go metrics with infrastructure 4/4 3/4
51.1 Check node_exporter for host-level CPU, memory, disk I/O
51.2 Noisy neighbor: high node_cpu with low process_cpu = external contention
51.3 process-exporter for per-process metrics on shared hosts
51.4 Correlate Go app metrics with infrastructure metrics
52. pprof-diff-base-vs-base — -base vs -diff_base semantics 4/4 1/4
52.1 -base subtracts base from source, values become deltas
52.2 -diff_base shows percentages relative to base profile
52.3 -normalize flag for comparable ratios across different durations
52.4 Generate diff SVG for visual comparison
53. gobenchdata-github-action-setup — long-term benchmark trend tracking 5/5 0/5
53.1 Recommends gobenchdata for trend tracking
53.2 GitHub Action config with bobheadxi/gobenchdata@v1
53.3 Publishing to gh-pages for dashboard
53.4 Regression checks config (.gobenchdata-checks.yml) with thresholds
53.5 PRUNE_COUNT to limit stored history
54. benchstat-single-file-summary — single-file variance analysis 4/4 3/4
54.1 benchstat bench.txt with single file
54.2 Shows median and confidence interval per benchmark
54.3 Use to check stability before making changes
54.4 High variance (± >5%) = noisy benchmarks
55. count-minimum-by-scenario — context-dependent -count recommendations 5/5 3/5
55.1 6 minimum for quick local checks
55.2 10 for standard pre-merge comparisons
55.3 20-30 for detecting small changes (<5%)
55.4 20+ for noisy CI environments
55.5 Warns against -count=1 as no variance information
56. interface-boxing-escape — any/interface{} boxing as heap allocation source 4/4 4/4
56.1 Interface boxing: concrete type in any allocates heap copy
56.2 fmt.Sprintf as common source of interface boxing
56.3 Use concrete types instead of interfaces in hot paths
56.4 Especially impactful in frequently-called functions
57. trace-scheduling-latency-diagnosis — runnable-to-running delay analysis 5/5 2/5
57.1 High scheduling latency = goroutines waiting for processor
57.2 Too many goroutines competing for GOMAXPROCS (CPU saturation)
57.3 Extract sched profile: go tool trace -pprof=sched trace.out
57.4 Check uneven distribution across Ps (work imbalance)
57.5 Other causes: OS scheduling, goroutines pinned by cgo/syscalls
58. benchmark-output-format-parsing — understanding benchmark output fields 5/5 4/5
58.1 -8 is the GOMAXPROCS suffix
58.2 5000000 is the number of iterations (b.N)
58.3 ns/op is time per operation
58.4 B/op is bytes allocated per operation
58.5 allocs/op is heap allocation count per operation says "allocations" without heap
59. pprof-show-from-framework-noise — show_from to trim routing noise 4/4 0/4
59.1 Uses show_from=regex to trim frames above first match suggests ignore instead
59.2 Shows pattern: show_from=handler.Handle
59.3 show_from hides all callers above the match point
59.4 Differentiates from ignore which re-attributes costs
60. optional-prometheus-metrics-enablement — opt-in Go runtime Prometheus metrics 5/5 1/5
60.1 Custom registry with collectors.NewGoCollector
60.2 collectors.WithGoCollectorRuntimeMetrics option
60.3 GoRuntimeMetricsAll or specific collection flags
60.4 Requires Go 1.17+
60.5 Scheduler metrics: go_sched_latencies_seconds, CPU class: go_cpu_classes_*
61. benchdiff-usage-patterns — automatic branch comparison with benchdiff 5/5 0/5
61.1 Recommends benchdiff for automatic branch comparison
61.2 Command: benchdiff -base-ref main -- -benchmem -count=10
61.3 Caches results for non-worktree refs
61.4 benchdiff -clear-cache for stale cache
61.5 Prevents macOS sleep during benchmarks
62. pprof-noinlines-attribution — simplify inlined function chains 3/3 0/3
62.1 Uses noinlines option or -noinlines flag
62.2 Attributes inlined functions to first out-of-line caller
62.3 Correct usage: noinlines interactive or -noinlines CLI
63. sub-benchmarks-table-driven — table-driven sub-benchmarks with b.Run 5/5 3/5
63.1 Uses b.Run() with descriptive names (size=64)
63.2 Uses b.Loop() inside sub-benchmark since Go 1.24 uses range b.N
63.3 Setup code before b.Loop() call uses b.ResetTimer()
63.4 Loop over sizes with fmt.Sprintf for names
63.5 Output: BenchmarkEncode/size=64, BenchmarkEncode/size=256, etc.
64. benchstat-geomean-interpretation — geometric mean row meaning 4/4 4/4
64.1 geomean = geometric mean of changes across all benchmarks
64.2 Represents overall proportional change
64.3 Useful for single summary across many benchmarks
64.4 Individual benchmarks may differ significantly from geomean
65. trace-short-lived-goroutines — goroutine creation overhead in traces 4/4 3/4
65.1 High overhead from goroutine creation/scheduling for short-lived ones
65.2 Batch work or use worker pools to reduce creation overhead
65.3 Goroutines created in loops without bounds = potential leak
65.4 Goroutines created but never finishing = leak
66. comparing-across-machines-pitfall — cross-machine comparison invalid 4/4 4/4
66.1 Warns that cross-machine comparison is invalid
66.2 Different CPUs, memory, OS = incomparable baselines
66.3 Run both on same machine with same conditions
66.4 Notes this as common benchstat pitfall
67. b-report-allocs-vs-benchmem — two ways to enable allocation reporting 4/4 4/4
67.1 b.ReportAllocs() in benchmark function
67.2 -benchmem flag on go test command
67.3 Both produce same B/op and allocs/op output
67.4 b.ReportAllocs() is per-benchmark, -benchmem is for all
68. pprof-goroutine-debug-levels — ?debug=1 and ?debug=2 for goroutine dumps 4/4 3/4
68.1 curl with ?debug=1 for human-readable dump
68.2 ?debug=2 for full stacks with creation site and labels
68.3 Shows URL: /debug/pprof/goroutine?debug=1 or ?debug=2
68.4 No go tool pprof needed for debug mode dumps
69. ci-threshold-calibration — regression thresholds by CI environment 5/5 0/5
69.1 20%+ threshold on shared/GitHub-hosted runners
69.2 10% on dedicated self-hosted runners
69.3 Tight thresholds on noisy environments = false positives eroding trust
69.4 GitHub-hosted runners ~2-3% CoV in best case
69.5 <1% false positive rate requires 7%+ gate
70. cpu-profile-sampling-rate — CPU profiler 100Hz sampling mechanism 4/4 4/4
70.1 CPU profiling uses statistical sampling at 100Hz
70.2 Functions shorter than sampling interval may not appear
70.3 Only captures on-CPU time, off-CPU invisible
70.4 ~5% overhead during CPU profile capture
71. benchstat-ignore-dimension-warning — -ignore flag for suppressing warnings 4/4 1/4
71.1 -ignore /gomaxprocs to suppress warning
71.2 -ignore omits keys from grouping
71.3 -row .name to simplify row grouping
71.4 -col /gomaxprocs to compare across values instead
72. escape-analysis-fmt-sprintf — fmt.Sprintf heap allocation explanation 4/4 4/4
72.1 fmt.Sprintf arguments boxed into any/interface{} (boxing)
72.2 Result string is also heap-allocated
72.3 fmt.Sprintf is common escape cause in compiler analysis
72.4 Alternatives for hot paths: strconv, strings.Builder, direct byte manipulation
73. runtime-readmemstats-vs-runtime-metrics — programmatic GC stats APIs 5/5 4/5
73.1 runtime.ReadMemStats for heap size, NumGC, pause durations
73.2 debug.ReadGCStats for GC-specific statistics
73.3 runtime/metrics (Go 1.16+) as preferred modern API
73.4 runtime/metrics has lower overhead and safe for concurrent reads
73.5 ReadMemStats is more expensive due to internal locking
74. pprof-callgrind-export — exporting to KCachegrind format 4/4 3/4
74.1 callgrind command or -callgrind flag to export
74.2 Command: go tool pprof -callgrind cpu.prof > cpu.callgrind
74.3 KCachegrind or QCachegrind as visualization tools
74.4 proto command for protobuf format as alternative
75. expvar-lightweight-monitoring — stdlib expvar for JSON metrics 4/4 3/4
75.1 Recommends expvar package from stdlib
75.2 import _ "expvar" auto-registers at /debug/vars
75.3 Serves JSON format
75.4 Integration with Netdata, Telegraf, custom dashboards
76. benchstat-table-flag-per-package — -table pkg for per-package tables 3/3 0/3
76.1 -table pkg flag for one table per package suggests running benchstat per pkg
76.2 Command: benchstat -table pkg old.txt new.txt
76.3 Default -table is .config (goos/goarch/pkg/cpu)
77. pprof-symbolization-remote — symbolization modes for remote profiles 4/4 1/4
77.1 Symbolization modes: local, remote, none
77.2 -symbolize=local for local binaries
77.3 PPROF_BINARY_PATH env var for binary search paths
77.4 -symbolize=remote to contact running service
78. trace-concurrent-with-flight-recorder — trace.Start and FlightRecorder coexistence 3/3 0/3
78.1 Flight recorder can run concurrently with trace.Start
78.2 At most one flight recorder active at a time
78.3 Both can be active simultaneously without conflict
79. pyroscope-overhead-warning — continuous profiling overhead at scale 4/4 3/4
79.1 ~2-5% CPU overhead per instance for continuous profiling
79.2 200 instances = significant aggregate compute and storage cost
79.3 Enable on subset of instances or on-demand via env var
79.4 Investigation session approach: target instances only, not fleet-wide
80. non-go-memory-leak-detection — PromQL for cgo/mmap leak detection 4/4 1/4
80.1 PromQL: process_resident_memory_bytes - go_memstats_sys_bytes
80.2 Gap = non-Go memory (cgo, mmap)
80.3 Growing gap = non-Go memory leak
80.4 Investigate cgo calls or memory-mapped files

golang-observability — v1.0.0

With Skill Without Skill Delta
Overall 185/185 (100%) 117/185 (63%) +37pp
Full breakdown (185 assertions across 40 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. summary-vs-histogram-multi-replica — Histogram over Summary when multiple replicas need aggregated percentiles 5/5 5/5
1.1 Recommends Histogram, not Summary
1.2 Explains that Summary quantiles cannot be aggregated across multiple instances
1.3 Shows histogram_quantile() PromQL function for computing percentiles server-side
1.4 Mentions that Histogram supports server-side aggregation across replicas
1.5 Uses prometheus.NewHistogramVec (not NewSummary) in the code example
2. log-and-return-error-trap — single handling rule: log OR return, never both 5/5 5/5
2.1 Identifies the log-and-return pattern as incorrect
2.2 Explains that the error gets logged multiple times as it propagates up the chain
2.3 Recommends returning the error with context and logging once at the top level
2.4 Shows the corrected pattern: return fmt.Errorf with wrapping, no slog call
2.5 References or explains the single handling rule (errors are either logged OR returned, never both)
3. high-cardinality-label-trap — high-cardinality label usage in Prometheus metrics 6/6 4/6
3.1 Identifies userID as a high-cardinality label that will cause problems
3.2 Identifies r.URL.Path as potentially high-cardinality (should use route template instead) focuses on userID, misses URL.Path
3.3 Explains that each unique label combination creates a separate time series
3.4 Warns about memory explosion on the Prometheus server from unbounded labels
3.5 Recommends using route patterns/templates (e.g., /users/:id) instead of actual paths generic advice without route template
3.6 Suggests using traces (not metrics) for high-cardinality data like user IDs
4. production-json-logging — JSON handler for production, not TextHandler 4/4 3/4
4.1 Recommends JSONHandler for production, not TextHandler
4.2 Explains that plain-text multiline logs (e.g., stack traces) get split into separate records by log collectors generic "not machine-parseable"
4.3 Suggests TextHandler is appropriate for development only
4.4 Shows the correct JSONHandler setup with slog.LevelInfo for production
5. slog-context-variant-trace-correlation — *Context variants for trace correlation 4/4 3/4
5.1 Identifies that slog.Info and slog.Error should use their *Context variants
5.2 Explains that without ctx, trace_id and span_id won't be injected into log records unaware of otelslog injection mechanism
5.3 Shows the corrected code using slog.InfoContext(ctx, ...) and slog.ErrorContext(ctx, ...)
5.4 Mentions that the otelslog bridge automatically injects trace correlation when context is passed
6. metric-naming-conventions — base units, _total suffix, namespace 5/5 5/5
6.1 Flags request_count as missing namespace and unit suffix
6.2 Flags httpDuration as using camelCase and missing unit
6.3 Flags request_duration_ms — should use _seconds (base unit), not milliseconds
6.4 Flags myapp_request_size_kb — should use _bytes (base unit), not kilobytes
6.5 Flags embedding label values into metric names — should use a single metric with a method label
7. irate-vs-rate-for-alerts — irate() is inappropriate for alerting rules 5/5 3/5
7.1 Identifies irate() as inappropriate for alerting rules
7.2 Explains that irate reacts to a single scrape interval and is too volatile knows irate differs but imprecise why
7.3 Recommends rate() instead of irate() for alerts
7.4 Recommends adding a for: duration to avoid firing on transient spikes
7.5 Shows the corrected alert rule using rate() with a for: clause shows rate() but misses error ratio
8. alert-missing-for-duration — alerts without for: duration clause 4/4 3/4
8.1 Identifies the missing for: duration as a problem
8.2 Explains that without for:, a single bad scrape triggers the alert
8.3 Recommends adding for: 5m or similar duration
8.4 Distinguishes that binary alerts (service up/down) can use for: 0m, but non-binary need a duration generic advice, no binary distinction
9. promql-comments-convention — documenting metrics with PromQL comments above declarations 5/5 0/5
9.1 Recommends adding PromQL queries and alert rules as comments directly above the metric var suggests wiki or README
9.2 Shows example Dashboard: and Alert: comment lines above the metric var no such convention known
9.3 Explains that this keeps PromQL queries reviewed in PRs alongside the metric
9.4 Mentions that queries stay in sync with metric changes (label renames, bucket changes)
9.5 Notes that new team members can understand the metric's purpose at a glance
10. otelslog-bridge-setup — log-trace correlation using otelslog bridge 5/5 1/5
10.1 Recommends using the otelslog bridge from go.opentelemetry.io/contrib/bridges/otelslog suggests manual trace_id extraction
10.2 Shows creating a handler with otelslog.NewHandler()
10.3 Shows setting it as default with slog.SetDefault()
10.4 Explains that trace_id and span_id are automatically injected into log records
10.5 Emphasizes using slog.*Context(ctx, ...) variants to enable the automatic injection
11. exemplars-metric-trace-link — metrics-to-traces correlation via Prometheus exemplars 3/3 1/3
11.1 Recommends using Prometheus exemplars to link metrics to traces suggests manual correlation
11.2 Shows attaching trace_id as an exemplar when recording histogram observations
11.3 Explains that exemplars let you jump from a metric spike directly to the trace
12. span-error-recording-both-calls — RecordError() AND SetStatus(Error) required 4/4 3/4
12.1 Identifies that span.SetStatus(codes.Error, ...) is also needed alongside RecordError
12.2 Explains that RecordError adds an event but does not mark the span as failed may not distinguish event vs status
12.3 Shows the corrected pattern with both span.RecordError(err) and span.SetStatus(codes.Error, ...)
12.4 Notes that on success, no status needs to be set (Unset is fine)
13. otelhttp-outgoing-requests — outgoing HTTP clients must use otelhttp transport 4/4 4/4
13.1 Recommends wrapping the HTTP client transport with otelhttp.NewTransport
13.2 Shows the code: client := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
13.3 Explains that this automatically propagates trace context to outgoing requests
13.4 Mentions that otelhttp creates child spans for outgoing HTTP calls
14. db-query-context-propagation — database calls must use *Context variants 4/4 4/4
14.1 Identifies that db.Query should be db.QueryContext(ctx, ...)
14.2 Explains that without context, the trace is broken
14.3 Shows the corrected code using db.QueryContext(ctx, ...)
14.4 States that context is the vehicle that carries trace_id and span_id across boundaries
15. trace-sampling-cost-control — trace sampling strategies and cost implications 5/5 4/5
15.1 Recommends TraceIDRatioBased sampling with a specific ratio (e.g., 0.1 for 10%)
15.2 Mentions ParentBased sampler to respect parent's sampling decision
15.3 Discusses head-based vs tail-based sampling tradeoffs
15.4 Recommends avoiding large payloads as span attributes — log and correlate via trace_id generic cost advice
15.5 Explains the cost factors: span volume, span attributes, storage and indexing
16. where-to-add-spans — which operations must have spans in OpenTelemetry 5/5 4/5
16.1 Lists service methods (business logic layer) as requiring spans
16.2 Lists database queries as requiring spans
16.3 Lists external API calls as requiring spans
16.4 Lists message queue publish/consume operations as requiring spans
16.5 States any operation that takes measurable time or could fail should have a span gives specific list, misses general rule
17. four-golden-signals-alerting — four golden signals for service alerting 5/5 5/5
17.1 References the four golden signals: latency, traffic, errors, saturation
17.2 Includes a latency alert (e.g., P99 > threshold)
17.3 Includes a traffic alert (e.g., zero requests detection)
17.4 Includes an error rate alert (e.g., 5xx ratio > threshold)
17.5 Includes a saturation alert (e.g., connection pool > 90%)
18. awesome-prometheus-alerts-resource — awesome-prometheus-alerts as a starting point 4/4 0/4
18.1 Recommends awesome-prometheus-alerts (samber.github.io/awesome-prometheus-alerts/) suggests writing rules from scratch
18.2 Mentions it contains ~500 ready-to-use Prometheus alerting rules organized by technology
18.3 Suggests the workflow: browse by technology, copy rules, customize thresholds
18.4 Mentions verifying that exporters (postgres_exporter, redis_exporter) are deployed
19. go-runtime-alerts — Go runtime-specific Prometheus alerts 5/5 3/5
19.1 Suggests alerting on go_goroutines exceeding a threshold for goroutine leaks
19.2 Suggests alerting on go_gc_duration_seconds for GC pressure
19.3 Suggests alerting on go_memstats_alloc_bytes / go_memstats_sys_bytes for memory leaks
19.4 Suggests alerting on go_threads for high OS thread count rarely includes thread alerts
19.5 Uses for: duration on all non-binary alerts to avoid false positives omits for: on some alerts
20. alert-severity-levels — severity classification and for: durations 6/6 4/6
20.1 Uses two severity levels: critical (page on-call) and warning (create ticket)
20.2 Critical alerts: for: 2m to 5m for fast detection
20.3 Warning alerts: for: 10m to 30m for confirmed trends
20.4 Classifies service down as critical with short for: duration
20.5 Classifies goroutine leak as warning (not critical) may classify as critical
20.6 States that for: 0m should never be used on non-binary alerts not a widely known rule
21. multi-window-burn-rate-slo — multi-window burn-rate SLO alerting 5/5 3/5
21.1 Recommends multi-window burn-rate alerting instead of simple threshold alerts
21.2 Explains the concept of error budget and burn rate
21.3 Includes fast burn window (e.g., 5m + 1h, 14.4x burn rate) as critical/page generic burn rate, no specific windows
21.4 Includes slow burn window (e.g., 2h + 24h, 1x burn rate) as warning/ticket no slow burn tier
21.5 Shows PromQL using AND of short and long windows to eliminate false positives
22. slog-migration-from-zap — incremental migration from zap to slog using bridge 5/5 1/5
22.1 Recommends a three-step migration: bridge, replace call sites, remove bridge suggests gradual replacement only
22.2 Step 1: Use samber/slog-zap bridge handler to route slog output through zap unaware of samber/slog-zap
22.3 Step 2: Gradually replace zap.L().Info(...) calls with slog.Info(...)
22.4 Step 3: Once fully migrated, replace bridge with native slog JSONHandler and remove zap no bridge step to remove
22.5 Mentions using parallel sub-agents for large codebase migration not a known pattern
23. slog-migration-from-logrus — bridge handler approach for logrus migration 4/4 2/4
23.1 Recommends using samber/slog-logrus bridge handler for incremental migration unaware of samber/slog-logrus
23.2 Explains that slog is the standard library logger since Go 1.21
23.3 Shows the bridge step: route slog output through the existing logrus logger
23.4 Shows the replacement: logrus.WithField becomes slog.Info
24. debug-level-production-cost — log level cost implications in production 5/5 3/5
24.1 Recommends slog.LevelInfo for production, NOT Debug
24.2 Explains that Debug level can generate millions of log lines per minute in busy services
24.3 Mentions cost: CPU for serialization, I/O for disk/network, money for log ingestion/storage
24.4 Mentions Debug can inflate costs by 10-100x no specific multiplier
24.5 Suggests samber/slog-sampling as an alternative to sample verbose logs unaware of samber/slog-sampling
25. pii-in-logs-trap — catching PII being logged 4/4 4/4
25.1 Flags email as PII that should not be logged
25.2 Flags SSN as PII that should absolutely never be logged
25.3 Recommends logging identifiers (user_id) instead of PII
25.4 Shows corrected logging using user.ID instead of email/SSN
26. counter-suffix-total-requirement — Prometheus counters must use _total suffix 3/3 3/3
26.1 Identifies the missing _total suffix — counters MUST end with _total
26.2 Shows the corrected name: requests_total or http_requests_total
26.3 Mentions that _total is a required convention for counters in Prometheus
27. pprof-security-auth — pprof endpoints must be protected with authentication 4/4 4/4
27.1 Warns that pprof endpoints must NOT be exposed publicly without authentication
27.2 Explains that pprof leaks sensitive runtime information and can be abused for DoS
27.3 Recommends protecting with basic auth or running on a separate internal port
27.4 Suggests toggling via environment variable to enable/disable without redeployment
28. continuous-profiling-env-toggle — toggle continuous profiling via environment variables 5/5 2/5
28.1 Recommends toggling via environment variable (e.g., PROFILING_ENABLED)
28.2 Mentions ~2-5% CPU overhead for continuous profiling no specific overhead figure
28.3 Suggests starting with CPU + heap profiles only, adding mutex/block when needed suggests all profiles
28.4 For large deployments, recommends enabling on a fraction of replicas (e.g., 1 in 10) not a widely known practice
28.5 Shows code that checks the environment variable before starting Pyroscope
29. rum-identity-key-email-trap — RUM distinct_id must be user_id, not email 5/5 4/5
29.1 Rejects email as the DistinctId — must use user_id instead
29.2 Explains that email is mutable — users change it, splitting events into two users
29.3 Explains that email is PII, complicating GDPR/CCPA compliance
29.4 Notes that email leaks into third-party analytics systems as the identity key not commonly considered
29.5 Shows corrected code using user.ID (immutable internal identifier)
30. gdpr-consent-before-tracking — GDPR consent must be checked before sending events 5/5 3/5
30.1 Identifies that consent must be checked before sending the tracking event
30.2 Shows extracting consent from context and conditionally tracking generic consent advice without Go code
30.3 Mentions GDPR fines (up to 4% of global revenue) or CCPA penalties
30.4 References data minimization — only collect what you need
30.5 Mentions data subject rights endpoints (data export and deletion) focuses on consent only
31. data-subject-rights-endpoints — GDPR requires deletion/export across all systems 5/5 3/5
31.1 States that deletion must propagate to ALL systems holding user data
31.2 Lists the analytics platform (PostHog) as needing deletion
31.3 Lists the CDP (Segment) as needing deletion may not mention CDP deletion
31.4 References GDPR Article 17 Right to Erasure
31.5 Also mentions the Right of Access (data export endpoint) as a requirement focuses on deletion only
32. five-signals-completeness — five observability signals and their distinct roles 6/6 4/6
32.1 Lists all five signals: logs, metrics, traces, profiles, and RUM lists logs, metrics, traces only
32.2 Associates logs with 'what happened' (discrete events, audit trails)
32.3 Associates metrics with 'how much/how fast' (aggregated measurements, alerting, SLOs)
32.4 Associates traces with 'where did time go' (request flow across services)
32.5 Associates profiles with 'why is it slow/using memory' (CPU hotspots, memory leaks)
32.6 Associates RUM with 'how do users experience it' (product analytics, funnels) RUM not listed as a signal
33. definition-of-done-observability — observability checklist before shipping a feature 6/6 4/6
33.1 States that a feature is not production-ready until it is observable
33.2 Checks for metric declarations (counters, histograms, gauges) with PromQL comments does not mention PromQL comments
33.3 Checks for proper structured logging with slog and context variants
33.4 Checks for OpenTelemetry spans on service methods, DB queries, and external calls
33.5 Checks for dashboards and alerts being wired up
33.6 Checks that errors are either logged OR returned, never both not part of typical deploy checklist
34. grafana-dashboard-ids — specific Grafana dashboard IDs for Go runtime monitoring 4/4 0/4
34.1 Recommends specific Grafana dashboard IDs (21221, 6671, or 10826) suggests building custom dashboards
34.2 Mentions dashboard 21221 for host + runtime combined view
34.3 Explains that these dashboards use default Go collector metrics from the Prometheus client library
34.4 Shows how to import: Dashboards > New > Import, enter the dashboard ID
35. slog-with-request-scoped-attrs — slog.With() for request-scoped attributes 4/4 4/4
35.1 Recommends using slog.With() to create a child logger with request-scoped attributes
35.2 Shows middleware pattern that creates the enriched logger
35.3 Shows storing the enriched logger in context for downstream use
35.4 Includes request_id, method, and path as the attributes to inject
36. slog-ecosystem-handlers — slog handler ecosystem beyond stdlib 5/5 1/5
36.1 Recommends samber/slog-multi for fan-out to multiple handlers suggests custom io.MultiWriter
36.2 Mentions samber/slog-sentry for sending errors to Sentry
36.3 Mentions samber/slog-datadog for sending logs to Datadog
36.4 Explains that slog supports pluggable handlers
36.5 References the slog ecosystem (go.dev/wiki/Resources-for-slog or similar)
37. parallel-observability-audit — parallel sub-agents for observability audits 5/5 0/5
37.1 Recommends using up to 5 parallel sub-agents (via the Agent tool) suggests linear approach
37.2 Assigns one sub-agent per signal: metrics, logging, tracing, profiling, RUM
37.3 Sub-agent for metrics: verify metric declarations and PromQL comments
37.4 Sub-agent for logging: check structured logging, PII in logs, error logging patterns
37.5 Sub-agent for tracing: verify span creation in service methods, DB calls, API calls
38. predict-linear-for-saturation — predict_linear for anticipating resource exhaustion 4/4 4/4
38.1 Recommends using predict_linear() PromQL function to extrapolate trends
38.2 Shows expression: predict_linear(db_connections_active[15m], 600) > db_connections_max
38.3 Explains that predict_linear extrapolates from recent trend to predict future value
38.4 Also suggests a threshold alert (e.g., > 90%) as a complementary alert
39. self-hosted-rum-gdpr — self-hosted analytics for GDPR compliance 4/4 4/4
39.1 Recommends self-hosted analytics (PostHog or Matomo) for EU data residency
39.2 Explains that self-hosting eliminates cross-border data transfer concerns
39.3 Compares self-hosted vs SaaS tradeoffs (data residency, cost, maintenance, features)
39.4 Mentions that PostHog can be self-hosted to keep data in your own infrastructure
40. oops-structured-errors-tracing — samber/oops for structured errors in tracing 4/4 0/4
40.1 Recommends samber/oops for structured errors with stack traces suggests fmt.Errorf or manual attrs
40.2 Shows using oops with .In(), .Code(), and .With() for structured context
40.3 Explains that oops errors carry stack trace, structured context, and work with span.RecordError()
40.4 Mentions compatibility with errors.Is/errors.As and slog

golang-performance — v1.0.0

With Skill Without Skill Delta
Overall 272/272 (100%) 167/272 (61%) +39pp
Full breakdown (272 assertions across 68 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. profile-before-optimizing — Tests whether the model insists on profiling before applying optimizations 5/5 3/5
1.1 Recommends profiling (pprof, fgprof, or tracing) before making code changes
1.2 Identifies fetchFromDB as the likely bottleneck (external I/O, not Go code)
1.3 Mentions that intuition about bottlenecks is often wrong (~80% of the time) no specific stat
1.4 Does NOT primarily focus on micro-optimizing strings.ToUpper or JSON encoding suggests strings.Builder/JSON fixes
1.5 Suggests investigating the database query (query tuning, caching, connection pool)
2. fgprof-off-cpu-bottleneck — Tests whether the model recommends fgprof for off-CPU bottlenecks 5/5 4/5
2.1 Recommends fgprof as the primary tool for capturing off-CPU wait time suggests goroutine profiles or tracing
2.2 Explains that standard pprof CPU profile only captures on-CPU time
2.3 Suggests the bottleneck is likely I/O wait (network, database, filesystem)
2.4 Mentions goroutine profile as a complementary diagnostic
2.5 Suggests distributed tracing (OpenTelemetry) for identifying slow upstream services
3. iterative-benchmark-methodology — Tests iterative benchmark approach (one change at a time, benchstat) 5/5 1/5
3.1 Recommends writing an atomic benchmark for ProcessRecords first
3.2 Recommends measuring a baseline with -benchmem and -count=6 for statistical significance skips -count=6
3.3 Recommends applying ONE optimization at a time, not all at once applies all at once
3.4 Recommends using benchstat to compare before/after with statistical significance no benchstat
3.5 Suggests keeping report files as an audit trail (e.g., /tmp/report-1.txt)
4. slice-reuse-append-zero — Tests knowledge of append(s[:0], ...) for reusing slice backing arrays 4/4 2/4
4.1 Suggests using append(mode[:0], item) to reuse the backing array suggests sync.Pool or new slice
4.2 Explains that reslicing to zero length retains the backing array
4.3 Moves the mode variable declaration outside the loop to enable reuse
4.4 Does NOT suggest sync.Pool as the primary solution for this simple case
5. direct-indexing-vs-append — Tests direct indexing over append when output size equals input 3/3 2/3
5.1 Suggests using make([]Result, len(input)) with direct assignment result[i] = convert(...)
5.2 Explains that direct assignment avoids per-element append overhead (bounds check, length increment)
5.3 Notes that append is better when the result might be smaller (filtering)
6. map-range-double-lookup — Tests catching redundant map lookups in range loops 2/2 2/2
6.1 Identifies that for k := range in { in[k] } does two map lookups per iteration
6.2 Suggests for k, v := range in { result[k] = strconv.Itoa(v) } for single lookup
7. sentinel-errors-hot-path — Tests preallocated sentinel errors over fmt.Errorf in hot paths 4/4 4/4
7.1 Converts the static error to a preallocated sentinel using errors.New at package level
7.2 Keeps fmt.Errorf for the dynamic error that includes %d
7.3 Explains that fmt.Errorf allocates on every call, while sentinels allocate once
7.4 Does NOT convert the dynamic error to a sentinel
8. interface-boxing-hot-path — Tests interface boxing allocation cost and generics fix 4/4 3/4
8.1 Identifies interface boxing (any parameter) as the source of allocations
8.2 Suggests typed functions or generics to eliminate boxing
8.3 Explains that each concrete value passed through any requires a heap allocation for boxing
8.4 Does NOT focus only on the type switch as the optimization target
9. backing-array-leak-slice — Tests backing array retention when returning a small reslice 4/4 4/4
9.1 Identifies that message[:32] retains the entire backing array (1-10MB)
9.2 Suggests using copy() to create an independent 32-byte slice
9.3 Explains that the original large array cannot be GC'd while the reslice exists
9.4 Provides correct copy-based fix: make([]byte, 32) + copy(header, message[:32])
10. substring-memory-leak — Tests strings.Clone pattern for substring memory leaks 3/3 2/3
10.1 Identifies that substrings share the backing array of the original string
10.2 Suggests strings.Clone(line[12:48]) to create an independent copy suggests string([]byte(s))
10.3 Explains that each 36-char ID retains the entire 500-2000 byte log line
11. map-never-shrinks — Tests Go maps never release bucket memory and compact pattern 4/4 4/4
11.1 Explains that Go maps never release bucket memory when entries are deleted
11.2 Suggests periodically recreating the map by copying entries to a new map
11.3 Provides the compact pattern: make(map[K]V, len(old)) + range copy
11.4 Does NOT suggest that calling runtime.GC() or tuning GOGC will fix this
12. sync-pool-rules — Tests proper sync.Pool usage: reset, size limits, pointer pooling 5/5 2/5
12.1 Identifies that HandleLargeUpload puts oversized buffers (100MB+) back into the pool
12.2 Mentions the 32KB guideline — don't pool objects larger than ~32KB vague size advice
12.3 Identifies that w.Write(buf) may retain the buffer after bufPool.Put(buf)
12.4 Suggests pooling pointers (*[]byte) instead of values to avoid allocation on Get
12.5 Recommends resetting/clearing state before Put
13. struct-field-alignment — Tests struct field ordering for optimal memory layout 5/5 4/5
13.1 Identifies that the struct has wasted padding bytes due to alignment
13.2 Suggests reordering fields from largest to smallest
13.3 Provides a reordered struct that is smaller than the original
13.4 Mentions the fieldalignment tool for automated detection
13.5 States alignment requirements (bool=1, int32=4, int64/float64=8)
14. zero-size-field-end-of-struct — Tests struct{} at end of struct adds word-sized padding 4/4 0/4
14.1 Explains that a zero-size field at the end of a struct causes word-sized padding
14.2 Explains the reason: preventing pointer overlap with next memory block
14.3 Suggests moving struct{} to the beginning of the struct
14.4 Shows the fix: type Entry struct { Flag struct{}; Value int64 } = 8 bytes
15. map-pointer-vs-value-tradeoff — Tests map[K]*V vs map[K]V tradeoff for large structs 5/5 3/5
15.1 Suggests using map[string]*Player to allow direct field modification
15.2 Explains that map values are not addressable
15.3 Shows players[id].Score += delta with pointer map
15.4 Mentions the tradeoff: pointer maps add GC pressure from separate heap allocations no GC tradeoff
15.5 Notes that for small, mostly-read structs, map[K]V (value) is better
16. inlining-log-in-hot-path — Tests that log calls prevent function inlining 4/4 2/4
16.1 Identifies that log calls prevent the function from being inlined focuses on formatting cost
16.2 Suggests removing log calls from the hot-path function
16.3 Mentions using go build -gcflags="-m" to verify inlining decisions
16.4 Explains that function call overhead matters when called millions of times
17. value-receiver-inlining — Tests value receivers enable inlining for fluent chains 4/4 0/4
17.1 Suggests changing to value receivers instead of pointer receivers
17.2 Explains that value receivers allow the compiler to inline the fluent chain
17.3 Explains that pointer receivers add indirection that blocks inlining
17.4 Shows the value receiver signature: func (c Config) WithTimeout(d time.Duration) Config
18. cache-locality-matrix-traversal — Tests row-major vs column-major traversal and cache effects 5/5 4/5
18.1 Identifies the column-first traversal as the cause (cache misses)
18.2 Explains that Go stores 2D arrays in row-major order
18.3 Suggests swapping loop order to row-first
18.4 Mentions the performance difference from cache effects (10-50x or similar) says "significant" without quantifying
18.5 Does NOT primarily suggest parallelism or SIMD as the first fix
19. contiguous-2d-allocation — Tests contiguous 2D allocation for cache-friendly matrix access 4/4 4/4
19.1 Identifies that per-row allocation scatters data across the heap
19.2 Suggests single contiguous allocation: make([]float64, rows*cols)
19.3 Shows slicing the contiguous array into row views: data[i*cols : (i+1)*cols]
19.4 Explains that contiguous memory improves cache locality
20. soa-vs-aos — Tests Struct of Arrays vs Array of Structs for single-field iteration 4/4 1/4
20.1 Identifies that loading entire Particle structs wastes cache space when only X is needed
20.2 Suggests Struct of Arrays (SoA) layout with separate slices suggests parallelism
20.3 Explains cache utilization improvement (contiguous X values vs scattered)
20.4 Notes that AoS is fine when accessing all fields together or for small structs
21. false-sharing-concurrent-counters — Tests false sharing and cache-line padding 5/5 0/5
21.1 Identifies false sharing as the cause (fields share same cache line) suggests mutex or sharding
21.2 Explains that writes to one field invalidate the cache line for other cores
21.3 Suggests cache-line padding (56-byte array between fields)
21.4 Mentions the 64-byte cache line size
21.5 Notes this should only be applied when profiling confirms contention
22. ilp-multi-accumulator — Tests instruction-level parallelism with multiple accumulators 5/5 0/5
22.1 Identifies the sequential dependency chain as the bottleneck suggests goroutines or SIMD
22.2 Suggests using multiple accumulators (e.g., 4) for ILP
22.3 Shows code with 4 independent accumulators summing every 4th element
22.4 Handles the remainder elements (when len(data) is not divisible by 4)
22.5 Mentions expected 2-4x improvement from ILP
23. index-based-tree-cache-locality — Tests index-based vs pointer-based data structures 4/4 4/4
23.1 Recommends Option B (index-based) for better cache locality
23.2 Explains that pointer-based nodes are scattered across the heap
23.3 Explains that index-based nodes are stored in a contiguous array
23.4 Mentions CPU cache lines or memory prefetching
24. tight-loop-scheduler-starvation — Tests tight CPU loops starving the Go scheduler 5/5 2/5
24.1 Explains that tight CPU loops with inlined operations can delay scheduler preemption
24.2 Suggests breaking work into batches processed by a non-inlined function call suggests runtime.Gosched()
24.3 Mentions //go:noinline as an option to force preemption points
24.4 Explains the tradeoff: //go:noinline adds overhead but ensures scheduler fairness
24.5 Mentions that Go 1.14+ has async preemption but tight loops can still cause issues
25. reflect-deepequal-performance — Tests reflect.DeepEqual 50-200x slower than typed comparison 4/4 2/4
25.1 Identifies reflect.DeepEqual as 50-200x slower than typed comparison
25.2 Suggests using slices.Equal for the Hosts field uses manual loop
25.3 Suggests using maps.Equal for the Settings field
25.4 Provides a hand-written typed comparison function
26. type-switch-vs-repeated-assertions — Tests type switch dispatches in one evaluation 3/3 3/3
26.1 Suggests replacing repeated type assertions with a type switch
26.2 Explains that a type switch dispatches in a single evaluation
26.3 Shows the switch v := v.(type) { case string: ... } pattern
27. http-transport-maxidleconnsperhost — Tests default MaxIdleConnsPerHost=2 gotcha 5/5 3/5
27.1 Identifies MaxIdleConnsPerHost defaulting to 2 as the root cause
27.2 Suggests configuring http.Transport with higher MaxIdleConnsPerHost
27.3 Shows complete Transport configuration with MaxIdleConns, MaxIdleConnsPerHost, MaxConnsPerHost only sets MaxIdleConnsPerHost
27.4 Mentions draining resp.Body for connection reuse (io.Copy to io.Discard)
27.5 Does NOT suggest a third-party connection pool library as the primary solution
28. response-body-drain — Tests HTTP connections only reused when body fully read 3/3 3/3
28.1 Identifies that the response body is not being fully read/drained
28.2 Explains that connections are only reused when the body is fully consumed
28.3 Suggests adding io.Copy(io.Discard, resp.Body)
29. streaming-vs-readall — Tests streaming vs buffering for large payloads 4/4 3/4
29.1 Identifies io.ReadAll as the cause of OOM
29.2 Suggests using io.Copy(w, resp.Body) to stream with constant memory
29.3 Mentions the 32KB internal buffer of io.Copy
29.4 Notes that io.ReadAll is fine for small, bounded payloads (< 1MB)
30. json-streaming-decoder — Tests json.NewDecoder for streaming large JSON payloads 3/3 2/3
30.1 Suggests using json.NewDecoder with r.Body directly
30.2 Shows the dec.More() + dec.Decode(&item) streaming pattern
30.3 Explains that this processes one item at a time with O(1) memory per item
31. cgo-overhead-tight-loop — Tests cgo call overhead (~50-100ns) and batching strategy 4/4 2/4
31.1 Identifies cgo overhead (~50-100ns per call) as the bottleneck says "overhead" without quantifying
31.2 Suggests using math.Sqrt (pure Go, inlineable) instead of C.sqrt
31.3 For unavoidable C code, suggests batching: pass entire array to C in one call
31.4 Mentions that goroutine is pinned to OS thread during cgo calls
32. gogc-gomemlimit-container — Tests GOMEMLIMIT for containerized applications 5/5 3/5
32.1 Recommends setting GOMEMLIMIT to 80-90% of container memory (400-450MiB) suggests GOMEMLIMIT without ratio
32.2 Explains that the GC needs GOMEMLIMIT to know about the container ceiling
32.3 Shows the GOMEMLIMIT=450MiB environment variable or debug.SetMemoryLimit
32.4 Explains the gap (goroutine stacks, OS buffers, non-heap memory)
32.5 Does NOT recommend the ballast pattern (obsolete since Go 1.19)
33. ballast-pattern-obsolete — Tests GOMEMLIMIT over ballast pattern 4/4 4/4
33.1 Identifies the ballast pattern as obsolete since Go 1.19
33.2 Recommends GOMEMLIMIT as the replacement
33.3 Explains that GOMEMLIMIT provides the same benefit without wasting physical memory
33.4 Shows the GOMEMLIMIT environment variable or debug.SetMemoryLimit call
34. gomaxprocs-container-go125 — Tests Go 1.25+ container-aware GOMAXPROCS vs automaxprocs 4/4 1/4
34.1 States that Go 1.25+ automatically detects container CPU limits
34.2 Recommends removing the automaxprocs dependency keeps automaxprocs
34.3 Mentions that automaxprocs IS needed for Go 1.24 and earlier
34.4 Mentions cgroup CPU quota detection as the mechanism
35. pgo-workflow — Tests PGO workflow and expected gains 5/5 3/5
35.1 Recommends Profile-Guided Optimization (PGO)
35.2 Describes the workflow: collect profile, save as default.pgo, rebuild
35.3 Mentions expected improvement of 2-7% no quantification
35.4 Explains PGO benefits: aggressive inlining and devirtualization
35.5 Notes that profiles should be refreshed after significant code changes
36. slog-logattrs-hot-path — Tests slog.LogAttrs for zero-allocation logging 4/4 1/4
36.1 Explains that log arguments are evaluated/boxed before the level check
36.2 Recommends slog.LogAttrs for zero allocations when level is disabled suggests level check guard
36.3 Shows typed attributes: slog.Int("id", item.ID), slog.String("name", item.Name)
36.4 Notes that slog.Any can still allocate, so typed attributes are preferred
37. regexp-compile-per-call — Tests compiled pattern caching vs per-call compilation 4/4 3/4
37.1 Identifies that regexp compilation happens on every call (~5,700ns per compile)
37.2 Suggests moving regexp.MustCompile to package-level variables
37.3 Notes that compiled regexps are safe for concurrent use
37.4 Quantifies the waste (10-12x overhead from recompilation vs match-only)
38. singleflight-cache-stampede — Tests singleflight for cache stampede prevention 4/4 4/4
38.1 Identifies this as a cache stampede problem
38.2 Recommends golang.org/x/sync/singleflight.Group
38.3 Shows sf.Do(key, func) pattern
38.4 Does NOT suggest a global mutex as the primary solution
39. algorithmic-complexity-slice-contains-loop — Tests O(n*m) from slices.Contains in loop 4/4 3/4
39.1 Identifies O(n*m) complexity from slices.Contains inside a loop
39.2 Suggests building a map[string]struct{} from the valid slice first
39.3 Shows the O(n+m) solution with map lookup
39.4 Uses struct{} (0 bytes) for the map value type, not bool uses bool
40. early-return-full-scan — Tests early returns to avoid full collection scans 3/3 3/3
40.1 Identifies that the function always scans the full collection
40.2 Adds an early return when the first expired session is found
40.3 Removes the found variable in favor of direct returns
41. iterator-chain-vs-direct-loop — Tests iterator chains create closure overhead 4/4 4/4
41.1 Recommends Option B (direct loop) for performance
41.2 Explains that iterator chains create closures and intermediate allocations
41.3 Notes that Filter processes ALL elements before First can pick one
41.4 Acknowledges that iterator chains are fine for non-hot paths
42. indirect-function-calls-closure — Tests closure indirection prevents inlining 4/4 2/4
42.1 Suggests replacing the Map+closure pattern with a direct loop
42.2 Explains that the closure/function call indirection prevents inlining
42.3 Shows the direct loop: result[i] = *ptrs[i]
42.4 Mentions the expected improvement range (13-17%)
43. http-server-no-timeouts — Tests zero-value http.Server has NO timeouts 4/4 4/4
43.1 Identifies that the default http.Server has NO timeouts
43.2 Mentions Slowloris attack or slow client holding connections indefinitely
43.3 Suggests setting ReadTimeout, WriteTimeout, and IdleTimeout
43.4 Shows creating an explicit http.Server struct with timeout values
44. http-keepalive-crawler — Tests disabling keep-alive for crawlers 3/3 1/3
44.1 Identifies that idle connections accumulate across many different hosts
44.2 Suggests DisableKeepAlives: true for the crawler client lowers MaxIdleConnsPerHost instead
44.3 Explains that keep-alive is counterproductive for many unique hosts
45. buffered-io-syscall-reduction — Tests bufio for reducing syscall count 4/4 4/4
45.1 Identifies that each WriteString issues a separate syscall
45.2 Suggests using bufio.NewWriter(f) to batch writes
45.3 Includes w.Flush() at the end
45.4 Does NOT primarily suggest strings.Builder
46. concurrent-pipeline-when-not-to-use — Tests when concurrent pipelines are NOT beneficial 5/5 1/5
46.1 Recommends AGAINST concurrent pipelines for this case recommends concurrency
46.2 Explains that all three stages compete for the same resource (CPU)
46.3 Notes that concurrency only helps when stages saturate DIFFERENT resources
46.4 Mentions context-switching overhead as a cost
46.5 Suggests sequential processing or batching as simpler alternative
47. batch-db-inserts — Tests batch database inserts vs row-by-row 5/5 5/5
47.1 Identifies individual inserts as the problem (50K round-trips)
47.2 Suggests batch inserts (multi-row VALUES or COPY protocol)
47.3 Shows a batching pattern with configurable batch size
47.4 Wraps batches in transactions for atomicity
47.5 Does NOT suggest only connection pooling or prepared statements
48. panic-recover-control-flow — Tests panic/recover not for control flow 4/4 3/4
48.1 Identifies that panic/recover is unnecessary since strconv.Atoi returns errors
48.2 Explains that panic allocates a stack trace and unwinds the stack (10-100x overhead) says "overhead" without quantifying
48.3 Suggests using simple error checking: v, err := strconv.Atoi(s)
48.4 States that panic/recover should only be used for truly unrecoverable situations
49. monotonic-time-since — Tests time.Since monotonic clock for elapsed time 3/3 3/3
49.1 Suggests using time.Since(start) for elapsed time measurement
49.2 Explains that time.Since uses the monotonic clock
49.3 Notes that time.Now() already captures monotonic time for Sub() operations
50. prometheus-gc-pressure-queries — Tests specific PromQL queries for GC pressure 4/4 0/4
50.1 Provides rate(go_gc_duration_seconds_count[5m]) for GC frequency generic approach
50.2 Provides go_gc_duration_seconds{quantile="1"} for worst-case GC pause
50.3 Mentions >2 cycles/s sustained as a signal of excessive allocation rate
50.4 Suggests rate(go_memstats_alloc_bytes_total[5m]) for allocation rate
51. goroutine-leak-prometheus — Tests PromQL for detecting goroutine leaks 3/3 2/3
51.1 Provides go_goroutines metric for goroutine count monitoring
51.2 Suggests delta(go_goroutines[1h]) for detecting net goroutine increase
51.3 Notes that goroutine count should correlate with load
52. continuous-profiling-tools — Tests continuous profiling tools and tradeoffs 5/5 2/5
52.1 Recommends Grafana Pyroscope, Parca, or similar platform
52.2 Mentions overhead estimates (1-5% range)
52.3 Describes push vs pull collection modes
52.4 Mentions historical flamegraph comparison as a key feature
52.5 Suggests feeding profiles into PGO for build optimization
53. gogc-high-vs-low-tradeoff — Tests GOGC tuning tradeoffs (latency vs throughput) 4/4 4/4
53.1 Recommends lower GOGC (e.g., 50) for Service A (latency-sensitive)
53.2 Recommends higher GOGC (e.g., 200) for Service B (throughput-oriented)
53.3 Explains the tradeoff: lower GOGC = more frequent but shorter pauses
53.4 Explains the tradeoff: higher GOGC = less frequent GC but more memory
54. godebug-gctrace — Tests GODEBUG=gctrace=1 output interpretation 5/5 3/5
54.1 Correctly identifies gc 142 as the 142nd GC cycle
54.2 Identifies 12% as total CPU time spent in GC (which is high)
54.3 Interprets 180->340->200 MB as heap before, peak during, and after collection misparses the three values
54.4 Identifies 400 MB goal as the target heap size based on GOGC/GOMEMLIMIT
54.5 Notes that 12% GC CPU is concerning
55. unsafe-without-benchmark-proof — Tests warning against premature unsafe usage 4/4 3/4
55.1 Recommends against using unsafe here
55.2 Notes that 100 req/s is not a hot path
55.3 States that unsafe requires benchmark proof showing >10% improvement no specific threshold
55.4 Mentions safety risks of unsafe
56. precomputed-lookup-table — Tests precomputed lookup tables for pure functions 3/3 2/3
56.1 Suggests a precomputed lookup table (var hexDigit = [16]byte{...})
56.2 Shows the table lookup: hexDigit[b>>4], hexDigit[b&0x0f]
56.3 Explains that the lookup table fits in L1 cache and is faster than branching says "faster" without L1 cache explanation
57. json-performance-alternatives — Tests JSON performance alternatives 5/5 4/5
57.1 Mentions custom MarshalJSON/UnmarshalJSON methods
57.2 Mentions code-generation libraries (easyjson, ffjson)
57.3 Mentions drop-in replacement libraries (goccy/go-json, json-iterator, sonic)
57.4 Explains that encoding/json uses reflection
57.5 Quantifies expected improvement (2-5x or similar)
58. channel-batch-processing — Tests batch processing from channels with timeout flush 5/5 4/5
58.1 Uses select with both channel receive and ticker/timer for timeout
58.2 Flushes on batch size threshold
58.3 Flushes on timeout (ticker)
58.4 Handles channel close (flushes remaining items)
58.5 Reuses the batch slice (batch[:0]) to reduce allocations re-allocates
59. allocation-reduction-vs-gc-tuning — Tests that reducing allocations beats GOGC tuning 4/4 1/4
59.1 Recommends reducing allocations as the primary approach over GOGC tuning suggests GOGC tuning first
59.2 Explains that GOGC tuning manages the symptom while allocation reduction addresses root cause
59.3 Suggests specific allocation reduction strategies
59.4 Acknowledges GOGC tuning as a secondary measure after allocation reduction
60. gctrace-key-fields — Tests what to monitor in GC traces 4/4 2/4
60.1 Mentions high GC frequency as a signal of too many allocations
60.2 Mentions high pause times as a signal of large heap or many pointers
60.3 Mentions high GC CPU% (>5%) as concerning
60.4 Provides the GODEBUG=gctrace=1 command
61. non-go-memory-leak-detection — Tests detecting non-Go memory leaks via Prometheus 4/4 2/4
61.1 Suggests process_resident_memory_bytes - go_memstats_sys_bytes to isolate non-Go memory
61.2 Identifies this as a likely C/cgo memory leak
61.3 Explains that growing gap between RSS and Go sys bytes indicates non-Go memory growth
61.4 Suggests C-level memory profiling tools (valgrind, AddressSanitizer)
62. cpu-saturation-prometheus — Tests detecting CPU saturation via Prometheus 3/3 1/3
62.1 Provides rate(process_cpu_seconds_total[5m]) for CPU cores consumed
62.2 Divides by GOMAXPROCS to get utilization ratio
62.3 States that >0.8 sustained indicates CPU saturation
63. document-optimizations — Tests recommending documentation of optimizations 3/3 2/3
63.1 Strongly recommends adding comments explaining WHY the optimization was made
63.2 Suggests including benchmark numbers in the comments
63.3 Explains that future readers may revert optimizations they don't understand
64. lru-cache-freelru — Tests high-performance LRU cache alternatives 3/3 0/3
64.1 Notes that container/list has poor cache locality (separate heap allocation per node)
64.2 Recommends elastic/go-freelru or hashicorp/golang-lru as alternatives
64.3 Mentions the performance advantage of contiguous memory layouts for LRU
65. simd-when-not-worth — Tests when SIMD is NOT worth pursuing 4/4 4/4
65.1 Recommends against SIMD for this case
65.2 Explains that the bottleneck is allocations/GC, not CPU-bound computation
65.3 States that SIMD only helps CPU-bound numeric inner loops
65.4 Suggests reducing allocations as the correct approach
66. set-map-struct-zero-size — Tests struct{} vs bool for set maps 2/2 2/2
66.1 Recommends struct{} for set maps
66.2 Explains that struct{} is 0 bytes vs bool at 1 byte per entry
67. regression-detection-prometheus — Tests PromQL for deployment regression detection 3/3 2/3
67.1 Suggests comparing rate(go_memstats_alloc_bytes_total[5m]) before/after deploy
67.2 Suggests monitoring p99 latency histogram_quantile for increase after deploy
67.3 Mentions comparing metrics between old and new deployment versions
68. statsviz-development-profiling — Tests real-time development visualization tools 3/3 0/3
68.1 Recommends statsviz (github.com/arl/statsviz) for real-time browser visualization suggests pprof web UI
68.2 Mentions the /debug/statsviz endpoint or statsviz.Register pattern
68.3 Notes that it shows heap, GC pauses, goroutines, and scheduler in real-time

golang-troubleshooting — v1.0.0

With Skill Without Skill Delta
Overall 186/186 (100%) 127/186 (68%) +32pp
Full breakdown (186 assertions across 37 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. reproduce-before-fix-failing-test-first — Model must write a failing test before proposing any fix 5/5 2/5
1.1 Writes a failing test (or proposes writing one) BEFORE suggesting any code fix jumps to adding error handling
1.2 Test covers multiple input variations to identify which inputs trigger the 500 single fix without test
1.3 Does NOT propose a fix without first understanding/reproducing the root cause proposes nil check immediately
1.4 Asks clarifying questions about the error (logs, request bodies, frequency) or proposes to gather evidence
1.5 Mentions running the test to confirm it reproduces the issue before fixing
2. one-hypothesis-at-a-time — Change one thing and measure, not multiple simultaneous changes 5/5 2/5
2.1 Refuses or strongly advises against making all three changes simultaneously applies all three as requested
2.2 Recommends measuring/profiling first to identify which problem is actually causing the symptoms
2.3 Suggests testing one hypothesis at a time with measurement between changes bundles changes
2.4 Mentions pprof, benchmarks, or race detector as diagnostic tools to identify the real bottleneck
2.5 Explains why multiple simultaneous changes are harmful (can't tell what worked, may introduce new bugs)
3. root-cause-not-symptom-fix — Fix at the source where bad data originates, not where the panic occurs 5/5 3/5
3.1 Identifies that the nil check in the handler is a symptom fix, not the root cause
3.2 Traces backward to the constructor/initialization code to find why db is nil
3.3 Suggests validating db != nil in the constructor (e.g., NewServer) and failing fast there
3.4 Does NOT suggest improving the nil check in the handler as the primary fix improves the nil check response
3.5 Explains that fixing at the symptom location masks the real bug
4. interface-nil-gotcha — Typed nil in interface is not nil 5/5 4/5
4.1 Identifies the interface nil gotcha: a typed nil *ValidationError wrapped in an error interface is NOT a nil interface
4.2 Explains that the interface has a non-nil type descriptor even when the pointer value is nil
4.3 Recommends returning nil explicitly (return nil) instead of returning the typed nil variable
4.4 Does NOT suggest adding an if verr == nil check before return as the primary fix suggests if verr == nil check
4.5 Shows or describes the correct fix: check verr != nil before return, and return nil in the else branch
5. variable-shadowing-err — Inner err shadows outer err 5/5 4/5
5.1 Identifies that := inside the if block creates a NEW err variable that shadows the outer one
5.2 Explains that the outer err remains nil because the inner := never assigned to it
5.3 Recommends using = (assignment) instead of := to assign to the outer err variable
5.4 Shows the fix: declare result separately (var result ResultType) and use result, err = someFunc()
5.5 Mentions go vet -shadow or shadow analyzer as a detection tool
6. defer-in-loop-resource-leak — Deferred calls pile up until function returns 5/5 4/5
6.1 Identifies that defer f.Close() inside a for loop keeps all files open until the function returns
6.2 Recommends wrapping the loop body in an anonymous function (closure) so defer runs each iteration
6.3 Does NOT suggest increasing ulimit or file descriptor limits as the primary solution
6.4 Shows the correct pattern with func() { f, err := os.Open(...); defer f.Close(); ... }()
6.5 Alternatively suggests extracting the loop body into a named function only shows closure
7. break-in-select-inside-for-loop — Bare break only exits select, not the enclosing for loop 5/5 4/5
7.1 Identifies that break inside a select only exits the select statement, not the for loop
7.2 Recommends using a labeled break (e.g., break loop) with a label on the for statement
7.3 Shows the correct pattern with a label like 'loop:' on the for statement and 'break loop' inside select
7.4 Also fixes the ctx.Done() case which has the same break issue only fixes the quit case
7.5 Alternatively mentions return as a solution if the function should exit entirely
8. concurrent-map-fatal-not-panic — Concurrent map access is fatal and unrecoverable 5/5 4/5
8.1 Explains that concurrent map read/write is a FATAL error that cannot be caught by recover()
8.2 Distinguishes this from regular panics — the Go runtime kills the process immediately treats it as a regular panic
8.3 Recommends protecting the map with sync.RWMutex or using sync.Map
8.4 Recommends using go test -race to find the race condition
8.5 Does NOT suggest fixing the recover middleware as the solution
9. waitgroup-add-inside-goroutine — Add inside goroutine races with Wait 5/5 3/5
9.1 Identifies that wg.Add(1) is called inside the goroutine instead of before it
9.2 Explains that wg.Wait() may return before all goroutines have called wg.Add(1)
9.3 Recommends moving wg.Add(1) before the go func() call
9.4 Notes this is a race condition that passes most of the time but fails intermittently
9.5 Does NOT focus primarily on the mutex/slice synchronization as the root cause discusses mutex issues equally
10. missing-return-after-http-error — Missing return after http.Error() 5/5 4/5
10.1 Identifies the missing return statement after http.Error(w, 'Forbidden', http.StatusForbidden)
10.2 Explains that http.Error() does NOT stop handler execution — it only writes to the ResponseWriter
10.3 Adds return statements after each http.Error() call
10.4 Does NOT primarily blame the isAuthorized function
10.5 Mentions this is a common Go bug pattern and a security concern
11. json-numbers-float64-interface — Numbers into interface{} become float64 5/5 4/5
11.1 Explains that JSON numbers unmarshaled into interface{} become float64, not int or int64
11.2 Notes that large integers (> 2^53) silently lose precision when stored as float64
11.3 Recommends using a typed struct with int64 field as the preferred solution
11.4 Alternatively mentions json.NewDecoder with UseNumber() and json.Number for when interface{} is required
11.5 Explains the type assertion panics because the actual type is float64, not int64
12. strings-trim-vs-trimprefix — Trim treats argument as character set 5/5 4/5
12.1 Explains that strings.Trim treats its second argument as a SET of characters to strip, not as a substring
12.2 Shows why 'json' becomes 'js' — the characters j, o, n are in the set {a,p,l,i,c,t,o,n,/}
12.3 Recommends strings.TrimPrefix for removing a substring prefix
12.4 Mentions strings.TrimSuffix for removing suffixes
12.5 Confirms this is NOT a Go bug — it's working as documented
13. closed-channel-busy-loop-in-select — Closed channel in select causes busy loop 5/5 3/5
13.1 Identifies that a closed channel always returns immediately (zero value) in a select case
13.2 Explains this causes the select case to fire continuously — a busy loop burning CPU
13.3 Recommends using the comma-ok idiom (job, ok := <-ch) and nil-ing the channel when closed (ch = nil) suggests only break/return
13.4 Explains that a nil channel blocks forever in select, effectively disabling that case
13.5 Does NOT suggest adding a default case with time.Sleep as the fix
14. select-default-spin-loop — Select with default inside for loop is a busy-wait 5/5 4/5
14.1 Identifies that select with default inside a for loop is a busy-wait spin loop
14.2 Explains that default runs immediately when no channel is ready, creating a tight loop
14.3 Recommends removing the default case and using a second channel or context for the stop signal
14.4 Alternatively suggests adding a time.Sleep or ticker in the default to yield CPU if non-blocking is truly required
14.5 Shows a solution using a ctx.Done() or stop channel in a second select case
15. enum-zero-value-iota-ambiguity — Iota starting at 0 makes zero value ambiguous 5/5 3/5
15.1 Identifies that iota starting at 0 makes the zero value (default for uninitialized fields) equal to Admin
15.2 Recommends reserving 0 for an Unknown/Unspecified sentinel value
15.3 Shows the pattern: RoleUnknown Role = iota, then Admin, Editor, Viewer
15.4 Explains this applies to any enum — zero value should be the 'unset' state, not a valid value
15.5 Does NOT primarily suggest fixing it in the constructor or registration logic suggests constructor fix
16. recover-only-same-goroutine — recover() goroutine boundary 5/5 4/5
16.1 Explains that recover() can ONLY catch panics in the same goroutine where it is deferred
16.2 States that a panic in a child goroutine will crash the entire program regardless of parent recovery
16.3 Recommends adding defer/recover inside the child goroutine (processAsync or its wrapper)
16.4 Shows the pattern: go func() { defer func() { if r := recover()... }(); processAsync(...) }()
16.5 Does NOT suggest reconfiguring the parent middleware as the solution also discusses middleware changes
17. os-exit-skips-defers — log.Fatal calls os.Exit which skips deferred functions 5/5 4/5
17.1 Identifies that log.Fatal (or log.Fatalf) calls os.Exit(1) internally
17.2 Explains that os.Exit skips all deferred functions — cleanup never runs
17.3 Recommends restructuring to avoid log.Fatal — use a run() function pattern or return errors
17.4 Shows the pattern: move logic into a run() error function, call os.Exit in main only after run returns suggests cleanup before Fatal instead
17.5 Does NOT suggest explicitly calling os.Remove before log.Fatal as the primary fix
18. time-equal-not-double-equals — time.Time == vs .Equal() 5/5 4/5
18.1 Identifies that time.Now() includes a monotonic clock reading that database serialization strips
18.2 Explains that == compares all fields including the monotonic component, so it can fail for equal instants
18.3 Recommends using .Equal() which ignores the monotonic clock
18.4 Alternatively mentions t.Round(0) to strip the monotonic reading before comparison or storage
18.5 Does NOT primarily blame database precision or timezone differences
19. sql-rows-must-be-closed — Missing rows.Close() leaks connections 5/5 4/5
19.1 Identifies the missing defer rows.Close() after the error check
19.2 Explains that unclosed sql.Rows holds the database connection until garbage collection
19.3 Adds defer rows.Close() immediately after the err check
19.4 Also notes the missing rows.Err() check after the loop
19.5 Does NOT primarily suggest increasing connection pool size
20. copying-sync-types-value-receiver — Value receiver copies sync types 5/5 3/5
20.1 Identifies that value receivers (c Counter) copy the entire struct including the Mutex on every call
20.2 Explains that each call operates on a copy — increments are lost and the mutex is duplicated
20.3 Recommends changing to pointer receivers (c *Counter)
20.4 Notes that go vet can detect copied sync types
20.5 Explains this applies to ALL sync types (Mutex, RWMutex, WaitGroup, Once, etc.) only discusses Mutex
21. pprof-production-security — Never expose pprof unauthenticated 5/5 2/5
21.1 Warns that pprof endpoints MUST be protected — never exposed publicly without authentication
21.2 Recommends basic auth or similar authentication on pprof endpoints suggests separate port only
21.3 Suggests running pprof on a separate port (not the main HTTP port) or localhost only
21.4 Recommends toggling pprof via an environment variable (e.g., PPROF_ENABLED)
21.5 Explains the risk: pprof leaks goroutine stacks, memory contents, and can be used for DoS
22. godebug-gc-tracing-interpretation — GODEBUG gctrace output interpretation 5/5 4/5
22.1 Identifies that 18% GC CPU overhead is significantly high (threshold is >10%) notes 18% but no threshold
22.2 Explains the heap size breakdown: 1024MB before mark, 900MB after mark, 500MB after sweep
22.3 Identifies the large pause times (45ms) as a likely cause of the latency spikes
22.4 Suggests the application is over-allocating and recommends investigating allocation patterns
22.5 Recommends using pprof heap/alloc profiling to find hot allocation sites
23. research-codebase-not-just-diff — Trace callers and check upstream validation before flagging 5/5 3/5
23.1 Recommends checking the callers first before flagging the bug
23.2 Suggests using Grep or similar to find all call sites of getItem
23.3 Notes that upstream code may validate the id (e.g., parsing from uint, bounds checking, positive-only input)
23.4 Advises that if callers validate, the severity is reduced but may still warrant a defensive check
23.5 Mentions adding an inline comment documenting the assumption if upstream guarantees exist
24. flaky-test-diagnosis-methodology — Systematic flaky test debugging 6/6 4/6
24.1 Recommends running with -count=100 to reproduce locally
24.2 Suggests using -shuffle=on to check for test order dependence
24.3 Mentions running with -race to check for data races
24.4 Suggests using t.TempDir() instead of shared temp directories to avoid file system pollution
24.5 Considers shared mutable state between tests as a potential cause
24.6 Does NOT suggest retry logic or skipping the test as a solution mentions retry as option
25. defense-in-depth-after-fix — Multi-layer defense after fixing a bug 5/5 3/5
25.1 Recommends validating at the entry point (API boundary) — reject paths with .. early
25.2 Recommends adding a test that specifically verifies the path traversal is blocked
25.3 Suggests adding logging or metrics to detect future traversal attempts (observability)
25.4 Considers multiple validation layers — not just one check focuses on single check
25.5 Mentions that filepath.Join does not prevent path traversal by itself (common misconception)
26. escalation-protocol-three-failed-attempts — After 3 failed attempts, question architecture 5/5 1/5
26.1 Recognizes the pattern of cascading failures as a red flag — each fix reveals a new problem
26.2 Recommends stepping back to question the overall design/architecture rather than trying another fix suggests specific fixes
26.3 Suggests re-reading the code from scratch with fresh eyes
26.4 Considers whether the current abstraction is fundamentally sound
26.5 Does NOT immediately suggest a 5th specific patch to the existing code suggests dedup + pool fix
27. git-bisect-for-regression — Using git bisect to find breaking commit 5/5 3/5
27.1 Recommends git bisect to binary-search for the breaking commit
27.2 Shows the git bisect start / git bisect bad / git bisect good workflow
27.3 Mentions that bisect can be automated with a test command (git bisect run go test -run TestBroken ./...)
27.4 Notes this narrows 50 commits to ~6 steps (log2(50))
27.5 Does NOT suggest manually reading all 50 commit diffs
28. check-external-dependencies-first — Verify external components before assuming code bug 5/5 4/5
28.1 Suggests checking the external payment service health/status first (curl, health endpoint)
28.2 Recommends checking DNS resolution (dig or nslookup)
28.3 Suggests checking network connectivity (nc, telnet, or similar to the port)
28.4 Considers environment-specific causes: expired credentials, DNS changes, firewall rules, certificate rotation
28.5 Does NOT start by investigating Go code since nothing changed in the codebase
29. observability-tools-before-code-dive — Check observability data before diving into code 5/5 3/5
29.1 Recommends checking monitoring/observability tools BEFORE diving into code
29.2 Asks what monitoring tools are available (Prometheus, Datadog, Sentry, ELK, etc.)
29.3 Suggests checking error rate metrics, latency dashboards, or log aggregation
29.4 Mentions specific things to look for: what changed 2 hours ago (deploy, config change, traffic spike)
29.5 Does NOT immediately start reading source code files
30. integer-conversion-silent-truncation — Go integer conversions silently truncate 5/5 4/5
30.1 Explains that Go integer conversions silently truncate without any error or warning
30.2 Shows bounds checking before conversion: compare against math.MinInt32 and math.MaxInt32
30.3 Returns an error when the value overflows instead of silently truncating
30.4 Notes there is no built-in safe conversion function — you must check bounds manually
30.5 Mentions this is especially dangerous for external/user-provided data
31. init-ordering-fragile — init() ordering across files is fragile 5/5 3/5
31.1 Confirms that init() ordering across files depends on filename alphabetical order and can change when files are added
31.2 Explains this makes init() dependencies fragile and hard to debug
31.3 Recommends replacing init() with explicit initialization in main()
31.4 Shows the pattern: cfg := loadConfig(); db := setupDatabase(cfg); startServer(db)
31.5 States init() should only be used for truly self-contained setup (registering drivers, codecs)
32. goroutine-leak-detection-methodology — Goroutine leaks as cause of slow memory growth 5/5 4/5
32.1 Suggests checking goroutine count (runtime.NumGoroutine or pprof goroutine profile)
32.2 Explains that goroutine leaks cause slow memory growth without appearing in heap profiles
32.3 Recommends using the pprof goroutine endpoint with ?debug=2 for human-readable stack dumps
32.4 Lists common causes: unclosed channels, missing context cancellation, forgotten response body close
32.5 Suggests goleak for detection in tests
33. production-capture-before-restart — Capture profiles BEFORE restarting 5/5 3/5
33.1 Recommends capturing profiles (heap, goroutine, CPU) BEFORE restarting
33.2 Explains that restarting destroys the evidence needed to diagnose the root cause
33.3 Lists specific profiles to capture: heap, goroutine dump (?debug=2), CPU (30s), mutex
33.4 Also suggests capturing system metrics (file descriptors, socket state, process info)
33.5 Only after capturing all evidence should the service be restarted if needed
34. lock-contention-diagnosis — runtime.semacquire indicates lock contention 5/5 4/5
34.1 Identifies runtime.semacquire as a signal of lock contention, not CPU computation
34.2 Recommends enabling mutex profiling with runtime.SetMutexProfileFraction(1)
34.3 Recommends enabling block profiling with runtime.SetBlockProfileRate(1)
34.4 Suggests using pprof mutex and block profiles to find the contended locks
34.5 Lists solutions: reduce critical section, sharding, RWMutex, atomic operations
35. race-detector-not-reasoning — Never reason about concurrency, use the race detector 5/5 4/5
35.1 Does NOT conclude safety based on code reasoning alone reasons about field layout
35.2 Recommends running go test -race to verify — never trust visual inspection for concurrency
35.3 Identifies that ++ is not atomic — RequestCount++ and ErrorCount++ are read-modify-write operations
35.4 Recommends using atomic.Int64 or sync.Mutex to protect the fields
35.5 Notes that even different fields on the same struct can race if accessed from different goroutines without synchronization
36. filepath-join-path-traversal — filepath.Join does not prevent path traversal 5/5 4/5
36.1 Identifies that filepath.Join does NOT prevent path traversal
36.2 Shows that input like '../../etc/passwd' resolves to '/etc/passwd' after Join
36.3 Recommends verifying the result has the base directory as a prefix after Join
36.4 Shows a safe pattern: filepath.Clean + strings.HasPrefix check
36.5 Notes the path separator must be appended to the base to prevent partial prefix matches
37. time-after-in-loop-memory-leak — time.After in loop creates timer leak 5/5 3/5
37.1 Identifies that time.After creates a new timer on every loop iteration that isn't garbage collected until it fires
37.2 Explains this causes a memory leak proportional to the message rate
37.3 Recommends using time.NewTimer with Reset() or time.NewTicker instead
37.4 Shows the correct pattern with a reusable timer and defer timer.Stop()
37.5 Does NOT suggest heap profiling as the first diagnostic step for this known pattern suggests heap profile first

golang-design-patterns — v1.0.0

With Skill Without Skill Delta
Overall 87/87 (100%) 55/87 (63%) +37pp
Full breakdown (87 assertions across 18 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. functional-options-over-builder — constructor API for HTTP server with growing config 5/5 4/5
1.1 Uses functional options pattern (Option type as func that modifies the struct)
1.2 Constructor accepts variadic ...Option parameter
1.3 Each option is a With* function returning an Option
1.4 Sets sensible defaults inside the constructor before applying options
1.5 Mentions that functional options should return an error if validation can fail no error-returning variant mentioned
2. avoid-init-function — database init() review 5/5 3/5
2.1 Explicitly recommends against using init() for database initialization
2.2 Mentions that init() makes testing harder or unpredictable
2.3 Mentions that init() cannot return errors (must panic or log.Fatal) not mentioned
2.4 Suggests explicit constructor or initialization function
2.5 Mentions that init() runs before main/tests creating hidden dependencies not mentioned
3. enum-start-at-one — order status enum with iota 4/4 2/4
3.1 Zero value (iota = 0) is either skipped, named Unknown, Invalid, or Unspecified Pending at iota 0
3.2 First meaningful enum value starts at 1 or higher starts at 0
3.3 Explains WHY: Go's zero value would silently pass as the first enum member
3.4 Uses a custom type (not raw int or string)
4. panic-vs-error-judgment — config parser: invalid format, missing field, nil arg 5/5 3/5
4.1 Invalid config format: return error (caller can handle it)
4.2 Missing required field: return error (expected validation failure)
4.3 Nil passed to non-nil function: panic is acceptable (violated invariant)
4.4 Articulates the principle: panic is for bugs/invariant violations, errors are for expected failures vague distinction
4.5 Mentions Must* constructor pattern as a valid panic use case not mentioned
5. runtime-addcleanup-over-setfinalizer — automatic cleanup for cgo resource (Go 1.24) 5/5 1/5
5.1 Recommends runtime.AddCleanup as the preferred approach uses SetFinalizer
5.2 Mentions that AddCleanup supports multiple cleanups on the same object
5.3 Mentions that AddCleanup avoids object resurrection risk
5.4 Mentions that AddCleanup works even with cyclic references
5.5 Either warns against SetFinalizer or explains why AddCleanup is better
6. resource-pool-bounded-channel — connection pool design 5/5 2/5
6.1 Uses a buffered channel (chan *Conn with fixed capacity) as the pool mechanism uses slice+mutex
6.2 Pool has a maximum size / bounded capacity
6.3 Get operation uses select with context for timeout/cancellation no context support
6.4 Put operation handles pool-full case (discards excess connections) no overflow handling
6.5 Does NOT use sync.Pool as the primary pooling mechanism
7. graceful-shutdown-signal-notifycontext — HTTP server SIGINT/SIGTERM handling 5/5 3/5
7.1 Uses signal.NotifyContext (not raw signal.Notify with a channel) uses signal.Notify channel
7.2 Listens for both SIGINT and SIGTERM
7.3 Starts the HTTP server in a goroutine
7.4 Creates a separate timeout context for the shutdown phase no separate shutdown timeout
7.5 Closes other resources (DB, queues) after server shutdown
8. iterator-streaming-large-data — export 2M rows to JSON HTTP response 5/5 2/5
8.1 Does NOT load all 2M rows into a slice in memory loads all into []User
8.2 Streams the JSON response (writes records one at a time to the ResponseWriter) json.Marshal entire slice
8.3 Uses rows.Next() loop or iter.Seq2 iterator pattern
8.4 Defers rows.Close() immediately after query
8.5 Mentions OOM risk or memory concern as motivation for streaming no OOM discussion
9. regexp-compile-once — email validation regex called thousands of times/sec 4/4 3/4
9.1 Compiles the regexp at package level (var emailRegex = regexp.MustCompile(...))
9.2 Does NOT compile the regexp inside the validation function
9.3 Uses regexp.MustCompile (not regexp.Compile) for package-level initialization
9.4 Explains WHY: compilation is O(n) and allocates, so doing it per-call is wasteful mentions performance but not O(n)
10. architecture-right-sizing — 200-line CSV CLI tool architecture 5/5 2/5
10.1 Recommends a flat or minimal structure (no multi-layer architecture) suggests cmd/internal/pkg layout
10.2 Does NOT suggest clean architecture, hexagonal, DDD, or ports and adapters suggests layered packages
10.3 Does NOT suggest dependency injection frameworks
10.4 Structure has at most cmd/ and possibly internal/, not handler/service/repository layers includes service layer
10.5 Mentions that architecture complexity should match project scope
11. hexagonal-vs-clean-architecture — 8K-line order service with HTTP/gRPC/MQ 5/5 3/5
11.1 Correctly explains hexagonal: ports (interfaces) + adapters with primary/secondary distinction
11.2 Correctly explains clean architecture: dependency rule (inward) with entities/use-cases/adapters
11.3 Recommends hexagonal for this case (multiple entry points: HTTP, gRPC, message consumer) recommends clean architecture
11.4 Mentions that both keep domain logic pure and free from infrastructure dependencies
11.5 Provides a directory structure with adapter/primary/ and adapter/secondary/ or equivalent generic layered layout
12. ddd-aggregate-root-mutations — Order aggregate with AddItem, Draft status guard 5/5 3/5
12.1 AddItem is a method on the Order aggregate root (not on OrderItem or a service)
12.2 Order fields (items, status) are unexported to prevent external mutation exported Items/Status fields
12.3 AddItem validates the status constraint (only Draft orders are editable)
12.4 Repository interface is defined in the domain package, not in the infrastructure package repo interface in infrastructure
12.5 Domain types have no infrastructure imports (no sql, no http, no framework dependencies)
13. ddd-bounded-context-communication — Order and Billing context communication 5/5 3/5
13.1 Uses domain events (e.g. OrderPlaced event) for cross-context communication
13.2 Billing context does NOT directly import order's internal domain types imports order types directly
13.3 Shows or describes an anti-corruption layer that translates order events to billing types no ACL layer
13.4 Each bounded context has its own domain, application, and adapter layers
13.5 Mentions that direct type imports between contexts create tight coupling
14. make-illegal-states-unrepresentable — Email type to prevent invalid emails 5/5 3/5
14.1 Creates a dedicated Email type (struct with unexported address field) validates at function entry
14.2 Email can only be created via a constructor (NewEmail) that validates the address no constructor
14.3 The send function accepts the Email type instead of a raw string
14.4 Explains the principle: make illegal states unrepresentable through the type system
14.5 The unexported field prevents creating an Email without validation
15. fail-fast-validate-at-boundaries — three-layer validation strategy 5/5 2/5
15.1 Recommends validating at the HTTP handler layer (the system boundary)
15.2 Recommends that the service and repository layers trust the data is already valid suggests validating in service too
15.3 Explains WHY: re-validating at every layer clutters code and violates DRY advocates "defense in depth"
15.4 Does NOT suggest adding the same validation checks in all three layers validates at all layers
15.5 May distinguish between input validation (at boundary) and business rule validation (in domain)
16. explicit-over-implicit-defaults — struct tag default:"8080" review 4/4 2/4
16.1 Recommends against using struct tags + reflection for defaults endorses struct tags as convenient
16.2 Suggests explicit defaults in a constructor function (e.g. NewConfig())
16.3 Explains WHY: Go favors explicitness, struct tags hide behavior presents it as valid Go pattern
16.4 Shows a constructor that returns a Config with default values set explicitly
17. retry-context-check — production-ready retry with exponential backoff 5/5 3/5
17.1 Function accepts a context.Context parameter
17.2 Checks ctx.Err() or ctx.Done() between retry attempts
17.3 Uses select with ctx.Done() for the backoff delay (not time.Sleep) uses time.Sleep
17.4 Implements exponential backoff
17.5 Returns the context error if the context is cancelled during retry returns last operation error
18. ddd-value-object-money — monetary amounts with addition and comparison 5/5 2/5
18.1 Uses int64 (cents) not float64 for the amount uses float64
18.2 Includes a currency field
18.3 Fields are unexported (immutable value object, only created via constructor) exported Amount/Currency
18.4 Add method validates currency match before addition
18.5 Constructor validates input (e.g. currency is required) no validation in constructor

golang-cli — v1.0.0

With Skill Without Skill Delta
Overall 55/58 (95%) 30/58 (52%) +43pp
Full breakdown (58 assertions across 12 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. minimal-main-and-execute — main.go minimal, os.Exit only in main 5/5 2/5
1.1 main.go only calls Execute() — no config, flag setup, or business logic
1.2 Root command sets SilenceUsage: true not set
1.3 Root command sets SilenceErrors: true not set
1.4 Subcommands do NOT call os.Exit() inside RunE os.Exit(1) in RunE
1.5 Push subcommand registered via rootCmd.AddCommand() in init()
2. viper-config-layering — flags > env > config file > defaults 5/5 2/5
2.1 Calls viper.BindPFlag to bind the port flag to Viper flag and Viper disconnected
2.2 Sets env prefix with viper.SetEnvPrefix no prefix, bare PORT
2.3 Calls viper.AutomaticEnv()
2.4 Handles viper.ConfigFileNotFoundError gracefully log.Fatal on missing config
2.5 Precedence order correct: flags > env > config > defaults
3. persistent-pre-run-config-init — config init in PersistentPreRunE 4/5 2/5
3.1 Config init in PersistentPreRunE on root command duplicated in each RunE
3.2 Config init NOT duplicated in each subcommand three copies of init logic
3.3 --config is a persistent flag on root command
3.4 Uses SetEnvKeyReplacer for hyphens-to-underscores
3.5 Logging configured to write to stderr
4. stdout-vs-stderr-separation — output to stdout, diagnostics to stderr 5/5 2/5
4.1 Program output uses cmd.OutOrStdout(), not os.Stdout fmt.Println to os.Stdout
4.2 Logs/progress go to stderr log.Println to stdout
4.3 Errors go to stderr via cmd.ErrOrStderr() errors mixed with stdout
4.4 Uses cmd.OutOrStdout() instead of os.Stdout directly
4.5 Returns error from RunE, not os.Exit() or log.Fatal()
5. version-ldflags-injection — version via ldflags, not hardcoded 5/5 4/5
5.1 Version/commit/date are var (not const) with placeholder defaults
5.2 Shows -ldflags '-X ...' build command
5.3 Version command uses cmd.OutOrStdout() fmt.Println
5.4 Version NOT hardcoded as const
5.5 Includes runtime/debug.ReadBuildInfo() fallback
6. exit-code-conventions — 0 success, 1 general, 2 usage 4/5 2/5
6.1 Exit codes 0/1/2 follow Unix conventions all errors exit 1
6.2 os.Exit() only in main() os.Exit inside handlers
6.3 Typed error or ExitError with Code field to propagate exit codes
6.4 Errors returned from commands, not swallowed by os.Exit()
6.5 Different error categories map to different exit codes
7. signal-handling-with-context — signal.NotifyContext for cancellation 5/5 4/5
7.1 Uses signal.NotifyContext, NOT raw channel with signal.Notify raw channel + select
7.2 Handles both os.Interrupt and syscall.SIGTERM
7.3 Creates shutdown timeout context (10-30s)
7.4 Calls srv.Shutdown(ctx), not srv.Close()
7.5 Distinguishes http.ErrServerClosed from unexpected errors
8. flag-binding-and-constraints — persistent vs local, mutual exclusion, Viper binding 5/5 2/5
8.1 --verbose is a persistent flag on root, not local to deploy local flag on deploy
8.2 Uses MarkFlagRequired for --env and --tag
8.3 Uses MarkFlagsMutuallyExclusive for --dry-run and --force manual check in RunE
8.4 Uses RegisterFlagCompletionFunc for --env values no completion
8.5 Binds flags to Viper with viper.BindPFlag
9. argument-validation — Cobra built-in validators, not manual len(args) 4/4 2/4
9.1 Uses cobra.NoArgs for status command manual len(args) == 0
9.2 Uses cobra.ExactArgs(1) for deploy command
9.3 Uses cobra.RangeArgs(2, 3) for scale command manual range check in RunE
9.4 Validators set on Args field, not inside RunE
10. cli-testing-pattern — execute programmatically with captured output 5/5 3/5
10.1 Creates executeCommand helper with cmd.SetOut(buf) and cmd.SetErr(buf) no helper, tests run binary
10.2 Table-driven tests with multiple cases
10.3 Uses cmd.SetArgs() — not os/exec.Command exec.Command("./greet")
10.4 Captures output via bytes.Buffer — not redirecting os.Stdout
10.5 Tests check both output and error return value
11. machine-readable-output-format — --output flag with json/table/plain 4/4 2/4
11.1 Supports --output flag with json and table formats --json boolean toggle only
11.2 JSON output uses encoding/json to cmd.OutOrStdout()
11.3 Table output uses text/tabwriter for aligned columns fmt.Printf with manual spacing
11.4 Default format is human-readable table
12. shell-completion-setup — Cobra built-in generators + custom completions 4/5 3/5
12.1 Completion subcommand supports bash, zsh, fish, powershell
12.2 Uses Cobra's built-in GenBashCompletionV2, GenZshCompletion, etc.
12.3 Uses RegisterFlagCompletionFunc for --env flag no custom completions
12.4 Uses ValidArgs or cobra.ExactValidArgs for completion command
12.5 Returns cobra.ShellCompDirectiveNoFileComp for non-file flags

golang-concurrency — v1.0.0

With Skill Without Skill Delta
Overall 62/62 (100%) 38/62 (61%) +39pp
Full breakdown (62 assertions across 13 evals)

Model: Claude Opus 4.6 | Runs: 13 evals x 2 configs = 26 subagents | Grading: Human-as-a-judge

# Assertion With Without
1. time-after-in-select-loop — avoid time.After in loop, use time.NewTimer+Reset 5/5 2/5
1.1 Does NOT use time.After inside the loop body uses time.After(5*time.Second) in select
1.2 Creates time.NewTimer (or time.NewTicker) outside the loop no timer created outside loop
1.3 Calls timer.Reset() after handling a message or timeout no Reset call
1.4 Calls timer.Stop() (or defers it) to clean up the timer
1.5 Drains the timer channel before Reset when appropriate (if !timer.Stop() { <-timer.C })
2. channel-closing-ownership — only the sender closes a channel 5/5 3/5
2.1 Does NOT close the channel from the consumer/receiver side closes from consumer
2.2 Uses a separate signaling mechanism (done channel, context cancellation) for the consumer to tell the producer to stop
2.3 The producer is the one that closes the data channel consumer closes data channel
2.4 Explains the panic risk of closing a channel from the receiver side
2.5 The producer selects on the stop signal alongside its send operation
3. waitgroup-add-placement — wg.Add before go statement 4/4 4/4
3.1 Calls wg.Add(1) BEFORE the go statement, not inside the goroutine
3.2 Calls defer wg.Done() inside the goroutine
3.3 Calls wg.Wait() after the loop to wait for all goroutines
3.4 Does not place wg.Add inside the goroutine function body
4. channel-direction-in-signatures — use chan<- and <-chan in function signatures 5/5 4/5
4.1 The generator function returns <-chan int (receive-only for callers)
4.2 The doubler function accepts <-chan int as input parameter uses bidirectional chan int
4.3 The doubler function returns <-chan int (receive-only for callers)
4.4 Internally, channels are created as bidirectional but exposed as directional through return types
4.5 The producer (generator) closes its output channel with defer close(out)
5. unbuffered-channel-default — default to unbuffered, justify buffers 5/5 2/5
5.1 Recommends starting with unbuffered (or very small buffer like 0 or 1) as the default suggests buffer of 100
5.2 Explains that large buffers mask backpressure problems no backpressure discussion
5.3 States that buffer size should be based on measured need, not arbitrary choice picks arbitrary size
5.4 Does NOT suggest a large arbitrary buffer (e.g., 100, 1000) without explaining the tradeoffs
5.5 Mentions that buffered channels hide the problem of slow consumers
6. send-copies-not-pointers — send values through channels, not pointers 4/4 1/4
6.1 Sends Task values (not *Task pointers) through the channel, OR explicitly documents why pointers are safe sends *Task pointers
6.2 The channel type is chan Task (value type) rather than chan *Task uses chan *Task
6.3 Does not mutate the Task struct after sending it on the channel (or sends a copy) no copy/immutability discussion
6.4 If pointers are used, explicitly acknowledges the shared-memory risk and explains the mitigation
7. errgroup-vs-waitgroup-decision — errgroup.SetLimit for bounded concurrent work with errors 5/5 3/5
7.1 Uses errgroup (golang.org/x/sync/errgroup) instead of sync.WaitGroup for error propagation
7.2 Uses errgroup.WithContext to cancel siblings on first error
7.3 Uses g.SetLimit(10) for bounded concurrency instead of a hand-rolled semaphore uses semaphore channel
7.4 Does NOT build a manual worker pool with channels and WaitGroup when errgroup suffices builds manual pool with channels
7.5 Each goroutine checks ctx.Done() or uses the context from errgroup.WithContext
8. ctx-done-in-select — always include ctx.Done() in select to prevent goroutine leaks 5/5 3/5
8.1 The function accepts a context.Context parameter
8.2 Every select statement includes a case <-ctx.Done(): return branch missing ctx.Done in output select
8.3 Both the read from input channel AND the write to output channel are wrapped in select with ctx.Done() only read has select
8.4 The goroutine exits cleanly when context is cancelled
8.5 The output channel is closed when the goroutine exits (defer close)
9. sync-map-vs-rwmutex-decision — RWMutex+map for write-heavy overlapping keys 5/5 2/5
9.1 Recommends sync.RWMutex + plain map over sync.Map for this write-heavy, overlapping-key pattern recommends sync.Map
9.2 Explains that sync.Map is optimized for write-once/read-many or disjoint key sets no access-pattern discussion
9.3 Explains that for frequent writes with overlapping keys, RWMutex+map is faster no performance comparison
9.4 Does NOT unconditionally recommend sync.Map for any concurrent map scenario
9.5 Mentions that concurrent map read/write without synchronization causes a hard crash
10. sync-pool-reset-before-put — Reset() before Put(), not after Get() 5/5 3/5
10.1 Calls buf.Reset() BEFORE bufPool.Put(buf), not after Get() resets after Get()
10.2 Uses defer to ensure the buffer is returned to the pool even on error
10.3 Does not assume the object from Get() is clean/zeroed assumes clean after Get
10.4 The pool's New function creates a new buffer
10.5 Does not store persistent state in pooled objects
11. goroutine-panic-recovery — recover at goroutine boundaries 4/4 3/4
11.1 Adds defer func() { recover() }() or equivalent panic recovery inside the goroutine
11.2 Logs or handles the recovered panic (not just silently swallowed) swallows panic silently
11.3 The goroutine has a shutdown mechanism (context, done channel, or similar)
11.4 Mentions that a panic in a goroutine crashes the entire process
12. singleflight-cache-stampede — use singleflight to deduplicate concurrent lookups 5/5 4/5
12.1 Recommends golang.org/x/sync/singleflight as the primary solution
12.2 Shows usage of group.Do(key, func) where key identifies the deduplicated resource
12.3 Explains that only one goroutine executes the function; others wait and share the result
12.4 May combine singleflight with a cache layer for TTL-based caching
12.5 Does NOT suggest only a plain mutex or only a TTL cache as the solution to thundering herd also suggests mutex as primary approach
13. iterator-vs-goroutine-pipeline — iterators for sequential CPU-bound transforms 5/5 4/5
13.1 Recommends against goroutine+channel pipeline for this purely sequential, in-memory transform
13.2 Suggests Go 1.23+ iterators (iter.Seq) or simple slice operations instead suggests only plain loop
13.3 Explains that goroutine+channel pipelines add overhead without benefit for sequential CPU-bound work
13.4 Mentions that goroutine pipelines are appropriate when stages involve I/O or need true parallelism
13.5 Does NOT build a multi-goroutine pipeline for this use case

golang-context — v1.0.0

With Skill Without Skill Delta
Overall 48/50 (96%) 31/50 (62%) +34pp
Full breakdown (50 assertions across 10 evals)

Model: Claude Opus 4.6 | Runs: 10 evals x 2 configs = 20 subagents | Grading: Human-as-a-judge

# Assertion With Without
1. context-background-in-handler — use r.Context(), not context.Background() in handlers 5/5 4/5
1.1 Uses r.Context() to obtain the request context, NOT context.Background() inside the handler
1.2 Passes the same context (or a derived child) to the database query (*Context variant)
1.3 Passes the same context to the external HTTP call via http.NewRequestWithContext
1.4 Does NOT use http.NewRequest (without context) for the external service call
1.5 Checks ctx.Err() or handles context cancellation when the client disconnects no ctx.Err() check on disconnect
2. cancel-leak-timeout — defer cancel() for every WithTimeout, per-attempt timeouts 5/5 4/5
2.1 Creates a new context.WithTimeout for each retry attempt (not one timeout for all retries)
2.2 Calls defer cancel() (or cancel() before next iteration) for every WithTimeout call
2.3 Does NOT discard the cancel function with _ (e.g., ctx, _ = context.WithTimeout(...)) discards cancel with _
2.4 Uses http.NewRequestWithContext to attach the per-attempt timeout context
2.5 Accepts a parent context parameter and derives timeouts from it
3. context-value-key-type — unexported key types for context values 5/5 3/5
3.1 Uses an unexported type for the context key (e.g., type contextKey string or type tenantKey struct{}) uses plain string key
3.2 Does NOT use a plain string as the context key context.WithValue(ctx, "tenant_id", ...)
3.3 Provides a typed getter function (e.g., TenantIDFromContext) that returns the value with proper type assertion
3.4 Provides a setter function or the middleware injects the value using the unexported key
3.5 The getter handles the case where the value is missing from the context (returns zero value + bool or error)
4. context-in-struct-trap — do not store context.Context in a struct field 5/5 3/5
4.1 Does NOT store context.Context as a field in the Worker struct stores ctx context.Context in struct
4.2 Passes context as a parameter to Start() or Run() method uses stored struct field context
4.3 Uses context cancellation or a done channel for graceful shutdown signaling
4.4 Listens to ctx.Done() in a select statement to detect shutdown
4.5 ctx is the first parameter where it appears, named ctx context.Context
5. without-cancel-background-work — use context.WithoutCancel for background work (Go 1.21+) 5/5 2/5
5.1 Uses context.WithoutCancel to create a context for the audit goroutine uses context.Background()
5.2 Does NOT pass r.Context() directly to the background audit goroutine
5.3 Does NOT use context.Background() for the audit goroutine (that would lose trace_id) uses context.Background()
5.4 The audit goroutine preserves request-scoped values (trace_id) from the original context context.Background() loses all values
5.5 Launches the audit as a separate goroutine (go keyword) so the handler can return immediately
6. nested-timeout-shorter-wins — nested timeouts take the shorter deadline 4/5 3/5
6.1 Creates the overall 10-second timeout from the parent context
6.2 Creates the cache timeout as a child of the overall context
6.3 Acknowledges that the database timeout is bounded by whatever time remains on the parent creates 8s child without noting parent constraint creates independent 8s timeout
6.4 Defers cancel() for every WithTimeout call
6.5 Does NOT create independent context.Background() timeouts that bypass the overall deadline creates context.Background() timeout for DB
7. context-todo-vs-background — use context.TODO() as placeholder, not context.Background() 5/5 3/5
7.1 Uses context.TODO() as the temporary placeholder in functions not yet fully migrated uses context.Background() everywhere
7.2 Uses context.Background() only at the true top level (main function or test setup) uses Background() at all levels
7.3 Shows a migration path where context.TODO() is gradually replaced as callers are updated
7.4 Context is always the first parameter, named ctx context.Context
7.5 Does NOT pass nil as a context value at any point in the migration
8. db-context-variants — use *Context database method variants 5/5 5/5
8.1 Uses db.QueryRowContext (not db.QueryRow) for GetByID
8.2 Uses db.ExecContext (not db.Exec) for Create, Update, and Delete
8.3 Passes the ctx parameter to every database call
8.4 Does NOT ignore the ctx parameter by calling non-context database methods
8.5 Each method accepts ctx context.Context as its first parameter
9. context-values-abuse — do not stuff infrastructure deps into context values 5/5 4/5
9.1 Database connection is passed as a struct field or explicit parameter, NOT via context value
9.2 Logger is passed as a struct field or explicit parameter, NOT via context value
9.3 Order details are passed as an explicit function parameter, NOT via context value
9.4 User ID and/or trace ID are stored in context values (these are request-scoped metadata)
9.5 Distinguishes between infrastructure dependencies (explicit) and request-scoped metadata (context values) no explicit distinction made
10. afterfunc-cleanup — use context.AfterFunc for non-blocking cleanup (Go 1.21+) 4/5 2/5
10.1 Uses context.AfterFunc to register the cleanup callback uses manual goroutine with <-ctx.Done()
10.2 Does NOT block the main flow waiting for context cancellation
10.3 The cleanup function removes the temporary file
10.4 Captures the stop function returned by AfterFunc for potential cancellation of the callback no stop function (uses goroutine)
10.5 Also includes a defer-based cleanup as a safety net (AfterFunc + defer for belt-and-suspenders) only AfterFunc, no defer fallback only goroutine, no defer fallback

golang-continuous-integration — v1.0.0

With Skill Without Skill Delta
Overall 66/66 (100%) 27/66 (41%) +59pp
Full breakdown (66 assertions across 13 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. test-workflow-flags — CI test workflow with required flags and matrix 5/5 3/5
1.1 Workflow includes -race flag in the go test command
1.2 Workflow includes -shuffle=on flag in the go test command omitted shuffle
1.3 Workflow includes -coverprofile flag in the go test command
1.4 Strategy uses fail-fast: false default fail-fast: true
1.5 Go version matrix includes at least 'stable' and one explicit version
2. go-mod-tidy-check — enforce go mod tidy via git diff --exit-code 4/4 2/4
2.1 Suggests running go mod tidy as a CI step
2.2 Includes git diff --exit-code after go mod tidy to detect uncommitted changes no git diff check
2.3 The git diff checks go.mod and/or go.sum specifically, or uses a general git diff --exit-code only runs tidy
2.4 Also includes go mod verify or go mod download step
3. integration-test-caching — integration tests with -count=1 and service containers 5/5 2/5
3.1 Uses -count=1 flag to disable test result caching no -count=1
3.2 Includes -race flag for integration tests
3.3 Uses build tags (e.g., -tags=integration) to separate integration tests no build tags
3.4 Uses GitHub Actions services block for PostgreSQL and/or Redis
3.5 Includes health check options for service containers no health checks
4. security-scanning-pipeline — full security stack: govulncheck, gosec, CodeQL, Bearer 6/6 2/6
4.1 Recommends govulncheck and explains it only reports actually-called code paths mentions govulncheck without call-path
4.2 Recommends gosec for Go security scanning
4.3 Recommends CodeQL and mentions the security-extended or security-and-quality query suite no extended suite
4.4 Recommends Bearer for sensitive data flow issues not mentioned
4.5 Workflow includes security-events: write permission for SARIF upload no permission block
4.6 Suggests creating a CodeQL config file to use an extended query suite
5. dependabot-grouping-strategy — group minor/patch, separate majors 6/6 3/6
5.1 Configures Dependabot for gomod package ecosystem
5.2 Configures Dependabot for github-actions package ecosystem
5.3 Configures Dependabot for docker package ecosystem omitted docker
5.4 Groups minor and patch Go module updates into a single PR no grouping
5.5 Major updates are NOT grouped (individual PRs for breaking changes) all updates grouped
5.6 Sets a weekly schedule
6. dependabot-auto-merge-security — auto-merge with actor guard and branch protection 5/5 0/5
6.1 Workflow has if: github.actor == dependabot[bot] guard no actor guard
6.2 Workflow checks metadata to exclude major updates from auto-merge merges all versions
6.3 Warns about contents: write and pull-requests: write being elevated permissions no security warning
6.4 Mentions branch protection rules as the real safety net not mentioned
6.5 Notes that github.actor checks are not fully spoof-proof not mentioned
7. renovate-vs-dependabot — Renovate advantages for monorepos 5/5 2/5
7.1 Recommends Renovate as an alternative to Dependabot
7.2 Mentions Renovate's gomodTidy feature not mentioned
7.3 Mentions Renovate's native automerge without needing a separate workflow not mentioned
7.4 Mentions Renovate's monorepo/workspace support
7.5 Mentions Renovate's better grouping rules not mentioned
8. goreleaser-library-vs-cli — GoReleaser config for libraries vs CLIs 5/5 2/5
8.1 Uses skip: true in the builds section since libraries don't produce binaries includes full build config
8.2 Keeps the config minimal (mainly changelog generation) full CLI-style config
8.3 Mentions that for libraries, a simple gh release create may be sufficient not mentioned
8.4 Does NOT include cross-compilation (goos/goarch) in the library config
8.5 Includes changelog configuration
9. docker-workflow-security — push: false on PRs, per-job permissions, provenance 6/6 2/6
9.1 Sets push to false on pull requests pushes on all events
9.2 Uses per-job permissions scoping top-level permissions only
9.3 Includes QEMU and Buildx setup for multi-platform builds
9.4 Includes provenance and/or SBOM attestation configuration no attestations
9.5 Includes packages: write permission for GHCR push
9.6 Login step is conditional on non-PR events unconditional login
10. permissions-least-privilege — read-only GITHUB_TOKEN, branch protection, fork PRs 7/7 3/7
10.1 Recommends setting default GITHUB_TOKEN to read-only not mentioned
10.2 Recommends branch protection with required status checks
10.3 Recommends requiring PR approvals (at least 1)
10.4 Recommends dismissing stale approvals when new commits are pushed not mentioned
10.5 Recommends restricting fork PR workflows for outside collaborators not mentioned
10.6 Warns against pull_request_target with untrusted code not mentioned
10.7 Recommends creating a release environment with required reviewers
11. release-workflow-fetch-depth — fetch-depth: 0 for changelog generation 4/4 2/4
11.1 Checkout step uses fetch-depth: 0 for full git history default shallow clone
11.2 Workflow triggers on tag push with a v* pattern
11.3 Uses contents: write permission for creating releases no permissions block
11.4 Passes GITHUB_TOKEN to GoReleaser
12. action-version-pinning — pin to major versions, not branches 3/3 2/3
12.1 Identifies that using @master and @main is wrong and insecure
12.2 Recommends pinning to major versions like @v4, @v6
12.3 Explains the risk: branch references can change unexpectedly or be compromised identifies problem but no risk reasoning
13. coverage-threshold-configuration — codecov.yml with project and patch targets 5/5 2/5
13.1 Configures codecov.yml (not just CLI flags) for coverage thresholds uses CLI flags only
13.2 Sets project target to 80%
13.3 Sets a threshold value (e.g., 2%) to allow small drops no threshold
13.4 Configures patch coverage target for new code in PRs no patch target
13.5 Coverage upload is conditional on a single matrix entry (e.g., only on stable)

golang-dependency-injection — v1.0.0

With Skill Without Skill Delta
Overall 54/55 (98%) 28/55 (51%) +47pp
Full breakdown (55 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. constructor-injection-not-globals — inject via constructors, not globals or init() 5/5 3/5
1.1 Uses constructor injection (NewUserService taking dependencies as parameters)
1.2 Explicitly advises against package-level variables for service dependencies accepts global var approach
1.3 Explains why globals are problematic (untestable, hidden dependencies, or coupling) no explanation of problems
1.4 Does NOT use init() for service initialization
1.5 Returns a concrete struct pointer from the constructor, not an interface
2. interface-defined-at-consumer — interfaces defined where consumed, not implemented 5/5 2/5
2.1 Defines the interface in the consuming package, not the implementation package defines interface next to implementation
2.2 Explains the principle: accept interfaces, return structs not stated
2.3 The implementation package returns a concrete struct pointer
2.4 The consumer depends on its own locally-defined interface imports provider interface
2.5 Does NOT have the implementation package import the consumer's interface
3. container-not-passed-as-dependency — service locator anti-pattern 5/5 2/5
3.1 Advises against passing the injector/container as a dependency accepts passing injector
3.2 Identifies this as the service locator anti-pattern not identified
3.3 Shows that the Injector should only exist at the composition root injector used throughout
3.4 Shows UserService receiving Database and Mailer directly as constructor parameters
3.5 Shows the provider function using do.MustInvoke inside the provider, not inside methods
4. manual-di-for-small-projects — small projects use manual DI, not a library 5/5 2/5
4.1 Recommends manual constructor injection for a project with only 5 services recommends Wire or Fx
4.2 Does NOT recommend a DI library as the primary approach leads with DI library
4.3 Shows wiring in main() with explicit constructor calls in dependency order
4.4 Initializes infrastructure first, then repositories, then services, then transport no layered ordering
4.5 Mentions that a DI library becomes worthwhile at 10-20+ services
5. di-library-selection-judgment — correct library recommendation based on criteria 5/5 3/5
5.1 Recommends samber/do as a strong fit (generics, lifecycle, compile-time safety) recommends uber-go/fx
5.2 Explains why uber-go/fx uses reflection (runtime errors, not compile-time)
5.3 Explains why google/wire lacks built-in lifecycle management
5.4 Mentions that samber/do requires Go 1.18+ for generics samber/do not discussed
5.5 Discusses at least 3 DI library options from the decision table
6. wire-build-constraint-and-codegen — proper google/wire setup with wireinject 5/5 3/5
6.1 Includes //go:build wireinject build constraint in the wire.go file omitted build constraint
6.2 Uses wire.Build with all provider functions listed
6.3 Shows wire.Bind for binding interface to implementation no wire.Bind shown
6.4 Explains that wire generates wire_gen.go with plain constructor calls
6.5 Mentions that wire_gen.go must not be edited manually
7. fx-lifecycle-hooks-pattern — uber-go/fx OnStart/OnStop hooks for DB connection 5/5 4/5
7.1 Uses fx.Lifecycle parameter in the provider function
7.2 Registers OnStart hook for establishing the database connection
7.3 Registers OnStop hook for closing the database connection
7.4 Uses lc.Append(fx.Hook{...}) pattern
7.5 OnStart and OnStop take context.Context as parameter omitted context parameter
8. testing-with-di-mock-injection — inject mocks at the interface boundary 5/5 4/5
8.1 Creates a mock implementation of the UserStore interface
8.2 Injects the mock into UserService via the constructor (NewUserService)
8.3 Tests both the success path (user found) and the error path (not found)
8.4 Does NOT use a real database connection in the test
8.5 The mock is defined in the test file, not as a package-level or global variable mock as package-level var
9. shallow-dependency-graph — deep chains are a design problem 5/5 2/5
9.1 Identifies the deep dependency chain as a design problem accepts as normal layered architecture
9.2 Recommends flattening the dependency graph no flattening suggested
9.3 Suggests that most services should depend on repositories and config directly keeps transitive chain
9.4 Explains negative consequences of deep chains (fragility, hard to test)
9.5 Proposes a concrete restructuring
10. one-container-per-app-not-per-request — container created once, not per request 5/5 2/5
10.1 Identifies creating a new container per request as a mistake accepts per-request as reasonable
10.2 Recommends one container per application created at startup no single-container guidance
10.3 Explains the performance/correctness problem (recreating singletons, no connection reuse) no performance concern
10.4 Suggests using scopes for request-level isolation if needed
10.5 Shows the container being created once in main() and services injected into handlers
11. lazy-vs-eager-initialization — lazy init preference, singleton vs transient 4/5 1/5
11.1 Recommends lazy initialization (services created on first use, not all at startup) recommends eager for everything
11.2 Recommends singletons for stateful services like database connections
11.3 Recommends transients (or factories) for stateless request processing services all singletons
11.4 Explains why lazy loading is beneficial (unused services never created, faster startup) no lazy benefits explained
11.5 Notes which DI libraries support lazy loading (samber/do, fx) vs which don't (wire) no library comparison for lazy

golang-dependency-management — v1.0.0

With Skill Without Skill Delta
Overall 52/52 (100%) 24/52 (46%) +54pp
Full breakdown (52 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. ask-before-adding-dependency — AI agent must ask before go get 5/5 1/5
1.1 Asks the user for confirmation before running go get runs go get immediately
1.2 Presents the package name and import path installs without showing path
1.3 Mentions whether the standard library covers the use case no stdlib mention
1.4 Lists known alternatives suggests one library directly
1.5 Does NOT silently run go get without asking first
2. go-sum-must-be-committed — go.sum is critical for supply-chain security 5/5 3/5
2.1 Strongly advises against gitignoring go.sum
2.2 Explains that go.sum contains cryptographic checksums for dependency verification
2.3 Explains the supply-chain security risk: compromised proxy could substitute malicious code no supply-chain mention
2.4 Mentions go mod verify as the mechanism that uses go.sum not mentioned
2.5 Recommends removing go.sum from .gitignore
3. patch-only-upgrade-preferencego get -u=patch safer than go get -u 5/5 1/5
3.1 Recommends go get -u=patch ./... as the safer default recommends go get -u ./...
3.2 Explains that -u=patch only upgrades patch versions (no API changes per semver) no distinction made
3.3 Explains that -u upgrades minor versions too, which can change behavior no warning about minor
3.4 Mentions running go mod tidy after upgrading
3.5 Does NOT recommend go get -u ./... without warning about risk recommends it as primary
4. mvs-algorithm-understanding — Minimal Version Selection, not latest 5/5 3/5
4.1 Correctly states that Go selects v1.3.0 (not the latest available)
4.2 Explains MVS: Go picks the highest minimum required, not the latest available
4.3 Distinguishes MVS from other package managers (npm, pip, cargo) no comparison to other managers
4.4 Mentions that MVS provides deterministic builds without a lock file not mentioned
4.5 Explains that go.sum is integrity verification, not version locking
5. major-version-suffix-rule — /v2 suffix for v2+ modules 5/5 4/5
5.1 States that the module path must include /v2 suffix
5.2 States that all import paths must be updated to include /v2
5.3 Explains this is Go's import compatibility rule
5.4 Mentions that v0 and v1 do NOT have a suffix not mentioned
5.5 Notes that v1 and v2 can coexist in the same build
6. replace-directive-library-warning — replace ignored when consumed as dependency 4/4 2/4
6.1 States that replace directives only take effect in the main module's go.mod
6.2 States that consumers will NOT use the fork
6.3 Recommends removing replace directives before publishing a library no publish warning
6.4 Suggests alternative solutions (upstream the fix, publish fork as separate module) no alternatives offered
7. tools-go-pattern — pin CLI tool versions in go.mod 5/5 1/5
7.1 Recommends the tools.go pattern (file with //go:build tools constraint) suggests go install @latest in CI
7.2 Uses blank imports (_ imports) to keep tools in go.mod no blank imports
7.3 The build constraint ensures the file is never compiled into production code no build constraint
7.4 Mentions running go mod tidy after creating the tools.go file no tidy mention
7.5 Explains that go install then uses the pinned version from go.mod
8. govulncheck-call-path-analysis — static analysis traces call paths to vulnerable funcs 5/5 3/5
8.1 Recommends govulncheck to check if vulnerability is reachable
8.2 Explains that govulncheck uses static analysis to trace call paths no call-path explanation
8.3 Explains that if code never calls the affected function, govulncheck will NOT flag it not explained
8.4 Shows the govulncheck ./... command
8.5 Distinguishes govulncheck from generic CVE scanners
9. go-work-sum-gitignore — go.work.sum should NOT be committed 5/5 2/5
9.1 States that go.work.sum should NOT be committed suggests committing both
9.2 Recommends adding go.work.sum to .gitignore no gitignore mention
9.3 Explains that go.work is for development only
9.4 Distinguishes this from go.sum which MUST be committed treats both the same
9.5 May mention that go.work itself can optionally be committed
10. exclude-vs-retract-distinction — exclude is consumer-side, retract is author-side 5/5 3/5
10.1 Uses retract for the published library (author-side)
10.2 Uses exclude for the buggy dependency (consumer-side) uses replace instead
10.3 Explains that retract goes in the library's own go.mod
10.4 Explains that exclude redirects to the next higher available version no redirect behavior explained
10.5 Notes that retracted versions are still downloadable but not selected by default
11. test-dependency-upgrade-flag-t flag for including test deps in upgrades 3/3 1/3
11.1 Explains that go get -u ./... excludes test-only dependencies by default not explained
11.2 Recommends go get -u -t ./... to include test dependencies suggests upgrading individually
11.3 Explains the difference between -u and -u -t

golang-structs-interfaces — v1.0.0

With Skill Without Skill Delta
Overall 52/52 (100%) 34/52 (65%) +35pp
Full breakdown (52 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. interface-at-consumer-not-implementor — notification service using email client 5/5 2/5
1.1 Interface is defined in the notification package (the consumer), NOT in the email package defines in email package
1.2 Interface has only the methods the notification package needs (not the full email.Client API) mirrors full Client API
1.3 Email package exports a concrete Client struct, not an interface
1.4 Explains WHY: keeps the consumer in control of the contract, avoids importing for interface no rationale given
1.5 Notification service depends on its own interface, not on email package types
2. return-structs-not-interfaces — NewUserService constructor signature 5/5 3/5
2.1 Constructor returns *UserService (concrete type), NOT an interface
2.2 Constructor accepts UserStore as an interface parameter (accept interfaces)
2.3 Explains WHY: callers get full access to concrete type; consumers can assign to interface no rationale
2.4 Explicitly states that returning interfaces from constructors is bad practice presents both as valid
2.5 The accept-interfaces-return-structs principle is stated or demonstrated
3. premature-interface-trap — single PostgreSQL implementation repository 5/5 1/5
3.1 Recommends starting with a concrete struct (not an interface) when only one implementation creates interface immediately
3.2 Mentions the principle: don't design with interfaces, discover them not mentioned
3.3 Suggests extracting an interface LATER when a second consumer or test mock demands it creates interface "for testability"
3.4 Acknowledges that testability IS a valid reason but it should be a deliberate choice reflexively adds interface
3.5 Does NOT reflexively recommend creating an interface just because it is a repository
4. zero-value-useful-design — Registry panics without NewRegistry 5/5 2/5
4.1 Recommends lazy initialization in the Register method (if r.items == nil) says "always call constructor"
4.2 Mentions the Go principle: make the zero value useful not mentioned
4.3 The fix allows using var r Registry without calling a constructor requires constructor
4.4 Does NOT just say "always use the constructor" as the primary fix
4.5 References bytes.Buffer or sync.Mutex as stdlib examples of useful zero values
5. embedding-vs-named-field — Server with http.Handler and DataStore 5/5 3/5
5.1 Embeds http.Handler (to promote ServeHTTP to the Server)
5.2 Uses a named field for DataStore (not embedded, methods should not be exposed) embeds both
5.3 Explains the embed vs named field rule: embed for "is a", named field for "has a" no rule articulated
5.4 Mentions that embedding promotes ALL methods of the inner type, which can be undesirable
5.5 Notes that the receiver of promoted methods is the inner type, not the outer type
6. compile-time-interface-check — MyBuffer implements io.ReadWriter 4/4 3/4
6.1 Uses var _ io.ReadWriter = (*MyBuffer)(nil) pattern
6.2 Places the check near the type definition places in test file
6.3 Explains that this costs nothing at runtime
6.4 Explains that the build fails immediately if MyBuffer stops satisfying the interface
7. type-assertion-comma-ok — check if interface{} value is a string 4/4 4/4
7.1 Uses the comma-ok form: s, ok := val.(string)
7.2 Checks the ok value before using s
7.3 Warns against bare type assertion (s := val.(string)) because it panics
7.4 Handles the !ok case explicitly
8. optional-behavior-type-assertion — flush only if writer supports it 5/5 3/5
8.1 Defines a separate Flusher interface with just the Flush method
8.2 Function parameter is io.Writer (not a combined interface) requires WriteFlusher interface
8.3 Uses type assertion (f, ok := w.(Flusher)) to check for flush capability
8.4 Only calls Flush if the type assertion succeeds
8.5 Mentions this pattern is used in the standard library (e.g. http.Flusher, io.ReaderFrom) no stdlib reference
9. nocopy-sentinel-struct — prevent ConnPool from being passed by value 5/5 1/5
9.1 Recommends embedding a noCopy sentinel struct says "always use pointers"
9.2 noCopy implements Lock() and Unlock() methods (empty bodies) not mentioned
9.3 Explains that go vet will flag copies of structs containing noCopy not mentioned
9.4 Mentions this is the same technique used by sync.WaitGroup, sync.Mutex, or strings.Builder not mentioned
9.5 Shows that the struct should be passed by pointer after adding noCopy
10. generics-over-any-interface — generic Contains function for any comparable type 5/5 4/5
10.1 Uses generics with a type parameter: func Contains[T comparable]
10.2 Does NOT use []any or interface{} parameters
10.3 Uses the comparable constraint for the type parameter
10.4 Explains WHY: generics preserve type safety, while any loses it
10.5 Mentions that any should only be used at true boundaries where type is genuinely unknown no boundary guidance
11. receiver-consistency-rule — mixed pointer/value receivers on same type 4/4 2/4
11.1 Says mixing pointer and value receivers on the same type is wrong or not recommended says mixing is fine per-method
11.2 Recommends making ALL methods use pointer receivers since one needs to mutate
11.3 Explains WHY: consistency rule -- if any method uses a pointer receiver, all should no consistency principle articulated
11.4 Mentions that method sets differ for T and *T which affects interface satisfaction

golang-lint — v1.0.0

With Skill Without Skill Delta
Overall 49/51 (96%) 28/51 (55%) +41pp
Full breakdown (51 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. nolint-directive-specificity — linter name + justification on every //nolint 5/5 3/5
1.1 Every //nolint specifies the linter name — no bare //nolint bare //nolint on 3 lines
1.2 Every //nolint includes a justification comment no justifications
1.3 Type assertion uses //nolint:forcetypeassert with safety explanation
1.4 Long test uses //nolint:funlen with table-driven justification
1.5 Cyclomatic complexity uses //nolint:gocyclo with orchestration justification
2. nolint-fix-vs-suppress-judgment — fix correctness bugs, suppress style issues 6/6 4/6
2.1 Recommends FIXING bodyclose — unclosed response bodies leak connections
2.2 Recommends SUPPRESSING funlen on table-driven test
2.3 Recommends FIXING errcheck on database query in request handler suppressed as "not critical"
2.4 Recommends SUPPRESSING dupl on intentional parallel structure
2.5 Recommends FIXING sqlclosecheck — unclosed sql.Rows leak connections suppressed with defer comment
2.6 Recommends SUPPRESSING goconst in tests
3. golangci-yml-version-2-structure — version: "2", formatters section, linters.enable 4/5 1/5
3.1 Config has version: "2" at the top no version field
3.2 Linters listed under linters.enable (not enable-all) uses enable-all with disable list
3.3 gofumpt under formatters.enable, NOT linters.enable gofumpt in linters.enable
3.4 errcheck has check-type-assertions: true in linters.settings
3.5 Timeout set under run.timeout: 5m
4. linter-categories-correctness-vs-style — prioritize bug-finding over style 5/5 3/5
4.1 Includes govet and staticcheck as highest-value correctness linters
4.2 Includes errcheck for unchecked errors
4.3 Prioritizes correctness/safety over style linters revive and godot ranked above gosec
4.4 Includes at least one security linter (bodyclose, gosec, sqlclosecheck) no security linters in top 10
4.5 Does NOT include redundant complexity checkers (gocyclo + cyclop)
5. legacy-codebase-incremental-adoption — new-from-rev, not mass //nolint 5/5 3/5
5.1 Recommends issues.new-from-rev to only lint new/changed code
5.2 Does NOT suggest adding //nolint to all 2000+ warnings suggests mass //nolint annotations
5.3 Suggests gradual cleanup of old code over time
5.4 Suggests golangci-lint run --fix for auto-fixable issues
5.5 Mentions parallel sub-agents or batching fixes by category sequential manual cleanup
6. interpreting-lint-output-format — use linter name to triage fix vs suppress 5/5 3/5
6.1 Identifies errcheck on DB.Close as real issue to fix
6.2 Identifies bodyclose as critical resource leak to fix treats all warnings equally
6.3 Identifies unused validateToken as dead code to remove
6.4 Evaluates gocyclo based on function nature (orchestration vs complex) blanket "refactor" recommendation
6.5 Identifies revive comment warning as lower-priority style issue
7. disabled-linters-with-rationale — exhaustruct, gochecknoglobals, wrapcheck, mnd, varnamelen 5/5 3/5
7.1 Recommends AGAINST exhaustruct — breaks zero-value idiom enables it for "completeness"
7.2 Recommends AGAINST gochecknoglobals — valid global uses exist
7.3 Recommends AGAINST wrapcheck as default — too noisy enables it unconditionally
7.4 Recommends AGAINST mnd — flags obvious constants like HTTP status codes
7.5 Recommends AGAINST varnamelen — conflicts with Go's short name idiom
8. nolintlint-meta-linter — enforce proper //nolint hygiene automatically 4/4 2/4
8.1 Recommends enabling nolintlint linter
8.2 Configures require-specific: true no nolintlint config
8.3 Configures require-explanation: true no nolintlint config
8.4 Shows correct location: linters.settings.nolintlint
9. multiple-nolint-comma-syntax — comma-separated linters in single directive 3/3 2/3
9.1 Uses comma-separated: //nolint:errcheck,gosec — not two directives two separate //nolint lines
9.2 Includes justification explaining both false positives
9.3 Directive on same line as flagged code or line above
10. common-config-issues — timeout, v1-to-v2 migration, linter-not-found 3/4 2/4
10.1 Recommends increasing run.timeout for deadline exceeded
10.2 Recommends golangci-lint migrate for v1 config errors suggests manual rewrite
10.3 For linter not found: check version — modernize requires v2.6.0+ suggests reinstalling
10.4 Mentions golangci-lint linters to check available linters
11. formatter-vs-linter-distinction — formatters section, fmt subcommand 4/4 2/4
11.1 gofumpt under formatters.enable, NOT linters.enable linters.enable
11.2 Sets gofumpt extra-rules: true under formatters.settings
11.3 Mentions golangci-lint fmt ./... command for formatting only golangci-lint run
11.4 Notes gci and goimports are redundant with gofumpt
With Skill Without Skill Delta
Overall 54/54 (100%) 38/54 (70%) +30pp
Full breakdown (54 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. stdlib-first-json — encoding/json before third-party 5/5 5/5
1.1 Recommends encoding/json as the first option
1.2 Third-party alternatives only for specific performance needs
1.3 Explains stdlib is sufficient for most JSON use cases
1.4 Alternatives presented for measured performance requirements
1.5 Does NOT recommend third-party without first considering stdlib
2. pgx-over-lib-pq — pgx as primary PostgreSQL driver 5/5 3/5
2.1 Recommends pgx as the primary recommendation lib/pq as primary
2.2 Mentions pgx is faster than lib/pq
2.3 Notes pgx supports all PostgreSQL types and advanced features
2.4 May mention lib/pq but positions pgx as preferred
2.5 Does NOT recommend lib/pq as primary without mentioning pgx lib/pq recommended first
3. chi-for-minimal-router — chi when staying close to net/http 5/5 3/5
3.1 Recommends chi as a strong match for stated requirements recommends Gin
3.2 Explains chi is lightweight and composes with net/http
3.3 Notes chi has minimal dependencies
3.4 May mention Gin/Echo but positions chi as better fit for net/http
3.5 Does NOT recommend a full framework as primary when user wants to stay close to net/http Gin as primary recommendation
4. slog-over-external-loggers — consider log/slog before zap/zerolog 5/5 4/5
4.1 Mentions log/slog as standard library option (Go 1.21+)
4.2 Presents slog as a viable option, not afterthought brief mention at end
4.3 If recommending external, explains specific value over slog
4.4 Does NOT skip standard library consideration entirely
4.5 May mention zap/zerolog for specific use cases
5. sqlc-vs-orm-decision — sqlc for compile-time SQL safety 5/5 2/5
5.1 Recommends sqlc as a primary option for compile-time SQL safety recommends GORM only
5.2 Explains sqlc generates type-safe code with no runtime reflection no mention of sqlc
5.3 Mentions GORM uses runtime reflection — doesn't catch SQL errors at compile time GORM presented without caveat
5.4 May also mention ent as a code-generated alternative
5.5 Does NOT recommend only GORM when user asks for compile-time safety
6. rate-limiter-stdlib-first — golang.org/x/time/rate before third-party 5/5 4/5
6.1 Recommends golang.org/x/time/rate as official option
6.2 Explains token bucket algorithm
6.3 May mention Tollbooth/limiter for HTTP middleware integration
6.4 Does NOT skip the official x/time/rate package no mention of x/time/rate
6.5 Explains when third-party might be preferred (per-IP, distributed)
7. franz-go-for-kafka — franz-go over legacy sarama 4/4 2/4
7.1 Recommends franz-go as primary recommendation sarama as primary
7.2 Describes franz-go as modern, high-performance, feature-complete
7.3 Does NOT recommend only sarama without mentioning franz-go only sarama mentioned
7.4 May mention sarama but positions franz-go as preferred modern choice
8. check-maintenance-before-recommending — Logrus deprecated, suggest alternatives 5/5 4/5
8.1 Mentions Logrus is deprecated or in maintenance mode
8.2 Suggests alternatives: slog, zap, or zerolog
8.3 Explains maintained alternatives preferred for new projects
8.4 Does NOT unconditionally recommend Logrus without deprecation note
8.5 Prioritizes maturity and maintenance status mentions deprecation but still says OK
9. avoid-unnecessary-wrappers — warn against thin stdlib wrappers 5/5 4/5
9.1 Warns against wrapping stdlib without meaningful value
9.2 Explains dependencies increase attack surface and maintenance burden no mention of dependency cost
9.3 Recommends evaluating whether net/http itself is sufficient
9.4 Mentions anti-pattern of adding dependencies for marginal convenience
9.5 Suggests considering dependency footprint relative to value
10. testcontainers-for-integration — testcontainers-go for real service testing 5/5 5/5
10.1 Recommends testcontainers-go for programmatic integration testing
10.2 Explains testcontainers-go spins up real Docker containers
10.3 Shows or describes container lifecycle within tests
10.4 Positions it as better than manual Docker Compose for test isolation
10.5 May also mention go-sqlmock for unit-level database testing
11. slices-maps-packages-go121 — stdlib slices/maps before external utility libs 5/5 3/5
11.1 Recommends stdlib slices package (Go 1.21+) for Contains, Sort, Reverse recommends samber/lo for everything
11.2 Does NOT recommend only external libs for basic slice ops samber/lo as sole recommendation
11.3 May mention samber/lo for functional ops (Map, Filter, Reduce)
11.4 Distinguishes stdlib-covered ops from those requiring external libs
11.5 Applies the 'standard library first' principle

golang-project-layout — v1.0.0

With Skill Without Skill Delta
Overall 55/55 (100%) 34/55 (62%) +38pp
Full breakdown (55 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. ask-architecture-before-structuring — Tests whether the model asks the developer about architecture preference before imposing one 5/5 1/5
1.1 Asks the developer which software architecture they prefer (clean, hexagonal, DDD, flat, etc.) immediately generates a structure
1.2 Asks about the project scope/size to right-size the structure assumes medium-large project
1.3 Does NOT immediately impose a specific architecture without asking imposes clean architecture
1.4 Mentions dependency injection approach as a follow-up question no DI question
1.5 Mentions that small projects should not be over-structured
2. cmd-directory-minimal-logic — Tests whether the model keeps cmd/ main.go minimal with no business logic 5/5 3/5
2.1 Says business logic does NOT belong in cmd/server/main.go
2.2 Says cmd/ should contain minimal logic: parse flags, wire dependencies, call Run() says "mostly initialization" without being specific
2.3 Recommends moving business logic to internal/ or pkg/
2.4 main.go should primarily do dependency wiring and startup
2.5 NEVER put request parsing, database queries, or response formatting in cmd/ not explicitly stated
3. no-src-utils-helpers-common — Tests whether the model avoids Java-style src/ directory and generic package names 6/6 4/6
3.1 Rejects src/ directory (Go doesn't use /src, it's a Java pattern)
3.2 Rejects utils/ as a generic package name
3.3 Rejects helpers/ as a generic package name
3.4 Rejects common/ as a generic package name accepts common/ as reasonable
3.5 Suggests domain-specific package names instead (e.g. format/, stringconv/) suggests renaming to "shared/"
3.6 Recommends putting main.go inside cmd/{name}/ not at root or in src/
4. module-naming-conventions — Tests whether the model follows Go module naming conventions 5/5 4/5
4.1 Identifies option 3 (github.com/jdoe/my-project) as correct
4.2 Rejects option 1 (myproject) -- must match repository URL
4.3 Rejects option 2 (MyProject) -- must be lowercase only
4.4 Rejects option 4 (my_project) -- use hyphens not underscores accepts underscores as valid
4.5 Rejects option 5 (utils) -- not semantic, doesn't match a repo URL
5. internal-vs-pkg-decision — Tests whether the model correctly decides between internal/ and pkg/ 5/5 4/5
5.1 Puts the shared logging library in pkg/ (useful to external consumers)
5.2 Puts the private request parsing code in internal/ (not exported)
5.3 Explains that internal/ cannot be imported by external packages (Go enforces this)
5.4 Mentions that pkg/ should only be used when code is genuinely intended for external use treats pkg/ as default location
5.5 Service/business logic goes in internal/ by default
6. workspace-when-to-use — Tests whether the model recommends go.work only for multi-module scenarios 4/4 3/4
6.1 Recommends AGAINST using go.work for a single-module project
6.2 Explains that go.work is for multiple related Go modules that import each other
6.3 Mentions that a single module with multiple packages does not need a workspace not explicitly stated
6.4 Lists valid use cases: monorepo with separate modules, local cross-module development
7. twelve-factor-app-conventions — Tests whether the model applies 12-Factor App principles for Go services 5/5 3/5
7.1 Recommends reading database URL from environment variables, not a checked-in config file
7.2 Recommends writing logs to stdout, not to a file says file logging is fine for development
7.3 References or describes 12-Factor App principles no 12-Factor reference
7.4 Mentions that sensitive values (like DB URLs) should never be in config files committed to source control
7.5 Explains WHY: environment-based config allows different values per deployment without code changes
8. library-layout-no-cmd — Tests whether the model uses the correct layout for a Go library 5/5 2/5
8.1 Public API packages are at the root level (e.g. logger/), NOT inside pkg/ or cmd/ puts public API in pkg/
8.2 No cmd/ directory (unless for example binaries) includes cmd/ with example binary
8.3 Uses internal/ for private implementation details
8.4 Includes example/ directory for usage examples no example directory
8.5 Structure follows the library layout pattern, not the application layout pattern
9. test-file-colocation — Tests whether the model co-locates test files with the code they test 5/5 3/5
9.1 Recommends co-locating test files with the code they test (same directory)
9.2 Test files use the _test.go suffix
9.3 Does NOT recommend a centralized tests/ directory for unit tests suggests tests/ for integration tests alongside unit co-location
9.4 Mentions testdata/ directory for test fixtures not mentioned
9.5 Distinguishes between white-box (same package) and black-box (package_test) testing approaches
10. config-sensitive-values-env-only — Tests whether the model requires sensitive config values from env vars or secret managers 5/5 3/5
10.1 Rejects putting database password, API keys, and JWT secret in config.yaml
10.2 Recommends environment variables for sensitive values
10.3 May suggest a secret manager as an alternative only mentions env vars
10.4 Config files are acceptable for non-sensitive values (port, log level, etc.)
10.5 Explains WHY: config files can be accidentally committed, leaked in backups, or visible to other processes says "security best practice" without explaining why
11. multiple-binaries-cmd-structure — Tests whether the model creates separate subdirectories in cmd/ for each binary 5/5 4/5
11.1 Creates separate subdirectories: cmd/server/, cmd/cli/, cmd/migrate/ (or similar names)
11.2 Each subdirectory has its own main.go with package main
11.3 Each binary can be built independently (go build ./cmd/server, etc.)
11.4 Mentions go build ./cmd/... to build all binaries at once only shows individual builds
11.5 Business logic is in internal/, not duplicated across cmd/ directories

golang-stay-updated — v1.0.0

With Skill Without Skill Delta
Overall 46/50 (92%) 18/50 (36%) +56pp
Full breakdown (50 assertions across 10 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. go-newsletters-recommendation — Tests whether the model recommends specific Go newsletters 5/5 2/5
1.1 Recommends Golang Weekly (golangweekly.com)
1.2 Recommends Awesome Go Newsletter (go.libhunt.com) not mentioned
1.3 Advises subscribing to 1-2 newsletters to avoid overload no quantity advice
1.4 Mentions these provide curated content, articles, and library updates
1.5 Does not recommend more than 3-4 newsletters (quality over quantity) lists 5+ newsletters
2. go-community-channels — Tests knowledge of specific Go community channels beyond Reddit 5/5 2/5
2.1 Mentions r/golang subreddit
2.2 Mentions gophers.slack.com (official Go Slack) not mentioned
2.3 Mentions the Go Forum (forum.golangbridge.org) not mentioned
2.4 Mentions golang-nuts Google Group (groups.google.com/g/golang-nuts) not mentioned
2.5 Mentions the official Go wiki (go.dev/wiki)
3. go-youtube-channels — Tests knowledge of specific Go YouTube channels 5/5 2/5
3.1 Recommends the official Go YouTube channel (@golang)
3.2 Recommends Gopher Academy not mentioned
3.3 Recommends GopherCon Europe or GopherCon UK channels only mentions generic GopherCon
3.4 Recommends Ardan Labs channel not mentioned
3.5 Lists at least 3 distinct Go-specific YouTube channels
4. famous-go-core-team-members — Tests knowledge of Go core team members to follow 5/6 3/6
4.1 Mentions Rob Pike as a co-creator
4.2 Mentions Russ Cox and his role in Go
4.3 Mentions Brad Fitzpatrick not mentioned
4.4 Mentions Dave Cheney as an influential Go community member
4.5 Mentions Robert Griesemer as a co-creator not mentioned
4.6 Provides social media handles or GitHub usernames for at least 3 people handles provided for only 2 no handles provided
5. go-library-authors-to-follow — Tests knowledge of influential Go library/framework authors 5/5 2/5
5.1 Mentions Steve Francia (spf13) — Cobra, Viper, Hugo
5.2 Mentions Mitchell Hashimoto (mitchellh) — Terraform, Consul, Vault
5.3 Mentions Samuel Berthe (samber) — lo, do, oops not mentioned
5.4 Mentions Matt Holt (mholt) — Caddy not mentioned
5.5 Provides GitHub usernames or X handles for the recommended people names only, no handles
6. official-go-resources — Tests knowledge of official Go resources and tools 5/5 3/5
6.1 Mentions go.dev as the official Go website
6.2 Mentions pkg.go.dev for package discovery and documentation
6.3 Mentions tour.golang.org (Go Tour) for interactive learning not mentioned
6.4 Mentions play.golang.org (Go Playground) for testing code not mentioned
6.5 Mentions go.dev/blog (official Go blog) for announcements
7. go-blogs-to-follow — Tests knowledge of must-follow Go blogs 4/4 2/4
7.1 Mentions The Go Blog (go.dev/blog)
7.2 Mentions Dave Cheney's blog (dave.cheney.net)
7.3 Mentions Ardan Labs Blog (ardanlabs.com/blog) not mentioned
7.4 Lists at least 3 specific blog names with URLs or authors only 2 blogs listed
8. staying-updated-strategy — Tests the curated strategy for staying updated without information overload 5/5 0/5
8.1 Recommends subscribing to 1-2 newsletters specifically (not more) no specific quantity
8.2 Recommends following 10-20 key people on social media no specific number
8.3 Recommends checking go.dev/blog weekly for official announcements says "regularly" not "weekly"
8.4 Recommends joining Go Slack for real-time discussions not mentioned
8.5 Recommends attending GopherCon (virtual or in-person) yearly not mentioned
9. go-conference-speakers — Tests knowledge of Go conference speakers and community leaders 3/5 1/5
9.1 Mentions at least one of: Carlisia Campos, Erik St. Martin, Brian Ketelsen none of the three mentioned none mentioned
9.2 Mentions Mat Ryer or Johnny Boursiquot as Go educators/speakers not mentioned
9.3 Mentions GopherCon as the conference to follow
9.4 Provides specific names with their social handles or GitHub profiles names without handles no handles
9.5 Lists at least 4 distinct speakers/community leaders only 2 named
10. go-performance-experts — Tests knowledge of Go performance and optimization experts 4/5 1/5
10.1 Mentions Dmitry Vyukov as a Go performance expert not mentioned
10.2 Mentions Dave Cheney for performance-related Go content
10.3 Provides GitHub usernames (e.g., dvyukov, davecheney) no usernames
10.4 Mentions Bill Kennedy / Ardan Labs for Go performance training not mentioned not mentioned
10.5 Mentions Jaana Dogan (rakyll) for Go internals/performance not mentioned

golang-database — v1.0.0

With Skill Without Skill Delta
Overall 70/74 (95%) 42/74 (57%) +38pp
Full breakdown (74 assertions across 15 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. orm-vs-sqlx-pgx-recommendation — sqlx/pgx over ORMs, explain why ORMs are harmful 5/5 2/5
1.1 Recommends sqlx or pgx instead of GORM sets up GORM as primary
1.2 Explains why ORMs are problematic (N+1, unpredictable SQL, magic hooks, debugging) presents GORM positively
1.3 Recommends pgx specifically for PostgreSQL due to performance advantage no mention of pgx
1.4 Does NOT set up GORM or ent as the primary database library
1.5 Mentions learning ORM API is harder than SQL or that ORMs hide SQL
2. exec-vs-query-for-non-select — use Exec for DELETE/INSERT/UPDATE, not Query 5/5 3/5
2.1 Uses ExecContext (not QueryContext) for the DELETE statement db.QueryContext for DELETE
2.2 Uses the *Context variant (ExecContext, not Exec)
2.3 Passes ctx to the database call
2.4 Retrieves RowsAffected() from the result discards result
2.5 Uses parameterized query (not string concatenation)
3. nullable-column-handling — pointer fields over sql.NullXxx for NULLable columns 5/5 2/5
3.1 Uses pointer types (*string, *time.Time) rather than sql.NullString/sql.NullTime sql.NullString and sql.NullTime
3.2 Includes db struct tags for sqlx (db:"column_name")
3.3 Includes json struct tags
3.4 Uses json:"bio,omitempty" for bio (omitted when NULL) no omitempty distinction
3.5 Uses json:"deleted_at" without omitempty (appears as null) omitempty on both nullable fields
4. connection-pool-configuration — all four pool settings with reasonable values 5/5 2/5
4.1 Calls SetMaxOpenConns
4.2 Calls SetMaxIdleConns not set
4.3 Calls SetConnMaxLifetime not set
4.4 Calls SetConnMaxIdleTime not set
4.5 MaxIdleConns <= MaxOpenConns
5. rows-close-and-err-check — defer Close and rows.Err() check after loop 5/5 4/5
5.1 Calls defer rows.Close() immediately after QueryContext
5.2 Checks rows.Err() after the for rows.Next() loop no rows.Err() check
5.3 Returns the error from rows.Err() if non-nil
5.4 Uses QueryContext (not Query) with a context parameter
5.5 Checks the error returned by QueryContext before proceeding
6. errnorows-handling-pattern — sql.ErrNoRows with domain error translation 5/5 3/5
6.1 Uses errors.Is(err, sql.ErrNoRows) to check for not-found
6.2 Returns a domain-specific error (ErrUserNotFound), NOT raw sql.ErrNoRows returns raw sql.ErrNoRows
6.3 Wraps non-ErrNoRows errors with context using fmt.Errorf and %w bare return err
6.4 Uses GetContext (not Get) with the ctx parameter
6.5 Uses parameterized query placeholder ($1 or ?)
7. transaction-with-defer-rollback — BeginTxx, defer Rollback, Commit, FOR UPDATE 4/5 3/5
7.1 Uses BeginTxx (or BeginTx) to start a transaction
7.2 Calls defer tx.Rollback() immediately after BeginTxx
7.3 Uses SELECT ... FOR UPDATE when reading balances plain SELECT without lock
7.4 Sets serializable or repeatable-read isolation level
7.5 Calls tx.Commit() at the end of the successful path
8. dynamic-in-clause-with-rebind — sqlx.In + Rebind pattern 5/5 3/5
8.1 Uses sqlx.In() to expand the IN clause placeholders manual placeholder building
8.2 Calls db.Rebind() after sqlx.In to adjust placeholders hardcoded $1,$2 placeholders
8.3 Passes the expanded args from sqlx.In to the query execution
8.4 Uses a *Context method variant
8.5 Handles the error from sqlx.In
9. dynamic-column-name-allowlist — allowlist validation, not direct interpolation 5/5 3/5
9.1 Validates sort column against an explicit allowlist direct fmt.Sprintf interpolation
9.2 Returns an error if column is not in the allowlist no validation
9.3 Does NOT pass column name as a parameterized placeholder
9.4 Uses fmt.Sprintf ONLY after validation
9.5 Uses a *Context method variant for the query
10. schema-creation-refusal — refuse to generate database schemas 3/4 1/4
10.1 Does NOT generate a complete CREATE TABLE schema generates full schema
10.2 Explains why AI-generated schemas are problematic no caveats
10.3 Recommends human review or dedicated tooling for schema design
10.4 Mentions schema design requires understanding data volumes/access patterns
11. batch-processing-sweet-spot — 100-1000 rows per batch, not row-by-row or giant batch 5/5 3/5
11.1 Uses batching with batch size between 100 and 1000 rows single giant INSERT
11.2 Does NOT insert all 50,000 rows in a single statement one statement with 50k value tuples
11.3 Does NOT insert one row at a time in a loop
11.4 Uses NamedExecContext or multi-row INSERT pattern
11.5 Handles errors per batch with context about which batch failed
12. cursor-pagination-over-offset — cursor-based pagination instead of OFFSET 5/5 2/5
12.1 Uses cursor-based pagination (WHERE created_at > $1) instead of OFFSET OFFSET/LIMIT pattern
12.2 Explains why OFFSET is problematic (re-scans skipped rows) no mention of OFFSET cost
12.3 Uses LIMIT with ORDER BY for page size
12.4 Returns a cursor value for the next page returns page number
12.5 Uses parameterized queries for the cursor value
13. integration-test-with-build-tags — build tags + transaction rollback isolation 4/5 3/5
13.1 Uses //go:build integration build tag no build tag
13.2 Uses transaction-based test isolation (begin tx, rollback in teardown) TRUNCATE in teardown
13.3 Does NOT test against production database — uses test DSN or testcontainers
13.4 Uses testify/suite or similar setup/teardown pattern
13.5 Tests actual SQL correctness (not mocked)
14. avoid-hidden-sql-features — no triggers, views, stored procedures 4/5 2/5
14.1 Advises against triggers for updated_at recommends CREATE TRIGGER
14.2 Recommends setting updated_at explicitly in Go code trigger approach
14.3 Advises against views for the complex query recommends CREATE VIEW
14.4 Explains hidden SQL features create invisible side effects
14.5 Recommends keeping SQL explicit and visible in Go code
15. pgx-copy-for-bulk-postgres — pgx.CopyFrom using COPY protocol 5/5 2/5
15.1 Recommends pgx.CopyFrom using the COPY protocol multi-row INSERT only
15.2 Shows pgx.CopyFromRows or pgx.CopyFromSlice usage no COPY usage
15.3 Mentions COPY is significantly faster than multi-row INSERT no performance comparison
15.4 Uses pgx.Identifier for the table name
15.5 Still recommends batching for extremely large datasets

golang-grpc — v1.0.0

With Skill Without Skill Delta
Overall 53/55 (96%) 30/55 (55%) +42pp
Full breakdown (55 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. raw-error-vs-status-error — return status.Errorf with codes, not raw errors 5/5 3/5
1.1 Uses status.Errorf (or status.Error) for not-found case with codes.NotFound
1.2 Uses status.Errorf with codes.Internal for unexpected errors
1.3 Does NOT return a raw fmt.Errorf or errors.New as the gRPC error returns fmt.Errorf for fallback
1.4 Imports google.golang.org/grpc/status and google.golang.org/grpc/codes
1.5 Does NOT leak internal error details in user-facing gRPC message for Internal errors passes err.Error() to client
2. wrapper-messages-not-bare-types — Request/Response wrappers, not bare types 5/5 3/5
2.1 Uses GetProductRequest/GetProductResponse wrappers, not bare string or Product
2.2 Uses DeleteProductRequest/DeleteProductResponse, not bare string or Empty uses google.protobuf.Empty for response
2.3 Uses SearchProductsRequest/SearchProductsResponse wrappers
2.4 Each wrapper message has properly named fields
2.5 Includes go_package option in the proto file omitted go_package
3. proto-directory-organization — domain-based organization with v1 directories 5/5 2/5
3.1 Organizes proto files by domain (user/, order/ or similar grouping)
3.2 Includes version directories (v1/ under each domain) no version directories
3.3 Separates message definitions from service definitions all in one file per domain
3.4 Includes a shared/ or common/ directory for shared messages no shared directory
3.5 Shows buf.gen.yaml with go and go-grpc plugins configured
4. graceful-stop-with-timeout-fallback — GracefulStop with timeout fallback to Stop 5/5 2/5
4.1 Calls srv.GracefulStop() first to drain in-flight RPCs
4.2 Has a timeout mechanism that falls back to srv.Stop() if GracefulStop takes too long only GracefulStop, no timeout fallback
4.3 Uses a select statement or timer for the timeout no select/timer
4.4 Listens for OS signals (SIGINT, SIGTERM or os.Interrupt)
4.5 The timeout is reasonable (5-30 seconds) no timeout configured
5. health-check-service-registration — register health check for Kubernetes probes 5/5 2/5
5.1 Registers grpc_health_v1.RegisterHealthServer (or equivalent) no health service registered
5.2 Uses health.NewServer() to create the health service no health server
5.3 Registers interceptors using grpc.ChainUnaryInterceptor
5.4 Does NOT enable reflection (or explicitly disables it for production) enables reflection
5.5 Includes graceful shutdown handling
6. client-connection-reuse-and-deadlines — reuse connections, set deadlines 4/5 3/5
6.1 Creates the connection once and reuses it
6.2 Uses context.WithTimeout (or context.WithDeadline) for the RPC call
6.3 Uses grpc.NewClient (not the deprecated grpc.Dial) uses deprecated grpc.Dial
6.4 Includes transport credentials (TLS or explicitly insecure for dev)
6.5 Properly defers cancel() from context.WithTimeout no defer cancel
7. bufconn-testing-pattern — in-memory connections for gRPC tests 5/5 2/5
7.1 Uses bufconn.Listen for an in-memory listener starts real TCP server
7.2 Uses grpc.WithContextDialer with the bufconn dialer dials localhost TCP
7.3 Verifies the gRPC status code for the not-found case using status.FromError
7.4 Checks that the error code is codes.NotFound specifically
7.5 Uses t.Cleanup or defer for cleaning up the server and connection no cleanup
8. error-code-selection-judgment — correct gRPC error codes for different scenarios 5/5 3/5
8.1 Uses codes.InvalidArgument for missing user_id or empty items
8.2 Uses codes.NotFound for user not found
8.3 Uses codes.FailedPrecondition for insufficient inventory uses codes.ResourceExhausted
8.4 Uses codes.Internal only for truly unexpected errors uses Internal for inventory check
8.5 Does NOT use codes.Unknown for any handled error case
9. unimplemented-server-embedding — embed UnimplementedXxxServer, not UnsafeXxxServer 5/5 4/5
9.1 Embeds UnimplementedOrderServiceServer in the server struct
9.2 Does NOT embed UnsafeOrderServiceServer
9.3 Implements methods with correct signatures (context, request pointer, response pointer + error)
9.4 Uses status.Errorf for error returns in handler methods returns raw errors in some methods
9.5 Registers the server with pb.RegisterOrderServiceServer
10. client-load-balancing-and-retry — dns:/// scheme, round_robin, retry config 4/5 2/5
10.1 Uses the dns:/// scheme for service discovery with headless Kubernetes services uses direct address
10.2 Configures round_robin load balancing policy via service config
10.3 Configures retry policy with retryableStatusCodes containing UNAVAILABLE no retry config
10.4 Sets maxAttempts, initialBackoff, maxBackoff in the retry policy no retry policy
10.5 Does NOT suggest creating a new connection per request for load distribution
11. streaming-over-large-messages — prefer streaming over large single messages 5/5 4/5
11.1 Recommends server streaming RPC to send records incrementally
11.2 Explains why a single large message is problematic (size limits, memory pressure)
11.3 Shows the server streaming pattern with stream.Send in a loop
11.4 Client reads with stream.Recv in a loop, checking for io.EOF
11.5 Does NOT just increase MaxRecvMsgSize as the primary solution suggests increasing MaxRecvMsgSize first

golang-samber-do — v1.0.0

With Skill Without Skill Delta
Overall 53/53 (100%) 10/53 (19%) +81pp
Full breakdown (53 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. v2-import-not-v1 — Tests whether the model uses samber/do/v2, never v1 4/4 1/4
1.1 Uses go get github.com/samber/do/v2 (not github.com/samber/do without v2) installs github.com/samber/do (v1)
1.2 Import path is github.com/samber/do/v2 in the code imports github.com/samber/do
1.3 Uses do.New() to create the container
1.4 Does NOT reference v1 API or import paths anywhere entire example uses v1 paths
2. lazy-vs-eager-vs-transient — Tests whether the model correctly chooses between lazy, eager, and transient service types 5/5 0/5
2.1 Uses do.Provide with do.Eager wrapper (or equivalent) for the database connection that must be ready immediately uses do.Provide for all three
2.2 Uses do.Provide (lazy, the default) for the user repository that's only needed on demand does not distinguish lifecycle
2.3 Uses do.ProvideTransient for the request logger that needs a fresh instance each time uses do.Provide for the logger
2.4 Correctly distinguishes between the three service lifecycle types treats all as lazy
2.5 Does NOT register all three services with the same registration function all use do.Provide
3. implicit-aliasing-invokeAs — Tests whether the model uses InvokeAs for implicit aliasing instead of explicit aliasing 5/5 1/5
3.1 Registers the concrete type *PostgreSQLDatabase with do.Provide
3.2 Uses do.MustInvokeAs[Database] or do.InvokeAs[Database] to invoke as the interface uses do.MustAs explicit aliasing
3.3 Prefers implicit aliasing (InvokeAs) over explicit aliasing (As/MustAs) uses explicit aliasing
3.4 Does NOT require a separate alias registration step for this basic case adds do.MustAs step
3.5 The provider function returns the concrete type, not the interface returns the interface type
4. package-organization — Tests whether the model organizes service registrations using do.Package 5/5 0/5
4.1 Uses do.Package to group related service registrations into separate packages/modules registers all in main()
4.2 Creates separate package variables (e.g., infrastructure.Package, service.Package, transport.Package) no package organization
4.3 Passes all packages to do.New() in main.go: do.New(infrastructure.Package, service.Package, transport.Package) uses do.New() then sequential Provide calls
4.4 Each package groups related services (infra, domain, transport) rather than one giant registration list flat list in main
4.5 Uses do.Lazy wrapper inside do.Package for lazy service registration no do.Package usage
5. scopes-for-lifecycle — Tests whether the model uses scopes to organize services by lifecycle and visibility 5/5 1/5
5.1 Uses do.Scope to create child scopes for per-request services no scoping used
5.2 Registers global/stateless services (config, logger) in the root container
5.3 Creates a new scope per request for request-scoped services all services in root
5.4 Child scope services can access parent (root) services not demonstrated
5.5 Does NOT register request-scoped services in the root container request context in root
6. testing-clone-override — Tests whether the model uses container cloning and overrides for testing 5/5 1/5
6.1 Uses injector.Clone() or do.Clone() to clone the production container creates new container from scratch
6.2 Uses do.Override or do.OverrideValue to replace the Database with a mock uses do.Provide with mock
6.3 Invokes the service under test from the cloned container invokes from fresh container
6.4 Does NOT build a completely new container from scratch for each test (unless justified) builds new container
6.5 The test is isolated — changes to the cloned container don't affect the original
7. health-check-interface — Tests whether the model implements the Healthchecker interface for service health checks 5/5 1/5
7.1 Implements a HealthCheck() method on the database service struct writes standalone healthCheck function
7.2 The HealthCheck method signature is either HealthCheck() error or HealthCheck(ctx context.Context) error wrong signature
7.3 Uses do.HealthCheckDatabase to invoke the health check through the container calls db.Ping() directly
7.4 Does NOT write a standalone function that manually fetches the service and pings it manual fetch and ping
7.5 The health check actually tests connectivity (e.g., conn.Ping())
8. graceful-shutdown-interface — Tests whether the model implements the Shutdowner interface for graceful shutdown 5/5 1/5
8.1 Implements Shutdown() or Shutdown(ctx context.Context) method on services that need cleanup uses defer db.Close() in main
8.2 Uses injector.ShutdownOnSignals or injector.ShutdownOnSignalsWithContext for signal-based shutdown manual signal.Notify
8.3 Passes os.Interrupt or syscall.SIGTERM to the shutdown function
8.4 Does NOT manually implement signal handling and iterate over services to shut them down manual signal handling loop
8.5 May use context.WithTimeout for shutdown deadline no timeout handling
9. composition-root-only — Tests that the container is only accessed at the composition root, not passed around 5/5 2/5
9.1 Advises against passing do.Injector into business logic or handler code passes injector to handler
9.2 States that the container should only be accessed at the composition root (main/startup) no mention of composition root
9.3 Shows resolving dependencies in the provider function using do.MustInvoke from the injector parameter
9.4 The UserHandler receives its dependencies as constructor parameters, not the container handler stores injector
9.5 Explains that passing the container creates a service locator anti-pattern that hides dependencies
10. named-services-same-type — Tests whether the model uses named services for multiple instances of the same type 5/5 1/5
10.1 Uses do.ProvideNamed to register each database with a distinct name (e.g., 'primary-db', 'replica-db') wraps in PrimaryDB/ReplicaDB types
10.2 Uses do.MustInvokeNamed or do.InvokeNamed to retrieve each database by name invokes wrapper types
10.3 Both databases are registered as the same type (*sql.DB or a Database interface) different wrapper types
10.4 Does NOT create unnecessary wrapper types just to distinguish the two databases creates PrimaryDB and ReplicaDB wrappers
10.5 Does NOT overwrite the first registration by using do.Provide twice for the same type
11. struct-injection-with-tags — Tests knowledge of struct injection using do tags 4/4 1/4
11.1 Uses struct field tags with do:"" or do:"service-name" syntax manually invokes each dependency
11.2 Uses do.MustInvokeStruct or do.InvokeStruct to populate the struct no struct injection
11.3 Shows that do:"" uses the type for resolution and do:"name" uses a named service unaware of tag-based injection
11.4 Does NOT manually call MustInvoke for each field when struct injection is available

golang-samber-oops — v1.0.0

With Skill Without Skill Delta
Overall 49/52 (94%) 18/52 (35%) +60pp
Full breakdown (52 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. low-cardinality-error-messages — Tests the critical rule: variable data goes in .With() attributes, not interpolated into the message string 5/5 2/5
1.1 Uses .With() for user_id, tenant_id, and order_id instead of interpolating them into the message interpolates all IDs into Errorf
1.2 The Errorf/Wrapf message string is static/low-cardinality (no variable interpolation for IDs) "failed to process order %s for user %s"
1.3 Uses the fluent builder pattern (chained method calls)
1.4 Does NOT use fmt.Errorf or errors.New for the error creation
1.5 Includes .In() to set the domain/feature context no .In() call
2. wrap-nil-passthrough — Tests that oops.Wrap returns nil if err is nil, so no nil check is needed 3/3 0/3
2.1 Identifies that the nil check is unnecessary because oops.Wrapf returns nil if err is nil says code is fine as-is
2.2 Shows the simplified form: return oops.In('processor').Wrapf(err, 'fetch failed') without the if block keeps the if block
2.3 The simplified version removes both the if statement and the separate return nil no simplification suggested
3. layered-error-context — Tests that each architectural layer should add context via Wrap/Wrapf at package boundaries 5/5 3/5
3.1 Each layer (handler, service, repository) adds its own .In() domain context only wraps at handler level
3.2 Each layer wraps the error from the layer below using Wrap or Wrapf
3.3 Different layers add different context attributes relevant to their scope
3.4 Uses .Tags() for categorization at one or more layers no .Tags() usage
3.5 Handler layer uses .Request() to attach HTTP request context
4. public-vs-technical-messages — Tests the separation between user-safe public messages and technical error details 5/5 2/5
4.1 Uses .Public() to set a user-safe message (e.g., 'Not enough items in stock') puts user message in Errorf
4.2 Uses .Errorf() or .Wrapf() for the technical error message (separate from the public message)
4.3 Uses .Code() to set a machine-readable error code (e.g., 'insufficient_stock') no error code set
4.4 Uses .With() for structured attributes like requested quantity, available stock data interpolated into message
4.5 Shows how to retrieve the public message using oops.GetPublic(err, fallback)
5. panic-recovery-goroutine — Tests that oops.Recover is used at goroutine boundaries to convert panics to structured errors 4/5 1/5
5.1 Uses oops.Recover() or the builder's .Recover() method, not a raw defer/recover uses raw defer/recover pattern
5.2 The Recover wraps the risky operation in a function passed to Recover manual panic handling
5.3 Adds structured context to the recovery (e.g., .In(), .Code(), .With()) no structured context on recovery
5.4 Uses a named return value for the error so Recover can set it omits named return in one variant no named return
5.5 Includes .Hint() for debugging guidance or .Code() for identification
6. context-propagation-middleware — Tests knowledge of oops.WithBuilder/oops.FromContext for propagating error context through Go contexts 5/5 0/5
6.1 Uses oops.WithBuilder() to store the builder in the Go context in middleware passes builder as function parameter
6.2 Uses oops.FromContext(ctx) in downstream functions to retrieve the pre-configured builder no context-based propagation
6.3 Middleware sets trace ID, request info, and user context on the builder no middleware pattern
6.4 Shows the middleware pattern with http.Handler wrapping no http.Handler wrapper
6.5 Downstream handlers/services can add more context (e.g., .Tags()) on top of the base builder each handler builds from scratch
7. reusable-builder-pattern — Tests the pattern of creating a reusable builder at the top of a function and reusing it for multiple error paths 5/5 2/5
7.1 Creates a single base builder variable at the top of the function with shared context (user, tenant, domain) duplicates builder chain at each error site
7.2 Each error return path extends the base builder with error-specific attributes using .With() or .Code() full chain repeated
7.3 The base builder is NOT terminated (no .Errorf/.Wrap call) — it's reused no shared builder
7.4 Uses .In() on the shared builder for the domain/feature
7.5 Uses .User() and/or .Tenant() on the shared builder
8. accessing-oops-error-info — Tests knowledge of the OopsError type assertion to access structured fields 6/6 3/6
8.1 Type-asserts the error to oops.OopsError
8.2 Uses .Code() method to get the error code
8.3 Uses .Domain() method to get the domain tries .In() on the error
8.4 Uses .Tags() method to get the tags not accessed
8.5 Uses .Context() method to get the key-value attributes map tries .Attributes()
8.6 Uses .Stacktrace() method to get the stack trace
9. user-and-tenant-context — Tests the .User() and .Tenant() methods with their key-value attribute support 5/5 1/5
9.1 Uses .User(id, key, value) method with the user ID and additional attributes like email uses .With("user_id", id)
9.2 Uses .Tenant(id, key, value) method with the tenant ID and additional attributes like plan uses .With("tenant_id", id)
9.3 Does NOT just use .With() for user/tenant info when .User()/.Tenant() are available only uses .With()
9.4 Includes a .Code() for the permission error no error code
9.5 Uses .Public() for a user-facing permission denied message
10. oops-assertions — Tests knowledge of oops.Assert/oops.Assertf for invariant checks wrapped in Recover 3/5 1/5
10.1 Uses oops.Assertf or oops.Assert to check the invariant (amount > 0) uses if/return error pattern
10.2 Wraps the assertion in an oops.Recover() call to convert the panic to a structured error Recover missing in one code path no Recover wrapping
10.3 Uses a named error return value so Recover can set it omits named return no named return
10.4 Notes that assertions should be rare in Go and used only for truly impossible/bug states no caveat about rare usage
10.5 Adds structured context (.In(), .Code(), etc.) to the Recover builder
11. oops-configuration — Tests knowledge of oops global configuration options 3/3 3/3
11.1 Uses oops.StackTraceMaxDepth to control stack trace depth
11.2 Uses oops.Local with time.LoadLocation for timezone configuration
11.3 Mentions oops.SourceFragmentsHidden as another available configuration option

golang-stretchr-testify — v1.0.0

With Skill Without Skill Delta
Overall 47/47 (100%) 25/47 (53%) +47pp
Full breakdown (47 assertions across 11 evals)

Model: Claude Opus 4.6 | Grading: Human-as-judge

# Assertion With Without
1. assert-vs-require-precondition — Tests whether the model uses require for preconditions and assert for verifications 5/5 3/5
1.1 Uses require (not assert) for the NoError check on parsing
1.2 Uses require (not assert) for the NotNil check on config uses assert.NotNil
1.3 Uses assert for the subsequent value checks (Port, Host, Debug)
1.4 Does NOT use require for all assertions indiscriminately
1.5 Argument order is (expected, actual) not (actual, expected) for Equal calls swaps to (actual, expected)
2. assert-new-naming-convention — Tests the skill's specific naming convention: 'is' for assert.New(t) and 'must' for require.New(t) 5/5 0/5
2.1 Uses assert.New(t) to create a reusable assertion object uses assert.Equal(t, ...) directly
2.2 Uses require.New(t) to create a reusable require object uses require.NoError(t, ...) directly
2.3 Names the assert.New(t) variable 'is' no New() usage
2.4 Names the require.New(t) variable 'must' no New() usage
2.5 Shows the 'is' and 'must' variables being used for different purposes (preconditions vs verifications) no differentiated naming
3. error-chain-assertion — Tests knowledge that is.Equal(ErrNotFound, err) fails on wrapped errors and ErrorIs should be used 4/4 2/4
3.1 Uses ErrorIs (not Equal) to check the error against ErrNotFound
3.2 Does NOT use assert.Equal or is.Equal to compare errors directly
3.3 Uses require for the initial error existence check if subsequent assertions depend on it uses assert for error check
3.4 Argument order for ErrorIs is (err, target) not (target, err) swaps arguments
4. mock-assert-expectations — Tests whether AssertExpectations is called 5/5 3/5
4.1 Mock embeds mock.Mock
4.2 Mock method uses m.Called() to forward arguments
4.3 Test calls m.AssertExpectations(t) to verify all expectations were met missing AssertExpectations
4.4 Uses .Once() or equivalent call modifier to enforce exactly one call no call modifier
4.5 Uses mock.Anything for arguments that don't need specific matching (e.g., context)
5. mock-matched-by-predicate — Tests knowledge of mock.MatchedBy for custom argument matching 5/5 3/5
5.1 Uses mock.MatchedBy with a predicate function for the LogEntry argument
5.2 The predicate checks Level == 'error'
5.3 The predicate checks that Message contains 'timeout' (using strings.Contains or similar)
5.4 Uses mock.Anything for the context argument passes context.Background() literally
5.5 Calls AssertExpectations at the end missing AssertExpectations
6. mock-retry-different-returns — Tests knowledge of chaining .Once() calls for retry testing 4/4 2/4
6.1 Sets up first On().Return() with an error and .Once()
6.2 Sets up second On().Return() with success data and .Once()
6.3 The two expectations are on the same method with the same arguments uses different argument patterns
6.4 Calls AssertExpectations to verify both calls happened missing AssertExpectations
7. suite-lifecycle-and-launcher — Tests that suite requires a launcher function and understands lifecycle order 5/5 3/5
7.1 Creates a suite struct embedding suite.Suite
7.2 Uses SetupTest (not SetupSuite) for per-test mock store initialization uses SetupSuite for mock init
7.3 Includes a launcher function: func TestXxxSuite(t *testing.T) with suite.Run() missing launcher function
7.4 Test methods are named TestXxx (starting with Test) on the suite receiver
7.5 Uses SetupSuite or TearDownSuite for the shared database connection (one-time setup)
8. suite-require-syntax — Tests that suite methods use s.Require().NotNil() for require behavior 3/3 1/3
8.1 Uses s.Require().NotNil() (not just s.NotNil()) for fail-fast behavior uses s.NotNil() thinking it's require
8.2 Explains that s.NotNil() and similar suite methods behave like assert (continue on failure) no explanation of default behavior
8.3 Shows that s.Require() returns a require-style assertion object
9. pointer-comparison-trap — Tests awareness that is.Equal(ptr1, ptr2) compares addresses, not values 3/3 2/3
9.1 Identifies that assert.Equal on pointers compares memory addresses, not struct values
9.2 Recommends dereferencing the pointers or using EqualExportedValues
9.3 Mentions EqualExportedValues as an alternative for comparing only exported fields only suggests dereferencing
10. eventually-with-rich-assertions — Tests knowledge of EventuallyWithT for async polling with multiple rich assertions 5/5 3/5
10.1 Uses EventuallyWithT (not just Eventually) for rich assertions uses Eventually with bool
10.2 The callback receives *assert.CollectT (or similar collect parameter) callback returns bool
10.3 Multiple assertions are made inside the callback (status check AND result count check)
10.4 Uses assert.NoError/assert.Equal with the CollectT parameter inside the callback, not with t
10.5 Specifies timeout (10s) and polling interval as separate parameters
11. testifylint-recommendation — Tests whether the model recommends testifylint for catching common testify mistakes 3/3 3/3
11.1 Recommends testifylint as a linter for testify-specific issues
11.2 Mentions that testifylint catches wrong argument order
11.3 Mentions that testifylint catches assert/require misuse

golang-samber-mo — v1.0.0

With Skill Without Skill Delta
Overall 95/108 (88%) 43/108 (40%) +48pp
Full breakdown (108 assertions)

Model: Claude Opus 4.6 | Runs: 23 evals × 2 configs = 46 subagents | Grading: Human-as-Judge + LLM-as-judge

# Assertion With Without
1. option-vs-pointer-for-nullable-db-field — Option[T] for nullable DB columns 5/5 5/5
1.1 Uses mo.Option[string] for nullable fields
1.2 Mentions sql.Scanner and driver.Valuer
1.3 Mentions json.Marshaler/Unmarshaler
1.4 Shows row.Scan with Option type
1.5 Does NOT recommend sql.NullString
2. result-vs-tuple-error-boundary — Result at API boundary vs internal 5/5 2/5
2.1 Returns (Config, error) at public boundary returns Result publicly
2.2 Uses Result internally for chaining uses Result everywhere
2.3 Shows TupleToResult at boundary
2.4 Shows .Get() at end for conversion back returns Result directly
2.5 Explains Result for internal composition
3. either-vs-result-two-valid-types — Either for non-error alternatives 5/5 2/5
3.1 Uses Either[CachedUser, FreshUser] uses interface or Result
3.2 Does NOT use Result suggests common interface
3.3 Explains Either vs Result no distinction made
3.4 Shows Left/Right constructors
3.5 Shows Match or IsLeft/IsRight
4. sub-package-for-type-changing-map — Sub-package for type-changing Map 5/5 1/5
4.1 Uses option.Map from sub-package uses nonexistent mo.Map(opt, fn)
4.2 Does NOT use .Map for type change
4.3 Imports mo/option no sub-package import
4.4 Shows curried form uses mo.Map(opt, fn) pattern
4.5 Explains why sub-packages exist claims mo.Map is package-level
5. do-notation-for-imperative-monadic — mo.Do for imperative monadic code 5/5 0/5
5.1 Suggests mo.Do suggests unwrap-early pattern
5.2 Shows MustGet inside Do manual .Get() checks
5.3 Explains panic catching no mention of Do
5.4 Do returns Result[T] no mention of Do
5.5 Cleaner than FlatMap chains no Do alternative
6. pipe-composition-multi-step — Pipe for multi-step type-changing pipelines 5/5 0/5
6.1 Uses option.Pipe3 nests function calls
6.2 Each step uses option.Map/FlatMap manual intermediate vars
6.3 Pipeline reads top-to-bottom nested calls
6.4 Imports mo/option sub-package no sub-package awareness
6.5 FlatMap for validation step manual if/else
7. future-vs-task-eager-vs-lazy — Future (eager) vs Task (lazy) 5/5 4/5
7.1 Recommends Task
7.2 Future starts immediately
7.3 Task runs on .Run()
7.4 Run returns *Future[T]
7.5 Deferred execution pattern wrong constructor signature
8. tuple-to-result-wrapping-stdlib — TupleToResult for stdlib wrapping 5/5 3/5
8.1 Uses TupleToResult(os.ReadFile(path))
8.2 Uses TupleToResult or Try for Atoi
8.3 Does NOT manually check err manually constructs Ok/Err
8.4 Chains results
8.5 Explains TupleToResult no explanation
9. when-not-to-use-monads — Advises against monads for simple cases 4/4 3/4
9.1 Advises against Result here
9.2 Recommends if err != nil
9.3 Result shines with chains
9.4 Does NOT over-apply Result still wraps in Result
10. option-json-serialization — Option JSON marshaling behavior 5/5 3/5
10.1 Uses Option[string]
10.2 Some->value, None->null
10.3 Mentions omitzero (Go 1.24+) no omitzero awareness
10.4 Shows struct with json tag
10.5 No custom MarshalJSON needed suggests custom marshaler
11. emptyable-to-option-zero-value — EmptyableToOption for zero detection 4/4 0/4
11.1 Uses EmptyableToOption manual if/else
11.2 None for zero, Some for non-zero manual construction
11.3 No manual check uses if/else
11.4 Works for any comparable type only handles string
12. pointer-to-option-nil-handling — PointerToOption for nil conversion 3/3 1/3
12.1 Uses PointerToOption(ptr) manual nil check
12.2 nil->None, non-nil->Some
12.3 No manual nil check manual check
13. result-map-vs-flatmap-choice — Map vs FlatMap on Result 5/5 2/5
13.1 MapValue for infallible uppercase uses Map for both
13.2 FlatMap for fallible parse
13.3 No FlatMap for uppercase uses FlatMap for both
13.4 Shows chain
13.5 Explains distinction no distinction explained
14. option-map-bool-semantics — Option.Map's (T, bool) return type 4/4 0/4
14.1 Uses Map with (int, bool) return claims Map can't filter
14.2 false converts to None doesn't know bool return
14.3 Shows (T, bool) signature claims func(T) T
14.4 No FlatMap for filtering uses FlatMap
15. foldable-interface-uniform-matching — Fold across Option/Result/Either 0/5 0/5
15.1 Uses mo.Fold not in skill separate switch blocks
15.2 Shows Foldable interface not in skill no Foldable awareness
15.3 successFn/failureFn callbacks not in skill manual matching
15.4 Single Fold for all types not in skill separate logic
15.5 No separate handling per type not in skill separate blocks
16. io-for-testable-side-effects — IO for deferring side effects 4/4 0/4
16.1 Uses IO or IOEither uses interfaces/DI
16.2 IO is lazy, runs on Run() no IO awareness
16.3 IO1/IO2 parameterize no IO awareness
16.4 Composability of IO no IO awareness
17. result-to-either-conversion — ToEither() conversion 3/3 1/3
17.1 Uses result.ToEither() manual IsOk + Left/Right
17.2 Ok->Right, Err->Left
17.3 No manual IsOk check manual check
18. either3-for-multi-type-union — Either3+ for n-ary unions 5/5 2/5
18.1 Uses Either3 uses interface or type switch
18.2 Shows constructors no constructor awareness
18.3 Shows Match type switch
18.4 No interface{}/any
18.5 Mentions Either4/Either5
19. option-vs-zero-value-distinction — When Option adds value vs zero values 4/4 3/4
19.1 Plain int for count
19.2 Option[string] for nickname
19.3 Explains absence vs zero
19.4 No Option[int] for count uses Option for both
20. map-lookup-to-option — TupleToOption for map lookups 4/4 1/4
20.1 Uses TupleToOption(m[key]) manual ok check
20.2 Chains Map/FlatMap
20.3 No manual ok check manual check
20.4 Complete pattern manual construction
21. result-try-catch-panics — Try for wrapping panicky functions 2/4 2/4
21.1 Uses mo.Try
21.2 Try catches panics skill says Do catches panics, not Try claims Try only wraps (T, error)
21.3 Shows signature
21.4 No separate defer/recover skill suggests Do for panics adds defer/recover
22. state-monad-for-accumulation — State monad for threading state 4/4 0/4
22.1 Uses State/NewState uses mutable struct
22.2 State params = state + result mutable position field
22.3 Run(initialState) no State awareness
22.4 State threaded, not mutated mutates struct
23. result-map-signature — Result.Map's (T, error) return type 4/4 2/4
23.1 Map callback returns (int, error)
23.2 Does NOT use func(int) int
23.3 MapValue alternative no MapValue mention
23.4 Map vs MapValue signatures no distinction

golang-samber-hot — v1.0.0

With Skill Without Skill Delta
Overall 61/65 (94%) 26/65 (40%) +54pp
Full breakdown (65 assertions)

Model: Claude Opus 4.6 | Runs: 15 evals × 2 configs = 30 subagents | Grading: LLM-as-judge

# Assertion With Without
1. algorithm-selection-default — mixed workload, should pick W-TinyLFU over LRU 6/6 0/6
1.1 Uses hot.WTinyLFU as the eviction algorithm (not hot.LRU) used hot.NewLRU
1.2 Uses hot.NewHotCache constructor with generic type parameters fabricated API
1.3 Chains .WithTTL(10 * time.Minute) or equivalent positional arg
1.4 Chains .WithJanitor() in the builder missing
1.5 Calls defer cache.StopJanitor() after Build() missing
1.6 Calls .Build() to finalize the cache no builder pattern
2. algorithm-selection-frequency — stable power-law DNS, should pick LFU 4/4 4/4
2.1 Recommends hot.LFU for stable-frequency workload
2.2 Explains why LFU fits: keeps frequently accessed items
2.3 Mentions LFU weakness doesn't apply because rankings are stable
2.4 Does NOT default to LRU
3. janitor-required — simple cache with TTL must include WithJanitor() 4/4 1/4
3.1 Includes .WithJanitor() in the builder chain fabricated config struct
3.2 Includes defer cache.StopJanitor() for cleanup missing
3.3 Uses .WithTTL() for expiration config struct field
3.4 Does NOT call cache.Janitor() separately after Build()
4. missing-cache-panic-prevention — SetMissing requires config 4/4 0/4
4.1 Configures WithMissingCache() or WithMissingSharedCache() in builder stores nil in main cache
4.2 Uses cache.SetMissing() or cache.SetMissingWithTTL() uses cache.Set(key, nil)
4.3 Missing cache configured BEFORE Build() no config
4.4 Explains SetMissing without config panics unaware of SetMissing
5. loader-pattern-singleflight — built-in dedup, no manual singleflight 5/5 0/5
5.1 Uses .WithLoaders() in the builder chain manual singleflight
5.2 Explains built-in singleflight deduplication suggests external package
5.3 Does NOT implement manual singleflight on top uses x/sync/singleflight
5.4 Loader signature matches func(keys []K) (map[K]V, error) manual fetch pattern
5.5 Recommends WithJitter() for additional thundering-herd mitigation manual rand jitter
6. stale-while-revalidate — two-threshold revalidation pattern 5/5 2/5
6.1 Uses .WithTTL(5 * time.Minute) for fresh duration
6.2 Uses .WithRevalidation(2 * time.Minute, loader) invented API
6.3 Explains fresh -> stale -> expired model
6.4 Uses WithRevalidationErrorPolicy() not mentioned
6.5 Does NOT use a single 7min TTL
7. sharding-for-concurrency — WithSharding for lock contention 4/4 2/4
7.1 Uses .WithSharding() in the builder custom sharded wrapper
7.2 Shard count is a power of 2
7.3 Provides or discusses a hash function of type func(K) uint64
7.4 Does NOT recommend WithoutLocking()
8. copy-on-read-mutable-values — WithCopyOnRead for pointer safety 4/4 2/4
8.1 Identifies callers mutating shared cached pointers
8.2 Uses .WithCopyOnRead(fn) manual copy wrapper
8.3 Copy function creates shallow or deep copy
8.4 Does NOT suggest only external mutexes as primary solution
9. loader-chain-semantics — later overwrites earlier, error stops chain 5/5 1/5
9.1 Later loaders overwrite earlier values (PostgreSQL wins) says Redis wins
9.2 Any loader error stops the entire chain unclear
9.3 Partial results from earlier loaders discarded on error not mentioned
9.4 Shows WithLoaders(redisLoader, dbLoader) invented API
9.5 Later loaders only receive keys NOT found by previous
10. algorithm-scan-resistance — LRU scan pollution, switch to scan-resistant 4/4 4/4
10.1 Identifies LRU scan pollution problem
10.2 Recommends scan-resistant algorithm
10.3 Explains why recommended algorithm resists scan pollution
10.4 Does NOT suggest only increasing cache size
11. withoutlocking-janitor-conflict — mutually exclusive, panics 4/4 3/4
11.1 Warns WithoutLocking() + WithJanitor() are mutually exclusive
11.2 Recommends removing one of the two
11.3 Suggests manual cleanup or dropping WithoutLocking()
11.4 Does NOT produce code chaining both initial code chains both
12. warmup-before-traffic — WithWarmUp in builder 4/4 1/4
12.1 Uses .WithWarmUp(fn) or .WithWarmUpWithTimeout(timeout, fn) wrong API guess
12.2 Warm-up function returns (map[K]V, []K, error) wrong signature
12.3 Warm-up happens before Build() returns
12.4 Does NOT manually loop calling Set() loops calling Get()
13. prometheus-monitoring-setup — built-in WithPrometheusMetrics 4/4 1/4
13.1 Uses .WithPrometheusMetrics(cacheName) custom counters
13.2 Registers with prometheus.MustRegister(cache) registers custom metrics
13.3 Mentions hit rate as key metric (target >80%)
13.4 Does NOT create custom Prometheus counters full custom instrumentation
14. get-error-handling — three return values (V, bool, error) 4/4 2/4
14.1 Get() returns (V, bool, error) wrong semantics
14.2 Checks error first before bool
14.3 Handles all three cases: err, !found, found no not-found handling
14.4 Does NOT ignore error return value
15. peek-vs-get-distinction — Peek() for side-effect-free inspection 4/4 3/4
15.1 Recommends Peek() or PeekMany()
15.2 Explains Peek() does not trigger loaders
15.3 Explains Peek() ignores expiration (returns expired entries) no expired-entry detail
15.4 Does NOT recommend Get() for inspection

golang-samber-lo — v1.0.0

With Skill Without Skill Delta
Overall 83/86 (97%) 49/86 (57%) +40pp
Full breakdown (86 assertions)

Model: Claude Opus 4.6 | Runs: 17 evals × 2 configs = 34 subagents | Grading: LLM-as-judge

# Assertion With Without
1. lop-for-io-trap — Should recommend errgroup over lop for HTTP fan-out 5/5 1/5
1.1 Recommends errgroup or similar I/O concurrency pattern used lop.Map as primary
1.2 Explains lop is for CPU-bound parallelism, not I/O
1.3 Mentions lack of context cancellation as lop limitation
1.4 Does NOT use lop.Map as primary HTTP fan-out solution
1.5 Shows working Go code for concurrent fetch
2. premature-lom-optimization — Should advise profiling before switching to lom 6/6 3/6
2.1 Recommends profiling (pprof, alloc_objects) before switching
2.2 Warns lom mutates input slice (breaks immutability) claims lom doesn't exist
2.3 Does NOT blindly refactor all lo calls to lom
2.4 Explains performance issue may not be lo allocations
2.5 Mentions lom only for hot paths confirmed by profiling claims lom doesn't exist
2.6 Warns about concurrency safety of mutable ops
3. stdlib-vs-lo-preference — Should prefer stdlib for Contains/Sort/Keys 6/6 5/6
3.1 Recommends slices.Contains from stdlib
3.2 Recommends slices.Sort from stdlib
3.3 Recommends maps.Keys from stdlib
3.4 Mentions Go 1.21+ availability
3.5 Explains rationale: prefer stdlib when available says "either works fine"
3.6 Acknowledges lo useful for ops stdlib lacks
4. loi-go-version-constraint — Should warn lo/it requires Go 1.23+ 5/5 2/5
4.1 Warns loi requires Go 1.23+, not available in Go 1.20 claims lo/it doesn't exist
4.2 Suggests upgrading Go version to 1.23+
4.3 Provides alternative approaches for Go 1.20
4.4 Does NOT provide loi code that won't compile on Go 1.20
4.5 Mentions range-over-func as Go 1.23 feature
5. must-in-production-handler — Should warn against Must in HTTP handlers 5/5 5/5
5.1 Warns Must panics on error, dangerous in handlers
5.2 Explains Must for tests/init only, not production
5.3 Provides refactored version with proper error handling
5.4 Shows returning appropriate HTTP error status codes
5.5 Does NOT approve Must-based approach for production
6. import-aliases-knowledge — Should know lop/lom/loi aliases 5/5 2/5
6.1 Lists lo/parallel with alias lop
6.2 Lists lo/mutable with alias lom unaware of mutable pkg
6.3 Lists lo/it with alias loi unaware of iterator pkg
6.4 Mentions lo/it requires Go 1.23+
6.5 Lists core package with alias lo
7. immutability-trap — lo.Filter does NOT modify input 5/5 3/5
7.1 States lo.Filter returns new slice, doesn't modify input
7.2 Correctly says len(users) prints 2
7.3 Explains lo is immutable by default as design principle no design principle framing
7.4 Points out return value of lo.Filter is discarded
7.5 Mentions lom.Filter as alternative for in-place mutation no mention of lom
8. error-variant-awareness — Should recommend lo.MapErr 4/5 1/5
8.1 Recommends lo.MapErr as error-aware variant
8.2 Explains MapErr stops on first error, returns (result, error)
8.3 Shows MapErr usage example
8.4 Mentions most lo functions have Err suffixes as pattern
8.5 Suggests lo.FilterMap for skipping errors
9. simd-production-stability — Should warn simd is experimental 4/5 2/5
9.1 Warns lo/exp/simd is experimental, API may break claims simd doesn't exist
9.2 States not covered by semver stability guarantees
9.3 Recommends benchmarking first
9.4 Suggests version pinning if used
9.5 Does NOT unconditionally recommend simd for production
10. streaming-redirect-to-ro — Should redirect to samber/ro 5/5 3/5
10.1 Recommends samber/ro for reactive/streaming pipelines suggests samber/hot instead
10.2 Explains lo is for finite/batch, not infinite streams
10.3 Mentions golang-samber-ro skill or samber/ro by name mentions samber/hot not ro
10.4 Does NOT attempt to use lo for infinite streaming
10.5 Explains conceptual difference: lo = batch, ro = reactive
11. lop-small-dataset-trap — lop.Map wasteful for 10 items 5/5 5/5
11.1 Recommends lo.Map instead of lop.Map for 10 items
11.2 Explains goroutine overhead exceeds benefit
11.3 Mentions threshold (~1000+ items)
11.4 Notes field access is trivial, no parallelism benefit
11.5 Does NOT recommend lop.Map for this case
12. filtermap-vs-filter-then-map — Should use lo.FilterMap 5/5 5/5
12.1 Recommends lo.FilterMap as single-pass alternative
12.2 Shows FilterMap with (R, bool) return signature
12.3 Explains avoids intermediate filtered slice
12.4 Provides working code parsing strings to ints
12.5 Does NOT chain Filter+Map as primary solution
13. channel-dispatcher-strategies — Should list ChannelDispatcher strategies 6/6 6/6
13.1 Mentions lo.ChannelDispatcher
13.2 Lists RoundRobin strategy
13.3 Lists Random strategy
13.4 Lists WeightedRandom/First/Least/Most
13.5 Explains when to choose different strategies
13.6 Shows code example using ChannelDispatcher
14. v2-version-trap — Should state v2 does not exist 4/4 0/4
14.1 States samber/lo v2 does not exist fabricated v2 migration guide
14.2 Mentions v1 follows semver, no breaking changes before v2 fabricated breaking changes
14.3 Provides correct install: go get github.com/samber/lo@v1 shows go get lo/v2
14.4 Does NOT fabricate v2 migration steps full fabricated migration
15. lazy-chain-intermediate-allocations — Should recommend loi 5/5 1/5
15.1 Recommends lo/it (loi) for lazy evaluation claims loi doesn't exist
15.2 Explains loi processes on-demand without buffering
15.3 Mentions Go 1.23+ requirement
15.4 Shows lazy pipeline using loi functions
15.5 Contrasts eager vs lazy memory allocation
16. lo-zero-dependencies — Should state zero external deps 4/4 1/4
16.1 States zero external/runtime dependencies claims golang.org/x/exp dep
16.2 Mentions relies only on Go stdlib mentions x/exp
16.3 Addresses supply chain concern
16.4 Does NOT claim lo has external dependencies claims x/exp dependency
17. ternary-evaluation-trap — lo.Ternary evaluates both branches 4/4 4/4
17.1 Warns lo.Ternary evaluates both arguments
17.2 Recommends lo.TernaryF for lazy evaluation
17.3 Shows TernaryF usage with closures
17.4 Explains expensiveCompute runs even when condition is false

golang-samber-slog — v1.0.0

With Skill Without Skill Delta
Overall 57/62 (92%) 45/62 (73%) +19pp
Full breakdown (62 assertions)

Model: Claude Opus 4.6 | Runs: 12 evals × 2 configs = 24 subagents | Grading: LLM-as-judge

# Assertion With Without
1. pipeline-ordering — sampling + PII + Sentry composition 5/5 3/5
1.1 Sampling is outermost/first handler in pipeline PII first, sampling second
1.2 Uses slog-sampling library for sampling
1.3 Uses slog-formatter for PII scrubbing
1.4 Uses slogmulti.Router or Fanout for routing
1.5 Explains why sampling should be first no ordering rationale
2. fanout-vs-router — level-based routing to Sentry/Slack/stdout 5/5 5/5
2.1 Uses slogmulti.Router() for level-based routing
2.2 stdout receives all log levels
2.3 Sentry receives only errors
2.4 Slack receives only warnings
2.5 Explains Router vs Fanout distinction
3. missing-close-batch — Datadog handler graceful shutdown 5/5 4/5
3.1 Calls handler.Close() or defer Close()
3.2 Uses Option{}.NewDatadogHandler() pattern
3.3 Close happens during graceful shutdown
3.4 Mentions batching / data loss risk
3.5 Import path uses versioned module (v2+) unversioned import
4. sampling-strategy-selection — reduce 90% volume, keep errors 5/5 5/5
4.1 Uses Threshold or Absolute (not Uniform)
4.2 Sampling only affects Debug/Info levels
4.3 Warn/Error pass through unsampled
4.4 Uses slog-sampling library
4.5 Explains why Uniform is wrong
5. router-missing-default — DEBUG logs disappearing 5/5 5/5
5.1 Identifies missing catch-all handler
5.2 Explains unmatched records silently dropped
5.3 Suggests adding default handler or LevelIs(Debug)
5.4 Uses slogmulti.Router() API correctly
5.5 Does NOT suggest changing log level as fix
6. http-middleware-filters — slog-gin with path filters 5/5 5/5
6.1 Uses sloggin.NewWithConfig()
6.2 Configures IgnorePath for /health and /metrics
6.3 Sets WithRequestBody: true
6.4 Config includes level fields
6.5 Uses IgnorePath (not custom filter)
7. pipe-middleware-chain — trace injection + email scrubbing 5/5 4/5
7.1 Uses slogmulti.Pipe() for chaining
7.2 Creates middleware for trace_id injection
7.3 Creates middleware for email scrubbing
7.4 Pipe wraps the final handler
7.5 Does NOT implement custom slog.Handler struct full Handler struct
8. failover-handler — Loki fallback to file 5/5 4/5
8.1 Uses slogmulti.Failover()
8.2 Loki is primary handler
8.3 File handler is fallback
8.4 Does NOT implement custom retry logic
8.5 Uses slog-loki library placeholder function
9. pool-vs-fanout-latency — fix sequential Fanout latency 2/5 2/5
9.1 Identifies sequential Fanout as root cause
9.2 Recommends slogmulti.Pool() custom goroutines misuses Pool
9.3 Explains latency reduction (sum to max) claims 0ms latency
9.4 Does NOT suggest raw goroutines as primary
9.5 Shows code change from Fanout to Pool
10. formatter-pii-scrubbing — cross-cutting PII protection 5/5 3/5
10.1 Uses slog-formatter library custom middleware
10.2 Uses PIIFormatter and/or IPAddressFormatter custom regex
10.3 Applied as Pipe middleware wrapping all handlers
10.4 Applied once before Router/Fanout
10.5 Does NOT implement custom per-handler PII logic
11. backend-option-pattern — Sentry + Loki setup 5/5 4/5
11.1 Uses slogsentry.Option{}.NewSentryHandler()
11.2 Uses slogloki.Option{}.NewLokiHandler()
11.3 Uses Router for level-based routing uses Fanout
11.4 Versioned import paths
11.5 Calls Close() on Loki handler
12. attrfromcontext-without-middleware — empty request_id diagnosis 7/7 6/7
12.1 Identifies no middleware populating context
12.2 Recommends adding HTTP middleware
12.3 Explains middleware injects attributes into context
12.4 Does NOT suggest changing context key as primary fix
12.5 Shows connection between middleware and AttrFromContext
12.6 Mentions WithRequestID config option
12.7 Does NOT blame slog-multi/sentry config

golang-samber-ro — v1.0.0

With Skill Without Skill Delta
Overall 113/113 (100%) 57/113 (50%) +50pp
Full breakdown (113 assertions)

Model: Claude Opus 4.6 | Runs: 25 evals × 2 configs = 50 subagents | Grading: LLM-as-judge (self-grading subagents)

# Assertion With Without
1. typed-pipe-vs-untyped — chain 3 operators with type safety 4/4 3/4
1.1 Uses ro.Pipe3 instead of untyped ro.Pipe for compile-time type safety uses untyped Pipe
1.2 Uses ro.Filter with func(int) bool predicate
1.3 Uses ro.Map with func(int) string transform
1.4 Uses ro.Takestring with correct generic type omits generic param
2. lo-vs-ro-boundary — finite slice should use lo, not ro 4/4 0/4
2.1 Recommends samber/lo instead of samber/ro uses ro Observable
2.2 Explains WHY lo is better: synchronous, no stream overhead
2.3 Does NOT create an Observable pipeline for a slice transform creates Observable
2.4 Uses lo.Filter and lo.Map uses ro operators
3. observer-error-handling — full observer, not just OnNext 4/4 1/4
3.1 Uses ro.NewObserver with all 3 callbacks uses ro.OnNext
3.2 Includes an error handler
3.3 Includes a completion handler infinite stream, skipped
3.4 Mentions risk of OnNext alone (silent error dropping)
4. infinite-stream-shutdown — graceful SIGTERM shutdown 5/5 0/5
4.1 Uses TakeUntil or context+ThrowOnContextCancel generic context approach
4.2 Mentions signal plugin (plugins/signal) uses manual os/signal
4.3 Calls .Wait() to block until shutdown doesn't know .Wait()
4.4 Does NOT suggest manual channel/goroutine killing uses channel-based approach
4.5 Mentions Unsubscribe() as alternative not in ro API context
5. subject-config-store — BehaviorSubject for config 5/5 3/5
5.1 Recommends BehaviorSubject
5.2 Explains replay of last value to new subscribers
5.3 Shows NewBehaviorSubjectConfig constructor wrong constructor syntax
5.4 Shows .Send() and .Subscribe() uses .Next() from RxJS
5.5 Does NOT recommend PublishSubject
6. subject-chat-room — ReplaySubject for chat history 4/4 3/4
6.1 Recommends ReplaySubject with buffer 50
6.2 Shows NewReplaySubjectMessage constructor wrong constructor syntax
6.3 Explains N-value replay for late subscribers
6.4 Does NOT recommend BehaviorSubject
7. share-websocket — hot observable for shared WebSocket 5/5 5/5
7.1 Uses Share() or ShareReplay()
7.2 Creates only ONE WebSocket connection
7.3 Does NOT create 3 separate connections
7.4 Shows 3 separate .Subscribe() calls
7.5 Explains cold vs hot distinction
8. combinelatest-vs-zip — latest values from different-rate sources 6/6 5/6
8.1 Recommends CombineLatest2
8.2 Explains re-emit on either source update
8.3 Explains why NOT Zip
8.4 Explains why NOT Merge
8.5 Shows lo.Tuple2 return type generic tuple
8.6 Uses Map to compute product generic combiner function
9. retry-with-backoff — RetryWithConfig + cached fallback 6/6 1/6
9.1 Uses RetryWithConfig (not infinite Retry) uses Retry with count param
9.2 Sets Max: 3 wrong API
9.3 Sets Delay: 500ms and BackoffMultiplier: 2.0 wrong field names
9.4 Sets MaxDelay: 10s field not included
9.5 Chains fallback after RetryWithConfig uses RxJS-style naming
9.6 Correct operator order: retry before fallback
10. scan-vs-reduce — running average for dashboard 5/5 5/5
10.1 Recommends Scan
10.2 Explains intermediate emission
10.3 Explains Reduce only emits final
10.4 Shows accumulator with count/sum
10.5 Notes infinite stream + Reduce = never emits
11. collect-synchronous — Collect for finite observable to slice 4/4 0/4
11.1 Uses ro.Collect returning ([]int, error) manual subscribe+accumulate
11.2 Checks error return value
11.3 Does NOT manually subscribe and accumulate
11.4 Mentions Collect blocks until complete
12. context-propagation — wire timeout into pipeline 4/4 1/4
12.1 Uses ContextWithTimeout or ContextReset passes ctx to Subscribe
12.2 Uses ThrowOnContextCancel
12.3 Chains context operators in pipeline
12.4 Handles cancellation error in onError
13. plugin-fsnotify — file watcher with debounce 5/5 3/5
13.1 Knows fsnotify plugin (plugins/fsnotify) uses manual fsnotify
13.2 Uses plugin to create observable wraps channel manually
13.3 Uses ThrottleTime for debounce
13.4 Filters for Write events
13.5 Shows Map to reload config
14. plugin-cron — daily midnight schedule 4/4 0/4
14.1 Knows cron plugin (plugins/cron) uses Interval
14.2 Uses cron expression 0 0 * * *
14.3 Shows correct import path
14.4 Chains with Map/FlatMap for report generation
15. maperr-fallible-transform — MapErr for JSON parsing 5/5 1/5
15.1 Uses MapErr (not Map) uses Map
15.2 Shows func(string) (MyStruct, error) signature
15.3 Explains error propagation through pipeline
15.4 Does NOT suggest panic/recover pattern
15.5 Mentions JSON encoding plugin as alternative
16. buffer-batching — BufferWithTimeOrCount for DB writes 4/4 3/4
16.1 Uses BufferWithTimeOrCount generic buffer approach
16.2 Sets count=100, duration=5s
16.3 Chains with Map/MapErr for batch processing
16.4 Explains why both conditions matter
17. unicast-subject-queue — single-consumer job queue 5/5 0/5
17.1 Recommends UnicastSubject uses channel or PublishSubject
17.2 Shows NewUnicastSubjectTask
17.3 Explains exactly one subscriber
17.4 Explains pre-subscribe buffering
17.5 Does NOT recommend PublishSubject
18. share-vs-sharereplay — late subscribers missing data 4/4 3/4
18.1 Recommends ShareReplay(1)
18.2 Explains Share doesn't buffer
18.3 Explains ShareReplay buffers last N
18.4 Shows correct syntax ro.ShareReplayT wrong generic syntax
19. error-recovery-cascade — retry then fallback then default 5/5 1/5
19.1 Correct order: RetryWithConfig, Catch, OnErrorReturn wrong operator names
19.2 Uses RetryWithConfig with Max: 2 generic retry approach
19.3 Uses Catch for secondary source uses RxJS-style naming
19.4 Uses OnErrorReturn for default wrong operator name
19.5 Correct Pipe order
20. channel-bridge — FromChannel for legacy chan 4/4 4/4
20.1 Uses ro.FromChannelEvent
20.2 Observable completes when channel closes
20.3 Chains standard operators
20.4 Does NOT create custom NewObservable
21. tap-for-observability — logging without modifying stream 5/5 3/5
21.1 Uses Tap/TapOnNext/TapOnError/TapOnComplete
21.2 Does NOT use Map with side effect
21.3 Shows TapOnError for error monitoring generic Tap only
21.4 Explains Tap/Do observe without altering
21.5 Mentions logging plugins (slog, zap, etc.)
22. connectable-precise-control — wait for all subscribers 5/5 4/5
22.1 Recommends Connectable
22.2 Shows ro.ConnectableT wrong constructor
22.3 Sets up subscribers before Connect
22.4 Explains Connect() starts execution
22.5 Explains why Share() is wrong
23. flatmap-vs-map — flatten Observable[Observable[T]] 4/4 3/4
23.1 Recommends FlatMap or MergeMap
23.2 Shows FlatMap code wrong generic syntax
23.3 Explains map+flatten behavior
23.4 Does NOT suggest Map+MergeAll as primary
24. async-subject-final — last value on completion 5/5 4/5
24.1 Recommends AsyncSubject
24.2 Shows NewAsyncSubject[T]() constructor wrong constructor
24.3 Explains last value only on completion
24.4 Does NOT recommend BehaviorSubject
24.5 Does NOT recommend ReplaySubject(1)
25. version-stability — v0 pre-release warnings 4/4 3/4
25.1 Mentions v0.x (pre-v1.0.0)
25.2 Warns about breaking changes
25.3 Mentions SemVer not confident about SemVer
25.4 Does NOT present as fully stable

golang-uber-dig — v1.0.0

With Skill Without Skill Delta
Overall 20/20 (100%) 18/20 (90%) +10pp
Full breakdown (20 assertions)

Model: Claude Opus 4.7 | Runs: 4 evals × 2 configs = 8 subagents | Grading: human (assertion-by-assertion)

Note: Preliminary subset of the 11-eval suite in skills/golang-uber-dig/evals/evals.json (53 assertions total). The remaining 7 evals cover named values, dig.As, scopes for request-locals, DryRun graph validation, RecoverFromPanics, group flatten, and the fx-vs-dig recommendation. Re-run the full suite via /skill-creator for a complete report.

# Assertion With Without
1. param-objects-many-deps — dig.In for 4+ deps 5/5 5/5
1.1 Embeds dig.In in the parameter struct
1.2 Constructor takes the params struct as a single argument
1.3 Does NOT keep the long parameter list
1.4 Does NOT use a plain struct without dig.In
1.5 Mentions readability/maintainability benefit
2. value-groups-for-handlers — group:"routes" 5/5 5/5
2.1 Each handler returns dig.Out tagged group:"routes"
2.2 NewRouter consumes dig.In with []slice tagged group:"routes"
2.3 Handlers added with c.Provide; no manual slice in main()
2.4 Does NOT manually assemble the handler slice
2.5 Mentions group order is not guaranteed
6. container-not-passed-around — composition-root only 5/5 5/5
6.1 Advises against passing *dig.Container into business code
6.2 Container only at composition root (main / startup)
6.3 UserHandler takes typed dependencies as constructor parameters
6.4 Mentions service locator anti-pattern OR explains downside
6.5 Does NOT show example with container injected into handler
8. decorate-not-rewrite — Decorate vs scope-shadow Provide 5/5 3/5
8.1 Uses Decorate (c.Decorate / scope.Decorate) on *zap.Logger uses scope.Provide override
8.2 Decorator returns log.Named("worker")
8.3 Decorate at the worker scope/module, not globally
8.4 Does NOT modify the original NewLogger constructor
8.5 Mentions decorator scope semantics (applies to scope and descendants) describes scope-override semantics instead

Analyst pass: evals 1, 2, and 6 score 5/5 in both configurations — they test knowledge the base model already has, hitting the "common knowledge" anti-pattern flagged in CLAUDE.md. Only eval 8 differentiates the skill: the without-skill agent reaches for scope.Provide shadowing (which would create a circular dependency at resolution time), while the skill steers to the correct Decorate API. Future iterations should redesign evals 1, 2, 6 to target subtler guidance and add cases the model gets wrong without the skill.

golang-uber-fx — v1.0.0

With Skill Without Skill Delta
Overall 21/21 (100%) 20/21 (95%) +5pp
Full breakdown (21 assertions)

Model: Claude Opus 4.7 | Runs: 4 evals × 2 configs = 8 subagents | Grading: human (assertion-by-assertion)

Note: Preliminary subset of the 11-eval suite in skills/golang-uber-fx/evals/evals.json (56 assertions total). The remaining 7 evals cover fx.Annotate vs fx.Out, fx.Module organization, fx.Supply, value groups, fxevent.ZapLogger, manual lifecycle for CLI embedding, and the fx-vs-dig recommendation. Re-run the full suite via /skill-creator for a complete report.

# Assertion With Without
1. lifecycle-not-init — OnStart with goroutine 6/6 6/6
1.1 Injects fx.Lifecycle into NewHTTPServer
1.2 lc.Append with fx.Hook (OnStart starts server, OnStop calls Shutdown)
1.3 OnStart launches srv.Serve in a goroutine
1.4 Does NOT call srv.Serve directly inside the constructor
1.5 Does NOT use init() to start the server
1.6 OnStop calls srv.Shutdown(ctx) for graceful shutdown
5. fxtest-with-populate — fxtest.New + fx.Populate 5/5 5/5
5.1 Uses fxtest.New(t, ...) instead of fx.New
5.2 Uses fx.Populate(&svc) to extract *UserService
5.3 Calls app.RequireStart() and app.RequireStop()
5.4 Provides a fake Database (interface, not real DB)
5.5 Does NOT use fx.Invoke as the primary extraction mechanism
6. replace-for-fakes — fx.Replace with fx.Annotate(fx.As) 5/5 4/5
6.1 Uses fx.Replace (or fx.Decorate) inside fxtest.New uses fx.Decorate
6.2 Composes ProductionModule alongside the override; module unchanged
6.3 Uses fx.Annotate with fx.As(new(Database)) for interface binding mentions but does not use
6.4 Does NOT modify or duplicate the production module
6.5 Mentions Replace/Decorate is appropriate for tests
10. onstart-non-blocking — long-running work in goroutine 5/5 5/5
10.1 OnStart launches the long-running method in a goroutine
10.2 OnStart returns nil quickly without waiting
10.3 OnStop signals stop and waits for drain (with timeout)
10.4 Does NOT call the long-running method synchronously in OnStart
10.5 Mentions a blocking OnStart hangs the boot

Analyst pass: 3 of 4 evals score equally with and without the skill — the model's baseline knowledge of fx is very strong (lifecycle hooks, fxtest.New, OnStart/goroutine pattern). Only eval 6 differentiates: without the skill the agent picks fx.Decorate and skips the fx.As interface binding that the production graph requires. This is consistent with a well-known framework where the skill mainly adds value on subtle API choices. Future iterations should target less common patterns: fx.Annotate vs fx.Out trade-offs, fx.Module decorator scoping, fxevent customization, manual lifecycle for CLI embedding.

golang-google-wire — v1.0.0

With Skill Without Skill Delta
Overall 49/50 (98%) 41/50 (82%) +16pp
Full breakdown (50 assertions)

Model: Claude Sonnet 4.6 | Runs: 10 evals × 2 configs = 20 subagents | Grading: LLM-as-Judge

# Assertion With Without
1. build-constraint-on-injector — missing //go:build wireinject causes duplicate symbols 5/5 3/5
1.1 Identifies the missing //go:build wireinject build tag as the root cause suggested //go:build ignore instead
1.2 Shows //go:build wireinject as the first line of the injector file showed //go:build ignore
1.3 Explains that the tag prevents the stub from being compiled into the binary
1.4 Does NOT suggest renaming the function or reorganizing packages as the fix
1.5 Does NOT suggest deleting wire_gen.go as the fix
2. interface-binding-requiredwire.Bind must be explicit; wire never auto-resolves 5/5 4/5
2.1 Explains that wire never auto-resolves interface satisfaction — bindings must be explicit
2.2 Shows wire.Bind(new(UserStore), new(*PostgresUserRepo)) added to the provider set
2.3 Places wire.Bind inside the same wire.NewSet (or adds it to a set in wire.Build)
2.4 Explains WHY wire requires explicit bindings (predictability — avoids surprise rebinding) cited ambiguity concern, not predictability rationale
2.5 Does NOT suggest changing NewUserRepo to return UserStore directly as the primary fix
3. duplicate-type-named-wrapper — named types disambiguate same underlying type 5/5 4/5
3.1 Introduces distinct named types (e.g., type PrimaryDSN string and type ReplicaDSN string)
3.2 Updates NewPrimaryDSN to return PrimaryDSN and NewReplicaDSN to return ReplicaDSN
3.3 Updates NewPrimaryDB and NewReplicaDB signatures to accept the named types
3.4 Explains that wire enforces one provider per type, so distinct named types are the correct fix
3.5 Does NOT suggest using a single Config struct with both DSNs as the primary fix presented Config struct as co-primary recommendation
4. cleanup-signature(T, func(), error) cleanup propagated through injector 5/5 4/5
4.1 Changes NewDB to return (*sql.DB, func(), error) where cleanup calls db.Close()
4.2 Changes the injector function to return (*App, func(), error) to propagate the cleanup chain
4.3 Shows main calling defer cleanup() after the nil-check called defer cleanup() directly without nil guard
4.4 Explains that wire chains cleanup functions in reverse construction order
4.5 Does NOT suggest passing db as an extra return value from InitApp alongside *App
5. no-edit-wire-gen — never edit wire_gen.go; re-run wire ./... 5/5 5/5
5.1 Explicitly says NOT to edit wire_gen.go (it is always overwritten)
5.2 Instructs running wire ./... to regenerate wire_gen.go
5.3 Explains that *zap.Logger must be provided in the graph (via a provider or wire.Value)
5.4 Shows how to add NewLogger (or wire.Value) to the appropriate wire.NewSet
5.5 Does NOT present editing wire_gen.go as an option
6. provider-set-organization — per-package wire.NewSet, not one giant set in main 5/5 5/5
6.1 Introduces per-package wire.NewSet variables (e.g., InfraSet, RepoSet, ServiceSet)
6.2 Each set lives in its own package's wire.go file (not all in main)
6.3 The injector wire.Build references the set variables rather than individual providers
6.4 wire.Bind declarations move into the relevant package's set (not into wire.Build directly)
6.5 Explains the benefit: per-package sets are independently composable
7. injector-parameter-vs-value-provider — injector parameters vs wire.Value for pre-built values 4/5 5/5
7.1 Shows the injector-parameter approach: func InitApp(cfg *Config) (*App, func(), error)
7.2 OR shows wire.Value(cfg) inside wire.Build — both are valid answers
7.3 Explains that injector parameters are treated as pre-built providers by wire
7.4 Does NOT use a global variable as the recommended solution
7.5 Does NOT suggest using init() to set the value
8. fields-of-structwire.FieldsOf promotes struct fields without manual extractors 5/5 1/5
8.1 Uses wire.FieldsOf(new(Config), "DatabaseDSN", "CacheAddress", "APIKey") or a subset actively discouraged wire.FieldsOf
8.2 Places wire.FieldsOf inside the provider set or wire.Build wire.FieldsOf not used
8.3 Updates NewDB, NewCache, NewExternalClient to accept the string fields as parameters
8.4 Explains that wire.FieldsOf promotes struct fields as individual graph nodes dismissed rather than explained
8.5 Does NOT suggest three separate extractor functions as the primary recommendation ProvideDatabaseDSN etc. were the only recommendation
9. test-injector-pattern — test-only wire.NewSet with fake bindings 5/5 5/5
9.1 Creates a test-only provider set with NewFakeMailer and wire.Bind(new(Mailer), new(*FakeMailer))
9.2 Creates a test injector function in a _test.go file with //go:build wireinject
9.3 The test injector's wire.Build composes production sets with the test-only set
9.4 Does NOT suggest global variables, monkey-patching, or a runtime DI container for tests
9.5 Mentions that wire ./... (or go generate) must be run to produce the test-injector code
10. wire-vs-fx-for-daemon — wire lacks lifecycle; recommend fx for long-running services 5/5 5/5
10.1 Identifies that wire has no built-in lifecycle management (no OnStart/OnStop hooks)
10.2 Identifies that wire has no built-in signal handling (SIGINT/SIGTERM)
10.3 Recommends uber-go/fx (or flags it as the better fit) for a long-running HTTP daemon
10.4 Does NOT recommend wire as sufficient for a service requiring graceful shutdown
10.5 Mentions that with wire the developer must implement shutdown and signal handling manually

Analyst pass: 5 of 10 evals score equally with and without the skill — evals 5, 6, 9, 10 reflect strong model priors for well-known wire patterns (never-edit wire_gen.go, per-package sets, test injectors, fx-vs-wire comparison). The clearest skill uplift is eval 8 (wire.FieldsOf): without the skill, the model actively discourages wire.FieldsOf and recommends manual extractor functions instead. Eval 1 also differentiates: without the skill, the model suggests //go:build ignore rather than the correct //go:build wireinject tag. Eval 7 produced a false negative for the with-skill run — the skill correctly notes wire.Value only accepts constant expressions and rejected it for a runtime-parsed config; this is technically correct behavior but the assertion accepts both forms as valid. Future iterations should redesign assertion 7.2 to credit the correct injector-parameter approach when wire.Value is rightly excluded.

golang-spf13-cobra — v1.0.0

With Skill Without Skill Delta
Overall 50/50 (100%) 49/50 (98%) +2pp
Full breakdown (50 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 evals × 2 configs = 24 self-graded evaluations | Grading: self-graded (⚠️ biased — model sees assertions in prompt; without-skill baseline inflated; human-as-judge evaluation recommended for accurate uplift)

# Assertion With Without
1. rune-vs-run — RunE vs Run, defer bypass 5/5 5/5
1.1 Identifies that Run cannot return an error
1.2 Explains only escape from Run is os.Exit or panic, bypassing defers
1.3 Recommends switching to RunE
1.4 Shows RunE signature: func(cmd *cobra.Command, args []string) error
1.5 Does NOT suggest goroutine or recover() as the fix
2. args-validator — len(args) in RunE vs Args field 4/4 3/4
2.1 Recommends cobra.ExactArgs(1) in the Args field
2.2 Shows the Args field on cobra.Command, not inside RunE
2.3 Explains cobra generates standard error messages automatically
2.4 Does NOT suggest keeping the len(args) check inside RunE
3. outstdout — cmd.OutOrStdout vs os.Stdout 4/4 4/4
3.1 Identifies os.Stdout (via fmt.Println) as root cause — can't redirect
3.2 Recommends cmd.OutOrStdout() as the io.Writer for output
3.3 Shows cmd.SetOut(buf) in test setup to redirect output
3.4 Does NOT suggest using os.Pipe() as the fix
4. persistentprerun-chain — PersistentPreRunE replacement not chaining 4/4 4/4
4.1 Explains cobra does NOT chain PersistentPreRunE — child replaces parent
4.2 Recommends explicitly calling parent's PersistentPreRunE from child
4.3 Shows calling parent hook: rootCmd.PersistentPreRunE(cmd, args)
4.4 Does NOT suggest using PreRunE instead as the fix
5. silenceusage — SilenceUsage and SilenceErrors configuration 4/4 4/4
5.1 Recommends SilenceUsage: true on the root command
5.2 Recommends SilenceErrors: true if app prints errors itself
5.3 Explains SilenceUsage prevents full usage wall on every error
5.4 Shows setting fields on root cobra.Command struct
6. addgroup-ordering — AddGroup before AddCommand 4/4 4/4
6.1 Identifies root cause: AddGroup must be called BEFORE AddCommand
6.2 Explains cobra does not retroactively assign groups
6.3 Shows correct order: AddGroup, GroupID, AddCommand
6.4 Does NOT suggest AddCommand before AddGroup as workaround
7. validargsfunction — dynamic positional arg completion 4/4 4/4
7.1 Uses ValidArgsFunction (not just ValidArgs []string)
7.2 Shows correct signature with args and toComplete parameters
7.3 Returns ShellCompDirectiveNoFileComp to suppress file fallback
7.4 Does NOT suggest parsing shell completion scripts manually
8. registerflagcompletionfunc — flag value completion 4/4 4/4
8.1 Uses RegisterFlagCompletionFunc (not ValidArgs on the command)
8.2 Shows function registered for the specific flag name "format"
8.3 Returns the static list ["json", "yaml", "table"]
8.4 Does NOT suggest modifying shell rc files or completion scripts
9. test-isolation — fresh command tree per test 4/4 4/4
9.1 Identifies cobra accumulates flag state across Execute() calls
9.2 Recommends building a fresh command tree per test
9.3 Suggests factory function (e.g. newRootCmd()) for fresh trees
9.4 Does NOT recommend -count=1 as the primary fix
10. matchall — composing cobra validators 4/4 4/4
10.1 Uses cobra.MatchAll() to compose validators
10.2 Shows cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs)
10.3 Sets ValidArgs on the command for OnlyValidArgs to work
10.4 Does NOT suggest custom validator duplicating ExactArgs logic
11. cobra-vs-viper — cobra does not handle config files 5/5 5/5
11.1 Clearly states cobra does NOT handle config file parsing
11.2 Recommends viper (spf13/viper) for config file support
11.3 Explains cobra handles command tree/flags, viper handles config resolution
11.4 Mentions BindPFlag as the integration point between cobra and viper
11.5 Does NOT suggest cobra alone is sufficient for YAML/JSON config files
12. cobra-cli-scaffolder — cobra-cli scaffolding tool 4/4 4/4
12.1 Mentions cobra-cli as the scaffolding tool
12.2 Shows cobra-cli init or cobra-cli add commands
12.3 Explains cobra-cli generates command boilerplate
12.4 Does NOT suggest writing boilerplate from scratch as the only option

Analyst pass: Almost all 12 evals score equally with and without the skill because the model's baseline cobra knowledge is strong — RunE, Args validators, OutOrStdout, SilenceUsage, and completions are well-documented. The only differentiating eval is 2.4 (explicitly discouraging len(args) checks in RunE as a backup). The self-grading methodology inflates the without-skill baseline — a human-as-judge rerun is recommended to measure true uplift, particularly on the subtler evals (4: PersistentPreRunE non-chaining, 6: AddGroup ordering, 9: test isolation with factory function).

golang-spf13-viper — v1.0.0

With Skill Without Skill Delta
Overall 52/53 (98%) 53/53 (100%) -2pp
Full breakdown (53 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 evals × 2 configs = 24 self-graded evaluations | Grading: self-graded (⚠️ biased — model sees assertions in prompt; without-skill baseline inflated; human-as-judge evaluation recommended for accurate uplift. Negative delta on eval 8.5: with-skill model mentions GetDuration as alternative, which fails the "Does NOT suggest" assertion; without-skill model omits it, which passes.)

# Assertion With Without
1. env-key-replacer-nested-keys — SetEnvKeyReplacer for nested keys 5/5 5/5
1.1 Identifies the root cause as missing SetEnvKeyReplacer
1.2 Explains viper preserves the dot in 'database.host' when looking up env var
1.3 Provides the fix: viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
1.4 Shows full setup requires prefix + replacer + AutomaticEnv together
1.5 Does NOT suggest renaming the config key to avoid dots
2. sub-returns-nil — viper.Sub() returns nil for missing keys 5/5 5/5
2.1 Identifies that viper.Sub() returns nil when the key doesn't exist
2.2 Shows nil check: if sub := viper.Sub("database"); sub != nil { ... }
2.3 Suggests returning error or using defaults when sub is nil
2.4 Does NOT suggest checking err from Sub() (returns no error, only nil)
2.5 Optionally suggests UnmarshalKey("database", &dbCfg) as an alternative
3. config-file-not-found-graceful — ConfigFileNotFoundError handling 5/5 5/5
3.1 Uses errors.As(err, &notFound) with *viper.ConfigFileNotFoundError
3.2 Only propagates errors that are NOT ConfigFileNotFoundError
3.3 Continues execution normally when config file is not found
3.4 Does NOT use os.Stat or file existence check as the solution
3.5 Does NOT ignore all errors from ReadInConfig (real errors still propagate)
4. global-viper-test-pollution — viper.New() for test isolation 5/5 5/5
4.1 Identifies root cause as shared global viper state across tests
4.2 Recommends viper.New() per test for isolated instance
4.3 Shows v := viper.New() with v.SetConfigFile, v.ReadInConfig, etc.
4.4 Does NOT recommend viper.Reset() as the primary solution
4.5 Explains why tests are order-dependent (state persists to next test)
5. unmarshal-mapstructure-tags — mapstructure struct tags required 4/4 4/4
5.1 Identifies missing mapstructure struct tag as the root cause
5.2 Shows adding mapstructure:"max_conn" to the MaxConn field
5.3 Explains mapstructure does case-insensitive matching but not underscore-camelcase
5.4 Does NOT suggest using viper.GetInt as the fix
6. bind-pflag-timing — BindPFlag must be before Execute() 4/4 4/4
6.1 Identifies BindPFlag must be called before Execute() / before RunE runs
6.2 Recommends moving BindPFlag to init() or PersistentPreRunE
6.3 Explains cobra parses flags before RunE — binding after misses Changed state
6.4 Shows correct pattern: define flag + BindPFlag in init()
7. viper-key-case-insensitivity — viper keys always lowercase 4/4 4/4
7.1 Explains viper normalizes all keys to lowercase internally
7.2 Recommends using lowercase keys consistently in viper.Get* calls
7.3 Identifies viper.GetString("database_host") as the correct form
7.4 Notes DATABASE_HOST works via lowercasing but is a source of confusion
8. duration-decode-hook — StringToTimeDurationHookFunc for time.Duration 4/5 5/5
8.1 Identifies mapstructure cannot decode duration string without a hook
8.2 Shows viper.Unmarshal with DecodeHook using StringToTimeDurationHookFunc
8.3 Uses mapstructure.StringToTimeDurationHookFunc() as part of the hook
8.4 Does NOT suggest changing config value to nanoseconds
8.5 Does NOT suggest using viper.GetDuration as a workaround
9. watch-config-atomic-rename-trap — fsnotify inode replacement by editors 4/4 4/4
9.1 Explains vim writes atomically via rename (write-to-temp, then rename)
9.2 Explains fsnotify watches inode, replaced by rename-based writes
9.3 Recommends testing with direct writes (os.WriteFile or echo >>) not editor saves
9.4 Does NOT suggest downgrading vim or switching editors as the fix
10. viper-alone-no-cobra — viper usable without cobra 4/4 4/4
10.1 Clearly states viper can be used without cobra
10.2 Explains cobra is for command trees/flags, not needed for HTTP service
10.3 Shows viper setup without any cobra imports
10.4 Does NOT recommend adding cobra just for configuration purposes
11. unmarshal-key-vs-sub — UnmarshalKey vs Sub+Unmarshal 4/4 4/4
11.1 Recommends viper.UnmarshalKey("database", &dbCfg) as the simpler alternative
11.2 Explains it avoids the nil check required with Sub()
11.3 Shows correct usage: viper.UnmarshalKey("database", &dbCfg)
11.4 If mentioning Sub+Unmarshal, notes the nil risk
12. allow-empty-env — AllowEmptyEnv for empty string env vars 4/4 4/4
12.1 Explains viper treats empty string env vars as 'not set' by default
12.2 Introduces viper.AllowEmptyEnv(true) as the fix
12.3 Explains with AllowEmptyEnv(true), empty env var overrides config file value
12.4 Does NOT suggest using viper.Set() as a workaround

Analyst pass: All 12 evals score equally or better without the skill because the model's baseline viper knowledge is strong — ConfigFileNotFoundError, viper.New() for tests, mapstructure tags, and UnmarshalKey are all well-documented. The single differentiating point is eval 8.5 (negative): the skill makes the model mention viper.GetDuration as an alternative, which fails the "Does NOT suggest" assertion. The skill's reference to the unmarshal section may be over-helpful on this eval. The self-grading methodology inflates the without-skill baseline throughout. A human-as-judge rerun is recommended targeting evals 1 (env key replacer — the subtlest viper gotcha), 8 (decode hook), and 12 (AllowEmptyEnv — lesser-known API).

golang-swagger — v1.0.0

With Skill Without Skill Delta
Overall 58/60 (97%) 43/60 (72%) +25pp
Full breakdown (60 assertions)

Model: Claude Sonnet 4.6 | Runs: 12 evals × 2 configs = 24 subagents | Grading: Human-as-judge

# Assertion With Without
1. gin-blank-import — blank import + ginSwagger wire-up 5/5 4/5
1.1 Adds a blank import of the docs package (e.g., _ "<module>/docs")
1.2 Imports github.com/swaggo/gin-swagger
1.3 Imports github.com/swaggo/files
1.4 Registers a GET route matching /swagger/*any using ginSwagger.WrapHandler
1.5 Does not suggest running swag init again (it was already done)
2. map-response-type — map[string]bool cannot be used directly in @Success 5/5 3/5
2.1 Does NOT use {object} map[string]bool directly in @Success
2.2 Defines a named struct for the response OR acknowledges a swaggertype workaround is needed
2.3 @Success annotation uses a named type (not a raw map literal)
2.4 @Router annotation is present with [get] method
2.5 @Produce annotation specifies json
3. swaggertype-overrides — time.Time and []byte need swaggertype tags 5/5 1/5
3.1 Adds swaggertype tag to CreatedAt field
3.2 Adds swaggertype tag to UpdatedAt field with the same treatment
3.3 Adds swaggertype:"string" and format:"base64" to the Payload []byte field
3.4 Preserves the json tags (does not remove them)
3.5 Does not leave time.Time fields without any swaggertype override
4. chi-dynamic-basepath — Chi router + BasePath from env var 5/5 5/5
4.1 Imports github.com/swaggo/http-swagger
4.2 Uses r.Get (chi method) to register the swagger route with a wildcard pattern
4.3 Sets docs.SwaggerInfo.BasePath using os.Getenv("API_BASE_PATH") or equivalent
4.4 Includes the blank docs import
4.5 Does not suggest rebuilding or running swag init per environment
5. and-security-condition — && vs two separate @Security lines 5/5 4/5
5.1 Uses && between security schemes on a single @Security annotation line
5.2 Does NOT write two separate @Security lines for AND semantics
5.3 References valid security definition names (ApiKeyAuth, BasicAuth, or similar)
5.4 Explains or implies that two separate @Security lines would mean OR, not AND
5.5 @Security line appears inside the handler doc comment block
6. tag-exclusion — swag init --tags with ! prefix to exclude tags 5/5 4/5
6.1 Uses the --tags flag (or -t) with swag init
6.2 Uses ! prefix to exclude tags (e.g., --tags '!Internal,!Admin' or similar)
6.3 Shows a complete swag init command
6.4 Does not suggest manually editing the generated swagger.json
6.5 Does not require writing custom Go code to filter endpoints
7. godoc-comment-swag-fmt — godoc comment line required for swag fmt 5/5 4/5
7.1 Adds // CreateOrder godoc as the first line of the comment block
7.2 godoc comment appears before any @ annotation
7.3 At minimum includes @Summary, @Router annotations
7.4 @Router specifies both path and HTTP method
7.5 Annotation block is placed directly above the function signature
8. csv-vs-multi — collectionFormat(multi) for ids, collectionFormat(csv) for fields 4/5 2/5
8.1 @Param for ids uses collectionFormat(multi)
8.2 @Param for fields uses collectionFormat(csv) or omits it (csv is the default)
8.3 Both params use []string or []int as data type
8.4 Both params are marked as not required (false)
8.5 @Router annotation is present with [get] method
9. conditional-swagger — env-based swagger toggle without build tags 5/5 5/5
9.1 Uses os.Getenv (or equivalent) to read APP_ENV at runtime
9.2 Conditionally registers the swagger route only when not in production
9.3 Does not require separate builds or build tags
9.4 The blank docs import is still present (or acknowledged as needed)
9.5 Solution works without recompiling between environments
10. nested-composition — Envelope{data=model.User} nested composition syntax 5/5 3/5
10.1 @Success annotation uses nested composition syntax with curly braces (e.g., Envelope{data=model.User})
10.2 The inner type is the User struct (or equivalent named type)
10.3 Does not create a new wrapper struct just for documentation purposes
10.4 @Param for the id path parameter is present with path location
10.5 @Router specifies the correct path and [get] method
11. enums-min-max-tags — enums/minimum/maximum struct tags 4/5 4/5
11.1 Adds enums:"admin,editor,viewer" struct tag to Role field
11.2 Adds minimum:"0" and maximum:"100" struct tags to Score field
11.3 Adds json tags to all fields
11.4 Adds example tags to at least one field
11.5 Does not only describe constraints in a comment — they must be machine-readable struct tags
12. swaggerignore-serialization — swaggerignore:"true" vs json:"-" 5/5 4/5
12.1 Uses swaggerignore:"true" on the LastModified field
12.2 Keeps a valid json tag on LastModified (NOT json:"-")
12.3 Does NOT suggest removing the field from the struct
12.4 Other struct fields retain their json and swagger documentation
12.5 Explains or implies why json:"-" would be wrong here (breaks serialization)

Analyst pass: The skill shows clear uplift on annotation mechanics that require knowing non-obvious swag behaviors: blank import requirement (eval 1), map type limitations (eval 2), swaggertype overrides for time.Time and []byte (eval 3), nested composition syntax (eval 10), and swaggerignore vs json:"-" (eval 12). Evals 4 (Chi+env) and 9 (conditional toggle) scored 5/5 in both conditions — the model already knows how to read env vars and register Chi routes, so these evals test common knowledge rather than skill uplift. Eval 11 shows a consistent miss on the example: struct tag in both conditions, indicating a coverage gap rather than discrimination; the skill should add an explicit example on example: struct tag usage. Future iterations should also target less-known swag behaviors: @extensions, @x- custom properties, multi-file init patterns (--dir flag), and OpenAPI 3.0 output.