@malamute/ai-rules 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 (46) hide show
  1. package/README.md +174 -0
  2. package/bin/cli.js +5 -0
  3. package/configs/_shared/.claude/commands/fix-issue.md +38 -0
  4. package/configs/_shared/.claude/commands/generate-tests.md +49 -0
  5. package/configs/_shared/.claude/commands/review-pr.md +77 -0
  6. package/configs/_shared/.claude/rules/accessibility.md +270 -0
  7. package/configs/_shared/.claude/rules/performance.md +226 -0
  8. package/configs/_shared/.claude/rules/security.md +188 -0
  9. package/configs/_shared/.claude/skills/debug/SKILL.md +118 -0
  10. package/configs/_shared/.claude/skills/learning/SKILL.md +224 -0
  11. package/configs/_shared/.claude/skills/review/SKILL.md +86 -0
  12. package/configs/_shared/.claude/skills/spec/SKILL.md +112 -0
  13. package/configs/_shared/CLAUDE.md +174 -0
  14. package/configs/angular/.claude/rules/components.md +257 -0
  15. package/configs/angular/.claude/rules/state.md +250 -0
  16. package/configs/angular/.claude/rules/testing.md +422 -0
  17. package/configs/angular/.claude/settings.json +31 -0
  18. package/configs/angular/CLAUDE.md +251 -0
  19. package/configs/dotnet/.claude/rules/api.md +370 -0
  20. package/configs/dotnet/.claude/rules/architecture.md +199 -0
  21. package/configs/dotnet/.claude/rules/database/efcore.md +408 -0
  22. package/configs/dotnet/.claude/rules/testing.md +389 -0
  23. package/configs/dotnet/.claude/settings.json +9 -0
  24. package/configs/dotnet/CLAUDE.md +319 -0
  25. package/configs/nestjs/.claude/rules/auth.md +321 -0
  26. package/configs/nestjs/.claude/rules/database/prisma.md +305 -0
  27. package/configs/nestjs/.claude/rules/database/typeorm.md +379 -0
  28. package/configs/nestjs/.claude/rules/modules.md +215 -0
  29. package/configs/nestjs/.claude/rules/testing.md +315 -0
  30. package/configs/nestjs/.claude/rules/validation.md +279 -0
  31. package/configs/nestjs/.claude/settings.json +15 -0
  32. package/configs/nestjs/CLAUDE.md +263 -0
  33. package/configs/nextjs/.claude/rules/components.md +211 -0
  34. package/configs/nextjs/.claude/rules/state/redux-toolkit.md +429 -0
  35. package/configs/nextjs/.claude/rules/state/zustand.md +299 -0
  36. package/configs/nextjs/.claude/rules/testing.md +315 -0
  37. package/configs/nextjs/.claude/settings.json +29 -0
  38. package/configs/nextjs/CLAUDE.md +376 -0
  39. package/configs/python/.claude/rules/database/sqlalchemy.md +355 -0
  40. package/configs/python/.claude/rules/fastapi.md +272 -0
  41. package/configs/python/.claude/rules/flask.md +332 -0
  42. package/configs/python/.claude/rules/testing.md +374 -0
  43. package/configs/python/.claude/settings.json +18 -0
  44. package/configs/python/CLAUDE.md +273 -0
  45. package/package.json +41 -0
  46. package/src/install.js +315 -0
@@ -0,0 +1,315 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.spec.ts"
4
+ - "test/**/*.e2e-spec.ts"
5
+ ---
6
+
7
+ # NestJS Testing
8
+
9
+ ## Unit Tests
10
+
11
+ ### Test Services in Isolation
12
+
13
+ Mock all dependencies. Focus on business logic.
14
+
15
+ ```typescript
16
+ import { Test, TestingModule } from '@nestjs/testing';
17
+ import { UsersService } from './users.service';
18
+ import { UsersRepository } from './users.repository';
19
+ import { NotFoundException } from '@nestjs/common';
20
+
21
+ describe('UsersService', () => {
22
+ let service: UsersService;
23
+ let repository: jest.Mocked<UsersRepository>;
24
+
25
+ beforeEach(async () => {
26
+ const module: TestingModule = await Test.createTestingModule({
27
+ providers: [
28
+ UsersService,
29
+ {
30
+ provide: UsersRepository,
31
+ useValue: {
32
+ findById: jest.fn(),
33
+ create: jest.fn(),
34
+ update: jest.fn(),
35
+ delete: jest.fn(),
36
+ },
37
+ },
38
+ ],
39
+ }).compile();
40
+
41
+ service = module.get<UsersService>(UsersService);
42
+ repository = module.get(UsersRepository);
43
+ });
44
+
45
+ describe('findOne', () => {
46
+ it('should return user when found', async () => {
47
+ const user = { id: '1', email: 'test@example.com' };
48
+ repository.findById.mockResolvedValue(user);
49
+
50
+ const result = await service.findOne('1');
51
+
52
+ expect(result).toEqual(user);
53
+ expect(repository.findById).toHaveBeenCalledWith('1');
54
+ });
55
+
56
+ it('should throw NotFoundException when user not found', async () => {
57
+ repository.findById.mockResolvedValue(null);
58
+
59
+ await expect(service.findOne('1')).rejects.toThrow(NotFoundException);
60
+ });
61
+ });
62
+ });
63
+ ```
64
+
65
+ ### Test Controllers
66
+
67
+ Test request handling, pipes, guards.
68
+
69
+ ```typescript
70
+ import { Test, TestingModule } from '@nestjs/testing';
71
+ import { UsersController } from './users.controller';
72
+ import { UsersService } from './users.service';
73
+
74
+ describe('UsersController', () => {
75
+ let controller: UsersController;
76
+ let service: jest.Mocked<UsersService>;
77
+
78
+ beforeEach(async () => {
79
+ const module: TestingModule = await Test.createTestingModule({
80
+ controllers: [UsersController],
81
+ providers: [
82
+ {
83
+ provide: UsersService,
84
+ useValue: {
85
+ findOne: jest.fn(),
86
+ create: jest.fn(),
87
+ },
88
+ },
89
+ ],
90
+ }).compile();
91
+
92
+ controller = module.get<UsersController>(UsersController);
93
+ service = module.get(UsersService);
94
+ });
95
+
96
+ describe('findOne', () => {
97
+ it('should return user from service', async () => {
98
+ const user = { id: '1', email: 'test@example.com' };
99
+ service.findOne.mockResolvedValue(user);
100
+
101
+ const result = await controller.findOne('1');
102
+
103
+ expect(result).toEqual(user);
104
+ expect(service.findOne).toHaveBeenCalledWith('1');
105
+ });
106
+ });
107
+ });
108
+ ```
109
+
110
+ ### Mock Repository Pattern
111
+
112
+ ```typescript
113
+ // Create reusable mock factory
114
+ export const createMockRepository = <T = any>() => ({
115
+ find: jest.fn(),
116
+ findOne: jest.fn(),
117
+ create: jest.fn(),
118
+ save: jest.fn(),
119
+ update: jest.fn(),
120
+ delete: jest.fn(),
121
+ createQueryBuilder: jest.fn(() => ({
122
+ where: jest.fn().mockReturnThis(),
123
+ andWhere: jest.fn().mockReturnThis(),
124
+ orderBy: jest.fn().mockReturnThis(),
125
+ skip: jest.fn().mockReturnThis(),
126
+ take: jest.fn().mockReturnThis(),
127
+ getMany: jest.fn(),
128
+ getOne: jest.fn(),
129
+ })),
130
+ });
131
+
132
+ // Usage
133
+ const module = await Test.createTestingModule({
134
+ providers: [
135
+ UsersService,
136
+ {
137
+ provide: getRepositoryToken(User),
138
+ useValue: createMockRepository(),
139
+ },
140
+ ],
141
+ }).compile();
142
+ ```
143
+
144
+ ## E2E Tests
145
+
146
+ ### Setup Test Application
147
+
148
+ ```typescript
149
+ // test/app.e2e-spec.ts
150
+ import { Test, TestingModule } from '@nestjs/testing';
151
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
152
+ import * as request from 'supertest';
153
+ import { AppModule } from '../src/app.module';
154
+
155
+ describe('AppController (e2e)', () => {
156
+ let app: INestApplication;
157
+
158
+ beforeAll(async () => {
159
+ const moduleFixture: TestingModule = await Test.createTestingModule({
160
+ imports: [AppModule],
161
+ }).compile();
162
+
163
+ app = moduleFixture.createNestApplication();
164
+
165
+ // Apply same pipes as production
166
+ app.useGlobalPipes(
167
+ new ValidationPipe({
168
+ whitelist: true,
169
+ forbidNonWhitelisted: true,
170
+ transform: true,
171
+ }),
172
+ );
173
+
174
+ await app.init();
175
+ });
176
+
177
+ afterAll(async () => {
178
+ await app.close();
179
+ });
180
+
181
+ describe('/users', () => {
182
+ it('POST /users - should create user', () => {
183
+ return request(app.getHttpServer())
184
+ .post('/users')
185
+ .send({
186
+ email: 'test@example.com',
187
+ password: 'password123',
188
+ })
189
+ .expect(201)
190
+ .expect((res) => {
191
+ expect(res.body).toHaveProperty('id');
192
+ expect(res.body.email).toBe('test@example.com');
193
+ });
194
+ });
195
+
196
+ it('POST /users - should reject invalid email', () => {
197
+ return request(app.getHttpServer())
198
+ .post('/users')
199
+ .send({
200
+ email: 'invalid-email',
201
+ password: 'password123',
202
+ })
203
+ .expect(400);
204
+ });
205
+
206
+ it('GET /users/:id - should return user', () => {
207
+ return request(app.getHttpServer())
208
+ .get('/users/existing-id')
209
+ .expect(200)
210
+ .expect((res) => {
211
+ expect(res.body).toHaveProperty('email');
212
+ });
213
+ });
214
+
215
+ it('GET /users/:id - should return 404 for unknown user', () => {
216
+ return request(app.getHttpServer())
217
+ .get('/users/unknown-id')
218
+ .expect(404);
219
+ });
220
+ });
221
+ });
222
+ ```
223
+
224
+ ### Test with Authentication
225
+
226
+ ```typescript
227
+ describe('Protected Routes (e2e)', () => {
228
+ let app: INestApplication;
229
+ let authToken: string;
230
+
231
+ beforeAll(async () => {
232
+ // ... setup app
233
+
234
+ // Get auth token
235
+ const response = await request(app.getHttpServer())
236
+ .post('/auth/login')
237
+ .send({ email: 'test@example.com', password: 'password' });
238
+
239
+ authToken = response.body.accessToken;
240
+ });
241
+
242
+ it('GET /profile - should return user profile with token', () => {
243
+ return request(app.getHttpServer())
244
+ .get('/profile')
245
+ .set('Authorization', `Bearer ${authToken}`)
246
+ .expect(200);
247
+ });
248
+
249
+ it('GET /profile - should return 401 without token', () => {
250
+ return request(app.getHttpServer())
251
+ .get('/profile')
252
+ .expect(401);
253
+ });
254
+ });
255
+ ```
256
+
257
+ ### Test Database Isolation
258
+
259
+ Use a test database and clean between tests:
260
+
261
+ ```typescript
262
+ import { DataSource } from 'typeorm';
263
+
264
+ describe('Users E2E', () => {
265
+ let app: INestApplication;
266
+ let dataSource: DataSource;
267
+
268
+ beforeAll(async () => {
269
+ const module = await Test.createTestingModule({
270
+ imports: [AppModule],
271
+ }).compile();
272
+
273
+ app = module.createNestApplication();
274
+ await app.init();
275
+
276
+ dataSource = module.get(DataSource);
277
+ });
278
+
279
+ beforeEach(async () => {
280
+ // Clean database before each test
281
+ await dataSource.synchronize(true);
282
+ });
283
+
284
+ afterAll(async () => {
285
+ await dataSource.destroy();
286
+ await app.close();
287
+ });
288
+ });
289
+ ```
290
+
291
+ ## Test Coverage Goals
292
+
293
+ - **Services**: 90%+ (business logic)
294
+ - **Controllers**: 80%+ (happy paths + error cases)
295
+ - **Guards/Pipes**: 80%+
296
+ - **E2E**: Cover all API endpoints
297
+
298
+ ## Testing Commands
299
+
300
+ ```bash
301
+ # Unit tests
302
+ npm run test
303
+
304
+ # Watch mode
305
+ npm run test:watch
306
+
307
+ # Coverage report
308
+ npm run test:cov
309
+
310
+ # E2E tests
311
+ npm run test:e2e
312
+
313
+ # Single file
314
+ npm run test -- users.service.spec.ts
315
+ ```
@@ -0,0 +1,279 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.dto.ts"
4
+ - "src/**/dto/*.ts"
5
+ ---
6
+
7
+ # NestJS Validation & DTOs
8
+
9
+ ## DTO Rules
10
+
11
+ ### Always Use class-validator
12
+
13
+ Every DTO property must have validation decorators.
14
+
15
+ ```typescript
16
+ import {
17
+ IsEmail,
18
+ IsString,
19
+ IsOptional,
20
+ MinLength,
21
+ MaxLength,
22
+ IsUUID,
23
+ IsEnum,
24
+ IsArray,
25
+ ValidateNested,
26
+ IsInt,
27
+ Min,
28
+ Max,
29
+ } from 'class-validator';
30
+ import { Type } from 'class-transformer';
31
+
32
+ export class CreateUserDto {
33
+ @IsEmail()
34
+ email: string;
35
+
36
+ @IsString()
37
+ @MinLength(8)
38
+ @MaxLength(100)
39
+ password: string;
40
+
41
+ @IsString()
42
+ @IsOptional()
43
+ @MaxLength(50)
44
+ name?: string;
45
+
46
+ @IsEnum(UserRole)
47
+ @IsOptional()
48
+ role?: UserRole;
49
+ }
50
+ ```
51
+
52
+ ### Use Mapped Types for Variants
53
+
54
+ ```typescript
55
+ import { PartialType, PickType, OmitType, IntersectionType } from '@nestjs/mapped-types';
56
+
57
+ // All fields optional
58
+ export class UpdateUserDto extends PartialType(CreateUserDto) {}
59
+
60
+ // Only specific fields
61
+ export class LoginDto extends PickType(CreateUserDto, ['email', 'password']) {}
62
+
63
+ // Exclude fields
64
+ export class PublicUserDto extends OmitType(CreateUserDto, ['password']) {}
65
+
66
+ // Combine DTOs
67
+ export class CreateUserWithAddressDto extends IntersectionType(
68
+ CreateUserDto,
69
+ CreateAddressDto,
70
+ ) {}
71
+ ```
72
+
73
+ ### Validate Nested Objects
74
+
75
+ ```typescript
76
+ export class CreateOrderDto {
77
+ @IsUUID()
78
+ userId: string;
79
+
80
+ @IsArray()
81
+ @ValidateNested({ each: true })
82
+ @Type(() => OrderItemDto)
83
+ items: OrderItemDto[];
84
+
85
+ @ValidateNested()
86
+ @Type(() => AddressDto)
87
+ shippingAddress: AddressDto;
88
+ }
89
+
90
+ export class OrderItemDto {
91
+ @IsUUID()
92
+ productId: string;
93
+
94
+ @IsInt()
95
+ @Min(1)
96
+ @Max(100)
97
+ quantity: number;
98
+ }
99
+ ```
100
+
101
+ ### Transform Input Data
102
+
103
+ ```typescript
104
+ import { Transform, Type } from 'class-transformer';
105
+
106
+ export class QueryDto {
107
+ @IsOptional()
108
+ @Type(() => Number)
109
+ @IsInt()
110
+ @Min(1)
111
+ page?: number = 1;
112
+
113
+ @IsOptional()
114
+ @Type(() => Number)
115
+ @IsInt()
116
+ @Min(1)
117
+ @Max(100)
118
+ limit?: number = 20;
119
+
120
+ @IsOptional()
121
+ @Transform(({ value }) => value?.toLowerCase().trim())
122
+ @IsString()
123
+ search?: string;
124
+
125
+ @IsOptional()
126
+ @Transform(({ value }) => value === 'true' || value === true)
127
+ @IsBoolean()
128
+ active?: boolean;
129
+ }
130
+ ```
131
+
132
+ ## Validation Groups
133
+
134
+ Use groups for conditional validation:
135
+
136
+ ```typescript
137
+ export class UserDto {
138
+ @IsUUID({ groups: ['update'] })
139
+ @IsOptional({ groups: ['create'] })
140
+ id?: string;
141
+
142
+ @IsEmail({ groups: ['create', 'update'] })
143
+ email: string;
144
+
145
+ @IsString({ groups: ['create'] })
146
+ @MinLength(8, { groups: ['create'] })
147
+ password: string;
148
+ }
149
+
150
+ // Controller usage
151
+ @Post()
152
+ create(
153
+ @Body(new ValidationPipe({ groups: ['create'] }))
154
+ dto: UserDto,
155
+ ) {}
156
+
157
+ @Patch(':id')
158
+ update(
159
+ @Body(new ValidationPipe({ groups: ['update'] }))
160
+ dto: UserDto,
161
+ ) {}
162
+ ```
163
+
164
+ ## Custom Validators
165
+
166
+ ```typescript
167
+ import {
168
+ ValidatorConstraint,
169
+ ValidatorConstraintInterface,
170
+ ValidationArguments,
171
+ registerDecorator,
172
+ } from 'class-validator';
173
+
174
+ @ValidatorConstraint({ async: true })
175
+ export class IsUniqueEmailConstraint implements ValidatorConstraintInterface {
176
+ constructor(private readonly usersService: UsersService) {}
177
+
178
+ async validate(email: string): Promise<boolean> {
179
+ const user = await this.usersService.findByEmail(email);
180
+ return !user;
181
+ }
182
+
183
+ defaultMessage(args: ValidationArguments): string {
184
+ return 'Email already exists';
185
+ }
186
+ }
187
+
188
+ // Decorator factory
189
+ export function IsUniqueEmail(validationOptions?: ValidationOptions) {
190
+ return function (object: object, propertyName: string) {
191
+ registerDecorator({
192
+ target: object.constructor,
193
+ propertyName,
194
+ options: validationOptions,
195
+ constraints: [],
196
+ validator: IsUniqueEmailConstraint,
197
+ });
198
+ };
199
+ }
200
+
201
+ // Usage
202
+ export class CreateUserDto {
203
+ @IsEmail()
204
+ @IsUniqueEmail()
205
+ email: string;
206
+ }
207
+ ```
208
+
209
+ ## API Documentation with Swagger
210
+
211
+ Combine validation with OpenAPI documentation:
212
+
213
+ ```typescript
214
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
215
+
216
+ export class CreateUserDto {
217
+ @ApiProperty({
218
+ example: 'user@example.com',
219
+ description: 'User email address',
220
+ })
221
+ @IsEmail()
222
+ email: string;
223
+
224
+ @ApiProperty({
225
+ minLength: 8,
226
+ description: 'User password (min 8 characters)',
227
+ })
228
+ @IsString()
229
+ @MinLength(8)
230
+ password: string;
231
+
232
+ @ApiPropertyOptional({
233
+ example: 'John Doe',
234
+ maxLength: 50,
235
+ })
236
+ @IsString()
237
+ @IsOptional()
238
+ @MaxLength(50)
239
+ name?: string;
240
+ }
241
+ ```
242
+
243
+ ## Response DTOs
244
+
245
+ Use separate DTOs for responses to control exposed data:
246
+
247
+ ```typescript
248
+ import { Exclude, Expose, Type } from 'class-transformer';
249
+
250
+ export class UserResponseDto {
251
+ @Expose()
252
+ id: string;
253
+
254
+ @Expose()
255
+ email: string;
256
+
257
+ @Expose()
258
+ name: string;
259
+
260
+ @Exclude()
261
+ password: string;
262
+
263
+ @Expose()
264
+ @Type(() => Date)
265
+ createdAt: Date;
266
+
267
+ constructor(partial: Partial<UserResponseDto>) {
268
+ Object.assign(this, partial);
269
+ }
270
+ }
271
+
272
+ // Service usage with ClassSerializerInterceptor
273
+ @UseInterceptors(ClassSerializerInterceptor)
274
+ @Get(':id')
275
+ async findOne(@Param('id') id: string): Promise<UserResponseDto> {
276
+ const user = await this.usersService.findOne(id);
277
+ return new UserResponseDto(user);
278
+ }
279
+ ```
@@ -0,0 +1,15 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(npm run start:dev)",
5
+ "Bash(npm run build)",
6
+ "Bash(npm run test*)",
7
+ "Bash(npm run lint*)",
8
+ "Bash(npm run format*)",
9
+ "Bash(npx prisma *)",
10
+ "Bash(npx typeorm *)",
11
+ "Bash(npm install *)"
12
+ ],
13
+ "deny": []
14
+ }
15
+ }