@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,408 @@
1
+ ---
2
+ paths:
3
+ - "src/Infrastructure/**/*.cs"
4
+ - "**/*DbContext*.cs"
5
+ - "**/*Repository*.cs"
6
+ - "**/Configurations/**/*.cs"
7
+ ---
8
+
9
+ # Entity Framework Core Rules
10
+
11
+ ## DbContext Setup
12
+
13
+ ```csharp
14
+ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
15
+ : DbContext(options), IApplicationDbContext
16
+ {
17
+ public DbSet<User> Users => Set<User>();
18
+ public DbSet<Post> Posts => Set<Post>();
19
+
20
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
21
+ {
22
+ // Apply all configurations from assembly
23
+ modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
24
+
25
+ base.OnModelCreating(modelBuilder);
26
+ }
27
+
28
+ public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
29
+ {
30
+ // Audit timestamps
31
+ foreach (var entry in ChangeTracker.Entries<BaseEntity>())
32
+ {
33
+ switch (entry.State)
34
+ {
35
+ case EntityState.Added:
36
+ entry.Entity.CreatedAt = DateTime.UtcNow;
37
+ break;
38
+ case EntityState.Modified:
39
+ entry.Entity.UpdatedAt = DateTime.UtcNow;
40
+ break;
41
+ }
42
+ }
43
+
44
+ return await base.SaveChangesAsync(cancellationToken);
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Entity Configuration
50
+
51
+ ### Fluent Configuration (Preferred)
52
+
53
+ ```csharp
54
+ // Configurations/UserConfiguration.cs
55
+ public class UserConfiguration : IEntityTypeConfiguration<User>
56
+ {
57
+ public void Configure(EntityTypeBuilder<User> builder)
58
+ {
59
+ builder.ToTable("users");
60
+
61
+ builder.HasKey(u => u.Id);
62
+
63
+ builder.Property(u => u.Id)
64
+ .HasColumnName("id")
65
+ .ValueGeneratedNever(); // Use app-generated GUIDs
66
+
67
+ builder.Property(u => u.Email)
68
+ .HasColumnName("email")
69
+ .HasMaxLength(256)
70
+ .IsRequired();
71
+
72
+ builder.HasIndex(u => u.Email)
73
+ .IsUnique();
74
+
75
+ builder.Property(u => u.PasswordHash)
76
+ .HasColumnName("password_hash")
77
+ .HasMaxLength(256)
78
+ .IsRequired();
79
+
80
+ builder.Property(u => u.Name)
81
+ .HasColumnName("name")
82
+ .HasMaxLength(100);
83
+
84
+ builder.Property(u => u.Role)
85
+ .HasColumnName("role")
86
+ .HasConversion<string>()
87
+ .HasMaxLength(50);
88
+
89
+ builder.Property(u => u.CreatedAt)
90
+ .HasColumnName("created_at")
91
+ .HasDefaultValueSql("GETUTCDATE()");
92
+
93
+ builder.Property(u => u.UpdatedAt)
94
+ .HasColumnName("updated_at");
95
+
96
+ // Relationships
97
+ builder.HasMany(u => u.Posts)
98
+ .WithOne(p => p.Author)
99
+ .HasForeignKey(p => p.AuthorId)
100
+ .OnDelete(DeleteBehavior.Cascade);
101
+
102
+ // Value Object
103
+ builder.OwnsOne(u => u.Address, address =>
104
+ {
105
+ address.Property(a => a.Street).HasColumnName("address_street");
106
+ address.Property(a => a.City).HasColumnName("address_city");
107
+ address.Property(a => a.ZipCode).HasColumnName("address_zip");
108
+ });
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Naming Conventions
114
+
115
+ | C# | Database |
116
+ |----|----------|
117
+ | PascalCase properties | snake_case columns |
118
+ | PascalCase entities | snake_case tables |
119
+
120
+ ```csharp
121
+ // Global snake_case convention
122
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
123
+ {
124
+ foreach (var entity in modelBuilder.Model.GetEntityTypes())
125
+ {
126
+ // Table name
127
+ entity.SetTableName(ToSnakeCase(entity.GetTableName()!));
128
+
129
+ // Column names
130
+ foreach (var property in entity.GetProperties())
131
+ {
132
+ property.SetColumnName(ToSnakeCase(property.Name));
133
+ }
134
+
135
+ // Foreign keys
136
+ foreach (var key in entity.GetForeignKeys())
137
+ {
138
+ key.SetConstraintName(ToSnakeCase(key.GetConstraintName()!));
139
+ }
140
+ }
141
+ }
142
+
143
+ private static string ToSnakeCase(string name)
144
+ {
145
+ return string.Concat(name.Select((c, i) =>
146
+ i > 0 && char.IsUpper(c) ? "_" + c : c.ToString()))
147
+ .ToLower();
148
+ }
149
+ ```
150
+
151
+ ## Repository Pattern
152
+
153
+ ```csharp
154
+ // Domain/Interfaces/IRepository.cs
155
+ public interface IRepository<T> where T : class
156
+ {
157
+ Task<T?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
158
+ Task<IReadOnlyList<T>> GetAllAsync(CancellationToken cancellationToken = default);
159
+ Task AddAsync(T entity, CancellationToken cancellationToken = default);
160
+ void Update(T entity);
161
+ void Remove(T entity);
162
+ }
163
+
164
+ // Infrastructure/Repositories/Repository.cs
165
+ public class Repository<T>(ApplicationDbContext context) : IRepository<T>
166
+ where T : class
167
+ {
168
+ protected readonly DbSet<T> DbSet = context.Set<T>();
169
+
170
+ public virtual async Task<T?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
171
+ {
172
+ return await DbSet.FindAsync([id], cancellationToken);
173
+ }
174
+
175
+ public virtual async Task<IReadOnlyList<T>> GetAllAsync(CancellationToken cancellationToken = default)
176
+ {
177
+ return await DbSet.ToListAsync(cancellationToken);
178
+ }
179
+
180
+ public async Task AddAsync(T entity, CancellationToken cancellationToken = default)
181
+ {
182
+ await DbSet.AddAsync(entity, cancellationToken);
183
+ await context.SaveChangesAsync(cancellationToken);
184
+ }
185
+
186
+ public void Update(T entity)
187
+ {
188
+ DbSet.Update(entity);
189
+ }
190
+
191
+ public void Remove(T entity)
192
+ {
193
+ DbSet.Remove(entity);
194
+ }
195
+ }
196
+ ```
197
+
198
+ ## Query Patterns
199
+
200
+ ### Specification Pattern
201
+
202
+ ```csharp
203
+ // Base specification
204
+ public abstract class Specification<T>
205
+ {
206
+ public abstract Expression<Func<T, bool>> ToExpression();
207
+
208
+ public bool IsSatisfiedBy(T entity)
209
+ {
210
+ return ToExpression().Compile()(entity);
211
+ }
212
+ }
213
+
214
+ // Concrete specification
215
+ public class ActiveUsersSpecification : Specification<User>
216
+ {
217
+ public override Expression<Func<User, bool>> ToExpression()
218
+ {
219
+ return user => user.IsActive && user.EmailVerified;
220
+ }
221
+ }
222
+
223
+ // Usage in repository
224
+ public async Task<IReadOnlyList<User>> GetBySpecificationAsync(
225
+ Specification<User> spec,
226
+ CancellationToken cancellationToken = default)
227
+ {
228
+ return await DbSet
229
+ .Where(spec.ToExpression())
230
+ .ToListAsync(cancellationToken);
231
+ }
232
+ ```
233
+
234
+ ### Pagination
235
+
236
+ ```csharp
237
+ public async Task<PaginatedList<User>> GetPaginatedAsync(
238
+ int page,
239
+ int pageSize,
240
+ CancellationToken cancellationToken = default)
241
+ {
242
+ var query = DbSet.AsNoTracking();
243
+
244
+ var totalCount = await query.CountAsync(cancellationToken);
245
+
246
+ var items = await query
247
+ .OrderBy(u => u.CreatedAt)
248
+ .Skip((page - 1) * pageSize)
249
+ .Take(pageSize)
250
+ .ToListAsync(cancellationToken);
251
+
252
+ return new PaginatedList<User>(items, totalCount, page, pageSize);
253
+ }
254
+ ```
255
+
256
+ ### Efficient Queries
257
+
258
+ ```csharp
259
+ // Use AsNoTracking for read-only queries
260
+ var users = await context.Users
261
+ .AsNoTracking()
262
+ .Where(u => u.IsActive)
263
+ .ToListAsync();
264
+
265
+ // Project to DTO directly (avoid loading full entity)
266
+ var userDtos = await context.Users
267
+ .AsNoTracking()
268
+ .Where(u => u.IsActive)
269
+ .Select(u => new UserDto(u.Id, u.Email, u.Name))
270
+ .ToListAsync();
271
+
272
+ // Explicit loading for related data
273
+ var user = await context.Users.FindAsync(id);
274
+ await context.Entry(user)
275
+ .Collection(u => u.Posts)
276
+ .LoadAsync();
277
+
278
+ // Split queries for large includes
279
+ var users = await context.Users
280
+ .Include(u => u.Posts)
281
+ .Include(u => u.Comments)
282
+ .AsSplitQuery()
283
+ .ToListAsync();
284
+ ```
285
+
286
+ ## Migrations
287
+
288
+ ```bash
289
+ # Create migration
290
+ dotnet ef migrations add AddUsersTable \
291
+ -p src/Infrastructure \
292
+ -s src/WebApi \
293
+ -o Data/Migrations
294
+
295
+ # Apply migrations
296
+ dotnet ef database update -p src/Infrastructure -s src/WebApi
297
+
298
+ # Generate SQL script
299
+ dotnet ef migrations script -p src/Infrastructure -s src/WebApi -o migration.sql
300
+
301
+ # Revert last migration
302
+ dotnet ef migrations remove -p src/Infrastructure -s src/WebApi
303
+ ```
304
+
305
+ ### Migration Best Practices
306
+
307
+ ```csharp
308
+ public partial class AddUsersTable : Migration
309
+ {
310
+ protected override void Up(MigrationBuilder migrationBuilder)
311
+ {
312
+ migrationBuilder.CreateTable(
313
+ name: "users",
314
+ columns: table => new
315
+ {
316
+ id = table.Column<Guid>(nullable: false),
317
+ email = table.Column<string>(maxLength: 256, nullable: false),
318
+ created_at = table.Column<DateTime>(nullable: false, defaultValueSql: "GETUTCDATE()")
319
+ },
320
+ constraints: table =>
321
+ {
322
+ table.PrimaryKey("pk_users", x => x.id);
323
+ });
324
+
325
+ migrationBuilder.CreateIndex(
326
+ name: "ix_users_email",
327
+ table: "users",
328
+ column: "email",
329
+ unique: true);
330
+ }
331
+
332
+ protected override void Down(MigrationBuilder migrationBuilder)
333
+ {
334
+ migrationBuilder.DropTable(name: "users");
335
+ }
336
+ }
337
+ ```
338
+
339
+ ## Transactions
340
+
341
+ ```csharp
342
+ // Unit of Work pattern
343
+ public interface IUnitOfWork
344
+ {
345
+ Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
346
+ Task BeginTransactionAsync(CancellationToken cancellationToken = default);
347
+ Task CommitTransactionAsync(CancellationToken cancellationToken = default);
348
+ Task RollbackTransactionAsync(CancellationToken cancellationToken = default);
349
+ }
350
+
351
+ // Explicit transaction
352
+ await using var transaction = await context.Database.BeginTransactionAsync();
353
+ try
354
+ {
355
+ await context.Users.AddAsync(user);
356
+ await context.SaveChangesAsync();
357
+
358
+ await context.AuditLogs.AddAsync(auditLog);
359
+ await context.SaveChangesAsync();
360
+
361
+ await transaction.CommitAsync();
362
+ }
363
+ catch
364
+ {
365
+ await transaction.RollbackAsync();
366
+ throw;
367
+ }
368
+ ```
369
+
370
+ ## Soft Delete
371
+
372
+ ```csharp
373
+ // Base entity with soft delete
374
+ public abstract class SoftDeletableEntity
375
+ {
376
+ public DateTime? DeletedAt { get; set; }
377
+ public bool IsDeleted => DeletedAt.HasValue;
378
+ }
379
+
380
+ // Global query filter
381
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
382
+ {
383
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
384
+ {
385
+ if (typeof(SoftDeletableEntity).IsAssignableFrom(entityType.ClrType))
386
+ {
387
+ var parameter = Expression.Parameter(entityType.ClrType, "e");
388
+ var property = Expression.Property(parameter, nameof(SoftDeletableEntity.DeletedAt));
389
+ var filter = Expression.Lambda(
390
+ Expression.Equal(property, Expression.Constant(null, typeof(DateTime?))),
391
+ parameter);
392
+
393
+ modelBuilder.Entity(entityType.ClrType).HasQueryFilter(filter);
394
+ }
395
+ }
396
+ }
397
+
398
+ // Soft delete instead of hard delete
399
+ public void SoftDelete(SoftDeletableEntity entity)
400
+ {
401
+ entity.DeletedAt = DateTime.UtcNow;
402
+ }
403
+
404
+ // Include deleted records when needed
405
+ var allUsers = await context.Users
406
+ .IgnoreQueryFilters()
407
+ .ToListAsync();
408
+ ```