Phase 1 of mathias/skills extraction (infra#62 Track D — homelab next-step plan addendum). Imports ~/dev/.skills/ verbatim (19 skill dirs + SKILLS_INDEX.md) and adds the installation surface: - Taskfile.yml — install / update / list / release / check targets - install.sh — bootstrap installer for hosts without Task. Idempotent symlink wirer; default checkout at ~/.local/share/skills/ on every host; SKILLS_REF env var pins a tag (default: main). - .gitea/workflows/release.yml — auto-tag every push to main by Bump-Type footer (major/minor/patch, default patch). Skipped when commit contains [skip-release]. - README — usage, versioning, contribution flow, secret-hygiene rule. Phase 1 wires Claude Code only (~/.claude/skills/<name> global + <repo>/.claude/skills/<name> per-repo). Phase 2 adds Crush, opencode, antigravity, and gitea-resident agents (cobalt-dingo, agentsquad) once their skill conventions are researched. Public repo, markdown-only — no secrets, no client names. Verified via pre-push grep before initial push. [skip-release]
10 KiB
name, description
| name | description |
|---|---|
| cognitive-load | Measure how much mental effort a codebase demands from developers. Use when identifying hotspots, before architectural decisions, or when code feels hard to understand. |
Cognitive Load Analysis
Overview
Cognitive load is the mental effort required to understand, modify, and reason about code. High cognitive load leads to bugs, slow development, and burnout.
This skill provides a framework for measuring and reducing cognitive load using the Cognitive Load Index (CLI), scored 0–1000.
CLI Score Rating Scale
| CLI Score | Rating | Action |
|---|---|---|
| 0–100 | Excellent | Model codebase |
| 101–250 | Good | Minor improvements |
| 251–400 | Moderate | Address hotspots |
| 401–600 | Concerning | Plan refactoring |
| 601–800 | Poor | Significant refactoring needed |
| 801–999 | Severe | Consider rewrite of affected areas |
The 8 Dimensions
D1: Structural Complexity
Cyclomatic complexity — the number of independent paths through code. Each if, for, switch, case, &&, || adds one.
Thresholds for Go:
- Simple function: < 5 (no issue)
- Moderate: 5–10 (watch closely)
- Complex: 10–15 (consider refactoring)
- Critical: > 15 (must refactor)
Detection:
# Install: go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
gocyclo -over 10 ./...
Go hotspots: Request handlers that mix auth, validation, business logic, and response formatting in one function.
D2: Nesting Depth
Maximum nesting depth — how many levels deep a reader must track.
Thresholds for Go:
- Ideal: 1–2 levels
- Acceptable: 3 levels
- Warning: 4 levels
- Critical: 5+ levels
Fix: Early returns and guard clauses eliminate deep nesting:
// Bad: 4 levels deep
func process(r *http.Request) error {
if r != nil {
if r.Body != nil {
data, err := io.ReadAll(r.Body)
if err == nil {
// actual work at level 4
}
}
}
return nil
}
// Good: guard clauses, 1 level
func process(r *http.Request) error {
if r == nil {
return errors.New("nil request")
}
if r.Body == nil {
return errors.New("no request body")
}
data, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
// actual work at level 1
return nil
}
D3: Volume/Size
Lines of code per file and function. Size alone isn't the issue — hidden responsibilities are.
Thresholds for Go:
- Function: > 50 lines is a signal (Go can go longer, but be honest about why)
- File: > 400 lines often means too many responsibilities
- Package: > 2000 lines total may need splitting
Go note: Table-driven tests inflate LOC legitimately. Distinguish test LOC from production LOC when assessing.
D4: Naming Quality
Identifiers that don't reveal intent force readers to build a mental model from context.
Poor naming signals:
- Single-letter variables outside loop indices:
x,d,tmp - Generic suffixes:
Manager,Handler,Processor,Data,Info,Util - Abbreviations:
usrLst,custRepo,cfg2 - Inconsistency:
getUserById,FetchCustomerByID,retrieveClientByIdfor the same concept
Good naming in Go:
// Bad
func (m *Manager) proc(d interface{}) error { ... }
// Good
func (s *OrderService) PlaceOrder(ctx context.Context, req PlaceOrderRequest) (Order, error) { ... }
D5: Coupling
How many packages/types a given unit depends on. High coupling means a change in one place ripples widely.
Detection:
# Count imports per file
grep -c "^import" *.go
# or examine import blocks manually
Go-specific coupling smells:
- A domain type that imports from
database/sql,net/http, andsmtpsimultaneously - A package that imports more than 10 other internal packages
- Circular imports (Go compiler rejects these, but near-circular is still a smell)
D6: Cohesion
Whether things that belong together are together. Low cohesion means related code is scattered.
Signs of low cohesion:
- A function that calls many unrelated packages
- A package where functions don't share data or purpose
- Utility packages that grow without bound (
util,common,helpers)
D7: Duplication
Repeated logic that must be updated in multiple places when requirements change.
Detection:
# Simple: find identical error messages that suggest copy-paste
grep -r "error:" --include="*.go" | sort | uniq -d
Go note: Apply the Rule of Three — don't extract the first or second duplication. The third is the signal.
D8: Navigability
How easily can a reader find relevant code and understand entry points?
Signs of poor navigability:
- No clear entry point (no
main.go, noNew*constructors) - Package names that don't reflect content
- Files that mix many unrelated types
- Deep directory nesting with unclear boundaries
Go-Specific Cognitive Load Hotspots
Deeply Nested Error Handling
Go's explicit error handling is a feature, but it can create visual noise:
// High cognitive load: multiple nested error paths
func (s *Service) Process(ctx context.Context, id string) error {
u, err := s.store.GetUser(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) {
o, err := s.store.GetOrgByUserID(ctx, id)
if err != nil {
if errors.Is(err, ErrNotFound) {
return ErrEntityNotFound
}
return fmt.Errorf("get org: %w", err)
}
// ... more nesting
}
return fmt.Errorf("get user: %w", err)
}
// ... happy path
}
// Lower cognitive load: extract to focused functions
func (s *Service) Process(ctx context.Context, id string) error {
entity, err := s.resolveEntity(ctx, id)
if err != nil {
return fmt.Errorf("resolve entity: %w", err)
}
return s.processEntity(ctx, entity)
}
Goroutine Soup
Goroutines without clear lifecycle management are high cognitive load:
// High load: who owns these goroutines? When do they stop?
func startWorkers() {
go processQueue()
go cleanupExpired()
go sendNotifications()
}
// Lower load: explicit lifecycle with context and WaitGroup
func startWorkers(ctx context.Context) *WorkerPool {
pool := &WorkerPool{}
pool.wg.Add(3)
go pool.processQueue(ctx)
go pool.cleanupExpired(ctx)
go pool.sendNotifications(ctx)
return pool
}
func (p *WorkerPool) Shutdown() {
p.cancel()
p.wg.Wait()
}
Over-Generalization with Generics
Generics add expressive power but also cognitive load. Use them sparingly:
// High load: generic just because you can
func Transform[T any, U any](items []T, fn func(T) U) []U { ... }
// Lower load: concrete types when you have only one use case
func transformOrders(orders []Order) []OrderSummary { ... }
Extract generics only when you genuinely have multiple type applications.
Init Functions
init() functions run invisibly and create hidden state:
// High load: what order do these run? What state do they set?
func init() {
db = mustConnectDB()
cache = newCache()
}
// Lower load: explicit initialization in main or constructor
func main() {
db := mustConnectDB(cfg.DatabaseURL)
cache := newCache(cfg.CacheSize)
svc := NewService(db, cache)
// ...
}
Long Interfaces
An interface with 10 methods forces implementors to understand all 10 methods before reasoning about any:
// High cognitive load: 10 methods before you can implement
type UserRepository interface {
Create(ctx context.Context, u User) error
GetByID(ctx context.Context, id UserID) (User, error)
GetByEmail(ctx context.Context, email string) (User, error)
Update(ctx context.Context, u User) error
Delete(ctx context.Context, id UserID) error
List(ctx context.Context, filter Filter) ([]User, error)
Count(ctx context.Context, filter Filter) (int64, error)
Exists(ctx context.Context, id UserID) (bool, error)
Lock(ctx context.Context, id UserID) error
Unlock(ctx context.Context, id UserID) error
}
// Lower load: separate by use case
type UserReader interface {
GetByID(ctx context.Context, id UserID) (User, error)
GetByEmail(ctx context.Context, email string) (User, error)
}
type UserWriter interface {
Create(ctx context.Context, u User) error
Update(ctx context.Context, u User) error
Delete(ctx context.Context, id UserID) error
}
When to Measure
- Before starting refactoring work: establish a baseline
- When a package/file is frequently the source of bugs
- When team members consistently report confusion about a subsystem
- Before a major feature addition that will touch complex code
Hotspot Identification Process
-
Size sweep: Find files > 400 LOC and functions > 50 LOC
find . -name "*.go" ! -name "*_test.go" | xargs wc -l | sort -rn | head -20 -
Complexity sweep: Find high cyclomatic complexity
gocyclo -over 10 ./... -
Naming sweep: Find packages with vague names
ls internal/ # Are there `util`, `common`, `helpers`? -
Coupling sweep: Find files with many imports
grep -l "^import" **/*.go | xargs grep -c '"' | sort -t: -k2 -rn | head -10 -
Duplication sweep:
# Look for copied error strings, identical function signatures grep -rn "TODO\|FIXME\|HACK" --include="*.go" # Technical debt markers
Reduction Strategies
| High CLI Dimension | Primary Fix |
|---|---|
| D1: Structural Complexity | Extract function, replace conditional with polymorphism |
| D2: Nesting Depth | Guard clauses, early returns |
| D3: Volume/Size | Extract type, split package |
| D4: Naming | Rename (most impactful, cheapest refactoring) |
| D5: Coupling | Introduce interface, apply DIP |
| D6: Cohesion | Extract type, reorganize package |
| D7: Duplication | Extract shared function (after Rule of Three) |
| D8: Navigability | Reorganize package structure, add package docs |
Cross-References
- Load
refactoringskill for systematic techniques to reduce identified hotspots - Load
solidskill for structural fixes (coupling, cohesion) - Load
clean-codeskill for naming improvements