@malamute/ai-rules 1.0.0 → 1.2.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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,220 @@
1
+ ---
2
+ paths:
3
+ - "**/*.cs"
4
+ ---
5
+
6
+ # C# Async Patterns
7
+
8
+ ## Task vs ValueTask
9
+
10
+ ```csharp
11
+ // Use Task for most async operations
12
+ public async Task<User> GetUserAsync(int id)
13
+
14
+ // Use ValueTask when:
15
+ // 1. Method often completes synchronously
16
+ // 2. Hot path with many allocations
17
+ public async ValueTask<User?> GetCachedUserAsync(int id)
18
+ {
19
+ if (_cache.TryGetValue(id, out var user))
20
+ return user; // Synchronous path - no allocation
21
+
22
+ return await _repository.GetByIdAsync(id);
23
+ }
24
+
25
+ // BAD - ValueTask misuse (awaiting multiple times)
26
+ var task = GetValueTaskAsync();
27
+ await task;
28
+ await task; // Undefined behavior!
29
+ ```
30
+
31
+ ## Parallel Execution
32
+
33
+ ```csharp
34
+ // GOOD - parallel independent operations
35
+ var userTask = _userService.GetUserAsync(userId);
36
+ var ordersTask = _orderService.GetOrdersAsync(userId);
37
+ var prefsTask = _prefService.GetPreferencesAsync(userId);
38
+
39
+ await Task.WhenAll(userTask, ordersTask, prefsTask);
40
+
41
+ var user = await userTask;
42
+ var orders = await ordersTask;
43
+ var prefs = await prefsTask;
44
+
45
+ // BAD - sequential when parallel is possible
46
+ var user = await _userService.GetUserAsync(userId);
47
+ var orders = await _orderService.GetOrdersAsync(userId);
48
+ var prefs = await _prefService.GetPreferencesAsync(userId);
49
+ ```
50
+
51
+ ## Task.WhenAll Error Handling
52
+
53
+ ```csharp
54
+ // Handle all exceptions from parallel tasks
55
+ try
56
+ {
57
+ await Task.WhenAll(task1, task2, task3);
58
+ }
59
+ catch (Exception)
60
+ {
61
+ // Only first exception is thrown
62
+ // Check individual tasks for all errors
63
+ var exceptions = new[] { task1, task2, task3 }
64
+ .Where(t => t.IsFaulted)
65
+ .SelectMany(t => t.Exception!.InnerExceptions);
66
+
67
+ foreach (var ex in exceptions)
68
+ {
69
+ _logger.LogError(ex, "Task failed");
70
+ }
71
+ throw;
72
+ }
73
+ ```
74
+
75
+ ## Cancellation
76
+
77
+ ```csharp
78
+ // GOOD - check cancellation in loops
79
+ public async Task ProcessBatchAsync(
80
+ IEnumerable<Item> items,
81
+ CancellationToken cancellationToken)
82
+ {
83
+ foreach (var item in items)
84
+ {
85
+ cancellationToken.ThrowIfCancellationRequested();
86
+ await ProcessItemAsync(item, cancellationToken);
87
+ }
88
+ }
89
+
90
+ // GOOD - cancellation with timeout
91
+ using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
92
+ try
93
+ {
94
+ await LongOperationAsync(cts.Token);
95
+ }
96
+ catch (OperationCanceledException)
97
+ {
98
+ _logger.LogWarning("Operation timed out");
99
+ }
100
+
101
+ // Link multiple cancellation tokens
102
+ using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
103
+ requestToken,
104
+ applicationStoppingToken);
105
+ ```
106
+
107
+ ## Async Streams
108
+
109
+ ```csharp
110
+ // GOOD - IAsyncEnumerable for streaming data
111
+ public async IAsyncEnumerable<User> GetUsersStreamAsync(
112
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
113
+ {
114
+ await foreach (var user in _context.Users.AsAsyncEnumerable()
115
+ .WithCancellation(cancellationToken))
116
+ {
117
+ yield return user;
118
+ }
119
+ }
120
+
121
+ // Consuming async stream
122
+ await foreach (var user in GetUsersStreamAsync(cancellationToken))
123
+ {
124
+ await ProcessUserAsync(user);
125
+ }
126
+ ```
127
+
128
+ ## Avoid Async Pitfalls
129
+
130
+ ```csharp
131
+ // BAD - async void (except event handlers)
132
+ public async void ProcessAsync() // Exceptions lost, can't await
133
+ {
134
+ await Task.Delay(1000);
135
+ }
136
+
137
+ // GOOD - return Task
138
+ public async Task ProcessAsync()
139
+ {
140
+ await Task.Delay(1000);
141
+ }
142
+
143
+ // BAD - blocking on async (.Result, .Wait())
144
+ public void Process()
145
+ {
146
+ var result = GetDataAsync().Result; // Deadlock risk!
147
+ }
148
+
149
+ // BAD - unnecessary async/await
150
+ public async Task<int> GetCountAsync()
151
+ {
152
+ return await _repository.CountAsync(); // Just return the task
153
+ }
154
+
155
+ // GOOD - return task directly when no additional await needed
156
+ public Task<int> GetCountAsync()
157
+ {
158
+ return _repository.CountAsync();
159
+ }
160
+ ```
161
+
162
+ ## Thread Safety
163
+
164
+ ```csharp
165
+ // GOOD - use concurrent collections
166
+ private readonly ConcurrentDictionary<int, User> _cache = new();
167
+
168
+ // GOOD - SemaphoreSlim for async locking
169
+ private readonly SemaphoreSlim _semaphore = new(1, 1);
170
+
171
+ public async Task<User> GetOrCreateUserAsync(int id)
172
+ {
173
+ await _semaphore.WaitAsync();
174
+ try
175
+ {
176
+ if (!_cache.TryGetValue(id, out var user))
177
+ {
178
+ user = await _repository.GetByIdAsync(id);
179
+ _cache[id] = user;
180
+ }
181
+ return user;
182
+ }
183
+ finally
184
+ {
185
+ _semaphore.Release();
186
+ }
187
+ }
188
+
189
+ // BAD - lock with async (will not compile correctly)
190
+ lock (_syncObject)
191
+ {
192
+ await SomeAsyncOperation(); // Can't await inside lock
193
+ }
194
+ ```
195
+
196
+ ## Channel for Producer/Consumer
197
+
198
+ ```csharp
199
+ public class BackgroundProcessor
200
+ {
201
+ private readonly Channel<WorkItem> _channel =
202
+ Channel.CreateBounded<WorkItem>(new BoundedChannelOptions(100)
203
+ {
204
+ FullMode = BoundedChannelFullMode.Wait
205
+ });
206
+
207
+ public async ValueTask QueueWorkAsync(WorkItem item)
208
+ {
209
+ await _channel.Writer.WriteAsync(item);
210
+ }
211
+
212
+ public async Task ProcessAsync(CancellationToken stoppingToken)
213
+ {
214
+ await foreach (var item in _channel.Reader.ReadAllAsync(stoppingToken))
215
+ {
216
+ await ProcessItemAsync(item);
217
+ }
218
+ }
219
+ }
220
+ ```
@@ -0,0 +1,314 @@
1
+ ---
2
+ paths:
3
+ - "**/*.cs"
4
+ - "**/*.csproj"
5
+ ---
6
+
7
+ # C# Code Style Rules
8
+
9
+ ## Nullability
10
+
11
+ Enable nullable reference types in all projects:
12
+
13
+ ```xml
14
+ <PropertyGroup>
15
+ <Nullable>enable</Nullable>
16
+ </PropertyGroup>
17
+ ```
18
+
19
+ ```csharp
20
+ // GOOD - explicit nullability
21
+ public string Name { get; set; } = string.Empty;
22
+ public string? MiddleName { get; set; }
23
+
24
+ // BAD - ambiguous
25
+ public string Name { get; set; } // Warning: non-nullable not initialized
26
+ ```
27
+
28
+ ## Naming Conventions
29
+
30
+ | Element | Convention | Example |
31
+ |---------|------------|---------|
32
+ | Classes | PascalCase | `UserService` |
33
+ | Interfaces | IPascalCase | `IUserRepository` |
34
+ | Methods | PascalCase | `GetUserById` |
35
+ | Properties | PascalCase | `FirstName` |
36
+ | Private fields | _camelCase | `_userRepository` |
37
+ | Local variables | camelCase | `currentUser` |
38
+ | Constants | PascalCase | `MaxRetryCount` |
39
+ | Async methods | Suffix `Async` | `GetUserAsync` |
40
+
41
+ ```csharp
42
+ // GOOD
43
+ private readonly IUserRepository _userRepository;
44
+ public async Task<User?> GetUserByIdAsync(int id) { }
45
+
46
+ // BAD
47
+ private IUserRepository userRepository; // Missing underscore
48
+ public async Task<User?> GetUserById(int id) { } // Missing Async suffix
49
+ ```
50
+
51
+ ## Type Guidelines
52
+
53
+ ### Use `var` When Type Is Obvious
54
+
55
+ ```csharp
56
+ // GOOD - type is obvious
57
+ var user = new User();
58
+ var users = await _repository.GetAllAsync();
59
+ var count = items.Count;
60
+
61
+ // GOOD - type not obvious, be explicit
62
+ User user = GetUser();
63
+ IEnumerable<string> names = GetNames();
64
+
65
+ // BAD - redundant
66
+ User user = new User();
67
+ ```
68
+
69
+ ### Prefer Collection Expressions (C# 12+)
70
+
71
+ ```csharp
72
+ // GOOD - collection expressions
73
+ int[] numbers = [1, 2, 3];
74
+ List<string> names = ["Alice", "Bob"];
75
+ Dictionary<string, int> ages = new() { ["Alice"] = 30 };
76
+
77
+ // BAD - verbose
78
+ int[] numbers = new int[] { 1, 2, 3 };
79
+ List<string> names = new List<string> { "Alice", "Bob" };
80
+ ```
81
+
82
+ ### Use Pattern Matching
83
+
84
+ ```csharp
85
+ // GOOD - pattern matching
86
+ if (user is { IsActive: true, Role: "Admin" })
87
+ {
88
+ // ...
89
+ }
90
+
91
+ return status switch
92
+ {
93
+ OrderStatus.Pending => "Awaiting payment",
94
+ OrderStatus.Shipped => "On the way",
95
+ OrderStatus.Delivered => "Completed",
96
+ _ => "Unknown"
97
+ };
98
+
99
+ // BAD - verbose checks
100
+ if (user != null && user.IsActive && user.Role == "Admin")
101
+ ```
102
+
103
+ ## Async/Await
104
+
105
+ ### Always Use Async All The Way
106
+
107
+ ```csharp
108
+ // GOOD - async all the way
109
+ public async Task<User> GetUserAsync(int id)
110
+ {
111
+ return await _repository.GetByIdAsync(id);
112
+ }
113
+
114
+ // BAD - blocking on async
115
+ public User GetUser(int id)
116
+ {
117
+ return _repository.GetByIdAsync(id).Result; // Deadlock risk!
118
+ }
119
+
120
+ // BAD - async void (except event handlers)
121
+ public async void ProcessData() // Can't await, exceptions lost
122
+ ```
123
+
124
+ ### ConfigureAwait in Libraries
125
+
126
+ ```csharp
127
+ // In library code (not ASP.NET Core)
128
+ await SomeOperationAsync().ConfigureAwait(false);
129
+
130
+ // In ASP.NET Core controllers - not needed
131
+ await SomeOperationAsync();
132
+ ```
133
+
134
+ ### Cancellation Tokens
135
+
136
+ ```csharp
137
+ // GOOD - propagate cancellation
138
+ public async Task<List<User>> GetUsersAsync(CancellationToken cancellationToken = default)
139
+ {
140
+ return await _context.Users
141
+ .Where(u => u.IsActive)
142
+ .ToListAsync(cancellationToken);
143
+ }
144
+
145
+ // BAD - ignoring cancellation
146
+ public async Task<List<User>> GetUsersAsync()
147
+ {
148
+ return await _context.Users.ToListAsync();
149
+ }
150
+ ```
151
+
152
+ ## LINQ Best Practices
153
+
154
+ ```csharp
155
+ // GOOD - method syntax for complex queries
156
+ var activeAdmins = users
157
+ .Where(u => u.IsActive)
158
+ .Where(u => u.Role == Role.Admin)
159
+ .OrderBy(u => u.Name)
160
+ .Select(u => new UserDto(u.Id, u.Name))
161
+ .ToList();
162
+
163
+ // GOOD - query syntax for joins
164
+ var result = from order in orders
165
+ join customer in customers on order.CustomerId equals customer.Id
166
+ select new { order.Id, customer.Name };
167
+
168
+ // BAD - multiple enumerations
169
+ var users = GetUsers();
170
+ var count = users.Count(); // First enumeration
171
+ var first = users.First(); // Second enumeration
172
+
173
+ // GOOD - materialize once
174
+ var users = GetUsers().ToList();
175
+ var count = users.Count;
176
+ var first = users[0];
177
+ ```
178
+
179
+ ## Records and Immutability
180
+
181
+ ```csharp
182
+ // GOOD - immutable DTOs with records
183
+ public record UserDto(int Id, string Name, string Email);
184
+
185
+ public record CreateUserCommand(string Name, string Email)
186
+ {
187
+ public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
188
+ }
189
+
190
+ // GOOD - with-expressions for modifications
191
+ var updated = user with { Name = "New Name" };
192
+
193
+ // BAD - mutable DTOs
194
+ public class UserDto
195
+ {
196
+ public int Id { get; set; }
197
+ public string Name { get; set; }
198
+ }
199
+ ```
200
+
201
+ ## Error Handling
202
+
203
+ ### Specific Exceptions
204
+
205
+ ```csharp
206
+ // GOOD - specific exceptions
207
+ public User GetUser(int id)
208
+ {
209
+ return _repository.GetById(id)
210
+ ?? throw new NotFoundException($"User {id} not found");
211
+ }
212
+
213
+ // BAD - generic exception
214
+ throw new Exception("User not found");
215
+ ```
216
+
217
+ ### Never Swallow Exceptions
218
+
219
+ ```csharp
220
+ // BAD - silent failure
221
+ try
222
+ {
223
+ await _service.ProcessAsync();
224
+ }
225
+ catch (Exception)
226
+ {
227
+ // Swallowed
228
+ }
229
+
230
+ // GOOD - log and handle
231
+ try
232
+ {
233
+ await _service.ProcessAsync();
234
+ }
235
+ catch (Exception ex)
236
+ {
237
+ _logger.LogError(ex, "Failed to process");
238
+ throw;
239
+ }
240
+ ```
241
+
242
+ ## Disposables
243
+
244
+ ```csharp
245
+ // GOOD - using declaration (C# 8+)
246
+ await using var connection = new SqlConnection(connectionString);
247
+ await connection.OpenAsync();
248
+
249
+ // GOOD - using statement for limited scope
250
+ using (var stream = File.OpenRead(path))
251
+ {
252
+ // ...
253
+ }
254
+ // Stream disposed here
255
+
256
+ // BAD - manual disposal
257
+ var connection = new SqlConnection(connectionString);
258
+ try
259
+ {
260
+ await connection.OpenAsync();
261
+ }
262
+ finally
263
+ {
264
+ connection.Dispose();
265
+ }
266
+ ```
267
+
268
+ ## File-Scoped Namespaces
269
+
270
+ ```csharp
271
+ // GOOD - file-scoped (C# 10+)
272
+ namespace MyApp.Services;
273
+
274
+ public class UserService
275
+ {
276
+ // ...
277
+ }
278
+
279
+ // BAD - block-scoped (unnecessary nesting)
280
+ namespace MyApp.Services
281
+ {
282
+ public class UserService
283
+ {
284
+ // ...
285
+ }
286
+ }
287
+ ```
288
+
289
+ ## Primary Constructors (C# 12+)
290
+
291
+ ```csharp
292
+ // GOOD - primary constructor
293
+ public class UserService(IUserRepository repository, ILogger<UserService> logger)
294
+ {
295
+ public async Task<User?> GetUserAsync(int id)
296
+ {
297
+ logger.LogInformation("Getting user {Id}", id);
298
+ return await repository.GetByIdAsync(id);
299
+ }
300
+ }
301
+
302
+ // Also valid - traditional constructor with readonly fields
303
+ public class UserService
304
+ {
305
+ private readonly IUserRepository _repository;
306
+ private readonly ILogger<UserService> _logger;
307
+
308
+ public UserService(IUserRepository repository, ILogger<UserService> logger)
309
+ {
310
+ _repository = repository;
311
+ _logger = logger;
312
+ }
313
+ }
314
+ ```