* 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.
900 KiB
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. taskqueue — TaskStatus 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. parser — Parse, 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. validator — Validate(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. sliceutil — AddDefaults(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. user — UserProfile 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. db — PingAll(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. protocol — WriteHeader(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. parser — ExtractToken(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. dispatch — ProcessMessage(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. worker — Task.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. router — NewRouter(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. pricing — IsDiscountApplied 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. cache — Cache.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. pipeline — FanIn(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. parser — TryParse 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. notify — NotifyAll(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. inventory — Inventory.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. httputil — HeaderString(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. stats — SuccessRate(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. fetcher — FetchAll(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. dashboard — New(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. protocol — PackAge(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. search — ExtractBefore(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. http — Client.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. billing — ChargesMatch(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. filter — RemoveEmpty(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. middleware — Handle(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. team — NewTeam(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. fileutil — WriteAll(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. config — Merge(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. text — FirstWord(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. net — EncodeAddr(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. testing — AssertPrice(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. server — Server.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. pool — Utilization(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. store — Store.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. scanner — ScanAll(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. metric — PercentChange(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 1–12 (72 assertions) are unchanged from the v1.0.0 run below. Evals 13 (extended from 2 to 4 assertions), 14–16, 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 20–23 (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 20–22 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 1–19 (evals 1–12 from the original Claude Sonnet 4.6 run, unchanged; evals 13–19 freshly run this update); Kimi K3 for evals 20–23 (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-preference — go 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 | ✓ | ✓ |
golang-popular-libraries — v1.0.0
| 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-creatorfor 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-creatorfor 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-required — wire.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-struct — wire.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, ¬Found) 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.