@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.
- package/README.md +174 -0
- package/bin/cli.js +5 -0
- package/configs/_shared/.claude/commands/fix-issue.md +38 -0
- package/configs/_shared/.claude/commands/generate-tests.md +49 -0
- package/configs/_shared/.claude/commands/review-pr.md +77 -0
- package/configs/_shared/.claude/rules/accessibility.md +270 -0
- package/configs/_shared/.claude/rules/performance.md +226 -0
- package/configs/_shared/.claude/rules/security.md +188 -0
- package/configs/_shared/.claude/skills/debug/SKILL.md +118 -0
- package/configs/_shared/.claude/skills/learning/SKILL.md +224 -0
- package/configs/_shared/.claude/skills/review/SKILL.md +86 -0
- package/configs/_shared/.claude/skills/spec/SKILL.md +112 -0
- package/configs/_shared/CLAUDE.md +174 -0
- package/configs/angular/.claude/rules/components.md +257 -0
- package/configs/angular/.claude/rules/state.md +250 -0
- package/configs/angular/.claude/rules/testing.md +422 -0
- package/configs/angular/.claude/settings.json +31 -0
- package/configs/angular/CLAUDE.md +251 -0
- package/configs/dotnet/.claude/rules/api.md +370 -0
- package/configs/dotnet/.claude/rules/architecture.md +199 -0
- package/configs/dotnet/.claude/rules/database/efcore.md +408 -0
- package/configs/dotnet/.claude/rules/testing.md +389 -0
- package/configs/dotnet/.claude/settings.json +9 -0
- package/configs/dotnet/CLAUDE.md +319 -0
- package/configs/nestjs/.claude/rules/auth.md +321 -0
- package/configs/nestjs/.claude/rules/database/prisma.md +305 -0
- package/configs/nestjs/.claude/rules/database/typeorm.md +379 -0
- package/configs/nestjs/.claude/rules/modules.md +215 -0
- package/configs/nestjs/.claude/rules/testing.md +315 -0
- package/configs/nestjs/.claude/rules/validation.md +279 -0
- package/configs/nestjs/.claude/settings.json +15 -0
- package/configs/nestjs/CLAUDE.md +263 -0
- package/configs/nextjs/.claude/rules/components.md +211 -0
- package/configs/nextjs/.claude/rules/state/redux-toolkit.md +429 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +299 -0
- package/configs/nextjs/.claude/rules/testing.md +315 -0
- package/configs/nextjs/.claude/settings.json +29 -0
- package/configs/nextjs/CLAUDE.md +376 -0
- package/configs/python/.claude/rules/database/sqlalchemy.md +355 -0
- package/configs/python/.claude/rules/fastapi.md +272 -0
- package/configs/python/.claude/rules/flask.md +332 -0
- package/configs/python/.claude/rules/testing.md +374 -0
- package/configs/python/.claude/settings.json +18 -0
- package/configs/python/CLAUDE.md +273 -0
- package/package.json +41 -0
- package/src/install.js +315 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# NestJS Project Guidelines
|
|
2
|
+
|
|
3
|
+
@../_shared/CLAUDE.md
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- NestJS 10+ (latest stable)
|
|
8
|
+
- TypeScript strict mode
|
|
9
|
+
- Node.js 20+
|
|
10
|
+
- Jest for testing
|
|
11
|
+
- Supertest for E2E
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
### Modular Monolith (Recommended)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
├── modules/
|
|
20
|
+
│ ├── users/
|
|
21
|
+
│ │ ├── users.module.ts
|
|
22
|
+
│ │ ├── users.controller.ts
|
|
23
|
+
│ │ ├── users.service.ts
|
|
24
|
+
│ │ ├── dto/
|
|
25
|
+
│ │ │ ├── create-user.dto.ts
|
|
26
|
+
│ │ │ └── update-user.dto.ts
|
|
27
|
+
│ │ ├── entities/
|
|
28
|
+
│ │ │ └── user.entity.ts
|
|
29
|
+
│ │ └── users.repository.ts
|
|
30
|
+
│ │
|
|
31
|
+
│ ├── auth/
|
|
32
|
+
│ │ ├── auth.module.ts
|
|
33
|
+
│ │ ├── auth.controller.ts
|
|
34
|
+
│ │ ├── auth.service.ts
|
|
35
|
+
│ │ ├── strategies/
|
|
36
|
+
│ │ │ ├── jwt.strategy.ts
|
|
37
|
+
│ │ │ └── local.strategy.ts
|
|
38
|
+
│ │ └── guards/
|
|
39
|
+
│ │ └── jwt-auth.guard.ts
|
|
40
|
+
│ │
|
|
41
|
+
│ └── [feature]/
|
|
42
|
+
│ └── ...
|
|
43
|
+
│
|
|
44
|
+
├── common/
|
|
45
|
+
│ ├── decorators/
|
|
46
|
+
│ ├── filters/
|
|
47
|
+
│ ├── guards/
|
|
48
|
+
│ ├── interceptors/
|
|
49
|
+
│ └── pipes/
|
|
50
|
+
│
|
|
51
|
+
├── config/
|
|
52
|
+
│ ├── config.module.ts
|
|
53
|
+
│ └── database.config.ts
|
|
54
|
+
│
|
|
55
|
+
├── app.module.ts
|
|
56
|
+
└── main.ts
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Module Principles
|
|
60
|
+
|
|
61
|
+
- **Single Responsibility**: Each module owns one domain
|
|
62
|
+
- **Clear Boundaries**: Modules communicate via exported services
|
|
63
|
+
- **In-Memory Communication**: Direct method calls, not HTTP between modules
|
|
64
|
+
- **Barrel Exports**: Use `index.ts` for clean imports
|
|
65
|
+
|
|
66
|
+
## Code Style
|
|
67
|
+
|
|
68
|
+
### Controllers
|
|
69
|
+
|
|
70
|
+
- Handle HTTP concerns only (request/response)
|
|
71
|
+
- Delegate business logic to services
|
|
72
|
+
- Use DTOs for all inputs
|
|
73
|
+
- Return consistent response shapes
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
@Controller('users')
|
|
77
|
+
export class UsersController {
|
|
78
|
+
constructor(private readonly usersService: UsersService) {}
|
|
79
|
+
|
|
80
|
+
@Post()
|
|
81
|
+
@HttpCode(HttpStatus.CREATED)
|
|
82
|
+
create(@Body() createUserDto: CreateUserDto): Promise<User> {
|
|
83
|
+
return this.usersService.create(createUserDto);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Get(':id')
|
|
87
|
+
findOne(@Param('id', ParseUUIDPipe) id: string): Promise<User> {
|
|
88
|
+
return this.usersService.findOne(id);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Services
|
|
94
|
+
|
|
95
|
+
- Contain all business logic
|
|
96
|
+
- Inject repositories/other services
|
|
97
|
+
- Throw appropriate NestJS exceptions
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
@Injectable()
|
|
101
|
+
export class UsersService {
|
|
102
|
+
constructor(private readonly usersRepository: UsersRepository) {}
|
|
103
|
+
|
|
104
|
+
async findOne(id: string): Promise<User> {
|
|
105
|
+
const user = await this.usersRepository.findById(id);
|
|
106
|
+
if (!user) {
|
|
107
|
+
throw new NotFoundException(`User with ID ${id} not found`);
|
|
108
|
+
}
|
|
109
|
+
return user;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### DTOs
|
|
115
|
+
|
|
116
|
+
- Always use class-validator decorators
|
|
117
|
+
- Use class-transformer for transformation
|
|
118
|
+
- Extend with PartialType/PickType/OmitType for variants
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export class CreateUserDto {
|
|
122
|
+
@IsEmail()
|
|
123
|
+
@IsNotEmpty()
|
|
124
|
+
email: string;
|
|
125
|
+
|
|
126
|
+
@IsString()
|
|
127
|
+
@MinLength(8)
|
|
128
|
+
password: string;
|
|
129
|
+
|
|
130
|
+
@IsString()
|
|
131
|
+
@IsOptional()
|
|
132
|
+
name?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Commands
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Development
|
|
142
|
+
npm run start:dev
|
|
143
|
+
|
|
144
|
+
# Build
|
|
145
|
+
npm run build
|
|
146
|
+
|
|
147
|
+
# Tests
|
|
148
|
+
npm run test # Unit tests
|
|
149
|
+
npm run test:watch # Watch mode
|
|
150
|
+
npm run test:cov # Coverage
|
|
151
|
+
npm run test:e2e # E2E tests
|
|
152
|
+
|
|
153
|
+
# Linting
|
|
154
|
+
npm run lint
|
|
155
|
+
npm run format
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Global Setup (main.ts)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
async function bootstrap() {
|
|
162
|
+
const app = await NestFactory.create(AppModule);
|
|
163
|
+
|
|
164
|
+
// Global validation pipe
|
|
165
|
+
app.useGlobalPipes(
|
|
166
|
+
new ValidationPipe({
|
|
167
|
+
whitelist: true,
|
|
168
|
+
forbidNonWhitelisted: true,
|
|
169
|
+
transform: true,
|
|
170
|
+
transformOptions: {
|
|
171
|
+
enableImplicitConversion: true,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Global prefix
|
|
177
|
+
app.setGlobalPrefix('api/v1');
|
|
178
|
+
|
|
179
|
+
// CORS
|
|
180
|
+
app.enableCors();
|
|
181
|
+
|
|
182
|
+
// Swagger (dev only)
|
|
183
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
184
|
+
const config = new DocumentBuilder()
|
|
185
|
+
.setTitle('API')
|
|
186
|
+
.setVersion('1.0')
|
|
187
|
+
.addBearerAuth()
|
|
188
|
+
.build();
|
|
189
|
+
const document = SwaggerModule.createDocument(app, config);
|
|
190
|
+
SwaggerModule.setup('docs', app, document);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await app.listen(process.env.PORT ?? 3000);
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Common Patterns
|
|
198
|
+
|
|
199
|
+
### Custom Decorator
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// current-user.decorator.ts
|
|
203
|
+
export const CurrentUser = createParamDecorator(
|
|
204
|
+
(data: keyof User | undefined, ctx: ExecutionContext) => {
|
|
205
|
+
const request = ctx.switchToHttp().getRequest();
|
|
206
|
+
const user = request.user;
|
|
207
|
+
return data ? user?.[data] : user;
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Usage
|
|
212
|
+
@Get('profile')
|
|
213
|
+
getProfile(@CurrentUser() user: User) { ... }
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Global Exception Filter
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
@Catch()
|
|
220
|
+
export class AllExceptionsFilter implements ExceptionFilter {
|
|
221
|
+
catch(exception: unknown, host: ArgumentsHost) {
|
|
222
|
+
const ctx = host.switchToHttp();
|
|
223
|
+
const response = ctx.getResponse<Response>();
|
|
224
|
+
|
|
225
|
+
const status =
|
|
226
|
+
exception instanceof HttpException
|
|
227
|
+
? exception.getStatus()
|
|
228
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
229
|
+
|
|
230
|
+
const message =
|
|
231
|
+
exception instanceof HttpException
|
|
232
|
+
? exception.message
|
|
233
|
+
: 'Internal server error';
|
|
234
|
+
|
|
235
|
+
response.status(status).json({
|
|
236
|
+
statusCode: status,
|
|
237
|
+
message,
|
|
238
|
+
timestamp: new Date().toISOString(),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Response Interceptor
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
@Injectable()
|
|
248
|
+
export class TransformInterceptor<T>
|
|
249
|
+
implements NestInterceptor<T, Response<T>>
|
|
250
|
+
{
|
|
251
|
+
intercept(
|
|
252
|
+
context: ExecutionContext,
|
|
253
|
+
next: CallHandler,
|
|
254
|
+
): Observable<Response<T>> {
|
|
255
|
+
return next.handle().pipe(
|
|
256
|
+
map((data) => ({
|
|
257
|
+
success: true,
|
|
258
|
+
data,
|
|
259
|
+
})),
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "apps/**/*.tsx"
|
|
4
|
+
- "libs/**/*.tsx"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Component Rules (Next.js 15 / React 19)
|
|
8
|
+
|
|
9
|
+
## Server vs Client Components
|
|
10
|
+
|
|
11
|
+
### Server Components (Default)
|
|
12
|
+
|
|
13
|
+
No directive needed. Use for:
|
|
14
|
+
- Data fetching
|
|
15
|
+
- Database access
|
|
16
|
+
- Accessing secrets/environment variables
|
|
17
|
+
- Heavy dependencies that don't need to ship to client
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
// app/users/page.tsx
|
|
21
|
+
// This is a Server Component by default
|
|
22
|
+
import { db } from '@/lib/db';
|
|
23
|
+
|
|
24
|
+
export default async function UsersPage() {
|
|
25
|
+
const users = await db.user.findMany();
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ul>
|
|
29
|
+
{users.map((user) => (
|
|
30
|
+
<li key={user.id}>{user.name}</li>
|
|
31
|
+
))}
|
|
32
|
+
</ul>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Client Components
|
|
38
|
+
|
|
39
|
+
Add `'use client'` at the top. Use for:
|
|
40
|
+
- Interactivity (onClick, onChange, etc.)
|
|
41
|
+
- React hooks (useState, useEffect, useContext)
|
|
42
|
+
- Browser APIs (localStorage, window, etc.)
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
'use client';
|
|
46
|
+
|
|
47
|
+
import { useState } from 'react';
|
|
48
|
+
|
|
49
|
+
export function Counter() {
|
|
50
|
+
const [count, setCount] = useState(0);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<button onClick={() => setCount(count + 1)}>
|
|
54
|
+
Count: {count}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Decision Tree
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Need hooks or interactivity?
|
|
64
|
+
├── YES → 'use client'
|
|
65
|
+
└── NO → Server Component (default)
|
|
66
|
+
└── Fetching data? → async component
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Component Patterns
|
|
70
|
+
|
|
71
|
+
### Props Interface
|
|
72
|
+
|
|
73
|
+
Always define props interface above component:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
interface UserCardProps {
|
|
77
|
+
user: User;
|
|
78
|
+
isSelected?: boolean;
|
|
79
|
+
onSelect: (user: User) => void;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function UserCard({ user, isSelected = false, onSelect }: UserCardProps) {
|
|
83
|
+
return (/* ... */);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Composition over Props
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// BAD - prop drilling
|
|
91
|
+
<Card
|
|
92
|
+
title="User Profile"
|
|
93
|
+
subtitle="Details"
|
|
94
|
+
icon={<UserIcon />}
|
|
95
|
+
footer={<Button>Edit</Button>}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
// GOOD - composition
|
|
99
|
+
<Card>
|
|
100
|
+
<CardHeader>
|
|
101
|
+
<UserIcon />
|
|
102
|
+
<CardTitle>User Profile</CardTitle>
|
|
103
|
+
<CardDescription>Details</CardDescription>
|
|
104
|
+
</CardHeader>
|
|
105
|
+
<CardFooter>
|
|
106
|
+
<Button>Edit</Button>
|
|
107
|
+
</CardFooter>
|
|
108
|
+
</Card>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Async Server Components
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// Server Component can be async
|
|
115
|
+
export default async function UsersPage() {
|
|
116
|
+
const users = await getUsers(); // Direct await
|
|
117
|
+
|
|
118
|
+
return <UserList users={users} />;
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Client Component with Server Data
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
// page.tsx (Server)
|
|
126
|
+
export default async function Page() {
|
|
127
|
+
const data = await fetchData();
|
|
128
|
+
return <InteractiveComponent initialData={data} />;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// interactive-component.tsx (Client)
|
|
132
|
+
'use client';
|
|
133
|
+
|
|
134
|
+
export function InteractiveComponent({ initialData }: Props) {
|
|
135
|
+
const [data, setData] = useState(initialData);
|
|
136
|
+
// Can now use hooks with server-fetched data
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## File Organization
|
|
141
|
+
|
|
142
|
+
### Co-located Components
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
app/
|
|
146
|
+
users/
|
|
147
|
+
page.tsx # Route
|
|
148
|
+
_components/ # Private folder (underscore prefix)
|
|
149
|
+
user-list.tsx # Only used in /users
|
|
150
|
+
user-card.tsx
|
|
151
|
+
user-form.tsx
|
|
152
|
+
actions.ts # Server Actions
|
|
153
|
+
loading.tsx # Loading UI
|
|
154
|
+
error.tsx # Error UI
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Shared Components
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
libs/
|
|
161
|
+
shared/
|
|
162
|
+
ui/
|
|
163
|
+
button.tsx
|
|
164
|
+
input.tsx
|
|
165
|
+
card.tsx
|
|
166
|
+
index.ts # Barrel export
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Naming Conventions
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// Component files: kebab-case
|
|
173
|
+
user-card.tsx
|
|
174
|
+
user-list.tsx
|
|
175
|
+
|
|
176
|
+
// Components: PascalCase
|
|
177
|
+
export function UserCard() {}
|
|
178
|
+
export function UserList() {}
|
|
179
|
+
|
|
180
|
+
// Hooks: camelCase with 'use' prefix
|
|
181
|
+
export function useUserData() {}
|
|
182
|
+
|
|
183
|
+
// Event handlers: handle + Event
|
|
184
|
+
const handleClick = () => {};
|
|
185
|
+
const handleSubmit = () => {};
|
|
186
|
+
const handleUserSelect = (user: User) => {};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Exports
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
// Named exports for reusable components
|
|
193
|
+
export function Button() {}
|
|
194
|
+
export function Input() {}
|
|
195
|
+
|
|
196
|
+
// Default export ONLY for:
|
|
197
|
+
// - page.tsx
|
|
198
|
+
// - layout.tsx
|
|
199
|
+
// - loading.tsx
|
|
200
|
+
// - error.tsx
|
|
201
|
+
// - not-found.tsx
|
|
202
|
+
export default function Page() {}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Avoid
|
|
206
|
+
|
|
207
|
+
- `useEffect` for data fetching (use Server Components)
|
|
208
|
+
- Default exports for library components
|
|
209
|
+
- Inline styles (use CSS modules or Tailwind)
|
|
210
|
+
- Anonymous components
|
|
211
|
+
- Props spreading without type safety
|