From nexus-agents
Designs stable, hard-to-misuse interfaces for REST endpoints, MCP tool schemas, module boundaries, and type contracts. Use when defining new or evolving public surfaces.
npx claudepluginhub williamzujkowski/nexus-agentsThis skill is limited to using the following tools:
<!--
Guides stable API and interface design for REST/GraphQL endpoints, module boundaries, type contracts, component props, and frontend-backend separations.
Designs clean, consistent RESTful APIs with versioning strategies, error handling, pagination, rate limiting, and OpenAPI documentation.
Share bugs, ideas, or general feedback.
src/ package boundaries)deprecation-and-migration for the safe-removal pathWith a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract.
Every observable behavior — undocumented quirks, error message text, ordering, timing — becomes a de facto contract. Design implications:
deprecation-and-migration.Avoid forcing consumers to choose between multiple versions of the same dependency or API. Diamond-dependency problems arise when different consumers need different versions of the same thing. Design to extend rather than fork — see CLAUDE.md Anti-Sprawl Policy ("ONE canonical implementation path").
Define the interface before implementing it. The contract IS the spec — implementation follows.
// Define the contract first
export interface ITaskAnalyzer {
analyze(task: Task): TaskAnalysisResult;
}
// Then implement
export class SharedTaskAnalyzer implements ITaskAnalyzer { ... }
Pick one error strategy and use it everywhere in a given surface:
Result<T, E> returned, never thrown. See core/result.ts. Used by all MCP tool handlers, adapter calls, parsers.{ error: { code, message, details? } }. Never mix shapes.Do not mix patterns within one module. Consumers can't predict behavior when some endpoints throw, others return null, others return { error }.
Trust internal code. Validate at system edges where external input enters. From .rules/untrusted-input.md:
| Boundary | Validate |
|---|---|
| MCP tool input | Zod schema (we have one per tool) |
| HTTP route handlers | Zod schema before any business logic |
| External service responses | Zod schema — third-party = untrusted (see UNTRUSTED_INPUT_HARDENING.md) |
| Env var loading | Type-narrow + parse to typed config |
| File reads of user-supplied paths | Path-traversal guard (must be within cwd subtree) |
Internal modules accept already-validated types. Re-validating wastes work and obscures where validation actually lives.
Third-party API responses are untrusted data. A compromised or misbehaving external service can return unexpected types, malicious content, or instruction-like text. Validate the shape AND content before using in any logic, rendering, or decision-making.
// Bad: consumer must check multiple fields and they can be inconsistent
interface TaskStatus {
status: string;
assignee?: string;
completedAt?: Date;
cancelReason?: string;
}
// Good: consumer gets type narrowing
type TaskStatus =
| { type: 'pending' }
| { type: 'in_progress'; assignee: string; startedAt: Date }
| { type: 'completed'; completedAt: Date; completedBy: string }
| { type: 'cancelled'; reason: string; cancelledAt: Date };
interface CreateTaskInput {
title: string;
description?: string;
}
interface Task {
// includes server-generated fields
id: string;
title: string;
description: string | null;
createdAt: Date;
updatedAt: Date;
createdBy: string;
}
type TaskId = string & { readonly __brand: 'TaskId' };
type UserId = string & { readonly __brand: 'UserId' };
// Prevents accidentally passing UserId where TaskId is expected
function getTask(id: TaskId): Promise<Task> { ... }
| Excuse | Counter |
|---|---|
| "We'll document the API later" | Types ARE the documentation. Define them first; comments add color but the type is the contract. |
| "We don't need pagination for now" | You will the moment someone has > 100 items. List endpoints without pagination are a roadmap to performance bugs. |
| "We'll version the API when we need to" | Breaking changes without versioning break consumers silently. Design for additive extension from day one. |
| "Nobody uses that undocumented behavior" | Hyrum's Law: if it's observable, somebody depends on it. Treat every public symbol as a commitment. |
| "We can maintain two versions" | Multiple versions multiply maintenance cost and create diamond-dependency problems. Prefer One-Version + extend. |
| "Internal APIs don't need contracts" | Internal consumers are still consumers. Contracts prevent coupling and enable parallel work. |
| "Validation is duplicated, let me share it" | Validate at boundaries — once. Don't share validation between boundary and internal code; that's how internal trust gets broken. |
deprecation-and-migration discipline/api/createTask instead of POST /api/tasks)any introduced (banned per CLAUDE.md zero-any policy)any, no untyped object passes)src/exports/*.ts barrels (canonical surface), not direct module imports