kramme-cc-workflow/skills/kramme:code:api-design/SKILL.md
(experimental) Design stable APIs and module boundaries. Covers contract-first approach, Hyrum's Law, validation placement (at boundaries, not between internal functions), consistent error shapes with HTTP status mapping, naming conventions, and TypeScript patterns for interface stability. Use when adding HTTP endpoints, public modules, SDK surfaces, or any interface with external or cross-team callers. Includes a Design It Twice mode (opt-in via --design-twice or the phrase 'design it twice') that drafts radically different shapes — in parallel via sub-agents on Claude Code, sequentially elsewhere — before committing to one.
npx skillsauth add abildtoft/kramme-cc-workflow kramme:code:api-designInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Design stable APIs and module boundaries with a contract-first workflow. Decide the shape, validation points, error cases, and naming before writing the handler, so the interface does not have to be rediscovered or reshaped once callers exist — the contract is surface area you can never take back.
"With 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."
Implication: every observable behavior is part of the contract — including bugs, timing quirks, ordering of fields, and undocumented side effects. You cannot "fix" an observable behavior later without breaking at least one caller. Design the contract now as if every incidental detail is permanent, because it is.
Before sketching any type or route, emit a SIMPLICITY CHECK marker stating the smallest coherent interface that satisfies the requirement. Only expand beyond that if a concrete requirement forces it.
SIMPLICITY CHECK: <the smallest interface that meets the requirement>
If the interface you end up designing is not the smallest version, write a second line explaining what forced the expansion. If there is no forcing requirement, ship the smaller surface.
Design the contract before the handler. The contract is: input type, output type, error cases, HTTP status mapping, and naming. Write it down — as a TypeScript type, an OpenAPI stub, a comment block, whatever the project reads — before writing implementation code.
Signs the contract is not first:
If any of these are happening, stop. Write the contract, then continue.
Validation belongs at the points where untrusted data enters your trusted code — once — and nowhere else; internal functions trust their inputs. The full trust-boundary doctrine (what counts as a boundary, the validate-once pattern, third-party responses) lives in /kramme:code:harden-security.
Before applying Rules 3–5, scan the project's existing API surfaces for an established error shape, naming style, or pagination envelope. If a consistent convention exists, match it — consistency with existing callers beats the canonical shapes below (Hyrum's Law has already locked the existing shape in). The canonical shapes in Rules 3–5 apply only to greenfield surfaces with no established convention.
Every error returned from the API uses the same shape: { code, message, details? } (the APIError type). HTTP status comes from a fixed mapping: 400 invalid data, 401 not authenticated, 403 not authorized, 404 not found, 409 conflict, 422 validation failed, 500 server error (never expose internals).
Never mix error shapes across endpoints — callers build parsing logic against the first shape they see, and Hyrum's Law nails that shape in place. The canonical APIError type, full status mapping, worked examples per status code, and the 500-disclosure rule live in references/error-shapes.md.
Quick reference for REST/JSON surfaces:
/tasks, not /createTask).camelCase.is, has, or can (isArchived, hasPermission).UPPER_SNAKE (STATUS_ACTIVE, not active or Active).Full table plus worked good/bad examples and per-ecosystem deviations (Python snake_case, Go PascalCase for exported fields) live in references/naming-conventions.md.
List endpoints paginate. Always. Even the ones that "only return a few rows today." Fixed shape:
type PaginatedResponse<T> = {
data: T[];
pagination: {
page: number;
pageSize: number;
totalItems: number;
totalPages: number;
};
};
Callers build pagination UI and prefetching logic against this shape. Adding pagination later is a breaking change; adding it now is free.
Three patterns keep TypeScript interfaces stable under Hyrum's Law:
CreateTaskInput is not Task. Do not reuse the read type as the write payload.Code examples, factory patterns, and the per-pattern rationale live in references/typescript-patterns.md. Non-TS projects can skip this rule, but the underlying intent (don't conflate read and write shapes; don't let raw strings flow where a typed ID is expected) applies everywhere.
When you notice an adjacent endpoint with an inconsistent shape, a sibling that uses a different error format, or a neighboring module that missed pagination, emit a NOTICED BUT NOT TOUCHING marker and move on. Do not silently reshape the adjacent surface during this design.
NOTICED BUT NOT TOUCHING: <what you saw>
Why skipping: <out-of-scope / unrelated / deferred>
The reason: reshaping adjacent contracts is itself a breaking change, and it deserves its own design pass — not a drive-by edit during work on a different surface.
/teams/:teamId/tasks, not /getTasksForTeam)./tasks?status=OPEN&assignee=abc).PATCH with only the changed fields.PUT with the complete resource.The first interface that comes to mind is rarely the best. Design It Twice drafts radically different shapes for the same problem in parallel, then compares them in the open before any single design is locked in.
Platform requirement — the parallel form requires a multi-agent capability (Claude Code's Agent tool). On platforms without it, fall back to the sequential variant: draft each design in turn under the same constraint slate, taking care not to read prior designs while drafting the next, then compare them with the same rubric. references/design-it-twice.md covers both forms.
When this mode applies — opt-in only. Trigger on --design-twice, on the user phrase "design it twice" or "show me alternatives". For high-leverage surfaces (SDK, cross-team contract) where the obvious shape feels suspiciously obvious, ask before entering this mode. Do not enter it by default; the cost of multiplication is wasted on low-stakes interfaces.
How it works — frame the problem (not the interface), produce 3+ designs each pinned to a different constraint (minimize methods / maximize flexibility / optimize common case / ports & adapters), present each in full sequentially, then compare in prose by depth, locality, and seam placement. End with a single recommendation: pick one, hybridize and explain the borrow, or redesign because the framing was wrong. See references/design-it-twice.md for the full process, prompt template, and comparison rubric.
Relationship to the rules — Design It Twice is upstream of Rule 0–7. Once a design is picked, every rule above applies to that design. The mode does not loosen any rule; it raises the chance the picked design is worth applying them to.
kramme:siw:generate-phases — when a planned phase introduces a new interface, run this skill first to lock the contract before slicing begins.kramme:code:incremental — once the contract is locked, the implementation is sliced through the incremental loop. Each slice conforms to the contract rather than rediscovering it.kramme:injection-reviewer and kramme:auth-reviewer agents verify the validation and authorization boundaries set here. A contract that declares its validation boundary makes these reviews mechanical.These are the lies you will tell yourself to justify shipping an unstable contract:
If you notice any of these in your own design, stop and redesign:
/api/createTask, /deleteUser).user_internal_id, debug_payload).error field, 400 with no error at all.Before declaring the contract done, self-check every item:
APIError shape with a correct HTTP status from the Rule 3 mapping (full table in references/error-shapes.md)?NOTICED BUT NOT TOUCHING entry for every inconsistency in adjacent code that this design did not fix?If any answer is no, close the gap before handing off to implementation.
tools
Requires Linear MCP. Implements one Linear issue end to end, selects applicable code-review, convention, and PR-refactor gates, runs them to bounded convergence, verifies, and optionally opens the PR and iterates on CI and review feedback until green. Use when the user wants a single Linear issue taken from implementation through a clean Pull Request. Not for implementation-only work, SIW-tracked issues, stacked PRs, existing PR updates, or post-merge rollout.
development
Reviews PR and local changes for convention drift and overcaution against documented rules and mined peer-file practice. Use for new patterns, dependencies, abstractions, or defensive complexity that departs from established practice; every finding cites evidence. Supports --inline. Not for general code quality (use kramme:pr:code-review) or spec review (use kramme:siw:spec-audit --team).
testing
Charts huge or foggy initiatives into a local `.context` decision map and resolves one typed frontier ticket per session until the work is ready for SIW or another execution workflow. Use when the route to a destination cannot fit in one agent session or parallel workspaces need coordinated planning state. Not for clear specs, ordinary issue decomposition, implementation, or Linear-native tracking.
development
Investigates a question against primary sources and saves one cited Markdown artifact. Use for reading legwork: official docs/API facts, source-code or spec checks, standards, and first-party service behavior before planning or implementation. Not for making product or architecture decisions, implementing code, broad web search, secondary blog summaries, or uncited answers.