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

View File

@@ -0,0 +1,376 @@
# Clean Code Practices
## What is Clean Code?
Code that is:
- **Easy to understand** - reveals intent clearly
- **Easy to change** - modifications are localized
- **Easy to test** - dependencies are injectable
- **Simple** - no unnecessary complexity
## The Human-Centered Approach
Code has THREE consumers:
1. **Users** - get their needs met
2. **Customers** - make or save money
3. **Developers** - must maintain it
Design for all three, but remember: **developers read code 10x more than they write it.**
## Naming Principles
### 1. Consistency & Uniqueness (HIGHEST PRIORITY)
Same concept = same name everywhere. One name per concept.
```typescript
// BAD: Inconsistent names for same concept
getUserById(id)
fetchCustomerById(id)
retrieveClientById(id)
// GOOD: Consistent
getUser(id)
getOrder(id)
getProduct(id)
```
### 2. Understandability
Use domain language, not technical jargon.
```typescript
// BAD: Technical
const arr = users.filter(u => u.isActive);
// GOOD: Domain language
const activeCustomers = users.filter(user => user.isActive);
```
### 3. Specificity
Avoid vague names: `data`, `info`, `manager`, `handler`, `processor`, `utils`
```typescript
// BAD: Vague
class DataManager { }
function processInfo(data) { }
// GOOD: Specific
class OrderRepository { }
function validatePayment(payment) { }
```
### 4. Brevity (but not at cost of clarity)
Short names are good only if meaning is preserved.
```typescript
// BAD: Too cryptic
const usrLst = getUsrs();
// BAD: Unnecessarily long
const listOfAllActiveUsersInTheSystem = getActiveUsers();
// GOOD: Brief but clear
const activeUsers = getActiveUsers();
```
### 5. Searchability
Names should be unique enough to grep/search.
```typescript
// BAD: Common word, hard to search
const data = fetch();
// GOOD: Unique, searchable
const orderSummary = fetchOrderSummary();
```
### 6. Pronounceability
You should be able to say it in conversation.
```typescript
// BAD
const genymdhms = generateYearMonthDayHourMinuteSecond();
// GOOD
const timestamp = generateTimestamp();
```
### 7. Austerity
Avoid unnecessary filler words.
```typescript
// BAD: Redundant
const userData = user; // 'Data' adds nothing
class UserClass { } // 'Class' adds nothing
// GOOD
const user = user;
class User { }
```
---
## Object Calisthenics (9 Rules)
Exercises to improve OO design. Follow strictly during practice, relax slightly in production.
### 1. One Level of Indentation per Method
```typescript
// BAD: Multiple levels
function process(orders: Order[]) {
for (const order of orders) {
if (order.isValid()) {
for (const item of order.items) {
if (item.inStock) {
// process...
}
}
}
}
}
// GOOD: Extract methods
function process(orders: Order[]) {
orders.filter(o => o.isValid()).forEach(processOrder);
}
function processOrder(order: Order) {
order.items.filter(i => i.inStock).forEach(processItem);
}
```
### 2. Don't Use the ELSE Keyword
Use early returns, guard clauses, or polymorphism.
```typescript
// BAD: else
function getDiscount(user: User): number {
if (user.isPremium) {
return 20;
} else {
return 0;
}
}
// GOOD: Early return
function getDiscount(user: User): number {
if (user.isPremium) return 20;
return 0;
}
```
### 3. Wrap All Primitives and Strings
Primitives should be wrapped in domain objects when they have meaning.
```typescript
// BAD: Primitive obsession
function createUser(email: string, age: number) { }
// GOOD: Value objects
class Email {
constructor(private value: string) {
if (!this.isValid(value)) throw new InvalidEmail();
}
private isValid(email: string): boolean { ... }
}
class Age {
constructor(private value: number) {
if (value < 0 || value > 150) throw new InvalidAge();
}
}
function createUser(email: Email, age: Age) { }
```
### 4. First-Class Collections
Any class with a collection should have no other instance variables.
```typescript
// BAD: Collection mixed with other state
class Order {
items: OrderItem[] = [];
customerId: string;
total: number;
}
// GOOD: Collection is its own class
class OrderItems {
constructor(private items: OrderItem[] = []) {}
add(item: OrderItem): void { ... }
total(): Money { ... }
isEmpty(): boolean { ... }
}
class Order {
constructor(
private items: OrderItems,
private customerId: CustomerId
) {}
}
```
### 5. One Dot per Line (Law of Demeter)
Don't chain through object graphs.
```typescript
// BAD: Train wreck
const city = order.customer.address.city;
// GOOD: Tell, don't ask
const city = order.getShippingCity();
```
### 6. Don't Abbreviate
If a name is too long to type, the class is doing too much.
```typescript
// BAD
const custRepo = new CustRepo();
const ord = new Ord();
// GOOD
const customerRepository = new CustomerRepository();
const order = new Order();
```
### 7. Keep All Entities Small
- Classes: < 50 lines
- Methods: < 10 lines
- Files: < 100 lines
If larger, it's probably doing too much. Split it.
### 8. No Classes with More Than Two Instance Variables
Forces small, focused classes.
```typescript
// BAD: Too many variables
class Order {
id: string;
customerId: string;
items: Item[];
total: number;
status: string;
}
// GOOD: Composed of smaller objects
class Order {
constructor(
private id: OrderId,
private details: OrderDetails
) {}
}
class OrderDetails {
constructor(
private customer: Customer,
private lineItems: LineItems
) {}
}
```
### 9. No Getters/Setters/Properties
Objects should have behavior, not just data. Tell objects what to do.
```typescript
// BAD: Data bag with getters
class Account {
getBalance(): number { return this.balance; }
setBalance(value: number) { this.balance = value; }
}
// Caller does the work
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
}
// GOOD: Behavior-rich object
class Account {
withdraw(amount: Money): WithdrawResult {
if (!this.canWithdraw(amount)) {
return WithdrawResult.insufficientFunds();
}
this.balance = this.balance.subtract(amount);
return WithdrawResult.success();
}
}
// Caller tells, object decides
const result = account.withdraw(amount);
```
---
## Comments
### When to Write Comments
**Only write comments to explain WHY, not WHAT or HOW.**
Code explains what and how. Comments explain business reasons, non-obvious decisions, or warnings.
```typescript
// BAD: Explains what (redundant)
// Add 1 to counter
counter++;
// GOOD: Explains why
// Compensate for 0-based indexing in legacy API
counter++;
```
### Prefer Self-Documenting Code
Instead of commenting, rename to make intent clear.
```typescript
// BAD: Comment needed
// Check if user can access premium features
if (user.subscriptionLevel >= 2 && !user.isBanned) { }
// GOOD: Self-documenting
if (user.canAccessPremiumFeatures()) { }
```
---
## Formatting
### Vertical Spacing
- Related code together
- Blank lines between concepts
- Most important/public at top
### Horizontal Spacing
- Consistent indentation
- Space around operators
- Max line length ~80-120 characters
### Storytelling
Code should read top-to-bottom like a story. High-level at top, details below.
```typescript
class OrderProcessor {
// Public API first
process(order: Order): ProcessResult {
this.validate(order);
this.calculateTotals(order);
return this.save(order);
}
// Supporting methods below, in order of appearance
private validate(order: Order): void { ... }
private calculateTotals(order: Order): void { ... }
private save(order: Order): ProcessResult { ... }
}
```

View File

@@ -0,0 +1,334 @@
# Code Smells & Anti-Patterns
## What Are Code Smells?
Indicators that something MAY be wrong. Not bugs, but design problems that make code hard to understand, change, or test.
## The Five Categories
### 1. Bloaters
Code that has grown too large.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Long Method** | > 10 lines | Extract Method |
| **Large Class** | > 50 lines, multiple responsibilities | Extract Class |
| **Long Parameter List** | > 3 parameters | Introduce Parameter Object |
| **Data Clumps** | Same group of variables appear together | Extract Class |
| **Primitive Obsession** | Primitives instead of small objects | Wrap in Value Object |
### 2. Object-Orientation Abusers
Misuse of OO principles.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Switch Statements** | Type checking, large switch/if-else | Replace with Polymorphism |
| **Parallel Inheritance** | Adding subclass requires adding another | Merge Hierarchies |
| **Refused Bequest** | Subclass doesn't use parent methods | Replace Inheritance with Delegation |
| **Alternative Classes** | Different interfaces, same concept | Rename, Extract Superclass |
### 3. Change Preventers
Code that makes changes difficult.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Divergent Change** | One class changed for many reasons | Extract Class (SRP) |
| **Shotgun Surgery** | One change touches many classes | Move Method/Field together |
| **Parallel Inheritance** | (see above) | Merge Hierarchies |
### 4. Dispensables
Code that can be removed.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Comments** | Explaining bad code | Rename, Extract Method |
| **Duplicate Code** | Copy-paste | Extract Method, Pull Up Method |
| **Dead Code** | Unreachable code | Delete |
| **Speculative Generality** | "Just in case" code | Delete (YAGNI) |
| **Lazy Class** | Class that does almost nothing | Inline Class |
### 5. Couplers
Excessive coupling between classes.
| Smell | Symptom | Refactoring |
|-------|---------|-------------|
| **Feature Envy** | Method uses another class's data extensively | Move Method |
| **Inappropriate Intimacy** | Classes know too much about each other | Move Method, Extract Class |
| **Message Chains** | `a.getB().getC().getD()` | Hide Delegate |
| **Middle Man** | Class only delegates | Inline Class |
---
## The Seven Most Common Code Smells
### 1. Long Method
**Symptom:** Method > 10 lines, doing multiple things.
```typescript
// SMELL
function processOrder(order: Order) {
// Validate
if (!order.items.length) throw new Error('Empty');
if (!order.customer) throw new Error('No customer');
// Calculate
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
if (item.discount) {
total -= item.discount;
}
}
// Apply tax
const taxRate = getTaxRate(order.customer.state);
total = total * (1 + taxRate);
// Save
db.orders.insert({ ...order, total });
// Notify
emailService.send(order.customer.email, 'Order confirmed');
}
// REFACTORED
function processOrder(order: Order) {
validateOrder(order);
const total = calculateTotal(order);
saveOrder(order, total);
notifyCustomer(order);
}
```
### 2. Large Class
**Symptom:** Class with many responsibilities, > 50 lines.
```typescript
// SMELL: God class
class User {
// User data
name: string;
email: string;
// Authentication
login() { }
logout() { }
resetPassword() { }
// Preferences
setTheme() { }
setLanguage() { }
// Notifications
sendEmail() { }
sendSMS() { }
// Billing
charge() { }
refund() { }
}
// REFACTORED: Separate classes
class User { name: string; email: string; }
class AuthService { login(); logout(); resetPassword(); }
class UserPreferences { setTheme(); setLanguage(); }
class NotificationService { sendEmail(); sendSMS(); }
class BillingService { charge(); refund(); }
```
### 3. Feature Envy
**Symptom:** Method uses another class's data more than its own.
```typescript
// SMELL: Order envies Customer
class Order {
calculateShipping(customer: Customer): number {
if (customer.country === 'US') {
if (customer.state === 'CA') return 10;
return 15;
}
return 25;
}
}
// REFACTORED: Move to Customer
class Customer {
getShippingCost(): number {
if (this.country === 'US') {
if (this.state === 'CA') return 10;
return 15;
}
return 25;
}
}
class Order {
calculateShipping(): number {
return this.customer.getShippingCost();
}
}
```
### 4. Primitive Obsession
**Symptom:** Using primitives for domain concepts.
```typescript
// SMELL
function createUser(email: string, age: number, zipCode: string) {
// No validation, easy to pass wrong values
if (!email.includes('@')) throw new Error();
if (age < 0) throw new Error();
}
// REFACTORED: Value objects
class Email {
constructor(private value: string) {
if (!value.includes('@')) throw new InvalidEmail();
}
}
class Age {
constructor(private value: number) {
if (value < 0 || value > 150) throw new InvalidAge();
}
}
function createUser(email: Email, age: Age, address: Address) {
// Type system prevents invalid data
}
```
### 5. Switch Statements
**Symptom:** Switching on type, repeated across codebase.
```typescript
// SMELL
function getArea(shape: Shape): number {
switch (shape.type) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return 0.5 * shape.base * shape.height;
}
}
function getPerimeter(shape: Shape): number {
switch (shape.type) { // Same switch again!
case 'circle': return 2 * Math.PI * shape.radius;
// ...
}
}
// REFACTORED: Polymorphism
interface Shape {
getArea(): number;
getPerimeter(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea(): number { return Math.PI * this.radius ** 2; }
getPerimeter(): number { return 2 * Math.PI * this.radius; }
}
```
### 6. Inappropriate Intimacy
**Symptom:** Classes know too much about each other's internals.
```typescript
// SMELL
class Order {
process() {
const inventory = new Inventory();
// Reaching into inventory's internals
for (const item of this.items) {
const stock = inventory.stockLevels[item.sku];
if (stock.quantity < item.quantity) {
throw new Error('Out of stock');
}
inventory.stockLevels[item.sku].quantity -= item.quantity;
}
}
}
// REFACTORED: Tell, don't ask
class Inventory {
reserve(items: OrderItem[]): ReserveResult {
// Inventory manages its own state
for (const item of items) {
if (!this.canReserve(item)) {
return ReserveResult.outOfStock(item);
}
}
this.deductStock(items);
return ReserveResult.success();
}
}
class Order {
process(inventory: Inventory) {
const result = inventory.reserve(this.items);
if (!result.isSuccess()) {
throw new OutOfStockError(result.failedItem);
}
}
}
```
### 7. Speculative Generality
**Symptom:** "Just in case" abstractions that aren't used.
```typescript
// SMELL: Over-engineered for hypothetical needs
interface PaymentProcessor {
process(): void;
rollback(): void;
audit(): void;
generateReport(): void;
scheduleRecurring(): void;
}
class StripeProcessor implements PaymentProcessor {
process() { /* actual code */ }
rollback() { throw new Error('Not implemented'); }
audit() { throw new Error('Not implemented'); }
generateReport() { throw new Error('Not implemented'); }
scheduleRecurring() { throw new Error('Not implemented'); }
}
// REFACTORED: YAGNI
interface PaymentProcessor {
process(): void;
}
class StripeProcessor implements PaymentProcessor {
process() { /* actual code */ }
}
// Add other methods when actually needed
```
---
## Prevention Strategies
1. **Follow Object Calisthenics** - Rules prevent most smells
2. **Practice TDD** - Tests reveal design problems early
3. **Review in pairs** - Fresh eyes catch smells
4. **Refactor continuously** - Don't let smells accumulate
5. **Apply SOLID** - Prevents structural smells
6. **Use static analysis** - Tools catch common issues
---
## When You Find a Smell
1. **Confirm it's a problem** - Not all smells need fixing
2. **Ensure test coverage** - Before refactoring
3. **Refactor in small steps** - Keep tests passing
4. **Commit frequently** - Easy to revert if needed