@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,321 @@
1
+ ---
2
+ paths:
3
+ - "src/**/auth/**/*.ts"
4
+ - "src/**/*.guard.ts"
5
+ - "src/**/*.strategy.ts"
6
+ ---
7
+
8
+ # NestJS Authentication
9
+
10
+ ## Passport + JWT Pattern
11
+
12
+ ### Module Structure
13
+
14
+ ```
15
+ src/modules/auth/
16
+ ├── auth.module.ts
17
+ ├── auth.controller.ts
18
+ ├── auth.service.ts
19
+ ├── strategies/
20
+ │ ├── jwt.strategy.ts
21
+ │ └── local.strategy.ts
22
+ ├── guards/
23
+ │ ├── jwt-auth.guard.ts
24
+ │ └── local-auth.guard.ts
25
+ ├── decorators/
26
+ │ ├── current-user.decorator.ts
27
+ │ └── public.decorator.ts
28
+ └── dto/
29
+ ├── login.dto.ts
30
+ └── register.dto.ts
31
+ ```
32
+
33
+ ### JWT Strategy
34
+
35
+ ```typescript
36
+ import { Injectable } from '@nestjs/common';
37
+ import { PassportStrategy } from '@nestjs/passport';
38
+ import { ExtractJwt, Strategy } from 'passport-jwt';
39
+ import { ConfigService } from '@nestjs/config';
40
+
41
+ export interface JwtPayload {
42
+ sub: string;
43
+ email: string;
44
+ role: string;
45
+ }
46
+
47
+ @Injectable()
48
+ export class JwtStrategy extends PassportStrategy(Strategy) {
49
+ constructor(private readonly configService: ConfigService) {
50
+ super({
51
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
52
+ ignoreExpiration: false,
53
+ secretOrKey: configService.getOrThrow<string>('JWT_SECRET'),
54
+ });
55
+ }
56
+
57
+ validate(payload: JwtPayload) {
58
+ // Returned value is attached to request.user
59
+ return {
60
+ id: payload.sub,
61
+ email: payload.email,
62
+ role: payload.role,
63
+ };
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### Local Strategy (Login)
69
+
70
+ ```typescript
71
+ import { Injectable, UnauthorizedException } from '@nestjs/common';
72
+ import { PassportStrategy } from '@nestjs/passport';
73
+ import { Strategy } from 'passport-local';
74
+ import { AuthService } from '../auth.service';
75
+
76
+ @Injectable()
77
+ export class LocalStrategy extends PassportStrategy(Strategy) {
78
+ constructor(private readonly authService: AuthService) {
79
+ super({
80
+ usernameField: 'email', // Use email instead of username
81
+ });
82
+ }
83
+
84
+ async validate(email: string, password: string) {
85
+ const user = await this.authService.validateUser(email, password);
86
+ if (!user) {
87
+ throw new UnauthorizedException('Invalid credentials');
88
+ }
89
+ return user;
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Auth Service
95
+
96
+ ```typescript
97
+ import { Injectable, UnauthorizedException } from '@nestjs/common';
98
+ import { JwtService } from '@nestjs/jwt';
99
+ import { UsersService } from '../users/users.service';
100
+ import * as bcrypt from 'bcrypt';
101
+
102
+ @Injectable()
103
+ export class AuthService {
104
+ constructor(
105
+ private readonly usersService: UsersService,
106
+ private readonly jwtService: JwtService,
107
+ ) {}
108
+
109
+ async validateUser(email: string, password: string) {
110
+ const user = await this.usersService.findByEmail(email);
111
+ if (!user) return null;
112
+
113
+ const isValid = await bcrypt.compare(password, user.password);
114
+ if (!isValid) return null;
115
+
116
+ const { password: _, ...result } = user;
117
+ return result;
118
+ }
119
+
120
+ async login(user: { id: string; email: string; role: string }) {
121
+ const payload: JwtPayload = {
122
+ sub: user.id,
123
+ email: user.email,
124
+ role: user.role,
125
+ };
126
+
127
+ return {
128
+ accessToken: this.jwtService.sign(payload),
129
+ user: {
130
+ id: user.id,
131
+ email: user.email,
132
+ role: user.role,
133
+ },
134
+ };
135
+ }
136
+
137
+ async register(dto: RegisterDto) {
138
+ const hashedPassword = await bcrypt.hash(dto.password, 10);
139
+ const user = await this.usersService.create({
140
+ ...dto,
141
+ password: hashedPassword,
142
+ });
143
+
144
+ return this.login(user);
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Guards
150
+
151
+ ```typescript
152
+ // jwt-auth.guard.ts
153
+ import { Injectable, ExecutionContext } from '@nestjs/common';
154
+ import { AuthGuard } from '@nestjs/passport';
155
+ import { Reflector } from '@nestjs/core';
156
+ import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
157
+
158
+ @Injectable()
159
+ export class JwtAuthGuard extends AuthGuard('jwt') {
160
+ constructor(private reflector: Reflector) {
161
+ super();
162
+ }
163
+
164
+ canActivate(context: ExecutionContext) {
165
+ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
166
+ context.getHandler(),
167
+ context.getClass(),
168
+ ]);
169
+
170
+ if (isPublic) {
171
+ return true;
172
+ }
173
+
174
+ return super.canActivate(context);
175
+ }
176
+ }
177
+
178
+ // local-auth.guard.ts
179
+ import { Injectable } from '@nestjs/common';
180
+ import { AuthGuard } from '@nestjs/passport';
181
+
182
+ @Injectable()
183
+ export class LocalAuthGuard extends AuthGuard('local') {}
184
+ ```
185
+
186
+ ### Custom Decorators
187
+
188
+ ```typescript
189
+ // public.decorator.ts
190
+ import { SetMetadata } from '@nestjs/common';
191
+
192
+ export const IS_PUBLIC_KEY = 'isPublic';
193
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
194
+
195
+ // current-user.decorator.ts
196
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
197
+
198
+ export const CurrentUser = createParamDecorator(
199
+ (data: string | undefined, ctx: ExecutionContext) => {
200
+ const request = ctx.switchToHttp().getRequest();
201
+ const user = request.user;
202
+ return data ? user?.[data] : user;
203
+ },
204
+ );
205
+
206
+ // roles.decorator.ts
207
+ import { SetMetadata } from '@nestjs/common';
208
+
209
+ export const ROLES_KEY = 'roles';
210
+ export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
211
+ ```
212
+
213
+ ### Roles Guard
214
+
215
+ ```typescript
216
+ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
217
+ import { Reflector } from '@nestjs/core';
218
+ import { ROLES_KEY } from '../decorators/roles.decorator';
219
+
220
+ @Injectable()
221
+ export class RolesGuard implements CanActivate {
222
+ constructor(private reflector: Reflector) {}
223
+
224
+ canActivate(context: ExecutionContext): boolean {
225
+ const requiredRoles = this.reflector.getAllAndOverride<string[]>(
226
+ ROLES_KEY,
227
+ [context.getHandler(), context.getClass()],
228
+ );
229
+
230
+ if (!requiredRoles) {
231
+ return true;
232
+ }
233
+
234
+ const { user } = context.switchToHttp().getRequest();
235
+ return requiredRoles.includes(user.role);
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Controller Usage
241
+
242
+ ```typescript
243
+ @Controller('auth')
244
+ export class AuthController {
245
+ constructor(private readonly authService: AuthService) {}
246
+
247
+ @Public()
248
+ @UseGuards(LocalAuthGuard)
249
+ @Post('login')
250
+ login(@CurrentUser() user) {
251
+ return this.authService.login(user);
252
+ }
253
+
254
+ @Public()
255
+ @Post('register')
256
+ register(@Body() dto: RegisterDto) {
257
+ return this.authService.register(dto);
258
+ }
259
+
260
+ @Get('profile')
261
+ getProfile(@CurrentUser() user) {
262
+ return user;
263
+ }
264
+
265
+ @Roles('admin')
266
+ @UseGuards(RolesGuard)
267
+ @Get('admin')
268
+ adminOnly(@CurrentUser() user) {
269
+ return { message: 'Admin access granted', user };
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Module Configuration
275
+
276
+ ```typescript
277
+ @Module({
278
+ imports: [
279
+ UsersModule,
280
+ PassportModule,
281
+ JwtModule.registerAsync({
282
+ inject: [ConfigService],
283
+ useFactory: (config: ConfigService) => ({
284
+ secret: config.getOrThrow('JWT_SECRET'),
285
+ signOptions: {
286
+ expiresIn: config.get('JWT_EXPIRES_IN', '1d'),
287
+ },
288
+ }),
289
+ }),
290
+ ],
291
+ controllers: [AuthController],
292
+ providers: [AuthService, JwtStrategy, LocalStrategy],
293
+ exports: [AuthService],
294
+ })
295
+ export class AuthModule {}
296
+ ```
297
+
298
+ ### Global Guard Setup (app.module.ts)
299
+
300
+ ```typescript
301
+ import { APP_GUARD } from '@nestjs/core';
302
+
303
+ @Module({
304
+ providers: [
305
+ {
306
+ provide: APP_GUARD,
307
+ useClass: JwtAuthGuard,
308
+ },
309
+ ],
310
+ })
311
+ export class AppModule {}
312
+ ```
313
+
314
+ ## Security Best Practices
315
+
316
+ - Never store plain passwords - always use bcrypt
317
+ - Use environment variables for secrets
318
+ - Set appropriate JWT expiration times
319
+ - Implement refresh token rotation for long sessions
320
+ - Rate limit authentication endpoints
321
+ - Log failed authentication attempts
@@ -0,0 +1,305 @@
1
+ ---
2
+ paths:
3
+ - "prisma/**/*.prisma"
4
+ - "src/**/*.repository.ts"
5
+ - "src/**/prisma*.ts"
6
+ ---
7
+
8
+ # NestJS with Prisma
9
+
10
+ ## Setup
11
+
12
+ ### Prisma Module
13
+
14
+ ```typescript
15
+ // prisma/prisma.module.ts
16
+ import { Global, Module } from '@nestjs/common';
17
+ import { PrismaService } from './prisma.service';
18
+
19
+ @Global()
20
+ @Module({
21
+ providers: [PrismaService],
22
+ exports: [PrismaService],
23
+ })
24
+ export class PrismaModule {}
25
+
26
+ // prisma/prisma.service.ts
27
+ import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
28
+ import { PrismaClient } from '@prisma/client';
29
+
30
+ @Injectable()
31
+ export class PrismaService
32
+ extends PrismaClient
33
+ implements OnModuleInit, OnModuleDestroy
34
+ {
35
+ async onModuleInit() {
36
+ await this.$connect();
37
+ }
38
+
39
+ async onModuleDestroy() {
40
+ await this.$disconnect();
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Schema Design
46
+
47
+ ### Model Conventions
48
+
49
+ ```prisma
50
+ // prisma/schema.prisma
51
+
52
+ generator client {
53
+ provider = "prisma-client-js"
54
+ }
55
+
56
+ datasource db {
57
+ provider = "postgresql"
58
+ url = env("DATABASE_URL")
59
+ }
60
+
61
+ model User {
62
+ id String @id @default(uuid())
63
+ email String @unique
64
+ password String
65
+ name String?
66
+ role Role @default(USER)
67
+ createdAt DateTime @default(now()) @map("created_at")
68
+ updatedAt DateTime @updatedAt @map("updated_at")
69
+
70
+ posts Post[]
71
+ profile Profile?
72
+
73
+ @@map("users")
74
+ }
75
+
76
+ model Post {
77
+ id String @id @default(uuid())
78
+ title String
79
+ content String?
80
+ published Boolean @default(false)
81
+ createdAt DateTime @default(now()) @map("created_at")
82
+ updatedAt DateTime @updatedAt @map("updated_at")
83
+
84
+ author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
85
+ authorId String @map("author_id")
86
+
87
+ categories Category[]
88
+
89
+ @@index([authorId])
90
+ @@map("posts")
91
+ }
92
+
93
+ enum Role {
94
+ USER
95
+ ADMIN
96
+ }
97
+ ```
98
+
99
+ ### Naming Conventions
100
+
101
+ - Models: PascalCase (`User`, `BlogPost`)
102
+ - Fields: camelCase (`createdAt`, `authorId`)
103
+ - Database tables: snake_case via `@@map("users")`
104
+ - Database columns: snake_case via `@map("created_at")`
105
+
106
+ ## Repository Pattern
107
+
108
+ ```typescript
109
+ // users/users.repository.ts
110
+ import { Injectable } from '@nestjs/common';
111
+ import { PrismaService } from '../prisma/prisma.service';
112
+ import { Prisma, User } from '@prisma/client';
113
+
114
+ @Injectable()
115
+ export class UsersRepository {
116
+ constructor(private readonly prisma: PrismaService) {}
117
+
118
+ async findById(id: string): Promise<User | null> {
119
+ return this.prisma.user.findUnique({
120
+ where: { id },
121
+ });
122
+ }
123
+
124
+ async findByEmail(email: string): Promise<User | null> {
125
+ return this.prisma.user.findUnique({
126
+ where: { email },
127
+ });
128
+ }
129
+
130
+ async findMany(params: {
131
+ skip?: number;
132
+ take?: number;
133
+ where?: Prisma.UserWhereInput;
134
+ orderBy?: Prisma.UserOrderByWithRelationInput;
135
+ }): Promise<User[]> {
136
+ const { skip, take, where, orderBy } = params;
137
+ return this.prisma.user.findMany({
138
+ skip,
139
+ take,
140
+ where,
141
+ orderBy,
142
+ });
143
+ }
144
+
145
+ async create(data: Prisma.UserCreateInput): Promise<User> {
146
+ return this.prisma.user.create({ data });
147
+ }
148
+
149
+ async update(id: string, data: Prisma.UserUpdateInput): Promise<User> {
150
+ return this.prisma.user.update({
151
+ where: { id },
152
+ data,
153
+ });
154
+ }
155
+
156
+ async delete(id: string): Promise<void> {
157
+ await this.prisma.user.delete({ where: { id } });
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Query Patterns
163
+
164
+ ### Select Specific Fields
165
+
166
+ ```typescript
167
+ // Return only needed fields
168
+ const user = await this.prisma.user.findUnique({
169
+ where: { id },
170
+ select: {
171
+ id: true,
172
+ email: true,
173
+ name: true,
174
+ // password excluded
175
+ },
176
+ });
177
+ ```
178
+
179
+ ### Include Relations
180
+
181
+ ```typescript
182
+ const userWithPosts = await this.prisma.user.findUnique({
183
+ where: { id },
184
+ include: {
185
+ posts: {
186
+ where: { published: true },
187
+ orderBy: { createdAt: 'desc' },
188
+ take: 10,
189
+ },
190
+ },
191
+ });
192
+ ```
193
+
194
+ ### Pagination
195
+
196
+ ```typescript
197
+ async findPaginated(page: number, limit: number) {
198
+ const [data, total] = await Promise.all([
199
+ this.prisma.user.findMany({
200
+ skip: (page - 1) * limit,
201
+ take: limit,
202
+ orderBy: { createdAt: 'desc' },
203
+ }),
204
+ this.prisma.user.count(),
205
+ ]);
206
+
207
+ return {
208
+ data,
209
+ meta: {
210
+ total,
211
+ page,
212
+ limit,
213
+ totalPages: Math.ceil(total / limit),
214
+ },
215
+ };
216
+ }
217
+ ```
218
+
219
+ ### Transactions
220
+
221
+ ```typescript
222
+ async transferCredits(fromId: string, toId: string, amount: number) {
223
+ return this.prisma.$transaction(async (tx) => {
224
+ const sender = await tx.user.update({
225
+ where: { id: fromId },
226
+ data: { credits: { decrement: amount } },
227
+ });
228
+
229
+ if (sender.credits < 0) {
230
+ throw new Error('Insufficient credits');
231
+ }
232
+
233
+ await tx.user.update({
234
+ where: { id: toId },
235
+ data: { credits: { increment: amount } },
236
+ });
237
+
238
+ return { success: true };
239
+ });
240
+ }
241
+ ```
242
+
243
+ ### Soft Delete Pattern
244
+
245
+ ```prisma
246
+ model User {
247
+ id String @id @default(uuid())
248
+ deletedAt DateTime? @map("deleted_at")
249
+ // ...
250
+ }
251
+ ```
252
+
253
+ ```typescript
254
+ // Middleware approach
255
+ this.prisma.$use(async (params, next) => {
256
+ if (params.model === 'User') {
257
+ if (params.action === 'delete') {
258
+ params.action = 'update';
259
+ params.args['data'] = { deletedAt: new Date() };
260
+ }
261
+ if (params.action === 'findMany' || params.action === 'findFirst') {
262
+ params.args['where'] = {
263
+ ...params.args['where'],
264
+ deletedAt: null,
265
+ };
266
+ }
267
+ }
268
+ return next(params);
269
+ });
270
+ ```
271
+
272
+ ## Migrations
273
+
274
+ ```bash
275
+ # Create migration
276
+ npx prisma migrate dev --name add_users_table
277
+
278
+ # Apply migrations (production)
279
+ npx prisma migrate deploy
280
+
281
+ # Reset database (dev only)
282
+ npx prisma migrate reset
283
+
284
+ # Generate client after schema change
285
+ npx prisma generate
286
+ ```
287
+
288
+ ## Testing with Prisma
289
+
290
+ ```typescript
291
+ // Mock PrismaService
292
+ const mockPrismaService = {
293
+ user: {
294
+ findUnique: jest.fn(),
295
+ findMany: jest.fn(),
296
+ create: jest.fn(),
297
+ update: jest.fn(),
298
+ delete: jest.fn(),
299
+ },
300
+ };
301
+
302
+ // E2E: Use test database
303
+ // .env.test
304
+ DATABASE_URL="postgresql://localhost:5432/myapp_test"
305
+ ```