create-loadout 1.0.2 → 1.0.4

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/dist/claude-md.js CHANGED
@@ -99,10 +99,10 @@ export async function generateClaudeMd(projectPath, config) {
99
99
  const hasPostHog = config.integrations.includes('posthog');
100
100
  const hasSentry = config.integrations.includes('sentry');
101
101
  const hasClerk = config.integrations.includes('clerk');
102
- let content = `# ${config.name}
103
-
104
- ## Tech Stack
105
-
102
+ let content = `# ${config.name}
103
+
104
+ ## Tech Stack
105
+
106
106
  `;
107
107
  for (const section of sections) {
108
108
  content += `### ${section.name}\n`;
@@ -111,368 +111,539 @@ export async function generateClaudeMd(projectPath, config) {
111
111
  }
112
112
  content += '\n';
113
113
  }
114
- content += `## Development Commands
115
-
116
- \`\`\`bash
117
- npm run dev # Start development server
118
- npm run build # Build for production
119
- npm run start # Start production server
120
- npm run lint # Run ESLint
121
- \`\`\`
114
+ content += `## Development Commands
115
+
116
+ \`\`\`bash
117
+ npm run dev # Start development server
118
+ npm run build # Build for production
119
+ npm run start # Start production server
120
+ npm run lint # Run ESLint
121
+ \`\`\`
122
122
  `;
123
123
  if (hasDb) {
124
- content += `
125
- ### Database Commands
126
-
127
- \`\`\`bash
128
- npm run db:generate # Generate migrations from schema changes
129
- npm run db:migrate # Apply migrations to database
130
- npm run db:studio # Open Drizzle Studio to browse data
131
- \`\`\`
124
+ content += `
125
+ ### Database Commands
126
+
127
+ \`\`\`bash
128
+ npm run db:generate # Generate migrations from schema changes
129
+ npm run db:migrate # Apply migrations to database
130
+ npm run db:studio # Open Drizzle Studio to browse data
131
+ \`\`\`
132
132
  `;
133
133
  }
134
134
  if (config.integrations.includes('inngest')) {
135
- content += `
136
- ### Background Jobs
137
-
138
- \`\`\`bash
139
- npm run inngest:dev # Start Inngest dev server for local testing
140
- \`\`\`
135
+ content += `
136
+ ### Background Jobs
137
+
138
+ \`\`\`bash
139
+ npm run inngest:dev # Start Inngest dev server for local testing
140
+ \`\`\`
141
141
  `;
142
142
  }
143
- content += `
144
- ## Architecture
145
-
146
- ### Directory Structure
147
-
148
- \`\`\`
149
- ├── app/ # Next.js App Router pages and API routes
150
- ├── components/ # React components (including shadcn/ui)
143
+ content += `
144
+ ## Architecture
145
+
146
+ ### Directory Structure
147
+
148
+ \`\`\`
149
+ ├── app/ # Next.js App Router pages and API routes
150
+ ├── components/ # React components (including shadcn/ui)
151
151
  `;
152
152
  if (config.integrations.includes('resend') || config.integrations.includes('postmark')) {
153
- content += `│ └── emails/ # Email templates
153
+ content += `│ └── emails/ # Email templates
154
154
  `;
155
155
  }
156
156
  if (hasPostHog || hasSentry) {
157
- content += `├── instrumentation-client.ts # Client-side init
157
+ content += `├── instrumentation-client.ts # Client-side init
158
158
  `;
159
159
  }
160
160
  if (hasSentry) {
161
- content += `├── instrumentation.ts # Server-side Sentry registration
161
+ content += `├── instrumentation.ts # Server-side Sentry registration
162
162
  `;
163
163
  }
164
164
  if (hasDb) {
165
- content += `├── actions/ # Server actions (form submissions)
166
- │ └── *.actions.ts
167
- ├── services/ # Business logic orchestration
168
- │ └── *.service.ts
169
- ├── dao/ # Data access objects (database queries)
170
- │ └── *.dao.ts
171
- ├── mappers/ # Data transformation
172
- │ └── *.mapper.ts
173
- ├── models/ # Type definitions
174
- │ ├── *.dto.ts # Database types (InferSelectModel)
175
- │ ├── *.view.ts # View models for UI
176
- │ ├── *.schema.ts # Zod validation + ServiceRequest/Result
177
- │ ├── *.state.ts # Action state objects
178
- │ └── *ServiceError.enum.ts # Service error enums
165
+ content += `├── actions/ # Server actions (form submissions)
166
+ │ └── *.actions.ts
167
+ ├── services/ # Business logic orchestration
168
+ │ └── *.service.ts
169
+ ├── dao/ # Data access objects (database queries)
170
+ │ └── *.dao.ts
171
+ ├── mappers/ # Data transformation
172
+ │ └── *.mapper.ts
173
+ ├── models/ # Type definitions
174
+ │ ├── *.dto.ts # Database types (InferSelectModel)
175
+ │ ├── *.view.ts # View models for UI
176
+ │ ├── *.schema.ts # Zod validation + ServiceRequest/Result
177
+ │ ├── *.state.ts # Action state objects
178
+ │ └── *ServiceError.enum.ts # Service error enums
179
179
  `;
180
180
  }
181
181
  else {
182
- content += `├── services/ # Business logic services
183
- │ └── *.service.ts
182
+ content += `├── services/ # Business logic services
183
+ │ └── *.service.ts
184
184
  `;
185
185
  }
186
- content += `├── lib/
187
- │ ├── config.ts # Centralized environment variables
188
- │ ├── stores/ # Zustand stores for client state
189
- │ │ └── *.store.ts
186
+ content += `├── lib/
187
+ │ ├── config.ts # Centralized environment variables
188
+ │ ├── stores/ # Zustand stores for client state
189
+ │ │ └── *.store.ts
190
190
  `;
191
191
  if (hasDb) {
192
- content += `│ └── db/ # Database client and schema
192
+ content += `│ └── db/ # Database client and schema
193
193
  `;
194
194
  }
195
- content += `└── public/ # Static assets
196
- \`\`\`
195
+ content += `└── public/ # Static assets
196
+ \`\`\`
197
197
  `;
198
198
  if (hasDb) {
199
- content += `
200
- ### Layered Architecture
201
-
202
- The application follows a strict 4-layer architecture:
203
-
204
- \`\`\`
205
- UI Components (app/, components/)
206
-
207
- Server Actions (actions/*.actions.ts)
208
-
209
- Services (services/*.service.ts)
210
-
211
- DAOs (dao/*.dao.ts)
212
-
213
- Database (Drizzle ORM)
214
- \`\`\`
215
-
216
- **Layer responsibilities:**
217
-
218
- - **Actions** - Handle form submissions, validate with Zod, check auth, call services, revalidate cache
219
- - **Services** - Orchestrate business logic, coordinate multiple DAOs
220
- - **DAOs** - Encapsulate all database queries using Drizzle ORM
221
- - **Mappers** - Transform between DTOs, service requests, and view models
222
-
223
- ### Model File Naming Conventions
224
-
225
- Files in \`models/\` follow strict naming:
226
-
227
- | Pattern | Purpose | Example |
228
- |---------|---------|---------|
229
- | \`*.dto.ts\` | Database types from Drizzle | \`UserDto\`, \`UserInsertDto\` |
230
- | \`*.view.ts\` | View models for UI | \`UserView\` |
231
- | \`*.schema.ts\` | Zod schemas + service types | \`UserCreateFormSchema\`, \`UserCreateServiceRequest\` |
232
- | \`*.state.ts\` | Action state objects | \`UserCreateState\` |
233
- | \`*ServiceError.enum.ts\` | Service error enums | \`UserServiceError\` |
234
-
235
- ### Action File Organization
236
-
237
- One action file per domain entity: \`actions/{entity}.action.ts\`. Do NOT split by operation type.
238
-
239
- \`\`\`
240
- actions/
241
- project.action.ts # createProject, updateProject, deleteProject, searchProjects
242
- task.action.ts # createTask, updateTask, deleteTask, searchTasks
243
- comment.action.ts # createComment, deleteComment
244
- settings.action.ts # updateSettings
245
- \`\`\`
246
-
247
- Do NOT create separate files like \`project.create.action.ts\` or \`task.search.action.ts\`.
248
-
249
- ### Server Action Pattern
250
-
251
- \`\`\`typescript
252
- 'use server';
253
-
254
- export async function createEntity(
255
- state: EntityCreateState,
256
- formData: FormData
257
- ): Promise<EntityCreateState> {
258
- const user = await currentUser();
259
- if (!user) {
260
- return { success: false, error: 'Not authenticated', data: null };
261
- }
262
-
263
- const rawData = Object.fromEntries(formData);
264
- const validated = EntityCreateSchema.safeParse(rawData);
265
-
266
- if (!validated.success) {
267
- return { success: false, error: z.prettifyError(validated.error), data: null };
268
- }
269
-
270
- try {
271
- const result = await entityService.createEntity(validated.data);
272
- revalidatePath('/entities');
273
- return { success: true, error: null, data: result };
274
- } catch (error) {
275
- return { success: false, error: 'Failed to create entity', data: null };
276
- }
277
- }
278
- \`\`\`
279
-
280
- ### DAO Pattern
281
-
282
- \`\`\`typescript
283
- export class EntityDAO {
284
- async create(dto: EntityInsertDto): Promise<EntityDto | undefined> {
285
- const [created] = await db.insert(entities).values(dto).returning();
286
- return created;
287
- }
288
-
289
- async getById(id: string): Promise<EntityDto | undefined> {
290
- return await db.query.entities.findFirst({
291
- where: eq(entities.id, id),
292
- });
293
- }
294
- }
295
-
296
- export const entityDAO = new EntityDAO();
297
- \`\`\`
298
-
299
- ### Service Error Enums
300
-
301
- Each service class has a corresponding error enum in \`models/{serviceName}ServiceError.enum.ts\`. Services throw errors using enum values, actions catch and translate to user-friendly messages.
302
-
303
- \`\`\`typescript
304
- // models/performanceServiceError.enum.ts
305
- export enum PerformanceServiceError {
306
- NotFound = "PERFORMANCE_NOT_FOUND",
307
- NotOwned = "PERFORMANCE_NOT_OWNED",
308
- DuplicateTime = "PERFORMANCE_DUPLICATE_TIME",
309
- }
310
-
311
- // services/performance.service.ts
312
- import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
313
-
314
- if (conflict) {
315
- throw new Error(PerformanceServiceError.DuplicateTime);
316
- }
317
-
318
- // actions/performance.action.ts
319
- import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
320
-
321
- catch (error) {
322
- if (error instanceof Error) {
323
- switch (error.message) {
324
- case PerformanceServiceError.DuplicateTime:
325
- return { success: false, error: 'A performance already exists at this time', data: null };
326
- case PerformanceServiceError.NotFound:
327
- return { success: false, error: 'Performance not found', data: null };
328
- case PerformanceServiceError.NotOwned:
329
- return { success: false, error: 'You do not have permission to modify this performance', data: null };
330
- }
331
- }
332
- return { success: false, error: 'Failed to update performance', data: null };
333
- }
334
- \`\`\`
199
+ content += `
200
+ ### Layered Architecture
201
+
202
+ The application follows a strict 4-layer architecture:
203
+
204
+ \`\`\`
205
+ UI Components (app/, components/)
206
+
207
+ Server Actions (actions/*.actions.ts)
208
+
209
+ Services (services/*.service.ts)
210
+
211
+ DAOs (dao/*.dao.ts)
212
+
213
+ Database (Drizzle ORM)
214
+ \`\`\`
215
+
216
+ **Layer responsibilities:**
217
+
218
+ - **Actions** - Handle form submissions, validate with Zod, check auth, call services, revalidate cache
219
+ - **Services** - Orchestrate business logic, coordinate multiple DAOs
220
+ - **DAOs** - Encapsulate all database queries using Drizzle ORM
221
+ - **Mappers** - Transform between DTOs, service requests, and view models
222
+
223
+ ### Model File Naming Conventions
224
+
225
+ Files in \`models/\` follow strict naming:
226
+
227
+ | Pattern | Purpose | Example |
228
+ |---------|---------|---------|
229
+ | \`*.dto.ts\` | Database types from Drizzle | \`UserDto\`, \`UserInsertDto\` |
230
+ | \`*.view.ts\` | View models for UI | \`UserView\` |
231
+ | \`*.schema.ts\` | Zod schemas + service types | \`UserCreateFormSchema\`, \`UserCreateServiceRequest\` |
232
+ | \`*.state.ts\` | Action state objects | \`UserCreateState\` |
233
+ | \`*ServiceError.enum.ts\` | Service error enums | \`UserServiceError\` |
234
+
235
+ ### Action File Organization
236
+
237
+ One action file per domain entity: \`actions/{entity}.action.ts\`. Do NOT split by operation type.
238
+
239
+ \`\`\`
240
+ actions/
241
+ project.action.ts # createProject, updateProject, deleteProject, searchProjects
242
+ task.action.ts # createTask, updateTask, deleteTask, searchTasks
243
+ comment.action.ts # createComment, deleteComment
244
+ settings.action.ts # updateSettings
245
+ \`\`\`
246
+
247
+ Do NOT create separate files like \`project.create.action.ts\` or \`task.search.action.ts\`.
248
+
249
+ ### Server Action Pattern
250
+
251
+ \`\`\`typescript
252
+ 'use server';
253
+
254
+ export async function createEntity(
255
+ state: EntityCreateState,
256
+ formData: FormData
257
+ ): Promise<EntityCreateState> {
258
+ const user = await currentUser();
259
+ if (!user) {
260
+ return { success: false, error: 'Not authenticated', data: null };
261
+ }
262
+
263
+ const rawData = Object.fromEntries(formData);
264
+ const validated = EntityCreateSchema.safeParse(rawData);
265
+
266
+ if (!validated.success) {
267
+ return { success: false, error: z.prettifyError(validated.error), data: null };
268
+ }
269
+
270
+ try {
271
+ const result = await entityService.createEntity(validated.data);
272
+ revalidatePath('/entities');
273
+ return { success: true, error: null, data: result };
274
+ } catch (error) {
275
+ return { success: false, error: 'Failed to create entity', data: null };
276
+ }
277
+ }
278
+ \`\`\`
279
+
280
+ ### DAO Pattern
281
+
282
+ \`\`\`typescript
283
+ export class EntityDAO {
284
+ async create(dto: EntityInsertDto): Promise<EntityDto | undefined> {
285
+ const [created] = await db.insert(entities).values(dto).returning();
286
+ return created;
287
+ }
288
+
289
+ async getById(id: string): Promise<EntityDto | undefined> {
290
+ return await db.query.entities.findFirst({
291
+ where: eq(entities.id, id),
292
+ });
293
+ }
294
+ }
295
+
296
+ export const entityDAO = new EntityDAO();
297
+ \`\`\`
298
+
299
+ ### Service Error Enums
300
+
301
+ Each service class has a corresponding error enum in \`models/{serviceName}ServiceError.enum.ts\`. Services throw errors using enum values, actions catch and translate to user-friendly messages.
302
+
303
+ \`\`\`typescript
304
+ // models/performanceServiceError.enum.ts
305
+ export enum PerformanceServiceError {
306
+ NotFound = "PERFORMANCE_NOT_FOUND",
307
+ NotOwned = "PERFORMANCE_NOT_OWNED",
308
+ DuplicateTime = "PERFORMANCE_DUPLICATE_TIME",
309
+ }
310
+
311
+ // services/performance.service.ts
312
+ import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
313
+
314
+ if (conflict) {
315
+ throw new Error(PerformanceServiceError.DuplicateTime);
316
+ }
317
+
318
+ // actions/performance.action.ts
319
+ import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
320
+
321
+ catch (error) {
322
+ if (error instanceof Error) {
323
+ switch (error.message) {
324
+ case PerformanceServiceError.DuplicateTime:
325
+ return { success: false, error: 'A performance already exists at this time', data: null };
326
+ case PerformanceServiceError.NotFound:
327
+ return { success: false, error: 'Performance not found', data: null };
328
+ case PerformanceServiceError.NotOwned:
329
+ return { success: false, error: 'You do not have permission to modify this performance', data: null };
330
+ }
331
+ }
332
+ return { success: false, error: 'Failed to update performance', data: null };
333
+ }
334
+ \`\`\`
335
+
336
+ ### No Rogue Types
337
+
338
+ Types must come from Zod schemas (\`z.infer<typeof Schema>\`) or \`models/\` files. Do NOT define interfaces in component or action files.
339
+
340
+ \`\`\`typescript
341
+ // ❌ Wrong - interface defined in component
342
+ interface TodoItem {
343
+ id: string;
344
+ title: string;
345
+ }
346
+
347
+ // ✅ Correct - import from schema or models
348
+ import { type TodoCreatePayload } from "@/models/todoCreate.schema";
349
+ \`\`\`
350
+
351
+ ### Service Request/Result Patterns
352
+
353
+ **ServiceRequest** - Derive from form schema payload, extend with context:
354
+
355
+ \`\`\`typescript
356
+ // ✅ Correct - derive from schema, extend with additional fields
357
+ export type TodoCreateServiceRequest = TodoCreateFormPayload & { userId: string };
358
+
359
+ // ✅ Correct - direct assignment when no additions needed
360
+ export type TodoUpdateServiceRequest = TodoUpdateFormPayload;
361
+
362
+ // ❌ Wrong - manually retyping all fields
363
+ export type TodoCreateServiceRequest = {
364
+ title: string;
365
+ description?: string;
366
+ userId: string;
367
+ };
368
+ \`\`\`
369
+
370
+ **ServiceResult** - Only for compound returns (2+ fields). Do NOT wrap single values:
371
+
372
+ \`\`\`typescript
373
+ // ❌ Wrong - overkill wrapper for single value
374
+ export type TodoCreateServiceResult = { todoId: string };
375
+
376
+ // ✅ Correct - return directly
377
+ async createTodo(request): Promise<string> {
378
+ return created.id;
379
+ }
380
+
381
+ // ✅ Correct - compound data warrants a type
382
+ export type SearchServiceResult = {
383
+ items: ItemDto[];
384
+ totalCount: number;
385
+ };
386
+ \`\`\`
387
+
388
+ **Simple gets** - No ServiceRequest/ServiceResult. Let the signature be the contract:
389
+
390
+ \`\`\`typescript
391
+ findById(id: string): Promise<TodoDto | null>
392
+ findByUserId(userId: string): Promise<TodoDto[]>
393
+ \`\`\`
394
+
395
+ ### Action Success/Error Handling
396
+
397
+ Do NOT use \`useEffect\` to react to \`useActionState\` results. Wrap the action in a \`useCallback\` that handles side effects inline.
398
+
399
+ \`\`\`typescript
400
+ // ❌ Wrong - useEffect watching action state
401
+ useEffect(() => {
402
+ if (createState.success) {
403
+ toast.success("Created");
404
+ setDialogOpen(false);
405
+ }
406
+ }, [createState]);
407
+
408
+ // ✅ Correct - wrapper handles side effects inline
409
+ const handleCreateAction = useCallback(async (prevState: State, formData: FormData) => {
410
+ const result = await createThing(prevState, formData);
411
+ if (result.success) {
412
+ toast.success("Created");
413
+ setDialogOpen(false);
414
+ }
415
+ if (result.error) {
416
+ toast.error(result.error);
417
+ }
418
+ return result;
419
+ }, []);
420
+
421
+ const [createState, createFormAction, isCreatePending] = useActionState(
422
+ handleCreateAction,
423
+ initialState
424
+ );
425
+ \`\`\`
426
+
427
+ ### Error Logging in Actions
428
+
429
+ Use format \`actions/{filename}/{functionName}:\` for console.error calls:
430
+
431
+ \`\`\`typescript
432
+ console.error("actions/todo.actions.ts/createTodo:", error);
433
+ \`\`\`
335
434
  `;
336
435
  }
337
- content += `
338
- ## Client State Management (Zustand)
339
-
340
- For complex multi-step forms or flows, use Zustand stores.
341
-
342
- **Store location**: \`lib/stores/*.store.ts\`
343
-
344
- ### Store Pattern
345
-
346
- \`\`\`typescript
347
- import { createStore, useStore } from 'zustand';
348
-
349
- interface FormState {
350
- title: string;
351
- description: string;
352
- setTitle: (title: string) => void;
353
- setDescription: (description: string) => void;
354
- reset: () => void;
355
- }
356
-
357
- const initialState = {
358
- title: '',
359
- description: '',
360
- };
361
-
362
- export const formStore = createStore<FormState>()((set) => ({
363
- ...initialState,
364
- setTitle: (title) => set({ title }),
365
- setDescription: (description) => set({ description }),
366
- reset: () => set(initialState),
367
- }));
368
-
369
- export const useFormStore = <T>(selector: (state: FormState) => T): T => {
370
- return useStore(formStore, selector);
371
- };
372
- \`\`\`
373
-
374
- ### Usage in Components
375
-
376
- \`\`\`tsx
377
- 'use client';
378
-
379
- import { Input } from '@/components/ui/input';
380
- import { Label } from '@/components/ui/label';
381
-
382
- function TitleStep() {
383
- const title = useFormStore((state) => state.title);
384
- const setTitle = useFormStore((state) => state.setTitle);
385
-
386
- return (
387
- <div className="space-y-2">
388
- <Label htmlFor="title">Title</Label>
389
- <Input
390
- id="title"
391
- value={title}
392
- onChange={(e) => setTitle(e.target.value)}
393
- placeholder="Enter a title"
394
- />
395
- </div>
396
- );
397
- }
398
- \`\`\`
399
-
400
- ## Code Style
401
-
402
- ### No Comments
403
-
404
- Do not add comments to the codebase. Code should be self-documenting through:
405
- - Clear, descriptive variable and function names
406
- - Proper TypeScript types
407
- - Logical code structure
408
- - Small, focused functions
409
-
410
- ### Closure Variable Naming
411
-
412
- Always use verbose singular names in closures (\`.map()\`, \`.filter()\`, etc.):
413
-
414
- \`\`\`typescript
415
- // ✅ Correct
416
- users.map((user) => user.email)
417
- items.filter((item) => item.isActive)
418
-
419
- // ❌ Wrong
420
- users.map((u) => u.email)
421
- items.filter((i) => i.isActive)
422
- \`\`\`
423
-
424
- ## Utility Functions
425
-
426
- Import from \`@/lib/utils\`:
427
-
428
- \`\`\`typescript
429
- import { cn, formatDate, formatRelative, debounce } from '@/lib/utils';
430
-
431
- // Class name merging (shadcn/ui)
432
- cn('text-sm', isActive && 'text-blue-500')
433
-
434
- // Date formatting with Luxon
435
- formatDate(new Date()) // "Jan 15, 2024"
436
- formatDate('2024-01-15', 'yyyy-MM-dd') // "2024-01-15"
437
- formatRelative(new Date()) // "2 hours ago"
438
-
439
- // Debounce function calls
440
- const debouncedSearch = debounce((query: string) => {
441
- // search logic
442
- }, 300);
443
- \`\`\`
444
-
445
- ## Environment Variables
446
-
447
- Copy \`.env.example\` to \`.env.local\` and fill in your API keys.
448
-
449
- **Important:** Import environment variables from \`@/lib/config\`:
450
-
451
- \`\`\`typescript
452
- // ✅ Good
453
- import { STRIPE_SECRET_KEY } from '@/lib/config';
454
-
455
- // ❌ Avoid
456
- const key = process.env.STRIPE_SECRET_KEY;
457
- \`\`\`
436
+ content += `
437
+ ## Client State Management (Zustand)
438
+
439
+ For complex multi-step forms or flows, use Zustand stores.
440
+
441
+ **Store location**: \`lib/stores/*.store.ts\`
442
+
443
+ ### Store Pattern
444
+
445
+ \`\`\`typescript
446
+ import { createStore, useStore } from 'zustand';
447
+
448
+ interface FormState {
449
+ title: string;
450
+ description: string;
451
+ setTitle: (title: string) => void;
452
+ setDescription: (description: string) => void;
453
+ reset: () => void;
454
+ }
455
+
456
+ const initialState = {
457
+ title: '',
458
+ description: '',
459
+ };
460
+
461
+ export const formStore = createStore<FormState>()((set) => ({
462
+ ...initialState,
463
+ setTitle: (title) => set({ title }),
464
+ setDescription: (description) => set({ description }),
465
+ reset: () => set(initialState),
466
+ }));
467
+
468
+ export const useFormStore = <T>(selector: (state: FormState) => T): T => {
469
+ return useStore(formStore, selector);
470
+ };
471
+ \`\`\`
472
+
473
+ ### Usage in Components
474
+
475
+ \`\`\`tsx
476
+ 'use client';
477
+
478
+ import { Input } from '@/components/ui/input';
479
+ import { Label } from '@/components/ui/label';
480
+
481
+ function TitleStep() {
482
+ const title = useFormStore((state) => state.title);
483
+ const setTitle = useFormStore((state) => state.setTitle);
484
+
485
+ return (
486
+ <div className="space-y-2">
487
+ <Label htmlFor="title">Title</Label>
488
+ <Input
489
+ id="title"
490
+ value={title}
491
+ onChange={(e) => setTitle(e.target.value)}
492
+ placeholder="Enter a title"
493
+ />
494
+ </div>
495
+ );
496
+ }
497
+ \`\`\`
498
+
499
+ ## Code Style
500
+
501
+ ### No Comments
502
+
503
+ Do not add comments to the codebase. Code should be self-documenting through:
504
+ - Clear, descriptive variable and function names
505
+ - Proper TypeScript types
506
+ - Logical code structure
507
+ - Small, focused functions
508
+
509
+ ### Closure Variable Naming
510
+
511
+ Always use verbose singular names in closures (\`.map()\`, \`.filter()\`, etc.):
512
+
513
+ \`\`\`typescript
514
+ // ✅ Correct
515
+ users.map((user) => user.email)
516
+ items.filter((item) => item.isActive)
517
+
518
+ // ❌ Wrong
519
+ users.map((u) => u.email)
520
+ items.filter((i) => i.isActive)
521
+ \`\`\`
522
+
523
+ ### Server vs Client Components
524
+
525
+ Page files (\`page.tsx\`) must be server components - do NOT add \`"use client"\` to pages. Extract interactive parts into separate client component files.
526
+
527
+ \`\`\`
528
+ app/settings/
529
+ page.tsx # Server component (NO "use client")
530
+ components/
531
+ settings-form.tsx # Client component ("use client")
532
+ app/dashboard/components/ # Shared components for sibling pages
533
+ \`\`\`
534
+
535
+ ### No Module-Scope Mutable State
536
+
537
+ Do NOT use \`let\` variables at module scope. Modules are cached and state leaks between SSR requests.
538
+
539
+ \`\`\`typescript
540
+ // Wrong - module-scope mutable state
541
+ let nextId = 1;
542
+
543
+ // ✅ Correct - generate inline
544
+ crypto.randomUUID();
545
+ \`\`\`
546
+
547
+ ### User-Friendly Errors
548
+
549
+ All error messages shown to users must be actionable and understandable. Do NOT expose technical errors. Log technical details to \`console.error()\`, show friendly messages to users.
550
+
551
+ \`\`\`typescript
552
+ // Wrong - technical error
553
+ return { error: "User ID is missing from request" };
554
+
555
+ // Correct - user-friendly
556
+ return { error: "Something went wrong. Please try again." };
557
+ \`\`\`
558
+
559
+ ### Date Manipulation with Luxon
560
+
561
+ Use Luxon for all date manipulation. Do NOT manually construct ISO date strings.
562
+
563
+ \`\`\`typescript
564
+ // ❌ Wrong - manual string construction
565
+ const dateFrom = \`\${year}-\${String(month).padStart(2, "0")}-01\`;
566
+
567
+ // ✅ Correct - use Luxon
568
+ import { DateTime } from "luxon";
569
+ const startOfMonth = DateTime.local(year, month, 1);
570
+ const dateFrom = startOfMonth.toISODate()!;
571
+ const dateTo = startOfMonth.plus({ months: 1 }).toISODate()!;
572
+ \`\`\`
573
+
574
+ ### Zod Patterns
575
+
576
+ Use \`z.iso.date()\` for ISO date strings (NOT the deprecated \`z.string().date()\`).
577
+
578
+ Use \`z.enum(TheEnum)\` for enum validation (NOT \`z.nativeEnum\` or \`Object.values\`).
579
+
580
+ For JSON array fields in form schemas, use \`z.preprocess\`:
581
+
582
+ \`\`\`typescript
583
+ // ✅ Correct - preprocess handles JSON, Zod validates the array
584
+ export const MyFormSchema = z.object({
585
+ items: z.preprocess(
586
+ (val) => (typeof val === "string" ? JSON.parse(val) : val),
587
+ ItemSchema.array()
588
+ ),
589
+ });
590
+
591
+ // ❌ Wrong - transform with manual error handling
592
+ items: z.string().transform((val, ctx) => { ... ctx.addIssue(...) })
593
+ \`\`\`
594
+
595
+ ## Utility Functions
596
+
597
+ Import from \`@/lib/utils\`:
598
+
599
+ \`\`\`typescript
600
+ import { cn, formatDate, formatRelative, debounce } from '@/lib/utils';
601
+
602
+ // Class name merging (shadcn/ui)
603
+ cn('text-sm', isActive && 'text-blue-500')
604
+
605
+ // Date formatting with Luxon
606
+ formatDate(new Date()) // "Jan 15, 2024"
607
+ formatDate('2024-01-15', 'yyyy-MM-dd') // "2024-01-15"
608
+ formatRelative(new Date()) // "2 hours ago"
609
+
610
+ // Debounce function calls
611
+ const debouncedSearch = debounce((query: string) => {
612
+ // search logic
613
+ }, 300);
614
+ \`\`\`
615
+
616
+ ## Environment Variables
617
+
618
+ Copy \`.env.example\` to \`.env\` and fill in your API keys.
619
+
620
+ **Important:** Import environment variables from \`@/lib/config\`:
621
+
622
+ \`\`\`typescript
623
+ // ✅ Good
624
+ import { STRIPE_SECRET_KEY } from '@/lib/config';
625
+
626
+ // ❌ Avoid
627
+ const key = process.env.STRIPE_SECRET_KEY;
628
+ \`\`\`
458
629
  `;
459
630
  if (hasClerk) {
460
- content += `
461
- ## Authentication
462
-
463
- **Provider:** Clerk
464
-
465
- - Route protection via \`proxy.ts\` (Next.js 16+)
466
- - Use \`currentUser()\` in Server Components and Actions for auth checks
467
- - Client-side: Use Clerk hooks (\`useUser()\`, \`useAuth()\`, \`<SignedIn>\`, \`<SignedOut>\`)
468
-
469
- \`\`\`typescript
470
- // Server-side auth check
471
- const user = await currentUser();
472
- if (!user) {
473
- return redirect('/');
474
- }
475
- \`\`\`
631
+ content += `
632
+ ## Authentication
633
+
634
+ **Provider:** Clerk
635
+
636
+ - Route protection via \`proxy.ts\` (Next.js 16+)
637
+ - Use \`currentUser()\` in Server Components and Actions for auth checks
638
+ - Client-side: Use Clerk hooks (\`useUser()\`, \`useAuth()\`, \`<SignedIn>\`, \`<SignedOut>\`)
639
+
640
+ \`\`\`typescript
641
+ // Server-side auth check
642
+ const user = await currentUser();
643
+ if (!user) {
644
+ return redirect('/');
645
+ }
646
+ \`\`\`
476
647
  `;
477
648
  }
478
649
  await fs.writeFile(path.join(projectPath, 'CLAUDE.md'), content);