ginskill-init 1.0.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 (92) hide show
  1. package/README.md +77 -0
  2. package/agents/developer.md +56 -0
  3. package/agents/frontend-design.md +69 -0
  4. package/agents/mobile-reviewer.md +36 -0
  5. package/agents/review-code.md +49 -0
  6. package/agents/security-scanner.md +50 -0
  7. package/agents/tester.md +72 -0
  8. package/bin/cli.js +226 -0
  9. package/package.json +20 -0
  10. package/skills/ai-asset-generator/SKILL.md +255 -0
  11. package/skills/ai-asset-generator/docs/gen-image.md +274 -0
  12. package/skills/ai-asset-generator/docs/genvideo.md +341 -0
  13. package/skills/ai-asset-generator/docs/remove-background.md +19 -0
  14. package/skills/ai-asset-generator/generate-credit-assets.mjs +180 -0
  15. package/skills/ai-asset-generator/generate-ginbrowser-assets.mjs +242 -0
  16. package/skills/ai-asset-generator/generate-sty-icon.mjs +149 -0
  17. package/skills/ai-asset-generator/lib/bg-remove.mjs +34 -0
  18. package/skills/ai-asset-generator/lib/env.mjs +38 -0
  19. package/skills/ai-asset-generator/lib/kie-client.mjs +88 -0
  20. package/skills/ai-asset-generator/scripts/scaffold-generator.mjs +203 -0
  21. package/skills/ai-build-ai/SKILL.md +124 -0
  22. package/skills/ai-build-ai/docs/agent-teams.md +293 -0
  23. package/skills/ai-build-ai/docs/checkpointing.md +161 -0
  24. package/skills/ai-build-ai/docs/create-agent.md +399 -0
  25. package/skills/ai-build-ai/docs/create-mcp.md +395 -0
  26. package/skills/ai-build-ai/docs/create-skill.md +299 -0
  27. package/skills/ai-build-ai/docs/headless-mode.md +614 -0
  28. package/skills/ai-build-ai/docs/hooks.md +578 -0
  29. package/skills/ai-build-ai/docs/memory-claude-md.md +375 -0
  30. package/skills/ai-build-ai/docs/output-styles.md +208 -0
  31. package/skills/ai-build-ai/docs/overview.md +162 -0
  32. package/skills/ai-build-ai/docs/permissions.md +391 -0
  33. package/skills/ai-build-ai/docs/plugins.md +396 -0
  34. package/skills/ai-build-ai/docs/sandbox.md +262 -0
  35. package/skills/ai-build-ai/scripts/load-tutorial.sh +54 -0
  36. package/skills/icon-generator/SKILL.md +270 -0
  37. package/skills/mobile-app-review/SKILL.md +321 -0
  38. package/skills/mobile-app-review/references/apple-review.md +132 -0
  39. package/skills/mobile-app-review/references/google-play-review.md +203 -0
  40. package/skills/mongodb/SKILL.md +667 -0
  41. package/skills/mongodb/references/mongoose-patterns.md +368 -0
  42. package/skills/nestjs-architecture/SKILL.md +1086 -0
  43. package/skills/nestjs-architecture/references/advanced-patterns.md +590 -0
  44. package/skills/performance/SKILL.md +509 -0
  45. package/skills/react-fsd-architecture/SKILL.md +693 -0
  46. package/skills/react-fsd-architecture/references/fsd-patterns.md +747 -0
  47. package/skills/react-query/SKILL.md +685 -0
  48. package/skills/react-query/references/query-patterns.md +365 -0
  49. package/skills/review-code/SKILL.md +321 -0
  50. package/skills/review-code/references/clean-code-principles.md +395 -0
  51. package/skills/review-code/references/frontend-patterns.md +136 -0
  52. package/skills/review-code/references/nestjs-patterns.md +184 -0
  53. package/skills/review-code/scripts/check-module.sh +201 -0
  54. package/skills/review-code/scripts/deep-scan.sh +604 -0
  55. package/skills/review-code/scripts/dep-check.sh +522 -0
  56. package/skills/review-code/scripts/detect-duplicates.sh +466 -0
  57. package/skills/review-code/scripts/format-check.sh +577 -0
  58. package/skills/review-code/scripts/run-review.sh +167 -0
  59. package/skills/review-code/scripts/scan-codebase.sh +152 -0
  60. package/skills/security-scanner/SKILL.md +327 -0
  61. package/skills/security-scanner/references/nestjs-security.md +260 -0
  62. package/skills/security-scanner/references/nextjs-security.md +201 -0
  63. package/skills/security-scanner/references/react-native-security.md +199 -0
  64. package/skills/security-scanner/scripts/security-scan.sh +478 -0
  65. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  66. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  67. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  68. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  69. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  70. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  71. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  72. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  73. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  74. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  75. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  76. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  77. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  78. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  79. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  80. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  81. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  82. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  83. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  84. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  85. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  86. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  87. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  88. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  89. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  90. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  91. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  92. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
@@ -0,0 +1,590 @@
1
+ # Advanced NestJS Architecture Patterns
2
+
3
+ Detailed reference for patterns beyond the main SKILL.md. Load this when the user works on advanced infrastructure.
4
+
5
+ ## Custom Decorators
6
+
7
+ ### Compose Multiple Decorators
8
+
9
+ ```typescript
10
+ // shared/decorators/auth.decorator.ts
11
+ import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'
12
+ import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger'
13
+
14
+ export function Auth(...roles: Role[]) {
15
+ return applyDecorators(
16
+ SetMetadata('roles', roles),
17
+ UseGuards(HybridAuthGuard, RolesGuard),
18
+ ApiBearerAuth(),
19
+ ApiUnauthorizedResponse({ description: 'Unauthorized' }),
20
+ )
21
+ }
22
+
23
+ // Usage — single decorator replaces 4 lines
24
+ @Auth(Role.ADMIN)
25
+ @Get('admin/users')
26
+ async getAdminUsers() { ... }
27
+ ```
28
+
29
+ ### Current User Decorator
30
+
31
+ ```typescript
32
+ // shared/decorators/current-user.decorator.ts
33
+ export const CurrentUser = createParamDecorator(
34
+ (field: keyof UserDocument | undefined, ctx: ExecutionContext) => {
35
+ const request = ctx.switchToHttp().getRequest()
36
+ const user = request.user
37
+ return field ? user?.[field] : user
38
+ },
39
+ )
40
+
41
+ // Usage
42
+ @Get('me')
43
+ async getProfile(@CurrentUser() user: UserDocument) { ... }
44
+
45
+ @Get('me/name')
46
+ async getName(@CurrentUser('firstName') name: string) { ... }
47
+ ```
48
+
49
+ ### API Field Selection Decorator
50
+
51
+ ```typescript
52
+ // shared/decorators/api-select.decorator.ts
53
+ export const ApiSelect = createParamDecorator(
54
+ (data: unknown, ctx: ExecutionContext): string => {
55
+ const request = ctx.switchToHttp().getRequest()
56
+ return request.query.fields || '' // 'name,email,avatar'
57
+ },
58
+ )
59
+
60
+ // Usage in service
61
+ async findAll(fields: string) {
62
+ const select = fields.split(',').join(' ')
63
+ return this.userModel.find().select(select).lean()
64
+ }
65
+ ```
66
+
67
+ ## Interceptors
68
+
69
+ ### Response Transform Interceptor
70
+
71
+ ```typescript
72
+ // shared/interceptors/transform.interceptor.ts
73
+ @Injectable()
74
+ export class TransformInterceptor<T> implements NestInterceptor<T, ResponseWrapper<T>> {
75
+ intercept(context: ExecutionContext, next: CallHandler): Observable<ResponseWrapper<T>> {
76
+ return next.handle().pipe(
77
+ map((data) => ({
78
+ success: true,
79
+ data,
80
+ timestamp: new Date().toISOString(),
81
+ })),
82
+ )
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Logging Interceptor
88
+
89
+ ```typescript
90
+ // shared/interceptors/logging.interceptor.ts
91
+ @Injectable()
92
+ export class LoggingInterceptor implements NestInterceptor {
93
+ private readonly logger = new Logger('HTTP')
94
+
95
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
96
+ const request = context.switchToHttp().getRequest()
97
+ const { method, url } = request
98
+ const start = Date.now()
99
+
100
+ return next.handle().pipe(
101
+ tap(() => {
102
+ const duration = Date.now() - start
103
+ this.logger.log(`${method} ${url} — ${duration}ms`)
104
+ }),
105
+ )
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### Cache Interceptor (Per-Route)
111
+
112
+ ```typescript
113
+ // shared/interceptors/cache.interceptor.ts
114
+ @Injectable()
115
+ export class HttpCacheInterceptor extends CacheInterceptor {
116
+ trackBy(context: ExecutionContext): string | undefined {
117
+ const request = context.switchToHttp().getRequest()
118
+ // Only cache GET requests for authenticated users
119
+ if (request.method !== 'GET') return undefined
120
+ return `${request.user?.id}:${request.url}`
121
+ }
122
+ }
123
+
124
+ // Usage
125
+ @UseInterceptors(HttpCacheInterceptor)
126
+ @CacheTTL(300) // 5 minutes
127
+ @Get()
128
+ async findAll() { ... }
129
+ ```
130
+
131
+ ## Middleware Patterns
132
+
133
+ ### Request Context Middleware
134
+
135
+ ```typescript
136
+ // shared/middleware/request-context.middleware.ts
137
+ @Injectable()
138
+ export class RequestContextMiddleware implements NestMiddleware {
139
+ use(req: Request, res: Response, next: NextFunction) {
140
+ // Attach unique request ID for tracing
141
+ req['requestId'] = req.headers['x-request-id'] || randomUUID()
142
+ res.setHeader('x-request-id', req['requestId'])
143
+
144
+ // Attach start time for duration tracking
145
+ req['startTime'] = Date.now()
146
+
147
+ next()
148
+ }
149
+ }
150
+
151
+ // Register in module
152
+ export class AppModule implements NestModule {
153
+ configure(consumer: MiddlewareConsumer) {
154
+ consumer.apply(RequestContextMiddleware).forRoutes('*')
155
+ }
156
+ }
157
+ ```
158
+
159
+ ### Conditional Middleware
160
+
161
+ ```typescript
162
+ // Apply middleware only to specific routes
163
+ export class AppModule implements NestModule {
164
+ configure(consumer: MiddlewareConsumer) {
165
+ consumer
166
+ .apply(RawBodyMiddleware)
167
+ .forRoutes({ path: 'webhooks/*', method: RequestMethod.POST })
168
+
169
+ consumer
170
+ .apply(LoggingMiddleware)
171
+ .exclude({ path: 'health', method: RequestMethod.GET })
172
+ .forRoutes('*')
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## API Versioning
178
+
179
+ ### URI Versioning (Recommended)
180
+
181
+ ```typescript
182
+ // main.ts
183
+ app.enableVersioning({
184
+ type: VersioningType.URI,
185
+ defaultVersion: '1',
186
+ prefix: 'api/v',
187
+ })
188
+
189
+ // Controller
190
+ @Controller({ path: 'users', version: '1' })
191
+ export class UserV1Controller { ... }
192
+
193
+ @Controller({ path: 'users', version: '2' })
194
+ export class UserV2Controller { ... }
195
+
196
+ // Routes:
197
+ // GET /api/v1/users → UserV1Controller
198
+ // GET /api/v2/users → UserV2Controller
199
+ ```
200
+
201
+ ### Version-Neutral Routes
202
+
203
+ ```typescript
204
+ // Health check available at /health (no version prefix)
205
+ @Controller({ path: 'health', version: VERSION_NEUTRAL })
206
+ export class HealthController { ... }
207
+ ```
208
+
209
+ ## Testing Strategies
210
+
211
+ ### Unit Test — Service
212
+
213
+ ```typescript
214
+ // features/order/order.service.spec.ts
215
+ describe('OrderService', () => {
216
+ let service: OrderService
217
+ let model: Model<OrderDocument>
218
+
219
+ beforeEach(async () => {
220
+ const module = await Test.createTestingModule({
221
+ providers: [
222
+ OrderService,
223
+ {
224
+ provide: getModelToken(Order.name),
225
+ useValue: {
226
+ find: jest.fn(),
227
+ findOne: jest.fn(),
228
+ create: jest.fn(),
229
+ countDocuments: jest.fn(),
230
+ },
231
+ },
232
+ {
233
+ provide: EventEmitter2,
234
+ useValue: { emit: jest.fn() },
235
+ },
236
+ ],
237
+ }).compile()
238
+
239
+ service = module.get(OrderService)
240
+ model = module.get(getModelToken(Order.name))
241
+ })
242
+
243
+ describe('findOneOrFail', () => {
244
+ it('should throw NotFoundException when order not found', async () => {
245
+ jest.spyOn(model, 'findOne').mockReturnValue({
246
+ lean: () => ({ exec: () => Promise.resolve(null) }),
247
+ } as any)
248
+
249
+ await expect(service.findOneOrFail('id', 'userId'))
250
+ .rejects.toThrow(NotFoundException)
251
+ })
252
+ })
253
+ })
254
+ ```
255
+
256
+ ### Integration Test — Controller
257
+
258
+ ```typescript
259
+ // features/order/order.controller.spec.ts
260
+ describe('OrderController (e2e)', () => {
261
+ let app: INestApplication
262
+ let mongoServer: MongoMemoryServer
263
+
264
+ beforeAll(async () => {
265
+ mongoServer = await MongoMemoryServer.create()
266
+
267
+ const module = await Test.createTestingModule({
268
+ imports: [
269
+ MongooseModule.forRoot(mongoServer.getUri()),
270
+ OrderModule,
271
+ ],
272
+ }).compile()
273
+
274
+ app = module.createNestApplication()
275
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }))
276
+ await app.init()
277
+ })
278
+
279
+ afterAll(async () => {
280
+ await app.close()
281
+ await mongoServer.stop()
282
+ })
283
+
284
+ it('POST /orders — should validate input', () => {
285
+ return request(app.getHttpServer())
286
+ .post('/orders')
287
+ .send({ items: [] }) // empty items should fail
288
+ .expect(422)
289
+ })
290
+
291
+ it('POST /orders — should create order', () => {
292
+ return request(app.getHttpServer())
293
+ .post('/orders')
294
+ .send({ items: [{ productId: 'abc', quantity: 2 }] })
295
+ .expect(201)
296
+ .expect((res) => {
297
+ expect(res.body.total).toBeDefined()
298
+ })
299
+ })
300
+ })
301
+ ```
302
+
303
+ ### Testing Queue Processors
304
+
305
+ ```typescript
306
+ describe('OrderFulfillmentProcessor', () => {
307
+ let processor: OrderFulfillmentProcessor
308
+
309
+ beforeEach(async () => {
310
+ const module = await Test.createTestingModule({
311
+ providers: [
312
+ OrderFulfillmentProcessor,
313
+ { provide: OrderService, useValue: { findById: jest.fn(), updateStatus: jest.fn() } },
314
+ { provide: NotificationService, useValue: { send: jest.fn() } },
315
+ ],
316
+ }).compile()
317
+
318
+ processor = module.get(OrderFulfillmentProcessor)
319
+ })
320
+
321
+ it('should process fulfillment job', async () => {
322
+ const mockJob = { data: { orderId: '123' }, progress: jest.fn() } as any
323
+ const result = await processor.handleFulfillment(mockJob)
324
+ expect(result.status).toBe('confirmed')
325
+ expect(mockJob.progress).toHaveBeenCalledWith(100)
326
+ })
327
+ })
328
+ ```
329
+
330
+ ## Docker Setup
331
+
332
+ ### Multi-Stage Dockerfile
333
+
334
+ ```dockerfile
335
+ # Stage 1: Build
336
+ FROM node:20-alpine AS builder
337
+ WORKDIR /app
338
+ COPY package.json package-lock.json ./
339
+ RUN npm ci --ignore-scripts
340
+ COPY . .
341
+ RUN npm run build
342
+ RUN npm prune --production
343
+
344
+ # Stage 2: Production
345
+ FROM node:20-alpine AS production
346
+ WORKDIR /app
347
+
348
+ RUN addgroup -g 1001 -S nestjs && adduser -S nestjs -u 1001
349
+ COPY --from=builder --chown=nestjs:nestjs /app/dist ./dist
350
+ COPY --from=builder --chown=nestjs:nestjs /app/node_modules ./node_modules
351
+ COPY --from=builder --chown=nestjs:nestjs /app/package.json ./
352
+
353
+ USER nestjs
354
+ EXPOSE 3000
355
+ CMD ["node", "dist/main.js"]
356
+ ```
357
+
358
+ ### Docker Compose (Development)
359
+
360
+ ```yaml
361
+ version: '3.8'
362
+ services:
363
+ api:
364
+ build: .
365
+ ports: ['3000:3000']
366
+ env_file: .env
367
+ depends_on:
368
+ mongodb:
369
+ condition: service_healthy
370
+ redis:
371
+ condition: service_healthy
372
+ volumes:
373
+ - ./uploads:/app/uploads
374
+
375
+ mongodb:
376
+ image: mongo:7
377
+ ports: ['27017:27017']
378
+ volumes: ['mongo-data:/data/db']
379
+ healthcheck:
380
+ test: mongosh --eval "db.adminCommand('ping')"
381
+ interval: 10s
382
+ timeout: 5s
383
+ retries: 3
384
+
385
+ redis:
386
+ image: redis:7-alpine
387
+ ports: ['6379:6379']
388
+ healthcheck:
389
+ test: redis-cli ping
390
+ interval: 10s
391
+ timeout: 5s
392
+ retries: 3
393
+
394
+ volumes:
395
+ mongo-data:
396
+ ```
397
+
398
+ ## Swagger / OpenAPI Setup
399
+
400
+ ```typescript
401
+ // core/config/swagger.config.ts
402
+ export function setupSwagger(app: INestApplication) {
403
+ const config = new DocumentBuilder()
404
+ .setTitle('API Documentation')
405
+ .setVersion('1.0')
406
+ .addBearerAuth({
407
+ type: 'http',
408
+ scheme: 'bearer',
409
+ bearerFormat: 'JWT',
410
+ })
411
+ .build()
412
+
413
+ const document = SwaggerModule.createDocument(app, config)
414
+ SwaggerModule.setup('docs', app, document, {
415
+ swaggerOptions: {
416
+ persistAuthorization: true,
417
+ tagsSorter: 'alpha',
418
+ operationsSorter: 'alpha',
419
+ },
420
+ })
421
+ }
422
+ ```
423
+
424
+ ### Swagger Decorators on DTOs
425
+
426
+ ```typescript
427
+ export class CreateUserDto {
428
+ @ApiProperty({ example: 'John', description: 'First name' })
429
+ @IsString()
430
+ @MinLength(2)
431
+ firstName: string
432
+
433
+ @ApiProperty({ example: 'john@example.com' })
434
+ @IsEmail()
435
+ email: string
436
+
437
+ @ApiPropertyOptional({ example: '+1234567890' })
438
+ @IsString()
439
+ @IsOptional()
440
+ phone?: string
441
+ }
442
+ ```
443
+
444
+ ## File Upload Pattern
445
+
446
+ ```typescript
447
+ // features/media/media.controller.ts
448
+ @Controller('media')
449
+ export class MediaController {
450
+ constructor(private readonly mediaService: MediaStorageService) {}
451
+
452
+ @Post('upload')
453
+ @Auth(Role.USER)
454
+ @UseInterceptors(FileInterceptor('file', {
455
+ limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
456
+ fileFilter: (req, file, cb) => {
457
+ const allowed = /\.(jpg|jpeg|png|webp|gif)$/i
458
+ if (!allowed.test(file.originalname)) {
459
+ return cb(new BadRequestException('Only image files allowed'), false)
460
+ }
461
+ cb(null, true)
462
+ },
463
+ }))
464
+ async upload(
465
+ @CurrentUser() user: UserDocument,
466
+ @UploadedFile() file: Express.Multer.File,
467
+ ) {
468
+ return this.mediaService.upload(file, { userId: user.id })
469
+ }
470
+ }
471
+ ```
472
+
473
+ ### Storage Abstraction (S3 / Local)
474
+
475
+ ```typescript
476
+ // features/media/services/media-storage.service.ts
477
+ @Injectable()
478
+ export class MediaStorageService {
479
+ private readonly strategy: StorageStrategy
480
+
481
+ constructor(private config: ConfigService) {
482
+ this.strategy = config.get('STORAGE_TYPE') === 's3'
483
+ ? new S3StorageStrategy(config)
484
+ : new LocalStorageStrategy(config)
485
+ }
486
+
487
+ async upload(file: Express.Multer.File, opts: UploadOpts): Promise<UploadResult> {
488
+ return this.strategy.upload(file, opts)
489
+ }
490
+
491
+ async delete(key: string): Promise<void> {
492
+ return this.strategy.delete(key)
493
+ }
494
+ }
495
+ ```
496
+
497
+ ## WebSocket Gateway
498
+
499
+ ```typescript
500
+ // features/chat/chat.gateway.ts
501
+ @WebSocketGateway({
502
+ cors: { origin: '*' },
503
+ namespace: '/chat',
504
+ })
505
+ export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
506
+ @WebSocketServer()
507
+ server: Server
508
+
509
+ private readonly logger = new Logger('ChatGateway')
510
+
511
+ handleConnection(client: Socket) {
512
+ this.logger.log(`Client connected: ${client.id}`)
513
+ }
514
+
515
+ handleDisconnect(client: Socket) {
516
+ this.logger.log(`Client disconnected: ${client.id}`)
517
+ }
518
+
519
+ @SubscribeMessage('message')
520
+ handleMessage(client: Socket, payload: { room: string; content: string }) {
521
+ this.server.to(payload.room).emit('message', {
522
+ sender: client.id,
523
+ content: payload.content,
524
+ timestamp: new Date(),
525
+ })
526
+ }
527
+ }
528
+ ```
529
+
530
+ ## CI/CD Patterns
531
+
532
+ ### Git Hooks (Husky + lint-staged)
533
+
534
+ ```json
535
+ // package.json
536
+ {
537
+ "lint-staged": {
538
+ "*.ts": ["eslint --fix", "prettier --write"]
539
+ }
540
+ }
541
+ ```
542
+
543
+ ```bash
544
+ # .husky/pre-commit
545
+ npx lint-staged
546
+
547
+ # .husky/commit-msg
548
+ npx commitlint --edit $1
549
+ ```
550
+
551
+ ### Commitlint Config
552
+
553
+ ```javascript
554
+ // commitlint.config.js
555
+ module.exports = {
556
+ extends: ['@commitlint/config-conventional'],
557
+ rules: {
558
+ 'type-enum': [2, 'always', [
559
+ 'feat', 'fix', 'docs', 'style', 'refactor',
560
+ 'perf', 'test', 'build', 'ci', 'chore', 'revert',
561
+ ]],
562
+ 'subject-max-length': [2, 'always', 100],
563
+ },
564
+ }
565
+ ```
566
+
567
+ ## Scaling Considerations
568
+
569
+ ### Horizontal Scaling Checklist
570
+
571
+ - [ ] API layer is stateless (no in-memory sessions)
572
+ - [ ] Session/auth state stored in Redis
573
+ - [ ] File uploads go to S3 (not local disk)
574
+ - [ ] Queue workers can run as separate processes
575
+ - [ ] Database connection pool sized per instance
576
+ - [ ] Health checks return instance-specific metrics
577
+ - [ ] Graceful shutdown handles SIGTERM
578
+ - [ ] Sticky sessions disabled (or WebSocket uses Redis adapter)
579
+
580
+ ### Extracting Microservices
581
+
582
+ When a feature outgrows the monolith:
583
+
584
+ 1. The feature already has its own module with clear boundaries
585
+ 2. Extract the module into a standalone NestJS app
586
+ 3. Replace direct imports with HTTP/gRPC/message queue calls
587
+ 4. Events already decouple side effects — minimal rewiring needed
588
+ 5. Shared DTOs/interfaces move to a common package
589
+
590
+ This is the strength of feature-based architecture: each module is already a microservice boundary waiting to be extracted.