code-like-gopher — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited code-like-gopher (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when:
Before starting any Go work:
# Check Go version
go version
# Check go.mod version requirement
grep '^go ' go.mod 2>/dev/null | awk '{print $2}'
# Check if golangci-lint is available
command -v golangci-lintgo.mod and use appropriate language features// Go 1.23+ only
for i := range 10 {
fmt.Println(i)
}
// Go 1.22+ - loop variable capture is safe
for i := range items {
go func() {
fmt.Println(i) // safe, no need for tt := tt
}()
}golangci-lint is the standard tool for Go code quality.
# Install if missing
brew install golangci-lint # macOS
# or
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestCheck for existing config files:
.golangci.yml / .golangci.yaml.golangci.toml / .golangci.jsonIf config version is not "2", migrate:
golangci-lint migrate#### Minimal .golangci.yml Config
version: "2"
run:
timeout: 5m
tests: false
linters:
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
- misspell
- unconvert
- unparam
- gosec
- prealloc
- revive
- wrapcheck
settings:
govet:
enable:
- assign
- appends
- bools
- defers
- shadow
- unmarshal
- waitgroup
- lostcancel
- slog
- unreachable
errcheck:
check-type-assertions: true
exclude-functions:
- fmt.Fprintln
- fmt.Fprintf
wrapcheck:
ignore-package-globs:
- encoding/*
- github.com/pkg/*
revive:
enable-all-rules: true
rules:
- name: package-comments
disabled: true
- name: cognitive-complexity
disabled: true
- name: cyclomatic
disabled: true
- name: function-length
disabled: true
- name: line-length-limit
arguments: [120]
- name: add-constant
arguments:
- max-lit-count: "3"
allow-strs: '""'
allow-ints: "0,1,2,10,64"
formatters:
enable:
- gofmt
- gofumpt
- goimports
- golines
settings:
golines:
max-len: 120#### Usage
golangci-lint run ./...
golangci-lint fmt ./...All the details of the configuration file can be found here:
https://github.com/golangci/golangci-lint/blob/HEAD/.golangci.reference.yml
gofmt is your friend, use for formatting the *.go filesgoimports helps you to sort and find required package imports#### Variables
Name variables by what they hold, not their type:
// ❌ Bad
var userString string
var countInt int
var usersMap map[string]*User
var usersList []User
// ✅ Good
var username string
var count int
var users map[string]*User
var users []UserShort variable conventions:
i, j, k - loop indicesn - counter, total, quantityk, v - map key/valuea, b - same-type comparisonss - string valueserr - errorsctx - contextCollections always use plural names:
// ❌ Bad
var userString string
var countInt int
var usersMap map[string]*User
var companiesMap map[string]*Company
var productsMap map[string]*Product
var usersList []User
// ✅ Good
var username string
var count int
var users map[string]*User
var users []User
var companies map[string]*Company
var companies []Company
var products []Product#### Functions vs Methods
Functions - name by the result they return:
// ❌ Bad - describes operation
func Add(a, b int) int {}
// ✅ Good - describes result
func Sum(a, b int) int {}Methods - name by the action they perform:
type User struct {
email string
}
// Getter - no "Get" prefix
func (u User) Email() string {
return u.email
}
// Setter - "Set" prefix
func (u *User) SetEmail(email string) {
u.email = email
}#### Packages
util, common, misc, helpers, tools, models, api, typesor interfaces.
// ❌ Bad
package string_utils
func NewStringSet(...string) map[string]bool {}
// ✅ Good
package stringset
func New(...string) Set {}Constructor naming:
// When package exports one main type, use New()
q := list.New() // returns *list.List
r := ring.New(10) // returns *ring.RingRules:
import . notation, which can simplify tests that must runoutside the package they are testing, but should otherwise be avoided.
programmer. stdlib includes:
strconv (string conversion)
syscall (system call)
fmt (formatted I/O)http package iscalled Server, not HTTPServer. Client code refers to this type as http.Server, so there is no ambiguity.
pkg returns a valueof type pkg.Pkg (or *pkg.Pkg), the function name can often omit the type name without confusion:
start := time.Now() // start is a time.Time
t, err := time.Parse(time.Kitchen, "6:06PM") // t is a time.Time
ctx = context.WithTimeout(ctx, 10*time.Millisecond) // ctx is a context.Context
ip, ok := userip.FromContext(ctx) // ip is a net.IP#### Structs
provide getters and setters by your self, it's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner. The use of upper-case names for export provides the hook to discriminate the field from the method. A setter function, if needed, will likely be called SetOwner. Both names read well in practice:
owner := obj.Owner()
if owner != user {
obj.SetOwner(user)
}#### Interfaces
One-method interfaces use -er suffix:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Stringer interface {
String() string
}
// Combined interfaces
type ReadWriteCloser interface {
Reader
Writer
Closer
}Always wrap errors with context:
// ❌ Bad - loses context
if err != nil {
return err
}
// ✅ Good - stdlib way (Go 1.13+)
if err != nil {
return fmt.Errorf("authenticate user %s: %w", userID, err)
}Use `errors.Is` and `errors.As` for error checking:
// Check error type
if errors.Is(err, os.ErrNotExist) {
// handle not found
}
// Extract error type
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println(pathErr.Path)
}Custom error types:
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// Sentinel errors with context
func GetUser(id string) (*User, error) {
if user == nil {
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
}
return user, nil
}Use instead of config structs:
type Server struct {
port string
logger *slog.Logger
}
type Option func(*Server) error
func WithPort(port string) Option {
return func(s *Server) error {
if port == "" {
return errors.New("port cannot be empty")
}
s.port = port
return nil
}
}
func WithLogger(l *slog.Logger) Option {
return func(s *Server) error {
if l == nil {
return errors.New("logger cannot be nil")
}
s.logger = l
return nil
}
}
func New(opts ...Option) (*Server, error) {
s := &Server{
port: "8080", // default
}
for _, opt := range opts {
if err := opt(s); err != nil {
return nil, fmt.Errorf("apply option: %w", err)
}
}
return s, nil
}
// Usage
server, err := New(
WithPort("3000"),
WithLogger(slog.Default()),
)#### Golden Rule
Only the sender closes the channel, never the receiver.
The sender knows when work is finished; the receiver does not.
func produce(ch chan<- int) {
defer close(ch) // sender closes
for i := range 10 {
ch <- i
}
}
func consume(ch <-chan int) {
for v := range ch { // receiver just reads
fmt.Println(v)
}
}#### Buffered Channels
Prefer buffered channel with capacity 1 for signals:
// Signal/done channel
done := make(chan struct{}, 1)
// Error propagation
errCh := make(chan error, 1)#### Concurrent-Safe Maps
Use sync.Map for concurrent access:
var cache sync.Map
// Store
cache.Store("key", value)
// Load
if v, ok := cache.Load("key"); ok {
// use v
}
// LoadOrStore
actual, loaded := cache.LoadOrStore("key", newValue)bytes.Buffer, sync.Mutex)interface{}// ❌ Bad - context in struct
type Service struct {
ctx context.Context
}
// ✅ Good - context as first param
func (s *Service) Do(ctx context.Context, id string) error {}Compile-time interface checks:
var _ io.Reader = (*MyReader)(nil)
var _ http.Handler = (*MyHandler)(nil)#### Naming
// ❌ Bad - describes input
func TestTitleIllegalChar(t *testing.T) {}
// ✅ Good - describes behavior
func TestTitleEscapesSpecialCharacters(t *testing.T) {}#### Table-Driven Tests
func TestSum(t *testing.T) {
t.Parallel()
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -1, -2},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Go 1.22+ safe
got := Sum(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Sum(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
}
})
}
}brew install pre-commit
pre-commit installMinimal .pre-commit-config.yaml:
repos:
- repo: https://github.com/TekWizely/pre-commit-golang
rev: v1.0.0-rc.1
hooks:
- id: golangci-lint-mod
- id: go-mod-tidy
- id: go-test-modFormat:
[claude]: <verb> <description in lowercase>
- Detail 1
- Detail 2
Fixes #123
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>Example:
[claude]: add user authentication middleware
- Implement JWT validation
- Add rate limiting per user
- Handle token refresh
Fixes #42
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>| Task | Command |
|---|---|
| Check Go version | go version |
| Run linter | golangci-lint run ./... |
| Format code | golangci-lint fmt ./... |
| Run tests | go test -race ./... |
| Tidy modules | go mod tidy |
| Build | go build ./... |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.