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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/bin/codecruise.js +68 -0
- package/config/CLAUDE.md +107 -0
- package/config/agents/analyst.md +48 -0
- package/config/agents/architect-reviewer.md +161 -0
- package/config/agents/architect.md +119 -0
- package/config/agents/critic.md +63 -0
- package/config/agents/developer.md +96 -0
- package/config/agents/devops.md +81 -0
- package/config/agents/orchestrator.md +91 -0
- package/config/agents/planner.md +139 -0
- package/config/agents/retro.md +52 -0
- package/config/agents/reviewer.md +101 -0
- package/config/agents/security-reviewer.md +57 -0
- package/config/agents/stack/expo/AGENT.md +473 -0
- package/config/agents/stack/expo/rules/critical.md +427 -0
- package/config/agents/stack/expo/rules/native.md +455 -0
- package/config/agents/stack/expo/rules/navigation.md +445 -0
- package/config/agents/stack/expo/rules/performance.md +415 -0
- package/config/agents/stack/fastify/AGENT.md +397 -0
- package/config/agents/stack/fastify/rules/api-design.md +283 -0
- package/config/agents/stack/fastify/rules/critical.md +232 -0
- package/config/agents/stack/fastify/rules/queues.md +303 -0
- package/config/agents/stack/fastify/rules/security.md +384 -0
- package/config/agents/stack/index.yaml +48 -0
- package/config/agents/stack/nextjs/AGENT.md +421 -0
- package/config/agents/stack/nextjs/rules/components.md +413 -0
- package/config/agents/stack/nextjs/rules/critical.md +391 -0
- package/config/agents/stack/nextjs/rules/performance.md +403 -0
- package/config/agents/stack/nextjs/rules/styling.md +334 -0
- package/config/agents/stack/shared-ts/AGENT.md +384 -0
- package/config/agents/stack/shared-ts/rules/critical.md +315 -0
- package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
- package/config/agents/stack/shared-ts/rules/zod.md +427 -0
- package/config/agents/tester.md +79 -0
- package/config/commands/architect-discuss.md +366 -0
- package/config/commands/architect-list.md +160 -0
- package/config/commands/architect-review.md +111 -0
- package/config/commands/architect.md +118 -0
- package/config/commands/compact.md +118 -0
- package/config/commands/companion.md +279 -0
- package/config/commands/dashboard.md +152 -0
- package/config/commands/doctor.md +227 -0
- package/config/commands/dogfood-report.md +101 -0
- package/config/commands/flags/run-autonomous.md +110 -0
- package/config/commands/flags/run-pause.md +80 -0
- package/config/commands/ingest.md +173 -0
- package/config/commands/init.md +128 -0
- package/config/commands/metrics.md +87 -0
- package/config/commands/parallel.md +320 -0
- package/config/commands/pause.md +55 -0
- package/config/commands/plan-review.md +130 -0
- package/config/commands/plan.md +216 -0
- package/config/commands/production-check.md +308 -0
- package/config/commands/refine.md +323 -0
- package/config/commands/resume.md +72 -0
- package/config/commands/retro.md +121 -0
- package/config/commands/retry.md +75 -0
- package/config/commands/role.md +310 -0
- package/config/commands/run.md +417 -0
- package/config/commands/scope.md +85 -0
- package/config/commands/setup-permissions.md +104 -0
- package/config/commands/skip.md +75 -0
- package/config/commands/spec-forge.md +213 -0
- package/config/commands/spec-help.md +194 -0
- package/config/commands/spec-patch.md +342 -0
- package/config/commands/spec-resolve.md +110 -0
- package/config/commands/spec-review.md +153 -0
- package/config/commands/status.md +114 -0
- package/config/commands/sync.md +131 -0
- package/config/commands/task.md +138 -0
- package/config/commands/verify.md +124 -0
- package/config/hooks/README.md +632 -0
- package/config/hooks/activity-log.sh +187 -0
- package/config/hooks/anti-rationalize.sh +52 -0
- package/config/hooks/capture-verification.sh +112 -0
- package/config/hooks/collect-metrics.sh +135 -0
- package/config/hooks/enforce-file-scope.sh +75 -0
- package/config/hooks/enforce-state-machine.sh +161 -0
- package/config/hooks/enforce-tdd.sh +180 -0
- package/config/hooks/format.sh +40 -0
- package/config/hooks/lib/activity-helpers.sh +162 -0
- package/config/hooks/lib/read-settings.sh +71 -0
- package/config/hooks/load-context-skills.sh +95 -0
- package/config/hooks/notify.sh +81 -0
- package/config/hooks/pre-commit.sample +35 -0
- package/config/hooks/protect-files.sh +63 -0
- package/config/hooks/track-agents.sh +41 -0
- package/config/hooks/track-commands.sh +37 -0
- package/config/hooks/track-enforcement.sh +44 -0
- package/config/hooks/track-ooda.sh +77 -0
- package/config/hooks/validate-commit-msg.sh +35 -0
- package/config/hooks/validate-plan.sh +213 -0
- package/config/hooks/verify-criteria.sh +46 -0
- package/config/hooks/verify-todo-completion.sh +140 -0
- package/config/rules/comments.md +25 -0
- package/config/rules/decision-rules.md +308 -0
- package/config/rules/hygiene.md +247 -0
- package/config/rules/pattern-detection.md +372 -0
- package/config/rules/profiles.md +193 -0
- package/config/rules/recovery.md +83 -0
- package/config/rules/scope-detection.md +213 -0
- package/config/rules/standards.md +127 -0
- package/config/rules/workflow.md +121 -0
- package/config/schemas.md +767 -0
- package/config/settings.json +195 -0
- package/config/skills/backend/SKILL.md +734 -0
- package/config/skills/database/SKILL.md +426 -0
- package/config/skills/frontend/SKILL.md +434 -0
- package/config/skills/git/SKILL.md +396 -0
- package/config/skills/index.yaml +36 -0
- package/config/skills/observability/SKILL.md +430 -0
- package/config/skills/package-dev/SKILL.md +498 -0
- package/config/skills/performance/SKILL.md +378 -0
- package/config/skills/resilience/SKILL.md +573 -0
- package/config/skills/testing/SKILL.md +398 -0
- package/config/skills/testing-patterns/SKILL.md +276 -0
- package/config/skills/typescript/SKILL.md +152 -0
- package/config/templates/CLAUDE.md +70 -0
- package/config/templates/README.md +117 -0
- package/config/templates/steering/adr-template.md +102 -0
- package/config/templates/steering/product.md +60 -0
- package/config/templates/steering/rfc-template.md +159 -0
- package/config/templates/steering/structure.md +146 -0
- package/config/templates/steering/tech.md +85 -0
- package/package.json +40 -0
- package/src/install.js +163 -0
- package/src/report.js +310 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Zod Schema Patterns
|
|
2
|
+
|
|
3
|
+
Runtime validation with type inference.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Schema Basics
|
|
8
|
+
|
|
9
|
+
### ZOD-001: Define base schemas
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
|
|
14
|
+
// Primitives
|
|
15
|
+
const stringSchema = z.string();
|
|
16
|
+
const numberSchema = z.number();
|
|
17
|
+
const booleanSchema = z.boolean();
|
|
18
|
+
const dateSchema = z.date();
|
|
19
|
+
|
|
20
|
+
// With constraints
|
|
21
|
+
const emailSchema = z.string().email();
|
|
22
|
+
const positiveSchema = z.number().positive();
|
|
23
|
+
const nonEmptySchema = z.string().min(1);
|
|
24
|
+
|
|
25
|
+
// Optional and nullable
|
|
26
|
+
const optionalSchema = z.string().optional(); // string | undefined
|
|
27
|
+
const nullableSchema = z.string().nullable(); // string | null
|
|
28
|
+
const nullishSchema = z.string().nullish(); // string | null | undefined
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### ZOD-002: Use coercion for form data
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Coerce strings to proper types
|
|
35
|
+
const formSchema = z.object({
|
|
36
|
+
age: z.coerce.number().int().min(0).max(150),
|
|
37
|
+
birthDate: z.coerce.date(),
|
|
38
|
+
isActive: z.coerce.boolean(),
|
|
39
|
+
price: z.coerce.number().positive(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Handles: "25" -> 25, "true" -> true, "2024-01-01" -> Date
|
|
43
|
+
const data = formSchema.parse({
|
|
44
|
+
age: '25',
|
|
45
|
+
birthDate: '2024-01-01',
|
|
46
|
+
isActive: 'true',
|
|
47
|
+
price: '99.99',
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Object Schemas
|
|
54
|
+
|
|
55
|
+
### ZOD-003: Compose object schemas
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Base schema
|
|
59
|
+
const baseEntitySchema = z.object({
|
|
60
|
+
id: z.string().cuid(),
|
|
61
|
+
createdAt: z.coerce.date(),
|
|
62
|
+
updatedAt: z.coerce.date(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Extended schema
|
|
66
|
+
const userSchema = baseEntitySchema.extend({
|
|
67
|
+
email: z.string().email().toLowerCase(),
|
|
68
|
+
name: z.string().min(2).max(100),
|
|
69
|
+
role: z.enum(['USER', 'ADMIN']),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Merge schemas
|
|
73
|
+
const fullUserSchema = userSchema.merge(
|
|
74
|
+
z.object({
|
|
75
|
+
profile: z.object({
|
|
76
|
+
bio: z.string().optional(),
|
|
77
|
+
avatar: z.string().url().optional(),
|
|
78
|
+
}),
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### ZOD-004: Create input/output schemas
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Full entity (from database)
|
|
87
|
+
const userSchema = z.object({
|
|
88
|
+
id: z.string().cuid(),
|
|
89
|
+
email: z.string().email(),
|
|
90
|
+
password: z.string(),
|
|
91
|
+
name: z.string(),
|
|
92
|
+
createdAt: z.date(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Create input (no id, no timestamps)
|
|
96
|
+
const createUserSchema = userSchema.omit({
|
|
97
|
+
id: true,
|
|
98
|
+
createdAt: true,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Update input (all optional except id)
|
|
102
|
+
const updateUserSchema = userSchema
|
|
103
|
+
.partial()
|
|
104
|
+
.required({ id: true });
|
|
105
|
+
|
|
106
|
+
// Public output (no password)
|
|
107
|
+
const publicUserSchema = userSchema.omit({
|
|
108
|
+
password: true,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Types
|
|
112
|
+
type User = z.infer<typeof userSchema>;
|
|
113
|
+
type CreateUser = z.infer<typeof createUserSchema>;
|
|
114
|
+
type UpdateUser = z.infer<typeof updateUserSchema>;
|
|
115
|
+
type PublicUser = z.infer<typeof publicUserSchema>;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Transforms
|
|
121
|
+
|
|
122
|
+
### ZOD-005: Normalize input with transforms
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
const userInputSchema = z.object({
|
|
126
|
+
// Normalize email
|
|
127
|
+
email: z
|
|
128
|
+
.string()
|
|
129
|
+
.email()
|
|
130
|
+
.toLowerCase()
|
|
131
|
+
.trim(),
|
|
132
|
+
|
|
133
|
+
// Normalize whitespace in name
|
|
134
|
+
name: z
|
|
135
|
+
.string()
|
|
136
|
+
.trim()
|
|
137
|
+
.transform(s => s.replace(/\s+/g, ' ')),
|
|
138
|
+
|
|
139
|
+
// Extract digits from phone
|
|
140
|
+
phone: z
|
|
141
|
+
.string()
|
|
142
|
+
.optional()
|
|
143
|
+
.transform(s => s?.replace(/\D/g, '')),
|
|
144
|
+
|
|
145
|
+
// Parse tags from comma-separated string
|
|
146
|
+
tags: z
|
|
147
|
+
.string()
|
|
148
|
+
.transform(s => s.split(',').map(t => t.trim()).filter(Boolean)),
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### ZOD-006: Transform to domain types
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Transform to branded types
|
|
156
|
+
const userIdSchema = z.string().cuid().transform(id => id as UserId);
|
|
157
|
+
|
|
158
|
+
// Transform to class instance
|
|
159
|
+
const dateRangeSchema = z
|
|
160
|
+
.object({
|
|
161
|
+
start: z.coerce.date(),
|
|
162
|
+
end: z.coerce.date(),
|
|
163
|
+
})
|
|
164
|
+
.transform(({ start, end }) => new DateRange(start, end));
|
|
165
|
+
|
|
166
|
+
// Transform with validation
|
|
167
|
+
const positiveIntSchema = z
|
|
168
|
+
.number()
|
|
169
|
+
.transform((n, ctx) => {
|
|
170
|
+
const int = Math.floor(n);
|
|
171
|
+
if (int <= 0) {
|
|
172
|
+
ctx.addIssue({
|
|
173
|
+
code: z.ZodIssueCode.custom,
|
|
174
|
+
message: 'Must be positive integer',
|
|
175
|
+
});
|
|
176
|
+
return z.NEVER;
|
|
177
|
+
}
|
|
178
|
+
return int;
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Refinements
|
|
185
|
+
|
|
186
|
+
### ZOD-007: Add custom validation
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// Single field refinement
|
|
190
|
+
const passwordSchema = z
|
|
191
|
+
.string()
|
|
192
|
+
.min(8)
|
|
193
|
+
.refine(
|
|
194
|
+
(password) => /[A-Z]/.test(password),
|
|
195
|
+
{ message: 'Must contain uppercase letter' }
|
|
196
|
+
)
|
|
197
|
+
.refine(
|
|
198
|
+
(password) => /[0-9]/.test(password),
|
|
199
|
+
{ message: 'Must contain number' }
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// Object-level refinement
|
|
203
|
+
const dateRangeSchema = z
|
|
204
|
+
.object({
|
|
205
|
+
startDate: z.coerce.date(),
|
|
206
|
+
endDate: z.coerce.date(),
|
|
207
|
+
})
|
|
208
|
+
.refine(
|
|
209
|
+
(data) => data.endDate > data.startDate,
|
|
210
|
+
{
|
|
211
|
+
message: 'End date must be after start date',
|
|
212
|
+
path: ['endDate'],
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### ZOD-008: Use superRefine for complex validation
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const registrationSchema = z
|
|
221
|
+
.object({
|
|
222
|
+
password: z.string().min(8),
|
|
223
|
+
confirmPassword: z.string(),
|
|
224
|
+
email: z.string().email(),
|
|
225
|
+
})
|
|
226
|
+
.superRefine((data, ctx) => {
|
|
227
|
+
if (data.password !== data.confirmPassword) {
|
|
228
|
+
ctx.addIssue({
|
|
229
|
+
code: z.ZodIssueCode.custom,
|
|
230
|
+
message: 'Passwords must match',
|
|
231
|
+
path: ['confirmPassword'],
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (data.password.toLowerCase().includes(data.email.split('@')[0])) {
|
|
236
|
+
ctx.addIssue({
|
|
237
|
+
code: z.ZodIssueCode.custom,
|
|
238
|
+
message: 'Password cannot contain email',
|
|
239
|
+
path: ['password'],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Unions & Discriminated Unions
|
|
248
|
+
|
|
249
|
+
### ZOD-009: Use discriminated unions
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Event types
|
|
253
|
+
const eventSchema = z.discriminatedUnion('type', [
|
|
254
|
+
z.object({
|
|
255
|
+
type: z.literal('USER_CREATED'),
|
|
256
|
+
userId: z.string(),
|
|
257
|
+
email: z.string().email(),
|
|
258
|
+
}),
|
|
259
|
+
z.object({
|
|
260
|
+
type: z.literal('USER_UPDATED'),
|
|
261
|
+
userId: z.string(),
|
|
262
|
+
changes: z.record(z.unknown()),
|
|
263
|
+
}),
|
|
264
|
+
z.object({
|
|
265
|
+
type: z.literal('USER_DELETED'),
|
|
266
|
+
userId: z.string(),
|
|
267
|
+
deletedAt: z.date(),
|
|
268
|
+
}),
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
type Event = z.infer<typeof eventSchema>;
|
|
272
|
+
// Properly typed discriminated union
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### ZOD-010: Handle multiple valid shapes
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Config that can be string or object
|
|
279
|
+
const configSchema = z.union([
|
|
280
|
+
z.string(), // Simple string value
|
|
281
|
+
z.object({
|
|
282
|
+
value: z.string(),
|
|
283
|
+
options: z.object({
|
|
284
|
+
required: z.boolean().default(false),
|
|
285
|
+
maxLength: z.number().optional(),
|
|
286
|
+
}),
|
|
287
|
+
}),
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
// Normalize to object
|
|
291
|
+
const normalizedConfigSchema = configSchema.transform((config) => {
|
|
292
|
+
if (typeof config === 'string') {
|
|
293
|
+
return { value: config, options: { required: false } };
|
|
294
|
+
}
|
|
295
|
+
return config;
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Error Handling
|
|
302
|
+
|
|
303
|
+
### ZOD-011: Format errors for API responses
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { ZodError, ZodIssue } from 'zod';
|
|
307
|
+
|
|
308
|
+
interface FieldError {
|
|
309
|
+
field: string;
|
|
310
|
+
message: string;
|
|
311
|
+
code: string;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function formatZodError(error: ZodError): FieldError[] {
|
|
315
|
+
return error.issues.map((issue: ZodIssue) => ({
|
|
316
|
+
field: issue.path.join('.'),
|
|
317
|
+
message: issue.message,
|
|
318
|
+
code: issue.code,
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Usage
|
|
323
|
+
try {
|
|
324
|
+
userSchema.parse(input);
|
|
325
|
+
} catch (e) {
|
|
326
|
+
if (e instanceof ZodError) {
|
|
327
|
+
return {
|
|
328
|
+
error: {
|
|
329
|
+
code: 'VALIDATION_ERROR',
|
|
330
|
+
message: 'Invalid input',
|
|
331
|
+
details: formatZodError(e),
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
throw e;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### ZOD-012: Use safeParse for non-throwing validation
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// safeParse returns Result-like object
|
|
343
|
+
const result = userSchema.safeParse(input);
|
|
344
|
+
|
|
345
|
+
if (result.success) {
|
|
346
|
+
const user = result.data; // Typed as User
|
|
347
|
+
await saveUser(user);
|
|
348
|
+
} else {
|
|
349
|
+
const errors = formatZodError(result.error);
|
|
350
|
+
return { error: { code: 'VALIDATION_ERROR', details: errors } };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Async validation
|
|
354
|
+
const asyncResult = await asyncSchema.safeParseAsync(input);
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## Integration Patterns
|
|
360
|
+
|
|
361
|
+
### ZOD-013: Convert to JSON Schema (Fastify/OpenAPI)
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
365
|
+
|
|
366
|
+
const userSchema = z.object({
|
|
367
|
+
id: z.string().cuid(),
|
|
368
|
+
email: z.string().email(),
|
|
369
|
+
name: z.string().min(2),
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// For Fastify route schema
|
|
373
|
+
const routeSchema = {
|
|
374
|
+
body: zodToJsonSchema(createUserSchema),
|
|
375
|
+
response: {
|
|
376
|
+
200: zodToJsonSchema(userSchema),
|
|
377
|
+
400: zodToJsonSchema(errorSchema),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### ZOD-014: Environment validation
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
const envSchema = z.object({
|
|
386
|
+
NODE_ENV: z.enum(['development', 'test', 'production']),
|
|
387
|
+
PORT: z.coerce.number().default(3000),
|
|
388
|
+
DATABASE_URL: z.string().url(),
|
|
389
|
+
REDIS_URL: z.string().url(),
|
|
390
|
+
JWT_SECRET: z.string().min(32),
|
|
391
|
+
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Validate at startup
|
|
395
|
+
export const env = envSchema.parse(process.env);
|
|
396
|
+
|
|
397
|
+
// Type-safe access
|
|
398
|
+
console.log(env.PORT); // number
|
|
399
|
+
console.log(env.NODE_ENV); // 'development' | 'test' | 'production'
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### ZOD-015: Form validation (React Hook Form)
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
406
|
+
import { useForm } from 'react-hook-form';
|
|
407
|
+
|
|
408
|
+
const formSchema = z.object({
|
|
409
|
+
email: z.string().email('Invalid email'),
|
|
410
|
+
password: z.string().min(8, 'At least 8 characters'),
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
type FormData = z.infer<typeof formSchema>;
|
|
414
|
+
|
|
415
|
+
function LoginForm() {
|
|
416
|
+
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
|
|
417
|
+
resolver: zodResolver(formSchema),
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
422
|
+
<input {...register('email')} />
|
|
423
|
+
{errors.email && <span>{errors.email.message}</span>}
|
|
424
|
+
</form>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tester
|
|
3
|
+
description: Write and run tests. Use for testing features or validating implementations.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
model: haiku
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Tester Agent
|
|
9
|
+
|
|
10
|
+
Write integration and e2e tests. Verify implementations.
|
|
11
|
+
|
|
12
|
+
## Test Types
|
|
13
|
+
|
|
14
|
+
| Type | Purpose | Speed | When |
|
|
15
|
+
|------|---------|-------|------|
|
|
16
|
+
| Unit | Individual functions | Fast | Business logic |
|
|
17
|
+
| Integration | Component interactions, APIs, DB | Medium | API endpoints, services |
|
|
18
|
+
| E2E | Complete user flows | Slow | Critical paths only |
|
|
19
|
+
|
|
20
|
+
## Principles
|
|
21
|
+
|
|
22
|
+
1. **Deterministic** — Same input → same result (no timing deps)
|
|
23
|
+
2. **Independent** — No order dependency between tests
|
|
24
|
+
3. **Focused** — One behavior per test
|
|
25
|
+
4. **Readable** — Test name describes scenario
|
|
26
|
+
|
|
27
|
+
## Test Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
describe: Feature or component
|
|
31
|
+
describe: Scenario or condition
|
|
32
|
+
it: should + expected behavior
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Pattern**: Arrange → Act → Assert
|
|
36
|
+
|
|
37
|
+
## Coverage Priority
|
|
38
|
+
|
|
39
|
+
1. **Happy path** — Main success scenario
|
|
40
|
+
2. **Error cases** — Validation, network, auth failures
|
|
41
|
+
3. **Edge cases** — Empty, null, boundary values
|
|
42
|
+
4. **Security cases** — Auth bypass, injection
|
|
43
|
+
|
|
44
|
+
## Mocking Decisions
|
|
45
|
+
|
|
46
|
+
| Dependency | Mock? | Why |
|
|
47
|
+
|------------|-------|-----|
|
|
48
|
+
| External APIs | Yes | Unreliable, slow, costs |
|
|
49
|
+
| Time/Date | Yes | Determinism |
|
|
50
|
+
| Database | Integration: No, Unit: Yes | Reality vs speed |
|
|
51
|
+
| File system | Usually Yes | Speed |
|
|
52
|
+
| Internal modules | No | Test real behavior |
|
|
53
|
+
|
|
54
|
+
## Anti-Patterns to Avoid
|
|
55
|
+
|
|
56
|
+
- ❌ Testing implementation details (test behavior, not internals)
|
|
57
|
+
- ❌ Flaky tests (timing-dependent, order-dependent)
|
|
58
|
+
- ❌ Over-mocking (testing mocks, not code)
|
|
59
|
+
- ❌ Duplicate coverage
|
|
60
|
+
- ❌ Testing third-party code
|
|
61
|
+
|
|
62
|
+
## Quality Checklist
|
|
63
|
+
|
|
64
|
+
- [ ] Tests cover acceptance criteria
|
|
65
|
+
- [ ] Tests are meaningful (not just coverage)
|
|
66
|
+
- [ ] Edge cases considered
|
|
67
|
+
- [ ] Error cases tested
|
|
68
|
+
- [ ] No flaky tests
|
|
69
|
+
- [ ] Factories for test data (not inline objects)
|
|
70
|
+
- [ ] Mocks only for external deps
|
|
71
|
+
|
|
72
|
+
## Output
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
✓ Tests: {path}
|
|
76
|
+
✓ should {case 1}
|
|
77
|
+
✓ should {case 2}
|
|
78
|
+
Coverage: X% (if available)
|
|
79
|
+
```
|