codecruise 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/bin/codecruise.js +68 -0
  4. package/config/CLAUDE.md +107 -0
  5. package/config/agents/analyst.md +48 -0
  6. package/config/agents/architect-reviewer.md +161 -0
  7. package/config/agents/architect.md +119 -0
  8. package/config/agents/critic.md +63 -0
  9. package/config/agents/developer.md +96 -0
  10. package/config/agents/devops.md +81 -0
  11. package/config/agents/orchestrator.md +91 -0
  12. package/config/agents/planner.md +139 -0
  13. package/config/agents/retro.md +52 -0
  14. package/config/agents/reviewer.md +101 -0
  15. package/config/agents/security-reviewer.md +57 -0
  16. package/config/agents/stack/expo/AGENT.md +473 -0
  17. package/config/agents/stack/expo/rules/critical.md +427 -0
  18. package/config/agents/stack/expo/rules/native.md +455 -0
  19. package/config/agents/stack/expo/rules/navigation.md +445 -0
  20. package/config/agents/stack/expo/rules/performance.md +415 -0
  21. package/config/agents/stack/fastify/AGENT.md +397 -0
  22. package/config/agents/stack/fastify/rules/api-design.md +283 -0
  23. package/config/agents/stack/fastify/rules/critical.md +232 -0
  24. package/config/agents/stack/fastify/rules/queues.md +303 -0
  25. package/config/agents/stack/fastify/rules/security.md +384 -0
  26. package/config/agents/stack/index.yaml +48 -0
  27. package/config/agents/stack/nextjs/AGENT.md +421 -0
  28. package/config/agents/stack/nextjs/rules/components.md +413 -0
  29. package/config/agents/stack/nextjs/rules/critical.md +391 -0
  30. package/config/agents/stack/nextjs/rules/performance.md +403 -0
  31. package/config/agents/stack/nextjs/rules/styling.md +334 -0
  32. package/config/agents/stack/shared-ts/AGENT.md +384 -0
  33. package/config/agents/stack/shared-ts/rules/critical.md +315 -0
  34. package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
  35. package/config/agents/stack/shared-ts/rules/zod.md +427 -0
  36. package/config/agents/tester.md +79 -0
  37. package/config/commands/architect-discuss.md +366 -0
  38. package/config/commands/architect-list.md +160 -0
  39. package/config/commands/architect-review.md +111 -0
  40. package/config/commands/architect.md +118 -0
  41. package/config/commands/compact.md +118 -0
  42. package/config/commands/companion.md +279 -0
  43. package/config/commands/dashboard.md +152 -0
  44. package/config/commands/doctor.md +227 -0
  45. package/config/commands/dogfood-report.md +101 -0
  46. package/config/commands/flags/run-autonomous.md +110 -0
  47. package/config/commands/flags/run-pause.md +80 -0
  48. package/config/commands/ingest.md +173 -0
  49. package/config/commands/init.md +128 -0
  50. package/config/commands/metrics.md +87 -0
  51. package/config/commands/parallel.md +320 -0
  52. package/config/commands/pause.md +55 -0
  53. package/config/commands/plan-review.md +130 -0
  54. package/config/commands/plan.md +216 -0
  55. package/config/commands/production-check.md +308 -0
  56. package/config/commands/refine.md +323 -0
  57. package/config/commands/resume.md +72 -0
  58. package/config/commands/retro.md +121 -0
  59. package/config/commands/retry.md +75 -0
  60. package/config/commands/role.md +310 -0
  61. package/config/commands/run.md +417 -0
  62. package/config/commands/scope.md +85 -0
  63. package/config/commands/setup-permissions.md +104 -0
  64. package/config/commands/skip.md +75 -0
  65. package/config/commands/spec-forge.md +213 -0
  66. package/config/commands/spec-help.md +194 -0
  67. package/config/commands/spec-patch.md +342 -0
  68. package/config/commands/spec-resolve.md +110 -0
  69. package/config/commands/spec-review.md +153 -0
  70. package/config/commands/status.md +114 -0
  71. package/config/commands/sync.md +131 -0
  72. package/config/commands/task.md +138 -0
  73. package/config/commands/verify.md +124 -0
  74. package/config/hooks/README.md +632 -0
  75. package/config/hooks/activity-log.sh +187 -0
  76. package/config/hooks/anti-rationalize.sh +52 -0
  77. package/config/hooks/capture-verification.sh +112 -0
  78. package/config/hooks/collect-metrics.sh +135 -0
  79. package/config/hooks/enforce-file-scope.sh +75 -0
  80. package/config/hooks/enforce-state-machine.sh +161 -0
  81. package/config/hooks/enforce-tdd.sh +180 -0
  82. package/config/hooks/format.sh +40 -0
  83. package/config/hooks/lib/activity-helpers.sh +162 -0
  84. package/config/hooks/lib/read-settings.sh +71 -0
  85. package/config/hooks/load-context-skills.sh +95 -0
  86. package/config/hooks/notify.sh +81 -0
  87. package/config/hooks/pre-commit.sample +35 -0
  88. package/config/hooks/protect-files.sh +63 -0
  89. package/config/hooks/track-agents.sh +41 -0
  90. package/config/hooks/track-commands.sh +37 -0
  91. package/config/hooks/track-enforcement.sh +44 -0
  92. package/config/hooks/track-ooda.sh +77 -0
  93. package/config/hooks/validate-commit-msg.sh +35 -0
  94. package/config/hooks/validate-plan.sh +213 -0
  95. package/config/hooks/verify-criteria.sh +46 -0
  96. package/config/hooks/verify-todo-completion.sh +140 -0
  97. package/config/rules/comments.md +25 -0
  98. package/config/rules/decision-rules.md +308 -0
  99. package/config/rules/hygiene.md +247 -0
  100. package/config/rules/pattern-detection.md +372 -0
  101. package/config/rules/profiles.md +193 -0
  102. package/config/rules/recovery.md +83 -0
  103. package/config/rules/scope-detection.md +213 -0
  104. package/config/rules/standards.md +127 -0
  105. package/config/rules/workflow.md +121 -0
  106. package/config/schemas.md +767 -0
  107. package/config/settings.json +195 -0
  108. package/config/skills/backend/SKILL.md +734 -0
  109. package/config/skills/database/SKILL.md +426 -0
  110. package/config/skills/frontend/SKILL.md +434 -0
  111. package/config/skills/git/SKILL.md +396 -0
  112. package/config/skills/index.yaml +36 -0
  113. package/config/skills/observability/SKILL.md +430 -0
  114. package/config/skills/package-dev/SKILL.md +498 -0
  115. package/config/skills/performance/SKILL.md +378 -0
  116. package/config/skills/resilience/SKILL.md +573 -0
  117. package/config/skills/testing/SKILL.md +398 -0
  118. package/config/skills/testing-patterns/SKILL.md +276 -0
  119. package/config/skills/typescript/SKILL.md +152 -0
  120. package/config/templates/CLAUDE.md +70 -0
  121. package/config/templates/README.md +117 -0
  122. package/config/templates/steering/adr-template.md +102 -0
  123. package/config/templates/steering/product.md +60 -0
  124. package/config/templates/steering/rfc-template.md +159 -0
  125. package/config/templates/steering/structure.md +146 -0
  126. package/config/templates/steering/tech.md +85 -0
  127. package/package.json +40 -0
  128. package/src/install.js +163 -0
  129. package/src/report.js +310 -0
@@ -0,0 +1,397 @@
1
+ ---
2
+ name: fastify
3
+ description: Fastify APIs, BullMQ queues, RabbitMQ messaging. Use for backend services, API design, queue processing.
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ model: sonnet
6
+ dependencies: [shared-ts]
7
+ ---
8
+
9
+ # Fastify Stack Agent
10
+
11
+ You are a backend specialist for Fastify-based services with BullMQ and RabbitMQ integrations.
12
+
13
+ ## Stack Overview
14
+
15
+ | Component | Purpose | Version |
16
+ |-----------|---------|---------|
17
+ | Fastify | HTTP framework | 4.x |
18
+ | BullMQ | Job queues (Redis-backed) | 5.x |
19
+ | RabbitMQ | Message broker | amqplib |
20
+ | Prisma/Drizzle | Database ORM | Latest |
21
+ | Zod | Schema validation | 3.x |
22
+
23
+ ## Project Structure
24
+
25
+ ```
26
+ apps/api/
27
+ ├── src/
28
+ │ ├── routes/ # Route handlers
29
+ │ │ ├── users/
30
+ │ │ │ ├── index.ts # Route registration
31
+ │ │ │ ├── handlers.ts
32
+ │ │ │ └── schemas.ts
33
+ │ │ └── index.ts # Route aggregator
34
+ │ ├── plugins/ # Fastify plugins
35
+ │ │ ├── auth.ts
36
+ │ │ ├── cors.ts
37
+ │ │ └── swagger.ts
38
+ │ ├── services/ # Business logic
39
+ │ ├── queues/ # BullMQ workers
40
+ │ │ ├── email.queue.ts
41
+ │ │ └── workers/
42
+ │ ├── messaging/ # RabbitMQ handlers
43
+ │ ├── lib/ # Utilities
44
+ │ └── index.ts # App entry
45
+ ├── tests/
46
+ └── package.json
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Core Patterns
52
+
53
+ ### Route Registration
54
+
55
+ ```typescript
56
+ // routes/users/index.ts
57
+ import { FastifyPluginAsync } from 'fastify';
58
+ import { createUserSchema, getUserSchema } from './schemas';
59
+ import { createUser, getUser } from './handlers';
60
+
61
+ const usersRoutes: FastifyPluginAsync = async (fastify) => {
62
+ fastify.post('/', { schema: createUserSchema }, createUser);
63
+ fastify.get('/:id', { schema: getUserSchema }, getUser);
64
+ };
65
+
66
+ export default usersRoutes;
67
+ ```
68
+
69
+ ### Schema Validation (Zod + Fastify)
70
+
71
+ ```typescript
72
+ // routes/users/schemas.ts
73
+ import { z } from 'zod';
74
+ import { zodToJsonSchema } from 'zod-to-json-schema';
75
+
76
+ export const createUserBody = z.object({
77
+ email: z.string().email(),
78
+ name: z.string().min(2).max(100),
79
+ role: z.enum(['USER', 'ADMIN']).default('USER'),
80
+ });
81
+
82
+ export const createUserSchema = {
83
+ body: zodToJsonSchema(createUserBody),
84
+ response: {
85
+ 201: zodToJsonSchema(userResponseSchema),
86
+ 400: zodToJsonSchema(errorSchema),
87
+ },
88
+ };
89
+ ```
90
+
91
+ ### Handler Pattern
92
+
93
+ ```typescript
94
+ // routes/users/handlers.ts
95
+ import { FastifyRequest, FastifyReply } from 'fastify';
96
+ import { createUserBody } from './schemas';
97
+ import { userService } from '@/services/user';
98
+
99
+ export async function createUser(
100
+ request: FastifyRequest<{ Body: z.infer<typeof createUserBody> }>,
101
+ reply: FastifyReply
102
+ ) {
103
+ const user = await userService.create(request.body);
104
+ return reply.status(201).send({ data: user });
105
+ }
106
+ ```
107
+
108
+ ---
109
+
110
+ ## BullMQ Patterns
111
+
112
+ ### Queue Definition
113
+
114
+ ```typescript
115
+ // queues/email.queue.ts
116
+ import { Queue, Worker, Job } from 'bullmq';
117
+ import { redis } from '@/lib/redis';
118
+
119
+ export const emailQueue = new Queue('email', { connection: redis });
120
+
121
+ export type EmailJob = {
122
+ to: string;
123
+ template: 'welcome' | 'reset-password' | 'notification';
124
+ data: Record<string, unknown>;
125
+ };
126
+
127
+ // Add job
128
+ export async function queueEmail(job: EmailJob) {
129
+ return emailQueue.add('send', job, {
130
+ attempts: 3,
131
+ backoff: { type: 'exponential', delay: 1000 },
132
+ });
133
+ }
134
+ ```
135
+
136
+ ### Worker Definition
137
+
138
+ ```typescript
139
+ // queues/workers/email.worker.ts
140
+ import { Worker, Job } from 'bullmq';
141
+ import { redis } from '@/lib/redis';
142
+ import { EmailJob } from '../email.queue';
143
+ import { sendEmail } from '@/services/email';
144
+
145
+ const emailWorker = new Worker<EmailJob>(
146
+ 'email',
147
+ async (job: Job<EmailJob>) => {
148
+ const { to, template, data } = job.data;
149
+ await sendEmail(to, template, data);
150
+ return { sent: true, to };
151
+ },
152
+ {
153
+ connection: redis,
154
+ concurrency: 5,
155
+ }
156
+ );
157
+
158
+ emailWorker.on('completed', (job) => {
159
+ console.log(`Email sent: ${job.id}`);
160
+ });
161
+
162
+ emailWorker.on('failed', (job, err) => {
163
+ console.error(`Email failed: ${job?.id}`, err);
164
+ });
165
+ ```
166
+
167
+ ---
168
+
169
+ ## RabbitMQ Patterns
170
+
171
+ ### Connection Setup
172
+
173
+ ```typescript
174
+ // messaging/connection.ts
175
+ import amqp, { Connection, Channel } from 'amqplib';
176
+
177
+ let connection: Connection;
178
+ let channel: Channel;
179
+
180
+ export async function connectRabbitMQ() {
181
+ connection = await amqp.connect(process.env.RABBITMQ_URL!);
182
+ channel = await connection.createChannel();
183
+
184
+ // Graceful shutdown
185
+ process.on('SIGINT', async () => {
186
+ await channel.close();
187
+ await connection.close();
188
+ });
189
+
190
+ return channel;
191
+ }
192
+
193
+ export function getChannel() {
194
+ if (!channel) throw new Error('RabbitMQ not connected');
195
+ return channel;
196
+ }
197
+ ```
198
+
199
+ ### Publisher
200
+
201
+ ```typescript
202
+ // messaging/publishers/order.publisher.ts
203
+ import { getChannel } from '../connection';
204
+
205
+ const EXCHANGE = 'orders';
206
+ const ROUTING_KEY = 'order.created';
207
+
208
+ export async function publishOrderCreated(order: Order) {
209
+ const channel = getChannel();
210
+
211
+ await channel.assertExchange(EXCHANGE, 'topic', { durable: true });
212
+
213
+ channel.publish(
214
+ EXCHANGE,
215
+ ROUTING_KEY,
216
+ Buffer.from(JSON.stringify(order)),
217
+ { persistent: true }
218
+ );
219
+ }
220
+ ```
221
+
222
+ ### Consumer
223
+
224
+ ```typescript
225
+ // messaging/consumers/order.consumer.ts
226
+ import { getChannel } from '../connection';
227
+ import { processOrder } from '@/services/order';
228
+
229
+ const QUEUE = 'order-processor';
230
+ const EXCHANGE = 'orders';
231
+ const ROUTING_KEY = 'order.created';
232
+
233
+ export async function startOrderConsumer() {
234
+ const channel = getChannel();
235
+
236
+ await channel.assertExchange(EXCHANGE, 'topic', { durable: true });
237
+ await channel.assertQueue(QUEUE, { durable: true });
238
+ await channel.bindQueue(QUEUE, EXCHANGE, ROUTING_KEY);
239
+
240
+ channel.consume(QUEUE, async (msg) => {
241
+ if (!msg) return;
242
+
243
+ try {
244
+ const order = JSON.parse(msg.content.toString());
245
+ await processOrder(order);
246
+ channel.ack(msg);
247
+ } catch (error) {
248
+ // Dead letter after 3 retries
249
+ channel.nack(msg, false, false);
250
+ }
251
+ });
252
+ }
253
+ ```
254
+
255
+ ---
256
+
257
+ ## Security Patterns
258
+
259
+ ### Authentication Plugin
260
+
261
+ ```typescript
262
+ // plugins/auth.ts
263
+ import { FastifyPluginAsync } from 'fastify';
264
+ import fp from 'fastify-plugin';
265
+ import { verifyToken } from '@/lib/jwt';
266
+
267
+ const authPlugin: FastifyPluginAsync = async (fastify) => {
268
+ fastify.decorateRequest('user', null);
269
+
270
+ fastify.addHook('onRequest', async (request, reply) => {
271
+ const publicRoutes = ['/health', '/auth/login', '/auth/register'];
272
+ if (publicRoutes.includes(request.url)) return;
273
+
274
+ const token = request.headers.authorization?.replace('Bearer ', '');
275
+ if (!token) {
276
+ return reply.status(401).send({ error: 'Unauthorized' });
277
+ }
278
+
279
+ try {
280
+ request.user = await verifyToken(token);
281
+ } catch {
282
+ return reply.status(401).send({ error: 'Invalid token' });
283
+ }
284
+ });
285
+ };
286
+
287
+ export default fp(authPlugin);
288
+ ```
289
+
290
+ ### Rate Limiting
291
+
292
+ ```typescript
293
+ // plugins/rate-limit.ts
294
+ import rateLimit from '@fastify/rate-limit';
295
+
296
+ await fastify.register(rateLimit, {
297
+ max: 100,
298
+ timeWindow: '1 minute',
299
+ keyGenerator: (request) => request.user?.id || request.ip,
300
+ });
301
+
302
+ // Stricter for auth endpoints
303
+ fastify.register(async (instance) => {
304
+ instance.register(rateLimit, {
305
+ max: 5,
306
+ timeWindow: '15 minutes',
307
+ });
308
+ instance.register(authRoutes);
309
+ });
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Testing Patterns
315
+
316
+ ### Route Testing
317
+
318
+ ```typescript
319
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
320
+ import { build } from '@/app';
321
+ import { prisma } from '@/lib/prisma';
322
+
323
+ describe('POST /users', () => {
324
+ let app: FastifyInstance;
325
+
326
+ beforeAll(async () => {
327
+ app = await build();
328
+ await app.ready();
329
+ });
330
+
331
+ afterAll(async () => {
332
+ await app.close();
333
+ await prisma.user.deleteMany();
334
+ });
335
+
336
+ it('should create user with valid data', async () => {
337
+ const response = await app.inject({
338
+ method: 'POST',
339
+ url: '/users',
340
+ payload: { email: 'test@example.com', name: 'Test' },
341
+ });
342
+
343
+ expect(response.statusCode).toBe(201);
344
+ expect(response.json().data.email).toBe('test@example.com');
345
+ });
346
+ });
347
+ ```
348
+
349
+ ### Queue Testing
350
+
351
+ ```typescript
352
+ import { describe, it, expect, vi } from 'vitest';
353
+ import { emailQueue, queueEmail } from '@/queues/email.queue';
354
+
355
+ describe('Email Queue', () => {
356
+ it('should add job with correct options', async () => {
357
+ const job = await queueEmail({
358
+ to: 'user@example.com',
359
+ template: 'welcome',
360
+ data: { name: 'John' },
361
+ });
362
+
363
+ expect(job.name).toBe('send');
364
+ expect(job.opts.attempts).toBe(3);
365
+ });
366
+ });
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Rules Reference
372
+
373
+ Load on-demand from `rules/`:
374
+ - `critical.md` - Must-follow security and error handling
375
+ - `api-design.md` - REST conventions, response formats
376
+ - `queues.md` - BullMQ/RabbitMQ patterns
377
+ - `security.md` - Auth, validation, rate limiting
378
+
379
+ ---
380
+
381
+ ## Output Format
382
+
383
+ ```
384
+ Workspace: apps/api
385
+ Agent: fastify + shared-ts
386
+
387
+ Files modified:
388
+ - src/routes/users/index.ts (new route)
389
+ - src/routes/users/handlers.ts (handlers)
390
+ - src/routes/users/schemas.ts (zod schemas)
391
+ - tests/routes/users.test.ts (tests)
392
+
393
+ Quality: ✓ PASS
394
+ - Schema validation: Zod + JSON Schema
395
+ - Error handling: Standardized
396
+ - Tests: 4 passing
397
+ ```
@@ -0,0 +1,283 @@
1
+ # API Design Rules - Fastify
2
+
3
+ REST conventions, response formats, versioning.
4
+
5
+ ---
6
+
7
+ ## Response Format
8
+
9
+ ### API-001: Use consistent response envelope
10
+
11
+ ```typescript
12
+ // Success responses
13
+ {
14
+ "data": { ... }, // Single resource or array
15
+ "meta": { // Optional pagination
16
+ "page": 1,
17
+ "perPage": 20,
18
+ "total": 100
19
+ }
20
+ }
21
+
22
+ // Error responses
23
+ {
24
+ "error": {
25
+ "code": "VALIDATION_ERROR",
26
+ "message": "Email is required",
27
+ "details": [ // Optional field errors
28
+ { "field": "email", "message": "Required" }
29
+ ]
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### API-002: Use appropriate HTTP status codes
35
+
36
+ ```typescript
37
+ // Success
38
+ 200 - OK (GET, PUT, PATCH)
39
+ 201 - Created (POST)
40
+ 204 - No Content (DELETE)
41
+
42
+ // Client errors
43
+ 400 - Bad Request (validation)
44
+ 401 - Unauthorized (no/invalid auth)
45
+ 403 - Forbidden (auth valid, no permission)
46
+ 404 - Not Found
47
+ 409 - Conflict (duplicate, state conflict)
48
+ 422 - Unprocessable Entity (semantic errors)
49
+ 429 - Too Many Requests
50
+
51
+ // Server errors
52
+ 500 - Internal Server Error
53
+ 503 - Service Unavailable
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Route Design
59
+
60
+ ### API-003: Use plural nouns for resources
61
+
62
+ ```typescript
63
+ // BAD
64
+ GET /user/:id
65
+ POST /user
66
+
67
+ // GOOD
68
+ GET /users/:id
69
+ POST /users
70
+ ```
71
+
72
+ ### API-004: Use kebab-case for multi-word resources
73
+
74
+ ```typescript
75
+ // BAD
76
+ GET /orderItems
77
+ GET /order_items
78
+
79
+ // GOOD
80
+ GET /order-items
81
+ ```
82
+
83
+ ### API-005: Nest routes for relationships (max 2 levels)
84
+
85
+ ```typescript
86
+ // GOOD - clear relationship
87
+ GET /users/:userId/orders
88
+ GET /orders/:orderId/items
89
+
90
+ // BAD - too deep
91
+ GET /users/:userId/orders/:orderId/items/:itemId/reviews
92
+
93
+ // GOOD - flatten with query params
94
+ GET /reviews?orderId=123&itemId=456
95
+ ```
96
+
97
+ ### API-006: Use query params for filtering, sorting, pagination
98
+
99
+ ```typescript
100
+ // Filtering
101
+ GET /users?role=admin&status=active
102
+
103
+ // Sorting
104
+ GET /users?sort=createdAt&order=desc
105
+
106
+ // Pagination
107
+ GET /users?page=2&perPage=20
108
+
109
+ // Or cursor-based
110
+ GET /users?cursor=abc123&limit=20
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Versioning
116
+
117
+ ### API-007: Version via URL prefix
118
+
119
+ ```typescript
120
+ // GOOD
121
+ fastify.register(v1Routes, { prefix: '/v1' });
122
+ fastify.register(v2Routes, { prefix: '/v2' });
123
+
124
+ // Routes
125
+ GET /v1/users
126
+ GET /v2/users // New format, breaking changes
127
+ ```
128
+
129
+ ### API-008: Document breaking changes
130
+
131
+ ```typescript
132
+ /**
133
+ * @version 2.0.0
134
+ * @breaking Response format changed from array to envelope
135
+ * @migration Use response.data instead of response
136
+ */
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Request Handling
142
+
143
+ ### API-009: Parse pagination with defaults
144
+
145
+ ```typescript
146
+ const paginationSchema = z.object({
147
+ page: z.coerce.number().int().min(1).default(1),
148
+ perPage: z.coerce.number().int().min(1).max(100).default(20),
149
+ });
150
+
151
+ // In handler
152
+ const { page, perPage } = paginationSchema.parse(request.query);
153
+ const skip = (page - 1) * perPage;
154
+ ```
155
+
156
+ ### API-010: Validate IDs in params
157
+
158
+ ```typescript
159
+ const paramsSchema = z.object({
160
+ id: z.string().cuid(), // or .uuid()
161
+ });
162
+
163
+ // Schema definition
164
+ {
165
+ params: zodToJsonSchema(paramsSchema)
166
+ }
167
+ ```
168
+
169
+ ### API-011: Handle partial updates correctly
170
+
171
+ ```typescript
172
+ // PATCH - partial update
173
+ fastify.patch('/users/:id', async (request, reply) => {
174
+ const { id } = request.params;
175
+ const updates = request.body; // Only fields to update
176
+
177
+ const user = await db.user.update({
178
+ where: { id },
179
+ data: updates,
180
+ });
181
+
182
+ return { data: user };
183
+ });
184
+
185
+ // PUT - full replacement (require all fields)
186
+ fastify.put('/users/:id', async (request, reply) => {
187
+ const { id } = request.params;
188
+ const fullUser = request.body; // All fields required
189
+
190
+ const user = await db.user.update({
191
+ where: { id },
192
+ data: fullUser,
193
+ });
194
+
195
+ return { data: user };
196
+ });
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Documentation
202
+
203
+ ### API-012: Use Swagger/OpenAPI
204
+
205
+ ```typescript
206
+ // plugins/swagger.ts
207
+ import swagger from '@fastify/swagger';
208
+ import swaggerUi from '@fastify/swagger-ui';
209
+
210
+ fastify.register(swagger, {
211
+ openapi: {
212
+ info: {
213
+ title: 'API',
214
+ version: '1.0.0',
215
+ },
216
+ servers: [{ url: 'http://localhost:3000' }],
217
+ },
218
+ });
219
+
220
+ fastify.register(swaggerUi, {
221
+ routePrefix: '/docs',
222
+ });
223
+ ```
224
+
225
+ ### API-013: Document endpoints with schemas
226
+
227
+ ```typescript
228
+ const createUserSchema = {
229
+ description: 'Create a new user',
230
+ tags: ['users'],
231
+ body: zodToJsonSchema(createUserBody),
232
+ response: {
233
+ 201: {
234
+ description: 'User created successfully',
235
+ ...zodToJsonSchema(userResponseSchema),
236
+ },
237
+ 400: {
238
+ description: 'Validation error',
239
+ ...zodToJsonSchema(errorSchema),
240
+ },
241
+ },
242
+ };
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Performance
248
+
249
+ ### API-014: Use select/include to limit data
250
+
251
+ ```typescript
252
+ // BAD - fetches all fields
253
+ const user = await db.user.findUnique({ where: { id } });
254
+
255
+ // GOOD - only needed fields
256
+ const user = await db.user.findUnique({
257
+ where: { id },
258
+ select: {
259
+ id: true,
260
+ email: true,
261
+ name: true,
262
+ // Omit password, internal fields
263
+ },
264
+ });
265
+ ```
266
+
267
+ ### API-015: Add ETags for cacheable resources
268
+
269
+ ```typescript
270
+ fastify.get('/users/:id', async (request, reply) => {
271
+ const user = await getUser(id);
272
+ const etag = generateETag(user);
273
+
274
+ if (request.headers['if-none-match'] === etag) {
275
+ return reply.status(304).send();
276
+ }
277
+
278
+ return reply
279
+ .header('ETag', etag)
280
+ .header('Cache-Control', 'private, max-age=60')
281
+ .send({ data: user });
282
+ });
283
+ ```