/architecture
Guides the design of safely disposable code through contracts (traits/interfaces) and dependency inversion. Use when designing new modules, refactoring existing code, or making architectural decisions about component boundaries.
Claude Code
$ npx -y skills add ktnyt/cclsp --skill architecture --agent claude-code
Codex
$ npx skills add ktnyt/cclsp --skill architecture -a codex -y
Cursor
$ npx skills add ktnyt/cclsp --skill architecture -a cursor -y
Gemini CLI
$ npx skills add ktnyt/cclsp --skill architecture -a gemini-cli -y
GitHub Copilot
$ npx skills add ktnyt/cclsp --skill architecture -a github-copilot -y
OpenCode
$ npx skills add ktnyt/cclsp --skill architecture -a opencode -y
Windsurf
$ npx skills add ktnyt/cclsp --skill architecture -a windsurf -y
SKILL.md
architecture.SKILL.mdname: architecture
description: Guides the design of safely disposable code through contracts (traits/interfaces) and dependency inversion. Use when designing new modules, refactoring existing code, or making architectural decisions about component boundaries.
Safely Disposable Code via Contracts
Every implementation should be disposable. The system's correctness is defined by its contracts, not by any particular implementation behind them.
Core Principle
> Define **what** a component does (contract), not **how** it does it (implementation). > Any implementation that satisfies the contract is interchangeable.
A component is "safely disposable" when: 1. Its behavior is fully described by a contract (trait, interface, protocol) 2. No consumer depends on implementation details 3. It can be deleted and rewritten from the contract alone without breaking the system
Workflow
Step 1: Define the Contract
Start with the contract. Write the trait/interface **before** any implementation.
**Rust:**
pub trait UserRepository {
fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError>;
fn save(&self, user: &User) -> Result<(), RepoError>;
}**Go:**
type UserRepository interface {
FindByID(ctx context.Context, id UserID) (*User, error)
Save(ctx context.Context, user *User) error
}**TypeScript:**
interface UserRepository {
findById(id: UserId): Promise<User | null>;
save(user: User): Promise<void>;
}Rules:
- Keep contracts small (1-5 methods)
- Name contracts after the **role**, not the implementation (e.g., `UserRepository`, not `PostgresUserStore`)
- Define contracts where they are **consumed**, not where they are implemented
- Use domain types in signatures, not infrastructure types
Step 2: Define Error Contracts
Errors are part of the contract. Define domain-level error types that hide infrastructure details.
#[derive(Debug, thiserror::Error)]
pub enum RepoError {
#[error("entity not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("internal error")]
Internal(#[source] Box<dyn std::error::Error + Send + Sync>),
}The `Internal` variant wraps infrastructure errors without leaking them into the contract.
Step 3: Implement Against the Contract
Each implementation is a disposable artifact. Write it knowing it can be thrown away.
pub struct PgUserRepository {
pool: PgPool,
}
impl UserRepository for PgUserRepository {
fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError> {
// Postgres-specific code here.
// This entire struct is disposable.
}
fn save(&self, user: &User) -> Result<(), RepoError> {
// ...
}
}Step 4: Depend on Contracts, Not Implementations
Consumers accept the contract, never the concrete type.
pub struct UserService<R: UserRepository> {
repo: R,
}
impl<R: UserRepository> UserService<R> {
pub fn new(repo: R) -> Self {
Self { repo }
}
pub fn get_user(&self, id: UserId) -> Result<Option<User>, RepoError> {
self.repo.find_by_id(id)
}
}In Go, this is implicit — accept the interface:
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}Step 5: Test Against the Contract
Write tests that verify the contract, not the implementation. These tests can be reused across implementations.
// A test suite that works for ANY UserRepository implementation.
fn test_repository_contract(repo: &impl UserRepository) {
let user = User::new("test@example.com");
repo.save(&user).unwrap();
let found = repo.find_by_id(user.id()).unwrap();
assert_eq!(found, Some(user));
}
#[test]
fn pg_repo_satisfies_contract() {
let repo = PgUserRepository::new(test_pool());
test_repository_contract(&repo);
}
#[test]
fn in_memory_repo_satisfies_contract() {
let repo = InMemoryUserRepository::new();
test_repository_contract(&repo);
}Layered Architecture
Organize code so that dependencies always point inward toward the domain:
src/
├── domain/ # Contracts + domain types (zero external deps)
│ ├── model.rs # Domain entities and value objects
│ ├── repo.rs # Repository contracts (traits)
│ └── service.rs # Domain services using contracts
├── infra/ # Disposable implementations
│ ├── pg_repo.rs # Postgres implementation
│ └── http.rs # HTTP handlers
└── main.rs # Wiring (connects contracts to implementations)
- `domain/` defines contracts and types. It imports **nothing** from `infra/`.
- `infra/` implements contracts. It imports from `domain/`.
- `main.rs` wires implementations to contracts.
Disposability Checklist
Before considering a component done, verify:
- [ ] A contract (trait/interface) exists and lives in the domain layer
- [ ] The contract is named after the role, not the technology
- [ ] Errors are domain-level, not infrastructure-level
- [ ] Consumers depend on the contract, not the implementation
- [ ] The implementation can be deleted without changing any consumer code
- [ ] Contract-level tests exist and can run against any implementation
- [ ] No implementation details leak through the contract (e.g., SQL types, HTTP types)
Anti-Patterns
| Anti-Pattern | Problem | Fix | |---|---|---| | Fat contract | 10+ methods, hard to implement | Split into focused contracts | | Leaky contract | Infrastructure types in signatures | Use domain types only | | Concrete dependency | Consumer imports the struct directly | Accept the trait/interface | | God module | One module does everything | Extract contracts and split | | Premature abstraction | Contract with only one possible implementation forever | Wait until there's a reason to abstract |
When NOT to Abstract
Not everything needs a contract. Skip abstraction when:
- The c
Read more
name: architecture description: Guides the design of safely disposable code through contracts (traits/interfaces) and dependency inversion. Use when designing new modules, refactoring existing code, or making architectural decisions about component boundaries.
Safely Disposable Code via Contracts
Every implementation should be disposable. The system's correctness is defined by its contracts, not by any particular implementation behind them.
Core Principle
> Define **what** a component does (contract), not **how** it does it (implementation). > Any implementation that satisfies the contract is interchangeable.
A component is "safely disposable" when: 1. Its behavior is fully described by a contract (trait, interface, protocol) 2. No consumer depends on implementation details 3. It can be deleted and rewritten from the contract alone without breaking the system
Workflow
Step 1: Define the Contract
Start with the contract. Write the trait/interface **before** any implementation.
**Rust:**
pub trait UserRepository {
fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError>;
fn save(&self, user: &User) -> Result<(), RepoError>;
}**Go:**
type UserRepository interface {
FindByID(ctx context.Context, id UserID) (*User, error)
Save(ctx context.Context, user *User) error
}**TypeScript:**
interface UserRepository {
findById(id: UserId): Promise<User | null>;
save(user: User): Promise<void>;
}Rules:
- Keep contracts small (1-5 methods)
- Name contracts after the **role**, not the implementation (e.g., `UserRepository`, not `PostgresUserStore`)
- Define contracts where they are **consumed**, not where they are implemented
- Use domain types in signatures, not infrastructure types
Step 2: Define Error Contracts
Errors are part of the contract. Define domain-level error types that hide infrastructure details.
#[derive(Debug, thiserror::Error)]
pub enum RepoError {
#[error("entity not found: {0}")]
NotFound(String),
#[error("conflict: {0}")]
Conflict(String),
#[error("internal error")]
Internal(#[source] Box<dyn std::error::Error + Send + Sync>),
}The `Internal` variant wraps infrastructure errors without leaking them into the contract.
Step 3: Implement Against the Contract
Each implementation is a disposable artifact. Write it knowing it can be thrown away.
pub struct PgUserRepository {
pool: PgPool,
}
impl UserRepository for PgUserRepository {
fn find_by_id(&self, id: UserId) -> Result<Option<User>, RepoError> {
// Postgres-specific code here.
// This entire struct is disposable.
}
fn save(&self, user: &User) -> Result<(), RepoError> {
// ...
}
}Step 4: Depend on Contracts, Not Implementations
Consumers accept the contract, never the concrete type.
pub struct UserService<R: UserRepository> {
repo: R,
}
impl<R: UserRepository> UserService<R> {
pub fn new(repo: R) -> Self {
Self { repo }
}
pub fn get_user(&self, id: UserId) -> Result<Option<User>, RepoError> {
self.repo.find_by_id(id)
}
}In Go, this is implicit — accept the interface:
func NewUserService(repo UserRepository) *UserService {
return &UserService{repo: repo}
}Step 5: Test Against the Contract
Write tests that verify the contract, not the implementation. These tests can be reused across implementations.
// A test suite that works for ANY UserRepository implementation.
fn test_repository_contract(repo: &impl UserRepository) {
let user = User::new("test@example.com");
repo.save(&user).unwrap();
let found = repo.find_by_id(user.id()).unwrap();
assert_eq!(found, Some(user));
}
#[test]
fn pg_repo_satisfies_contract() {
let repo = PgUserRepository::new(test_pool());
test_repository_contract(&repo);
}
#[test]
fn in_memory_repo_satisfies_contract() {
let repo = InMemoryUserRepository::new();
test_repository_contract(&repo);
}Layered Architecture
Organize code so that dependencies always point inward toward the domain:
src/ ├── domain/ # Contracts + domain types (zero external deps) │ ├── model.rs # Domain entities and value objects │ ├── repo.rs # Repository contracts (traits) │ └── service.rs # Domain services using contracts ├── infra/ # Disposable implementations │ ├── pg_repo.rs # Postgres implementation │ └── http.rs # HTTP handlers └── main.rs # Wiring (connects contracts to implementations)
- `domain/` defines contracts and types. It imports **nothing** from `infra/`.
- `infra/` implements contracts. It imports from `domain/`.
- `main.rs` wires implementations to contracts.
Disposability Checklist
Before considering a component done, verify:
- [ ] A contract (trait/interface) exists and lives in the domain layer
- [ ] The contract is named after the role, not the technology
- [ ] Errors are domain-level, not infrastructure-level
- [ ] Consumers depend on the contract, not the implementation
- [ ] The implementation can be deleted without changing any consumer code
- [ ] Contract-level tests exist and can run against any implementation
- [ ] No implementation details leak through the contract (e.g., SQL types, HTTP types)
Anti-Patterns
| Anti-Pattern | Problem | Fix | |---|---|---| | Fat contract | 10+ methods, hard to implement | Split into focused contracts | | Leaky contract | Infrastructure types in signatures | Use domain types only | | Concrete dependency | Consumer imports the struct directly | Accept the trait/interface | | God module | One module does everything | Extract contracts and split | | Premature abstraction | Contract with only one possible implementation forever | Wait until there's a reason to abstract |
When NOT to Abstract
Not everything needs a contract. Skip abstraction when:
- The c
cclsp is a Model Context Protocol (MCP) server that seamlessly integrates LLM-based coding agents with Language Server Protocol (LSP) servers.
Other skills on cclsp.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about architecture.
- Which coding agents can use architecture?
- architecture installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

