Skip to content

Commit ec1d8b5

Browse files
xenobiasoftclaude
andcommitted
Add Claude Code rules for architecture and coding standards
Adds nine rule files under .claude/rules/ covering C# standards, domain layer, application layer, infrastructure layer, API design, Blazor components, React frontend, testing patterns, and error handling — giving Claude Code per-topic guidance aligned with the project's Clean Architecture and DDD conventions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e2d3dc7 commit ec1d8b5

9 files changed

Lines changed: 457 additions & 0 deletions

.claude/rules/api-design.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
paths:
3+
- "src/backend/Sudoku.Api/**/*.cs"
4+
---
5+
6+
# API Design Guidelines
7+
8+
## CQRS Command vs Query Separation
9+
10+
**Command endpoints** (`POST`, `PUT`, `PATCH`, `DELETE`) mutate state and **must not return domain data**:
11+
- `201 Created` with a `Location` header for creates
12+
- `204 No Content` for updates and deletes
13+
14+
**Query endpoints** (`GET`) read state and return data.
15+
16+
If a client needs the updated resource after a command, it issues a separate GET. Mixing the two breaks CQRS separation.
17+
18+
## REST Controller Examples
19+
20+
```csharp
21+
// COMMAND endpoint — mutates state, returns no data
22+
[HttpPost("{alias}/games/{difficulty}")]
23+
[ProducesResponseType(StatusCodes.Status201Created)]
24+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
25+
public async Task<ActionResult> CreateGame(string alias, string difficulty)
26+
{
27+
var result = await _gameService.CreateGameAsync(alias, difficulty);
28+
if (!result.IsSuccess)
29+
return BadRequest(result.Error);
30+
31+
return Created($"/api/players/{alias}/games/{result.Value.Id}", null);
32+
}
33+
34+
// QUERY endpoint — reads state, returns data
35+
[HttpGet("{alias}/games/{gameId}")]
36+
[ProducesResponseType(typeof(GameDto), StatusCodes.Status200OK)]
37+
[ProducesResponseType(StatusCodes.Status404NotFound)]
38+
public async Task<ActionResult<GameDto>> GetGame(string alias, string gameId)
39+
{
40+
var result = await _gameService.GetGameAsync(gameId);
41+
if (!result.IsSuccess)
42+
return NotFound();
43+
44+
return Ok(result.Value);
45+
}
46+
```
47+
48+
## Rules
49+
- Validate all user inputs at the controller boundary
50+
- Use `Result<T>` from the application layer; map to HTTP status codes in the controller
51+
- Include XML documentation for public API endpoints
52+
- Never return domain data from command endpoints

.claude/rules/application-layer.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
paths:
3+
- "src/backend/Sudoku.Application/**/*.cs"
4+
---
5+
6+
# Application Layer Guidelines
7+
8+
## CQRS Pattern
9+
```csharp
10+
public record CreateGameCommand(string PlayerAlias, GameDifficulty Difficulty);
11+
public record GetGameQuery(GameId GameId);
12+
```
13+
14+
## Command/Query Handlers
15+
```csharp
16+
public class CreateGameCommandHandler : ICommandHandler<CreateGameCommand>
17+
{
18+
private readonly IGameRepository _gameRepository;
19+
private readonly IDomainEventDispatcher _eventDispatcher;
20+
21+
public async Task<Result> HandleAsync(CreateGameCommand command)
22+
{
23+
// 1. Application logic / validation
24+
// 2. Domain interaction
25+
// 3. Persistence
26+
// 4. Dispatch domain events
27+
}
28+
}
29+
```
30+
31+
## Rules
32+
- All operations flow through `IMediator`
33+
- Handlers always return `Result<T>` — never throw exceptions for expected failures
34+
- Repository interfaces are defined here; implementations live in Infrastructure
35+
- No business logic in handlers — delegate to domain aggregates

.claude/rules/blazor-components.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
paths:
3+
- "src/frontend/Sudoku.Blazor/**/*.razor"
4+
- "src/frontend/Sudoku.Blazor/**/*.cs"
5+
---
6+
7+
# Blazor Component Guidelines
8+
9+
## Component Structure
10+
```csharp
11+
@page "/game/{GameId}"
12+
@using Sudoku.Application
13+
@inject IGameApplicationService GameService
14+
15+
<PageTitle>Sudoku Game</PageTitle>
16+
17+
<div class="sudoku-board">
18+
@if (game != null)
19+
{
20+
<SudokuBoard Game="@game" OnMoveMade="HandleMoveMade" />
21+
}
22+
</div>
23+
24+
@code {
25+
[Parameter] public string GameId { get; set; } = string.Empty;
26+
private GameDto? game;
27+
28+
protected override async Task OnInitializedAsync()
29+
{
30+
var result = await GameService.GetGameAsync(GameId);
31+
if (result.IsSuccess)
32+
game = result.Value;
33+
}
34+
35+
private async Task HandleMoveMade(int row, int column, int value)
36+
{
37+
// Handle move logic
38+
}
39+
}
40+
```
41+
42+
## Rules
43+
- Use `@inject` for dependency injection in components
44+
- Handle `Result<T>` from application services — don't assume success
45+
- Use `async/await` for all service calls
46+
- Blazor components are tested with bunit

.claude/rules/csharp-standards.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# C# Coding Standards & Conventions
2+
3+
## Naming Conventions
4+
- PascalCase for public members, classes, and methods
5+
- camelCase for private fields and local variables
6+
- UPPER_CASE for constants
7+
- Prefix private fields with underscore: `_fieldName`
8+
9+
## File Organization
10+
- One public class per file
11+
- File name should match class name
12+
- Group related classes in appropriate namespaces
13+
14+
## Code Style
15+
- Use expression-bodied members when appropriate
16+
- Prefer `var` for local variables when type is obvious
17+
- Use `readonly` for immutable fields
18+
- Use primary constructors when possible
19+
- Always use curly braces for code blocks
20+
21+
## Dependency Injection
22+
Register services via extension methods:
23+
24+
```csharp
25+
public static class ServiceCollectionExtensions
26+
{
27+
public static IServiceCollection AddSudokuServices(this IServiceCollection services)
28+
{
29+
services.AddScoped<IGameRepository, CosmosDbGameRepository>();
30+
services.AddScoped<ICommandHandler<CreateGameCommand>, CreateGameCommandHandler>();
31+
services.AddScoped<IGameApplicationService, GameApplicationService>();
32+
return services;
33+
}
34+
}
35+
```
36+
37+
## Anti-Patterns to Avoid
38+
- **Anemic Domain Models**: don't create entities that are just data containers
39+
- **Tight Coupling**: avoid direct dependencies between layers
40+
- **God Objects**: don't create classes with too many responsibilities
41+
- **Primitive Obsession**: use value objects instead of primitives for domain concepts
42+
- **Magic Numbers**: use constants or enums
43+
- **Long Methods**: keep methods focused and under 20 lines when possible
44+
- **Deep Nesting**: avoid deeply nested conditionals and loops

.claude/rules/domain-layer.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
paths:
3+
- "src/backend/Sudoku.Domain/**/*.cs"
4+
---
5+
6+
# Domain Layer Guidelines
7+
8+
## Entity Structure
9+
```csharp
10+
public class SudokuGame : AggregateRoot
11+
{
12+
private readonly List<Cell> _cells;
13+
private readonly List<DomainEvent> _domainEvents;
14+
15+
public GameId Id { get; private set; }
16+
public PlayerAlias PlayerAlias { get; private set; }
17+
public GameDifficulty Difficulty { get; private set; }
18+
public GameStatus Status { get; private set; }
19+
20+
private SudokuGame() { } // Private constructor for EF Core
21+
22+
public static SudokuGame Create(PlayerAlias playerAlias, GameDifficulty difficulty)
23+
{
24+
// Validation and creation logic
25+
}
26+
27+
public void MakeMove(int row, int column, int value)
28+
{
29+
// Business validation → state changes → raise domain event
30+
}
31+
}
32+
```
33+
34+
## Value Objects
35+
```csharp
36+
public record GameId(Guid Value)
37+
{
38+
public static GameId New() => new(Guid.NewGuid());
39+
public static GameId FromString(string value) => new(Guid.Parse(value));
40+
}
41+
```
42+
43+
## Domain Events
44+
```csharp
45+
public record GameCreatedEvent(GameId GameId, PlayerAlias PlayerAlias, GameDifficulty Difficulty) : DomainEvent;
46+
public record MoveMadeEvent(GameId GameId, int Row, int Column, int Value) : DomainEvent;
47+
```
48+
49+
## Specifications
50+
```csharp
51+
public interface ISpecification<T>
52+
{
53+
Expression<Func<T, bool>> Criteria { get; }
54+
}
55+
56+
public class GameByPlayerSpecification : ISpecification<Game>
57+
{
58+
private readonly PlayerAlias _playerAlias;
59+
60+
public Expression<Func<Game, bool>> Criteria =>
61+
game => game.PlayerAlias == _playerAlias;
62+
}
63+
```
64+
65+
## Rules
66+
- No business logic in controllers or handlers — all invariants enforced inside aggregates
67+
- Raise domain events for every significant state change
68+
- Use private setters; expose state only through domain methods
69+
- Use factory methods (`Create()`) instead of public constructors

.claude/rules/error-handling.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Error Handling Patterns
2+
3+
## Domain Exceptions
4+
```csharp
5+
public class InvalidMoveException : DomainException
6+
{
7+
public InvalidMoveException(int row, int column, int value)
8+
: base($"Invalid move: {value} at position ({row}, {column})")
9+
{
10+
}
11+
}
12+
```
13+
14+
## Result Pattern
15+
```csharp
16+
public class Result<T>
17+
{
18+
public bool IsSuccess { get; }
19+
public T? Value { get; }
20+
public string? Error { get; }
21+
22+
public static Result<T> Success(T value) => new(true, value, null);
23+
public static Result<T> Failure(string error) => new(false, default, error);
24+
}
25+
```
26+
27+
## Rules
28+
- Use `Result<T>` for expected failures — never throw exceptions for them
29+
- Use domain exceptions only for invariant violations inside aggregates
30+
- Validate at system boundaries (user input, external APIs); trust internal code
31+
- Don't add error handling for scenarios that can't happen
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
paths:
3+
- "src/backend/Sudoku.Infrastructure/**/*.cs"
4+
---
5+
6+
# Infrastructure Layer Guidelines
7+
8+
## Repository Implementations
9+
```csharp
10+
public class AzureBlobGameRepository : IGameRepository
11+
{
12+
private readonly BlobServiceClient _blobServiceClient;
13+
private readonly ILogger<AzureBlobGameRepository> _logger;
14+
15+
public async Task<Game?> GetByIdAsync(GameId id)
16+
{
17+
// Implementation with proper error handling
18+
}
19+
}
20+
```
21+
22+
## Rules
23+
- Implement interfaces defined in the Application layer — never the reverse
24+
- `CosmosDbGameRepository` is the primary persistent store
25+
- `InMemoryPuzzleRepository` is for puzzle generation only (performance optimization, not persisted)
26+
- `IDomainEventDispatcher` dispatches domain events after persistence
27+
- Use `async/await` consistently throughout
28+
- Dispose of resources properly

0 commit comments

Comments
 (0)