chore: bootstrap skills library — 19 skills + installer + CI auto-tag
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:
Mathias
2026-05-24 14:59:54 +02:00
commit d6a71e370e
33 changed files with 8688 additions and 0 deletions

368
refactoring/SKILL.md Normal file
View File

@@ -0,0 +1,368 @@
---
name: refactoring
description: Systematic refactoring methodology based on Martin Fowler's catalog. Apply during the REFACTOR phase of TDD, never while making code changes that modify behavior.
---
# Refactoring
## Core Principle
> "Refactoring is a controlled technique for improving the design of existing code. Its essence is applying a series of small behavior-preserving transformations." — Martin Fowler
**The First Rule:** Never refactor without tests. Tests are your safety net. If tests don't exist, write them first (load `tdd` skill).
**The Second Rule:** Each refactoring step must keep tests green. If a step breaks tests, undo it.
**Never mix refactoring and behavior change.** Separate commits for each.
## When to Refactor
- After the GREEN phase of TDD (REFACTOR phase)
- When code smell makes a feature hard to add
- When the Boy Scout Rule demands it — you're in the area, leave it better
- When technical debt is blocking velocity
**When NOT to refactor:**
- Code that works and won't change
- Code being replaced soon
- When you don't have tests
## The Process
### Step 1: Ensure Test Coverage
```bash
go test ./... # All tests pass first
go test -race ./... # Race detector clean
```
If tests don't exist, write them before refactoring. See the `tdd` skill.
### Step 2: Identify the Target
Load the `code-review` skill to detect smells. Prioritize:
1. **Architectural smells** — Shotgun Surgery, Divergent Change, God Objects
2. **Design smells** — Long Methods, Feature Envy, Primitive Obsession
3. **Readability smells** — naming, comments, duplication
### Step 3: Apply Techniques in Small Steps
Each technique below is one small step. Make the change. Run tests. Commit if green. Repeat.
### Step 4: Verify After Each Step
```bash
go test ./...
go test -race ./...
```
If red: undo the step (git checkout), understand why, try a smaller step.
## Technique Catalog
### Composing Methods
**Extract Function**
When: a code block can be named meaningfully; a function does multiple things.
```go
// Before
func processOrder(o *Order) {
// validate
if o.Items == nil { panic("no items") }
// calculate
total := 0.0
for _, item := range o.Items {
total += item.Price
}
// save
db.Save(o, total)
}
// After: each step extracted
func processOrder(o *Order) {
validateOrder(o)
total := calculateTotal(o.Items)
saveOrder(o, total)
}
```
Cross-reference: when naming extracted functions, load `clean-code` skill for naming conventions.
**Inline Function**
When: a function's body is as clear as its name; a function only does what its name says.
**Extract Variable**
When: a complex expression is hard to read inline.
```go
// Before
if user.Subscription.Level >= 2 && !user.IsBanned && user.VerifiedAt != nil { ... }
// After
canAccessPremium := user.Subscription.Level >= 2 && !user.IsBanned && user.VerifiedAt != nil
if canAccessPremium { ... }
```
**Replace Temp with Query**
When: a temporary variable holds the result of an expression used once.
### Moving Features Between Types
**Move Method**
When: Feature Envy detected — a method uses another type's data more than its own.
```go
// Before: Order envies Customer
func (o *Order) getShippingCost() float64 {
if o.Customer.Country == "SE" { return 0 }
return 50
}
// After: moved to Customer
func (c *Customer) ShippingCost() float64 {
if c.Country == "SE" { return 0 }
return 50
}
```
**Extract Type**
When: a cluster of methods and fields have a different responsibility than the rest of the type.
**Inline Type**
When: a type isn't doing enough to justify its existence (Lazy Class smell).
**Hide Delegate**
When: callers access objects through chains (`a.B().C()`).
**Remove Middle Man**
When: a type only delegates without adding value.
### Organizing Data
**Replace Data Value with Object**
When: Primitive Obsession detected — a string is being used for email, user ID, currency.
```go
// Before
type User struct {
Email string
}
// After: Email type carries validation and prevents misuse
type Email struct {
value string
}
func NewEmail(s string) (Email, error) {
if !strings.Contains(s, "@") {
return Email{}, fmt.Errorf("invalid email: %q", s)
}
return Email{value: s}, nil
}
type User struct {
Email Email
}
```
**Replace Magic Number with Constant**
```go
// Before
if retries > 3 { ... }
// After
const maxRetries = 3
if retries > maxRetries { ... }
```
### Simplifying Conditionals
**Decompose Conditional**
When: complex conditionals obscure intent.
```go
// Before
if plan.ExpiresAt.Before(time.Now()) && plan.GracePeriodDays > 0 { ... }
// After
if plan.IsExpiredWithGrace() { ... }
```
**Replace Nested Conditional with Guard Clauses**
When: deep nesting; use early returns instead.
```go
// Before: arrow code
func process(o *Order) error {
if o != nil {
if len(o.Items) > 0 {
if o.Customer != nil {
// actual logic
}
}
}
return nil
}
// After: guard clauses
func process(o *Order) error {
if o == nil {
return errors.New("nil order")
}
if len(o.Items) == 0 {
return errors.New("empty order")
}
if o.Customer == nil {
return errors.New("no customer")
}
// actual logic
return nil
}
```
**Replace Conditional with Polymorphism**
When: repeated switch/if-else on a type string. Replace with an interface.
See the `solid` skill, OCP section for full example.
**Introduce Null Object**
When: repeated nil checks for a dependency.
### Simplifying Function Calls
**Rename Method/Variable**
The most powerful refactoring. When you understand what something does, give it a name that reflects that understanding.
Cross-reference: load `clean-code` skill for naming conventions.
**Introduce Parameter Object**
When: Long Parameter List smell — multiple parameters that always appear together.
```go
// Before
func Search(query string, page, pageSize int, sortBy string, ascending bool) Results { ... }
// After
type SearchOptions struct {
Query string
Page int
PageSize int
SortBy string
Ascending bool
}
func Search(opts SearchOptions) Results { ... }
```
**Separate Query from Modifier (Command-Query Separation)**
When: a function both changes state and returns data.
```go
// Bad: modifies AND returns
func (s *Stack) PopAndReturn() (Item, bool) {
// removes and returns
}
// Good: separate
func (s *Stack) Peek() (Item, bool) { ... } // query only
func (s *Stack) Pop() bool { ... } // command only
```
### Dealing with Generalization
**Extract Interface**
When: you have multiple implementations or want to enable testing with a test double.
```go
// Before: depends on concrete type
type Service struct {
db *postgres.DB
}
// After: depends on interface
type Store interface {
Save(ctx context.Context, u User) error
}
type Service struct {
store Store
}
```
**Replace Inheritance with Delegation (Go: Embedding → Explicit Delegation)**
When: embedding is used but only some methods of the embedded type are needed.
## Refactoring Sequence Dependencies
Apply in this order to avoid breaking changes:
1. **Extract Variable** → then Extract Function → then Move Function
2. **Encapsulate Field** → before other data refactorings
3. **Extract Function** → before Move Function
4. **Rename** → before Extract Interface
## Go-Specific Refactoring Notes
### Extracting interfaces
In Go, define the interface at the point of use, not at the implementation:
```go
// The service package defines what it needs
package service
type UserStore interface {
GetByID(ctx context.Context, id UserID) (User, error)
}
```
### Error handling during refactoring
When extracting functions, preserve the error wrapping chain:
```go
// Extracted function must wrap errors with its context
func validateOrder(o *Order) error {
if len(o.Items) == 0 {
return errors.New("order has no items")
}
return nil
}
// Caller wraps the context
if err := validateOrder(o); err != nil {
return fmt.Errorf("process order: %w", err)
}
```
### Struct options pattern (for long parameter lists)
Go idiom for optional parameters:
```go
type ServerOption func(*Server)
func WithTimeout(d time.Duration) ServerOption {
return func(s *Server) { s.timeout = d }
}
func NewServer(addr string, opts ...ServerOption) *Server {
s := &Server{addr: addr, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
```
## Refactoring Commit Strategy
Refactoring commits should be separate from feature commits:
- `refactor: extract validateOrder from processOrder`
- `refactor: replace string email with Email value type`
- `refactor: rename UserData to UserProfile`
Never combine: `feat: add discount + refactor: extract discount calculator`
## Verification Checklist
After each refactoring session:
- [ ] `go test ./...` passes
- [ ] `go test -race ./...` passes
- [ ] No behavior change (same inputs produce same outputs)
- [ ] Committed with a `refactor:` prefix commit message
- [ ] Smells addressed are documented (if doing a review)
## Cross-References
- When applying Extract Function: load `clean-code` skill for naming conventions
- When introducing interfaces: load `solid` skill for DIP and ISP guidance
- When detecting smells: load `code-review` skill
- Source: Martin Fowler, *Refactoring: Improving the Design of Existing Code* (2nd ed.)
- Full catalog: https://refactoring.guru/refactoring