From 879802d25ca5b477df1feaf3c8dd81d8e29b2fa1 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sun, 25 Jan 2026 13:19:56 +0000 Subject: [PATCH 1/3] feat: Make repo AI-ready with comprehensive guidelines - Rewrite CLAUDE.md with architectural philosophy and patterns - Add .cursorrules for Cursor IDE users - Add .github/copilot-instructions.md for GitHub Copilot AI assistants can now understand: - Modular monolith + vertical slice philosophy - Feature structure and patterns - Decision guides for where to put code - Critical rules and rationale --- .cursorrules | 35 ++++ .github/copilot-instructions.md | 49 +++++ CLAUDE.md | 352 +++++++++++++++----------------- 3 files changed, 243 insertions(+), 193 deletions(-) create mode 100644 .cursorrules create mode 100644 .github/copilot-instructions.md diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000000..93e07a90a9 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,35 @@ +You are working on FullStackHero .NET Starter Kit - a modular monolith using vertical slice architecture. + +ARCHITECTURE: +- Features are self-contained in Modules/{Module}/Features/v1/{Feature}/ +- Each feature has: Command/Query, Handler, Validator, Endpoint +- Modules are in Modules/, framework in BuildingBlocks/, reference app in Playground/ +- DTOs go in Modules.{Name}.Contracts/ projects + +PATTERNS: +- Use Mediator library (NOT MediatR) - interfaces are ICommand, IQuery, ICommandHandler, IQueryHandler +- Handlers use primary constructors for DI +- Endpoints are static extension methods returning RouteHandlerBuilder +- Use .RequirePermission() for authorization +- Use [AsParameters] for query string binding +- All entities with tenant data implement IHasTenant (auto-filtered) + +RULES: +- Never return entities directly, always map to DTOs +- Every command needs an AbstractValidator +- Add .WithName() and .WithSummary() to all endpoints +- Build must pass with 0 warnings +- Don't modify BuildingBlocks without explicit approval + +COMMANDS: +- Build: dotnet build src/FSH.Framework.slnx +- Test: dotnet test src/FSH.Framework.slnx +- Run: dotnet run --project src/Playground/FSH.Playground.AppHost + +When adding a feature: +1. Create folder Modules/{Module}/Features/v1/{Name}/ +2. Add {Name}Command.cs or {Name}Query.cs +3. Add {Name}Handler.cs +4. Add {Name}Validator.cs (for commands) +5. Add {Name}Endpoint.cs +6. Wire in module's MapEndpoints() diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..f3c53d6fd6 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,49 @@ +# Copilot Instructions + +This is FullStackHero .NET Starter Kit - a modular monolith with vertical slice architecture. + +## Key Patterns + +**Feature Structure:** +``` +Modules/{Module}/Features/v1/{Feature}/ +├── {Feature}Command.cs # ICommand +├── {Feature}Handler.cs # ICommandHandler +├── {Feature}Validator.cs # AbstractValidator +└── {Feature}Endpoint.cs # Static RouteHandlerBuilder extension +``` + +**Use Mediator (not MediatR):** +```csharp +// ✓ Correct +public sealed record CreateUserCommand(string Email) : ICommand; +public sealed class CreateUserHandler : ICommandHandler + +// ✗ Wrong +public class CreateUserCommand : IRequest // This is MediatR +``` + +**Endpoints:** +```csharp +public static RouteHandlerBuilder MapCreateUserEndpoint(this IEndpointRouteBuilder e) => + e.MapPost("/", async (CreateUserCommand cmd, IMediator m, CancellationToken ct) => + TypedResults.Ok(await m.Send(cmd, ct))) + .WithName("CreateUser") + .WithSummary("Creates a new user") + .RequirePermission(IdentityPermissions.Users.Create); +``` + +## Rules + +1. DTOs in `Modules.{Module}.Contracts/` - never return entities +2. Every command needs a FluentValidation validator +3. Use `[AsParameters]` for query parameters +4. Use `.RequirePermission()` for auth +5. Build with 0 warnings: `dotnet build src/FSH.Framework.slnx` + +## Project Structure + +- `BuildingBlocks/` - Framework (don't modify without approval) +- `Modules/` - Feature modules (Identity, Multitenancy, Auditing) +- `Playground/` - Reference application +- `Tests/` - Architecture and unit tests diff --git a/CLAUDE.md b/CLAUDE.md index 65b54f181f..d8985ca29d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,239 +1,205 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +> AI assistant guidelines for FullStackHero .NET Starter Kit -## Build & Run Commands +## Philosophy -```bash -# Restore and build -dotnet restore src/FSH.Framework.slnx -dotnet build src/FSH.Framework.slnx - -# Run with Aspire (spins up Postgres + Redis via Docker) -dotnet run --project src/Playground/FSH.Playground.AppHost +This is a **modular monolith** using **vertical slice architecture**. Every decision optimizes for: -# Run API standalone (requires DB/Redis/JWT config in appsettings) -dotnet run --project src/Playground/Playground.Api +1. **Feature isolation** — Each feature is self-contained in one folder +2. **Module boundaries** — Modules are deployment-ready packages +3. **Explicit over implicit** — No magic, clear dependency flow +4. **Contract-first** — Public APIs via Contracts projects, internals stay internal -# Run all tests -dotnet test src/FSH.Framework.slnx +## Mental Model -# Run single test project -dotnet test src/Tests/Architecture.Tests +``` +┌─────────────────────────────────────────────────────────────┐ +│ Playground (Reference App) │ +│ └─ Wires modules together, runs the app │ +├─────────────────────────────────────────────────────────────┤ +│ Modules (Feature Packages) │ +│ ├─ Identity → Auth, users, roles, permissions │ +│ ├─ Multitenancy → Tenant isolation, per-tenant DBs │ +│ └─ Auditing → Audit trail, security logs │ +├─────────────────────────────────────────────────────────────┤ +│ BuildingBlocks (Framework) │ +│ └─ Core, Persistence, Web, Caching, Jobs, Events... │ +│ ⚠️ Changes here affect ALL modules │ +└─────────────────────────────────────────────────────────────┘ +``` -# Run specific test -dotnet test src/Tests/Architecture.Tests --filter "FullyQualifiedName~TestMethodName" +## The Vertical Slice -# Generate C# API client from OpenAPI spec (requires API running) -./scripts/openapi/generate-api-clients.ps1 -SpecUrl "https://localhost:7030/openapi/v1.json" +**One feature = one folder with everything it needs.** -# Check for OpenAPI drift (CI validation) -./scripts/openapi/check-openapi-drift.ps1 -SpecUrl "" ``` +Modules/Identity/Features/v1/CreateUser/ +├── CreateUserCommand.cs ← What (input) +├── CreateUserHandler.cs ← How (logic) +├── CreateUserValidator.cs ← Guard (validation) +└── CreateUserEndpoint.cs ← Where (HTTP binding) +``` + +**Why this works:** +- Change a feature? One folder to modify +- Delete a feature? Delete one folder +- Understand a feature? Read one folder +- Test a feature? Mock one handler + +## Decision Guide -## Architecture +### "Where do I put this?" -FullStackHero .NET 10 Starter Kit - multi-tenant SaaS framework using vertical slice architecture. +| You're building... | Put it in... | +|-------------------|--------------| +| New API endpoint | `Modules/{Module}/Features/v1/{Feature}/` | +| Shared DTO/contract | `Modules.{Module}.Contracts/` | +| Cross-cutting concern | `BuildingBlocks/` (needs approval) | +| New bounded context | New `Modules.{Name}/` project | +| Database migration | `Playground/Migrations.PostgreSQL/` | -### Repository Structure +### "Should I create a new module?" -- **src/BuildingBlocks/** - Reusable framework components (packaged as NuGets): Core (DDD primitives), Persistence (EF Core + specifications), Caching (Redis), Mailing, Jobs (Hangfire), Storage, Web (host wiring), Eventing -- **src/Modules/** - Feature modules (packaged as NuGets): Identity (JWT auth, users, roles), Multitenancy (Finbuckle), Auditing -- **src/Playground/** - Reference implementation using direct project references for development; includes Aspire AppHost, API, Blazor UI, PostgreSQL migrations -- **src/Tests/** - Architecture tests using NetArchTest.Rules, xUnit, Shouldly -- **scripts/openapi/** - NSwag-based C# client generation from OpenAPI spec; outputs to `Playground.Blazor/ApiClient/Generated.cs` -- **terraform/** - AWS infrastructure as code (modular) - - `modules/` - Reusable: network, ecs_cluster, ecs_service, rds_postgres, elasticache_redis, alb, s3_bucket - - `apps/playground/` - Playground deployment stack with `envs/{dev,staging,prod}/{region}/` - - `bootstrap/` - Initial AWS setup (S3 backend, etc.) +Ask: **Does this represent a separate bounded context?** +- Has its own domain entities? → New module +- Could be deployed independently? → New module +- Just a new feature in existing domain? → Add to existing module -### Module Pattern +## Code Patterns -Each module implements `IModule` with: -- `ConfigureServices(IHostApplicationBuilder)` - DI registration -- `MapEndpoints(IEndpointRouteBuilder)` - Minimal API endpoint mapping +### Command/Query (CQRS) -Feature structure within modules: +```csharp +// Command — changes state, returns result +public sealed record CreateUserCommand(string Email, string Name) : ICommand; + +// Query — reads state, no side effects +public sealed record GetUserQuery(Guid Id) : IQuery; ``` -Features/v1/{Feature}/ -├── {Feature}Command.cs (or Query) -├── {Feature}Handler.cs -├── {Feature}Validator.cs (FluentValidation) -└── {Feature}Endpoint.cs (static extension method on IEndpointRouteBuilder) + +### Handler + +```csharp +public sealed class CreateUserHandler( + IRepository repo, + ICurrentUser currentUser) : ICommandHandler +{ + public async ValueTask Handle(CreateUserCommand cmd, CancellationToken ct) + { + var user = User.Create(cmd.Email, cmd.Name, currentUser.TenantId); + await repo.AddAsync(user, ct); + return new UserCreatedResponse(user.Id); + } +} ``` -Contracts projects (`Modules.{Name}.Contracts/`) contain public DTOs shareable with clients. +### Endpoint -### Endpoint Pattern +```csharp +public static class CreateUserEndpoint +{ + public static RouteHandlerBuilder Map(this IEndpointRouteBuilder e) => + e.MapPost("/", async (CreateUserCommand cmd, IMediator m, CancellationToken ct) => + TypedResults.Created($"/users/{(await m.Send(cmd, ct)).Id}")) + .WithName(nameof(CreateUserCommand)) + .WithSummary("Create a new user") + .RequirePermission(IdentityPermissions.Users.Create); +} +``` + +### Validator -Endpoints are static extension methods returning `RouteHandlerBuilder`: ```csharp -public static RouteHandlerBuilder MapXxxEndpoint(this IEndpointRouteBuilder endpoint) +public sealed class CreateUserValidator : AbstractValidator { - return endpoint.MapPost("/path", async (..., IMediator mediator, CancellationToken ct) => + public CreateUserValidator() { - var result = await mediator.Send(command, ct); - return TypedResults.Ok(result); - }); + RuleFor(x => x.Email).NotEmpty().EmailAddress(); + RuleFor(x => x.Name).NotEmpty().MaximumLength(100); + } } ``` -### Platform Wiring +## Critical Rules -In `Program.cs`: -1. Register Mediator with command/query assemblies -2. Call `builder.AddHeroPlatform(...)` - enables auth, OpenAPI, caching, mailing, jobs, health, OTel -3. Call `builder.AddModules(moduleAssemblies)` to load modules -4. Call `app.UseHeroMultiTenantDatabases()` for tenant DB migrations -5. Call `app.UseHeroPlatform(p => p.MapModules = true)` to wire endpoints +| Rule | Rationale | +|------|-----------| +| Use `Mediator` not `MediatR` | Source-gen library, different interfaces (`ICommand`, `IQuery`) | +| Handlers use primary constructors | Clean DI, immutable dependencies | +| DTOs in Contracts project | Shareable with clients, no internal leakage | +| Entities implement `IHasTenant` | Auto-filtered by tenant, no manual filtering | +| Endpoints use `.RequirePermission()` | Centralized auth, auditable | +| Zero build warnings | CI enforces, no exceptions | -## Configuration +## Multi-Tenancy + +Every request has a tenant context. The framework handles: +- **Tenant resolution** — From header, subdomain, or path +- **Data isolation** — Queries auto-filter by `TenantId` +- **Per-tenant DBs** — Optional, configurable per tenant + +**Your code:** Just implement `IHasTenant` on entities. Done. + +## Commands + +```bash +# Build (must pass with 0 warnings) +dotnet build src/FSH.Framework.slnx -Key settings (appsettings or env vars): -- `DatabaseOptions:Provider` - postgres or mssql -- `DatabaseOptions:ConnectionString` - Primary database -- `CachingOptions:Redis` - Redis connection -- `JwtOptions:SigningKey` - Required in production +# Test (architecture tests enforce patterns) +dotnet test src/FSH.Framework.slnx -## Code Standards +# Run with infrastructure (Postgres + Redis via Aspire) +dotnet run --project src/Playground/FSH.Playground.AppHost -- .NET 10, C# latest, nullable enabled -- SonarAnalyzer.CSharp with code style enforced in build -- API versioning in URL path (`/api/v1/...`) -- Mediator library (not MediatR) for commands/queries -- FluentValidation for request validation -- **Zero warnings policy**: After making any code changes, always verify the build produces no warnings. Run `dotnet build src/FSH.Framework.slnx` and ensure "0 Warning(s)" in output. Fix any warnings before considering work complete. +# Run API only (needs manual DB/Redis setup) +dotnet run --project src/Playground/Playground.Api +``` -## Blazor UI Components +## Adding a Feature (Step by Step) -The framework provides reusable Blazor components in `BuildingBlocks/Blazor.UI/Components/` with consistent styling. +1. **Create folder:** `Modules/{Module}/Features/v1/{FeatureName}/` -### FshPageHeader Component +2. **Add Command/Query:** + ```csharp + public sealed record {Name}Command(...) : ICommand<{Result}>; + ``` -Use `FshPageHeader` for consistent page headers across Playground.Blazor: +3. **Add Handler:** + ```csharp + public sealed class {Name}Handler(...) : ICommandHandler<{Name}Command, {Result}> + ``` -```razor -@using FSH.Framework.Blazor.UI.Components.Page +4. **Add Validator** (commands only): + ```csharp + public sealed class {Name}Validator : AbstractValidator<{Name}Command> + ``` - - - - Action - - -``` +5. **Add Endpoint:** + ```csharp + public static RouteHandlerBuilder Map{Name}Endpoint(this IEndpointRouteBuilder e) => ... + ``` -**Parameters:** -- `Title` (required): Main page title -- `Description` (optional): Description text below title -- `DescriptionContent` (optional): RenderFragment for complex descriptions -- `ActionContent` (optional): RenderFragment for action buttons on the right -- `TitleTypo` (optional): Typography style (default: Typo.h5) -- `Class` (optional): Additional CSS classes -- `PageTitleSuffix` (optional): Suffix for browser tab title - -**Features:** -- Modern card design with MudPaper Elevation="2" -- Subtle gradient background with primary color accent -- Left border accent in primary color -- Dark mode support - -### FshUserProfile Component - -Modern user profile dropdown for app bars/navbars with avatar, user info, and menu: - -```razor -@using FSH.Framework.Blazor.UI.Components.User - - -``` +6. **Wire in Module:** Add `group.Map{Name}Endpoint();` in `MapEndpoints()` -**Parameters:** -- `UserName` (required): User's display name -- `UserEmail` (optional): User's email address -- `UserRole` (optional): User's role or title -- `AvatarUrl` (optional): URL to user's avatar (shows initials if not provided) -- `ShowUserName` (optional): Show username next to avatar (default: true, hidden on mobile) -- `ShowUserInfo` (optional): Show user info in menu header (default: true) -- `MenuItems` (optional): Custom RenderFragment for menu items (uses default Profile/Settings/Logout if not provided) -- `OnProfileClick` (optional): Callback for Profile menu item -- `OnSettingsClick` (optional): Callback for Settings menu item -- `OnLogoutClick` (optional): Callback for Logout menu item - -**Features:** -- Responsive design (hides username on mobile) -- Avatar with initials fallback -- Smooth hover animations and transitions -- Gradient menu header with user info -- Customizable menu items via RenderFragment -- Scoped CSS for isolated styling - -### FshStatCard Component - -Statistics card for displaying metrics with icon, value, label, and optional badge: - -```razor -@using FSH.Framework.Blazor.UI.Components.Cards - - -``` +7. **Test:** `dotnet build && dotnet test` -**Parameters:** -- `Icon` (required): MudBlazor icon to display -- `IconColor` (optional): Color theme for icon and accent (default: Primary) -- `Value` (required): Main metric value to display -- `Label` (required): Description of the metric -- `Badge` (optional): Small badge text below label -- `BadgeColor` (optional): Color for the badge (default: Primary) - -**Features:** -- Hover animation with lift effect (`translateY(-4px)`) and enhanced shadow -- Uses MudCard with Elevation="2" for consistent Material Design styling -- Scoped CSS with `::deep` for proper Blazor CSS isolation -- Consistent structure matching the original stats-card pattern used throughout the app - -### FSH Design Tokens - -The framework uses CSS custom properties for consistent styling across all components. These are defined in `fsh-theme.css`: - -```css -:root { - /* Border Radius */ - --fsh-radius: 10px; - --fsh-radius-sm: 8px; - --fsh-radius-lg: 16px; - --fsh-radius-xl: 20px; - --fsh-radius-full: 9999px; - - /* Shadows */ - --fsh-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08); - --fsh-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08); - --fsh-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.1); - - /* Card Styling */ - --fsh-card-bg: #ffffff; - --fsh-card-border: rgba(0, 0, 0, 0.08); - --fsh-card-shadow: var(--fsh-shadow-md); - - /* Text Colors */ - --fsh-text-primary: #1a1a2e; - --fsh-text-secondary: #64748b; - - /* Transitions */ - --fsh-transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1); +## Configuration + +### Required (Production) +```json +{ + "DatabaseOptions": { "ConnectionString": "..." }, + "CachingOptions": { "Redis": "..." }, + "JwtOptions": { "SigningKey": "..." } } ``` -Dark mode overrides are automatically applied when `.mud-theme-dark` is present. Use these tokens in custom components to ensure consistent styling. +### The stack +- **.NET 10** with C# latest +- **PostgreSQL** (default) or SQL Server +- **Redis** for distributed caching +- **Hangfire** for background jobs +- **FluentValidation** for input validation +- **OpenTelemetry** for observability From b1263f91ed77692514a37b64ad3e84f0a2fd4002 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sun, 25 Jan 2026 13:20:51 +0000 Subject: [PATCH 2/3] chore: Remove Cursor and Copilot files, keep Claude only --- .cursorrules | 35 ----------------------- .github/copilot-instructions.md | 49 --------------------------------- 2 files changed, 84 deletions(-) delete mode 100644 .cursorrules delete mode 100644 .github/copilot-instructions.md diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 93e07a90a9..0000000000 --- a/.cursorrules +++ /dev/null @@ -1,35 +0,0 @@ -You are working on FullStackHero .NET Starter Kit - a modular monolith using vertical slice architecture. - -ARCHITECTURE: -- Features are self-contained in Modules/{Module}/Features/v1/{Feature}/ -- Each feature has: Command/Query, Handler, Validator, Endpoint -- Modules are in Modules/, framework in BuildingBlocks/, reference app in Playground/ -- DTOs go in Modules.{Name}.Contracts/ projects - -PATTERNS: -- Use Mediator library (NOT MediatR) - interfaces are ICommand, IQuery, ICommandHandler, IQueryHandler -- Handlers use primary constructors for DI -- Endpoints are static extension methods returning RouteHandlerBuilder -- Use .RequirePermission() for authorization -- Use [AsParameters] for query string binding -- All entities with tenant data implement IHasTenant (auto-filtered) - -RULES: -- Never return entities directly, always map to DTOs -- Every command needs an AbstractValidator -- Add .WithName() and .WithSummary() to all endpoints -- Build must pass with 0 warnings -- Don't modify BuildingBlocks without explicit approval - -COMMANDS: -- Build: dotnet build src/FSH.Framework.slnx -- Test: dotnet test src/FSH.Framework.slnx -- Run: dotnet run --project src/Playground/FSH.Playground.AppHost - -When adding a feature: -1. Create folder Modules/{Module}/Features/v1/{Name}/ -2. Add {Name}Command.cs or {Name}Query.cs -3. Add {Name}Handler.cs -4. Add {Name}Validator.cs (for commands) -5. Add {Name}Endpoint.cs -6. Wire in module's MapEndpoints() diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index f3c53d6fd6..0000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,49 +0,0 @@ -# Copilot Instructions - -This is FullStackHero .NET Starter Kit - a modular monolith with vertical slice architecture. - -## Key Patterns - -**Feature Structure:** -``` -Modules/{Module}/Features/v1/{Feature}/ -├── {Feature}Command.cs # ICommand -├── {Feature}Handler.cs # ICommandHandler -├── {Feature}Validator.cs # AbstractValidator -└── {Feature}Endpoint.cs # Static RouteHandlerBuilder extension -``` - -**Use Mediator (not MediatR):** -```csharp -// ✓ Correct -public sealed record CreateUserCommand(string Email) : ICommand; -public sealed class CreateUserHandler : ICommandHandler - -// ✗ Wrong -public class CreateUserCommand : IRequest // This is MediatR -``` - -**Endpoints:** -```csharp -public static RouteHandlerBuilder MapCreateUserEndpoint(this IEndpointRouteBuilder e) => - e.MapPost("/", async (CreateUserCommand cmd, IMediator m, CancellationToken ct) => - TypedResults.Ok(await m.Send(cmd, ct))) - .WithName("CreateUser") - .WithSummary("Creates a new user") - .RequirePermission(IdentityPermissions.Users.Create); -``` - -## Rules - -1. DTOs in `Modules.{Module}.Contracts/` - never return entities -2. Every command needs a FluentValidation validator -3. Use `[AsParameters]` for query parameters -4. Use `.RequirePermission()` for auth -5. Build with 0 warnings: `dotnet build src/FSH.Framework.slnx` - -## Project Structure - -- `BuildingBlocks/` - Framework (don't modify without approval) -- `Modules/` - Feature modules (Identity, Multitenancy, Auditing) -- `Playground/` - Reference application -- `Tests/` - Architecture and unit tests From 9b2191fee53ce174b070a5297b514da54c33bd85 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sun, 25 Jan 2026 13:24:04 +0000 Subject: [PATCH 3/3] feat: Complete AI-ready setup with rules, skills, and agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Structure: - CLAUDE.md → Entry point with quick reference - .claude/rules.md → 12 hard constraints with rationale - .claude/skills.md → Step-by-step guides for common tasks - .claude/agents.md → AI behavior guidelines and decision framework This enables AI assistants to: - Understand architectural philosophy - Follow patterns consistently - Make correct decisions about code placement - Catch common mistakes before they happen --- .claude/agents.md | 107 +++++++++++++++++++ .claude/rules.md | 76 ++++++++++++++ .claude/skills.md | 257 ++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 213 ++++++++++---------------------------- 4 files changed, 497 insertions(+), 156 deletions(-) create mode 100644 .claude/agents.md create mode 100644 .claude/rules.md create mode 100644 .claude/skills.md diff --git a/.claude/agents.md b/.claude/agents.md new file mode 100644 index 0000000000..112cc75bbc --- /dev/null +++ b/.claude/agents.md @@ -0,0 +1,107 @@ +# Agents + +How AI assistants should behave when working with this codebase. + +## Identity + +You are assisting with **FullStackHero .NET Starter Kit** — a production-ready, multi-tenant SaaS framework. + +Your role: Help developers build features following established patterns, not invent new ones. + +## Before You Start + +1. **Understand the request** — Is it a feature, fix, or question? +2. **Locate the context** — Which module? Which layer? +3. **Check existing patterns** — Find similar code to follow +4. **Verify constraints** — Review rules before implementing + +## Behavior + +### Do +- Follow existing patterns exactly — consistency over creativity +- Reference actual code in the repo as examples +- Ask clarifying questions before making assumptions +- Verify builds pass with 0 warnings after changes +- Keep changes minimal and focused + +### Don't +- Invent new architectural patterns +- Modify BuildingBlocks without explicit approval +- Skip validation on any command +- Return entities directly from endpoints +- Use MediatR patterns (this uses Mediator library) + +## Response Style + +### For "How do I..." questions +1. Point to existing similar code +2. Show the pattern with repo-specific examples +3. List the files they need to create/modify + +### For "Create a..." requests +1. Confirm the module and feature name +2. Generate all required files (Command, Handler, Validator, Endpoint) +3. Show where to wire it up +4. Include the build verification step + +### For "Fix..." requests +1. Understand the error/issue first +2. Check if it violates any rules +3. Propose minimal fix following existing patterns +4. Verify the fix doesn't break other things + +## Decision Framework + +### "Where does this go?" +``` +Is it a new API endpoint? + → Modules/{Module}/Features/v1/{Name}/ + +Is it a shared type for external use? + → Modules.{Module}.Contracts/ + +Is it a cross-cutting concern? + → BuildingBlocks/ (needs approval) + +Is it a new business domain? + → New Modules.{Name}/ project +``` + +### "Should I create a new module?" +``` +Does it have its own domain entities? → Yes = new module +Could it be deployed independently? → Yes = new module +Is it just a feature in existing domain? → No = existing module +``` + +### "Which pattern do I use?" +``` +Changing state? → Command + Handler + Validator + Endpoint +Reading data? → Query + Handler + Endpoint +Domain event? → Implement IDomainEvent, raise from entity +Background work? → Use Hangfire job +``` + +## Verification Checklist + +Before considering any task complete: + +- [ ] Code follows vertical slice structure +- [ ] DTOs are in Contracts project +- [ ] Command has a validator +- [ ] Endpoint has `.RequirePermission()` or `.AllowAnonymous()` +- [ ] Endpoint has `.WithName()` and `.WithSummary()` +- [ ] Using Mediator interfaces (not MediatR) +- [ ] `dotnet build src/FSH.Framework.slnx` shows 0 warnings +- [ ] No direct entity exposure in responses + +## Common Mistakes to Catch + +| If you see... | It's wrong because... | Fix | +|---------------|----------------------|-----| +| `IRequest` | That's MediatR | Use `ICommand` or `IQuery` | +| `IRequestHandler` | That's MediatR | Use `ICommandHandler` or `IQueryHandler` | +| Entity in response | Exposes internals | Create DTO in Contracts | +| No validator | Validation required | Add `AbstractValidator` | +| Manual tenant filter | Framework handles this | Implement `IHasTenant` | +| Code in BuildingBlocks | Affects all modules | Move to module or get approval | diff --git a/.claude/rules.md b/.claude/rules.md new file mode 100644 index 0000000000..fa4cb09115 --- /dev/null +++ b/.claude/rules.md @@ -0,0 +1,76 @@ +# Rules + +Hard constraints that must always be followed. + +## Architecture Rules + +1. **Vertical Slices Only** + - Every feature lives in `Modules/{Module}/Features/v1/{FeatureName}/` + - Never scatter feature code across multiple directories + - One folder = one complete feature + +2. **Module Boundaries** + - Modules only communicate through Contracts projects + - Never reference internal types across modules + - DTOs always in `Modules.{Name}.Contracts/` + +3. **BuildingBlocks Protection** + - Changes to `BuildingBlocks/` require explicit approval + - These affect ALL modules - changes ripple everywhere + - Prefer extension over modification + +## Code Rules + +4. **Mediator Not MediatR** + - Use `Mediator` source generator library + - Interfaces: `ICommand`, `IQuery`, `ICommandHandler`, `IQueryHandler` + - Never use `IRequest` or `IRequestHandler` (those are MediatR) + +5. **Validation Required** + - Every command must have an `AbstractValidator` + - No exceptions - validation is not optional + - Validators auto-register via FluentValidation + +6. **No Entity Exposure** + - Never return domain entities from endpoints + - Always map to DTOs from Contracts project + - Entities are internal implementation details + +7. **Explicit Authorization** + - Every endpoint needs `.RequirePermission()` or `.AllowAnonymous()` + - Use permission constants from `{Module}PermissionConstants` + - No implicit security + +8. **Zero Warnings** + - Build must pass with 0 warnings + - Run `dotnet build src/FSH.Framework.slnx` and verify + - CI enforces this - no exceptions + +## Naming Rules + +9. **Consistent Naming** + | Type | Pattern | + |------|---------| + | Commands | `{Action}{Entity}Command` | + | Queries | `Get{Entity}Query` or `Get{Entities}Query` | + | Handlers | `{CommandOrQuery}Handler` | + | Validators | `{Command}Validator` | + | Endpoints | `{CommandOrQuery}Endpoint` | + | DTOs | `{Entity}Dto`, `{Entity}Response`, `{Action}{Entity}Request` | + +10. **File = Type** + - One public type per file + - Filename matches type name exactly + - `CreateUserCommand.cs` contains `CreateUserCommand` + +## Multi-Tenancy Rules + +11. **Tenant Isolation** + - Entities with tenant data implement `IHasTenant` + - Framework auto-filters queries by tenant + - Never manually filter by TenantId in queries + +12. **No Hardcoded Tenants** + - Never hardcode tenant IDs + - Use `ICurrentUser.TenantId` for current tenant + - Tenant context comes from framework diff --git a/.claude/skills.md b/.claude/skills.md new file mode 100644 index 0000000000..20ec25126a --- /dev/null +++ b/.claude/skills.md @@ -0,0 +1,257 @@ +# Skills + +Step-by-step guides for common tasks. + +## Skill: Add a Feature + +**When:** Creating a new API endpoint with business logic. + +**Steps:** + +1. **Create the folder** + ``` + Modules/{Module}/Features/v1/{FeatureName}/ + ``` + +2. **Create Command or Query** + ```csharp + // For state changes + public sealed record CreateProductCommand( + string Name, + decimal Price) : ICommand; + + // For reads + public sealed record GetProductQuery(Guid Id) : IQuery; + ``` + +3. **Create Handler** + ```csharp + public sealed class CreateProductHandler( + IRepository repository, + ICurrentUser currentUser) : ICommandHandler + { + public async ValueTask Handle( + CreateProductCommand command, + CancellationToken ct) + { + var product = Product.Create(command.Name, command.Price, currentUser.TenantId); + await repository.AddAsync(product, ct); + return new CreateProductResponse(product.Id); + } + } + ``` + +4. **Create Validator** (commands only) + ```csharp + public sealed class CreateProductValidator : AbstractValidator + { + public CreateProductValidator() + { + RuleFor(x => x.Name).NotEmpty().MaximumLength(200); + RuleFor(x => x.Price).GreaterThan(0); + } + } + ``` + +5. **Create Endpoint** + ```csharp + public static class CreateProductEndpoint + { + public static RouteHandlerBuilder Map(this IEndpointRouteBuilder endpoints) => + endpoints.MapPost("/", async ( + CreateProductCommand command, + IMediator mediator, + CancellationToken ct) => TypedResults.Created( + $"/products/{(await mediator.Send(command, ct)).Id}")) + .WithName(nameof(CreateProductCommand)) + .WithSummary("Create a new product") + .RequirePermission(CatalogPermissions.Products.Create); + } + ``` + +6. **Wire in Module** + ```csharp + // In module's MapEndpoints method + var products = endpoints.MapGroup("/products").WithTags("Products"); + products.MapCreateProductEndpoint(); + ``` + +7. **Add DTO to Contracts** + ```csharp + // In Modules.{Module}.Contracts/ + public sealed record CreateProductResponse(Guid Id); + public sealed record ProductDto(Guid Id, string Name, decimal Price); + ``` + +8. **Verify** + ```bash + dotnet build src/FSH.Framework.slnx # Must show 0 warnings + dotnet test src/FSH.Framework.slnx + ``` + +--- + +## Skill: Add a Module + +**When:** Creating a new bounded context (new business domain). + +**Steps:** + +1. **Create projects** + ``` + src/Modules/{Name}/ + ├── Modules.{Name}/ + │ ├── Modules.{Name}.csproj + │ ├── {Name}Module.cs + │ ├── {Name}PermissionConstants.cs + │ └── Features/v1/ + └── Modules.{Name}.Contracts/ + └── Modules.{Name}.Contracts.csproj + ``` + +2. **Implement IModule** + ```csharp + public sealed class CatalogModule : IModule + { + public void ConfigureServices(IHostApplicationBuilder builder) + { + // Register services, DbContext, etc. + } + + public void MapEndpoints(IEndpointRouteBuilder endpoints) + { + var group = endpoints.MapGroup("/api/v1/catalog"); + // Map feature endpoints here + } + } + ``` + +3. **Add permission constants** + ```csharp + public static class CatalogPermissionConstants + { + public static class Products + { + public const string View = "Products.View"; + public const string Create = "Products.Create"; + public const string Update = "Products.Update"; + public const string Delete = "Products.Delete"; + } + } + ``` + +4. **Register in Program.cs** + ```csharp + var moduleAssemblies = new Assembly[] + { + typeof(IdentityModule).Assembly, + typeof(MultitenancyModule).Assembly, + typeof(AuditingModule).Assembly, + typeof(CatalogModule).Assembly, // Add new module + }; + ``` + +5. **Add Mediator assemblies** (if module has commands/queries) + ```csharp + builder.Services.AddMediator(o => + { + o.Assemblies = [ + // ... existing assemblies + typeof(CreateProductCommand).Assembly, + typeof(CreateProductHandler).Assembly, + ]; + }); + ``` + +--- + +## Skill: Add Entity with Multi-Tenancy + +**When:** Creating a domain entity that should be tenant-isolated. + +```csharp +public sealed class Product : AggregateRoot, IHasTenant, IAuditableEntity, ISoftDeletable +{ + public string Name { get; private set; } = null!; + public decimal Price { get; private set; } + + // IHasTenant + public string TenantId { get; private set; } = null!; + + // IAuditableEntity + public DateTimeOffset CreatedAt { get; set; } + public string? CreatedBy { get; set; } + public DateTimeOffset? LastModifiedAt { get; set; } + public string? LastModifiedBy { get; set; } + + // ISoftDeletable + public DateTimeOffset? DeletedAt { get; set; } + public string? DeletedBy { get; set; } + + private Product() { } // EF Core + + public static Product Create(string name, decimal price, string tenantId) + { + var product = new Product + { + Id = Guid.NewGuid(), + Name = name, + Price = price, + TenantId = tenantId + }; + product.AddDomainEvent(new ProductCreatedEvent(product.Id)); + return product; + } +} +``` + +--- + +## Skill: Query with Pagination + +```csharp +public sealed record GetProductsQuery( + string? Search, + int PageNumber = 1, + int PageSize = 10) : IQuery>; + +public sealed class GetProductsHandler( + IReadRepository repository) : IQueryHandler> +{ + public async ValueTask> Handle( + GetProductsQuery query, + CancellationToken ct) + { + var spec = new ProductSearchSpec(query.Search, query.PageNumber, query.PageSize); + return await repository.PaginatedListAsync(spec, ct); + } +} +``` + +--- + +## Skill: Run & Test + +```bash +# Build (must be 0 warnings) +dotnet build src/FSH.Framework.slnx + +# Run tests +dotnet test src/FSH.Framework.slnx + +# Run with Aspire (Postgres + Redis auto-provisioned) +dotnet run --project src/Playground/FSH.Playground.AppHost + +# Run API only (manual DB setup required) +dotnet run --project src/Playground/Playground.Api + +# Add migration +dotnet ef migrations add {Name} \ + --project src/Playground/Migrations.PostgreSQL \ + --startup-project src/Playground/Playground.Api + +# Apply migrations +dotnet ef database update \ + --project src/Playground/Migrations.PostgreSQL \ + --startup-project src/Playground/Playground.Api +``` diff --git a/CLAUDE.md b/CLAUDE.md index d8985ca29d..2102031df4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,115 +1,89 @@ # CLAUDE.md -> AI assistant guidelines for FullStackHero .NET Starter Kit +> FullStackHero .NET Starter Kit — AI Assistant Guidelines -## Philosophy +## Quick Reference -This is a **modular monolith** using **vertical slice architecture**. Every decision optimizes for: - -1. **Feature isolation** — Each feature is self-contained in one folder -2. **Module boundaries** — Modules are deployment-ready packages -3. **Explicit over implicit** — No magic, clear dependency flow -4. **Contract-first** — Public APIs via Contracts projects, internals stay internal +```bash +dotnet build src/FSH.Framework.slnx # Build (0 warnings required) +dotnet test src/FSH.Framework.slnx # Test +dotnet run --project src/Playground/FSH.Playground.AppHost # Run +``` -## Mental Model +## Project Structure ``` -┌─────────────────────────────────────────────────────────────┐ -│ Playground (Reference App) │ -│ └─ Wires modules together, runs the app │ -├─────────────────────────────────────────────────────────────┤ -│ Modules (Feature Packages) │ -│ ├─ Identity → Auth, users, roles, permissions │ -│ ├─ Multitenancy → Tenant isolation, per-tenant DBs │ -│ └─ Auditing → Audit trail, security logs │ -├─────────────────────────────────────────────────────────────┤ -│ BuildingBlocks (Framework) │ -│ └─ Core, Persistence, Web, Caching, Jobs, Events... │ -│ ⚠️ Changes here affect ALL modules │ -└─────────────────────────────────────────────────────────────┘ +src/ +├── BuildingBlocks/ # Framework core (⚠️ don't modify without approval) +├── Modules/ # Business modules — add features here +│ ├── Identity/ # Auth, users, roles, permissions +│ ├── Multitenancy/ # Tenant management +│ └── Auditing/ # Audit logging +├── Playground/ # Reference application +└── Tests/ # Architecture + unit tests ``` -## The Vertical Slice +## The Pattern -**One feature = one folder with everything it needs.** +Every feature = 4 files in one folder: ``` -Modules/Identity/Features/v1/CreateUser/ -├── CreateUserCommand.cs ← What (input) -├── CreateUserHandler.cs ← How (logic) -├── CreateUserValidator.cs ← Guard (validation) -└── CreateUserEndpoint.cs ← Where (HTTP binding) +Modules/{Module}/Features/v1/{Feature}/ +├── {Feature}Command.cs → ICommand +├── {Feature}Handler.cs → ICommandHandler +├── {Feature}Validator.cs → AbstractValidator +└── {Feature}Endpoint.cs → RouteHandlerBuilder extension ``` -**Why this works:** -- Change a feature? One folder to modify -- Delete a feature? Delete one folder -- Understand a feature? Read one folder -- Test a feature? Mock one handler - -## Decision Guide - -### "Where do I put this?" +## Essential Rules -| You're building... | Put it in... | -|-------------------|--------------| -| New API endpoint | `Modules/{Module}/Features/v1/{Feature}/` | -| Shared DTO/contract | `Modules.{Module}.Contracts/` | -| Cross-cutting concern | `BuildingBlocks/` (needs approval) | -| New bounded context | New `Modules.{Name}/` project | -| Database migration | `Playground/Migrations.PostgreSQL/` | +| Rule | Why | +|------|-----| +| Use `Mediator` not `MediatR` | Different library, different interfaces | +| DTOs in Contracts project | Keep internals internal | +| Every command needs validator | No unvalidated input | +| `.RequirePermission()` on endpoints | Explicit authorization | +| Zero build warnings | CI enforces this | -### "Should I create a new module?" +## Deep Dive -Ask: **Does this represent a separate bounded context?** -- Has its own domain entities? → New module -- Could be deployed independently? → New module -- Just a new feature in existing domain? → Add to existing module +| Topic | File | +|-------|------| +| All rules & constraints | [.claude/rules.md](.claude/rules.md) | +| Step-by-step guides | [.claude/skills.md](.claude/skills.md) | +| AI behavior guidelines | [.claude/agents.md](.claude/agents.md) | -## Code Patterns +--- -### Command/Query (CQRS) +## Quick Patterns +### Command + Handler ```csharp -// Command — changes state, returns result -public sealed record CreateUserCommand(string Email, string Name) : ICommand; +public sealed record CreateUserCommand(string Email, string Name) : ICommand; -// Query — reads state, no side effects -public sealed record GetUserQuery(Guid Id) : IQuery; -``` - -### Handler - -```csharp -public sealed class CreateUserHandler( - IRepository repo, - ICurrentUser currentUser) : ICommandHandler +public sealed class CreateUserHandler(IRepository repo) + : ICommandHandler { - public async ValueTask Handle(CreateUserCommand cmd, CancellationToken ct) + public async ValueTask Handle(CreateUserCommand cmd, CancellationToken ct) { - var user = User.Create(cmd.Email, cmd.Name, currentUser.TenantId); + var user = User.Create(cmd.Email, cmd.Name); await repo.AddAsync(user, ct); - return new UserCreatedResponse(user.Id); + return user.Id; } } ``` ### Endpoint - ```csharp -public static class CreateUserEndpoint -{ - public static RouteHandlerBuilder Map(this IEndpointRouteBuilder e) => - e.MapPost("/", async (CreateUserCommand cmd, IMediator m, CancellationToken ct) => - TypedResults.Created($"/users/{(await m.Send(cmd, ct)).Id}")) - .WithName(nameof(CreateUserCommand)) - .WithSummary("Create a new user") - .RequirePermission(IdentityPermissions.Users.Create); -} +public static RouteHandlerBuilder MapCreateUserEndpoint(this IEndpointRouteBuilder e) => + e.MapPost("/", async (CreateUserCommand cmd, IMediator m, CancellationToken ct) => + TypedResults.Created($"/users/{await m.Send(cmd, ct)}")) + .WithName("CreateUser") + .WithSummary("Create a new user") + .RequirePermission(IdentityPermissions.Users.Create); ``` ### Validator - ```csharp public sealed class CreateUserValidator : AbstractValidator { @@ -121,85 +95,12 @@ public sealed class CreateUserValidator : AbstractValidator } ``` -## Critical Rules - -| Rule | Rationale | -|------|-----------| -| Use `Mediator` not `MediatR` | Source-gen library, different interfaces (`ICommand`, `IQuery`) | -| Handlers use primary constructors | Clean DI, immutable dependencies | -| DTOs in Contracts project | Shareable with clients, no internal leakage | -| Entities implement `IHasTenant` | Auto-filtered by tenant, no manual filtering | -| Endpoints use `.RequirePermission()` | Centralized auth, auditable | -| Zero build warnings | CI enforces, no exceptions | - -## Multi-Tenancy - -Every request has a tenant context. The framework handles: -- **Tenant resolution** — From header, subdomain, or path -- **Data isolation** — Queries auto-filter by `TenantId` -- **Per-tenant DBs** — Optional, configurable per tenant - -**Your code:** Just implement `IHasTenant` on entities. Done. +--- -## Commands +## Configuration (Production) -```bash -# Build (must pass with 0 warnings) -dotnet build src/FSH.Framework.slnx - -# Test (architecture tests enforce patterns) -dotnet test src/FSH.Framework.slnx - -# Run with infrastructure (Postgres + Redis via Aspire) -dotnet run --project src/Playground/FSH.Playground.AppHost - -# Run API only (needs manual DB/Redis setup) -dotnet run --project src/Playground/Playground.Api ``` - -## Adding a Feature (Step by Step) - -1. **Create folder:** `Modules/{Module}/Features/v1/{FeatureName}/` - -2. **Add Command/Query:** - ```csharp - public sealed record {Name}Command(...) : ICommand<{Result}>; - ``` - -3. **Add Handler:** - ```csharp - public sealed class {Name}Handler(...) : ICommandHandler<{Name}Command, {Result}> - ``` - -4. **Add Validator** (commands only): - ```csharp - public sealed class {Name}Validator : AbstractValidator<{Name}Command> - ``` - -5. **Add Endpoint:** - ```csharp - public static RouteHandlerBuilder Map{Name}Endpoint(this IEndpointRouteBuilder e) => ... - ``` - -6. **Wire in Module:** Add `group.Map{Name}Endpoint();` in `MapEndpoints()` - -7. **Test:** `dotnet build && dotnet test` - -## Configuration - -### Required (Production) -```json -{ - "DatabaseOptions": { "ConnectionString": "..." }, - "CachingOptions": { "Redis": "..." }, - "JwtOptions": { "SigningKey": "..." } -} +DatabaseOptions:ConnectionString ← Required +CachingOptions:Redis ← Required +JwtOptions:SigningKey ← Required (256-bit) ``` - -### The stack -- **.NET 10** with C# latest -- **PostgreSQL** (default) or SQL Server -- **Redis** for distributed caching -- **Hangfire** for background jobs -- **FluentValidation** for input validation -- **OpenTelemetry** for observability