.claude/skills/api-contracts-standards/SKILL.md
Use when designing, reviewing, or enforcing interface-first API contracts, public API surfaces, or cross-library type signatures in .NET projects.
npx skillsauth add klod68/littlerae api-contracts-standardsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Field | Value |
|---|---|
| Name | API Contracts & Interface-First Design Standards |
| Domain | API Design, Interfaces, Contracts |
| Level | Feature |
| Tags | api, contracts, interfaces, public-surface, dip |
Activate this skill when the task involves:
These rules layer on top of base architectural standards. On conflict, these win.
<!-- SHARED:rules/api-contracts.md -->Apply these rules for all .NET projects. "Program to an interface, not to an implementation" is the load-bearing rule of the entire architecture.
Define the contract before writing the implementation. An interface is a promise. It describes what a component does, not how. No implementation exists without a prior interface definition. Contracts defined after implementation are descriptive; contracts defined before are prescriptive. Only prescriptive contracts can be enforced by the Scaffolder, verified by the Reviewer, and independently implemented without coordination overhead.
1. Designer defines interface → committed to design artifact
2. Scaffolder creates interface file + implementation shell
3. Implementer fills implementation — BOUND BY the interface, cannot alter it
4. Reviewer verifies: implementation matches interface EXACTLY
If an Implementer discovers a gap in the interface during implementation, they MUST surface it to the Designer — not silently add methods to the interface. Adding to an interface is a design decision, not an implementation decision.
// Correct — segregated by concern
public interface I{Entity}Reader
{
Task<{Entity}?> GetByIdAsync({EntityId} id, CancellationToken ct);
Task<IReadOnlyList<{Entity}>> GetActiveAsync(CancellationToken ct);
}
public interface I{Entity}Writer
{
Task AddAsync({Entity} entity, CancellationToken ct);
Task UpdateAsync({Entity} entity, CancellationToken ct);
}
// Wrong — combined concerns force query handlers to depend on write methods
public interface I{Entity}Repository
{
Task<{Entity}?> GetByIdAsync({EntityId} id, CancellationToken ct);
Task AddAsync({Entity} entity, CancellationToken ct);
Task<byte[]> ExportAsync({Entity} entity, CancellationToken ct); // unrelated concern
}
// Correct
public interface IExportService
{
Task<byte[]> ExportAsync({Entity} entity, CancellationToken ct);
}
// Wrong — no CancellationToken
public interface IExportService
{
Task<byte[]> ExportAsync({Entity} entity); // BLOCKER
}
CancellationToken must be the last parameter on every async interface method, named ct.
// Correct — return read-only abstraction
Task<IReadOnlyList<{Entity}>> GetActiveAsync(CancellationToken ct);
// Wrong — concrete collection type leaks implementation detail
Task<List<{Entity}>> GetActiveAsync(CancellationToken ct);
Task<{Entity}[]> GetActiveAsync(CancellationToken ct);
| Return scenario | Correct type |
|---|---|
| Single item, may not exist | Task<{T}?> |
| Single item, must exist | Task<{T}> |
| Multiple items | Task<IReadOnlyList<{T}>> |
| Paginated | Task<PagedResult<{T}>> |
| Stream | IAsyncEnumerable<{T}> |
| Operation result with possible failure | Task<Result<{T}>> |
// Correct — query handler maps to DTO before crossing to Presentation
public async Task<{Entity}Dto> Handle(Get{Entity}ByIdQuery query, CancellationToken ct)
{
var entity = await _reader.GetByIdAsync(new {EntityId}(query.Id), ct);
return entity is null ? null : MapToDto(entity);
}
// Wrong — domain entity crosses into Presentation layer
public async Task<{Entity}> GetEntity(Guid id) // BLOCKER — entity in controller/component
// Correct — expected failures via Result
public interface I{Entity}Service
{
Task<Result<{EntityId}>> CreateAsync(string title, CancellationToken ct);
Task<Result> ArchiveAsync({EntityId} id, CancellationToken ct);
}
// Wrong — using exceptions for expected business failures
public interface I{Entity}Service
{
Task<{EntityId}> CreateAsync(string title, CancellationToken ct);
// caller must catch {Entity}NotFoundException — bad contract
}
Before any interface is considered complete, it must satisfy:
| Check | Requirement |
|---|---|
| All async methods have CancellationToken ct as last parameter | Yes |
| All async methods are suffixed with Async | Yes |
| All return types are from the approved type table above | Yes |
| No concrete types in parameters or return types (except domain entities in Application-layer interfaces) | Yes |
| XML <summary> doc on the interface type | Yes |
| XML <remarks> (UC-NNN / ADR-NNN) on the interface type | Yes |
| Interface Category | Location |
|---|---|
| Repository contracts | Application/Interfaces/Repositories/ |
| Domain/application service contracts | Application/Interfaces/Services/ |
| Domain-level contracts (e.g., IDomainEventDispatcher) | Domain/Common/ |
| Presentation-only contracts (e.g., INavigationService) | Presentation/ |
Interfaces are NEVER defined in Infrastructure. Infrastructure only implements.
When a breaking change to a public interface is required:
IExportService → IExportServiceV2 temporarily.| Check | Signal | Severity |
|---|---|---|
| Missing CancellationToken | Async interface method without ct parameter | BLOCKER |
| Concrete return type | List<T>, T[], Dictionary<K,V> in interface return | MAJOR |
| Domain entity in Presentation | Entity type as action/component return type | BLOCKER |
| Implementation adds public method not in interface | public method on class not declared in interface | MAJOR |
| No XML doc on public interface | Interface without <summary> | MINOR |
| Exception for expected failure | Interface method throws for not-found / validation | MAJOR |
| Interface in Infrastructure layer | New public interface in any Infrastructure namespace | BLOCKER |
| Anti-Pattern | Fix |
|---|---|
| Fat interface with 8+ methods | Split by read/write concern and by subject |
| Missing ct on async method | Always include CancellationToken ct as last parameter |
| List<T> or T[] return type | Use IReadOnlyList<T> or IAsyncEnumerable<T> |
| Throwing for not-found in a repository | Return null or Result.Failure |
| Defining an interface in Infrastructure | Move to Application/Interfaces/ |
| Silent interface extension by Implementer | Surface to Designer; create ADR |
api-design.md — RESTful conventions, HTTP methods, pagination, error responsescqrs.md — CQRS via MediatR, command/query separation, pipeline behaviorsnaming.md — Naming conventions for types, methods, properties, namespacesnullability.md — Nullable reference types, suppression discipline, collection rulesdesign-patterns.md — Approved GoF patterns: Factory, Repository, Decorator, Strategy, Specificationtools
Use when cross-cutting concerns (logging, metrics, validation, authorization) are tangled into command handlers or service methods, when building database command pipelines with reorderable concerns, or when HTTP client pipelines or message handlers need composable, independently-replaceable processing stages. Covers ICommandInterceptor interface, InterceptorPipeline with reverse-chain construction, zero-cost Empty sentinel to skip overhead when no interceptors are registered, and ConfigureAwait(false) discipline for library code. Domain: Architecture, Cross-Cutting Concerns. Level: Intermediate. Tags: interceptor, pipeline, middleware, decorator, cross-cutting-concerns.
development
Use when writing integration tests for Razor Pages, MVC, or Minimal API applications to validate routing, middleware, page rendering, and HTTP behavior without a browser or live server, or when adding fast smoke tests to a CI pipeline. Covers WebApplicationFactory<Program> setup with public partial class Program, in-memory test server, AngleSharp HTML parsing, CSS selector assertions, redirect and status code testing, and a shared static fixture pattern for minimal per-test startup overhead. Domain: Testing, ASP.NET Core. Level: Intermediate. Tags: integration-testing, webapplicationfactory, razor-pages, anglesharp, http-testing.
development
Use when designing indexes for new tables, diagnosing slow queries that are not using indexes efficiently, reviewing index fragmentation and maintenance, or when the current indexing strategy results in key lookups, table scans, or missing index warnings. Covers clustered index key selection (narrow, unique, ever-increasing), non-clustered index design for query patterns, covering indexes with INCLUDE columns, filtered indexes for subset queries, composite index column ordering, DMV-based monitoring for missing and unused indexes, and rebuild vs reorganize maintenance thresholds. Domain: Database, Performance. Level: Intermediate. Tags: index, sql-server, covering-index, filtered-index, performance, dmv, maintenance.
development
Use when building a searchable in-memory catalog or registry for documentation sites, admin panels, or type/API browsers where you need keyword matching, fuzzy search, and ranked results without an external search engine or database. Covers RegistryService with weighted scoring across name, description, keywords, and method names; Levenshtein fuzzy matching; synonym expansion; category and subcategory filtering; and singleton DI registration for datasets of hundreds to low thousands of items. Domain: Search, Data Access Patterns. Level: Intermediate. Tags: search, registry, fuzzy-matching, in-memory, catalog, filtering.