From coding-standards
Review .NET / C# code against team conventions — messaging architecture, API design, testing, dependency management, and analyser compliance. Auto-invoked when reviewing .cs files.
npx claudepluginhub hpsgd/turtlestack --plugin coding-standardsThis skill is limited to using the following tools:
Review .NET and C# code against team architectural patterns and conventions. This methodology covers messaging architecture, API design, testing, dependency management, and analyser compliance.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
Review .NET and C# code against team architectural patterns and conventions. This methodology covers messaging architecture, API design, testing, dependency management, and analyser compliance.
Execute all seven passes. Every finding requires file, line, and evidence.
The foundational rule: every message handler performs exactly one unit of work. No loops with heavy inline logic. No handlers that orchestrate multiple unrelated operations.
Handler scan — find all message handlers in changed files:
grep -rn 'IHandleMessages\|ICommandHandler\|IEventHandler\|Handle\s*(' --include='*.cs' [changed files]
Loop detection in handlers — grep for foreach, for (, while (, Parallel.For, .Select( inside handler methods. Each loop that performs I/O, database access, or publishes messages is a finding:
foreach (var item in items) { await _repository.Save(item); }Handler responsibility — read each handler. It should:
Side effects in handlers — handlers should not send emails, call external APIs, or write to multiple aggregates. Those belong in separate handlers triggered by domain events.
[AggregateHandler] usage — aggregates that handle commands must use the [AggregateHandler] attribute with cascading returns, not manual event publishing:
grep -rn 'AggregateHandler\|\.Publish\|\.Send\(' --include='*.cs' [changed files]
If a handler inside an aggregate calls _bus.Publish() or _mediator.Send() manually, that is a finding. Use cascading return types instead — the infrastructure handles event dispatch.
Return types — aggregate handlers return domain events as their result. The framework publishes them. Verify handlers return the event, not void or Task.
Event immutability — domain events must be immutable records or classes with init-only properties. Grep for set; in event classes:
grep -rn 'set;' --include='*.cs' [event file paths]
URL structure — API endpoints must reflect entity ownership hierarchically:
/api/organisations/{orgId}/projects/{projectId}/tasks/{taskId}/api/tasks/{taskId} (loses the ownership chain)
Read controller route attributes and verify the hierarchy matches the domain model.Static LoadAsync for preconditions — endpoints that need to validate entity existence or permissions before executing should use the static LoadAsync pattern:
public static async Task<IResult> LoadAsync(Guid id, IRepository repo)
This separates precondition loading from business logic. If a controller action starts with 5 lines of "fetch and check if null" logic, that belongs in LoadAsync.
List endpoints — pagination, sort, filter — every list endpoint must support:
Grep for list endpoints (methods returning collections) and verify these parameters exist:
grep -rn 'IEnumerable\|IList\|List<\|IQueryable\|Task<.*\[\]>' --include='*.cs' [changed files]
If a list endpoint loads all records and filters in memory (.ToList() followed by .Where()), that is a critical finding.
Consistent response shapes — all endpoints should return consistent wrapper types. No raw primitives as responses.
Constructor injection only — dependencies come through the constructor, never through property injection, service locator, or static helpers:
grep -rn 'ServiceLocator\|GetService\|Resolve<\|\.GetRequiredService' --include='*.cs' [changed files]
Every hit is a finding. Use constructor injection with interfaces.
External dependencies behind interfaces — every external system (database, HTTP client, file system, clock, message bus) must be accessed through an interface. Direct usage of HttpClient, DateTime.Now, File.ReadAllText is a finding:
grep -rn 'DateTime\.Now\|DateTime\.UtcNow\|File\.\|Directory\.' --include='*.cs' [changed files]
Use IDateTimeProvider, IFileSystem, etc. This enables testing.
Central package management — verify the solution uses Directory.Packages.props for NuGet versions. Individual .csproj files should not specify package versions:
grep -rn 'Version=' --include='*.csproj' [changed files]
Package versions in .csproj files are a finding (they should use VersionOverride only in exceptional cases).
Session lifecycle — database sessions / units of work must be managed by infrastructure, not by handlers. Handlers should not call SaveChanges(), Commit(), or Dispose() on sessions directly. The pipeline handles that.
Transaction boundaries — if a handler needs a transaction broader than the default, it must be explicit and documented. Grep for BeginTransaction, TransactionScope:
grep -rn 'BeginTransaction\|TransactionScope' --include='*.cs' [changed files]
Each hit requires a comment explaining why the default session management is insufficient.
Warnings-as-errors — verify the project treats analyser warnings as errors. Check .csproj or Directory.Build.props:
grep -rn 'TreatWarningsAsErrors\|WarningsAsErrors' --include='*.csproj' --include='*.props'
If TreatWarningsAsErrors is not true, that is a finding.
Suppression audit — grep for all analyser suppressions:
grep -rn '#pragma warning disable\|SuppressMessage\|GlobalSuppressions' --include='*.cs' [changed files]
Every suppression must have:
Nullable reference types — verify <Nullable>enable</Nullable> is set. Grep for null! (null-forgiving operator):
grep -rn 'null!' --include='*.cs' [changed files]
Each null! is a finding unless it is in a test fixture or deserialization constructor with a comment.
BDD naming — test methods must use BDD-style names describing the scenario:
WhenCreatingUserWithDuplicateEmail_ShouldReturnConflictTestCreateUser, CreateUser_Test, Test1grep -rn '\[Fact\]\|\[Theory\]\|\[Test\]' --include='*.cs' -A1 [changed files]
Read the method name on the line after each attribute.
Test structure — each test follows Arrange/Act/Assert (or Given/When/Then). Tests with multiple Act steps test too much — split them.
No test logic — tests should not contain if, switch, try/catch, or loops. A test that branches is testing multiple things.
Integration over mocking — prefer integration tests with real dependencies (in-memory database, test bus) over unit tests that mock everything. Tests that mock the thing being tested are always wrong.
### [SEVERITY] [Pass]: [Short description]
**File:** `path/to/File.cs:42`
**Evidence:** [code or grep output]
**Standard:** [which rule is violated]
**Fix:** [concrete code change or architectural suggestion]
## .NET Review
### Summary
- Files reviewed: N
- Message architecture: X findings
- Aggregate patterns: X findings
- API design: X findings
- Dependencies: X findings
- Sessions: X findings
- Analysers: X findings
- Testing: X findings
### Findings
[grouped by severity: critical, important, suggestion]
### Clean Areas
[what was done well]
If all checks pass: "No findings. .NET review complete — all changed files comply with team conventions." Do not fabricate issues.
/coding-standards:review-standards — cross-cutting quality and writing style checks that apply to all languages. Run alongside this review./coding-standards:review-git — commit message and PR conventions. Run when reviewing a PR.