diff --git a/.claude/agents/dotnet-developer.md b/.claude/agents/dotnet-developer.md index 08f96564..04bfb9f5 100644 --- a/.claude/agents/dotnet-developer.md +++ b/.claude/agents/dotnet-developer.md @@ -22,6 +22,6 @@ Use the following skills based on the task at hand: | Testing | [dotnet-tester](../../skills/csharp/dotnet-tester/SKILL.md) | Write and execute unit tests using xUnit, FakeItEasy, and AwesomeAssertions | | Data Access | [ef-core](../../skills/csharp/ef-core/SKILL.md) | Entity Framework Core best practices, DbContext design, migrations, and queries | | Package Management | [nuget-manager](../../skills/csharp/nuget-manager/SKILL.md) | Add, remove, and update NuGet packages via dotnet CLI | -| Documentation | [csharp-docs](../../skills/csharp/csharp-docs/SKILL.md) | C# XML documentation comments following Microsoft standards | +| Documentation | [dotnet-xmldocs](../../skills/csharp/dotnet-xmldocs/SKILL.md) | C# XML documentation comments following Microsoft standards | | Code Review | [code-review](../../skills/general/code-review/SKILL.md) | Structured code reviews covering quality, security, and performance | | Refactoring | [refactoring](../../skills/general/refactoring/SKILL.md) | Improve code structure and maintainability without changing behavior | diff --git a/.claude/skills/dotnet-aspnet/SKILL.md b/.claude/skills/dotnet-aspnet/SKILL.md index 1a6a094e..7667d20e 100644 --- a/.claude/skills/dotnet-aspnet/SKILL.md +++ b/.claude/skills/dotnet-aspnet/SKILL.md @@ -1,372 +1,46 @@ --- name: dotnet-aspnet -description: ASP.NET Core best practices for building Web and REST APIs. Use when creating controllers, minimal APIs, configuring middleware, routing, model binding, validation, dependency injection, authentication, authorization, error handling with ProblemDetails, OpenAPI/Swagger, health checks, CORS, rate limiting, or structuring an ASP.NET Core project. +description: Applies ASP.NET Core best practices for building Web and REST APIs. Use when creating controllers, minimal APIs, configuring middleware, routing, model binding, validation, authentication, authorization, error handling with ProblemDetails, OpenAPI/Swagger, health checks, CORS, rate limiting, or structuring an ASP.NET Core project. For DI, Options pattern, and configuration that apply to any .NET host, use dotnet-fundamentals. --- # ASP.NET Core Web API Best Practices -## Project Structure +## When to Use -- Organize code by feature/domain, not by layer (avoid generic `Controllers/`, `Services/`, `Models/` folders) -- Use `Program.cs` as the composition root — register services, configure middleware pipeline -- Keep `Program.cs` lean by extracting registration into extension methods (e.g., `AddApplicationServices()`, `AddAuthenticationServices()`) -- Use feature folders: - ``` - Features/ - Orders/ - OrdersController.cs - CreateOrderRequest.cs - OrderResponse.cs - OrderService.cs - Products/ - ... - ``` +- Creating new controllers, minimal API endpoints, or feature folders in an ASP.NET Core project +- Configuring the middleware pipeline, routing, or model binding +- Adding authentication, authorization policies, or `[Authorize]`-based access control +- Implementing error handling with `ProblemDetails` (RFC 9457) or `IExceptionHandler` +- Wiring up OpenAPI/Swagger, health checks, CORS, rate limiting, output caching, or response compression +- Reviewing or restructuring an existing ASP.NET Core project to align with best practices -## Controllers vs Minimal APIs +This skill covers the **HTTP / web layer only**. For dependency injection, Options pattern, and configuration that apply to any .NET host, see [`dotnet-fundamentals`](../dotnet-fundamentals/SKILL.md). -### Controllers +## Core Principles -Use controllers for larger APIs with shared conventions, filters, and complex routing: +- Use feature folders, not layer folders. Keep `Program.cs` lean — extract registrations into extension methods. +- Always use `[ApiController]` on MVC controllers; prefer `TypedResults` in minimal APIs. +- Accept `CancellationToken` on every async endpoint. +- Map domain exceptions to ProblemDetails — never leak internal exception messages. +- Middleware order matters: exception handler → status code pages → HSTS/HTTPS → CORS → auth → rate limiter → endpoints. -```csharp -[ApiController] -[Route("api/[controller]")] -public class OrdersController(IOrderService orderService) : ControllerBase -{ - [HttpGet("{id:guid}")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetById(Guid id, CancellationToken ct) - { - var order = await orderService.GetByIdAsync(id, ct); - return order is null ? NotFound() : Ok(order); - } -} -``` +## Reference Index -- Always use `[ApiController]` attribute — enables automatic model validation, binding source inference, and ProblemDetails responses -- Use primary constructor injection for dependencies -- Return `IActionResult` or `ActionResult` for endpoints with multiple response types -- Accept `CancellationToken` on all async endpoints +Detailed patterns and code samples live in `references/`: -### Minimal APIs - -Use minimal APIs for simple endpoints, microservices, or when startup performance matters: - -```csharp -var group = app.MapGroup("/api/orders") - .WithTags("Orders") - .RequireAuthorization(); - -group.MapGet("/{id:guid}", async (Guid id, IOrderService service, CancellationToken ct) => -{ - var order = await service.GetByIdAsync(id, ct); - return order is null ? Results.NotFound() : Results.Ok(order); -}) -.WithName("GetOrderById") -.Produces() -.ProducesProblem(StatusCodes.Status404NotFound); -``` - -- Use `MapGroup()` to share route prefixes, filters, and metadata -- Use `TypedResults` instead of `Results` for compile-time response type verification -- Add `.WithName()` for OpenAPI operation IDs and link generation -- Use endpoint filters for cross-cutting concerns - -## Routing & Endpoints - -- Use attribute routing on controllers (`[Route]`, `[HttpGet]`, etc.) -- Apply route constraints: `{id:guid}`, `{page:int:min(1)}`, `{slug:regex(^[a-z-]+$)}` -- Use API versioning via URL segment (`/api/v1/orders`) or header-based versioning with `Asp.Versioning.Http` -- Keep route templates consistent — plural nouns for resources (`/api/orders`, not `/api/order`) -- Use `LinkGenerator` for generating URLs to named endpoints - -## Model Binding & Validation - -- Use binding source attributes explicitly: `[FromBody]`, `[FromQuery]`, `[FromRoute]`, `[FromHeader]` -- With `[ApiController]`, complex types default to `[FromBody]`, simple types to `[FromRoute]`/`[FromQuery]` -- Validate with Data Annotations or FluentValidation: - -```csharp -public record CreateOrderRequest( - [Required] string CustomerId, - [Required, MinLength(1)] List Items); -``` - -- For FluentValidation: register validators via `AddValidatorsFromAssemblyContaining()` and use a validation filter or `IEndpointFilter` -- Return `ValidationProblemDetails` (automatic with `[ApiController]`) for validation failures - -## Dependency Injection - -- Register services in `Program.cs` or via extension methods -- Use appropriate lifetimes: - - **Transient**: Stateless, lightweight services - - **Scoped**: Per-request services (DbContext, unit of work) - - **Singleton**: Thread-safe, shared state (caches, configuration) -- Use keyed services (.NET 8+) when multiple implementations of the same interface are needed: - ```csharp - builder.Services.AddKeyedScoped("stripe"); - builder.Services.AddKeyedScoped("paypal"); - ``` -- Prefer interface-based registration for testability -- Avoid service locator pattern — do not inject `IServiceProvider` into business logic - -## Middleware Pipeline - -Order matters — register middleware in the correct sequence: - -```csharp -app.UseExceptionHandler(); -app.UseStatusCodePages(); -app.UseHsts(); -app.UseHttpsRedirection(); -app.UseCors(); -app.UseAuthentication(); -app.UseAuthorization(); -app.UseRateLimiter(); -app.UseOutputCache(); -app.MapControllers(); -``` - -### Custom Middleware - -```csharp -public class RequestTimingMiddleware(RequestDelegate next) -{ - public async Task InvokeAsync(HttpContext context) - { - var sw = Stopwatch.StartNew(); - context.Response.OnStarting(() => - { - context.Response.Headers["X-Response-Time-Ms"] = sw.ElapsedMilliseconds.ToString(); - return Task.CompletedTask; - }); - await next(context); - } -} -``` - -- Use primary constructors for middleware -- Inject scoped services via `InvokeAsync` parameters, not the constructor -- Keep middleware focused — one concern per middleware - -## Configuration - -- Use the Options pattern for strongly-typed configuration: - -```csharp -public class SmtpOptions -{ - public const string SectionName = "Smtp"; - public required string Host { get; init; } - public int Port { get; init; } = 587; - public required string Username { get; init; } -} - -builder.Services.AddOptions() - .BindConfiguration(SmtpOptions.SectionName) - .ValidateDataAnnotations() - .ValidateOnStart(); -``` - -- Use `ValidateOnStart()` to catch configuration errors at startup, not at first use -- Use `appsettings.json` for defaults, `appsettings.{Environment}.json` for overrides -- Use User Secrets (`dotnet user-secrets`) for local development — never commit secrets -- Use environment variables or a vault for production secrets -- Inject `IOptions` for static config, `IOptionsMonitor` for reloadable config - -## Authentication & Authorization - -### JWT Bearer Authentication - -```csharp -builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => - { - options.Authority = builder.Configuration["Auth:Authority"]; - options.Audience = builder.Configuration["Auth:Audience"]; - }); -``` - -### Authorization Policies - -```csharp -builder.Services.AddAuthorizationBuilder() - .AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")) - .AddPolicy("CanEditOrder", policy => - policy.Requirements.Add(new OrderOwnerRequirement())); -``` - -- Use policy-based authorization over role checks in attributes -- Apply `[Authorize]` at controller level, `[AllowAnonymous]` for exceptions -- Implement `IAuthorizationHandler` for resource-based authorization -- For minimal APIs use `.RequireAuthorization("PolicyName")` - -## Error Handling - -### ProblemDetails (RFC 9457) - -```csharp -builder.Services.AddProblemDetails(options => -{ - options.CustomizeProblemDetails = ctx => - { - ctx.ProblemDetails.Extensions["traceId"] = ctx.HttpContext.TraceIdentifier; - }; -}); -``` - -### Global Exception Handling (.NET 8+) - -```csharp -public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler -{ - public async ValueTask TryHandleAsync( - HttpContext context, Exception exception, CancellationToken ct) - { - logger.LogError(exception, "Unhandled exception"); - - var problem = new ProblemDetails - { - Status = StatusCodes.Status500InternalServerError, - Title = "An error occurred", - Type = "https://httpstatuses.com/500" - }; - - context.Response.StatusCode = problem.Status.Value; - await context.Response.WriteAsJsonAsync(problem, ct); - return true; - } -} - -builder.Services.AddExceptionHandler(); -``` - -- Use `IExceptionHandler` (.NET 8+) instead of custom exception middleware -- Map domain exceptions to appropriate HTTP status codes -- Never expose internal exception details in production responses -- Use `app.UseStatusCodePages()` for consistent responses on empty status codes (404, 405, etc.) - -## API Conventions & Response Types - -- Annotate endpoints with `[ProducesResponseType]` for OpenAPI documentation -- Use `[Consumes]` and `[Produces]` for content type negotiation -- Return consistent response envelopes or use ProblemDetails for errors -- Use `TypedResults` in minimal APIs for compile-time response verification: - -```csharp -group.MapPost("/", async (CreateOrderRequest req, IOrderService service, CancellationToken ct) => -{ - var id = await service.CreateAsync(req, ct); - return TypedResults.Created($"/api/orders/{id}", new { id }); -}); -``` - -## OpenAPI / Swagger - -- Use the built-in OpenAPI support (.NET 9+) or Swashbuckle/NSwag for earlier versions: - -```csharp -builder.Services.AddOpenApi(); -// ... -app.MapOpenApi(); -``` - -- Enable XML comments in `.csproj` for automatic documentation: - ```xml - - true - $(NoWarn);1591 - - ``` -- Use `[Tags]`, `[EndpointSummary]`, `[EndpointDescription]` for metadata -- Use `WithName()` and `WithTags()` on minimal API endpoints -- Use the `csharp-docs` skill for writing XML documentation comments - -## Health Checks - -```csharp -builder.Services.AddHealthChecks() - .AddCheck("self", () => HealthCheckResult.Healthy()) - .AddNpgSql(connectionString, name: "database") - .AddRedis(redisConnectionString, name: "cache"); - -app.MapHealthChecks("/health", new HealthCheckOptions -{ - ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse -}); - -app.MapHealthChecks("/health/ready", new HealthCheckOptions -{ - Predicate = check => check.Tags.Contains("ready") -}); -``` - -- Separate liveness (`/health`) and readiness (`/health/ready`) endpoints -- Tag health checks for selective filtering -- Use NuGet packages `AspNetCore.HealthChecks.*` for common dependencies -- Use the `nuget-manager` skill for adding health check packages - -## Cross-Cutting Concerns - -### CORS - -```csharp -builder.Services.AddCors(options => -{ - options.AddPolicy("AllowFrontend", policy => - policy.WithOrigins("https://app.example.com") - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials()); -}); - -app.UseCors("AllowFrontend"); -``` - -### Rate Limiting (.NET 7+) - -```csharp -builder.Services.AddRateLimiter(options => -{ - options.AddFixedWindowLimiter("api", limiter => - { - limiter.PermitLimit = 100; - limiter.Window = TimeSpan.FromMinutes(1); - }); - options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; -}); - -app.UseRateLimiter(); -``` - -- Apply per-endpoint with `[EnableRateLimiting("api")]` or `.RequireRateLimiting("api")` - -### Output Caching (.NET 7+) - -```csharp -builder.Services.AddOutputCache(options => -{ - options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5))); - options.AddPolicy("NoCache", builder => builder.NoCache()); -}); -``` - -### Response Compression - -```csharp -builder.Services.AddResponseCompression(options => -{ - options.EnableForHttps = true; - options.Providers.Add(); - options.Providers.Add(); -}); -``` +- **[project-and-endpoints.md](references/project-and-endpoints.md)** — Project structure, Controllers vs Minimal APIs, routing, API conventions, response types (`TypedResults`, `[ProducesResponseType]`) +- **[model-binding-validation.md](references/model-binding-validation.md)** — Binding sources (`[FromBody]`, `[FromRoute]`, etc.), Data Annotations, FluentValidation, `ValidationProblemDetails` +- **[middleware.md](references/middleware.md)** — Pipeline order, custom middleware with primary constructors +- **[auth.md](references/auth.md)** — JWT Bearer, authorization policies, `IAuthorizationHandler`, `RequireAuthorization` +- **[error-handling.md](references/error-handling.md)** — ProblemDetails, `IExceptionHandler` (.NET 8+), `UseStatusCodePages` +- **[openapi-and-cross-cutting.md](references/openapi-and-cross-cutting.md)** — OpenAPI/Swagger, XML doc generation, health checks (liveness/readiness), CORS, rate limiting, output caching, response compression ## Related Skills +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — Foundation: DI, Options pattern, configuration, modern C# idioms used by every endpoint and service in this skill - **[ef-core](../ef-core/SKILL.md)** — Data access with Entity Framework Core -- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Unit and integration testing -- **[csharp-docs](../csharp-docs/SKILL.md)** — XML documentation comments -- **[nuget-manager](../nuget-manager/SKILL.md)** — NuGet package management -- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — SDK/client library generation +- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Unit and integration testing for endpoints +- **[dotnet-xmldocs](../dotnet-xmldocs/SKILL.md)** — XML documentation comments (feed OpenAPI) +- **[nuget-manager](../nuget-manager/SKILL.md)** — Invoked for adding health-check, resilience, and middleware packages +- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — Generates typed HTTP clients for consuming these APIs +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Reviews ASP.NET Core code for security, performance, and architecture issues diff --git a/.claude/skills/dotnet-aspnet/references/auth.md b/.claude/skills/dotnet-aspnet/references/auth.md new file mode 100644 index 00000000..52f26414 --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/auth.md @@ -0,0 +1,26 @@ +# Authentication & Authorization + +## JWT Bearer Authentication + +```csharp +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.Authority = builder.Configuration["Auth:Authority"]; + options.Audience = builder.Configuration["Auth:Audience"]; + }); +``` + +## Authorization Policies + +```csharp +builder.Services.AddAuthorizationBuilder() + .AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")) + .AddPolicy("CanEditOrder", policy => + policy.Requirements.Add(new OrderOwnerRequirement())); +``` + +- Use policy-based authorization over role checks in attributes +- Apply `[Authorize]` at controller level, `[AllowAnonymous]` for exceptions +- Implement `IAuthorizationHandler` for resource-based authorization +- For minimal APIs use `.RequireAuthorization("PolicyName")` diff --git a/.claude/skills/dotnet-aspnet/references/error-handling.md b/.claude/skills/dotnet-aspnet/references/error-handling.md new file mode 100644 index 00000000..09282b57 --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/error-handling.md @@ -0,0 +1,44 @@ +# Error Handling + +## ProblemDetails (RFC 9457) + +```csharp +builder.Services.AddProblemDetails(options => +{ + options.CustomizeProblemDetails = ctx => + { + ctx.ProblemDetails.Extensions["traceId"] = ctx.HttpContext.TraceIdentifier; + }; +}); +``` + +## Global Exception Handling (.NET 8+) + +```csharp +public class GlobalExceptionHandler(ILogger logger) : IExceptionHandler +{ + public async ValueTask TryHandleAsync( + HttpContext context, Exception exception, CancellationToken ct) + { + logger.LogError(exception, "Unhandled exception"); + + var problem = new ProblemDetails + { + Status = StatusCodes.Status500InternalServerError, + Title = "An error occurred", + Type = "https://httpstatuses.com/500" + }; + + context.Response.StatusCode = problem.Status.Value; + await context.Response.WriteAsJsonAsync(problem, ct); + return true; + } +} + +builder.Services.AddExceptionHandler(); +``` + +- Use `IExceptionHandler` (.NET 8+) instead of custom exception middleware +- Map domain exceptions to appropriate HTTP status codes +- Never expose internal exception details in production responses +- Use `app.UseStatusCodePages()` for consistent responses on empty status codes (404, 405, etc.) diff --git a/.claude/skills/dotnet-aspnet/references/middleware.md b/.claude/skills/dotnet-aspnet/references/middleware.md new file mode 100644 index 00000000..9940ff34 --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/middleware.md @@ -0,0 +1,38 @@ +# Middleware Pipeline + +Order matters — register middleware in the correct sequence: + +```csharp +app.UseExceptionHandler(); +app.UseStatusCodePages(); +app.UseHsts(); +app.UseHttpsRedirection(); +app.UseCors(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseRateLimiter(); +app.UseOutputCache(); +app.MapControllers(); +``` + +## Custom Middleware + +```csharp +public class RequestTimingMiddleware(RequestDelegate next) +{ + public async Task InvokeAsync(HttpContext context) + { + var sw = Stopwatch.StartNew(); + context.Response.OnStarting(() => + { + context.Response.Headers["X-Response-Time-Ms"] = sw.ElapsedMilliseconds.ToString(); + return Task.CompletedTask; + }); + await next(context); + } +} +``` + +- Use primary constructors for middleware +- Inject scoped services via `InvokeAsync` parameters, not the constructor +- Keep middleware focused — one concern per middleware diff --git a/.claude/skills/dotnet-aspnet/references/model-binding-validation.md b/.claude/skills/dotnet-aspnet/references/model-binding-validation.md new file mode 100644 index 00000000..1023f6d2 --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/model-binding-validation.md @@ -0,0 +1,71 @@ +# Model Binding & Validation + +ASP.NET Core-specific concerns around mapping HTTP requests onto typed arguments and validating them. + +## Binding Sources + +- Use binding source attributes explicitly: `[FromBody]`, `[FromQuery]`, `[FromRoute]`, `[FromHeader]`, `[FromForm]`, `[FromServices]`. +- With `[ApiController]`, the framework infers binding sources: complex types default to `[FromBody]`, simple types to `[FromRoute]` / `[FromQuery]`. +- For minimal APIs, binding is determined by parameter type and route template; use the attributes when inference is ambiguous. + +```csharp +[HttpPost] +public IActionResult Create( + [FromBody] CreateOrderRequest body, + [FromHeader(Name = "X-Tenant-Id")] string tenantId, + CancellationToken ct) +{ + // ... +} +``` + +## Data Annotation Validation + +```csharp +public record CreateOrderRequest( + [Required] string CustomerId, + [Required, MinLength(1)] List Items); +``` + +- With `[ApiController]`, validation failures automatically produce a `400 Bad Request` with a `ValidationProblemDetails` body. +- Combine with `[StringLength]`, `[Range]`, `[RegularExpression]`, `[EmailAddress]` for richer constraints. + +## FluentValidation + +For validation logic that exceeds attribute capabilities (cross-field rules, async lookups), use FluentValidation: + +```csharp +public class CreateOrderRequestValidator : AbstractValidator +{ + public CreateOrderRequestValidator() + { + RuleFor(x => x.CustomerId).NotEmpty().Length(5, 50); + RuleFor(x => x.Items).NotEmpty(); + RuleForEach(x => x.Items).SetValidator(new OrderItemRequestValidator()); + } +} + +builder.Services.AddValidatorsFromAssemblyContaining(); +``` + +Wire validators into the pipeline via an `IEndpointFilter` (minimal APIs) or a custom MVC filter that runs `IValidator` and returns `ValidationProblemDetails` on failure. + +## ProblemDetails for Validation Errors + +`[ApiController]` returns `ValidationProblemDetails` (RFC 9457 extension) automatically. For minimal APIs, return it explicitly: + +```csharp +app.MapPost("/orders", async (CreateOrderRequest req, IValidator validator, CancellationToken ct) => +{ + var result = await validator.ValidateAsync(req, ct); + if (!result.IsValid) + return Results.ValidationProblem(result.ToDictionary()); + + // proceed + return Results.Created(...); +}); +``` + +--- + +**For DI lifetimes, the Options pattern, and configuration (`appsettings.json`, User Secrets, environment variables): see the [`dotnet-fundamentals`](../../dotnet-fundamentals/SKILL.md) skill.** diff --git a/.claude/skills/dotnet-aspnet/references/openapi-and-cross-cutting.md b/.claude/skills/dotnet-aspnet/references/openapi-and-cross-cutting.md new file mode 100644 index 00000000..02d6a43a --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/openapi-and-cross-cutting.md @@ -0,0 +1,100 @@ +# OpenAPI, Health Checks & Cross-Cutting Concerns + +## OpenAPI / Swagger + +- Use the built-in OpenAPI support (.NET 9+) or Swashbuckle/NSwag for earlier versions: + +```csharp +builder.Services.AddOpenApi(); +// ... +app.MapOpenApi(); +``` + +- Enable XML comments in `.csproj` for automatic documentation: + ```xml + + true + $(NoWarn);1591 + + ``` +- Use `[Tags]`, `[EndpointSummary]`, `[EndpointDescription]` for metadata +- Use `WithName()` and `WithTags()` on minimal API endpoints +- Use the `dotnet-xmldocs` skill for writing XML documentation comments + +## Health Checks + +```csharp +builder.Services.AddHealthChecks() + .AddCheck("self", () => HealthCheckResult.Healthy()) + .AddNpgSql(connectionString, name: "database") + .AddRedis(redisConnectionString, name: "cache"); + +app.MapHealthChecks("/health", new HealthCheckOptions +{ + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse +}); + +app.MapHealthChecks("/health/ready", new HealthCheckOptions +{ + Predicate = check => check.Tags.Contains("ready") +}); +``` + +- Separate liveness (`/health`) and readiness (`/health/ready`) endpoints +- Tag health checks for selective filtering +- Use NuGet packages `AspNetCore.HealthChecks.*` for common dependencies +- Use the `nuget-manager` skill for adding health check packages + +## CORS + +```csharp +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowFrontend", policy => + policy.WithOrigins("https://app.example.com") + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials()); +}); + +app.UseCors("AllowFrontend"); +``` + +## Rate Limiting (.NET 7+) + +```csharp +builder.Services.AddRateLimiter(options => +{ + options.AddFixedWindowLimiter("api", limiter => + { + limiter.PermitLimit = 100; + limiter.Window = TimeSpan.FromMinutes(1); + }); + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; +}); + +app.UseRateLimiter(); +``` + +- Apply per-endpoint with `[EnableRateLimiting("api")]` or `.RequireRateLimiting("api")` + +## Output Caching (.NET 7+) + +```csharp +builder.Services.AddOutputCache(options => +{ + options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(5))); + options.AddPolicy("NoCache", builder => builder.NoCache()); +}); +``` + +## Response Compression + +```csharp +builder.Services.AddResponseCompression(options => +{ + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); +}); +``` diff --git a/.claude/skills/dotnet-aspnet/references/project-and-endpoints.md b/.claude/skills/dotnet-aspnet/references/project-and-endpoints.md new file mode 100644 index 00000000..61649e98 --- /dev/null +++ b/.claude/skills/dotnet-aspnet/references/project-and-endpoints.md @@ -0,0 +1,92 @@ +# Project Structure & Endpoints + +## Project Structure + +- Organize code by feature/domain, not by layer (avoid generic `Controllers/`, `Services/`, `Models/` folders) +- Use `Program.cs` as the composition root — register services, configure middleware pipeline +- Keep `Program.cs` lean by extracting registration into extension methods (e.g., `AddApplicationServices()`, `AddAuthenticationServices()`) +- Use feature folders: + ``` + Features/ + Orders/ + OrdersController.cs + CreateOrderRequest.cs + OrderResponse.cs + OrderService.cs + Products/ + ... + ``` + +## Controllers vs Minimal APIs + +### Controllers + +Use controllers for larger APIs with shared conventions, filters, and complex routing: + +```csharp +[ApiController] +[Route("api/[controller]")] +public class OrdersController(IOrderService orderService) : ControllerBase +{ + [HttpGet("{id:guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetById(Guid id, CancellationToken ct) + { + var order = await orderService.GetByIdAsync(id, ct); + return order is null ? NotFound() : Ok(order); + } +} +``` + +- Always use `[ApiController]` attribute — enables automatic model validation, binding source inference, and ProblemDetails responses +- Use primary constructor injection for dependencies +- Return `IActionResult` or `ActionResult` for endpoints with multiple response types +- Accept `CancellationToken` on all async endpoints + +### Minimal APIs + +Use minimal APIs for simple endpoints, microservices, or when startup performance matters: + +```csharp +var group = app.MapGroup("/api/orders") + .WithTags("Orders") + .RequireAuthorization(); + +group.MapGet("/{id:guid}", async (Guid id, IOrderService service, CancellationToken ct) => +{ + var order = await service.GetByIdAsync(id, ct); + return order is null ? Results.NotFound() : Results.Ok(order); +}) +.WithName("GetOrderById") +.Produces() +.ProducesProblem(StatusCodes.Status404NotFound); +``` + +- Use `MapGroup()` to share route prefixes, filters, and metadata +- Use `TypedResults` instead of `Results` for compile-time response type verification +- Add `.WithName()` for OpenAPI operation IDs and link generation +- Use endpoint filters for cross-cutting concerns + +## Routing & Endpoints + +- Use attribute routing on controllers (`[Route]`, `[HttpGet]`, etc.) +- Apply route constraints: `{id:guid}`, `{page:int:min(1)}`, `{slug:regex(^[a-z-]+$)}` +- Use API versioning via URL segment (`/api/v1/orders`) or header-based versioning with `Asp.Versioning.Http` +- Keep route templates consistent — plural nouns for resources (`/api/orders`, not `/api/order`) +- Use `LinkGenerator` for generating URLs to named endpoints + +## API Conventions & Response Types + +- Annotate endpoints with `[ProducesResponseType]` for OpenAPI documentation +- Use `[Consumes]` and `[Produces]` for content type negotiation +- Return consistent response envelopes or use ProblemDetails for errors +- Use `TypedResults` in minimal APIs for compile-time response verification: + +```csharp +group.MapPost("/", async (CreateOrderRequest req, IOrderService service, CancellationToken ct) => +{ + var id = await service.CreateAsync(req, ct); + return TypedResults.Created($"/api/orders/{id}", new { id }); +}); +``` diff --git a/.claude/skills/dotnet-fundamentals/SKILL.md b/.claude/skills/dotnet-fundamentals/SKILL.md new file mode 100644 index 00000000..4677198a --- /dev/null +++ b/.claude/skills/dotnet-fundamentals/SKILL.md @@ -0,0 +1,41 @@ +--- +name: dotnet-fundamentals +description: Applies modern .NET fundamentals — dependency injection, Options pattern, configuration, and modern C# idioms. Use when registering services in any .NET host (ASP.NET Core, Worker Service, Console, MAUI), binding configuration with IOptions, choosing DI lifetimes, configuring appsettings.json / User Secrets / environment variables, or applying primary constructors, required properties, nullable reference types, and CancellationToken propagation. +--- + +# Modern .NET Fundamentals + +## When to Use + +- Working with .NET/C# Code +- Registering services in any `IServiceCollection` (ASP.NET Core, Worker Service, Console app, MAUI, library DI extension methods) +- Choosing a DI lifetime (Transient, Scoped, Singleton) or registering keyed services (.NET 8+) +- Binding configuration sections to a strongly-typed Options class +- Setting up `appsettings.json`, environment-specific overrides, User Secrets, or environment variables +- Adopting primary constructors, `required` properties, nullable reference types, or `CancellationToken` propagation in new code + +## Core Principles + +- This skill is **technology-agnostic across .NET hosts**. ASP.NET Core, EF Core, and SDK builders all sit on top of these fundamentals. +- **Interface-first registration** — register services via their abstraction (`AddScoped()`), not the concrete type. Enables substitution and testing. +- **No service locator** — never inject `IServiceProvider` into business logic. Constructor-inject the dependencies you actually need. +- **Options over constructor parameters for configuration** — bind config sections to `IOptions`, do not pass raw `IConfiguration` values around. +- **Fail fast** — use `ValidateDataAnnotations().ValidateOnStart()` so misconfiguration surfaces at startup, not at first use. +- **Immutable configuration** — Options classes use `required` properties and `init`-only setters. +- **Cancellation flows everywhere** — every async method takes a `CancellationToken` with default as its last parameter and forwards it. + +## Reference Index + +- **[dependency-injection.md](references/dependency-injection.md)** — `IServiceCollection` registration, lifetimes, keyed services, interface-based registration, anti-service-locator +- **[options-pattern.md](references/options-pattern.md)** — `IOptions` vs `IOptionsMonitor` vs `IOptionsSnapshot`, `BindConfiguration`, `ValidateDataAnnotations`, `ValidateOnStart` +- **[configuration.md](references/configuration.md)** — `appsettings.json` and environment overrides, User Secrets, environment variables, production secret stores +- **[modern-patterns.md](references/modern-patterns.md)** — primary constructors, `required` / `init`-only properties, nullable reference types, `CancellationToken` propagation + +## Related Skills + +- **dotnet-aspnet** — Builds the HTTP layer (controllers, minimal APIs, middleware, routing, auth, ProblemDetails) on an ASP.NET Core host +- **dotnet-sdk-builder** — Generates .NET SDK / client libraries (DI extension methods, typed HTTP clients, typed Options, typed exceptions) +- **ef-core** — Entity Framework Core data access (DbContext, entities, LINQ, migrations); registered via DI and configured via Options +- **dotnet-reviewer** — Structured .NET code review producing a severity-tagged Markdown report +- **dotnet-tester** — Writes and runs C#/.NET unit tests (xUnit, FakeItEasy, AwesomeAssertions) and identifies missing test cases +- **nuget-manager** — Use whenever NuGet packages are added, removed, or updated in a project (dotnet CLI, central version management, version verification) diff --git a/.claude/skills/dotnet-fundamentals/references/configuration.md b/.claude/skills/dotnet-fundamentals/references/configuration.md new file mode 100644 index 00000000..8a4c529f --- /dev/null +++ b/.claude/skills/dotnet-fundamentals/references/configuration.md @@ -0,0 +1,79 @@ +# Configuration + +Configuration in .NET is layered — later sources override earlier ones. The default `HostApplicationBuilder` order is: + +1. `appsettings.json` +2. `appsettings.{Environment}.json` (e.g. `appsettings.Development.json`) +3. User Secrets (Development only) +4. Environment variables +5. Command-line arguments + +The first source that supplies a key wins for that key; sources don't merge sections deeply, they override per leaf value. + +## `appsettings.json` + +Use for defaults that ship with the application: + +```json +{ + "Smtp": { + "Host": "smtp.example.com", + "Port": 587 + }, + "Logging": { + "LogLevel": { + "Default": "Information" + } + } +} +``` + +Use `appsettings.{Environment}.json` for environment-specific overrides: + +```json +// appsettings.Development.json +{ + "Smtp": { "Host": "localhost" } +} +``` + +The active environment is set via `DOTNET_ENVIRONMENT` (generic host) or `ASPNETCORE_ENVIRONMENT` (ASP.NET Core). + +## User Secrets + +For local development secrets, never commit them. Initialize once per project: + +```bash +dotnet user-secrets init +dotnet user-secrets set "Smtp:Password" "dev-password" +``` + +User Secrets are stored outside the project directory (in `~/.microsoft/usersecrets//secrets.json`) and are only loaded in the Development environment. + +## Environment Variables + +Use for production secrets and container/Kubernetes deployments. Nested keys use `__` (double underscore) as separator: + +```bash +export Smtp__Host=smtp.prod.example.com +export Smtp__Password=$(cat /run/secrets/smtp-password) +``` + +The `__` separator is portable (works on Linux, macOS, Windows). `:` is also accepted but is not valid in environment variable names on Linux. + +## Production Secret Stores + +For production, integrate a real secret store rather than environment variables when secrets need rotation, auditing, or RBAC: + +- **Azure Key Vault** — `AddAzureKeyVault(...)` from `Azure.Extensions.AspNetCore.Configuration.Secrets` +- **AWS Secrets Manager** — via `AWSSDK.Extensions.NETCore.Setup` +- **HashiCorp Vault** — via `VaultSharp` plus a custom configuration provider + +These plug into the same `IConfiguration` pipeline, so consuming code keeps using `IOptions` — only the registration in `Program.cs` differs. + +## Anti-Patterns + +- **Hard-coded secrets** — never commit a real password, API key, or connection string to source control. Local defaults should point at obviously-fake values. +- **`IConfiguration` injected into business logic** — bind to a typed Options class instead. Business logic should never know about configuration providers. +- **`config["Foo:Bar"]` indexing across the codebase** — magic strings + no validation. Bind once, inject `IOptions`. +- **Per-environment code branching** — if behavior differs between Development and Production, model it as configuration, not as `if (env.IsDevelopment())` branches scattered through services. diff --git a/.claude/skills/dotnet-fundamentals/references/dependency-injection.md b/.claude/skills/dotnet-fundamentals/references/dependency-injection.md new file mode 100644 index 00000000..330f0b8f --- /dev/null +++ b/.claude/skills/dotnet-fundamentals/references/dependency-injection.md @@ -0,0 +1,81 @@ +# Dependency Injection + +## Registration + +Register services on the host's `IServiceCollection`. The same API applies to ASP.NET Core (`builder.Services`), Worker Services, Console apps using `HostApplicationBuilder`, and library extension methods (`public static IServiceCollection AddMyFeature(this IServiceCollection services)`). + +```csharp +services.AddScoped(); +services.AddSingleton(); +services.AddTransient(); +``` + +- Register services in `Program.cs` or via extension methods (`AddApplicationServices()`, `AddPersistence()`, etc.) to keep `Program.cs` lean. +- Prefer interface-based registration for testability — register against the abstraction, not the concrete type. + +## Lifetimes + +- **Transient** — Stateless, lightweight services. New instance per resolution. +- **Scoped** — One instance per logical operation (per HTTP request in ASP.NET Core, per `IServiceScope` in worker scenarios). Typical for `DbContext`, unit-of-work, and per-request caches. +- **Singleton** — Thread-safe shared state (caches, configuration providers, expensive-to-build clients). Must be safe for concurrent use. + +Lifetime mismatches (e.g. a Singleton capturing a Scoped service) are a common bug — `ValidateScopes` and `ValidateOnBuild` catch this at startup. + +```csharp +var host = Host.CreateApplicationBuilder() + .ConfigureContainer((ctx, services) => + { + // Register here + }) + .Build(); +``` + +For ASP.NET Core, scope validation is on by default in Development. For other hosts, enable it explicitly via `ServiceProviderOptions`. + +## Keyed Services (.NET 8+) + +Use keyed services when multiple implementations of the same interface need to coexist and be selected by key: + +```csharp +services.AddKeyedScoped("stripe"); +services.AddKeyedScoped("paypal"); + +public class CheckoutHandler( + [FromKeyedServices("stripe")] IPaymentGateway stripe, + [FromKeyedServices("paypal")] IPaymentGateway paypal) +{ + // ... +} +``` + +## Anti-Patterns + +- **Service locator** — Do not inject `IServiceProvider` into business logic and resolve dependencies at runtime. Constructor-inject the specific dependencies you need. +- **Capturing scoped services in singletons** — A singleton holding a reference to a scoped service leaks the scoped instance and causes use-after-dispose bugs. Use `IServiceScopeFactory` to create a fresh scope when needed. +- **Concrete-type registration when an interface exists** — Registering `services.AddScoped()` instead of `services.AddScoped()` defeats substitution and testing. +- **Static state / singletons outside DI** — All cross-cutting state goes through DI. No `public static` mutable state. + +## Library Extension Method Pattern + +When shipping a NuGet library that registers services for consumers, expose a single extension method: + +```csharp +public static class MyFeatureServiceCollectionExtensions +{ + public static IServiceCollection AddMyFeature( + this IServiceCollection services, + Action? configure = null) + { + services.AddOptions() + .BindConfiguration(MyFeatureOptions.SectionName) + .ValidateDataAnnotations() + .ValidateOnStart(); + + if (configure is not null) + services.Configure(configure); + + services.AddScoped(); + return services; + } +} +``` diff --git a/.claude/skills/dotnet-fundamentals/references/modern-patterns.md b/.claude/skills/dotnet-fundamentals/references/modern-patterns.md new file mode 100644 index 00000000..c8f3e043 --- /dev/null +++ b/.claude/skills/dotnet-fundamentals/references/modern-patterns.md @@ -0,0 +1,87 @@ +# Modern C# / .NET Patterns + +Conventions for code written against current .NET. Use these consistently in new code; match existing project style if it predates these features. + +## Primary Constructors (C# 12) + +- For middleware, inject scoped services via `InvokeAsync` parameters, not the primary constructor. + +## `required` Properties + `init`-Only Setters + +Force callers to supply mandatory values at object initialization; prevent mutation afterward. + +```csharp +public record CreateOrderRequest +{ + public required string CustomerId { get; init; } + public required IReadOnlyList Items { get; init; } + public string? Notes { get; init; } +} + +var req = new CreateOrderRequest +{ + CustomerId = "C-123", + Items = items, +}; +``` + +- Combine with `record` for value-semantics DTOs. +- `required` runs at compile time — the compiler refuses initialization expressions that omit a required member. +- Use `init` (not `set`) for immutable-after-construction shape. + +## Nullable Reference Types + +Enable project-wide in the `.csproj`: + +```xml +enable +``` + +- Declare a reference type as nullable explicitly: `string?` means "may be null", `string` means "guaranteed non-null". +- Treat nullable warnings as errors (`true`) in new projects. +- For legacy code being migrated, use `annotations` first (annotations only, no warnings) to add types incrementally, then flip to `enable`. +- Use the null-forgiving `!` operator only at provable-non-null boundaries (post-validation, after `ArgumentNullException.ThrowIfNull`). Do not sprinkle it to silence warnings. + +## `CancellationToken` Propagation + +Every async method takes a `CancellationToken` as its **last** parameter and forwards it to every async call it makes. + +```csharp +public async Task GetByIdAsync(Guid id, CancellationToken ct = default) +{ + var dto = await _db.Orders + .AsNoTracking() + .FirstOrDefaultAsync(o => o.Id == id, ct); + return dto is null ? null : await _mapper.MapAsync(dto, ct); +} +``` + +- Parameter is named `ct` or `cancellationToken` consistently within a codebase. +- Default to `default` only for entry points that have no caller-supplied token (e.g. CLI `Main`); library code should require the caller to pass one. +- In ASP.NET Core, `HttpContext.RequestAborted` is automatically bound to action parameters of type `CancellationToken`. +- Never swallow `OperationCanceledException` — let it bubble. The host treats it as expected cancellation. + +## File-Scoped Namespaces (C# 10) + +```csharp +namespace MyCompany.MyProduct.Orders; + +public class OrderService { /* ... */ } +``` + +Default for all new files. Removes one level of indentation across the file. + +## `global using` Directives + +Centralize common imports in `GlobalUsings.cs`: + +```csharp +global using System; +global using System.Collections.Generic; +global using System.Threading; +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Options; +``` + +Use the SDK-provided `enable` for the default set; add project-specific globals via explicit `global using` declarations. diff --git a/.claude/skills/dotnet-fundamentals/references/options-pattern.md b/.claude/skills/dotnet-fundamentals/references/options-pattern.md new file mode 100644 index 00000000..74256cc1 --- /dev/null +++ b/.claude/skills/dotnet-fundamentals/references/options-pattern.md @@ -0,0 +1,89 @@ +# Options Pattern + +Bind configuration sections to strongly-typed classes via `Microsoft.Extensions.Options`. Validate at startup, inject by interface (`IOptions` / `IOptionsMonitor` / `IOptionsSnapshot`). + +## Defining an Options Class + +```csharp +public class SmtpOptions +{ + public const string SectionName = "Smtp"; + + public required string Host { get; init; } + public int Port { get; init; } = 587; + public required string Username { get; init; } + public required string Password { get; init; } +} +``` + +- Declare `SectionName` as a `const string` on the class so consumers and tests can refer to it without magic strings. +- Use `required` properties for values without sane defaults — binding fails clearly if a required value is missing. +- Use `init`-only setters to keep options immutable after binding. + +## Registration + +```csharp +services.AddOptions() + .BindConfiguration(SmtpOptions.SectionName) + .ValidateDataAnnotations() + .ValidateOnStart(); +``` + +- `BindConfiguration("Smtp")` binds the section automatically and re-binds when configuration reloads. +- `ValidateDataAnnotations()` runs `[Required]`, `[Range]`, etc. on the options class. +- `ValidateOnStart()` runs validation at host startup — misconfiguration surfaces immediately instead of at first use. + +For complex validation, implement `IValidateOptions`: + +```csharp +public class SmtpOptionsValidator : IValidateOptions +{ + public ValidateOptionsResult Validate(string? name, SmtpOptions options) + { + if (options.Port is < 1 or > 65535) + return ValidateOptionsResult.Fail("Port must be 1-65535"); + return ValidateOptionsResult.Success; + } +} + +services.AddSingleton, SmtpOptionsValidator>(); +``` + +## Consuming Options + +| Interface | Lifetime | When to use | +|---|---|---| +| `IOptions` | Singleton | Static configuration that does not change after startup. Cheapest. | +| `IOptionsSnapshot` | Scoped (ASP.NET Core only) | Per-request rebinding — picks up changes for each new request. | +| `IOptionsMonitor` | Singleton with change notifications | Long-lived components (background services, HTTP clients) that need to react to reloads via `OnChange`. | + +```csharp +public class EmailSender(IOptions options) +{ + private readonly SmtpOptions _smtp = options.Value; + // ... +} + +public class ReloadableEmailSender(IOptionsMonitor monitor) +{ + public ReloadableEmailSender(IOptionsMonitor monitor) + { + monitor.OnChange(opts => RebuildClient(opts)); + } +} +``` + +## Named Options + +When the same options shape exists multiple times (e.g. multiple SMTP backends): + +```csharp +services.Configure("Primary", config.GetSection("Smtp:Primary")); +services.Configure("Fallback", config.GetSection("Smtp:Fallback")); + +public class Mailer(IOptionsMonitor monitor) +{ + private readonly SmtpOptions _primary = monitor.Get("Primary"); + private readonly SmtpOptions _fallback = monitor.Get("Fallback"); +} +``` diff --git a/.claude/skills/dotnet-inspect/SKILL.md b/.claude/skills/dotnet-inspect/SKILL.md index 283006ec..abfe8fbf 100644 --- a/.claude/skills/dotnet-inspect/SKILL.md +++ b/.claude/skills/dotnet-inspect/SKILL.md @@ -1,6 +1,5 @@ --- name: dotnet-inspect -version: 0.7.5 description: Query .NET APIs across NuGet packages, platform libraries, and local files. Search for types, list API surfaces, compare and diff versions, find extension methods and implementors. Use whenever you need to answer questions about .NET library contents. --- @@ -227,3 +226,10 @@ Use `dnx` (like `npx`). Always use `-y` and `--` to prevent interactive prompts: ```bash dnx dotnet-inspect -y -- ``` + +## Related Skills + +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Uses dotnet-inspect to investigate API surface and version diffs during reviews +- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — Queries types and members of existing libraries when generating SDK wrappers +- **[nuget-manager](../nuget-manager/SKILL.md)** — Inspects packages before upgrading or replacing them +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — Discovers ASP.NET Core APIs across versions when migrating or adopting new features diff --git a/.claude/skills/dotnet-reviewer/SKILL.md b/.claude/skills/dotnet-reviewer/SKILL.md index 30795d1d..e10de5c5 100644 --- a/.claude/skills/dotnet-reviewer/SKILL.md +++ b/.claude/skills/dotnet-reviewer/SKILL.md @@ -1,21 +1,15 @@ --- name: dotnet-reviewer -description: Performs structured code reviews on .NET 10+ projects. Activates ONLY on explicit name — use the phrases "dotnet-reviewer", "dotnet code review", or "dotnet review". Reviews either uncommitted working-tree changes or committed changes on the current feature branch (vs. main). Produces a Markdown report under docs/reviews/ with severity-tagged findings ([Critical|Major|Minor|Suggestion|Nitpick][Security|Performance|Architecture|Code-Quality|Tests|.NET-Idioms]) and fix suggestions. Must NOT activate on generic "review my code" requests; other-language reviewers must not be hijacked. +description: Performs structured code reviews on .NET 10+ projects. Reviews either uncommitted working-tree changes or committed changes on the current feature branch (vs. main). Produces a Markdown report under docs/reviews/ with severity-tagged findings ([Critical|Major|Minor|Suggestion|Nitpick][Security|Performance|Architecture|Code-Quality|Tests|.NET-Idioms]) and fix suggestions. Must NOT activate on generic "review my code" requests; other-language reviewers must not be hijacked. --- # dotnet-reviewer -Structured code review for .NET 10+ projects. The skill is invoked by explicit name only and produces a Markdown report. +Structured code review for .NET 10+ projects. ## When to Use This Skill -Use ONLY when the user invokes one of: -- `dotnet-reviewer` -- `dotnet code review` -- `dotnet review` - -Do NOT activate on generic phrases like "review my code", "can you check this PR", "look at my changes". Those go to other reviewers (or to no skill at all). - +A Code review for a .NET 10+ project is needed. The user may add language preferences (e.g., "in German") — apply that to the report only. The skill itself remains in English. ## Prerequisites @@ -132,3 +126,13 @@ Output to chat: the file path and a one-line summary (e.g., `"Wrote review with - Runs destructive operations as "fixes" (no `git reset`, no deletions). - Includes secrets in logs or the report. - Reviews .NET versions below 10 — aborts with a clear message. + +## Related Skills + +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — Review findings reference DI lifetime, Options, and configuration best practices +- **[dotnet-xmldocs](../dotnet-xmldocs/SKILL.md)** — Code-quality checklist references XML documentation conventions +- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Test-quality findings reference this skill's expectations +- **[ef-core](../ef-core/SKILL.md)** — EF Core findings reference these data-access best practices +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — ASP.NET Core findings reference this skill's conventions +- **[nuget-manager](../nuget-manager/SKILL.md)** — Surfaced outdated/vulnerable packages are addressed via this skill +- **[dotnet-inspect](../dotnet-inspect/SKILL.md)** — Used to investigate API surface and version diffs during review diff --git a/.claude/skills/dotnet-reviewer/references/review-checklist-architecture.md b/.claude/skills/dotnet-reviewer/references/review-checklist-architecture.md index 2377dfb2..773733ba 100644 --- a/.claude/skills/dotnet-reviewer/references/review-checklist-architecture.md +++ b/.claude/skills/dotnet-reviewer/references/review-checklist-architecture.md @@ -22,10 +22,7 @@ ## Dependency Injection -- Lifetimes: avoid `Singleton` capturing `Scoped` (e.g., `DbContext` in a `Singleton` cache). -- Factory delegates over `IServiceProvider` parameters in constructors. -- No `BuildServiceProvider()` in `Configure`/`ConfigureServices` — that creates a second container. -- Validation of options on startup (`ValidateOnStart`). +See the `dotnet-aspnet` skill (Dependency Injection + Configuration sections) for lifetime rules and the Options pattern. Reviewer-specific hook: flag DI changes that capture a `Scoped` service inside a `Singleton`, call `BuildServiceProvider()` during composition, or register `IOptions` without `ValidateOnStart()`. ## Pattern Consistency diff --git a/.claude/skills/dotnet-reviewer/references/review-checklist-code-quality.md b/.claude/skills/dotnet-reviewer/references/review-checklist-code-quality.md index b45440f8..ada47793 100644 --- a/.claude/skills/dotnet-reviewer/references/review-checklist-code-quality.md +++ b/.claude/skills/dotnet-reviewer/references/review-checklist-code-quality.md @@ -42,18 +42,13 @@ ## Comments and Docs -- Public API has XML docs, especially for libraries. +- Public API has XML docs, especially for libraries. See the `dotnet-xmldocs` skill for the tag conventions (``, ``, ``, ``, etc.). - Comments explain *why*, not *what*. Flag comments that restate the code. - TODOs without a ticket reference are a smell; flag. ## Tests (cross-cutting) -- New public behavior has at least one test. -- Tests assert behavior, not implementation (no over-mocking). -- Names describe the scenario: `Method_Condition_Expected`. -- Arrange/Act/Assert layout is visible. -- No conditional logic in test bodies (`if`/`for` inside tests). -- Edge cases: null, empty collection, boundary values. +See the `dotnet-tester` skill for AAA layout, `Method_Condition_Expected` naming, mocking conventions, and edge-case coverage. Reviewer-specific hook: flag any new public behavior that ships without at least one test, and any test body containing conditional logic (`if`/`for`). ## Logging diff --git a/.claude/skills/dotnet-reviewer/references/review-checklist-net10.md b/.claude/skills/dotnet-reviewer/references/review-checklist-net10.md index 932bcf50..8c67a22e 100644 --- a/.claude/skills/dotnet-reviewer/references/review-checklist-net10.md +++ b/.claude/skills/dotnet-reviewer/references/review-checklist-net10.md @@ -25,11 +25,6 @@ Apply when `detect-dotnet-version.sh` reports `target_frameworks` containing `ne - `` should not be pinned below the SDK's default unless a comment explains why. - `ImplicitUsings` enabled — flag stale top-of-file using directives that are already implicit. -## Things That Are Still Wrong +## Non-version-specific checks -These are not new in .NET 10 but are still common: - -- `.Result` / `.Wait()` on `Task` — sync-over-async deadlock risk. -- `async void` outside event handlers. -- `IEnumerable` enumerated multiple times when the source is a generator. -- `string` concatenation in loops where `StringBuilder` or `string.Create` fits. +For language- and runtime-neutral pitfalls (`.Result`/`.Wait()`, `async void`, allocation hot spots, etc.) see `review-checklist-performance.md` and `review-checklist-code-quality.md`. This file covers only what is specific to .NET 10. diff --git a/.claude/skills/dotnet-reviewer/references/review-checklist-performance.md b/.claude/skills/dotnet-reviewer/references/review-checklist-performance.md index a36c1257..f2f14282 100644 --- a/.claude/skills/dotnet-reviewer/references/review-checklist-performance.md +++ b/.claude/skills/dotnet-reviewer/references/review-checklist-performance.md @@ -26,12 +26,7 @@ ## EF Core -- N+1 queries — flag `foreach (entity) { ctx.Related.Where(...) }` patterns. -- Missing `.AsNoTracking()` on read-only queries. -- `Include` chains pulling unused columns — projection (`Select`) preferred for read paths. -- Filters applied client-side after `.ToList()` — push to SQL. -- `ChangeTracker` not cleared on long-lived contexts. -- Missing indexes on filtered/joined columns (call out in review when obvious from query shape). +See the `ef-core` skill (Performance section) for the full list of EF Core performance pitfalls. Reviewer-specific hook: flag any new query against a `DbContext` that is missing `.AsNoTracking()` on a read-only path, that calls `.ToList()` before a filter, or that uses `foreach` to iterate parent entities while issuing per-row child queries (N+1). ## Hot-Path Heuristics diff --git a/.claude/skills/dotnet-sdk-builder/SKILL.md b/.claude/skills/dotnet-sdk-builder/SKILL.md index 2f7f0c3d..cf4fb9ee 100644 --- a/.claude/skills/dotnet-sdk-builder/SKILL.md +++ b/.claude/skills/dotnet-sdk-builder/SKILL.md @@ -1,12 +1,19 @@ --- name: dotnet-sdk-builder -description: Generates complete .NET SDK libraries with DI support, interfaces, typed HTTP clients, Options pattern, and typed exceptions. Use when asked to create a .NET SDK, build a .NET client library, wrap a REST API in C#, or generate a typed HTTP client. Invokes csharp-docs for XML documentation and tester for tests. +description: Generates complete .NET SDK libraries with DI support, interfaces, typed HTTP clients, Options pattern, and typed exceptions. Use when asked to create a .NET SDK, build a .NET client library, wrap a REST API in C#, or generate a typed HTTP client. Invokes dotnet-xmldocs for XML documentation and dotnet-tester for tests. --- # .NET SDK Library Builder Generate complete, production-ready .NET SDK libraries from existing C# classes or API documentation. The output follows Microsoft's library design guidelines with full DI support, testability via interfaces, and idiomatic C# patterns. +## When to Use + +- Building a new .NET SDK or client library from existing C# classes, OpenAPI/Swagger specs, or REST API docs +- Wrapping a REST API in a typed C# client with DI registration (`AddXxx(...)`) +- Generating typed HTTP clients with `IHttpClientFactory`, Options pattern, and typed exceptions +- Producing libraries that follow Microsoft's library design guidelines (interface-first, no static state) + ## Workflow Overview Follow these steps in order. See the reference files for detailed guidance on each phase. @@ -92,26 +99,15 @@ Generate all components. See [di-patterns.md](references/di-patterns.md) and [ht | `XxxException` (+ subtypes) | Typed exceptions with diagnostic properties | | Model classes | Request/response DTOs | -**NuGet packages to add:** - -```xml - - - - - -``` - -Always use the latest stable, compatible with target framework version. -Always use skill 'nuget-manager' for managing NuGet packages and package versions. +**Required NuGet packages:** `Microsoft.Extensions.Http`, `Microsoft.Extensions.Options`, `Microsoft.Extensions.DependencyInjection.Abstractions` — plus `Microsoft.Extensions.Http.Resilience` if Step 5 selected resilience. Use the `nuget-manager` skill to add them; do not edit `.csproj` directly. ### Step 8: Document the Code -After generating all source files, invoke the `csharp-docs` skill to add XML documentation comments to all public types and members. +After generating all source files, invoke the `dotnet-xmldocs` skill to add XML documentation comments to all public types and members. ### Step 9: Write Tests -After documentation is complete, invoke the `tester` skill to generate unit and integration tests for the library. +After documentation is complete, invoke the `dotnet-tester` skill to generate unit and integration tests for the library. ## Key Design Principles @@ -127,3 +123,12 @@ After documentation is complete, invoke the `tester` skill to generate unit and - **[di-patterns.md](references/di-patterns.md)** — DI registration, Options pattern, extension method patterns - **[http-client-patterns.md](references/http-client-patterns.md)** — IHttpClientFactory, typed clients, resilience, typed exceptions - **[project-setup.md](references/project-setup.md)** — New project structure, folder layout, `.csproj` conventions + +## Related Skills + +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — Provides the DI, Options, and configuration patterns this skill emits in generated SDKs +- **[dotnet-xmldocs](../dotnet-xmldocs/SKILL.md)** — Invoked in Step 8 to document generated SDKs with XML comments +- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Invoked in Step 9 to generate unit and integration tests +- **[nuget-manager](../nuget-manager/SKILL.md)** — Invoked in Step 7 to add SDK runtime dependencies +- **[dotnet-inspect](../dotnet-inspect/SKILL.md)** — Queries existing libraries when generating SDK wrappers +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — Generates typed HTTP clients for consuming ASP.NET Core APIs diff --git a/.claude/skills/dotnet-sdk-builder/references/project-setup.md b/.claude/skills/dotnet-sdk-builder/references/project-setup.md index 4ffda6f8..db141d3e 100644 --- a/.claude/skills/dotnet-sdk-builder/references/project-setup.md +++ b/.claude/skills/dotnet-sdk-builder/references/project-setup.md @@ -36,7 +36,7 @@ Always ask the user for confirmation before creating a new project if not explic **Key settings:** - `enable` — always for new projects when .NET version supports it. - `true` — required for XML doc generation. -- `CS1591` — suppress "missing XML comment" warnings during development; remove after `csharp-docs` skill adds all docs. +- `CS1591` — suppress "missing XML comment" warnings during development; remove after `dotnet-xmldocs` skill adds all docs. - `true` — enforces code quality. - `latest` — enables the latest C# features for the target framework. @@ -69,6 +69,8 @@ MyCompany.GitHub/ - No `Services/` or `Abstractions/` sub-folders for small libraries — keep it flat. - For larger libraries with multiple API areas, group by area: `Repositories/`, `Users/`, etc. +> This layout applies to **library** projects. Web/API applications should organize by feature/domain instead — see the `dotnet-aspnet` skill. + ## Naming Conventions | Artifact | Pattern | Example | diff --git a/.claude/skills/dotnet-tester/SKILL.md b/.claude/skills/dotnet-tester/SKILL.md index 3b962135..1f7f6f8f 100644 --- a/.claude/skills/dotnet-tester/SKILL.md +++ b/.claude/skills/dotnet-tester/SKILL.md @@ -3,8 +3,19 @@ name: dotnet-tester description: Writes, executes, and completes unit tests for C#/.NET code using xUnit, FakeItEasy, and AwesomeAssertions. Uses a second agent to identify missing test cases. Use when asked to create .NET tests or improve test coverage. --- +# .NET Tester + Write comprehensive unit tests for the specified code. Follow a multi-step process with automatic identification of missing test cases. +## When to Use + +- User asks to create unit tests, add tests, or improve test coverage for C#/.NET code +- New C#/.NET production code lacks tests and needs them +- An existing test suite is missing edge cases or error-path coverage +- Working in a C# project that uses xUnit, FakeItEasy, AwesomeAssertions/FluentAssertions, NUnit, MSTest, or Moq + +Do **not** use this skill for non-.NET test code, or for integration tests that primarily exercise external systems without unit-level concerns. + ## Conventions - **Test Framework**: xUnit @@ -12,7 +23,7 @@ Write comprehensive unit tests for the specified code. Follow a multi-step proce - **Assertions**: AwesomeAssertions (a fork of FluentAssertions with identical API — use `Should()` as usual) - **Structure**: Each test method has Arrange/Act/Assert blocks, marked with comments - **Language**: English for code, comments, and test names -- **Style**: Adopt the existing test style from nearby test files in the project +- **Style**: The stack above is the default. If the project already uses a different stack (NUnit, MSTest, Moq, FluentAssertions, …), match the existing convention instead of switching. ## Phase 1: Write Tests @@ -119,3 +130,11 @@ At the end, provide a summary: - Use **descriptive test names** in the format `MethodName_Scenario_ExpectedBehavior` - For `[Theory]` tests: Use `[InlineData]` for simple types, `[MemberData]` for complex objects - Use `A.CallTo(...).MustHaveHappened()` sparingly – only when the call is the expected behavior + +## Related Skills + +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — Test DI-registered services using `IServiceCollection` overrides +- **[ef-core](../ef-core/SKILL.md)** — DbContext-backed unit and integration tests (SQLite in-memory, Testcontainers) +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Test-quality checks during code review +- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — Invoked by it in Step 9 to generate tests for new SDK libraries +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — Unit and integration tests for ASP.NET Core endpoints diff --git a/.claude/skills/dotnet-xmldocs/SKILL.md b/.claude/skills/dotnet-xmldocs/SKILL.md new file mode 100644 index 00000000..b688596b --- /dev/null +++ b/.claude/skills/dotnet-xmldocs/SKILL.md @@ -0,0 +1,41 @@ +--- +name: dotnet-xmldocs +description: Adds and reviews C# XML documentation comments following Microsoft's documentation standards. Use when writing or reviewing C# code that includes public APIs, complex logic, or when documentation is missing or insufficient. Covers , , , , , and all standard XML doc tags. +--- + +# C# Documentation Best Practices + +## When to Use + +- Writing or reviewing XML documentation comments (`///`) on C# types and members +- Adding documentation to public APIs of a new or existing C# library +- Reviewing missing, insufficient, or non-Microsoft-style XML docs in C# code +- Generating documentation that feeds OpenAPI/Swagger output or NuGet symbols + +## General Guidelines + +- Public members should be documented with XML comments. +- It is encouraged to document internal members as well, especially if they are complex or not self-explanatory. + +## Guidance for all APIs + +- Use `` to provide a brief, one sentence, description of what the type or member does. Start the summary with a present-tense, third-person verb. +- Use `` for additional information, which can include implementation details, usage notes, or any other relevant context. +- Use `` for language-specific keywords like `null`, `true`, `false`, `int`, `bool`, etc. +- Use `` for inline code snippets. +- Use `` for usage examples on how to use the member. + - Use `` for code blocks. `` tags should be placed within an `` tag. Add the language of the code example using the `language` attribute, for example, ``. +- Use `` to reference other types or members inline (in a sentence). +- Use `` for standalone (not in a sentence) references to other types or members in the "See also" section of the online docs. +- Use `` to inherit documentation from base classes or interfaces. + - Unless there is major behavior change, in which case you should document the differences. + +## Member-Specific Rules + +See [member-documentation-rules.md](./references/member-documentation-rules.md) for detailed wording conventions for methods (``, ``), constructors, properties (``, Gets/Sets patterns), and exceptions (``). + +## Related Skills + +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — XML docs feed OpenAPI/Swagger output +- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — Invokes this skill in Step 8 to document generated SDKs +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Code-quality checklist references these conventions for public-API docs diff --git a/.claude/skills/dotnet-xmldocs/references/member-documentation-rules.md b/.claude/skills/dotnet-xmldocs/references/member-documentation-rules.md new file mode 100644 index 00000000..8f58133f --- /dev/null +++ b/.claude/skills/dotnet-xmldocs/references/member-documentation-rules.md @@ -0,0 +1,41 @@ +# Member-Specific Documentation Rules + +## Methods + +- Use `` to describe method parameters. + - The description should be a noun phrase that doesn't specify the data type. + - Begin with an introductory article. + - If the parameter is a flag enum, start the description with "A bitwise combination of the enumeration values that specifies...". + - If the parameter is a non-flag enum, start the description with "One of the enumeration values that specifies...". + - If the parameter is a Boolean, the wording should be of the form "`` to ...; otherwise, ``.". + - If the parameter is an "out" parameter, the wording should be of the form "When this method returns, contains .... This parameter is treated as uninitialized.". +- Use `` to reference parameter names in documentation. +- Use `` to describe type parameters in generic types or methods. +- Use `` to reference type parameters in documentation. +- Use `` to describe what the method returns. + - The description should be a noun phrase that doesn't specify the data type. + - Begin with an introductory article. + - If the return type is Boolean, the wording should be of the form "`` if ...; otherwise, ``.". + +## Constructors + +- The summary wording should be "Initializes a new instance of the class [or struct].". + +## Properties + +- The `` should start with: + - "Gets or sets..." for a read-write property. + - "Gets..." for a read-only property. + - "Gets [or sets] a value that indicates whether..." for properties that return a Boolean value. +- Use `` to describe the value of the property. + - The description should be a noun phrase that doesn't specify the data type. + - If the property has a default value, add it in a separate sentence, for example, "The default is ``". + - If the value type is Boolean, the wording should be of the form "`` if ...; otherwise, ``. The default is ...". + +## Exceptions + +- Use `` to document exceptions thrown by constructors, properties, indexers, methods, operators, and events. +- Document all exceptions thrown directly by the member. +- For exceptions thrown by nested members, document only the exceptions users are most likely to encounter. +- The description of the exception describes the condition under which it's thrown. + - Omit "Thrown if ..." or "If ..." at the beginning of the sentence. Just state the condition directly, for example "An error occurred when accessing a Message Queuing API." diff --git a/.claude/skills/dotnet/SKILL.md b/.claude/skills/dotnet/SKILL.md new file mode 100644 index 00000000..61891a6a --- /dev/null +++ b/.claude/skills/dotnet/SKILL.md @@ -0,0 +1,46 @@ +--- +name: dotnet +description: Entry point and router for .NET and C# work — directs you to the right specialized .NET skill. Use when a request mentions .NET or C# in general but the specific tool is not obvious, or to get an overview of the available .NET skills. Routes to knowledge skills (dotnet-fundamentals, dotnet-aspnet, ef-core, dotnet-xmldocs) and workflow skills (dotnet-sdk-builder, dotnet-tester, dotnet-reviewer, dotnet-inspect, nuget-manager). When the matching skill is already clear, invoke that skill directly instead. +--- + +# .NET / C# Skill Router + +Signpost to the specialized .NET skills. This skill holds no best-practice knowledge +of its own — it maps a request to the appropriate specialized skill. + +## When to Use + +- A task concerns .NET or C#, but it is unclear which specialized skill applies. +- You need an overview of the available .NET skills and their responsibilities. + +When the matching skill is already clear, load it directly — the router is only +orientation, not an intermediate step. + +## Routing + +### Knowledge skills (best practices, `references/` only) + +| Concern | Skill | +|---------|-------| +| Dependency Injection, Options pattern, Configuration, modern C# idioms (for any .NET host) | `dotnet-fundamentals` | +| ASP.NET Core Web/REST APIs: controllers, Minimal APIs, middleware, routing, model binding, validation, auth, ProblemDetails, OpenAPI, health checks, CORS, rate limiting | `dotnet-aspnet` | +| Entity Framework Core: DbContext, entities/relationships, LINQ, migrations, repository patterns, N+1/performance | `ef-core` | +| C# XML doc comments (``, ``, ``, …) | `dotnet-xmldocs` | + +### Workflow skills (active tools, scripts, agents) + +| Concern | Skill | +|---------|-------| +| Generate a .NET SDK / client library / typed HTTP client | `dotnet-sdk-builder` | +| Write/run unit tests (xUnit, FakeItEasy, AwesomeAssertions) | `dotnet-tester` | +| Structured code review for .NET 10+ (explicit invocation only, see below) | `dotnet-reviewer` | +| Query .NET APIs in NuGet packages, platform libraries, or local files | `dotnet-inspect` | +| Manage NuGet packages (add/remove/update, `--outdated`, Central Package Management) | `nuget-manager` | + +## Notes + +- **`dotnet-reviewer` activates only on explicit name** — the phrases + `dotnet-reviewer`, `dotnet code review`, or `dotnet review`. It does **not** trigger + on generic "review my code", and the router does not trigger it automatically. +- **Composition:** `dotnet-sdk-builder` invokes `dotnet-xmldocs` and `dotnet-tester`; + `dotnet-aspnet` and `ef-core` build on `dotnet-fundamentals`. diff --git a/.claude/skills/ef-core/SKILL.md b/.claude/skills/ef-core/SKILL.md index abf9e4ee..38cb00b6 100644 --- a/.claude/skills/ef-core/SKILL.md +++ b/.claude/skills/ef-core/SKILL.md @@ -1,10 +1,18 @@ --- name: ef-core -description: Entity Framework Core best practices for .NET projects. Use when designing DbContext, creating entities or relationships, writing LINQ queries, managing migrations, implementing repository patterns, or troubleshooting N+1 queries and performance issues with EF Core. +description: Applies Entity Framework Core best practices for .NET projects. Use when designing DbContext, creating entities or relationships, writing LINQ queries, managing migrations, implementing repository patterns, or troubleshooting N+1 queries and performance issues with EF Core. --- # Entity Framework Core Best Practices +## When to Use + +- Designing or restructuring a `DbContext`, entities, or relationships +- Writing LINQ queries against EF Core, or troubleshooting N+1 / performance issues +- Creating, naming, or reviewing EF Core migrations +- Implementing concurrency control, repository patterns, or change tracking strategies +- Setting up EF Core tests with SQLite in-memory or Testcontainers + ## Data Context Design - Keep DbContext classes focused and cohesive @@ -60,7 +68,7 @@ See [concurrency-control.md](./references/concurrency-control.md) for `[Timestam ## Security -- Avoid SQL injection by using parameterized queries +- Use parameterized queries to prevent SQL injection - Implement appropriate data access permissions - Be careful with raw SQL queries - Consider data encryption for sensitive information @@ -89,3 +97,11 @@ See [concurrency-control.md](./references/concurrency-control.md) for `[Timestam - Test migrations in isolated environments - Consider snapshot testing for model changes - Use the `dotnet-tester` skill for generating unit and integration tests after schema changes + +## Related Skills + +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — DI lifetimes for `DbContext`, Options pattern for connection strings, modern C# idioms used in entity types +- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Generates DbContext-backed unit and integration tests (SQLite in-memory, Testcontainers) +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — Wires EF Core into ASP.NET Core via DI (scoped DbContext lifetimes) +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Reviews EF Core data-access code for performance and security issues +- **[nuget-manager](../nuget-manager/SKILL.md)** — Adds EF Core providers, Testcontainers, and SQLite packages diff --git a/.claude/skills/implementer/SKILL.md b/.claude/skills/implementer/SKILL.md index 86bba666..a054e179 100644 --- a/.claude/skills/implementer/SKILL.md +++ b/.claude/skills/implementer/SKILL.md @@ -18,6 +18,11 @@ Covers production code, tests, and documentation updates in every cycle. > The user always commits manually. If asked to commit, skip that request and inform > the user that committing is their responsibility. +> **CRITICAL RULE — USE SPECIALIZED SKILLS:** Never implement, test, document, or review +> with only your built-in knowledge when a specialized skill for the detected technology +> exists. Discover available skills at runtime and use them. This skill names NO concrete +> skills on purpose — it works with whatever skills exist now or are added later. + ## Flow Overview ``` @@ -33,6 +38,34 @@ Phase 4: Review (Sub-Agent) │ Phase 5: Summary ``` +## Skill Discovery & Capability Slots + +This workflow relies on **discovering specialized skills at runtime** rather than naming +them. Concrete skill names change over time — names are never hardcoded here. + +**How discovery works:** + +1. **Detect the tech stack** by *signals* (manifest/build files, source extensions, configs), + per affected file/module — repos may mix stacks. Examples like `*.csproj` → .NET or + `package.json` → TypeScript are illustrative only; infer any stack from its signals + (see [Phase 1.5](references/REFERENCE.md#phase-15)). +2. **List the available skills** using your runtime's skill-listing mechanism (do not assume + a fixed directory). +3. **Classify each skill by its `description`** — match purpose and technology from the text, + never from the name — and fill these **capability slots**: + +| Slot | Filled by a skill whose description covers… | Used in | +|------|---------------------------------------------|---------| +| `language-implementation` | writing code for the detected stack | Phase 3 | +| `language-testing` | the stack's test framework / test conventions | Phase 3 | +| `language-docs` | the stack's documentation conventions | Phase 3 | +| `language-review` | reviewing code for the detected stack | Phase 4 | +| `build-deps` | builds / package & dependency management | Phase 3 | + +**Usage rule:** If a slot is filled, you MUST use that skill for the matching work and pass +it as context to any sub-agent doing that work (sub-agents are stateless). If no skill +matches a slot, fall back to generic conventions and note the empty slot in the plan. + ## Phase 1 — Requirement Review Analyze the requirement before any code is written: @@ -42,9 +75,11 @@ Analyze the requirement before any code is written: 3. Clarify ambiguities — ask the user targeted questions using the ask_user tool 4. Identify affected components, files, and modules in the current codebase 5. Check for existing tests, documentation, and related code -6. Note any project-specific skills or agents that should be consulted +6. **Detect the tech stack and run skill discovery** (see *Skill Discovery & Capability + Slots*): build the capability-slot map for the affected areas -**Output:** Confirmed understanding of the requirement, resolved ambiguities, identified scope. +**Output:** Confirmed understanding of the requirement, resolved ambiguities, identified +scope, and the filled capability-slot map (with any empty slots noted). ## Phase 2 — Implementation Plan @@ -55,32 +90,40 @@ Create a structured plan with trackable tasks: - **Production code** changes - **Test** additions or updates - **Documentation** updates (if applicable) -3. Define task dependencies (what must be done first) -4. Identify tasks that can be parallelized via sub-agents -5. Check for project-specific skills, agents, or conventions that apply +3. Each task MUST record which **capability slots** it uses (from Phase 1 discovery), so + skill usage is part of the plan and verifiable +4. Define task dependencies (what must be done first) +5. Identify tasks that can be parallelized via sub-agents -**Output:** Task list with dependencies, ready for implementation. +**Output:** Task list with dependencies and per-task capability-slot assignments, ready for +implementation. ## Phase 3 — Implementation Execute tasks using sub-agents for parallel work where possible: 1. For each task (or group of independent tasks): - - Delegate to sub-agents (explore for research, task for builds/tests, general-purpose for complex changes) - - Implement production code changes - - Write or update tests to cover the changes - - Update relevant documentation -2. Run existing tests and linters to verify changes don't break anything + - Consult the task's assigned capability-slot skills BEFORE writing anything + - Delegate to sub-agents (explore for research, task for builds/tests, general-purpose for + complex changes), passing the relevant slot skill as context + - Implement production code changes (use the `language-implementation` slot) + - Write or update tests to cover the changes (use the `language-testing` slot) + - Update relevant documentation (use the `language-docs` slot) +2. Run existing tests and linters to verify changes don't break anything (use the + `build-deps` slot if filled) 3. Track task completion status -4. If project-specific skills or agents are available, use them for specialized work -**Important:** Respect the project's existing conventions, patterns, and tooling. +**Important:** Respect the project's existing conventions, patterns, and tooling. If a +capability slot is filled, you MUST use it; only fall back to generic work when the slot is +empty. ## Phase 4 — Review Run a thorough code review using a sub-agent: -1. Launch a code-review sub-agent to analyze all changes made +1. Launch a code-review sub-agent to analyze all changes made. If the `language-review` slot + is filled, use that skill (it knows stack-specific pitfalls); otherwise use a generic + review. When both a stack-specific and a generic review skill exist, combine them. 2. The review checks for: - Correctness and completeness against the requirement - Test coverage for new/changed code @@ -103,6 +146,18 @@ Provide a comprehensive summary of all work done: > **Reminder:** The user will commit the changes themselves. Do NOT create any commits. +## Red Flags — Do Not Skip Specialized Skills + +If you catch yourself thinking any of these, STOP and use the discovered slot skill: + +| Rationalization | Reality | +|-----------------|---------| +| "I already know this language well" | The skill may encode project-specific conventions you don't know. Consult it. | +| "It's a tiny change, no skill needed" | Small changes still follow the stack's patterns. Use the slot. | +| "I'll just use the generic approach" | Generic is the fallback ONLY when no slot skill exists. | +| "Discovery takes too long" | Discovery is one listing + classification. Skipping it produces off-convention code. | +| "The sub-agent will figure it out" | Sub-agents are stateless — pass them the slot skill explicitly. | + --- For detailed guidance on each phase, see [references/REFERENCE.md](references/REFERENCE.md). diff --git a/.claude/skills/implementer/references/REFERENCE.md b/.claude/skills/implementer/references/REFERENCE.md index 94b372c5..261b37ef 100644 --- a/.claude/skills/implementer/references/REFERENCE.md +++ b/.claude/skills/implementer/references/REFERENCE.md @@ -42,12 +42,35 @@ Prevent wasted effort from misunderstood requirements or unclear scope. - Check for documentation that needs updating - Use explore sub-agents for large codebase investigations -#### 1.5 Check for Project-Specific Resources - -- Look for project-specific skills (in `.claude/skills/`, `.github/skills/`, or `.agents/skills/`) -- Check for project-specific agents (in `.claude/agents/`, `.github/agents/`, or `.agents/`) -- Review instruction files (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md) -- Follow any project conventions and coding standards found +#### 1.5 Detect Stack & Discover Skills + +This step builds the **capability-slot map** used by all later phases. Never hardcode skill +names — classify by each skill's `description`. + +1. **Detect the tech stack** per affected file/module (repos may be multi-stack). Detect by + *signals*, not a fixed list — this keeps detection working for stacks not yet imagined: + - **Manifest / build files** declaring language, dependencies, or build config + (e.g. a `*.csproj`, `pom.xml`/`build.gradle`, `package.json`, `pyproject.toml`, + `go.mod`, `Cargo.toml`, `composer.json`, `Gemfile`). These are the strongest signal. + - **Source file extensions** of the affected files (e.g. `.cs`, `.java`, `.ts`, `.go`). + - **Lockfiles, toolchain configs, and CI files** that name a runtime or framework. + - **Framework markers** inside manifests/configs (a dependency name often identifies the + framework, not just the language). + + The list above is illustrative, not exhaustive. The rule is: infer language + framework + from whatever build/manifest/config/source signals are present, then map that stack to + capability slots — regardless of whether this skill ever mentioned that stack. +2. **List the available skills** using your runtime's own skill-listing mechanism. Do NOT + assume a fixed directory — the location varies across platforms (Claude Code, Copilot, + Codex) and across repos. +3. **Classify each skill by its `description`** and fill the capability slots: + `language-implementation`, `language-testing`, `language-docs`, `language-review`, + `build-deps`. Match on purpose + technology mentioned in the description, never on name. +4. For multi-stack repos, maintain a slot map **per stack** and select the right one per task. +5. Review instruction files (CLAUDE.md, AGENTS.md, .github/copilot-instructions.md) and + follow any project conventions and coding standards found. + +Record empty slots explicitly — they mean "use the generic fallback" for that work. ### Output @@ -56,7 +79,7 @@ Present the user with: - The identified acceptance criteria - Any clarifying questions (if applicable) - The affected areas of the codebase -- Any project-specific resources that will be used +- The detected tech stack(s) and the filled capability-slot map (note any empty slots) Wait for user confirmation before proceeding to Phase 2. @@ -86,6 +109,10 @@ Each task MUST address these three aspects: 2. **Tests:** What tests must be written or updated? 3. **Documentation:** What documentation needs updating? (can be "none" if truly not applicable) +Each task MUST also record its **capability slots** (from Phase 1.5), e.g. +`language-implementation`, `language-testing`, `language-docs`, `build-deps`. If a needed +slot is empty, mark it as "generic fallback" so the gap is visible in the plan. + #### 2.3 Dependencies - Identify which tasks depend on others @@ -104,7 +131,7 @@ Each task MUST address these three aspects: ### Output Present the user with: -- The complete task list with descriptions +- The complete task list with descriptions and per-task capability-slot assignments - A dependency graph (which tasks block which) - The planned execution order - Which tasks will be parallelized @@ -135,12 +162,15 @@ For each task (or parallel group of independent tasks): - Which files to modify - What tests to write - What conventions to follow - - Reference to project-specific skills/agents if relevant + - The capability-slot skill(s) assigned to this task — pass them explicitly, since + sub-agents are stateless and cannot discover your slot map on their own 4. **Review sub-agent output** before moving to next task 5. **Update task status** to `done` #### 3.2 Code Quality +- **Use the `language-implementation` slot skill** if filled; only write code from generic + knowledge when the slot is empty - Follow existing code style and conventions - Do not introduce new dependencies unless explicitly required - Keep changes minimal and focused on the requirement @@ -148,13 +178,16 @@ For each task (or parallel group of independent tasks): #### 3.3 Testing +- **Use the `language-testing` slot skill** if filled (it knows the stack's test framework + and conventions); only fall back to generic testing when the slot is empty - Write tests that cover the new/changed behavior - Ensure existing tests still pass -- Use the project's existing test framework and patterns - Cover edge cases identified in Phase 1 #### 3.4 Documentation +- **Use the `language-docs` slot skill** if filled (e.g. the stack's doc-comment conventions); + only fall back to generic documentation when the slot is empty - Update inline code documentation where needed - Update README or other docs if the change affects usage - Keep documentation changes in sync with code changes @@ -162,7 +195,8 @@ For each task (or parallel group of independent tasks): #### 3.5 Verification After all tasks are complete: -- Run the full test suite using a `task` sub-agent +- Run the full test suite using a `task` sub-agent (use the `build-deps` slot skill for the + correct build/test commands if filled) - Run any existing linters or type checkers - Fix any failures before proceeding @@ -184,7 +218,9 @@ Ensure implementation quality through an automated code review before the user c #### 4.1 Launch Code Review -Launch a `code-review` sub-agent with these instructions: +Launch a code-review sub-agent with these instructions: +- If the `language-review` slot is filled, use that skill (stack-specific pitfalls); if a + generic review skill also exists, combine both. Use generic-only when no slot is filled. - Review ALL changes made during this implementation session - Compare changes against the original requirement and acceptance criteria - Focus on substantive issues only (not style or formatting) @@ -271,6 +307,20 @@ End with a clear reminder: ### Handling Project-Specific Conventions - Always check for instruction files at the start (CLAUDE.md, AGENTS.md, etc.) -- Look for project-specific skills that might provide specialized guidance +- Run skill discovery (Phase 1.5) and use the resulting capability slots — never hardcode + skill names, so the workflow keeps working as skills are added or renamed - Follow the project's existing patterns for code style, testing, and documentation - When in doubt, ask the user about project conventions + +### Capability Slots — Quick Reference + +| Slot | Purpose | Phase | +|------|---------|-------| +| `language-implementation` | writing code for the detected stack | 3.2 | +| `language-testing` | the stack's test framework / conventions | 3.3 | +| `language-docs` | the stack's documentation conventions | 3.4 | +| `language-review` | stack-specific code review | 4 | +| `build-deps` | build, package & dependency management | 3.5 | + +**Rule:** A filled slot MUST be used (and passed to sub-agents); an empty slot means generic +fallback. Classify skills into slots by their `description`, never by name. diff --git a/.claude/skills/nuget-manager/SKILL.md b/.claude/skills/nuget-manager/SKILL.md index 0e088346..4d321e5f 100644 --- a/.claude/skills/nuget-manager/SKILL.md +++ b/.claude/skills/nuget-manager/SKILL.md @@ -5,6 +5,13 @@ description: Manages NuGet packages in .NET projects and solutions. Use when add # NuGet Manager +## When to Use + +- Adding, removing, or updating NuGet packages in a .NET project or solution +- Listing outdated packages and planning version bumps +- Verifying a specific package version exists before bumping it +- Working in a solution that uses `Directory.Packages.props` central version management + ## Prerequisites - .NET SDK installed (typically .NET 8.0 SDK or later, or a version compatible with the target solution). @@ -68,3 +75,12 @@ dotnet list package --outdated The output shows the current version, the latest resolved version, and the latest available version for each package. Use this as the basis for deciding which packages to update, then follow the **Updating Package Versions** workflow for each. +## Related Skills + +- **[dotnet-fundamentals](../dotnet-fundamentals/SKILL.md)** — Used when adding `Microsoft.Extensions.*` packages for DI, Options, and Configuration +- **[dotnet-aspnet](../dotnet-aspnet/SKILL.md)** — Invokes this skill for health-check, resilience, and middleware packages +- **[dotnet-sdk-builder](../dotnet-sdk-builder/SKILL.md)** — Invokes this skill in Step 7 to add SDK runtime dependencies +- **[dotnet-reviewer](../dotnet-reviewer/SKILL.md)** — Used when a review surfaces outdated or vulnerable packages +- **[dotnet-inspect](../dotnet-inspect/SKILL.md)** — Inspect package APIs before upgrading or replacing them +- **[ef-core](../ef-core/SKILL.md)** — Adds EF Core providers, Testcontainers, and SQLite packages + diff --git a/CLAUDE.md b/CLAUDE.md index bb7cb75d..c7265c48 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,9 +8,11 @@ - Always look if you know skills that will be useful for the task at hand before trying to solve the problem with your own knowledge. If you know skills that can be useful, ask if you should use them. - Always ask for help if you are stuck. - If a skill was explicitly requested in the prompt, use it without asking. If you can't find the skill, always ask if you should proceed without it. +- Use subagents as much as possible to avoid context pollution. +- ALWAYS verify that your changes are complete and work correctly. Use verification steps best suited for your changes. # Git Commit Instructions -- You MUST not git commit files unless explicitly asked to do so. +- You MUST not git commit files unless explicitly asked to do so by the user. - Stage files by name, not `git add -A` or `git add .` — those can sweep in secrets or large binaries. - Don't commit files that look like secrets (.env, credentials.json, *.pem). If the user explicitly asks, warn first. @@ -156,8 +158,16 @@ _service = Ensure.NotNull(service); ## Modern C# Features -- Use **primary constructors** when no constructor body is needed. -- Use private fields with guards instead of using primary constructor parameters directly, unless the parameter is assigned to a property. +- **Default to a primary constructor**, also with `Ensure.*` guards — put the guard in the field initializer, not a constructor body: + ```csharp + public sealed class Foo(IBar bar) : IFoo + { + private readonly IBar _bar = Ensure.NotNull(bar); + } + ``` +- Reference the fields, never the raw parameters (avoids capturing unguarded params). +- Use a classic constructor only when init needs real statements (control flow, ordering, multistep setup, logic before base(...)/this(...)). Guards/initializers don't count. +- A parameter assigned to a property goes via the property initializer, not a backing field. ## Async/Await @@ -174,7 +184,7 @@ _service = Ensure.NotNull(service); ## Documentation - Document all public members with XML documentation. -- Use the `csharp-docs` skill to ensure XML documentation follows best practices. +- Use the `dotnet-xmldocs` skill to ensure XML documentation follows best practices. - If you change code, always update the relevant XML documentation. ## Testing @@ -203,7 +213,7 @@ _service = Ensure.NotNull(service); - You MUST use the `dotnet-tester` skill for writing and editing tests. - You MUST use the `nuget-manager` skill for NuGet package management. - You MUST use the `dotnet-inspect` skill to query .NET APIs in NuGet packages, platform libraries (System.*, Microsoft.AspNetCore.*), or local .dll/.nupkg files — discover types and members, diff API surfaces between versions, find extension methods/implementors, locate SourceLink URLs, and triage breakages caused by package upgrades. -- You MUST use the `csharp-docs` skill to ensure XML documentation follows best practices. +- You MUST use the `dotnet-xmldocs` skill to ensure XML documentation follows best practices. -----------------------------------------------------------