Adds new Domain Aggregates using Clean Architecture and DDD principles with full CRUD scaffolding across all layers
This skill guides you through creating a complete Domain Aggregate with full CRUD operations following Clean Architecture, Domain-Driven Design (DDD), and bITdevKit patterns. It scaffolds code across all five layers: Domain, Infrastructure, Application, Mapping, and Presentation.
What You'll Create:
Time Estimate: 30-60 minutes for complete aggregate with CRUD operations.
Result<T> for error handlingsrc/Modules/[Module]/[Module].[Layer]/This skill assumes:
src/Modules/[Module]/
├── [Module].Domain/
│ ├── Model/
│ │ └── [Entity]Aggregate/
│ └── Events/
├── [Module].Application/
│ ├── Commands/
│ └── Queries/
├── [Module].Infrastructure/
│ └── EntityFramework/
│ └── Configurations/
├── [Module].Presentation/
│ ├── Web/
│ │ └── Endpoints/
│ └── [Module]MapperRegister.cs
Result<T>: No public constructors on aggregates[TypedEntityId<Guid>] attributeUse this skill when:
DO NOT use this skill when:
Answer these questions:
Entity Name: What is the aggregate called? (e.g., Product, Order, Invoice) Properties: What data does it hold? (Name, Description, Price, Status, etc.) Value Objects: Any complex types? (Email, Address, Money, etc.) Enumerations: Any status/type fields? (OrderStatus, ProductCategory, etc.) Business Rules: What validations? (Price > 0, Email format, Name required, etc.) Lifecycle Operations: What changes occur? (Create, Update status, Change price, etc.)
Example:
Entity: Product
Properties: Name (string), Description (string), Price (decimal), SKU (string), Status (enum)
Value Objects: Money (for Price)
Enumerations: ProductStatus (Draft, Active, Retired)
Business Rules: Name required, Price > 0, SKU unique
Operations: Create, ChangeName, ChangeDescription, ChangePrice, ChangeStatus
Based on requirements, create checklist:
[ ] Domain Layer
[ ] Create Product aggregate
[ ] Create Money value object (if needed)
[ ] Create ProductStatus enumeration
[ ] Create ProductCreatedDomainEvent
[ ] Create ProductUpdatedDomainEvent
[ ] Create ProductDeletedDomainEvent
[ ] Infrastructure Layer
[ ] Create ProductTypeConfiguration (EF Core)
[ ] Add DbSet<Product> to ModuleDbContext
[ ] Generate and apply migration
[ ] Application Layer
[ ] Create ProductCreateCommand + Handler
[ ] Create ProductUpdateCommand + Handler
[ ] Create ProductDeleteCommand + Handler
[ ] Create ProductFindOneQuery + Handler
[ ] Create ProductFindAllQuery + Handler
[ ] Mapping Layer
[ ] Create ProductModel DTO
[ ] Configure Product → ProductModel mapping
[ ] Configure value object mappings
[ ] Presentation Layer
[ ] Create ProductEndpoints (POST, GET, PUT, DELETE)
[ ] Register endpoints in module
[ ] Validation
[ ] Run build and fix errors
[ ] Test endpoints via Swagger
File: src/Modules/[Module]/[Module].Domain/Model/[Entity]Aggregate/[Entity].cs
Use template: [templates/aggregate-template.cs]
Key Patterns:
AuditableAggregateRoot<[Entity]Id>[TypedEntityId<Guid>] attributeCreate() returning Result<[Entity]>this.Change() builderExample:
[TypedEntityId<Guid>]
public class Product : AuditableAggregateRoot<ProductId>, IConcurrency
{
private Product() { }
private Product(string name, string description, decimal price, string sku)
{
this.Name = name;
this.Description = description;
this.Price = price;
this.SKU = sku;
}
public string Name { get; private set; }
public string Description { get; private set; }
public decimal Price { get; private set; }
public string SKU { get; private set; }
public ProductStatus Status { get; private set; } = ProductStatus.Draft;
public Guid ConcurrencyVersion { get; set; }
public static Result<Product> Create(string name, string description, decimal price, string sku)
{
return Result<Product>.Success()
.Ensure(_ => !string.IsNullOrWhiteSpace(name), new ValidationError("Name is required"))
.Ensure(_ => price > 0, new ValidationError("Price must be greater than zero"))
.Ensure(_ => !string.IsNullOrWhiteSpace(sku), new ValidationError("SKU is required"))
.Bind(_ => new Product(name, description, price, sku))
.Tap(e => e.DomainEvents
.Register(new ProductCreatedDomainEvent(e))
.Register(new EntityCreatedDomainEvent<Product>(e)));
}
public Result<Product> ChangeName(string name)
{
return this.Change()
.Ensure(_ => !string.IsNullOrWhiteSpace(name), "Name is required")
.Set(e => e.Name, name)
.Register(e => new ProductUpdatedDomainEvent(e))
.Apply();
}
public Result<Product> ChangePrice(decimal price)
{
return this.Change()
.Ensure(_ => price > 0, "Price must be greater than zero")
.Set(e => e.Price, price)
.Register(e => new ProductUpdatedDomainEvent(e))
.Apply();
}
public Result<Product> ChangeStatus(ProductStatus status)
{
return this.Change()
.When(_ => status != null)
.Set(e => e.Status, status)
.Register(e => new ProductUpdatedDomainEvent(e))
.Apply();
}
}
File: src/Modules/[Module]/[Module].Domain/Model/[ValueObject].cs
Use template: [templates/value-object-template.cs]
Example (EmailAddress-like pattern):
public class Money : ValueObject
{
private Money() { }
private Money(decimal amount, string currency)
{
this.Amount = amount;
this.Currency = currency;
}
public decimal Amount { get; private set; }
public string Currency { get; private set; }
public static Result<Money> Create(decimal amount, string currency)
{
return Result<Money>.Success()
.Ensure(_ => amount >= 0, new ValidationError("Amount cannot be negative"))
.Ensure(_ => !string.IsNullOrWhiteSpace(currency), new ValidationError("Currency is required"))
.Bind(_ => new Money(amount, currency));
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return this.Amount;
yield return this.Currency;
}
}
When to skip: If no complex types needed, skip this step.
File: src/Modules/[Module]/[Module].Domain/Model/[Entity]Aggregate/[Entity]Status.cs
Use template: [templates/enumeration-template.cs]
Example:
public partial class ProductStatus : Enumeration
{
public static readonly ProductStatus Draft = new(1, nameof(Draft), true, "Product is in draft");
public static readonly ProductStatus Active = new(2, nameof(Active), true, "Product is active");
public static readonly ProductStatus Retired = new(3, nameof(Retired), true, "Product is retired");
public bool Enabled { get; }
public string Description { get; }
}
When to skip: If no status/type field needed, skip this step.
Files:
src/Modules/[Module]/[Module].Domain/Events/[Entity]CreatedDomainEvent.cssrc/Modules/[Module]/[Module].Domain/Events/[Entity]UpdatedDomainEvent.cssrc/Modules/[Module]/[Module].Domain/Events/[Entity]DeletedDomainEvent.csUse template: [templates/domain-events-template.cs]
Example:
public partial class ProductCreatedDomainEvent(Product model) : DomainEventBase
{
public Product Model { get; private set; } = model;
}
public partial class ProductUpdatedDomainEvent(Product model) : DomainEventBase
{
public Product Model { get; private set; } = model;
}
public partial class ProductDeletedDomainEvent(ProductId id) : DomainEventBase
{
public ProductId Id { get; private set; } = id;
}
dotnet build src/Modules/[Module]/[Module].Domain/
Expected: Build succeeds with no errors.
If errors occur:
Validate using checklist: [checklists/01-domain-layer.md]
File: src/Modules/[Module]/[Module].Infrastructure/EntityFramework/Configurations/[Entity]TypeConfiguration.cs
Use template: [templates/ef-configuration-template.cs]
Key Patterns:
IEntityTypeConfiguration<[Entity]>builder.ToTable("[Entities]")builder.HasKey(e => e.Id)builder.Property(e => e.Id).ValueGeneratedOnAdd().HasConversion(...)builder.OwnsOne(e => e.Money, ...) or builder.Property(e => e.Email).HasConversion(...)builder.Property(e => e.Status).HasConversion(...)builder.Ignore(e => e.DomainEvents)builder.Property(e => e.ConcurrencyVersion).IsConcurrencyToken()Example:
public class ProductTypeConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasConversion(
id => id.Value,
value => ProductId.Create(value));
builder.Property(e => e.Name).IsRequired().HasMaxLength(256);
builder.Property(e => e.Description).HasMaxLength(1024);
builder.Property(e => e.Price).HasColumnType("decimal(18,2)");
builder.Property(e => e.SKU).IsRequired().HasMaxLength(50);
builder.Property(e => e.Status)
.HasConversion(
s => s.Id,
id => Enumeration.FromId<ProductStatus>(id));
builder.Property(e => e.ConcurrencyVersion).IsConcurrencyToken();
builder.Ignore(e => e.DomainEvents);
}
}
File: src/Modules/[Module]/[Module].Infrastructure/EntityFramework/[Module]DbContext.cs
Add DbSet property:
public DbSet<Product> Products => this.Set<Product>();
# From solution root
dotnet ef migrations add Add[Entity] --project src/Modules/[Module]/[Module].Infrastructure --startup-project src/Presentation.Web.Server --context [Module]DbContext
Example:
dotnet ef migrations add AddProduct --project src/Modules/CoreModule/CoreModule.Infrastructure --startup-project src/Presentation.Web.Server --context CoreModuleDbContext
Review migration file: Verify it creates correct table and columns.
dotnet build src/Modules/[Module]/[Module].Infrastructure/
Expected: Build succeeds with no errors.
Validate using checklist: [checklists/02-infrastructure-layer.md]
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]CreateCommand.cs
Use template: [templates/command-create-template.cs]
Example:
public class ProductCreateCommand : IRequest<Result<ProductModel>>
{
public string Name { get; init; }
public string Description { get; init; }
public decimal Price { get; init; }
public string SKU { get; init; }
}
public class ProductCreateCommandValidator : AbstractValidator<ProductCreateCommand>
{
public ProductCreateCommandValidator()
{
this.RuleFor(c => c.Name).NotEmpty().MaximumLength(256);
this.RuleFor(c => c.Price).GreaterThan(0);
this.RuleFor(c => c.SKU).NotEmpty().MaximumLength(50);
}
}
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]CreateCommandHandler.cs
Use template: [templates/command-create-handler-template.cs]
Key Patterns:
RequestHandlerBase<TRequest, TResponse>IGenericRepository<[Entity]> and IMapper[Retry] and [Timeout] attributesResult<T> pattern throughout[Entity].Create(...)await repository.InsertAsync(...)mapper.Map<[Entity], [Entity]Model>(...)Example:
[Retry(2)]
[Timeout(30)]
public class ProductCreateCommandHandler(IGenericRepository<Product> repository, IMapper mapper)
: RequestHandlerBase<ProductCreateCommand, Result<ProductModel>>
{
public override async Task<Result<ProductModel>> Process(
ProductCreateCommand request,
CancellationToken cancellationToken)
{
var productResult = Product.Create(
request.Name,
request.Description,
request.Price,
request.SKU);
if (productResult.IsFailure)
{
return Result<ProductModel>.Failure().WithMessages(productResult.Messages);
}
var product = await repository.InsertAsync(productResult.Value, cancellationToken);
return mapper.Map<Product, ProductModel>(product);
}
}
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]UpdateCommand.cs
Use template: [templates/command-update-template.cs]
Example:
public class ProductUpdateCommand : IRequest<Result<ProductModel>>
{
public Guid Id { get; init; }
public string Name { get; init; }
public string Description { get; init; }
public decimal Price { get; init; }
}
public class ProductUpdateCommandValidator : AbstractValidator<ProductUpdateCommand>
{
public ProductUpdateCommandValidator()
{
this.RuleFor(c => c.Id).NotEmpty();
this.RuleFor(c => c.Name).NotEmpty().MaximumLength(256);
this.RuleFor(c => c.Price).GreaterThan(0);
}
}
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]UpdateCommandHandler.cs
Use template: [templates/command-update-handler-template.cs]
Key Patterns:
await repository.FindOneAsync(id, ...)Result.Failure() if not foundentity.ChangeName(...).ChangePrice(...)await repository.UpdateAsync(...)Example:
[Retry(2)]
[Timeout(30)]
public class ProductUpdateCommandHandler(IGenericRepository<Product> repository, IMapper mapper)
: RequestHandlerBase<ProductUpdateCommand, Result<ProductModel>>
{
public override async Task<Result<ProductModel>> Process(
ProductUpdateCommand request,
CancellationToken cancellationToken)
{
var product = await repository.FindOneAsync(
ProductId.Create(request.Id),
cancellationToken: cancellationToken);
if (product == null)
{
return Result<ProductModel>.Failure($"Product with ID {request.Id} not found");
}
var nameResult = product.ChangeName(request.Name);
if (nameResult.IsFailure)
{
return Result<ProductModel>.Failure().WithMessages(nameResult.Messages);
}
var priceResult = product.ChangePrice(request.Price);
if (priceResult.IsFailure)
{
return Result<ProductModel>.Failure().WithMessages(priceResult.Messages);
}
await repository.UpdateAsync(product, cancellationToken);
return mapper.Map<Product, ProductModel>(product);
}
}
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]DeleteCommand.cs
Use template: [templates/command-delete-template.cs]
Example:
public class ProductDeleteCommand : IRequest<Result>
{
public Guid Id { get; init; }
}
public class ProductDeleteCommandValidator : AbstractValidator<ProductDeleteCommand>
{
public ProductDeleteCommandValidator()
{
this.RuleFor(c => c.Id).NotEmpty();
}
}
File: src/Modules/[Module]/[Module].Application/Commands/[Entity]DeleteCommandHandler.cs
Use template: [templates/command-delete-handler-template.cs]
Example:
[Retry(2)]
[Timeout(30)]
public class ProductDeleteCommandHandler(IGenericRepository<Product> repository)
: RequestHandlerBase<ProductDeleteCommand, Result>
{
public override async Task<Result> Process(
ProductDeleteCommand request,
CancellationToken cancellationToken)
{
var product = await repository.FindOneAsync(
ProductId.Create(request.Id),
cancellationToken: cancellationToken);
if (product == null)
{
return Result.Failure($"Product with ID {request.Id} not found");
}
product.DomainEvents.Register(new ProductDeletedDomainEvent(product.Id));
await repository.DeleteAsync(product, cancellationToken);
return Result.Success();
}
}
File: src/Modules/[Module]/[Module].Application/Queries/[Entity]FindOneQuery.cs
Use template: [templates/query-findone-template.cs]
Example:
public class ProductFindOneQuery : IRequest<Result<ProductModel>>
{
public Guid Id { get; init; }
}
public class ProductFindOneQueryValidator : AbstractValidator<ProductFindOneQuery>
{
public ProductFindOneQueryValidator()
{
this.RuleFor(q => q.Id).NotEmpty();
}
}
File: src/Modules/[Module]/[Module].Application/Queries/[Entity]FindOneQueryHandler.cs
Use template: [templates/query-findone-handler-template.cs]
Example:
[Timeout(30)]
public class ProductFindOneQueryHandler(IGenericRepository<Product> repository, IMapper mapper)
: RequestHandlerBase<ProductFindOneQuery, Result<ProductModel>>
{
public override async Task<Result<ProductModel>> Process(
ProductFindOneQuery request,
CancellationToken cancellationToken)
{
var product = await repository.FindOneAsync(
ProductId.Create(request.Id),
cancellationToken: cancellationToken);
if (product == null)
{
return Result<ProductModel>.Failure($"Product with ID {request.Id} not found");
}
return mapper.Map<Product, ProductModel>(product);
}
}
File: src/Modules/[Module]/[Module].Application/Queries/[Entity]FindAllQuery.cs
Use template: [templates/query-findall-template.cs]
Example:
public class ProductFindAllQuery : IRequest<Result<IEnumerable<ProductModel>>>
{
// Optional: Add filtering/sorting parameters
}
public class ProductFindAllQueryValidator : AbstractValidator<ProductFindAllQuery>
{
public ProductFindAllQueryValidator()
{
// No validation needed for basic find all
}
}
File: src/Modules/[Module]/[Module].Application/Queries/[Entity]FindAllQueryHandler.cs
Use template: [templates/query-findall-handler-template.cs]
Example:
[Timeout(30)]
public class ProductFindAllQueryHandler(IGenericRepository<Product> repository, IMapper mapper)
: RequestHandlerBase<ProductFindAllQuery, Result<IEnumerable<ProductModel>>>
{
public override async Task<Result<IEnumerable<ProductModel>>> Process(
ProductFindAllQuery request,
CancellationToken cancellationToken)
{
var products = await repository.FindAllAsync(cancellationToken: cancellationToken);
return Result<IEnumerable<ProductModel>>.Success(
products.Select(p => mapper.Map<Product, ProductModel>(p)));
}
}
dotnet build src/Modules/[Module]/[Module].Application/
Expected: Build succeeds with no errors.
Validate using checklist: [checklists/03-application-layer.md]
File: src/Modules/[Module]/[Module].Presentation/[Entity]Model.cs
Use template: [templates/model-template.cs]
Example:
public class ProductModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string SKU { get; set; }
public string Status { get; set; }
public DateTime CreatedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
File: src/Modules/[Module]/[Module].Presentation/[Module]MapperRegister.cs
Use template: [templates/mapper-register-template.cs]
Add mapping configurations:
public class CoreModuleMapperRegister : IRegister
{
public void Register(TypeAdapterConfig config)
{
// Existing mappings...
// Product mappings
config.ForType<Product, ProductModel>()
.Map(dest => dest.Id, src => src.Id.Value)
.Map(dest => dest.Status, src => src.Status.Name);
// Value object conversions (if applicable)
config.NewConfig<Money, decimal>()
.MapWith(src => src.Amount);
}
}
dotnet build src/Modules/[Module]/[Module].Presentation/
Expected: Build succeeds with no errors.
Validate using checklist: [checklists/04-mapping-layer.md]
File: src/Modules/[Module]/[Module].Presentation/Web/Endpoints/[Entity]Endpoints.cs
Use template: [templates/endpoint-template.cs]
Example:
public static class ProductEndpoints
{
public static IEndpointRouteBuilder MapProductEndpoints(this IEndpointRouteBuilder app)
{
app.MapPost("api/core/products", CreateProduct)
.WithName("CreateProduct")
.WithTags("Products")
.Produces<ProductModel>(StatusCodes.Status201Created)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
app.MapGet("api/core/products/{id:guid}", GetProduct)
.WithName("GetProduct")
.WithTags("Products")
.Produces<ProductModel>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
app.MapGet("api/core/products", GetAllProducts)
.WithName("GetAllProducts")
.WithTags("Products")
.Produces<IEnumerable<ProductModel>>(StatusCodes.Status200OK);
app.MapPut("api/core/products/{id:guid}", UpdateProduct)
.WithName("UpdateProduct")
.WithTags("Products")
.Produces<ProductModel>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
app.MapDelete("api/core/products/{id:guid}", DeleteProduct)
.WithName("DeleteProduct")
.WithTags("Products")
.Produces(StatusCodes.Status204NoContent)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
return app;
}
private static async Task<IResult> CreateProduct(
[FromServices] IRequester requester,
[FromBody] ProductCreateCommand command)
{
var result = await requester.RequestAsync(command);
return result.IsSuccess
? Results.Created($"/api/core/products/{result.Value.Id}", result.Value)
: Results.BadRequest(result.ToProblemDetails());
}
private static async Task<IResult> GetProduct(
[FromServices] IRequester requester,
Guid id)
{
var result = await requester.RequestAsync(new ProductFindOneQuery { Id = id });
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound(result.ToProblemDetails());
}
private static async Task<IResult> GetAllProducts(
[FromServices] IRequester requester)
{
var result = await requester.RequestAsync(new ProductFindAllQuery());
return result.IsSuccess
? Results.Ok(result.Value)
: Results.BadRequest(result.ToProblemDetails());
}
private static async Task<IResult> UpdateProduct(
[FromServices] IRequester requester,
Guid id,
[FromBody] ProductUpdateCommand command)
{
var commandWithId = command with { Id = id };
var result = await requester.RequestAsync(commandWithId);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound(result.ToProblemDetails());
}
private static async Task<IResult> DeleteProduct(
[FromServices] IRequester requester,
Guid id)
{
var result = await requester.RequestAsync(new ProductDeleteCommand { Id = id });
return result.IsSuccess
? Results.NoContent()
: Results.NotFound(result.ToProblemDetails());
}
}
File: src/Modules/[Module]/[Module].Presentation/[Module]Module.cs
Add endpoint registration:
app.MapProductEndpoints();
dotnet build src/Modules/[Module]/[Module].Presentation/
Expected: Build succeeds with no errors.
Validate using checklist: [checklists/05-presentation-layer.md]
dotnet build
Expected: Build succeeds with no errors.
If errors occur:
dotnet build --verbosity detailed for more informationdotnet ef database update --project src/Modules/[Module]/[Module].Infrastructure --startup-project src/Presentation.Web.Server --context [Module]DbContext
Expected: Database updated with new table.
dotnet run --project src/Presentation.Web.Server
Navigate to: https://localhost:5001/swagger
Test Endpoints:
Validate using checklist: [checklists/quality-gates.md]
Congratulations! You've successfully created a complete Domain Aggregate with full CRUD operations across all five layers.
Created Files Summary (for Product example):
Domain Layer (7 files):
- Product.cs (aggregate)
- Money.cs (value object, if created)
- ProductStatus.cs (enumeration)
- ProductCreatedDomainEvent.cs
- ProductUpdatedDomainEvent.cs
- ProductDeletedDomainEvent.cs
Infrastructure Layer (2 files):
- ProductTypeConfiguration.cs
- Add[Entity] migration
Application Layer (10 files):
- ProductCreateCommand.cs + ProductCreateCommandHandler.cs
- ProductUpdateCommand.cs + ProductUpdateCommandHandler.cs
- ProductDeleteCommand.cs + ProductDeleteCommandHandler.cs
- ProductFindOneQuery.cs + ProductFindOneQueryHandler.cs
- ProductFindAllQuery.cs + ProductFindAllQueryHandler.cs
Presentation Layer (2 files):
- ProductModel.cs (DTO)
- ProductEndpoints.cs
Total: ~21 files
User: "Add a Product aggregate with Name, Description, Price, and SKU properties"
Actions:
Result: Complete Product aggregate with CRUD operations (~20 files)
User: "Add a Customer aggregate with FirstName, LastName, Email (value object), and Status"
Actions:
Result: Complete Customer aggregate with value object (~22 files)
Before considering aggregate complete:
WRONG:
CORRECT:
[Entity].cs (e.g., Product.cs)[Name].cs (e.g., EmailAddress.cs)[Entity][Type].cs (e.g., ProductStatus.cs)[Entity][Action]DomainEvent.cs (e.g., ProductCreatedDomainEvent.cs)[Entity]TypeConfiguration.cs (e.g., ProductTypeConfiguration.cs)[Entity][Action]Command.cs (e.g., ProductCreateCommand.cs)[Entity][Action]CommandHandler.cs (e.g., ProductCreateCommandHandler.cs)[Entity][Action]Query.cs (e.g., ProductFindOneQuery.cs)[Entity][Action]QueryHandler.cs (e.g., ProductFindOneQueryHandler.cs)[Entity]Model.cs (e.g., ProductModel.cs)[Entity]Endpoints.cs (e.g., ProductEndpoints.cs)AGENTS.mdsrc/Modules/CoreModule/CoreModule-README.mddocs/ADR/ (especially ADR-0001, ADR-0003, ADR-0005, ADR-0012).github/skills/domain-add-aggregate/templates/ for all code templates.github/skills/domain-add-aggregate/examples/ for complete walkthroughs.github/skills/domain-add-aggregate/checklists/ for layer-specific validation