@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,370 @@
1
+ ---
2
+ paths:
3
+ - "src/WebApi/**/*.cs"
4
+ - "src/**/Controllers/**/*.cs"
5
+ - "src/**/Endpoints/**/*.cs"
6
+ ---
7
+
8
+ # ASP.NET Core API Rules
9
+
10
+ ## Minimal APIs vs Controllers
11
+
12
+ ### Minimal APIs (Recommended for .NET 8+)
13
+
14
+ ```csharp
15
+ // Organized by feature in extension methods
16
+ public static class UserEndpoints
17
+ {
18
+ public static IEndpointRouteBuilder MapUserEndpoints(this IEndpointRouteBuilder app)
19
+ {
20
+ var group = app.MapGroup("/api/users")
21
+ .WithTags("Users")
22
+ .RequireAuthorization();
23
+
24
+ group.MapGet("/", GetUsers);
25
+ group.MapGet("/{id:guid}", GetUser).WithName("GetUser");
26
+ group.MapPost("/", CreateUser);
27
+ group.MapPut("/{id:guid}", UpdateUser);
28
+ group.MapDelete("/{id:guid}", DeleteUser);
29
+
30
+ return app;
31
+ }
32
+
33
+ private static async Task<IResult> GetUsers(
34
+ [AsParameters] GetUsersQuery query,
35
+ ISender sender)
36
+ {
37
+ var result = await sender.Send(query);
38
+ return Results.Ok(result);
39
+ }
40
+
41
+ private static async Task<IResult> GetUser(
42
+ Guid id,
43
+ ISender sender)
44
+ {
45
+ var result = await sender.Send(new GetUserQuery(id));
46
+ return result is not null ? Results.Ok(result) : Results.NotFound();
47
+ }
48
+
49
+ private static async Task<IResult> CreateUser(
50
+ CreateUserCommand command,
51
+ ISender sender)
52
+ {
53
+ var id = await sender.Send(command);
54
+ return Results.CreatedAtRoute("GetUser", new { id }, new { id });
55
+ }
56
+
57
+ private static async Task<IResult> UpdateUser(
58
+ Guid id,
59
+ UpdateUserCommand command,
60
+ ISender sender)
61
+ {
62
+ if (id != command.Id) return Results.BadRequest();
63
+ await sender.Send(command);
64
+ return Results.NoContent();
65
+ }
66
+
67
+ private static async Task<IResult> DeleteUser(
68
+ Guid id,
69
+ ISender sender)
70
+ {
71
+ await sender.Send(new DeleteUserCommand(id));
72
+ return Results.NoContent();
73
+ }
74
+ }
75
+
76
+ // Program.cs
77
+ app.MapUserEndpoints();
78
+ ```
79
+
80
+ ### Controllers (When More Control Needed)
81
+
82
+ ```csharp
83
+ [ApiController]
84
+ [Route("api/[controller]")]
85
+ [Produces("application/json")]
86
+ public class UsersController(ISender sender) : ControllerBase
87
+ {
88
+ /// <summary>
89
+ /// Get all users with pagination
90
+ /// </summary>
91
+ [HttpGet]
92
+ [ProducesResponseType<PaginatedList<UserDto>>(StatusCodes.Status200OK)]
93
+ public async Task<IActionResult> GetAll([FromQuery] GetUsersQuery query)
94
+ {
95
+ return Ok(await sender.Send(query));
96
+ }
97
+
98
+ /// <summary>
99
+ /// Get a user by ID
100
+ /// </summary>
101
+ [HttpGet("{id:guid}")]
102
+ [ProducesResponseType<UserDto>(StatusCodes.Status200OK)]
103
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
104
+ public async Task<IActionResult> Get(Guid id)
105
+ {
106
+ var user = await sender.Send(new GetUserQuery(id));
107
+ return user is not null ? Ok(user) : NotFound();
108
+ }
109
+
110
+ /// <summary>
111
+ /// Create a new user
112
+ /// </summary>
113
+ [HttpPost]
114
+ [ProducesResponseType<Guid>(StatusCodes.Status201Created)]
115
+ [ProducesResponseType<ValidationProblemDetails>(StatusCodes.Status400BadRequest)]
116
+ public async Task<IActionResult> Create(CreateUserCommand command)
117
+ {
118
+ var id = await sender.Send(command);
119
+ return CreatedAtAction(nameof(Get), new { id }, new { id });
120
+ }
121
+
122
+ /// <summary>
123
+ /// Update an existing user
124
+ /// </summary>
125
+ [HttpPut("{id:guid}")]
126
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
127
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
128
+ public async Task<IActionResult> Update(Guid id, UpdateUserCommand command)
129
+ {
130
+ if (id != command.Id) return BadRequest();
131
+ await sender.Send(command);
132
+ return NoContent();
133
+ }
134
+
135
+ /// <summary>
136
+ /// Delete a user
137
+ /// </summary>
138
+ [HttpDelete("{id:guid}")]
139
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
140
+ public async Task<IActionResult> Delete(Guid id)
141
+ {
142
+ await sender.Send(new DeleteUserCommand(id));
143
+ return NoContent();
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## Request/Response Patterns
149
+
150
+ ### Pagination
151
+
152
+ ```csharp
153
+ // Query with pagination
154
+ public record GetUsersQuery(int Page = 1, int PageSize = 10) : IRequest<PaginatedList<UserDto>>;
155
+
156
+ // Paginated response
157
+ public class PaginatedList<T>
158
+ {
159
+ public IReadOnlyList<T> Items { get; }
160
+ public int Page { get; }
161
+ public int PageSize { get; }
162
+ public int TotalCount { get; }
163
+ public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
164
+ public bool HasPreviousPage => Page > 1;
165
+ public bool HasNextPage => Page < TotalPages;
166
+
167
+ public PaginatedList(IReadOnlyList<T> items, int count, int page, int pageSize)
168
+ {
169
+ Items = items;
170
+ TotalCount = count;
171
+ Page = page;
172
+ PageSize = pageSize;
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### Error Responses (Problem Details)
178
+
179
+ ```csharp
180
+ // Always use ProblemDetails for errors (RFC 7807)
181
+ app.UseExceptionHandler(exceptionApp =>
182
+ {
183
+ exceptionApp.Run(async context =>
184
+ {
185
+ var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;
186
+
187
+ var problemDetails = exception switch
188
+ {
189
+ ValidationException ex => new ValidationProblemDetails(
190
+ ex.Errors.GroupBy(e => e.PropertyName)
191
+ .ToDictionary(g => g.Key, g => g.Select(e => e.ErrorMessage).ToArray()))
192
+ {
193
+ Status = StatusCodes.Status400BadRequest,
194
+ Title = "Validation Error"
195
+ },
196
+ NotFoundException ex => new ProblemDetails
197
+ {
198
+ Status = StatusCodes.Status404NotFound,
199
+ Title = "Not Found",
200
+ Detail = ex.Message
201
+ },
202
+ UnauthorizedAccessException => new ProblemDetails
203
+ {
204
+ Status = StatusCodes.Status401Unauthorized,
205
+ Title = "Unauthorized"
206
+ },
207
+ _ => new ProblemDetails
208
+ {
209
+ Status = StatusCodes.Status500InternalServerError,
210
+ Title = "Server Error"
211
+ }
212
+ };
213
+
214
+ context.Response.StatusCode = problemDetails.Status ?? 500;
215
+ await context.Response.WriteAsJsonAsync(problemDetails);
216
+ });
217
+ });
218
+ ```
219
+
220
+ ## OpenAPI / Swagger
221
+
222
+ ```csharp
223
+ // Program.cs
224
+ builder.Services.AddEndpointsApiExplorer();
225
+ builder.Services.AddSwaggerGen(options =>
226
+ {
227
+ options.SwaggerDoc("v1", new OpenApiInfo
228
+ {
229
+ Title = "My API",
230
+ Version = "v1",
231
+ Description = "API Description"
232
+ });
233
+
234
+ options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
235
+ {
236
+ Description = "JWT Authorization header using the Bearer scheme",
237
+ Name = "Authorization",
238
+ In = ParameterLocation.Header,
239
+ Type = SecuritySchemeType.Http,
240
+ Scheme = "bearer"
241
+ });
242
+
243
+ options.AddSecurityRequirement(new OpenApiSecurityRequirement
244
+ {
245
+ {
246
+ new OpenApiSecurityScheme
247
+ {
248
+ Reference = new OpenApiReference
249
+ {
250
+ Type = ReferenceType.SecurityScheme,
251
+ Id = "Bearer"
252
+ }
253
+ },
254
+ Array.Empty<string>()
255
+ }
256
+ });
257
+
258
+ // Include XML comments
259
+ var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
260
+ options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFile));
261
+ });
262
+
263
+ // Enable only in development
264
+ if (app.Environment.IsDevelopment())
265
+ {
266
+ app.UseSwagger();
267
+ app.UseSwaggerUI();
268
+ }
269
+ ```
270
+
271
+ ## Authentication
272
+
273
+ ### JWT Bearer
274
+
275
+ ```csharp
276
+ // Program.cs
277
+ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
278
+ .AddJwtBearer(options =>
279
+ {
280
+ options.TokenValidationParameters = new TokenValidationParameters
281
+ {
282
+ ValidateIssuer = true,
283
+ ValidateAudience = true,
284
+ ValidateLifetime = true,
285
+ ValidateIssuerSigningKey = true,
286
+ ValidIssuer = builder.Configuration["Jwt:Issuer"],
287
+ ValidAudience = builder.Configuration["Jwt:Audience"],
288
+ IssuerSigningKey = new SymmetricSecurityKey(
289
+ Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!))
290
+ };
291
+ });
292
+
293
+ builder.Services.AddAuthorization();
294
+
295
+ // Middleware order matters!
296
+ app.UseAuthentication();
297
+ app.UseAuthorization();
298
+ ```
299
+
300
+ ### Policy-Based Authorization
301
+
302
+ ```csharp
303
+ // Define policies
304
+ builder.Services.AddAuthorization(options =>
305
+ {
306
+ options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin"));
307
+ options.AddPolicy("CanManageUsers", policy =>
308
+ policy.RequireClaim("permission", "users:manage"));
309
+ });
310
+
311
+ // Use on endpoints
312
+ app.MapDelete("/api/users/{id}", DeleteUser)
313
+ .RequireAuthorization("AdminOnly");
314
+
315
+ // Or on controllers
316
+ [Authorize(Policy = "AdminOnly")]
317
+ public class AdminController : ControllerBase { }
318
+ ```
319
+
320
+ ## Versioning
321
+
322
+ ```csharp
323
+ builder.Services.AddApiVersioning(options =>
324
+ {
325
+ options.DefaultApiVersion = new ApiVersion(1, 0);
326
+ options.AssumeDefaultVersionWhenUnspecified = true;
327
+ options.ReportApiVersions = true;
328
+ options.ApiVersionReader = ApiVersionReader.Combine(
329
+ new UrlSegmentApiVersionReader(),
330
+ new HeaderApiVersionReader("X-Api-Version"));
331
+ }).AddApiExplorer(options =>
332
+ {
333
+ options.GroupNameFormat = "'v'VVV";
334
+ options.SubstituteApiVersionInUrl = true;
335
+ });
336
+
337
+ // Usage
338
+ app.MapGroup("/api/v{version:apiVersion}/users")
339
+ .MapUserEndpoints()
340
+ .HasApiVersion(1.0)
341
+ .HasApiVersion(2.0);
342
+ ```
343
+
344
+ ## Rate Limiting
345
+
346
+ ```csharp
347
+ builder.Services.AddRateLimiter(options =>
348
+ {
349
+ options.AddFixedWindowLimiter("fixed", config =>
350
+ {
351
+ config.Window = TimeSpan.FromMinutes(1);
352
+ config.PermitLimit = 100;
353
+ config.QueueLimit = 0;
354
+ });
355
+
356
+ options.AddTokenBucketLimiter("token", config =>
357
+ {
358
+ config.TokenLimit = 100;
359
+ config.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
360
+ config.TokensPerPeriod = 10;
361
+ });
362
+
363
+ options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
364
+ });
365
+
366
+ app.UseRateLimiter();
367
+
368
+ // Apply to endpoints
369
+ app.MapGet("/api/users", GetUsers).RequireRateLimiting("fixed");
370
+ ```
@@ -0,0 +1,199 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.cs"
4
+ ---
5
+
6
+ # Clean Architecture Rules
7
+
8
+ ## Layer Dependencies
9
+
10
+ ```
11
+ WebApi → Application → Domain
12
+ WebApi → Infrastructure → Application → Domain
13
+ ```
14
+
15
+ ### Domain Layer (src/Domain/)
16
+
17
+ - **ZERO external dependencies** (no NuGet packages except primitives)
18
+ - Contains: Entities, Value Objects, Enums, Domain Events, Interfaces
19
+ - No references to other projects
20
+
21
+ ```csharp
22
+ // Good - Domain entity
23
+ namespace MyApp.Domain.Entities;
24
+
25
+ public class User
26
+ {
27
+ public Guid Id { get; private set; }
28
+ public string Email { get; private set; } = default!;
29
+ public string PasswordHash { get; private set; } = default!;
30
+
31
+ private User() { } // EF Core
32
+
33
+ public static User Create(string email, string passwordHash)
34
+ {
35
+ return new User
36
+ {
37
+ Id = Guid.NewGuid(),
38
+ Email = email,
39
+ PasswordHash = passwordHash
40
+ };
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Application Layer (src/Application/)
46
+
47
+ - References: Domain only
48
+ - Contains: Commands, Queries, DTOs, Interfaces, Validators, Mappings
49
+ - Allowed packages: MediatR, FluentValidation, AutoMapper
50
+
51
+ ```csharp
52
+ // Command + Handler in same folder
53
+ namespace MyApp.Application.Users.Commands.CreateUser;
54
+
55
+ public record CreateUserCommand(string Email, string Password) : IRequest<Guid>;
56
+
57
+ public class CreateUserCommandHandler(
58
+ IUserRepository userRepository,
59
+ IPasswordHasher passwordHasher
60
+ ) : IRequestHandler<CreateUserCommand, Guid>
61
+ {
62
+ public async Task<Guid> Handle(CreateUserCommand request, CancellationToken cancellationToken)
63
+ {
64
+ var hashedPassword = passwordHasher.Hash(request.Password);
65
+ var user = User.Create(request.Email, hashedPassword);
66
+ await userRepository.AddAsync(user, cancellationToken);
67
+ return user.Id;
68
+ }
69
+ }
70
+ ```
71
+
72
+ ### Infrastructure Layer (src/Infrastructure/)
73
+
74
+ - References: Application, Domain
75
+ - Contains: DbContext, Repositories, External Services, Configurations
76
+ - Implements interfaces defined in Application/Domain
77
+
78
+ ```csharp
79
+ // Repository implementation
80
+ namespace MyApp.Infrastructure.Repositories;
81
+
82
+ public class UserRepository(ApplicationDbContext context) : IUserRepository
83
+ {
84
+ public async Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
85
+ {
86
+ return await context.Users.FindAsync([id], cancellationToken);
87
+ }
88
+
89
+ public async Task AddAsync(User user, CancellationToken cancellationToken = default)
90
+ {
91
+ await context.Users.AddAsync(user, cancellationToken);
92
+ await context.SaveChangesAsync(cancellationToken);
93
+ }
94
+ }
95
+ ```
96
+
97
+ ### Presentation Layer (src/WebApi/)
98
+
99
+ - References: Application, Infrastructure
100
+ - Contains: Controllers/Endpoints, Middleware, Filters
101
+ - Only layer that knows about HTTP
102
+
103
+ ## CQRS Pattern
104
+
105
+ ### Commands (Write Operations)
106
+
107
+ ```csharp
108
+ // Commands modify state, return minimal data (ID or void)
109
+ public record CreateUserCommand(string Email, string Password) : IRequest<Guid>;
110
+ public record UpdateUserCommand(Guid Id, string Name) : IRequest;
111
+ public record DeleteUserCommand(Guid Id) : IRequest;
112
+ ```
113
+
114
+ ### Queries (Read Operations)
115
+
116
+ ```csharp
117
+ // Queries return data, never modify state
118
+ public record GetUserQuery(Guid Id) : IRequest<UserDto?>;
119
+ public record GetUsersQuery(int Page, int PageSize) : IRequest<PaginatedList<UserDto>>;
120
+ ```
121
+
122
+ ### Validation
123
+
124
+ ```csharp
125
+ // Validator in same folder as command
126
+ public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
127
+ {
128
+ public CreateUserCommandValidator(IUserRepository userRepository)
129
+ {
130
+ RuleFor(x => x.Email)
131
+ .NotEmpty()
132
+ .EmailAddress()
133
+ .MustAsync(async (email, ct) => !await userRepository.ExistsAsync(email, ct))
134
+ .WithMessage("Email already exists");
135
+
136
+ RuleFor(x => x.Password)
137
+ .NotEmpty()
138
+ .MinimumLength(8)
139
+ .Matches("[A-Z]").WithMessage("Must contain uppercase")
140
+ .Matches("[a-z]").WithMessage("Must contain lowercase")
141
+ .Matches("[0-9]").WithMessage("Must contain digit");
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Value Objects
147
+
148
+ ```csharp
149
+ // Immutable, equality by value
150
+ public record Email
151
+ {
152
+ public string Value { get; }
153
+
154
+ public Email(string value)
155
+ {
156
+ if (string.IsNullOrWhiteSpace(value))
157
+ throw new ArgumentException("Email cannot be empty");
158
+
159
+ if (!value.Contains('@'))
160
+ throw new ArgumentException("Invalid email format");
161
+
162
+ Value = value.ToLowerInvariant();
163
+ }
164
+
165
+ public static implicit operator string(Email email) => email.Value;
166
+ }
167
+ ```
168
+
169
+ ## Domain Events
170
+
171
+ ```csharp
172
+ // Domain event
173
+ public record UserCreatedEvent(Guid UserId, string Email) : INotification;
174
+
175
+ // In entity
176
+ public class User
177
+ {
178
+ private readonly List<INotification> _domainEvents = [];
179
+ public IReadOnlyCollection<INotification> DomainEvents => _domainEvents.AsReadOnly();
180
+
181
+ public static User Create(string email, string passwordHash)
182
+ {
183
+ var user = new User { Id = Guid.NewGuid(), Email = email, PasswordHash = passwordHash };
184
+ user._domainEvents.Add(new UserCreatedEvent(user.Id, user.Email));
185
+ return user;
186
+ }
187
+
188
+ public void ClearDomainEvents() => _domainEvents.Clear();
189
+ }
190
+
191
+ // Handler
192
+ public class UserCreatedEventHandler(IEmailService emailService) : INotificationHandler<UserCreatedEvent>
193
+ {
194
+ public async Task Handle(UserCreatedEvent notification, CancellationToken cancellationToken)
195
+ {
196
+ await emailService.SendWelcomeEmailAsync(notification.Email, cancellationToken);
197
+ }
198
+ }
199
+ ```