name: new-feature description: > Implements a new feature using docs-first delivery, boundary-driven structure, and skeleton-first execution. Load when adding a route, feature, or new user-visible behavior.
New Feature
Purpose
Use this skill when adding a feature, not just when writing code in isolation.
The goal is to choose boundaries deliberately:
- route or handler boundaries for transport and framework concerns
- feature boundaries for nested processing and rendering
- shared libraries or adapters for reusable low-level capabilities
Do not create files and layers just to satisfy a template. Create them because they own a distinct reason to change.
Precondition: Docs Must Be Implementable
Before coding, check whether the repository docs are concrete enough to build from without chat history.
If the task depends on values such as:
- route paths
- input shapes
- storage keys
- binding or environment variable names
- success behavior
- failure behavior
and those values are still vague, tighten the docs first.
In this repository, tighten the ADR first.
Do not create docs/spec.md unless the user explicitly asks for it.
If implementation planning is part of the task, the ADR should also record:
- a short implementation plan
- the planned files and which boundary will own each one
Delivery Order: Skeleton First
Build in this order:
- Types — define the shape of data and errors
- Interfaces — define the boundary API
- Tests — write tests against that boundary
- Implementation — fill in logic until tests pass
This keeps the design explicit and prevents "done" from meaning "probably works."
Choose Boundaries by Ownership
Route or Handler Boundary
A route or handler owns framework-facing concerns.
Typical responsibilities:
- path, query, params, body, cookies, headers
- request-scoped context, env, auth/session access
- status codes, redirects, headers, metadata
- deciding which feature or service to call
- rendering or returning the top-level response
Routes should not absorb every detail below them. They should gather inputs, call the right boundary, and own the response contract.
Feature Boundary
A feature owns nested behavior once the route has supplied explicit inputs.
Typical responsibilities:
- transforming route inputs into feature data
- loading or combining data for one user-facing capability
- rendering nested views, sections, forms, panels, or composed UI
- feature-specific error mapping
- feature-specific state transitions and decisions
Good feature boundaries are driven by one capability, not by a directory rule.
Shared Library or Adapter Boundary
Shared libraries or adapters own reusable capabilities that should not care which route or feature called them.
Typical responsibilities:
- API clients
- storage access
- parsers and serializers
- normalization helpers
- cache helpers
- cross-feature utility logic
If multiple features would need the same low-level behavior, it likely belongs here instead of inside one feature.
Colocation Pattern
When a feature boundary exists, colocate the files that belong to it.
[feature-name]/
├── index.ts
├── [feature].ts
├── [feature].tsx # primary component
├── [feature]-card.tsx # sub-component
├── [feature].types.ts
└── [feature].test.ts
Use index.ts as the public boundary when the feature is imported from
elsewhere.
This pattern includes not only logic and types, but also the UI component(s) that belong to the feature. A [feature].tsx file colocates the view layer alongside its data and business logic, avoiding the need for a separate components/ directory at the feature level.
- Next.js App Router
- Remix
- React Router data routers
- Nuxt
- SvelteKit
- SolidStart
- HonoX
- Astro
Feature Slices Are Optional
Do not create a feature slice only because a route has logic.
Keep code local to the route when it is:
- tiny
- truly route-specific
- unlikely to be reused
- mostly transport glue rather than nested behavior
Promote route-local code into a feature when:
- the nested behavior is no longer tiny
- there is meaningful processing after route inputs are gathered
- the rendering below the route deserves its own ownership
- the same capability may be reused by another route or context
Practical rule:
- tiny route-owned behavior can stay in the route file
- medium feature-owned behavior should move into a feature boundary
- reusable low-level behavior should move into shared libraries or adapters
Types Files Are Optional
Do not create [feature].types.ts just because a template says so.
Create a separate types file only when:
- multiple files in the boundary share those types
- the types clarify the public contract
- the contract is important enough to deserve its own surface
Do not use a types file as a dumping ground for unrelated constants.
Medium-Sized Components Are Allowed
It is acceptable to keep medium-sized feature-local UI inside the same boundary.
Good fit:
- feature-owned views
- sections and panels
- composed cards
- forms tied to one feature flow
- UI that depends mainly on one feature's inputs or state
Promote to shared components only when reuse is real.
Test Layers by Responsibility
Do not split tests by folder name alone. Split them by owned contract.
Use feature tests for:
- nested processing and rendering
- feature-specific data combination
- feature-specific error mapping
- feature state and decisions
Use shared library or adapter tests for:
- parsers
- storage access
- client behavior
- cache logic
- normalization helpers
If two test files assert the same behavior at different layers, one of them is probably too low-level or too high-level.
Completion Checklist
- docs were concrete enough to implement from, or were tightened first
- route, feature, and shared boundaries were chosen by ownership
- files were added because they own a distinct reason to change
- tests match the owned contract of each boundary
- the repository's existing validation commands pass