Serena MCP is mandatory for C# code. First call
mcp__serena__initial_instructionsto load the Serena tool manual, then use the Serena tools for ALL.csreading / searching / navigation / creation / editing — prefer symbol navigation (get_symbols_overview/find_symbol/find_referencing_symbols) over whole-file reads. NativeEdit/Writeon.csis hook-blocked (the TS/React frontend uses the native tools).
Authoring a Kommand command or query ({{ProductName}})
Use this when adding a use case to the backend. The authoritative rules are
.claude/rules/backend/cqrs-kommand.md, result-and-errors.md, api-design.md, and
docs/projectStandards/backend-architecture.md. Concrete code templates are in
reference/templates.md; deeper patterns (interceptor-owned transactions,
validation→Result, notifications-after-commit, command-vs-query-vs-service) in
reference/patterns.md.
Step 1 — classify the intent
| Intent | Use |
|---|---|
| Mutate state, return new state/id | ICommand<Result<TResponse>> |
| Mutate state, nothing to return | ICommand<Result> (or ICommand<Result<Unit>>) |
| Read state | IQuery<Result<TResponse>> — never mutates |
| Background / multi-system orchestration | a service class, NOT Kommand (and any message bus is a library → Dan's call) |
Step 2 — place the files (feature slice, one type per file)
{{ProjectName}}.Application/<Feature>/
├── Commands/ {Verb}{Noun}Command.cs · {Verb}{Noun}CommandHandler.cs · {Verb}{Noun}CommandValidator.cs
├── Queries/ Get{Noun}Query.cs · Get{Noun}QueryHandler.cs
└── DTOs/ {Noun}Response.cs
The endpoint is a single file in {{ProjectName}}.Api/Features/<Feature>/<UseCase>.cs (endpoint + request +
response together). The Application feature folder mirrors the API feature.
Step 3 — write the pieces (see templates)
- Command/query = a
recordimplementing the Kommand interface, returningResult<...>. - Handler = explicit constructor (no primary ctor) with
ArgumentNullExceptionguards;HandleAsync; injects the repositories it needs; does not open transactions or callSaveChanges(the interceptor does); returns success payloads orError.*; catches a narrowDomainException→Result.Failure. - Validator (business scope) =
IValidator<TCommand>(in namespaceKommand) returningValidationResult(Success()/Failure(...)); put the stable error/i18n code inValidationError.ErrorCode. Note: Kommand's built-inWithValidation()interceptor throwsValidationExceptionon failure — we skip it and register our own interceptor that returns a failedResult(Error.Validation(...)); see.claude/rules/backend/cqrs-kommand.md. - Endpoint =
IEndpoint; contract-validate (shape + JWT-inferable authz, no DB); dispatch viaIMediator.SendAsync/QueryAsync; map theResult<T>withToHttpResult(always ProblemDetails on failure).
Step 4 — check the conventions
- Imports:
using Kommand.Abstractions;for command/handler/query/mediator types andusing Kommand;for validators/interceptors/Unit(a validator file needsKommand, notKommand.Abstractions). No unusedusing(IDE0005 fails the build). Records for commands/queries/DTOs; rich classes for entities. - Handler returns
Result<T>; railway helpers areMap/Then/Match. - No
Handlers/folder, no single-file Application slices, no inlined response records, no primary ctors. - Validation lives in exactly one scope each: contract (API), business (
IValidator<T>), invariant (domain throws).