add-feature
Add a product feature end to end (API contract, apps/api module, client hook, web page, tests). Use when the user asks for a new resource, endpoint, screen or CRUD flow backed by the database.
SKILL.md.claude/skills/add-feature/SKILL.md
GitHubAdd a feature
The todo feature is the reference for every layer: copy its shape, not just its names.
- Table. If the feature stores data, add the model first (db-change skill): tenant
table with
org_id, forced row-level security and grants, thenbun run gen. - Scaffold.
bun run gen:new api-feature --args <name> <item> <model> '<row>', e.g.bun run gen:new api-feature --args projects project project '{"name":"Launch"}'(<model>is the Prisma client accessor,<row>the columns a test row needs besidesorg_id, as JSON). It writes and wires:packages/contracts/src/api/<name>.ts, registered inpackages/contracts/src/api/index.tsapps/api/src/modules/<name>/(module, repository, service, router, index), registered inapps/api/src/app.module.tsandapps/api/src/rpc/router.tspackages/client/src/api/<name>/list.ts(an infinite-query hook)- the
<item>.deleted.v1event inpackages/contracts/src/events.ts, described underworkspace.audit.eventsinpackages/i18n/messages/en.jsonand, as a draft to translate,es.json - a
describeblock at the end ofapps/api/test/api.integration.test.ts: empty for a new workspace, another workspace's rows never listed and never deletable, paging, the delete recording its event,NOT_FOUNDfor one that isn't there, 401 signed out, 422 on a bad page size or id The scaffold is alistprocedure that returnsidandcreatedAtand adeletethat emits its event in the same transaction; everything else is yours.
- Contract. Add the fields to the item schema and the writes (
create,update,delete) with input schemas and limits, likepackages/contracts/src/api/todo.ts. New error codes go inpackages/contracts/src/errors.tsanderrors.<CODE>in everypackages/i18n/messages/*.json, and every code the module's services throw goes in the module'serrorsOf(...)(outside production the API refuses to answer an undeclared one, so the integration tests find a missing one). For API-key access, add a scope topackages/contracts/src/api/scopes.tsand describe it underworkspace.apiKeys.scopes. - API. Repository: every query through
withTenant/tenantTx,readfor lists,writefor changes. Service: each change and its domain event (emitEvent, events declared inpackages/contracts/src/events.ts, described underworkspace.audit.eventsin i18n) in onetenantTx. Router: one line per procedure;inOrg,orgAdminorfreshfromapps/api/src/rpc/procedures.ts. - Client. One hook per procedure under
packages/client/src/api/<name>/, with optimistic updates likepackages/client/src/api/todo/(pure cache transforms in their own file, unit-tested). The generated list hook is tagged@publicso knip lets it wait for its first screen; drop the tag when the page below calls it. The contract exports only what another workspace imports (knip checks the packages' exports), so export the item's schema or type in the change that needs it. - Web. A module in
apps/web/src/modules/<name>/(components, pages,index.ts) and a route inapps/web/src/app/(app)/<name>/page.tsxthat re-exports the page withpageTitle(...). Data only through@repo/clienthooks; forms validate with the contract's input schema (seeapps/web/src/modules/dashboard/components/todo-list.tsx). Every string throughpackages/i18n, in every locale. - Tests. Extend the generated block in
apps/api/test/api.integration.test.ts: happy path, typed errors, and another organization seeing nothing. A user flow gets a spec inapps/web/e2e/(seetodos.spec.ts). bun run gen(the OpenAPI document changes), then the verify skill.- Reviews, before calling it done: the
revieweragent on the whole change; thesecurity-revieweragent, since new procedures are new entry points; themigration-revieweragent if step 1 added a migration; thefrontend-revieweragent for the web page; thei18n-checkeragent for the new copy. Fix what they report and run the verify skill again.