feat(hub-guide): expand plugin with 26 best-practice skills, hooks, and references

Transform hub-guide from a single-skill Hub monorepo guide into a
comprehensive programming best-practice plugin covering all situations.

Skills (26):
- Core: engineering-principles, clean-code, clean-architecture,
  design-patterns, testing, error-handling, security, api-design,
  git-workflow, documentation, logging-observability, performance
- Languages: typescript, python, rust, go
- Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs
- Infrastructure: docker, ci-cd, monitoring
- Monorepo: monorepo, hub-guide (existing)

Hooks:
- SessionStart: auto-detect project type and activate relevant skills
- PreToolUse (Write|Edit): inject language-specific rules per file type

Reference files for deep dives:
- clean-architecture/references/solid.md (SOLID + component principles)
- design-patterns/references/catalog.md (full GoF catalog with examples)
- testing/references/mocks.md (test double taxonomy)

Restructure plugin to modern skills/ directory format.
This commit is contained in:
asepharyana
2026-07-25 11:35:16 +07:00
parent ecdce01387
commit e513cddd68
36 changed files with 4950 additions and 16 deletions
+94
View File
@@ -0,0 +1,94 @@
---
name: clean-architecture
description: Apply Clean Architecture, hexagonal architecture, and SOLID principles when designing system boundaries, modules, or microservices. Use when structuring a new service, deciding what a component should own, untangling framework coupling, or whenever the user mentions "clean architecture," "hexagonal architecture," "onion architecture," "ports and adapters," "SOLID," "Dependency Rule," or "architecture boundaries."
---
# Clean Architecture
Keep business rules independent of frameworks, databases, and UI.
## The Dependency Rule
**Source code dependencies must point inward.** Nothing in an inner circle can know about something in an outer circle.
```
┌──────────────────────────────┐
│ Framework / DB / UI / IO │ ← outer: frameworks, drivers, devices
│ ┌──────────────────────────┐ │
│ │ Interface Adapters │ │ ← presenters, controllers, gateways
│ │ ┌──────────────────────┐ │ │
│ │ │ Application (Use Cases) │ │ ← orchestrate business flows
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ Domain / Entities│ │ │ │ ← pure business rules, no deps
│ │ │ └──────────────────┘ │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────┘ │
└──────────────────────────────┘
```
## Key Rules
1. **Domain layer** contains business entities and value objects. Zero framework imports. Zero database imports. Pure types and functions.
2. **Application layer** contains use cases — orchestrate domain objects to fulfill business flows. Depends only on domain. Declares ports (interfaces) for IO.
3. **Interface adapters** translate between use cases and the outside world — controllers, presenters, gateways. Depends on application layer + frameworks.
4. **Infrastructure/Framework layer** implements the ports declared by the application layer — database repos, HTTP clients, message queues.
5. **Screaming Architecture:** the project structure should scream "this is a [domain context]" — not "this is a Spring/Next.js/Django project."
## How to Check
- Can you swap the database without changing business logic? If not, boundary is violated.
- Can you unit-test a use case without spinning up a framework? If not, your use case depends on infrastructure.
- Do business entities import anything from the web framework or ORM? If so, revert that dependency.
## Practical Patterns
### Port-Adapter
```typescript
// Domain/Application port (declared here, implemented outside)
interface UserRepository {
findById(id: string): Promise<User | null>;
}
// Infrastructure adapter (implemented in infra layer)
class PostgresUserRepository implements UserRepository { ... }
```
### Use Case
```typescript
class CreateOrderUseCase {
constructor(private readonly repo: OrderRepository) {}
async execute(input: CreateOrderInput): Promise<Order> {
const order = Order.create(input.items, input.customerId);
return this.repo.save(order);
}
}
```
### Dependency Injection
Wire dependencies at the composition root — never in use cases or domain.
```typescript
// Composition Root
const orderRepo = new PostgresOrderRepository(db);
const createOrder = new CreateOrderUseCase(orderRepo);
```
## SOLID (Quick Ref)
- **SRP:** A class has one reason to change (one actor).
- **OCP:** Open for extension, closed for modification (polymorphism + strategy).
- **LSP:** Subtypes must be substitutable for their base types.
- **ISP:** Don't depend on interfaces you don't use (keep interfaces focused).
- **DIP:** Depend on abstractions, not concretions. Business rules don't import frameworks.
## Deeper Reference
When the task calls for it, load:
- **[references/solid.md](references/solid.md)** — Full SOLID treatment (SRP, OCP, LSP, ISP, DIP). Component principles (REP, CCP, CRP, ADP, SDP, SAP). Examples for each principle, historical evolution, and practical tests for violations.
## Anti-patterns
- ❌ Business logic in route handlers or controllers
- ❌ ORM entities directly exposed to the UI
- ❌ Database queries mixed into use cases
- ❌ Framework decorators on domain entities
- ❌ "Everything is a CRUD" — missing use case layer
@@ -0,0 +1,166 @@
# SOLID Principles — Deep Reference
## Single Responsibility Principle (SRP)
> "A class should have one, and only one, reason to change." — Robert C. Martin
**Evolution of the definition:**
- 2000 (PPP): "one reason to change"
- 2008 (Clean Code): class does one thing
- 2017 (Clean Architecture): "responsible to one, and only one, **actor**" — where actor is a person or tightly coupled group (e.g., accounting dept, HR dept, DevOps team)
### Practical Test
If you cannot describe a module's responsibility in one sentence without "and," it violates SRP.
```typescript
// ❌ Two actors: Accounting (calculatePay) + HR (save)
class Employee {
calculatePay(): Money { ... }
save(): void { ... }
}
// ✅ Separated by actor
class EmployeePaymentCalc { calculatePay(emp: Employee): Money }
class EmployeeRepository { save(emp: Employee): void }
```
### When SRP Is Violated
- Mixed persistence + business logic in the same class
- A controller that validates, orchestrates, AND formats the response
- A module that imports from both `domain/` and `infra/` packages
---
## Open/Closed Principle (OCP)
> "Software entities should be open for extension, closed for modification." — Bertrand Meyer
New behavior is added through **new code** (new classes, new modules), not by **editing existing, tested code**.
### Strategy Pattern (canonical OCP)
```typescript
// ❌ Closed for extension without modification
function calculateDiscount(type: string, amount: number) {
if (type === 'none') return 0;
if (type === 'seasonal') return amount * 0.1;
if (type === 'loyalty') return amount * 0.2;
}
// ✅ Open for extension — add new strategy, never touch this code
interface DiscountStrategy { apply(amount: number): number;
class SeasonalDiscount implements DiscountStrategy { apply(a) { return a * 0.1 } }
class LoyaltyDiscount implements DiscountStrategy { apply(a) { return a * 0.2 } }
class DiscountCalculator {
constructor(private strategies: DiscountStrategy[]) {}
calculate(amount: number) { return this.strategies.reduce((acc, s) => acc + s.apply(amount), 0); }
}
```
### OCP Warning Signs
- `if/else` or `switch` chains on a type/enum field
- Feature toggles mixed into business logic (use plugin architecture)
- Every new feature touches 5+ existing files
---
## Liskov Substitution Principle (LSP)
> "Objects of a superclass shall be replaceable with objects of its subclasses without breaking the system." — Barbara Liskov (1987)
**Revised (2020s):** "Subtypes must be substitutable for their base types." — applies to interfaces, protocols, and type parameters, not just class inheritance.
### The Square-Rectangle Problem (classic violation)
```typescript
class Rectangle { setWidth(w): void; setHeight(h): void }
class Square extends Rectangle {
setWidth(w) { super.setWidth(w); super.setHeight(w); } // Breaks caller's expectation
}
```
### Rules for Substitutability
1. **Preconditions cannot be strengthened** in the subtype — subtype must accept everything the base accepts.
2. **Postconditions cannot be weakened** — subtype must guarantee at least what the base guarantees.
3. **Invariants must be preserved** — the base class's invariants must hold in the subtype.
4. **History constraint** (Meyer): subtype methods cannot introduce state changes the base type wouldn't allow.
### LSP in Practice
```typescript
// Violation: PostgresUserRepo expects a table name, InMemoryUserRepo doesn't — not substitutable
interface UserRepository {
find(id: string): User;
}
class PostgresUserRepo implements UserRepository {
constructor(private table: string) {} // extra constraint
}
```
---
## Interface Segregation Principle (ISP)
> "No client should be forced to depend on methods it does not use." — Robert C. Martin
Fat interfaces force implementors to stub out methods they don't need.
```typescript
// ❌ Fat interface — forces every worker to implement onError, even if they never fail
interface Worker { work(): void; eat(): void; onError(e: Error): void }
// ✅ Segregated — each interface has one job
interface Workable { work(): void }
interface Eatable { eat(): void }
interface ErrorHandler { onError(e: Error): void }
```
### When ISP Is Violated
- A single interface has methods from different concerns (CRUD + reporting + admin)
- Classes implement interface methods as `throw new UnsupportedOperationException`
- Interface methods are unused in 80% of callers (consider splitting input vs output ports)
---
## Dependency Inversion Principle (DIP)
> "Abstractions should not depend on details. Details should depend on abstractions." — Robert C. Martin
**Not to be confused with Dependency Injection** (which is one way to implement DIP).
### High-level policy should not import low-level detail
```typescript
// ❌ High-level module depends on low-level detail
class CreateOrderUseCase {
private db = new PostgresConnection(); // violates DIP
}
// ✅ Both depend on abstraction
interface OrderRepository { save(order: Order): Promise<void> }
class CreateOrderUseCase {
constructor(private repo: OrderRepository) {} // depends on abstraction
}
class PostgresOrderRepo implements OrderRepository {} // detail depends on abstraction
```
### The Dependency Rule (Clean Architecture)
Source code dependencies point **inward** — nothing in an inner circle knows about something in an outer circle:
- Domain → no imports from framework/infra/db
- Application → imports domain, declares ports (interfaces)
- Infrastructure → implements ports
- Framework → wires everything at the composition root
---
## Component Principles (for larger systems)
### Cohesion Principles
| Principle | Statement |
|-----------|-----------|
| **REP** (Reuse-Release Equivalence) | The unit of reuse is the unit of release |
| **CCP** (Common Closure Principle) | Classes that change together belong together |
| **CRP** (Common Reuse Principle) | Don't depend on things you don't use |
### Coupling Principles
| Principle | Statement |
|-----------|-----------|
| **ADP** (Acyclic Dependencies Principle) | No cycles in the dependency graph |
| **SDP** (Stable Dependencies Principle) | Depend in the direction of stability |
| **SAP** (Stable Abstractions Principle) | Stable components should be abstract |