chore: bootstrap skills library — 19 skills + installer + CI auto-tag
Some checks failed
release / tag (push) Has been cancelled
Some checks failed
release / tag (push) Has been cancelled
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]
This commit is contained in:
346
cognitive-load/SKILL.md
Normal file
346
cognitive-load/SKILL.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
name: cognitive-load
|
||||
description: 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:**
|
||||
```bash
|
||||
# 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:
|
||||
```go
|
||||
// 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`, `retrieveClientById` for the same concept
|
||||
|
||||
**Good naming in Go:**
|
||||
```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:**
|
||||
```bash
|
||||
# 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`, and `smtp` simultaneously
|
||||
- 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:**
|
||||
```bash
|
||||
# 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`, no `New*` 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:
|
||||
|
||||
```go
|
||||
// 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:
|
||||
|
||||
```go
|
||||
// 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:
|
||||
|
||||
```go
|
||||
// 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:
|
||||
|
||||
```go
|
||||
// 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:
|
||||
|
||||
```go
|
||||
// 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
|
||||
|
||||
1. **Size sweep:** Find files > 400 LOC and functions > 50 LOC
|
||||
```bash
|
||||
find . -name "*.go" ! -name "*_test.go" | xargs wc -l | sort -rn | head -20
|
||||
```
|
||||
|
||||
2. **Complexity sweep:** Find high cyclomatic complexity
|
||||
```bash
|
||||
gocyclo -over 10 ./...
|
||||
```
|
||||
|
||||
3. **Naming sweep:** Find packages with vague names
|
||||
```bash
|
||||
ls internal/ # Are there `util`, `common`, `helpers`?
|
||||
```
|
||||
|
||||
4. **Coupling sweep:** Find files with many imports
|
||||
```bash
|
||||
grep -l "^import" **/*.go | xargs grep -c '"' | sort -t: -k2 -rn | head -10
|
||||
```
|
||||
|
||||
5. **Duplication sweep:**
|
||||
```bash
|
||||
# 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 `refactoring` skill for systematic techniques to reduce identified hotspots
|
||||
- Load `solid` skill for structural fixes (coupling, cohesion)
|
||||
- Load `clean-code` skill for naming improvements
|
||||
Reference in New Issue
Block a user