@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,379 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.entity.ts"
4
+ - "src/**/*.repository.ts"
5
+ - "src/**/typeorm*.ts"
6
+ ---
7
+
8
+ # NestJS with TypeORM
9
+
10
+ ## Setup
11
+
12
+ ### TypeORM Module
13
+
14
+ ```typescript
15
+ // app.module.ts
16
+ import { TypeOrmModule } from '@nestjs/typeorm';
17
+
18
+ @Module({
19
+ imports: [
20
+ TypeOrmModule.forRootAsync({
21
+ inject: [ConfigService],
22
+ useFactory: (config: ConfigService) => ({
23
+ type: 'postgres',
24
+ host: config.getOrThrow('DB_HOST'),
25
+ port: config.get('DB_PORT', 5432),
26
+ username: config.getOrThrow('DB_USER'),
27
+ password: config.getOrThrow('DB_PASSWORD'),
28
+ database: config.getOrThrow('DB_NAME'),
29
+ entities: [__dirname + '/**/*.entity{.ts,.js}'],
30
+ synchronize: config.get('NODE_ENV') !== 'production',
31
+ logging: config.get('NODE_ENV') === 'development',
32
+ }),
33
+ }),
34
+ ],
35
+ })
36
+ export class AppModule {}
37
+ ```
38
+
39
+ ## Entity Design
40
+
41
+ ### Entity Conventions
42
+
43
+ ```typescript
44
+ import {
45
+ Entity,
46
+ PrimaryGeneratedColumn,
47
+ Column,
48
+ CreateDateColumn,
49
+ UpdateDateColumn,
50
+ ManyToOne,
51
+ OneToMany,
52
+ JoinColumn,
53
+ Index,
54
+ } from 'typeorm';
55
+
56
+ @Entity('users')
57
+ export class User {
58
+ @PrimaryGeneratedColumn('uuid')
59
+ id: string;
60
+
61
+ @Column({ unique: true })
62
+ @Index()
63
+ email: string;
64
+
65
+ @Column()
66
+ password: string;
67
+
68
+ @Column({ nullable: true })
69
+ name?: string;
70
+
71
+ @Column({
72
+ type: 'enum',
73
+ enum: ['user', 'admin'],
74
+ default: 'user',
75
+ })
76
+ role: 'user' | 'admin';
77
+
78
+ @CreateDateColumn({ name: 'created_at' })
79
+ createdAt: Date;
80
+
81
+ @UpdateDateColumn({ name: 'updated_at' })
82
+ updatedAt: Date;
83
+
84
+ @OneToMany(() => Post, (post) => post.author)
85
+ posts: Post[];
86
+ }
87
+
88
+ @Entity('posts')
89
+ export class Post {
90
+ @PrimaryGeneratedColumn('uuid')
91
+ id: string;
92
+
93
+ @Column()
94
+ title: string;
95
+
96
+ @Column({ type: 'text', nullable: true })
97
+ content?: string;
98
+
99
+ @Column({ default: false })
100
+ published: boolean;
101
+
102
+ @ManyToOne(() => User, (user) => user.posts, { onDelete: 'CASCADE' })
103
+ @JoinColumn({ name: 'author_id' })
104
+ author: User;
105
+
106
+ @Column({ name: 'author_id' })
107
+ @Index()
108
+ authorId: string;
109
+
110
+ @CreateDateColumn({ name: 'created_at' })
111
+ createdAt: Date;
112
+
113
+ @UpdateDateColumn({ name: 'updated_at' })
114
+ updatedAt: Date;
115
+ }
116
+ ```
117
+
118
+ ### Naming Conventions
119
+
120
+ - Entities: PascalCase (`User`, `BlogPost`)
121
+ - Properties: camelCase (`createdAt`, `authorId`)
122
+ - Tables: snake_case via `@Entity('users')`
123
+ - Columns: snake_case via `{ name: 'created_at' }`
124
+
125
+ ## Repository Pattern
126
+
127
+ ### Custom Repository
128
+
129
+ ```typescript
130
+ // users/users.repository.ts
131
+ import { Injectable } from '@nestjs/common';
132
+ import { InjectRepository } from '@nestjs/typeorm';
133
+ import { Repository } from 'typeorm';
134
+ import { User } from './entities/user.entity';
135
+
136
+ @Injectable()
137
+ export class UsersRepository {
138
+ constructor(
139
+ @InjectRepository(User)
140
+ private readonly repository: Repository<User>,
141
+ ) {}
142
+
143
+ async findById(id: string): Promise<User | null> {
144
+ return this.repository.findOne({ where: { id } });
145
+ }
146
+
147
+ async findByEmail(email: string): Promise<User | null> {
148
+ return this.repository.findOne({ where: { email } });
149
+ }
150
+
151
+ async findMany(options: {
152
+ skip?: number;
153
+ take?: number;
154
+ where?: Partial<User>;
155
+ }): Promise<User[]> {
156
+ return this.repository.find(options);
157
+ }
158
+
159
+ async create(data: Partial<User>): Promise<User> {
160
+ const user = this.repository.create(data);
161
+ return this.repository.save(user);
162
+ }
163
+
164
+ async update(id: string, data: Partial<User>): Promise<User> {
165
+ await this.repository.update(id, data);
166
+ return this.findById(id);
167
+ }
168
+
169
+ async delete(id: string): Promise<void> {
170
+ await this.repository.delete(id);
171
+ }
172
+ }
173
+ ```
174
+
175
+ ### Module Registration
176
+
177
+ ```typescript
178
+ // users/users.module.ts
179
+ import { Module } from '@nestjs/common';
180
+ import { TypeOrmModule } from '@nestjs/typeorm';
181
+ import { User } from './entities/user.entity';
182
+ import { UsersRepository } from './users.repository';
183
+ import { UsersService } from './users.service';
184
+
185
+ @Module({
186
+ imports: [TypeOrmModule.forFeature([User])],
187
+ providers: [UsersRepository, UsersService],
188
+ exports: [UsersService],
189
+ })
190
+ export class UsersModule {}
191
+ ```
192
+
193
+ ## Query Patterns
194
+
195
+ ### QueryBuilder
196
+
197
+ ```typescript
198
+ async findWithFilters(filters: UserFilters) {
199
+ const qb = this.repository.createQueryBuilder('user');
200
+
201
+ if (filters.search) {
202
+ qb.andWhere(
203
+ '(user.name ILIKE :search OR user.email ILIKE :search)',
204
+ { search: `%${filters.search}%` },
205
+ );
206
+ }
207
+
208
+ if (filters.role) {
209
+ qb.andWhere('user.role = :role', { role: filters.role });
210
+ }
211
+
212
+ if (filters.createdAfter) {
213
+ qb.andWhere('user.createdAt >= :date', { date: filters.createdAfter });
214
+ }
215
+
216
+ return qb
217
+ .orderBy('user.createdAt', 'DESC')
218
+ .skip(filters.skip ?? 0)
219
+ .take(filters.take ?? 20)
220
+ .getMany();
221
+ }
222
+ ```
223
+
224
+ ### Relations
225
+
226
+ ```typescript
227
+ // Eager loading
228
+ const user = await this.repository.findOne({
229
+ where: { id },
230
+ relations: ['posts', 'profile'],
231
+ });
232
+
233
+ // QueryBuilder with relations
234
+ const users = await this.repository
235
+ .createQueryBuilder('user')
236
+ .leftJoinAndSelect('user.posts', 'post', 'post.published = :published', {
237
+ published: true,
238
+ })
239
+ .where('user.role = :role', { role: 'admin' })
240
+ .getMany();
241
+ ```
242
+
243
+ ### Pagination
244
+
245
+ ```typescript
246
+ async findPaginated(page: number, limit: number) {
247
+ const [data, total] = await this.repository.findAndCount({
248
+ skip: (page - 1) * limit,
249
+ take: limit,
250
+ order: { createdAt: 'DESC' },
251
+ });
252
+
253
+ return {
254
+ data,
255
+ meta: {
256
+ total,
257
+ page,
258
+ limit,
259
+ totalPages: Math.ceil(total / limit),
260
+ },
261
+ };
262
+ }
263
+ ```
264
+
265
+ ### Transactions
266
+
267
+ ```typescript
268
+ import { DataSource } from 'typeorm';
269
+
270
+ @Injectable()
271
+ export class TransferService {
272
+ constructor(private readonly dataSource: DataSource) {}
273
+
274
+ async transferCredits(fromId: string, toId: string, amount: number) {
275
+ return this.dataSource.transaction(async (manager) => {
276
+ const sender = await manager.findOne(User, { where: { id: fromId } });
277
+ const receiver = await manager.findOne(User, { where: { id: toId } });
278
+
279
+ if (sender.credits < amount) {
280
+ throw new Error('Insufficient credits');
281
+ }
282
+
283
+ sender.credits -= amount;
284
+ receiver.credits += amount;
285
+
286
+ await manager.save([sender, receiver]);
287
+
288
+ return { success: true };
289
+ });
290
+ }
291
+ }
292
+ ```
293
+
294
+ ### Soft Delete
295
+
296
+ ```typescript
297
+ import { DeleteDateColumn } from 'typeorm';
298
+
299
+ @Entity('users')
300
+ export class User {
301
+ // ...
302
+
303
+ @DeleteDateColumn({ name: 'deleted_at' })
304
+ deletedAt?: Date;
305
+ }
306
+
307
+ // Soft delete
308
+ await this.repository.softDelete(id);
309
+
310
+ // Restore
311
+ await this.repository.restore(id);
312
+
313
+ // Include soft-deleted in query
314
+ await this.repository.find({ withDeleted: true });
315
+ ```
316
+
317
+ ## Migrations
318
+
319
+ ```bash
320
+ # Generate migration from entity changes
321
+ npx typeorm migration:generate src/migrations/AddUsersTable -d src/data-source.ts
322
+
323
+ # Create empty migration
324
+ npx typeorm migration:create src/migrations/SeedData
325
+
326
+ # Run migrations
327
+ npx typeorm migration:run -d src/data-source.ts
328
+
329
+ # Revert last migration
330
+ npx typeorm migration:revert -d src/data-source.ts
331
+ ```
332
+
333
+ ### Migration Example
334
+
335
+ ```typescript
336
+ import { MigrationInterface, QueryRunner } from 'typeorm';
337
+
338
+ export class AddUsersTable1234567890 implements MigrationInterface {
339
+ public async up(queryRunner: QueryRunner): Promise<void> {
340
+ await queryRunner.query(`
341
+ CREATE TABLE users (
342
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
343
+ email VARCHAR(255) UNIQUE NOT NULL,
344
+ password VARCHAR(255) NOT NULL,
345
+ created_at TIMESTAMP DEFAULT NOW()
346
+ )
347
+ `);
348
+ }
349
+
350
+ public async down(queryRunner: QueryRunner): Promise<void> {
351
+ await queryRunner.query(`DROP TABLE users`);
352
+ }
353
+ }
354
+ ```
355
+
356
+ ## Testing with TypeORM
357
+
358
+ ```typescript
359
+ import { getRepositoryToken } from '@nestjs/typeorm';
360
+
361
+ const mockRepository = {
362
+ find: jest.fn(),
363
+ findOne: jest.fn(),
364
+ create: jest.fn(),
365
+ save: jest.fn(),
366
+ update: jest.fn(),
367
+ delete: jest.fn(),
368
+ };
369
+
370
+ const module = await Test.createTestingModule({
371
+ providers: [
372
+ UsersService,
373
+ {
374
+ provide: getRepositoryToken(User),
375
+ useValue: mockRepository,
376
+ },
377
+ ],
378
+ }).compile();
379
+ ```
@@ -0,0 +1,215 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.module.ts"
4
+ - "src/**/*.controller.ts"
5
+ - "src/**/*.service.ts"
6
+ ---
7
+
8
+ # NestJS Module Architecture
9
+
10
+ ## Module Design
11
+
12
+ ### Single Responsibility
13
+
14
+ Each module should own exactly one domain. If a module does multiple things, split it.
15
+
16
+ ```typescript
17
+ // Good: Focused modules
18
+ @Module({
19
+ imports: [DatabaseModule],
20
+ controllers: [UsersController],
21
+ providers: [UsersService, UsersRepository],
22
+ exports: [UsersService],
23
+ })
24
+ export class UsersModule {}
25
+
26
+ // Bad: Kitchen sink module
27
+ @Module({
28
+ controllers: [UsersController, OrdersController, PaymentsController],
29
+ providers: [/* too many */],
30
+ })
31
+ export class EverythingModule {}
32
+ ```
33
+
34
+ ### Module Boundaries
35
+
36
+ - Modules communicate via **exported services only**
37
+ - Never import internal providers from other modules
38
+ - Use barrel exports (`index.ts`) for clean imports
39
+
40
+ ```typescript
41
+ // users/index.ts
42
+ export { UsersModule } from './users.module';
43
+ export { UsersService } from './users.service';
44
+ export { User } from './entities/user.entity';
45
+ ```
46
+
47
+ ### Dynamic Modules for Configuration
48
+
49
+ ```typescript
50
+ @Module({})
51
+ export class DatabaseModule {
52
+ static forRoot(options: DatabaseOptions): DynamicModule {
53
+ return {
54
+ module: DatabaseModule,
55
+ global: true,
56
+ providers: [
57
+ {
58
+ provide: DATABASE_OPTIONS,
59
+ useValue: options,
60
+ },
61
+ DatabaseService,
62
+ ],
63
+ exports: [DatabaseService],
64
+ };
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Controller Rules
70
+
71
+ ### Controllers Handle HTTP Only
72
+
73
+ - Parse request (params, query, body)
74
+ - Call service methods
75
+ - Return response
76
+ - NO business logic
77
+
78
+ ```typescript
79
+ // Good
80
+ @Controller('users')
81
+ export class UsersController {
82
+ constructor(private readonly usersService: UsersService) {}
83
+
84
+ @Get(':id')
85
+ findOne(@Param('id', ParseUUIDPipe) id: string) {
86
+ return this.usersService.findOne(id);
87
+ }
88
+ }
89
+
90
+ // Bad: Business logic in controller
91
+ @Controller('users')
92
+ export class UsersController {
93
+ @Get(':id')
94
+ async findOne(@Param('id') id: string) {
95
+ const user = await this.userRepository.findOne(id);
96
+ if (!user) throw new NotFoundException();
97
+ if (user.deletedAt) throw new GoneException();
98
+ user.lastAccessed = new Date();
99
+ await this.userRepository.save(user);
100
+ return user;
101
+ }
102
+ }
103
+ ```
104
+
105
+ ### Use Pipes for Transformation/Validation
106
+
107
+ ```typescript
108
+ @Get(':id')
109
+ findOne(
110
+ @Param('id', ParseUUIDPipe) id: string,
111
+ @Query('include', new ParseArrayPipe({ optional: true })) include?: string[],
112
+ ) {
113
+ return this.usersService.findOne(id, { include });
114
+ }
115
+ ```
116
+
117
+ ## Service Rules
118
+
119
+ ### Services Contain Business Logic
120
+
121
+ - Validation rules
122
+ - Data transformations
123
+ - Orchestration between repositories
124
+ - Error handling with appropriate exceptions
125
+
126
+ ```typescript
127
+ @Injectable()
128
+ export class OrdersService {
129
+ constructor(
130
+ private readonly ordersRepository: OrdersRepository,
131
+ private readonly usersService: UsersService,
132
+ private readonly inventoryService: InventoryService,
133
+ ) {}
134
+
135
+ async createOrder(userId: string, dto: CreateOrderDto): Promise<Order> {
136
+ // Business logic belongs here
137
+ const user = await this.usersService.findOne(userId);
138
+
139
+ if (!user.verified) {
140
+ throw new ForbiddenException('Unverified users cannot place orders');
141
+ }
142
+
143
+ const available = await this.inventoryService.checkAvailability(dto.items);
144
+ if (!available) {
145
+ throw new ConflictException('Some items are out of stock');
146
+ }
147
+
148
+ return this.ordersRepository.create({ userId, ...dto });
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### Use NestJS Exceptions
154
+
155
+ ```typescript
156
+ // Use built-in HTTP exceptions
157
+ throw new NotFoundException('Resource not found');
158
+ throw new BadRequestException('Invalid input');
159
+ throw new UnauthorizedException('Authentication required');
160
+ throw new ForbiddenException('Access denied');
161
+ throw new ConflictException('Resource already exists');
162
+
163
+ // Custom exceptions extend HttpException
164
+ export class BusinessRuleException extends HttpException {
165
+ constructor(message: string) {
166
+ super(message, HttpStatus.UNPROCESSABLE_ENTITY);
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Dependency Injection
172
+
173
+ ### Prefer Constructor Injection
174
+
175
+ ```typescript
176
+ // Good
177
+ @Injectable()
178
+ export class UsersService {
179
+ constructor(
180
+ private readonly usersRepository: UsersRepository,
181
+ private readonly configService: ConfigService,
182
+ ) {}
183
+ }
184
+
185
+ // Avoid property injection
186
+ @Injectable()
187
+ export class UsersService {
188
+ @Inject()
189
+ private usersRepository: UsersRepository; // Harder to test
190
+ }
191
+ ```
192
+
193
+ ### Use Injection Tokens for Non-Class Dependencies
194
+
195
+ ```typescript
196
+ // constants.ts
197
+ export const CONFIG_OPTIONS = Symbol('CONFIG_OPTIONS');
198
+
199
+ // module.ts
200
+ @Module({
201
+ providers: [
202
+ {
203
+ provide: CONFIG_OPTIONS,
204
+ useValue: { apiKey: '...' },
205
+ },
206
+ ],
207
+ })
208
+ export class MyModule {}
209
+
210
+ // service.ts
211
+ @Injectable()
212
+ export class MyService {
213
+ constructor(@Inject(CONFIG_OPTIONS) private options: ConfigOptions) {}
214
+ }
215
+ ```