@malamute/ai-rules 1.0.0 → 1.3.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 (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -0,0 +1,388 @@
1
+ ---
2
+ paths:
3
+ - "**/*.Validator.cs"
4
+ - "**/Validators/**/*.cs"
5
+ - "**/Validation/**/*.cs"
6
+ ---
7
+
8
+ # .NET Validation (FluentValidation)
9
+
10
+ ## Basic Validator
11
+
12
+ ```csharp
13
+ // Validators/CreateUserRequestValidator.cs
14
+ using FluentValidation;
15
+
16
+ public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
17
+ {
18
+ public CreateUserRequestValidator()
19
+ {
20
+ RuleFor(x => x.Email)
21
+ .NotEmpty().WithMessage("Email is required")
22
+ .EmailAddress().WithMessage("Invalid email format")
23
+ .MaximumLength(255);
24
+
25
+ RuleFor(x => x.Name)
26
+ .NotEmpty().WithMessage("Name is required")
27
+ .Length(2, 100).WithMessage("Name must be between 2 and 100 characters")
28
+ .Matches(@"^[a-zA-Z\s'-]+$").WithMessage("Name contains invalid characters");
29
+
30
+ RuleFor(x => x.Password)
31
+ .NotEmpty()
32
+ .MinimumLength(8)
33
+ .Matches(@"[A-Z]").WithMessage("Password must contain uppercase letter")
34
+ .Matches(@"[a-z]").WithMessage("Password must contain lowercase letter")
35
+ .Matches(@"[0-9]").WithMessage("Password must contain digit")
36
+ .Matches(@"[^a-zA-Z0-9]").WithMessage("Password must contain special character");
37
+
38
+ RuleFor(x => x.ConfirmPassword)
39
+ .Equal(x => x.Password).WithMessage("Passwords do not match");
40
+
41
+ RuleFor(x => x.DateOfBirth)
42
+ .NotEmpty()
43
+ .LessThan(DateTime.Today).WithMessage("Date of birth must be in the past")
44
+ .Must(BeAtLeast18).WithMessage("Must be at least 18 years old");
45
+ }
46
+
47
+ private bool BeAtLeast18(DateTime dateOfBirth)
48
+ {
49
+ return dateOfBirth <= DateTime.Today.AddYears(-18);
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Async Validation
55
+
56
+ ```csharp
57
+ public class CreateUserRequestValidator : AbstractValidator<CreateUserRequest>
58
+ {
59
+ private readonly IUserRepository _userRepository;
60
+
61
+ public CreateUserRequestValidator(IUserRepository userRepository)
62
+ {
63
+ _userRepository = userRepository;
64
+
65
+ RuleFor(x => x.Email)
66
+ .NotEmpty()
67
+ .EmailAddress()
68
+ .MustAsync(BeUniqueEmail).WithMessage("Email already exists");
69
+
70
+ RuleFor(x => x.Username)
71
+ .NotEmpty()
72
+ .MustAsync(BeUniqueUsername).WithMessage("Username is taken");
73
+ }
74
+
75
+ private async Task<bool> BeUniqueEmail(string email, CancellationToken ct)
76
+ {
77
+ return !await _userRepository.ExistsAsync(u => u.Email == email, ct);
78
+ }
79
+
80
+ private async Task<bool> BeUniqueUsername(string username, CancellationToken ct)
81
+ {
82
+ return !await _userRepository.ExistsAsync(u => u.Username == username, ct);
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Conditional Validation
88
+
89
+ ```csharp
90
+ public class OrderValidator : AbstractValidator<CreateOrderRequest>
91
+ {
92
+ public OrderValidator()
93
+ {
94
+ RuleFor(x => x.ShippingAddress)
95
+ .NotEmpty()
96
+ .When(x => x.DeliveryMethod == DeliveryMethod.Shipping);
97
+
98
+ RuleFor(x => x.PickupLocation)
99
+ .NotEmpty()
100
+ .When(x => x.DeliveryMethod == DeliveryMethod.Pickup);
101
+
102
+ // Complex condition
103
+ When(x => x.PaymentMethod == PaymentMethod.CreditCard, () =>
104
+ {
105
+ RuleFor(x => x.CardNumber).NotEmpty().CreditCard();
106
+ RuleFor(x => x.ExpiryDate).NotEmpty().Must(BeValidExpiryDate);
107
+ RuleFor(x => x.Cvv).NotEmpty().Length(3, 4);
108
+ });
109
+
110
+ // Otherwise
111
+ Otherwise(() =>
112
+ {
113
+ RuleFor(x => x.BankAccountNumber).NotEmpty();
114
+ });
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Collection Validation
120
+
121
+ ```csharp
122
+ public class OrderValidator : AbstractValidator<CreateOrderRequest>
123
+ {
124
+ public OrderValidator()
125
+ {
126
+ RuleFor(x => x.Items)
127
+ .NotEmpty().WithMessage("Order must contain at least one item");
128
+
129
+ RuleForEach(x => x.Items)
130
+ .SetValidator(new OrderItemValidator());
131
+
132
+ // Custom collection rules
133
+ RuleFor(x => x.Items)
134
+ .Must(items => items.Sum(i => i.Quantity) <= 100)
135
+ .WithMessage("Maximum 100 items per order");
136
+ }
137
+ }
138
+
139
+ public class OrderItemValidator : AbstractValidator<OrderItem>
140
+ {
141
+ public OrderItemValidator()
142
+ {
143
+ RuleFor(x => x.ProductId).NotEmpty();
144
+ RuleFor(x => x.Quantity).GreaterThan(0).LessThanOrEqualTo(10);
145
+ RuleFor(x => x.Price).GreaterThan(0);
146
+ }
147
+ }
148
+ ```
149
+
150
+ ## Nested Objects
151
+
152
+ ```csharp
153
+ public class CustomerValidator : AbstractValidator<Customer>
154
+ {
155
+ public CustomerValidator()
156
+ {
157
+ RuleFor(x => x.Name).NotEmpty();
158
+
159
+ RuleFor(x => x.Address)
160
+ .NotNull()
161
+ .SetValidator(new AddressValidator());
162
+
163
+ // Inline nested validation
164
+ RuleFor(x => x.Contact)
165
+ .ChildRules(contact =>
166
+ {
167
+ contact.RuleFor(c => c.Phone).NotEmpty();
168
+ contact.RuleFor(c => c.Email).EmailAddress();
169
+ });
170
+ }
171
+ }
172
+
173
+ public class AddressValidator : AbstractValidator<Address>
174
+ {
175
+ public AddressValidator()
176
+ {
177
+ RuleFor(x => x.Street).NotEmpty().MaximumLength(200);
178
+ RuleFor(x => x.City).NotEmpty().MaximumLength(100);
179
+ RuleFor(x => x.PostalCode).NotEmpty().Matches(@"^\d{5}(-\d{4})?$");
180
+ RuleFor(x => x.Country).NotEmpty().Length(2);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Custom Validators
186
+
187
+ ```csharp
188
+ // Extensions/ValidationExtensions.cs
189
+ public static class ValidationExtensions
190
+ {
191
+ public static IRuleBuilderOptions<T, string> PhoneNumber<T>(
192
+ this IRuleBuilder<T, string> ruleBuilder)
193
+ {
194
+ return ruleBuilder
195
+ .Matches(@"^\+?[1-9]\d{1,14}$")
196
+ .WithMessage("Invalid phone number format");
197
+ }
198
+
199
+ public static IRuleBuilderOptions<T, string> Slug<T>(
200
+ this IRuleBuilder<T, string> ruleBuilder)
201
+ {
202
+ return ruleBuilder
203
+ .Matches(@"^[a-z0-9]+(?:-[a-z0-9]+)*$")
204
+ .WithMessage("Invalid slug format");
205
+ }
206
+
207
+ public static IRuleBuilderOptions<T, decimal> Currency<T>(
208
+ this IRuleBuilder<T, decimal> ruleBuilder)
209
+ {
210
+ return ruleBuilder
211
+ .GreaterThanOrEqualTo(0)
212
+ .PrecisionScale(18, 2, true)
213
+ .WithMessage("Invalid currency format");
214
+ }
215
+ }
216
+
217
+ // Usage
218
+ RuleFor(x => x.Phone).PhoneNumber();
219
+ RuleFor(x => x.Slug).Slug();
220
+ RuleFor(x => x.Price).Currency();
221
+ ```
222
+
223
+ ## Reusable Property Validators
224
+
225
+ ```csharp
226
+ // Validators/PropertyValidators/UniqueEmailValidator.cs
227
+ public class UniqueEmailValidator<T> : AsyncPropertyValidator<T, string>
228
+ {
229
+ private readonly IUserRepository _userRepository;
230
+ private readonly Guid? _excludeUserId;
231
+
232
+ public UniqueEmailValidator(IUserRepository userRepository, Guid? excludeUserId = null)
233
+ {
234
+ _userRepository = userRepository;
235
+ _excludeUserId = excludeUserId;
236
+ }
237
+
238
+ public override string Name => "UniqueEmailValidator";
239
+
240
+ public override async Task<bool> IsValidAsync(
241
+ ValidationContext<T> context,
242
+ string value,
243
+ CancellationToken ct)
244
+ {
245
+ if (string.IsNullOrEmpty(value)) return true;
246
+
247
+ var existingUser = await _userRepository.FindByEmailAsync(value, ct);
248
+
249
+ if (existingUser == null) return true;
250
+
251
+ return _excludeUserId.HasValue && existingUser.Id == _excludeUserId.Value;
252
+ }
253
+
254
+ protected override string GetDefaultMessageTemplate(string errorCode)
255
+ => "Email is already in use";
256
+ }
257
+
258
+ // Usage
259
+ RuleFor(x => x.Email).SetAsyncValidator(new UniqueEmailValidator<Request>(_userRepository));
260
+ ```
261
+
262
+ ## Registration
263
+
264
+ ```csharp
265
+ // Program.cs or Startup.cs
266
+ builder.Services.AddValidatorsFromAssemblyContaining<CreateUserRequestValidator>();
267
+
268
+ // With pipeline behavior for MediatR
269
+ builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
270
+ ```
271
+
272
+ ## MediatR Validation Behavior
273
+
274
+ ```csharp
275
+ // Behaviors/ValidationBehavior.cs
276
+ public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
277
+ where TRequest : IRequest<TResponse>
278
+ {
279
+ private readonly IEnumerable<IValidator<TRequest>> _validators;
280
+
281
+ public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
282
+ {
283
+ _validators = validators;
284
+ }
285
+
286
+ public async Task<TResponse> Handle(
287
+ TRequest request,
288
+ RequestHandlerDelegate<TResponse> next,
289
+ CancellationToken cancellationToken)
290
+ {
291
+ if (!_validators.Any())
292
+ {
293
+ return await next();
294
+ }
295
+
296
+ var context = new ValidationContext<TRequest>(request);
297
+
298
+ var validationResults = await Task.WhenAll(
299
+ _validators.Select(v => v.ValidateAsync(context, cancellationToken)));
300
+
301
+ var failures = validationResults
302
+ .SelectMany(r => r.Errors)
303
+ .Where(f => f != null)
304
+ .ToList();
305
+
306
+ if (failures.Count != 0)
307
+ {
308
+ throw new ValidationException(failures);
309
+ }
310
+
311
+ return await next();
312
+ }
313
+ }
314
+ ```
315
+
316
+ ## API Endpoint Integration
317
+
318
+ ```csharp
319
+ // Using Minimal APIs
320
+ app.MapPost("/users", async (
321
+ CreateUserRequest request,
322
+ IValidator<CreateUserRequest> validator,
323
+ IMediator mediator) =>
324
+ {
325
+ var result = await validator.ValidateAsync(request);
326
+
327
+ if (!result.IsValid)
328
+ {
329
+ return Results.ValidationProblem(result.ToDictionary());
330
+ }
331
+
332
+ var user = await mediator.Send(new CreateUserCommand(request));
333
+ return Results.Created($"/users/{user.Id}", user);
334
+ });
335
+
336
+ // Extension for automatic validation
337
+ public static class ValidationExtensions
338
+ {
339
+ public static async Task<IResult> ValidateAndExecute<T>(
340
+ this T request,
341
+ IValidator<T> validator,
342
+ Func<Task<IResult>> onValid)
343
+ {
344
+ var result = await validator.ValidateAsync(request);
345
+
346
+ return result.IsValid
347
+ ? await onValid()
348
+ : Results.ValidationProblem(result.ToDictionary());
349
+ }
350
+ }
351
+ ```
352
+
353
+ ## Anti-patterns
354
+
355
+ ```csharp
356
+ // BAD: Business logic in validators
357
+ public class OrderValidator : AbstractValidator<Order>
358
+ {
359
+ public OrderValidator(IInventoryService inventory)
360
+ {
361
+ RuleFor(x => x.Items)
362
+ .MustAsync(async (items, ct) =>
363
+ {
364
+ await inventory.ReserveItems(items); // Side effect!
365
+ return true;
366
+ });
367
+ }
368
+ }
369
+
370
+ // GOOD: Validators only validate, services handle business logic
371
+
372
+ // BAD: Catching exceptions in validators
373
+ RuleFor(x => x.Value)
374
+ .Must(v =>
375
+ {
376
+ try { return Parse(v); }
377
+ catch { return false; } // Silent failure
378
+ });
379
+
380
+ // GOOD: Clear validation
381
+ RuleFor(x => x.Value).Must(BeValidFormat).WithMessage("Invalid format");
382
+
383
+ // BAD: Overly specific error messages exposing internals
384
+ .WithMessage($"Query failed: {exception.Message}");
385
+
386
+ // GOOD: User-friendly messages
387
+ .WithMessage("Email validation failed. Please try again.");
388
+ ```
@@ -0,0 +1,29 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(dotnet run *)",
5
+ "Bash(dotnet build *)",
6
+ "Bash(dotnet test *)",
7
+ "Bash(dotnet watch *)",
8
+ "Bash(dotnet ef *)",
9
+ "Bash(dotnet add *)",
10
+ "Bash(dotnet restore)",
11
+ "Bash(dotnet format *)",
12
+ "Read",
13
+ "Edit",
14
+ "Write"
15
+ ],
16
+ "deny": [
17
+ "Bash(git push *)",
18
+ "Bash(git push)",
19
+ "Bash(rm -rf *)",
20
+ "Read(appsettings.*.json)",
21
+ "Read(**/secrets.json)",
22
+ "Read(**/*.pfx)"
23
+ ]
24
+ },
25
+ "env": {
26
+ "ASPNETCORE_ENVIRONMENT": "Development",
27
+ "DOTNET_WATCH_RESTART_ON_RUDE_EDIT": "true"
28
+ }
29
+ }
@@ -0,0 +1,144 @@
1
+ # FastAPI Project Guidelines
2
+
3
+ @../_shared/CLAUDE.md
4
+
5
+ ## Stack
6
+
7
+ - Python 3.12+
8
+ - FastAPI 0.115+ (Pydantic v2 by default)
9
+ - SQLAlchemy 2.0+ (async support)
10
+ - Pydantic v2 for validation
11
+ - pytest + httpx for testing
12
+ - uv or poetry for dependencies
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/app/
18
+ ├── main.py # Entry point
19
+ ├── config.py # Settings (pydantic-settings)
20
+ ├── database.py # DB session, engine
21
+ ├── [domain]/ # Feature modules
22
+ │ ├── router.py # API endpoints
23
+ │ ├── schemas.py # Pydantic models (request/response)
24
+ │ ├── models.py # SQLAlchemy models
25
+ │ ├── service.py # Business logic
26
+ │ ├── repository.py # Data access
27
+ │ └── dependencies.py # Route dependencies
28
+ ├── core/ # Shared utilities
29
+ │ ├── exceptions.py
30
+ │ └── security.py
31
+ └── common/
32
+ ├── models.py # Base models
33
+ └── schemas.py # Shared schemas
34
+ ```
35
+
36
+ ## FastAPI Patterns
37
+
38
+ ### Dependency Injection
39
+
40
+ ```python
41
+ from typing import Annotated
42
+ from fastapi import Depends
43
+
44
+ CurrentUser = Annotated[User, Depends(get_current_user)]
45
+ DbSession = Annotated[AsyncSession, Depends(get_db)]
46
+
47
+ @router.get("/users/me")
48
+ async def get_me(user: CurrentUser, db: DbSession) -> UserResponse:
49
+ return await user_service.get_profile(db, user.id)
50
+ ```
51
+
52
+ ### Lifespan (not `on_event`)
53
+
54
+ ```python
55
+ from contextlib import asynccontextmanager
56
+
57
+ @asynccontextmanager
58
+ async def lifespan(app: FastAPI):
59
+ # Startup
60
+ app.state.db = await create_engine()
61
+ app.state.redis = await aioredis.from_url(settings.redis_url)
62
+ yield
63
+ # Shutdown
64
+ await app.state.db.dispose()
65
+ await app.state.redis.close()
66
+
67
+ app = FastAPI(lifespan=lifespan)
68
+ ```
69
+
70
+ ### Response Models
71
+
72
+ ```python
73
+ @router.get(
74
+ "/users/{user_id}",
75
+ response_model=UserResponse,
76
+ status_code=status.HTTP_200_OK,
77
+ )
78
+ async def get_user(user_id: int, db: DbSession) -> User:
79
+ user = await user_repo.get(db, user_id)
80
+ if not user:
81
+ raise HTTPException(status_code=404, detail="User not found")
82
+ return user
83
+ ```
84
+
85
+ ## Pydantic v2
86
+
87
+ ```python
88
+ from pydantic import BaseModel, ConfigDict, Field, EmailStr
89
+
90
+ class UserBase(BaseModel):
91
+ email: EmailStr
92
+ name: str = Field(min_length=1, max_length=100)
93
+
94
+ class UserCreate(UserBase):
95
+ password: str = Field(min_length=8)
96
+
97
+ class UserResponse(UserBase):
98
+ id: int
99
+ model_config = ConfigDict(from_attributes=True)
100
+ ```
101
+
102
+ ## SQLAlchemy 2.0
103
+
104
+ ```python
105
+ from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase
106
+
107
+ class Base(DeclarativeBase):
108
+ pass
109
+
110
+ class User(Base):
111
+ __tablename__ = "users"
112
+
113
+ id: Mapped[int] = mapped_column(primary_key=True)
114
+ email: Mapped[str] = mapped_column(unique=True, index=True)
115
+ hashed_password: Mapped[str]
116
+ is_active: Mapped[bool] = mapped_column(default=True)
117
+ ```
118
+
119
+ ## Error Handling
120
+
121
+ ```python
122
+ class AppException(Exception):
123
+ def __init__(self, status_code: int, detail: str):
124
+ self.status_code = status_code
125
+ self.detail = detail
126
+
127
+ @app.exception_handler(AppException)
128
+ async def app_exception_handler(request: Request, exc: AppException):
129
+ return JSONResponse(
130
+ status_code=exc.status_code,
131
+ content={"error": exc.detail},
132
+ )
133
+ ```
134
+
135
+ ## Commands
136
+
137
+ ```bash
138
+ uvicorn app.main:app --reload # Dev server
139
+ pytest # Run tests
140
+ pytest --cov=app # Coverage
141
+ ruff check . && ruff format . # Lint + format
142
+ alembic upgrade head # Run migrations
143
+ alembic revision --autogenerate # Generate migration
144
+ ```