claude-code-pilot 3.1.1 → 3.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 (198) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +16 -11
  3. package/bin/install.js +127 -11
  4. package/manifest.json +20 -1
  5. package/package.json +4 -3
  6. package/src/agents/a11y-architect.md +141 -0
  7. package/src/agents/code-architect.md +71 -0
  8. package/src/agents/code-explorer.md +69 -0
  9. package/src/agents/code-simplifier.md +47 -0
  10. package/src/agents/comment-analyzer.md +45 -0
  11. package/src/agents/csharp-reviewer.md +101 -0
  12. package/src/agents/dart-build-resolver.md +201 -0
  13. package/src/agents/django-build-resolver.md +252 -0
  14. package/src/agents/django-reviewer.md +169 -0
  15. package/src/agents/fastapi-reviewer.md +79 -0
  16. package/src/agents/fsharp-reviewer.md +109 -0
  17. package/src/agents/pr-test-analyzer.md +45 -0
  18. package/src/agents/silent-failure-hunter.md +50 -0
  19. package/src/agents/swift-build-resolver.md +170 -0
  20. package/src/agents/swift-reviewer.md +116 -0
  21. package/src/agents/type-design-analyzer.md +41 -0
  22. package/src/available-rules/README.md +3 -1
  23. package/src/available-rules/dart/coding-style.md +159 -0
  24. package/src/available-rules/dart/hooks.md +66 -0
  25. package/src/available-rules/dart/patterns.md +261 -0
  26. package/src/available-rules/dart/security.md +135 -0
  27. package/src/available-rules/dart/testing.md +215 -0
  28. package/src/available-rules/web/coding-style.md +105 -0
  29. package/src/available-rules/web/design-quality.md +72 -0
  30. package/src/available-rules/web/hooks.md +129 -0
  31. package/src/available-rules/web/patterns.md +88 -0
  32. package/src/available-rules/web/performance.md +73 -0
  33. package/src/available-rules/web/security.md +66 -0
  34. package/src/available-rules/web/testing.md +64 -0
  35. package/src/commands/ccp/ai-integration-phase.md +36 -0
  36. package/src/commands/ccp/audit-fix.md +33 -0
  37. package/src/commands/ccp/code-review-fix.md +52 -0
  38. package/src/commands/ccp/cost-report.md +107 -0
  39. package/src/commands/ccp/eval-review.md +32 -0
  40. package/src/commands/ccp/extract_learnings.md +22 -0
  41. package/src/commands/ccp/import.md +37 -0
  42. package/src/commands/ccp/ingest-docs.md +42 -0
  43. package/src/commands/ccp/intel.md +179 -0
  44. package/src/commands/ccp/mvp-phase.md +45 -0
  45. package/src/commands/ccp/plan-prd.md +160 -0
  46. package/src/commands/ccp/plan-review-convergence.md +58 -0
  47. package/src/commands/ccp/pr-ecc.md +184 -0
  48. package/src/commands/ccp/scan.md +26 -0
  49. package/src/commands/ccp/security-scan.md +74 -0
  50. package/src/commands/ccp/sketch-wrap-up.md +31 -0
  51. package/src/commands/ccp/sketch.md +54 -0
  52. package/src/commands/ccp/spec-phase.md +62 -0
  53. package/src/commands/ccp/spike-wrap-up.md +31 -0
  54. package/src/commands/ccp/spike.md +51 -0
  55. package/src/commands/ccp/ultraplan-phase.md +33 -0
  56. package/src/hooks/ccp-bash-hook-dispatcher.js +96 -0
  57. package/src/hooks/ccp-context-monitor.js +23 -0
  58. package/src/hooks/ccp-doc-file-warning.js +93 -0
  59. package/src/hooks/ccp-pre-bash-dispatcher.js +24 -0
  60. package/src/hooks/ccp-read-injection-scanner.js +152 -0
  61. package/src/hooks/ccp-write-gateguard.js +868 -0
  62. package/src/hooks/kit-check-update.js +59 -7
  63. package/src/hooks/run-with-flags-shell.sh +1 -0
  64. package/src/hooks/run-with-flags.js +48 -1
  65. package/src/hooks/session-end.js +88 -1
  66. package/src/lib/hook-flags.js +14 -0
  67. package/src/lib/project-detect.js +0 -2
  68. package/src/lib/shell-substitution.js +499 -0
  69. package/src/pilot/references/agent-contracts.md +79 -0
  70. package/src/pilot/references/ai-evals.md +156 -0
  71. package/src/pilot/references/ai-frameworks.md +186 -0
  72. package/src/pilot/references/doc-conflict-engine.md +91 -0
  73. package/src/pilot/references/execute-mvp-tdd.md +81 -0
  74. package/src/pilot/references/gate-prompts.md +100 -0
  75. package/src/pilot/references/gates.md +70 -0
  76. package/src/pilot/references/mandatory-initial-read.md +2 -0
  77. package/src/pilot/references/mvp-concepts.md +49 -0
  78. package/src/pilot/references/planner-graphify-auto-update.md +67 -0
  79. package/src/pilot/references/planner-human-verify-mode.md +57 -0
  80. package/src/pilot/references/planner-mvp-mode.md +53 -0
  81. package/src/pilot/references/project-skills-discovery.md +19 -0
  82. package/src/pilot/references/revision-loop.md +97 -0
  83. package/src/pilot/references/skeleton-template.md +48 -0
  84. package/src/pilot/references/sketch-interactivity.md +41 -0
  85. package/src/pilot/references/sketch-theme-system.md +94 -0
  86. package/src/pilot/references/sketch-tooling.md +45 -0
  87. package/src/pilot/references/sketch-variant-patterns.md +81 -0
  88. package/src/pilot/references/spidr-splitting.md +69 -0
  89. package/src/pilot/references/thinking-models-debug.md +44 -0
  90. package/src/pilot/references/thinking-models-execution.md +50 -0
  91. package/src/pilot/references/thinking-models-planning.md +62 -0
  92. package/src/pilot/references/thinking-models-research.md +50 -0
  93. package/src/pilot/references/thinking-models-verification.md +55 -0
  94. package/src/pilot/references/user-story-template.md +58 -0
  95. package/src/pilot/references/verify-mvp-mode.md +85 -0
  96. package/src/pilot/references/worktree-path-safety.md +89 -0
  97. package/src/pilot/templates/AI-SPEC.md +246 -0
  98. package/src/pilot/templates/spec.md +307 -0
  99. package/src/pilot/workflows/ai-integration-phase.md +284 -0
  100. package/src/pilot/workflows/audit-fix.md +175 -0
  101. package/src/pilot/workflows/code-review-fix.md +497 -0
  102. package/src/pilot/workflows/eval-review.md +155 -0
  103. package/src/pilot/workflows/extract_learnings.md +242 -0
  104. package/src/pilot/workflows/help.md +5 -0
  105. package/src/pilot/workflows/import.md +246 -0
  106. package/src/pilot/workflows/ingest-docs.md +328 -0
  107. package/src/pilot/workflows/mvp-phase.md +199 -0
  108. package/src/pilot/workflows/plan-review-convergence.md +329 -0
  109. package/src/pilot/workflows/scan.md +102 -0
  110. package/src/pilot/workflows/sketch-wrap-up.md +285 -0
  111. package/src/pilot/workflows/sketch.md +360 -0
  112. package/src/pilot/workflows/spec-phase.md +262 -0
  113. package/src/pilot/workflows/spike-wrap-up.md +306 -0
  114. package/src/pilot/workflows/spike.md +452 -0
  115. package/src/pilot/workflows/ultraplan-phase.md +189 -0
  116. package/src/skills/accessibility/SKILL.md +146 -0
  117. package/src/skills/agent-architecture-audit/SKILL.md +256 -0
  118. package/src/skills/agent-eval/SKILL.md +145 -0
  119. package/src/skills/agent-harness-design/SKILL.md +73 -0
  120. package/src/skills/agent-introspection-debugging/SKILL.md +153 -0
  121. package/src/skills/android-clean-architecture/SKILL.md +339 -0
  122. package/src/skills/angular-developer/SKILL.md +154 -0
  123. package/src/skills/angular-developer/references/angular-animations.md +160 -0
  124. package/src/skills/angular-developer/references/angular-aria.md +410 -0
  125. package/src/skills/angular-developer/references/cli.md +86 -0
  126. package/src/skills/angular-developer/references/component-harnesses.md +59 -0
  127. package/src/skills/angular-developer/references/component-styling.md +91 -0
  128. package/src/skills/angular-developer/references/components.md +117 -0
  129. package/src/skills/angular-developer/references/creating-services.md +97 -0
  130. package/src/skills/angular-developer/references/data-resolvers.md +69 -0
  131. package/src/skills/angular-developer/references/define-routes.md +67 -0
  132. package/src/skills/angular-developer/references/defining-providers.md +72 -0
  133. package/src/skills/angular-developer/references/di-fundamentals.md +120 -0
  134. package/src/skills/angular-developer/references/e2e-testing.md +56 -0
  135. package/src/skills/angular-developer/references/effects.md +83 -0
  136. package/src/skills/angular-developer/references/hierarchical-injectors.md +43 -0
  137. package/src/skills/angular-developer/references/host-elements.md +80 -0
  138. package/src/skills/angular-developer/references/injection-context.md +63 -0
  139. package/src/skills/angular-developer/references/inputs.md +101 -0
  140. package/src/skills/angular-developer/references/linked-signal.md +59 -0
  141. package/src/skills/angular-developer/references/loading-strategies.md +61 -0
  142. package/src/skills/angular-developer/references/mcp.md +108 -0
  143. package/src/skills/angular-developer/references/navigate-to-routes.md +69 -0
  144. package/src/skills/angular-developer/references/outputs.md +86 -0
  145. package/src/skills/angular-developer/references/reactive-forms.md +122 -0
  146. package/src/skills/angular-developer/references/rendering-strategies.md +44 -0
  147. package/src/skills/angular-developer/references/resource.md +77 -0
  148. package/src/skills/angular-developer/references/route-animations.md +56 -0
  149. package/src/skills/angular-developer/references/route-guards.md +52 -0
  150. package/src/skills/angular-developer/references/router-lifecycle.md +45 -0
  151. package/src/skills/angular-developer/references/router-testing.md +87 -0
  152. package/src/skills/angular-developer/references/show-routes-with-outlets.md +68 -0
  153. package/src/skills/angular-developer/references/signal-forms.md +795 -0
  154. package/src/skills/angular-developer/references/signals-overview.md +94 -0
  155. package/src/skills/angular-developer/references/tailwind-css.md +69 -0
  156. package/src/skills/angular-developer/references/template-driven-forms.md +114 -0
  157. package/src/skills/angular-developer/references/testing-fundamentals.md +65 -0
  158. package/src/skills/api-connector-builder/SKILL.md +120 -0
  159. package/src/skills/code-tour/SKILL.md +236 -0
  160. package/src/skills/compose-multiplatform-patterns/SKILL.md +299 -0
  161. package/src/skills/csharp-testing/SKILL.md +321 -0
  162. package/src/skills/dart-flutter-patterns/SKILL.md +563 -0
  163. package/src/skills/dashboard-builder/SKILL.md +108 -0
  164. package/src/skills/dotnet-patterns/SKILL.md +321 -0
  165. package/src/skills/error-handling/SKILL.md +376 -0
  166. package/src/skills/fastapi-patterns/SKILL.md +327 -0
  167. package/src/skills/flox-environments/SKILL.md +496 -0
  168. package/src/skills/frontend-design/SKILL.md +145 -0
  169. package/src/skills/frontend-slides/SKILL.md +184 -0
  170. package/src/skills/frontend-slides/STYLE_PRESETS.md +330 -0
  171. package/src/skills/fsharp-testing/SKILL.md +280 -0
  172. package/src/skills/gateguard/SKILL.md +121 -0
  173. package/src/skills/github-ops/SKILL.md +144 -0
  174. package/src/skills/hookify-rules/SKILL.md +128 -0
  175. package/src/skills/ios-icon-gen/SKILL.md +157 -0
  176. package/src/skills/ios-icon-gen/scripts/generate_icons.swift +258 -0
  177. package/src/skills/ios-icon-gen/scripts/iconify_gen.sh +235 -0
  178. package/src/skills/knowledge-ops/SKILL.md +154 -0
  179. package/src/skills/liquid-glass-design/SKILL.md +279 -0
  180. package/src/skills/make-interfaces-feel-better/SKILL.md +151 -0
  181. package/src/skills/mysql-patterns/SKILL.md +412 -0
  182. package/src/skills/nestjs-patterns/SKILL.md +230 -0
  183. package/src/skills/plan-orchestrate/SKILL.md +220 -0
  184. package/src/skills/prisma-patterns/SKILL.md +371 -0
  185. package/src/skills/production-audit/SKILL.md +206 -0
  186. package/src/skills/security-bounty-hunter/SKILL.md +99 -0
  187. package/src/skills/security-scan/references/agentshield-policy-exception/candidate-playbook.md +49 -0
  188. package/src/skills/security-scan/references/agentshield-policy-exception/report.json +35 -0
  189. package/src/skills/security-scan/references/agentshield-policy-exception/scenario.json +62 -0
  190. package/src/skills/security-scan/references/agentshield-policy-exception/trace.json +45 -0
  191. package/src/skills/security-scan/references/agentshield-policy-exception/verifier-result.json +35 -0
  192. package/src/skills/swift-actor-persistence/SKILL.md +143 -0
  193. package/src/skills/swift-protocol-di-testing/SKILL.md +190 -0
  194. package/src/skills/swiftui-patterns/SKILL.md +259 -0
  195. package/src/skills/terminal-ops/SKILL.md +109 -0
  196. package/src/skills/ui-demo/SKILL.md +465 -0
  197. package/src/skills/vite-patterns/SKILL.md +449 -0
  198. package/src/skills/windows-desktop-e2e/SKILL.md +887 -0
@@ -0,0 +1,321 @@
1
+ ---
2
+ name: dotnet-patterns
3
+ description: Idiomatic C# and .NET patterns, conventions, dependency injection, async/await, and best practices for building robust, maintainable .NET applications.
4
+ origin: ECC
5
+ ---
6
+
7
+ # .NET Development Patterns
8
+
9
+ Idiomatic C# and .NET patterns for building robust, performant, and maintainable applications.
10
+
11
+ ## When to Activate
12
+
13
+ - Writing new C# code
14
+ - Reviewing C# code
15
+ - Refactoring existing .NET applications
16
+ - Designing service architectures with ASP.NET Core
17
+
18
+ ## Core Principles
19
+
20
+ ### 1. Prefer Immutability
21
+
22
+ Use records and init-only properties for data models. Mutability should be an explicit, justified choice.
23
+
24
+ ```csharp
25
+ // Good: Immutable value object
26
+ public sealed record Money(decimal Amount, string Currency);
27
+
28
+ // Good: Immutable DTO with init setters
29
+ public sealed class CreateOrderRequest
30
+ {
31
+ public required string CustomerId { get; init; }
32
+ public required IReadOnlyList<OrderItem> Items { get; init; }
33
+ }
34
+
35
+ // Bad: Mutable model with public setters
36
+ public class Order
37
+ {
38
+ public string CustomerId { get; set; }
39
+ public List<OrderItem> Items { get; set; }
40
+ }
41
+ ```
42
+
43
+ ### 2. Explicit Over Implicit
44
+
45
+ Be clear about nullability, access modifiers, and intent.
46
+
47
+ ```csharp
48
+ // Good: Explicit access modifiers and nullability
49
+ public sealed class UserService
50
+ {
51
+ private readonly IUserRepository _repository;
52
+ private readonly ILogger<UserService> _logger;
53
+
54
+ public UserService(IUserRepository repository, ILogger<UserService> logger)
55
+ {
56
+ _repository = repository ?? throw new ArgumentNullException(nameof(repository));
57
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
58
+ }
59
+
60
+ public async Task<User?> FindByIdAsync(Guid id, CancellationToken cancellationToken)
61
+ {
62
+ return await _repository.FindByIdAsync(id, cancellationToken);
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### 3. Depend on Abstractions
68
+
69
+ Use interfaces for service boundaries. Register via DI container.
70
+
71
+ ```csharp
72
+ // Good: Interface-based dependency
73
+ public interface IOrderRepository
74
+ {
75
+ Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken);
76
+ Task<IReadOnlyList<Order>> FindByCustomerAsync(string customerId, CancellationToken cancellationToken);
77
+ Task AddAsync(Order order, CancellationToken cancellationToken);
78
+ }
79
+
80
+ // Registration
81
+ builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();
82
+ ```
83
+
84
+ ## Async/Await Patterns
85
+
86
+ ### Proper Async Usage
87
+
88
+ ```csharp
89
+ // Good: Async all the way, with CancellationToken
90
+ public async Task<OrderSummary> GetOrderSummaryAsync(
91
+ Guid orderId,
92
+ CancellationToken cancellationToken)
93
+ {
94
+ var order = await _repository.FindByIdAsync(orderId, cancellationToken)
95
+ ?? throw new NotFoundException($"Order {orderId} not found");
96
+
97
+ var customer = await _customerService.GetAsync(order.CustomerId, cancellationToken);
98
+
99
+ return new OrderSummary(order, customer);
100
+ }
101
+
102
+ // Bad: Blocking on async
103
+ public OrderSummary GetOrderSummary(Guid orderId)
104
+ {
105
+ var order = _repository.FindByIdAsync(orderId, CancellationToken.None).Result; // Deadlock risk
106
+ return new OrderSummary(order);
107
+ }
108
+ ```
109
+
110
+ ### Parallel Async Operations
111
+
112
+ ```csharp
113
+ // Good: Concurrent independent operations
114
+ public async Task<DashboardData> LoadDashboardAsync(CancellationToken cancellationToken)
115
+ {
116
+ var ordersTask = _orderService.GetRecentAsync(cancellationToken);
117
+ var metricsTask = _metricsService.GetCurrentAsync(cancellationToken);
118
+ var alertsTask = _alertService.GetActiveAsync(cancellationToken);
119
+
120
+ await Task.WhenAll(ordersTask, metricsTask, alertsTask);
121
+
122
+ return new DashboardData(
123
+ Orders: await ordersTask,
124
+ Metrics: await metricsTask,
125
+ Alerts: await alertsTask);
126
+ }
127
+ ```
128
+
129
+ ## Options Pattern
130
+
131
+ Bind configuration sections to strongly-typed objects.
132
+
133
+ ```csharp
134
+ public sealed class SmtpOptions
135
+ {
136
+ public const string SectionName = "Smtp";
137
+
138
+ public required string Host { get; init; }
139
+ public required int Port { get; init; }
140
+ public required string Username { get; init; }
141
+ public bool UseSsl { get; init; } = true;
142
+ }
143
+
144
+ // Registration
145
+ builder.Services.Configure<SmtpOptions>(
146
+ builder.Configuration.GetSection(SmtpOptions.SectionName));
147
+
148
+ // Usage via injection
149
+ public class EmailService(IOptions<SmtpOptions> options)
150
+ {
151
+ private readonly SmtpOptions _smtp = options.Value;
152
+ }
153
+ ```
154
+
155
+ ## Result Pattern
156
+
157
+ Return explicit success/failure instead of throwing for expected failures.
158
+
159
+ ```csharp
160
+ public sealed record Result<T>
161
+ {
162
+ public bool IsSuccess { get; }
163
+ public T? Value { get; }
164
+ public string? Error { get; }
165
+
166
+ private Result(T value) { IsSuccess = true; Value = value; }
167
+ private Result(string error) { IsSuccess = false; Error = error; }
168
+
169
+ public static Result<T> Success(T value) => new(value);
170
+ public static Result<T> Failure(string error) => new(error);
171
+ }
172
+
173
+ // Usage
174
+ public async Task<Result<Order>> PlaceOrderAsync(CreateOrderRequest request)
175
+ {
176
+ if (request.Items.Count == 0)
177
+ return Result<Order>.Failure("Order must contain at least one item");
178
+
179
+ var order = Order.Create(request);
180
+ await _repository.AddAsync(order, CancellationToken.None);
181
+ return Result<Order>.Success(order);
182
+ }
183
+ ```
184
+
185
+ ## Repository Pattern with EF Core
186
+
187
+ ```csharp
188
+ public sealed class SqlOrderRepository : IOrderRepository
189
+ {
190
+ private readonly AppDbContext _db;
191
+
192
+ public SqlOrderRepository(AppDbContext db) => _db = db;
193
+
194
+ public async Task<Order?> FindByIdAsync(Guid id, CancellationToken cancellationToken)
195
+ {
196
+ return await _db.Orders
197
+ .Include(o => o.Items)
198
+ .AsNoTracking()
199
+ .FirstOrDefaultAsync(o => o.Id == id, cancellationToken);
200
+ }
201
+
202
+ public async Task<IReadOnlyList<Order>> FindByCustomerAsync(
203
+ string customerId,
204
+ CancellationToken cancellationToken)
205
+ {
206
+ return await _db.Orders
207
+ .Where(o => o.CustomerId == customerId)
208
+ .OrderByDescending(o => o.CreatedAt)
209
+ .AsNoTracking()
210
+ .ToListAsync(cancellationToken);
211
+ }
212
+
213
+ public async Task AddAsync(Order order, CancellationToken cancellationToken)
214
+ {
215
+ _db.Orders.Add(order);
216
+ await _db.SaveChangesAsync(cancellationToken);
217
+ }
218
+ }
219
+ ```
220
+
221
+ ## Middleware and Pipeline
222
+
223
+ ```csharp
224
+ // Custom middleware
225
+ public sealed class RequestTimingMiddleware
226
+ {
227
+ private readonly RequestDelegate _next;
228
+ private readonly ILogger<RequestTimingMiddleware> _logger;
229
+
230
+ public RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
231
+ {
232
+ _next = next;
233
+ _logger = logger;
234
+ }
235
+
236
+ public async Task InvokeAsync(HttpContext context)
237
+ {
238
+ var stopwatch = Stopwatch.StartNew();
239
+ try
240
+ {
241
+ await _next(context);
242
+ }
243
+ finally
244
+ {
245
+ stopwatch.Stop();
246
+ _logger.LogInformation(
247
+ "Request {Method} {Path} completed in {ElapsedMs}ms with status {StatusCode}",
248
+ context.Request.Method,
249
+ context.Request.Path,
250
+ stopwatch.ElapsedMilliseconds,
251
+ context.Response.StatusCode);
252
+ }
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Minimal API Patterns
258
+
259
+ ```csharp
260
+ // Organized with route groups
261
+ var orders = app.MapGroup("/api/orders")
262
+ .RequireAuthorization()
263
+ .WithTags("Orders");
264
+
265
+ orders.MapGet("/{id:guid}", async (
266
+ Guid id,
267
+ IOrderRepository repository,
268
+ CancellationToken cancellationToken) =>
269
+ {
270
+ var order = await repository.FindByIdAsync(id, cancellationToken);
271
+ return order is not null
272
+ ? TypedResults.Ok(order)
273
+ : TypedResults.NotFound();
274
+ });
275
+
276
+ orders.MapPost("/", async (
277
+ CreateOrderRequest request,
278
+ IOrderService service,
279
+ CancellationToken cancellationToken) =>
280
+ {
281
+ var result = await service.PlaceOrderAsync(request, cancellationToken);
282
+ return result.IsSuccess
283
+ ? TypedResults.Created($"/api/orders/{result.Value!.Id}", result.Value)
284
+ : TypedResults.BadRequest(result.Error);
285
+ });
286
+ ```
287
+
288
+ ## Guard Clauses
289
+
290
+ ```csharp
291
+ // Good: Early returns with clear validation
292
+ public async Task<ProcessResult> ProcessPaymentAsync(
293
+ PaymentRequest request,
294
+ CancellationToken cancellationToken)
295
+ {
296
+ ArgumentNullException.ThrowIfNull(request);
297
+
298
+ if (request.Amount <= 0)
299
+ throw new ArgumentOutOfRangeException(nameof(request.Amount), "Amount must be positive");
300
+
301
+ if (string.IsNullOrWhiteSpace(request.Currency))
302
+ throw new ArgumentException("Currency is required", nameof(request.Currency));
303
+
304
+ // Happy path continues here without nesting
305
+ var gateway = _gatewayFactory.Create(request.Currency);
306
+ return await gateway.ChargeAsync(request, cancellationToken);
307
+ }
308
+ ```
309
+
310
+ ## Anti-Patterns to Avoid
311
+
312
+ | Anti-Pattern | Fix |
313
+ |---|---|
314
+ | `async void` methods | Return `Task` (except event handlers) |
315
+ | `.Result` or `.Wait()` | Use `await` |
316
+ | `catch (Exception) { }` | Handle or rethrow with context |
317
+ | `new Service()` in constructors | Use constructor injection |
318
+ | `public` fields | Use properties with appropriate accessors |
319
+ | `dynamic` in business logic | Use generics or explicit types |
320
+ | Mutable `static` state | Use DI scoping or `ConcurrentDictionary` |
321
+ | `string.Format` in loops | Use `StringBuilder` or interpolated string handlers |
@@ -0,0 +1,376 @@
1
+ ---
2
+ name: error-handling
3
+ description: Patterns for robust error handling across TypeScript, Python, and Go. Covers typed errors, error boundaries, retries, circuit breakers, and user-facing error messages.
4
+ origin: ECC
5
+ ---
6
+
7
+ # Error Handling Patterns
8
+
9
+ Consistent, robust error handling patterns for production applications.
10
+
11
+ ## When to Activate
12
+
13
+ - Designing error types or exception hierarchies for a new module or service
14
+ - Adding retry logic or circuit breakers for unreliable external dependencies
15
+ - Reviewing API endpoints for missing error handling
16
+ - Implementing user-facing error messages and feedback
17
+ - Debugging cascading failures or silent error swallowing
18
+
19
+ ## Core Principles
20
+
21
+ 1. **Fail fast and loudly** — surface errors at the boundary where they occur; don't bury them
22
+ 2. **Typed errors over string messages** — errors are first-class values with structure
23
+ 3. **User messages ≠ developer messages** — show friendly text to users, log full context server-side
24
+ 4. **Never swallow errors silently** — every `catch` block must either handle, re-throw, or log
25
+ 5. **Errors are part of your API contract** — document every error code a client may receive
26
+
27
+ ## TypeScript / JavaScript
28
+
29
+ ### Typed Error Classes
30
+
31
+ ```typescript
32
+ // Define an error hierarchy for your domain
33
+ export class AppError extends Error {
34
+ constructor(
35
+ message: string,
36
+ public readonly code: string,
37
+ public readonly statusCode: number = 500,
38
+ public readonly details?: unknown,
39
+ ) {
40
+ super(message)
41
+ this.name = this.constructor.name
42
+ // Maintain correct prototype chain in transpiled ES5 JavaScript.
43
+ // Required for `instanceof` checks (e.g., `error instanceof NotFoundError`)
44
+ // to work correctly when extending the built-in Error class.
45
+ Object.setPrototypeOf(this, new.target.prototype)
46
+ }
47
+ }
48
+
49
+ export class NotFoundError extends AppError {
50
+ constructor(resource: string, id: string) {
51
+ super(`${resource} not found: ${id}`, 'NOT_FOUND', 404)
52
+ }
53
+ }
54
+
55
+ export class ValidationError extends AppError {
56
+ constructor(message: string, details: { field: string; message: string }[]) {
57
+ super(message, 'VALIDATION_ERROR', 422, details)
58
+ }
59
+ }
60
+
61
+ export class UnauthorizedError extends AppError {
62
+ constructor(reason = 'Authentication required') {
63
+ super(reason, 'UNAUTHORIZED', 401)
64
+ }
65
+ }
66
+
67
+ export class RateLimitError extends AppError {
68
+ constructor(public readonly retryAfterMs: number) {
69
+ super('Rate limit exceeded', 'RATE_LIMITED', 429)
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Result Pattern (no-throw style)
75
+
76
+ For operations where failure is expected and common (parsing, external calls):
77
+
78
+ ```typescript
79
+ type Result<T, E = AppError> =
80
+ | { ok: true; value: T }
81
+ | { ok: false; error: E }
82
+
83
+ function ok<T>(value: T): Result<T> {
84
+ return { ok: true, value }
85
+ }
86
+
87
+ function err<E>(error: E): Result<never, E> {
88
+ return { ok: false, error }
89
+ }
90
+
91
+ // Usage
92
+ async function fetchUser(id: string): Promise<Result<User>> {
93
+ try {
94
+ const user = await db.users.findUnique({ where: { id } })
95
+ if (!user) return err(new NotFoundError('User', id))
96
+ return ok(user)
97
+ } catch (e) {
98
+ return err(new AppError('Database error', 'DB_ERROR'))
99
+ }
100
+ }
101
+
102
+ const result = await fetchUser('abc-123')
103
+ if (!result.ok) {
104
+ // TypeScript knows result.error here
105
+ logger.error('Failed to fetch user', { error: result.error })
106
+ return
107
+ }
108
+ // TypeScript knows result.value here
109
+ console.log(result.value.email)
110
+ ```
111
+
112
+ ### API Error Handler (Next.js / Express)
113
+
114
+ ```typescript
115
+ import { NextRequest, NextResponse } from 'next/server'
116
+
117
+ function handleApiError(error: unknown): NextResponse {
118
+ // Known application error
119
+ if (error instanceof AppError) {
120
+ return NextResponse.json(
121
+ {
122
+ error: {
123
+ code: error.code,
124
+ message: error.message,
125
+ ...(error.details ? { details: error.details } : {}),
126
+ },
127
+ },
128
+ { status: error.statusCode },
129
+ )
130
+ }
131
+
132
+ // Zod validation error
133
+ if (error instanceof z.ZodError) {
134
+ return NextResponse.json(
135
+ {
136
+ error: {
137
+ code: 'VALIDATION_ERROR',
138
+ message: 'Request validation failed',
139
+ details: error.issues.map(i => ({
140
+ field: i.path.join('.'),
141
+ message: i.message,
142
+ })),
143
+ },
144
+ },
145
+ { status: 422 },
146
+ )
147
+ }
148
+
149
+ // Unexpected error — log details, return generic message
150
+ console.error('Unexpected error:', error)
151
+ return NextResponse.json(
152
+ { error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } },
153
+ { status: 500 },
154
+ )
155
+ }
156
+
157
+ export async function POST(req: NextRequest) {
158
+ try {
159
+ // ... handler logic
160
+ } catch (error) {
161
+ return handleApiError(error)
162
+ }
163
+ }
164
+ ```
165
+
166
+ ### React Error Boundary
167
+
168
+ ```typescript
169
+ import { Component, ErrorInfo, ReactNode } from 'react'
170
+
171
+ interface Props {
172
+ fallback: ReactNode
173
+ onError?: (error: Error, info: ErrorInfo) => void
174
+ children: ReactNode
175
+ }
176
+
177
+ interface State {
178
+ hasError: boolean
179
+ error: Error | null
180
+ }
181
+
182
+ export class ErrorBoundary extends Component<Props, State> {
183
+ state: State = { hasError: false, error: null }
184
+
185
+ static getDerivedStateFromError(error: Error): State {
186
+ return { hasError: true, error }
187
+ }
188
+
189
+ componentDidCatch(error: Error, info: ErrorInfo) {
190
+ this.props.onError?.(error, info)
191
+ console.error('Unhandled React error:', error, info)
192
+ }
193
+
194
+ render() {
195
+ if (this.state.hasError) return this.props.fallback
196
+ return this.props.children
197
+ }
198
+ }
199
+
200
+ // Usage
201
+ <ErrorBoundary fallback={<p>Something went wrong. Please refresh.</p>}>
202
+ <MyComponent />
203
+ </ErrorBoundary>
204
+ ```
205
+
206
+ ## Python
207
+
208
+ ### Custom Exception Hierarchy
209
+
210
+ ```python
211
+ class AppError(Exception):
212
+ """Base application error."""
213
+ def __init__(self, message: str, code: str, status_code: int = 500):
214
+ super().__init__(message)
215
+ self.code = code
216
+ self.status_code = status_code
217
+
218
+ class NotFoundError(AppError):
219
+ def __init__(self, resource: str, id: str):
220
+ super().__init__(f"{resource} not found: {id}", "NOT_FOUND", 404)
221
+
222
+ class ValidationError(AppError):
223
+ def __init__(self, message: str, details: list[dict] | None = None):
224
+ super().__init__(message, "VALIDATION_ERROR", 422)
225
+ self.details = details or []
226
+ ```
227
+
228
+ ### FastAPI Global Exception Handler
229
+
230
+ ```python
231
+ from fastapi import FastAPI, Request
232
+ from fastapi.responses import JSONResponse
233
+
234
+ app = FastAPI()
235
+
236
+ @app.exception_handler(AppError)
237
+ async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
238
+ return JSONResponse(
239
+ status_code=exc.status_code,
240
+ content={"error": {"code": exc.code, "message": str(exc)}},
241
+ )
242
+
243
+ @app.exception_handler(Exception)
244
+ async def generic_error_handler(request: Request, exc: Exception) -> JSONResponse:
245
+ # Log full details, return generic message
246
+ logger.exception("Unexpected error", exc_info=exc)
247
+ return JSONResponse(
248
+ status_code=500,
249
+ content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
250
+ )
251
+ ```
252
+
253
+ ## Go
254
+
255
+ ### Sentinel Errors and Error Wrapping
256
+
257
+ ```go
258
+ package domain
259
+
260
+ import "errors"
261
+
262
+ // Sentinel errors for type-checking
263
+ var (
264
+ ErrNotFound = errors.New("not found")
265
+ ErrUnauthorized = errors.New("unauthorized")
266
+ ErrConflict = errors.New("conflict")
267
+ )
268
+
269
+ // Wrap errors with context — never lose the original
270
+ func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
271
+ user, err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id)
272
+ if errors.Is(err, sql.ErrNoRows) {
273
+ return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
274
+ }
275
+ if err != nil {
276
+ return nil, fmt.Errorf("querying user %s: %w", id, err)
277
+ }
278
+ return user, nil
279
+ }
280
+
281
+ // At the handler level, unwrap to determine response
282
+ func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
283
+ user, err := h.service.GetUser(r.Context(), chi.URLParam(r, "id"))
284
+ if err != nil {
285
+ switch {
286
+ case errors.Is(err, domain.ErrNotFound):
287
+ writeError(w, http.StatusNotFound, "not_found", err.Error())
288
+ case errors.Is(err, domain.ErrUnauthorized):
289
+ writeError(w, http.StatusForbidden, "forbidden", "Access denied")
290
+ default:
291
+ slog.Error("unexpected error", "err", err)
292
+ writeError(w, http.StatusInternalServerError, "internal_error", "An unexpected error occurred")
293
+ }
294
+ return
295
+ }
296
+ writeJSON(w, http.StatusOK, user)
297
+ }
298
+ ```
299
+
300
+ ## Retry with Exponential Backoff
301
+
302
+ ```typescript
303
+ interface RetryOptions {
304
+ maxAttempts?: number
305
+ baseDelayMs?: number
306
+ maxDelayMs?: number
307
+ retryIf?: (error: unknown) => boolean
308
+ }
309
+
310
+ async function withRetry<T>(
311
+ fn: () => Promise<T>,
312
+ options: RetryOptions = {},
313
+ ): Promise<T> {
314
+ const {
315
+ maxAttempts = 3,
316
+ baseDelayMs = 500,
317
+ maxDelayMs = 10_000,
318
+ retryIf = () => true,
319
+ } = options
320
+
321
+ let lastError: unknown
322
+
323
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
324
+ try {
325
+ return await fn()
326
+ } catch (error) {
327
+ lastError = error
328
+ if (attempt === maxAttempts || !retryIf(error)) throw error
329
+
330
+ const jitter = Math.random() * baseDelayMs
331
+ const delay = Math.min(baseDelayMs * 2 ** (attempt - 1) + jitter, maxDelayMs)
332
+ await new Promise(resolve => setTimeout(resolve, delay))
333
+ }
334
+ }
335
+
336
+ throw lastError
337
+ }
338
+
339
+ // Usage: retry transient network errors, not 4xx
340
+ const data = await withRetry(() => fetch('/api/data').then(r => r.json()), {
341
+ maxAttempts: 3,
342
+ retryIf: (error) => !(error instanceof AppError && error.statusCode < 500),
343
+ })
344
+ ```
345
+
346
+ ## User-Facing Error Messages
347
+
348
+ Map error codes to human-readable messages. Keep technical details out of user-visible text.
349
+
350
+ ```typescript
351
+ const USER_ERROR_MESSAGES: Record<string, string> = {
352
+ NOT_FOUND: 'The requested item could not be found.',
353
+ UNAUTHORIZED: 'Please sign in to continue.',
354
+ FORBIDDEN: "You don't have permission to do that.",
355
+ VALIDATION_ERROR: 'Please check your input and try again.',
356
+ RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
357
+ INTERNAL_ERROR: 'Something went wrong on our end. Please try again later.',
358
+ }
359
+
360
+ export function getUserMessage(code: string): string {
361
+ return USER_ERROR_MESSAGES[code] ?? USER_ERROR_MESSAGES.INTERNAL_ERROR
362
+ }
363
+ ```
364
+
365
+ ## Error Handling Checklist
366
+
367
+ Before merging any code that touches error handling:
368
+
369
+ - [ ] Every `catch` block handles, re-throws, or logs — no silent swallowing
370
+ - [ ] API errors follow the standard envelope `{ error: { code, message } }`
371
+ - [ ] User-facing messages contain no stack traces or internal details
372
+ - [ ] Full error context is logged server-side
373
+ - [ ] Custom error classes extend a base `AppError` with a `code` field
374
+ - [ ] Async functions surface errors to callers — no fire-and-forget without fallback
375
+ - [ ] Retry logic only retries retriable errors (not 4xx client errors)
376
+ - [ ] React components are wrapped in `ErrorBoundary` for rendering errors