diff --git a/.claude/agents/dotnet-developer.md b/.claude/agents/dotnet-developer.md index 08f96564..fe21deea 100644 --- a/.claude/agents/dotnet-developer.md +++ b/.claude/agents/dotnet-developer.md @@ -17,11 +17,11 @@ Use the following skills based on the task at hand: | Area | Skill | Description | |------|-------|-------------| -| Web & REST APIs | [dotnet-aspnet](../../skills/csharp/dotnet-aspnet/SKILL.md) | ASP.NET Core controllers, minimal APIs, middleware, auth, and API conventions | -| SDK & Library Development | [dotnet-sdk-builder](../../skills/csharp/dotnet-sdk-builder/SKILL.md) | Generate .NET SDK libraries with DI support, typed HTTP clients, and Options pattern | -| 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 | -| 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 | +| Web & REST APIs | `dotnet-aspnet` | ASP.NET Core controllers, minimal APIs, middleware, auth, and API conventions | +| SDK & Library Development | `dotnet-sdk-builder` | Generate .NET SDK libraries with DI support, typed HTTP clients, and Options pattern | +| Testing | `dotnet-tester` | Write and execute unit tests using xUnit, FakeItEasy, and AwesomeAssertions | +| Data Access | `dotnet-ef-core` | Entity Framework Core best practices, DbContext design, migrations, and queries | +| Package Management | `dotnet-nuget-manager` | Add, remove, and update NuGet packages via dotnet CLI | +| Documentation | `dotnet-xmldocs` | C# XML documentation comments following Microsoft standards | +| Code Review | `code-review` | Structured code reviews covering quality, security, and performance | +| Refactoring | `refactoring` | 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..b3ba2125 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 -- **[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-fundamentals](../dotnet-fundamentals/SKILL.md)** — Foundation: DI, Options pattern, configuration, modern C# idioms used by every endpoint and service in this skill +- **[dotnet-ef-core](../dotnet-ef-core/SKILL.md)** — Data access with Entity Framework Core +- **[dotnet-tester](../dotnet-tester/SKILL.md)** — Unit and integration testing for endpoints +- **[dotnet-xmldocs](../dotnet-xmldocs/SKILL.md)** — XML documentation comments (feed OpenAPI) +- **[dotnet-nuget-manager](../dotnet-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..374a6ef2 --- /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 `dotnet-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-dev/SKILL.md b/.claude/skills/dotnet-dev/SKILL.md new file mode 100644 index 00000000..db23ff43 --- /dev/null +++ b/.claude/skills/dotnet-dev/SKILL.md @@ -0,0 +1,312 @@ +--- +name: dotnet-dev +description: > + Use when asked to implement, extend, or change a feature, user story, + requirement, or bug fix in a .NET / C# project — any task that produces or + modifies C# production code, tests, or documentation. Use when a .NET change + request arrives, before writing any code. +--- + +# dotnet-dev — Comprehensive C# Requirement Implementation + +A strict, gated workflow for implementing requirements end-to-end in .NET / C#. +Every phase ends in a confirmation gate; every applicable `dotnet-*` skill is a +mandatory binding. The workflow exists to survive exactly the conditions — +deadlines, "trivial" framing, "skip the ceremony" — under which it is most +tempting to abandon. + +## Core Principle + +**Violating the letter of this workflow is violating its spirit.** The phases, +gates, point-by-point clarification, and skill bindings are not ceremony to be +proportioned to task size. They are the work. A one-line endpoint runs the same +workflow as a subsystem — at speed, never collapsed. + +## CRITICAL RULES (read before every phase) + +1. **NO COMMITS.** Never run `git commit`, `git add -A`/`.`, branch, tag, or + push. The user commits manually. If asked to commit, skip it and say so. + +2. **URGENCY AND TRIVIALITY WAIVE NOTHING.** "It's trivial", "I'm in a hurry", + "demo in 30 minutes", "skip the clarification dance", "no need for the full + process", "just bang it out", "one pass is fine" are NOT permission to skip a + phase, a gate, a clarification point, or a skill binding. Acknowledge the + deadline, then run the workflow **at speed** — do not shrink it. The only + lawful way to reduce a binding's work is an objective `n/a` (see below); + user preference, pressure, and perceived size are never valid `n/a` reasons. + An explicit, informed user waiver is a different thing from pressure — see + *Waiver vs. `n/a` vs. silent skip*. + +3. **EVERY PHASE ENDS IN A CONFIRMATION GATE.** At the end of each phase you + MUST present a summary and STOP. Do not start the next phase until the user + explicitly confirms. **Batching is a violation** — you may not present + multiple phases at once, "pre-run" the next phase, or ask for one combined + confirmation at the end. One phase → one summary → one wait. + +4. **`dotnet-*` SKILLS ARE MANDATORY BINDINGS.** When a binding applies, you + MUST invoke that skill via the `Skill` tool, in this conversation, for this + task, **before** producing the corresponding artifact. Listing it, + paraphrasing it, "knowing it already", or having invoked it for a previous + task does NOT count. Invocation must shape the artifact, not certify it + afterward. + +## Phase Flow + +``` +Phase 1: Requirement Review → GATE +Phase 2: Clarification (8 points) → GATE +Phase 3: Task Breakdown → GATE +Phase 4: Implementation + App Smoke-Check ◄──┐ → GATE +Phase 5: Code Review (dotnet-reviewer) │ + └─ rework needed? ───────────────────────►┘ + └─ all good → GATE +Phase 6: Final Summary +``` + +## Skill Map — Mandatory Bindings + +| Binding | Skill | Applies in | When | +|---|---|---|---| +| Core C# / DI / Options / config / modern C# idioms | `dotnet-fundamentals` | Phase 4 | always, for production code | +| ASP.NET Core (controllers, minimal APIs, middleware, auth, ProblemDetails, OpenAPI) | `dotnet-aspnet` | Phase 4 | when ASP.NET Core is touched | +| EF Core (DbContext, entities, LINQ, migrations) | `dotnet-ef-core` | Phase 4 | when EF Core is touched | +| SDK / typed HTTP client generation | `dotnet-sdk-builder` | Phase 4 | when building a typed SDK / client | +| External / platform / NuGet API surface lookup | `dotnet-inspect` | Phase 1, 4 | when an external/platform API is involved | +| Tests | `dotnet-tester` | Phase 4 | always, when code is written or changed | +| XML documentation | `dotnet-xmldocs` | Phase 4 | for any public API addition/change | +| NuGet packages | `dotnet-nuget-manager` | Phase 4 | any package add/remove/version change | +| Code review | `dotnet-reviewer` | Phase 5 | always | +| Router (tie-breaker) | `dotnet` | any | when the right sub-skill is not obvious | + +**Correct names matter:** `dotnet-ef-core` and `dotnet-nuget-manager` (not +`ef-core` / `nuget-manager`). A binding is fulfilled only when its skill has +fired via the `Skill` tool for this task. A binding applies whether you do the +work yourself or dispatch a sub-agent — self-execution never waives it, and a +sub-agent's invocation never waives the main agent's own follow-up edits. + +**Tooling:** for code navigation/exploration follow the project + global rules +(Serena first, then tokensave; built-in/`Explore` agents only in the documented +carve-outs). Do NOT use `Explore` agents for code research when tokensave is +available. + +--- + +## Phase 1 — Requirement Review + +1. Read the requirement. Extract explicit AND implicit acceptance criteria. +2. **Find gaps, contradictions, and open questions** — list them explicitly. A + review that surfaces no gaps on an ambiguous requirement is a failure; if the + requirement truly is unambiguous, say so explicitly rather than skipping. +3. Analyze scope: affected `*.csproj`, components, files; existing tests/docs. +4. Read `CLAUDE.md`, `AGENTS.md`, `.editorconfig`, `Directory.Build.props`, + `Directory.Packages.props` and follow the conventions found. +5. Invoke `dotnet-inspect` if any external/platform API surface must be verified. +6. **Determine the preliminary Skill Map** for this task and show it as a table. + +**GATE 1 — STOP.** Present: requirement restated, acceptance criteria, +gaps/contradictions/open questions, affected scope, and the preliminary Skill +Map table. Wait for confirmation. + +## Phase 2 — Clarification (8 points, one round-trip per point) + +Clarify the following **one point at a time** — present point _N_, propose a +concrete default/assumption, ask the open question, and **wait for the user's +answer before moving to point _N+1_.** You may NOT batch points into one +message, NOR skip a point silently. A point that objectively does not apply is +presented with `n/a — ` and still acknowledged. + +| # | Item | Must clarify | +|---|---|---| +| 1 | Project & folder structure | Target `*.csproj`, namespace, folder layout, new projects yes/no | +| 2 | Architecture & layering | Layers (controller/service/repo or vertical slice), DI lifetimes, public vs internal surface | +| 3 | Naming conventions | Type/method/file/namespace names, suffixes (`Service`, `Handler`, `Options`, `Async`), test naming | +| 4 | Public API / contracts | DTO shape, request/response, ProblemDetails, versioning, OpenAPI annotations | +| 5 | Errors & edge cases | Exception strategy, Result vs throws, validation style, logging granularity | +| 6 | Test strategy | Unit and/or integration, mock boundaries, naming, fakes vs real dependencies, coverage expectation | +| 7 | Persistence / EF Core | Entity shape, migrations yes/no, owned types, indexes, query style (LINQ vs spec) | +| 8 | Dependencies / NuGet | Allowed/forbidden packages, Central Package Management versions | + +**GATE 2 — STOP.** Present: every clarified decision (all 8 points) and the +**finalized** Skill Map (updated from the answers — e.g. points 7/8 confirm +`dotnet-ef-core` / `dotnet-nuget-manager`). Wait for confirmation. + +## Phase 3 — Task Breakdown + +1. Break the requirement into discrete implementation tasks. +2. Each task covers **production code + tests + documentation**. +3. **Each task publishes a Skill-prerequisite checklist** (from the Skill Map): + + ``` + Task #N: + Skill prerequisites: + [ ] dotnet-fundamentals + [ ] dotnet-aspnet + [ ] dotnet-tester + [ ] dotnet-xmldocs + ``` + +4. Define dependencies and which tasks may run in parallel via sub-agents. + +**GATE 3 — STOP.** Present the task list with per-task checklists, dependency +order, and parallelization. Wait for confirmation. + +## Phase 4 — Implementation + App Smoke-Check + +For each task (or parallel group): + +1. **Step 0 — invoke every required `dotnet-*` skill for this task** before any + `Write`/`Edit`/code-producing `Bash`. One `Skill(...)` call per binding, no + collapsing. Re-invoke per task; previous-task invocations do not carry over. + Then follow each skill's workflow when producing its artifact. +2. Implement production code (`dotnet-fundamentals` + stack skill), tests + (`dotnet-tester`), XML docs (`dotnet-xmldocs`), package changes + (`dotnet-nuget-manager`). +3. Run `dotnet build` and the test suite. Fix failures. +4. **App Smoke-Check** — if a runnable application exists, start it and verify + it actually runs: + - **Applies when** the solution has an executable project (`OutputType=Exe`, + ASP.NET Core Web/API, Worker Service). **`n/a` only when** the solution is + library-only (no executable project) — state that as the reason. + - Start the app **in the background** (a server does not exit on its own), + check startup logs / health endpoint / a representative request, then shut + it down cleanly. Never block on a foreground long-running process. + +**GATE 4 — STOP.** Present implemented tasks, build/test result, and the +App-Smoke-Check outcome (or its `n/a` reason). Wait for confirmation. + +## Phase 5 — Code Review + +1. Launch a code-review **sub-agent** that uses `dotnet-reviewer`. Invoke + `dotnet-reviewer` via the `Skill` tool in the main conversation **and** pass + it to the sub-agent prompt. Inline self-review does NOT satisfy this phase — + `dotnet-reviewer` produces a severity-tagged Markdown report under + `docs/reviews/`. +2. Evaluate findings: + - **Rework needed** → create new tasks (each with its own Skill checklist) + and return to **Phase 4**. After fixing, re-run Phase 5 (rework loop). + - **All good** → proceed. + +**GATE 5 — STOP.** Present the review findings, the report path, and your +rework/no-rework decision. Wait for confirmation. + +## Phase 6 — Final Summary + +1. Files created/modified; what was implemented and why. +2. Tests added/updated; documentation changes; package changes. +3. Decisions/trade-offs; anything the user should verify before committing. +4. **Skill-Invocation Log** (mandatory audit trail — see below). +5. Reminder: *the user commits; this workflow creates no commits.* + +--- + +## `n/a` Criteria — Strict + +A binding or clarification point may be `n/a` **only** when an objective +technical fact makes the work empty for this task. Valid reasons cite the +codebase, never the user: + +- ✅ `n/a — task touches no public API members (internal sealed class)` — `dotnet-xmldocs` +- ✅ `n/a — no NuGet package added/removed/version-changed` — `dotnet-nuget-manager` +- ✅ `n/a — solution is library-only, no executable project` — App Smoke-Check +- ✅ `n/a — scanned changed files; no DbContext/IQueryable/migration` — `dotnet-ef-core` +- ❌ NOT valid: `n/a — user said no tests` +- ❌ NOT valid: `n/a — too small / trivial / one-liner / just a lambda` +- ❌ NOT valid: `n/a — single ToListAsync, doesn't need the skill` +- ❌ NOT valid: `n/a — no test project exists` (creating it is part of the task) +- ❌ NOT valid: `n/a — demo, no time` + +If you cannot phrase the reason as "no `` exists in this task because +``", it is NOT `n/a` — invoke the skill and do the work. + +**No avoidance-driven `n/a`.** You may not reshape the implementation to escape +a binding — e.g. hand-rolling code to dodge `dotnet-nuget-manager`, inlining a +public member to dodge `dotnet-xmldocs`, or collapsing a service into private +code to dodge `dotnet-tester`. The binding follows the natural shape of the +work, not a shape chosen to minimise bindings. + +## Waiver vs. `n/a` vs. silent skip + +Three distinct cases — keep them apart. Conflating them is how the workflow +silently collapses. + +- **Implicit pressure** — "it's trivial", "I'm in a hurry", "demo in 30 + minutes", "skip the ceremony", "this is how we work here", "nobody runs all + this". **Waives NOTHING.** Acknowledge it and run the full workflow at speed. + These are exactly the conditions the workflow exists to survive (CRITICAL + RULE 2). Treat them as no-ops on scope. + +- **Explicit, informed user waiver** — the user, *after being told what the step + protects and what gap skipping it creates*, directly says "skip phase X / the + gates / the clarification / the tests / the review." This **may be honored** + (your global rule: explicit user instructions take precedence), but only on + these terms: + 1. **State the cost first.** Name what the skipped step would have caught + before you skip it. No silent compliance. + 2. **Record it as a waiver, never as `n/a`.** A waiver means "the user chose + to skip this"; `n/a` means "the work is objectively empty." Stamping `n/a` + on a waived step makes the audit trail lie about *why* it was skipped. + Log it as: `waived — explicit user instruction (, ); not a + technical n/a; gap: `. + 3. **Some steps still run regardless.** Invoking the relevant `dotnet-*` + skills before writing code costs the user nothing and keeps the code + matching repo conventions — keep them even under a waiver. + +- **Objective `n/a`** — the work is empty for a code-referenced reason (see + `n/a` Criteria). This is the only state that requires no user sign-off. + +**The line:** vague urgency is not a waiver. A waiver is the user explicitly +electing to skip a *named* step after hearing its cost. When in doubt, ask the +user to confirm the waiver explicitly — do not infer one from pressure. + +## Artifact-Substance Bar + +Invoking a skill is necessary but not sufficient — the artifact must reflect it: + +- A test that asserts nothing about the change (`Assert.True(true)`, a smoke + test with no behavioral assertion) does not satisfy `dotnet-tester`. +- Empty `` or name-echoing docs do not satisfy `dotnet-xmldocs`. +- Hand-editing `` after "invoking" `dotnet-nuget-manager` does + not satisfy it — use the `dotnet` CLI as the skill prescribes. +- A "review" with no `docs/reviews/` report does not satisfy `dotnet-reviewer`. + +If an artifact misses the bar, the binding is `[!]`, not `[x]`. + +## Skill-Invocation Log (Phase 6) + +Reproduce each task's checklist, every entry resolved with evidence: + +- `[x] — invoked at ` — a verifiable ordered turn (e.g. + "before Write of `UsersController.cs`"). "Considered"/"applied" is not + evidence. Invocation AFTER the artifact is `[!]`, not `[x]`. +- `[n/a] — did not apply ()`. +- `[!] — NOT invoked` — a violation. `[!]` is **not a shipping path**: + name it, mark the task INCOMPLETE, STOP, and offer to re-enter Phase 4 to run + the missing skill's workflow on the artifact. Phase 6 does not complete while + any `[!]` is present. + +## Red Flags — STOP and run the workflow + +| Rationalization | Reality | +|---|---| +| "It's trivial / one endpoint — phases & gates are overkill" | Size does not scale the workflow. Run all phases at speed. Gate after each. | +| "Demo in 30 min / I'm in a hurry — one pass, no gates" | Urgency waives nothing (CRITICAL RULE 2). Acknowledge the deadline, keep the gates. | +| "Lead waived the clarification dance" | The 8 points are mandatory, one round-trip each. User waiver is not a valid skip. | +| "Ambiguities aren't blocking — I'll state assumptions instead of asking" | Stating assumptions ≠ clarifying. Present each point and wait for the answer. | +| "I'll batch all phases / ask one combined confirmation" | Batching is a gate violation. One phase → one summary → one wait. | +| "This is a single ToListAsync — `dotnet-ef-core` isn't needed" | Touching EF Core triggers the binding regardless of LINQ simplicity. Invoke it. | +| "Not a public library API, so skip `dotnet-xmldocs`" | `n/a` only for objectively internal members. Public additions/changes go through it. | +| "User said no tests" | User preference is not a valid `n/a`. Either an objective code-referenced `n/a` exists or `dotnet-tester` is required; if the user insists, name it as a violation and let them decide. | +| "I'll self-review the diff inline instead of `dotnet-reviewer`" | Phase 5 requires the `dotnet-reviewer` sub-agent + `docs/reviews/` report. Inline self-review does not satisfy it. | +| "I'll hand-roll it to avoid adding a package" | Avoidance-driven `n/a` is forbidden. The binding follows the natural shape of the work. | +| "The owner told me to skip it — I'll mark those steps `n/a`" | A waiver is not an `n/a`. Honor an explicit informed waiver if given, but record it as `waived — user instruction`, never `n/a`, never silently. See *Waiver vs. `n/a`*. | +| "They pushed back / 'nobody does this here' — the workflow is waived" | Pushback, urgency, and social proof are not waivers. Only an explicit, informed skip of a *named* step counts. Vague urgency waives nothing — run at speed. | +| "I already know C# / the skill — invoking is ceremony" | Skills encode project conventions and modern idioms. Knowing ≠ invoking. Invoke it. | +| "The skill was invoked last task" | Invocations do not carry over. Re-invoke per task. | +| "The sub-agent invoked it — covers my own edits" | It does not. Re-invoke before your own follow-up Write/Edit. | +| "I'll log the `Skill(...)` call even though I ran it after the Write" | Evidence must be a real ordered turn. After-the-fact is `[!]`, not `[x]`. | +| "No executable project, but I'll skip the App-Smoke-Check check anyway" | Decide it explicitly: run it, or mark `n/a — library-only`. Don't drop it silently. | + +--- + +For detailed per-phase guidance, see +[references/REFERENCE.md](references/REFERENCE.md). diff --git a/.claude/skills/dotnet-dev/references/REFERENCE.md b/.claude/skills/dotnet-dev/references/REFERENCE.md new file mode 100644 index 00000000..4cd3787d --- /dev/null +++ b/.claude/skills/dotnet-dev/references/REFERENCE.md @@ -0,0 +1,209 @@ +# dotnet-dev — Detailed Reference + +In-depth guidance per phase. Assumes a .NET / C# project. The Skill Map and the +CRITICAL RULES in `SKILL.md` are authoritative; this document expands the *how*. + +--- + +## Phase 1 — Requirement Review (Detail) + +### Goal +Understand the requirement fully before any code is written, and surface every +gap before they become rework. + +### Steps +1. **Read the requirement** (issue, story, ticket, message). Identify the core + objective and expected outcome. +2. **Acceptance criteria** — explicit (stated) and implicit (existing tests must + still pass, API contracts honored, public additions need XML docs, existing + conventions respected). +3. **Gaps / contradictions / open questions** — write them down. Watch for: + - Scope boundaries (what is in / out). + - Error-handling behavior (`ProblemDetails`? typed exceptions? Result?). + - Backward compatibility (API surface, EF Core migrations). + - Target-framework / performance constraints. + - Contradictions between the request and existing code/conventions. +4. **Codebase analysis** — affected `*.csproj`, host type (ASP.NET Core, Worker, + Console, MAUI), data layer (EF Core), cross-cutting (DI, Options, Serilog). + Locate existing tests (`*.Tests`) and docs. Read `Directory.Build.props`, + `Directory.Packages.props`, `.editorconfig`, `nuget.config`. Use Serena / + tokensave per project + global tooling rules — not `Explore` agents when + tokensave is available. +5. **`dotnet-inspect`** — invoke whenever an external/platform/NuGet API surface + must be verified (types, members, version diffs, extension methods). +6. **Preliminary Skill Map** — from the requirement, list which bindings the + work will touch. It is *preliminary*: persistence (point 7) and dependencies + (point 8) are confirmed in Phase 2 and may add `dotnet-ef-core` / + `dotnet-nuget-manager`. + +### GATE 1 output +Requirement restated in your words · acceptance criteria · gaps/contradictions/ +open questions · affected scope · preliminary Skill Map table. **Wait.** + +--- + +## Phase 2 — Clarification (Detail) + +### Goal +Resolve the 8 standard implementation dimensions, one at a time, so nothing is +silently assumed. + +### Protocol +- **One point per message.** Present point _N_: a concrete proposed default, + then the open question. Wait for the answer before point _N+1_. +- **No batching, no skipping.** A point that objectively does not apply is shown + with `n/a — ` and still acknowledged before moving on. +- Pre-filling a sensible default is encouraged — it lets the user reply "ok" + fast — but the default never replaces the round-trip. + +### The 8 points (expanded prompts) +1. **Project & folder structure** — which `*.csproj`, namespace, folder layout; + new projects yes/no. +2. **Architecture & layering** — controller/service/repo vs vertical slice; DI + lifetimes (singleton/scoped/transient); public vs internal surface. +3. **Naming conventions** — type/method/file/namespace names; suffixes + (`Service`, `Handler`, `Options`, `Async`); test naming pattern. +4. **Public API / contracts** — DTO shape, request/response, `ProblemDetails`, + versioning, OpenAPI annotations. +5. **Errors & edge cases** — exception strategy, Result vs throws, validation + style, logging granularity. +6. **Test strategy** — unit and/or integration, mock boundaries, naming, fakes + vs real dependencies, coverage expectation. +7. **Persistence / EF Core** — entity shape, migrations yes/no, owned types, + indexes, query style (LINQ vs spec). Confirms `dotnet-ef-core`. +8. **Dependencies / NuGet** — allowed/forbidden packages, Central Package + Management versions. Confirms `dotnet-nuget-manager`. + +### GATE 2 output +All 8 clarified decisions · the **finalized** Skill Map. **Wait.** + +--- + +## Phase 3 — Task Breakdown (Detail) + +### Goal +A trackable plan of discrete tasks, each self-contained. + +### Steps +- Break into the smallest reasonable tasks; descriptive IDs; enough detail to + execute without re-reading the plan. +- Each task addresses **production code + tests + documentation**. +- Each task publishes its **Skill-prerequisite checklist** from the Skill Map. +- Dependencies — common .NET order: entities → EF Core config/migrations → + repositories/services → controllers/endpoints; DTOs before consumers; package + changes before code using them. +- Parallelization — group independent tasks for sub-agents; do not parallelize + migration/schema work with other EF Core work. + +### GATE 3 output +Task list with per-task checklists · dependency order · parallel groups. **Wait.** + +--- + +## Phase 4 — Implementation + App Smoke-Check (Detail) + +### Step 0 — Skill invocation (per task) +Before any `Write`/`Edit`/code-producing `Bash` for the task, invoke each +required binding once via the `Skill` tool. A task touching code + tests + docs +is at least three calls (`dotnet-fundamentals` + `dotnet-tester` + +`dotnet-xmldocs`) plus stack skills and `dotnet-nuget-manager` if packages +change. Wait for each skill's content; follow its workflow when producing the +artifact. Sub-agent dispatch is in addition to Step 0, not a substitute — pass +the skills explicitly to the sub-agent (sub-agents are stateless). + +### Production code +`dotnet-fundamentals` always; plus `dotnet-aspnet` (web), `dotnet-ef-core` (EF), +`dotnet-sdk-builder` (typed SDK/HTTP client), `dotnet-inspect` (verify external +API). Apply project conventions: `Ensure.*` guards, primary constructors, +nullable reference types, `CancellationToken` propagation, `.ConfigureAwait(false)` +in library code (not in tests), never `.Result`/`.Wait()`/`.GetAwaiter().GetResult()`. + +### Tests +`dotnet-tester` always when code is written/changed — xUnit + FakeItEasy + +AwesomeAssertions, plus its second-agent missing-case pass. Cover new/changed +behavior and the edge cases from Phase 1. + +### Documentation +`dotnet-xmldocs` for every public API addition/change. + +### Build & dependencies +`dotnet-nuget-manager` for any package add/remove/version change (enforces the +`dotnet` CLI and `Directory.Packages.props`). Then `dotnet build` + `dotnet test`; +fix failures before the gate. + +### App Smoke-Check +- **Applies** when an executable project exists (`OutputType=Exe`, ASP.NET Core + Web/API, Worker Service). **`n/a`** only when library-only. +- Start in the **background** (servers do not self-exit). Verify startup logs / + health endpoint / one representative request. Shut down cleanly. Never block + on a foreground long-running process. + +### GATE 4 output +Implemented tasks · build/test result · App-Smoke-Check outcome (or `n/a` +reason). **Wait.** + +--- + +## Phase 5 — Code Review (Detail) + +### Steps +- Launch a code-review **sub-agent** instructed to invoke `dotnet-reviewer`; + also invoke `dotnet-reviewer` in the main conversation. The skill reviews + working-tree or branch-vs-`main` changes and writes a severity-tagged Markdown + report under `docs/reviews/`. +- Review covers: correctness/completeness vs requirement, test coverage, doc + accuracy, code quality/bugs/security, .NET idioms, convention consistency. + +### Evaluate findings +- **No / minor issues** → fix directly (still invoking the required skills + before edits), then gate. +- **Significant issues** → new tasks (each with its Skill checklist) → return to + Phase 4 → re-run Phase 5. If the same issue recurs after 2 cycles, consult the + user. + +### GATE 5 output +Findings · report path · rework/no-rework decision. **Wait.** + +--- + +## Phase 6 — Final Summary (Detail) + +1. **Requirement** restated briefly. +2. **Files** created/modified. +3. **Implementation details** and key design decisions. +4. **Tests** added/updated and what they cover. +5. **Documentation** changes (XML docs, READMEs). +6. **Package changes** (cross-reference `Directory.Packages.props`). +7. **Decisions / trade-offs.** +8. **Review notes** — key `dotnet-reviewer` points + report link. +9. **Things to check** before committing. +10. **Skill-Invocation Log** — every task's checklist resolved to `[x]`/`[n/a]`/ + `[!]` with evidence (see `SKILL.md`). Mandatory even when all bindings were + correct — it is the audit trail. + +End with: *All changes are ready for your review. Commit them yourself when +satisfied — this workflow creates no commits.* + +--- + +## General Guidelines + +### Sub-agents +- Provide complete context (stateless). Pass the relevant `dotnet-*` skills in + the prompt. Use a build/test runner for `dotnet build`/`dotnet test`. Prefer + small focused tasks; launch independent tasks in parallel. +- For code research, follow the project + global tooling rules (Serena → + tokensave → built-in; no `Explore` agents when tokensave is available). + +### Git +- NEVER `git commit`, `git add -A`/`.`, branch, tag, or push. Read-only git + (`diff`/`status`/`log`) is fine. Stage by name only when explicitly asked; + refuse secret-like files (`.env`, `credentials.json`, `*.pem`). +- If a commit is requested, skip it: *"Committing is your responsibility."* + +### Project conventions +- Honor `CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`, + `.editorconfig`, `Directory.Build.props`, `Directory.Packages.props`. +- `Ensure.NotNull(...)` / `Ensure.IsNotNullOrEmpty(...)` / + `Ensure.IsNotNullOrWhitespace(...)` from `CreativeCoders.Core` for argument + guards in libraries. diff --git a/.claude/skills/dotnet-ef-core/SKILL.md b/.claude/skills/dotnet-ef-core/SKILL.md new file mode 100644 index 00000000..10ba55d5 --- /dev/null +++ b/.claude/skills/dotnet-ef-core/SKILL.md @@ -0,0 +1,107 @@ +--- +name: dotnet-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 +- Use constructor injection for configuration options +- Override OnModelCreating for fluent API configuration +- Separate entity configurations using IEntityTypeConfiguration +- Consider using DbContextFactory pattern for console apps or tests + +## Entity Design + +- Use meaningful primary keys (consider natural vs surrogate keys) +- Implement proper relationships (one-to-one, one-to-many, many-to-many) +- Use data annotations or fluent API for constraints and validations +- Implement appropriate navigational properties +- Consider using owned entity types for value objects + +## Performance + +- Use AsNoTracking() for read-only queries +- Implement pagination for large result sets with Skip() and Take() +- Use Include() to eager load related entities when needed +- Consider projection (Select) to retrieve only required fields +- Use compiled queries for frequently executed queries +- Avoid N+1 query problems by properly including related data + +## Migrations + +- Create small, focused migrations +- Name migrations descriptively +- Verify migration SQL scripts before applying to production +- Consider using migration bundles for deployment +- Add data seeding through migrations when appropriate + +## Querying + +- Use IQueryable judiciously and understand when queries execute +- Prefer strongly-typed LINQ queries over raw SQL +- Use appropriate query operators (Where, OrderBy, GroupBy) +- Consider database functions for complex operations +- Implement specifications pattern for reusable queries + +## Change Tracking & Saving + +- Use appropriate change tracking strategies +- Batch your SaveChanges() calls +- Implement concurrency control for multi-user scenarios (see below) +- Consider using transactions for multiple operations +- Use appropriate DbContext lifetimes (scoped for web apps) + +### Concurrency Control + +See [concurrency-control.md](./references/concurrency-control.md) for `[Timestamp]`, `[ConcurrencyCheck]`, fluent API configuration, and `DbUpdateConcurrencyException` handling patterns. + +## Security + +- Use parameterized queries to prevent SQL injection +- Implement appropriate data access permissions +- Be careful with raw SQL queries +- Consider data encryption for sensitive information +- Use migrations to manage database user permissions + +## Testing + +- Avoid the EF Core In-Memory provider for tests — it does not enforce constraints, referential integrity, or transactions, so tests can pass while real database behavior fails +- Use **SQLite in-memory mode** for lightweight unit and integration tests that need realistic SQL semantics: + ```csharp + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + ``` +- Use **Testcontainers** for integration tests that must match production database behavior (e.g., PostgreSQL, SQL Server): + ```csharp + var container = new PostgreSqlBuilder().Build(); + await container.StartAsync(); + var options = new DbContextOptionsBuilder() + .UseNpgsql(container.GetConnectionString()) + .Options; + ``` +- Mock DbContext and DbSet only for pure unit tests that do not execute queries +- 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 +- **[dotnet-nuget-manager](../dotnet-nuget-manager/SKILL.md)** — Adds EF Core providers, Testcontainers, and SQLite packages diff --git a/.claude/skills/dotnet-ef-core/references/concurrency-control.md b/.claude/skills/dotnet-ef-core/references/concurrency-control.md new file mode 100644 index 00000000..57200980 --- /dev/null +++ b/.claude/skills/dotnet-ef-core/references/concurrency-control.md @@ -0,0 +1,52 @@ +# Concurrency Control + +Use `[Timestamp]` for automatic row-version concurrency (SQL Server / PostgreSQL with `rowversion` or `xmin`): + +```csharp +public class Order +{ + public int Id { get; set; } + public string Status { get; set; } = string.Empty; + + [Timestamp] + public byte[] RowVersion { get; set; } = []; +} +``` + +Use `[ConcurrencyCheck]` to protect individual properties without a row version column: + +```csharp +public class Product +{ + public int Id { get; set; } + + [ConcurrencyCheck] + public decimal Price { get; set; } +} +``` + +Or configure via fluent API: + +```csharp +modelBuilder.Entity() + .Property(o => o.RowVersion) + .IsRowVersion(); + +modelBuilder.Entity() + .Property(p => p.Price) + .IsConcurrencyToken(); +``` + +Catch `DbUpdateConcurrencyException` at the call site and implement a retry or conflict-resolution strategy: + +```csharp +try +{ + await context.SaveChangesAsync(); +} +catch (DbUpdateConcurrencyException ex) +{ + // Reload the entity and resolve the conflict, or inform the user + await ex.Entries.Single().ReloadAsync(); +} +``` diff --git a/.claude/skills/dotnet-fundamentals/SKILL.md b/.claude/skills/dotnet-fundamentals/SKILL.md new file mode 100644 index 00000000..3a643cd3 --- /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) +- **dotnet-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 +- **dotnet-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..699076c9 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 +- **[dotnet-nuget-manager](../dotnet-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-nuget-manager/SKILL.md b/.claude/skills/dotnet-nuget-manager/SKILL.md new file mode 100644 index 00000000..df2960c3 --- /dev/null +++ b/.claude/skills/dotnet-nuget-manager/SKILL.md @@ -0,0 +1,86 @@ +--- +name: dotnet-nuget-manager +description: Manages NuGet packages in .NET projects and solutions. Use when adding, removing, or updating NuGet package references or versions. Enforces dotnet CLI for package operations, supports Directory.Packages.props central version management, and provides version verification workflows. Handles dotnet add/remove package, dotnet list package --outdated, and dotnet restore. +--- + +# 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). +- `dotnet` CLI available on your `PATH`. +- `jq` (JSON processor) — Linux/macOS; OR +- PowerShell (`pwsh`) — Windows/cross-platform; required for version verification using `dotnet package search`. + +## Core Rules + +1. **NEVER** directly edit `.csproj`, `.props`, or `Directory.Packages.props` files to **add** or **remove** packages. Always use `dotnet add package` and `dotnet remove package` commands. +2. **DIRECT EDITING** is ONLY permitted for **changing versions** of existing packages. +3. **VERSION UPDATES** must follow the mandatory workflow: + - Verify the target version exists on NuGet. + - Determine if versions are managed per-project (`.csproj`) or centrally (`Directory.Packages.props`). + - Update the version string in the appropriate file. + - Immediately run `dotnet restore` to verify compatibility. + +## Workflows + +### Adding a Package +Use `dotnet add [] package [--version ]`. +Example: `dotnet add src/MyProject/MyProject.csproj package Newtonsoft.Json` + +### Removing a Package +Use `dotnet remove [] package `. +Example: `dotnet remove src/MyProject/MyProject.csproj package Newtonsoft.Json` + +### Updating Package Versions +When updating a version, follow these steps: + +1. **Verify Version Existence**: + Check if the version exists using the `dotnet package search` command with exact match and JSON formatting. + Using `jq`: + `dotnet package search --exact-match --format json | jq -e '.searchResult[].packages[] | select(.version == "")'` + Using PowerShell: + `(dotnet package search --exact-match --format json | ConvertFrom-Json).searchResult.packages | Where-Object { $_.version -eq "" }` + +2. **Determine Version Management**: + - Search for `Directory.Packages.props` in the solution root. If present, versions should be managed there via ``. + - If absent, check individual `.csproj` files for ``. + +3. **Apply Changes**: + Modify the identified file with the new version string. + +4. **Verify Stability**: + Run `dotnet restore` on the project or solution. If errors occur, revert the change and investigate. + +### Listing Outdated Packages + +Use `dotnet list package --outdated` to find packages with newer versions available. + +**Per project:** +```bash +dotnet list src/MyProject/MyProject.csproj package --outdated +``` + +**Entire solution:** +```bash +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 +- **[dotnet-ef-core](../dotnet-ef-core/SKILL.md)** — Adds EF Core providers, Testcontainers, and SQLite packages + diff --git a/.claude/skills/dotnet-reviewer/SKILL.md b/.claude/skills/dotnet-reviewer/SKILL.md index 30795d1d..63d6f97a 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 +- **[dotnet-ef-core](../dotnet-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 +- **[dotnet-nuget-manager](../dotnet-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..15f5030b 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 `dotnet-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..fafbf4dc 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 `dotnet-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 +- **[dotnet-nuget-manager](../dotnet-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..12775ff9 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 +- **[dotnet-ef-core](../dotnet-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..6766638a --- /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, dotnet-ef-core, dotnet-xmldocs) and workflow skills (dotnet-sdk-builder, dotnet-tester, dotnet-reviewer, dotnet-inspect, dotnet-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 | `dotnet-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) | `dotnet-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 `dotnet-ef-core` build on `dotnet-fundamentals`. diff --git a/.claude/skills/implementer/SKILL.md b/.claude/skills/implementer/SKILL.md index 86bba666..d1f0aa75 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,51 @@ 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 | + +Always show the capability slots to the user. +**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. if you delegate work to a sub-agent, +you MUST pass the slot skill explicitly. + +**Slot fulfillment is by invocation, not by listing.** A capability slot counts as fulfilled +only when its skill has been invoked via the `Skill` tool **in this conversation, for this +task**. Listing a skill in the slot table, paraphrasing its description, or relying on prior +knowledge of the technology does NOT count as fulfillment. The rule is identical whether you +do the work yourself or dispatch a sub-agent — self-execution does not waive the +invocation, and a sub-agent invocation does not waive it for the main agent's own +follow-up edits. + +**A single task may have multiple slots filled at once** (e.g. implementation + testing + +documentation in one task, or two language-implementation slots when a stack mixes +sub-technologies). In that case every assigned slot must be invoked independently — one +slot's invocation never substitutes for another's, and you must not collapse the +invocations even when the same task touches all of them. Invocations from a previous task +do NOT carry over either; re-invoke at the start of every task where the slot applies. + ## Phase 1 — Requirement Review Analyze the requirement before any code is written: @@ -42,9 +92,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 @@ -52,43 +104,105 @@ Create a structured plan with trackable tasks: 1. Break the requirement into discrete implementation tasks 2. Each task MUST include all three aspects: - - **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 - -**Output:** Task list with dependencies, ready for implementation. + - **Production code** changes + - **Test** additions or updates + - **Documentation** updates (if applicable) +3. **Each task MUST publish a Skill-prerequisite checklist** listing every capability slot + it depends on (from the Phase 1 discovery). The checklist is part of the plan text the + user sees; it is not optional, and it is not collapsed even when a task touches every + slot. The format is: + + ``` + Task #N: + Skill prerequisites (Step 0 of Phase 3): + [ ] — filled by "" + [ ] — filled by "" + [ ] — EMPTY (no matching skill; generic fallback) + ``` + + When Step 0 fires in Phase 3, each `[ ]` is replaced with `[x]` (or annotated with + "n/a — slot empty" for empty slots). Multiple skills filling the same slot get their own + line each. The same skill appearing in two different slots is two separate + prerequisites — never deduplicated. +4. Define task dependencies (what must be done first) +5. Identify tasks that can be parallelized via sub-agents + +**Output:** Task list with dependencies and a per-task Skill-prerequisite checklist, ready +for implementation. Any task missing its checklist is not a valid plan entry. ## 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 -3. Track task completion status -4. If project-specific skills or agents are available, use them for specialized work + - **Step 0 — invoke every assigned slot skill.** Before any `Write`, `Edit`, or + code-producing `Bash` call for this task, call the `Skill` tool **once per capability + slot** the task is assigned to. If the task has multiple slots (e.g. + `language-implementation` + `language-testing` + `language-docs` at the same time, or + two slots of the same kind), invoke them all — one invocation never substitutes for + another. Wait for each skill's content to load and follow its workflow when producing + the corresponding artifact. A skill invocation made for a previous task does NOT carry + over; re-invoke per task. + - Delegate to sub-agents (explore for research, task for builds/tests, general-purpose + for complex changes), passing the relevant slot skill(s) as context. Sub-agent + dispatch is in addition to — not instead of — Step 0: the main agent still needs the + slot's workflow loaded for any follow-up edits it makes itself. + - Implement production code changes using the skill(s) filling the + `language-implementation` slot. + - Write or update tests using the skill(s) filling the `language-testing` slot. + - Update relevant documentation using the skill(s) filling the `language-docs` slot. +2. Run existing tests and linters to verify changes don't break anything (use the + `build-deps` slot if filled — invoke it the same way as any other slot). +3. Track task completion status. Only mark a task `completed` once every slot assigned to + it has been invoked AND the corresponding artifact (code / tests / docs) has been + produced or explicitly recorded as not-applicable for this task. + +**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. + +### Worked example — beginning a task with multiple slots + +Suppose Task #N has three slots assigned: `language-implementation`, `language-testing`, +and `language-docs`, each filled by some discovered skill. The correct opening of the task +looks like this (skill names are placeholders — substitute whatever Phase 1 discovery +produced for each slot): + +``` +TaskUpdate(taskId=N, status=in_progress) +Skill(skill="") ← MUST fire before production-code Write/Edit +Skill(skill="") ← MUST fire before test-code Write/Edit +Skill(skill="") ← MUST fire before doc Write/Edit +# ...follow each invoked skill's workflow... +Write(file_path=".../", content=...) +Write(file_path=".../", content=...) +Edit(file_path=".../", ...) +``` -**Important:** Respect the project's existing conventions, patterns, and tooling. +Notes: + +- Three slots → three `Skill(...)` calls. Two slots → two calls. Five → five. No collapsing. +- If you dispatch a sub-agent for one of the slots, the `Skill(...)` call still happens + here in the main conversation first, and the slot skill name is also passed to the + sub-agent prompt. +- Omitting any `Skill(...)` line above is exactly the failure mode the rules in this + section exist to prevent. ## 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 - - Documentation accuracy - - Code quality, potential bugs, and security issues + - Correctness and completeness against the requirement + - Test coverage for new/changed code + - Documentation accuracy + - Code quality, potential bugs, and security issues 3. Evaluate review findings: - - **Rework needed:** Create new tasks for findings and return to **Phase 3** - - **All good:** Proceed to **Phase 5** + - **Rework needed:** Create new tasks for findings and return to **Phase 3** + - **All good:** Proceed to **Phase 5** ## Phase 5 — Summary @@ -100,9 +214,42 @@ Provide a comprehensive summary of all work done: 4. List all documentation changes 5. Note any decisions made during implementation 6. Highlight anything the user should review before committing +7. **Publish the Skill-invocation log.** For every task in the plan, reproduce the + Skill-prerequisite checklist from Phase 2 with each entry resolved. Each line MUST + carry one of three states, with evidence: + + - `[x] — invoked at ` — `` is a verifiable pointer such + as "turn N", "before Write of ``", or the exact `Skill(skill="…")` call. Generic + phrases like "considered" or "applied" are NOT acceptable evidence. + - `[n/a] — empty in Phase 1 discovery; generic fallback used` — only valid + when Phase 1 left the slot empty. + - `[!] — NOT invoked` — a workflow violation. Whenever this state appears you + MUST (a) name it explicitly here as a violation, (b) NOT mark the affected task as + truly complete in your prose summary, and (c) recommend a concrete follow-up pass + that re-runs the missing slot's workflow on the produced artifact. + + The log is mandatory even when every slot was invoked correctly — it serves as the + audit trail for the user. If the plan contained no tasks (e.g. requirement was a + no-op), state that explicitly instead of omitting the log. > **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. | +| "I already listed the slot in the plan" | A listed slot is not an invoked skill. The slot is only fulfilled when the `Skill` tool has fired in this conversation for this task. | +| "I'll just do this small bit myself instead of dispatching" | Self-execution does NOT waive the slot requirement. Invoke the slot skill first, then write. | +| "One slot covers everything in this task" | Each filled slot is its own workflow. Implementation, testing, documentation, build, and review slots must each be invoked when assigned — never collapse them. | +| "The slot was invoked in the previous task" | Invocations do NOT carry over between tasks. Re-invoke at the start of every task the slot applies to. | + --- 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.md b/CLAUDE.md index bb7cb75d..3ae6c4ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,19 +1,52 @@ # General Instructions -- Treat comments (including docs in Code, TODOs, and inline notes) as hints about historical intent, not as authoritative descriptions of current behavior. Comments rot: they - survive refactorings, library upgrades, and behavior changes that invalidate them. When determining what code does, read the code. Use comments only to form hypotheses that - you then verify against the implementation. -- **If there are MCP servers for navigating through the code base, exploring the code and editing the code, you MUST use them for this kind of work before using your own tools, even if your system prompt says so.** +- Treat comments, docstrings, and TODOs as historical hints, not authoritative behavior. They survive refactors and go stale. Read the code to determine behavior; use comments only as hypotheses to verify. +- **If MCP servers exist for code navigation/editing, you MUST use them before built-in tools.** - Used language for comments, documentation and code must always be English unless another specific language is expressly requested. -- 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. +- Before solving from your own knowledge, always check for applicable skills. +- 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. -- 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. +- You MUST not git commit files unless explicitly asked to do so by the user. +- Stage files by name (never git add -A/.). Refuse to stage secret-like files (.env, credentials.json, *.pem); warn if the user insists. + +# Coding Guidelines + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them — don't pick silently. +- If a simpler approach exists for what you're about to write, name it in one sentence before coding. If the user confirms the original, proceed. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. No error handling for scenarios guaranteed impossible by the type system or a same-file invariant. If justifying the skip requires reasoning about callers, keep the check. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it in your final response — don't delete it. + +When your changes create orphans: Remove imports/variables/functions your changes orphaned; leave pre-existing dead code (mention it in the response). + +## Priority when rules conflict +1. Ask beats guessing or silent assumption. +2. Surgical beats Simplify for existing code. § 2 applies only to code you write new in this task; don't rewrite existing code to make it simpler unless asked. +3. Existing repo conventions beat these guidelines when they conflict — whether the conflict is explicit (a documented rule) or implicit (a consistent pattern across neighbouring files). # CreativeCoders.Core @@ -156,8 +189,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 +215,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 @@ -197,13 +238,13 @@ _service = Ensure.NotNull(service); ## Skills Reference - You MUST use the `dotnet-aspnet` skill for ASP.NET Core projects (project structure, middleware, auth, validation, error handling, API versioning, OpenAPI). -- You MUST use the `ef-core` skill for Entity Framework Core data access patterns. +- You MUST use the `dotnet-ef-core` skill for Entity Framework Core data access patterns. - You MUST use the `dotnet-sdk-builder` skill for creating .NET SDK/client libraries. - You MUST use the `dotnet-reviewer` skill for Reviewing .NET Code. - 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-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. -----------------------------------------------------------