refactor(plugin)!: ship as a Claude Code plugin, one dir link per harness
Skills move to skills/<name>/SKILL.md and the repo gains both manifests, so it is now a marketplace hosting exactly one plugin whose root is the repo root (source: "."). Claude Code installs it from git; no symlinks involved. Why: install.sh wired the library into every consuming repo as an ABSOLUTE symlink to $CHECKOUT_DIR. The checkout moved three times, most recently when local-dev@1966d5a dropped the embedded .skills/ copy, and nothing migrated the links — 243 dangling links across 12 repos, hidden from git by the **/.claude/skills/ ignore rule. Worse, ~/dev spans two path roots (/Users/mathias on the iCloud-shared Macs, /home/mathias on koala), so an absolute link stored there is broken on one side by construction. Re-running the installer cannot fix that; only removing the mechanism can. install.sh keeps the harnesses with no plugin system and drops from 42 links per harness to one directory link, via the convergent ~/.agents/skills convention that Pi and OpenCode both read: ~/.agents/skills -> $CHECKOUT_DIR/skills ~/.config/crush/skills -> ~/.agents/skills ~/.config/opencode/skills -> ~/.agents/skills wire_claude_repo is deleted outright rather than gated behind a flag — on a mixed-root fleet it cannot be made correct. Antigravity and Vibe targets go too; both unused. CHECKOUT_DIR keeps its host-local default deliberately: pointing it into ~/dev would put a git working tree inside iCloud with two concurrent writers. Taskfile drops five near-identical wiring loops (they had already drifted) and delegates to install.sh. `task check` gains frontmatter validation, a stray-root-entry gate, and manifest/tag version parity — the loose secret-hygiene.md sat unloadable for months because nothing checked layout. Verified: claude plugin validate . passes clean; a probe skill loaded as skills:zzprobe through --plugin-dir; task check green at 42 skills. Refs: #23 Bump-Type: minor Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BeAp5LTscnz2W7Ubt4eJ6m
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
# Software Architecture
|
||||
|
||||
## The Goal of Architecture
|
||||
|
||||
Enable the development team to:
|
||||
1. **Add** features with minimal friction
|
||||
2. **Change** existing features safely
|
||||
3. **Remove** features cleanly
|
||||
4. **Test** features in isolation
|
||||
5. **Deploy** independently when possible
|
||||
|
||||
## Architectural Principles
|
||||
|
||||
### 1. Vertical Boundaries (Features/Slices)
|
||||
|
||||
Organize by **feature**, not by technical layer.
|
||||
|
||||
```
|
||||
BAD: Layer-first
|
||||
src/
|
||||
controllers/
|
||||
UserController.ts
|
||||
OrderController.ts
|
||||
services/
|
||||
UserService.ts
|
||||
OrderService.ts
|
||||
repositories/
|
||||
UserRepository.ts
|
||||
OrderRepository.ts
|
||||
|
||||
GOOD: Feature-first
|
||||
src/
|
||||
users/
|
||||
UserController.ts
|
||||
UserService.ts
|
||||
UserRepository.ts
|
||||
orders/
|
||||
OrderController.ts
|
||||
OrderService.ts
|
||||
OrderRepository.ts
|
||||
```
|
||||
|
||||
**Why:** Changes to "users" feature stay in `users/`. High cohesion within features.
|
||||
|
||||
### 2. Horizontal Boundaries (Layers)
|
||||
|
||||
Separate concerns into layers with clear dependencies.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ Presentation │ UI, Controllers, CLI
|
||||
├──────────────────────────────────────┤
|
||||
│ Application │ Use Cases, Orchestration
|
||||
├──────────────────────────────────────┤
|
||||
│ Domain │ Business Logic, Entities
|
||||
├──────────────────────────────────────┤
|
||||
│ Infrastructure │ Database, APIs, External
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3. The Dependency Rule
|
||||
|
||||
**Dependencies point INWARD.**
|
||||
|
||||
```
|
||||
Infrastructure → Application → Domain
|
||||
↓ ↓ ↓
|
||||
(outer) (middle) (inner)
|
||||
```
|
||||
|
||||
- Inner layers know NOTHING about outer layers
|
||||
- Domain has zero dependencies on infrastructure
|
||||
- Use interfaces to invert dependencies
|
||||
|
||||
```typescript
|
||||
// Domain defines the interface (inner)
|
||||
interface UserRepository {
|
||||
save(user: User): Promise<void>;
|
||||
findById(id: UserId): Promise<User | null>;
|
||||
}
|
||||
|
||||
// Infrastructure implements it (outer)
|
||||
class PostgresUserRepository implements UserRepository {
|
||||
save(user: User): Promise<void> {
|
||||
// SQL here
|
||||
}
|
||||
}
|
||||
|
||||
// Domain service uses the interface
|
||||
class UserService {
|
||||
constructor(private repo: UserRepository) {} // Depends on abstraction
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Contracts
|
||||
|
||||
Interfaces define boundaries between components.
|
||||
|
||||
```typescript
|
||||
// The contract
|
||||
interface PaymentGateway {
|
||||
charge(amount: Money, card: CardDetails): Promise<ChargeResult>;
|
||||
refund(chargeId: string): Promise<RefundResult>;
|
||||
}
|
||||
|
||||
// Multiple implementations possible
|
||||
class StripeGateway implements PaymentGateway { }
|
||||
class PayPalGateway implements PaymentGateway { }
|
||||
class MockGateway implements PaymentGateway { } // For tests
|
||||
```
|
||||
|
||||
### 5. Cross-Cutting Concerns
|
||||
|
||||
Concerns that span multiple features: logging, auth, validation, error handling.
|
||||
|
||||
**Options:**
|
||||
- Middleware/interceptors
|
||||
- Decorators
|
||||
- Aspect-oriented approaches
|
||||
- Base classes (use sparingly)
|
||||
|
||||
```typescript
|
||||
// Middleware approach
|
||||
class LoggingMiddleware {
|
||||
handle(request: Request, next: Handler): Response {
|
||||
console.log(`Request: ${request.path}`);
|
||||
const response = next(request);
|
||||
console.log(`Response: ${response.status}`);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Conway's Law
|
||||
|
||||
> "Organizations design systems that mirror their communication structure."
|
||||
|
||||
**Implication:** Team structure affects architecture. Align both intentionally.
|
||||
|
||||
---
|
||||
|
||||
## Common Architectural Styles
|
||||
|
||||
### Layered Architecture
|
||||
|
||||
Traditional layers: Presentation → Business → Persistence
|
||||
|
||||
**Pros:** Simple, well-understood
|
||||
**Cons:** Can become a "big ball of mud" without discipline
|
||||
|
||||
### Hexagonal Architecture (Ports & Adapters)
|
||||
|
||||
Domain at center, adapters around the edges.
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ HTTP Adapter │
|
||||
└─────────┬───────────┘
|
||||
│
|
||||
┌─────────────────▼─────────────────┐
|
||||
│ DOMAIN │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Business Logic │ │
|
||||
│ │ Use Cases │ │
|
||||
│ └─────────────────────────┘ │
|
||||
└─────────────────┬─────────────────┘
|
||||
│
|
||||
┌─────────▼───────────┐
|
||||
│ Database Adapter │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
**Ports:** Interfaces defined by the domain
|
||||
**Adapters:** Implementations that connect to the outside world
|
||||
|
||||
### Clean Architecture
|
||||
|
||||
Similar to Hexagonal, with explicit layers:
|
||||
|
||||
1. **Entities** - Enterprise business rules
|
||||
2. **Use Cases** - Application business rules
|
||||
3. **Interface Adapters** - Controllers, Presenters, Gateways
|
||||
4. **Frameworks & Drivers** - Web, DB, External interfaces
|
||||
|
||||
---
|
||||
|
||||
## Feature-Driven Structure (Frontend)
|
||||
|
||||
```
|
||||
src/
|
||||
features/
|
||||
auth/
|
||||
components/
|
||||
LoginForm.tsx
|
||||
SignupForm.tsx
|
||||
hooks/
|
||||
useAuth.ts
|
||||
services/
|
||||
authService.ts
|
||||
types/
|
||||
auth.types.ts
|
||||
index.ts # Public API
|
||||
checkout/
|
||||
components/
|
||||
hooks/
|
||||
services/
|
||||
types/
|
||||
index.ts
|
||||
shared/
|
||||
components/ # Truly shared UI
|
||||
hooks/ # Truly shared hooks
|
||||
utils/ # Truly shared utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Feature-Driven Structure (Backend)
|
||||
|
||||
```
|
||||
src/
|
||||
modules/
|
||||
users/
|
||||
domain/
|
||||
User.ts
|
||||
UserRepository.ts # Interface
|
||||
application/
|
||||
CreateUser.ts # Use case
|
||||
GetUser.ts # Use case
|
||||
infrastructure/
|
||||
PostgresUserRepo.ts
|
||||
presentation/
|
||||
UserController.ts
|
||||
UserDTO.ts
|
||||
orders/
|
||||
domain/
|
||||
application/
|
||||
infrastructure/
|
||||
presentation/
|
||||
shared/
|
||||
domain/ # Shared value objects
|
||||
infrastructure/ # Shared infra utilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The Walking Skeleton
|
||||
|
||||
Start with a minimal end-to-end slice:
|
||||
|
||||
1. **Thinnest possible feature** that touches all layers
|
||||
2. **Deployable** from day one
|
||||
3. **Proves the architecture** works
|
||||
|
||||
Example walking skeleton for e-commerce:
|
||||
- User can view ONE product (hardcoded)
|
||||
- User can add it to cart
|
||||
- User can "checkout" (just logs)
|
||||
|
||||
From there, flesh out each feature fully.
|
||||
|
||||
---
|
||||
|
||||
## Testing Architecture
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ E2E / Acceptance Tests │ Few, slow, high confidence
|
||||
├────────────────────────────────────────────┤
|
||||
│ Integration Tests │ Some, medium speed
|
||||
├────────────────────────────────────────────┤
|
||||
│ Unit Tests │ Many, fast, isolated
|
||||
└────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Test by layer:**
|
||||
- **Domain:** Unit tests (most tests here)
|
||||
- **Application:** Integration tests with mocked infra
|
||||
- **Infrastructure:** Integration tests with real dependencies
|
||||
- **E2E:** Critical paths only
|
||||
|
||||
---
|
||||
|
||||
## Architecture Decision Records (ADRs)
|
||||
|
||||
Document significant decisions:
|
||||
|
||||
```markdown
|
||||
# ADR 001: Use PostgreSQL for persistence
|
||||
|
||||
## Status
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
We need a database. Options: PostgreSQL, MongoDB, MySQL
|
||||
|
||||
## Decision
|
||||
PostgreSQL for:
|
||||
- ACID compliance
|
||||
- Team familiarity
|
||||
- JSON support for flexibility
|
||||
|
||||
## Consequences
|
||||
- Need PostgreSQL expertise
|
||||
- Schema migrations required
|
||||
- Excellent query capabilities
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Red Flags in Architecture
|
||||
|
||||
- **Circular dependencies** between modules
|
||||
- **Domain depending on infrastructure**
|
||||
- **Framework code in business logic**
|
||||
- **No clear boundaries** between features
|
||||
- **Shared mutable state** across modules
|
||||
- **"Util" or "Common" packages** that grow forever
|
||||
- **Database schema driving domain model**
|
||||
Reference in New Issue
Block a user