Claude Code Plugins

Community-maintained marketplace

Feedback

This skill should be used for significant development work including new projects, major features, refactoring, architecture changes, and migrations. Use when the task involves multiple files/services, external integrations, schema changes, deployment infrastructure, or architectural decisions requiring documentation. Do not use for trivial changes like typo fixes, simple renames, minor bug fixes within existing patterns, documentation-only updates, or configuration value changes.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name develop
description This skill should be used for significant development work including new projects, major features, refactoring, architecture changes, and migrations. Use when the task involves multiple files/services, external integrations, schema changes, deployment infrastructure, or architectural decisions requiring documentation. Do not use for trivial changes like typo fixes, simple renames, minor bug fixes within existing patterns, documentation-only updates, or configuration value changes.

Develop Skill

Complete development methodology for architecting and engineering significant development work following established patterns and conventions.


Decision-Making Philosophy

Autonomous with Rationale

Make decisions confidently when:

  • Requirements are clear
  • Established patterns apply
  • Technology choice is obvious based on complexity
  • Best practice is well-known

Ask questions when:

  • Requirements are ambiguous
  • Multiple valid approaches exist with different tradeoffs
  • User preferences unclear
  • Business constraints unknown

Always document:

  • Architectural decisions in ADRs (when adr/ directory exists)
  • Rationale for technology choices
  • Tradeoffs considered
  • Why alternative approaches were rejected

Technology Stack Decision Framework

Default: Prefer C# for:

  • Multiple delivery methods (file, blob, service bus, webhook)
  • Schema management and validation
  • Azure SDK integration
  • Complex error handling
  • Asynchronous operations
  • Strong typing requirements

Fallback: Bash only for:

  • Simple, single-purpose scripts
  • Minimal logic (< 100 lines)
  • No external API integrations
  • Shell operations (file manipulation, system commands)
  • Quick automation tasks

Document in ADR-001:

**Decision:** [C# with .NET 10 | Bash script]
**Rationale:** [Why this choice - analyze complexity factors]
**Tradeoffs:** [What was gained vs what was given up]
**Date:** [YYYY-MM-DD]

Code Style Guide

C# Conventions

Classes & Structure

// Default: internal sealed classes
internal sealed class ServiceName
{
    // Implementation
}

// Public only when part of API contract
public class PublicApiModel
{
    // Implementation
}

Primary constructors for simple DI classes:

internal sealed class ReportGenerator(
    ILogger<ReportGenerator> _log,
    DataService _dataService,
    ConfigService _config)
{
    // Fields automatically created from parameters

    public async Task<Report> GenerateAsync()
    {
        _log.LogInformation("Generating report...");
        return await _dataService.FetchAsync().ConfigureAwait(false);
    }
}

Traditional constructors for complex classes:

internal sealed class ComplexService
{
    /* Dependencies */
    private readonly DataService _dataService;
    private readonly ConfigService _config;
    private readonly ILogger<ComplexService> _log;

    /// <summary>
    ///     Default injectable constructor.
    /// </summary>
    public ComplexService(
        DataService dataService,
        ConfigService config,
        ILogger<ComplexService> log)
    {
        _dataService = dataService;
        _config = config;
        _log = log;
    }

    // Methods
}

File organization:

  • One class per file
  • File name matches class name exactly (e.g., ReportGenerator.cs)
  • PascalCase for all file names

Properties & Fields

// Init-only properties with collection expressions
public string Name { get; init; } = string.Empty;
public List<string> Tags { get; init; } = [];
public Dictionary<string, object> Metadata { get; init; } = [];

// Nullable properties - explicit annotation
public string? OptionalValue { get; init; }
public DateTime? ExpiryDate { get; init; }

// Private fields - underscore prefix, camelCase
private readonly ILogger<ServiceName> _log;
private readonly ConfigService _config;

// Get-only properties for immutable values
public string TenantId { get; }

Nullable reference types:

// Always enable in .csproj
<Nullable>enable</Nullable>

// Explicit null handling
if (value is null)
{
    throw new ArgumentNullException(nameof(value));
}

string? result = GetValue();
if (result is not null)
{
    ProcessValue(result);
}

// Null coalescing
var final = value ?? defaultValue;

Methods

// Async methods - always Async suffix
public async Task<Result> ProcessAsync()
{
    return await _service.FetchAsync().ConfigureAwait(false);
}

// ConfigureAwait(false) - always use
await SomeAsyncMethod().ConfigureAwait(false);

// Expression-bodied for simple one-liners
public string GetFullName() => $"{FirstName} {LastName}";
public override string ToString() => _value.ToLowerInvariant();

// Full methods for complex logic
public async Task<Report> GenerateReportAsync()
{
    _log.LogInformation("Starting report generation...");

    var data = await FetchDataAsync().ConfigureAwait(false);
    var processed = ProcessData(data);
    var report = CreateReport(processed);

    _log.LogInformation("Report generation complete");
    return report;
}

// Private static helpers
private static bool ValidateInput(string input)
{
    return !string.IsNullOrWhiteSpace(input) && input.Length <= 100;
}

Data Models

Records for DTOs and request models:

/// <summary>
///     Request model for creating a user.
/// </summary>
internal record CreateUserRequest(
    string FirstName,
    string LastName,
    string Email,
    string? PhoneNumber
);

// Records with init properties
public sealed record UserDetails
{
    [JsonPropertyName("id")]
    public string Id { get; init; } = string.Empty;

    [JsonPropertyName("displayName")]
    public string DisplayName { get; init; } = string.Empty;

    [JsonPropertyName("email")]
    public string Email { get; init; } = string.Empty;

    [JsonPropertyName("tags")]
    public List<string> Tags { get; init; } = [];
}

Classes for domain models with complex constructors:

internal class UserEntity
{
    public string Id { get; init; } = string.Empty;
    public string Name { get; init; } = string.Empty;
    public List<string> Roles { get; init; } = [];

    // Default constructor
    public UserEntity() { }

    // Constructor from external model
    public UserEntity(ExternalUser externalUser)
    {
        Id = externalUser.Identifier ?? string.Empty;
        Name = $"{externalUser.FirstName} {externalUser.LastName}";
        Roles = externalUser.Permissions?.Select(p => p.Name).ToList() ?? [];
    }
}

Classes for services with stateful behavior:

internal sealed class CacheService
{
    private readonly Dictionary<string, CachedValue> _cache = [];
    private readonly ILogger<CacheService> _log;

    public CacheService(ILogger<CacheService> log)
    {
        _log = log;
    }

    public void Set(string key, object value)
    {
        _cache[key] = new CachedValue(value, DateTime.UtcNow);
        _log.LogDebug("Cached value for key: {Key}", key);
    }
}

LINQ & Collections

// Fluent chaining with method syntax
var results = items
    .Where(x => !string.IsNullOrEmpty(x.Name))
    .Select(x => new { x.Name, x.Value })
    .OrderBy(x => x.Name)
    .ThenByDescending(x => x.Value)
    .ToList();

// Custom extension methods (Map, AddWhen patterns)
var mapped = items
    .Map(x => Transform(x))
    .AddWhen(condition, additionalItem);

// Lambda formatting
.Select(x => x.Name)                          // Single line
.Where(x => !string.IsNullOrEmpty(x.Value))   // Single line

.Select(item => new ComplexModel              // Multi-line when complex
{
    Id = item.Id,
    Name = item.Name,
    Processed = ProcessValue(item.Value)
})

// Collection expressions (C# 12)
List<string> items = [];
Dictionary<string, int> map = [];
string[] array = [];

Constants Organization

/// <summary>
///     Application constants organized by context.
/// </summary>
public static class Constants
{
    public static class ResourceTypes
    {
        public const string WebApp = "WebApp";
        public const string FunctionApp = "FunctionApp";
        public const string Database = "Database";

        public static readonly List<string> All = [
            WebApp,
            FunctionApp,
            Database
        ];
    }

    public static class Defaults
    {
        public const int TimeoutSeconds = 30;
        public const int RetryCount = 3;
        public const string DefaultRegion = "eastus";
    }
}

// Usage
var type = Constants.ResourceTypes.WebApp;
foreach (var resourceType in Constants.ResourceTypes.All)
{
    // Process each type
}

Logging Patterns

// Structured logging - always use placeholders, never string interpolation
_log.LogInformation(
    "Processing request... UserId={UserId};RequestType={RequestType}",
    userId,
    requestType);

_log.LogError(ex,
    "Failed to process request: {Error}",
    ex.Message);

_log.LogWarning(
    "Threshold exceeded: {CurrentValue} > {MaxValue}",
    currentValue,
    maxValue);

// Logger naming
private readonly ILogger<ServiceName> _log;  // Always named _log

Documentation

XML docs on public APIs:

/// <summary>
///     Generates a report based on provided criteria.
/// </summary>
/// <param name="criteria">The filtering and sorting criteria.</param>
/// <returns>A complete report with all requested data.</returns>
/// <exception cref="ArgumentNullException">When criteria is null.</exception>
/// <exception cref="ValidationException">When criteria is invalid.</exception>
public async Task<Report> GenerateReportAsync(ReportCriteria criteria)
{
    // Implementation
}

Code organization with regions:

// Public members - no wrapping needed, keep visible

public void PublicMethod() { }

#region Internals

private void HelperMethod() { }
private void AnotherHelper() { }

#endregion

Purpose:

  • Use #region Internals / #endregion to wrap ONLY private/internal members
  • Public API stays unwrapped and fully visible
  • Regions allow collapsing implementation details in IDE
  • Section comment lines (// ----) only for Bash/scripting languages

Inline comments - only when necessary:

// Only when business logic needs clarification
if (flags.Count(x => x) > 1)
{
    // Business rule: Cannot filter by multiple statuses simultaneously
    throw new InvalidOperationException("Cannot filter by more than one status");
}

// Self-documenting code preferred over comments
var activeUsers = users.Where(u => u.IsActive && u.LastLogin > cutoffDate);

Modern C# Features

// Records for immutable data
public sealed record Configuration(
    string ApiUrl,
    int TimeoutSeconds,
    bool EnableRetry
);

// Pattern matching
if (value is null) return defaultValue;
if (content is string stringContent) await ProcessStringAsync(stringContent);

var result = response switch
{
    { StatusCode: 200 } => "Success",
    { StatusCode: 404 } => "Not Found",
    _ => "Error"
};

// Init-only properties
public string Name { get; init; } = string.Empty;

// Target-typed new
Dictionary<string, string> headers = new() { ["Authorization"] = token };
List<int> numbers = new() { 1, 2, 3 };

// Collection expressions (C# 12)
List<string> items = [];
string[] names = [name1, name2, name3];

// Top-level statements (Program.cs)
var host = CreateHost();
await host.RunAsync();

// Primary constructors (C# 12)
internal sealed class Service(ILogger<Service> _log, Config _config)
{
    public void Process() => _log.LogInformation("Processing...");
}

Bash Conventions

#!/bin/bash

#######################################################################
# Constants
#######################################################################

# Color codes for output
_error="\e[0;31m##[error]"      # Red
_warning="\e[0;33m##[warning]"  # Yellow
_command="\e[0;34m##[command]"  # Blue
_debug="\e[0;35m##[debug]"      # Purple
_0="\e[0m"                       # Reset

#######################################################################
# Functions
#######################################################################

function print_banner() {
    echo "########################################################"
    echo "# Tool Name - Brief Description"
    echo "#"
    echo "# Additional context"
    echo "#"
    echo "# Author: Joshua Sprague"
    echo "########################################################"
}

function check_env() {
    if [[ -z "$REQUIRED_VAR" ]]; then
        echo -e "${_error}Missing required environment variable REQUIRED_VAR"
        exit 1
    fi
}

function print_debug_info() {
    echo
    echo -e "${_debug}SECRET_VALUE=${SECRET_VALUE:0:3}*****"
    echo -e "${_debug}PUBLIC_VALUE=$PUBLIC_VALUE"
}

function login_to_azure() {
    echo -e "${_command}az login -p *** -u *** --tenant ***"
    az login -p "$AZURE_SECRET" -u $AZURE_CLIENT_ID --tenant $AZURE_TENANT_ID > /dev/null 2>&1
    if [ $? -ne 0 ]; then
        echo -e "${_error}Azure login failed"
        exit 1
    fi
}

#######################################################################
# Main Script
#######################################################################

print_banner
check_env
print_debug_info
login_to_azure

# Business logic here

echo -e "${_0}\nDone - Operation completed successfully"

Key patterns:

  • Color-coded output for different message types
  • Functions organized logically (not alphabetically)
  • Check exit codes after critical operations ($?)
  • Mask secrets in logs (${SECRET:0:3}*****)
  • Environment variable configuration
  • Banner for tool identification
  • Validation function (check_env)

Project Structure Patterns

Console Applications

ProjectName.Console/
├── Domain/
│   ├── [Feature]/           # Feature-based folders (Employee, Project, etc.) - optional
│   │   ├── [Model].cs       # Domain models (optional)
│   │   └── [Service].cs     # Business logic services
│   ├── Models/              # Domain models (optional - only if mapping from entities)
│   ├── Services/            # Domain services (if not feature-organized)
│   └── CommandBuilders/     # CLI command handlers
├── Infrastructure/
│   ├── Entities/            # Database entities (EF Core, CosmosDB, etc.) - if applicable
│   ├── Messages/            # Integration message DTOs - if applicable
│   │   ├── Incoming/        # Service Bus, Event Grid inbound
│   │   └── Outgoing/        # Service Bus, Event Grid outbound
│   ├── Services/            # Infrastructure services
│   │   ├── [Entity]Repo.cs  # Data access repositories
│   │   ├── Integration/     # External API clients
│   │   └── Utilities/       # Infrastructure utilities
│   └── [DbContext].cs       # EF Core DbContext - if applicable
├── Outputs/                 # Delivery mechanisms (file, blob, etc.)
├── Resources/               # Embedded resources
├── Migrations/              # EF Core migrations - if applicable
├── Constants.cs
├── GlobalUsings.cs
├── Program.cs
└── AppConfig.cs

# Test project
Tests.ProjectName.Console/
├── Domain/
│   └── Services/
│       └── ServiceNameTests.cs
├── Infrastructure/
│   └── Services/
│       └── RepoTests.cs
└── TestFixtures/

Azure Functions

ProjectName.Functions/
├── Api/                     # HTTP-triggered functions
│   ├── [Feature]/           # Feature-based organization
│   │   ├── GetItems.cs
│   │   ├── CreateItem.cs
│   │   └── UpdateItem.cs
│   └── [OtherFeature]/
│       └── ProcessEvent.cs
├── Functions/               # Non-HTTP triggers (Timer, ServiceBus, Cosmos, etc.)
│   ├── Timer_[Name].cs
│   ├── ServiceBus_[Name].cs
│   └── CosmosDb_[Name].cs
├── Domain/
│   ├── [Feature]/           # Feature folders (Employee, Project, etc.) - optional
│   │   ├── [Model].cs       # Domain models (optional)
│   │   └── [Service].cs     # Business logic
│   ├── Models/              # Domain models (optional - only if mapping from entities)
│   └── Services/            # Domain services (if not feature-organized)
├── Infrastructure/
│   ├── Entities/            # Database entities (EF Core, CosmosDB) - if applicable
│   ├── Messages/            # Integration message DTOs - if applicable
│   │   ├── Incoming/        # Service Bus, Event Grid inbound
│   │   └── Outgoing/        # Service Bus, Event Grid outbound
│   ├── Realtime/            # SignalR hubs - if applicable
│   ├── Services/            # Infrastructure services
│   │   ├── [Entity]Repo.cs  # Data access repositories
│   │   ├── Integration/     # External API clients
│   │   └── Utilities/       # Infrastructure utilities
│   └── [DbContext].cs       # EF Core DbContext - if applicable
├── Migrations/              # EF Core migrations - if applicable
├── _FunctionBootstrap.cs    # Shared bootstrap
├── Constants.cs
├── GlobalUsings.cs
└── Program.cs

# Test project
Tests.ProjectName.Functions/
├── Api/
├── Domain/
└── Infrastructure/

Containerized Tools

project-name/
├── .github/
│   └── workflows/
│       └── ci-cd.yaml
├── src/
│   ├── Domain/
│   │   ├── [Feature]/       # Feature folders - optional
│   │   ├── Models/          # Domain models (optional - only if mapping from entities)
│   │   └── Services/        # Business logic services
│   ├── Infrastructure/
│   │   ├── Entities/        # Database entities - if applicable
│   │   ├── Messages/        # Integration DTOs - if applicable
│   │   │   ├── Incoming/
│   │   │   └── Outgoing/
│   │   ├── Services/        # Infrastructure services
│   │   │   ├── [Entity]Repo.cs
│   │   │   ├── Integration/
│   │   │   └── Utilities/
│   │   └── [DbContext].cs   # DbContext - if applicable
│   ├── Outputs/             # Delivery mechanisms
│   ├── Migrations/          # EF Core migrations - if applicable
│   ├── Constants.cs
│   ├── GlobalUsings.cs
│   ├── Program.cs
│   └── ProjectName.csproj
├── tests/
│   └── ProjectName.Tests/
│       ├── Domain/
│       └── Infrastructure/
├── Dockerfile
├── docker-compose.yml
├── makefile
├── README.md
├── CHANGELOG.md
└── .gitignore

Blazor Web Applications

ProjectName.Web/
├── Components/              # Blazor UI layer
│   ├── Layout/
│   ├── Pages/
│   │   ├── [Feature]/
│   │   └── [OtherFeature]/
│   └── Shared/
│       └── [Feature]/
├── Domain/
│   ├── [Feature]/           # Feature folders (Employee, Project, Security, etc.)
│   │   ├── [Model].cs       # Domain models (optional)
│   │   └── [Service].cs     # Business logic
│   ├── Models/              # Domain models (optional - only if mapping from entities)
│   └── Services/            # Domain services (if not feature-organized)
├── Infrastructure/
│   ├── Entities/            # Database entities (EF Core, CosmosDB)
│   ├── Messages/            # Integration message DTOs
│   │   ├── Incoming/        # Service Bus, Event Grid inbound
│   │   └── Outgoing/        # Service Bus, Event Grid outbound
│   ├── Realtime/            # SignalR hubs
│   │   ├── [Hub].cs
│   │   └── ConnectionCache.cs
│   ├── Services/            # Infrastructure services
│   │   ├── [Entity]Repo.cs  # Data access repositories
│   │   ├── Integration/     # External API clients
│   │   └── Utilities/       # Infrastructure utilities
│   └── AppDbContext.cs      # EF Core DbContext
├── Migrations/              # EF Core migrations
├── wwwroot/                 # Static web assets
├── Constants.cs
├── GlobalUsings.cs
├── Program.cs
└── ProjectName.Web.csproj

# Test project
Tests.ProjectName.Web/
├── Components/
├── Domain/
└── Infrastructure/

Libraries / NuGet Packages

ProjectName/
├── src/
│   └── ProjectName/
│       ├── Models/
│       ├── Services/
│       ├── Interfaces/
│       └── ProjectName.csproj
├── tests/
│   └── ProjectName.Tests/
├── README.md
├── CHANGELOG.md
└── LICENSE

Naming Conventions

Projects:

  • Company.Product.Component (e.g., dci.ErmApi.Console)
  • toolset--service--purpose (e.g., aztools--kubernetes--cluster-shutdown)
  • Consistent pattern within organization

Namespaces:

  • Match directory structure
  • Company.Product.Component.Layer (e.g., dci.ErmApi.Console.Domain.Services)

Files:

  • PascalCase, match class name exactly
  • One class per file

Layer Separation: Infrastructure vs Domain

Infrastructure Layer - Data access, external integrations, infrastructure concerns

Contains:

  • Entities/ - ALL database entities
    • EF Core entity classes
    • CosmosDB/DocumentDB documents
    • Database schema mappings
    • Navigation properties for relationships
  • Messages/ - Integration message DTOs
    • Incoming/ - Service Bus, Event Grid inbound messages
    • Outgoing/ - Service Bus, Event Grid outbound messages
    • External system message formats (lowercase properties matching external format)
  • Realtime/ - SignalR hubs and real-time infrastructure (if applicable)
    • Hub classes
    • Connection management
    • Real-time message models
  • Services/ - Infrastructure services
    • [Entity]Repo.cs - Data access repositories (thin, just DbContext access)
    • Integration/ - External API HTTP clients
    • Utilities/ - Infrastructure utility classes
    • Infrastructure concerns: caching, authentication, message processors
  • [DbContext].cs - EF Core database context

Does NOT contain:

  • Business logic
  • Domain models (unless they ARE the entities)
  • UI components

Domain Layer - Business logic and domain models

Contains:

  • [Feature]/ - Feature-based organization (Employee/, Project/, Security/) - optional
  • Models/ - Rich domain models (optional - only if mapping from entities)
    • Business logic via extension methods
    • Domain-specific behavior
    • NOT database entities
  • Services/ - Business logic services
    • Orchestration between repos and domain logic
    • Business rules and workflows
    • Can depend on Infrastructure services
  • Value Objects - Domain value objects (enums, structs)
  • Business Rules - Authorization logic, permission checks

Does NOT contain:

  • Database entities (those belong in Infrastructure/Entities)
  • Data access code (those belong in Infrastructure repos)
  • External API clients (those belong in Infrastructure/Services/Integration)
  • Integration DTOs (those belong in Infrastructure/Messages)

Migrations/ - EF Core migrations (if applicable)

Location: Project root (sibling to Domain and Infrastructure)

Contains:

  • EF Core migration files
  • Model snapshot

Dependency Direction: Domain → Infrastructure (Pragmatic, not Clean Architecture)

  • Domain services CAN depend on Infrastructure services
  • Domain models CAN use Infrastructure value objects
  • Infrastructure does NOT depend on Domain
  • This is simplified, pragmatic architecture for developer velocity

Example:

using ProjectName.Infrastructure;           // ✅ Allowed
using ProjectName.Infrastructure.Services;  // ✅ Allowed

namespace ProjectName.Domain.Employee;

internal class EmployeeService(
    CacheService cache,                      // Infrastructure service
    EmployeeRepo repo,                       // Infrastructure repo
    ExternalApiService api)                  // Infrastructure integration
{
    // Business logic here - orchestrates Infrastructure services
}

When to use Domain/Models/:

  • Only when mapping from Infrastructure entities to rich domain models
  • Domain models have business logic, behavior, extension methods
  • Not always necessary - many apps work directly with entities

Example mapping:

Infrastructure/Entities/User.cs (EF Core entity - simple data class)
    ↓ (mapped to)
Domain/Models/UserDetails.cs (Rich model with business logic methods)

Architecture Decision Records (ADRs)

When to Create adr/ Directory

Create adr/ directory when project involves:

  • Evaluating multiple technology options (C# vs Python vs Go)
  • Significant architectural patterns (microservices, CQRS, event sourcing)
  • Complex deployment tradeoffs (Kubernetes vs App Service vs Functions)
  • Performance/scalability architecture decisions
  • Security architecture requiring justification
  • Database technology choices (SQL vs NoSQL vs hybrid)
  • Any decision with long-term implications requiring documentation

Skip adr/ directory when project:

  • Follows established patterns without deviation
  • Has obvious technology choice (no alternatives evaluated)
  • Is a simple tool/script (< 500 lines)
  • Has no significant architectural tradeoffs
  • Is straightforward implementation of known patterns

ADR Format

File naming: adr/[number]-[short-title].md

Examples:

  • adr/0001-record-architecture-decisions.md
  • adr/0002-use-csharp-for-backend.md
  • adr/0003-deploy-with-container-app-jobs.md

Template:

# [Number]. [Title]

Date: YYYY-MM-DD

## Status

[Proposed | Accepted | Deprecated | Superseded by ADR-XXXX]

## Context

What is the issue we're seeing that is motivating this decision or change?

## Decision

What is the change that we're proposing and/or doing?

## Consequences

What becomes easier or more difficult to do because of this change?

Example ADR-0001

# 1. Record Architecture Decisions

Date: 2025-01-15

## Status

Accepted

## Context

We need to record the architectural decisions made on this project so that
future team members can understand why certain choices were made.

## Decision

We will use Architecture Decision Records, as described by Michael Nygard in
his article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions

We will keep ADRs in the `adr/` directory in the project repository.

## Consequences

- Architectural decisions will be documented and version controlled
- Team members can understand context behind decisions
- ADRs become part of project documentation

Example ADR-0002 (Technology Stack)

# 2. Use C# with .NET 10 for Backend Implementation

Date: 2025-01-15

## Status

Accepted

## Context

This project requires multiple output delivery methods (file, blob storage,
service bus, webhook), complex schema validation, Azure SDK integration, and
robust error handling. We need to choose a technology that supports these
requirements while maintaining type safety and developer productivity.

## Decision

We will use C# with .NET 10 for the backend implementation.

Alternatives considered:
- **Bash scripting**: Simple deployment but too complex for error handling and
  JSON manipulation at this scale
- **Python**: Good Azure SDK support but lacks strong typing without mypy,
  less familiar to team
- **Go**: Fast and self-contained but less mature Azure SDK, team less experienced

## Consequences

**Positive:**
- Strong typing catches errors at compile time
- Native async/await patterns for concurrent operations
- Mature Azure SDK with full feature support
- Better IDE tooling and refactoring support
- Team has strong C# experience

**Negative:**
- Larger container image compared to Go
- Slightly longer cold start compared to compiled languages
- Requires .NET runtime in container

When to Create Each ADR

Always create if present:

  • ADR-0001: Record architecture decisions (documents decision to use ADRs)

Create when applicable:

  • ADR-000X: Technology stack choice (if evaluated alternatives)
  • ADR-000X: Deployment model (if multiple options considered)
  • ADR-000X: Database choice (if SQL vs NoSQL decision made)
  • ADR-000X: Authentication strategy (if multiple approaches evaluated)
  • ADR-000X: API design patterns (if REST vs GraphQL vs gRPC decision)
  • ADR-000X: State management (if complex client state decisions)

Location:

  • adr/ directory at project root
  • Committed to git repository
  • Referenced in README.md

Testing

Testing Requirements

Tests are mandatory for:

  • All business logic in services
  • Data transformations and validation logic
  • Integration with external systems
  • Complex algorithms and workflows

Testing Implementation

Use the /qa-automation-expert skill for all testing tasks:

  • Test infrastructure setup
  • Test generation and implementation
  • Test analysis and quality assessment
  • Follow InfiniteLoop.Testing.Xunit patterns

Basic test project structure:

ProjectName.Tests/
├── Infrastructure/
│   ├── TestBase.cs
│   ├── Startup.cs
│   └── xunit.runner.json
├── Domain/
│   └── Services/
│       └── The{ServiceName}.cs
├── Outputs/
│   └── The{OutputName}.cs
└── ScenarioTests/
    └── {Workflow}ScenarioTests.cs

Essential requirements:

  • Follow InfiniteLoop.Testing.Xunit patterns (The{ClassName} naming, required attributes)
  • Use Shouldly for assertions
  • Given-When-Then structure with block comments
  • All tests must pass before considering work complete

For comprehensive testing implementation, use: /qa-automation-expert


Versioning Strategies

Conventional Commits

Format:

<type>: all lowercase, single sentence describing change

Types:

  • chore: - build/tooling changes, dependency updates
  • fix: - bug fixes (patch version bump)
  • feat: - new features (minor version bump)
  • docs: - documentation only changes

Rules:

  • ✅ Always lowercase
  • ✅ Always single sentence
  • ✅ No period at end
  • ✅ Descriptive but concise (50 chars or less preferred)

Examples:

✅ feat: add blob storage output delivery
✅ fix: handle null certificate thumbprints gracefully
✅ chore: update azure sdk packages to latest versions
✅ docs: add installation instructions for windows

❌ feat: Add blob storage (capitalized)
❌ fix: Handle null certs. Also update logging. (multiple sentences)
❌ chore: Updated packages (past tense, capitalized)
❌ Feat: add feature (wrong format)

Console Applications (with Auto-Update)

.csproj version configuration:

<PropertyGroup>
  <Version Condition="'$(Version)' == ''">0.0.0</Version>
  <AssemblyVersion>$(Version)</AssemblyVersion>
  <FileVersion>$(Version)</FileVersion>
  <InformationalVersion Condition="'$(InformationalVersion)' == ''">$(Version)-local</InformationalVersion>
</PropertyGroup>

Version retrieval at runtime:

public static string GetVersion()
{
    try
    {
        var assembly = Assembly.GetEntryAssembly();
        var version = assembly?.GetName().Version;
        if (version != null)
        {
            return $"v{version.Major}.{version.Minor}.{version.Build}";
        }
    }
    catch
    {
        // Fall through
    }

    return "unknown";
}

VersionCheckService pattern:

internal sealed class VersionCheckService
{
    private const int CacheTtlMinutes = 5;
    private readonly string _cacheFilePath = Path.Combine(
        Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
        ".appname",
        ".version-check");

    public async Task<VersionCheckResult?> CheckForUpdatesAsync(bool forceRefresh = false)
    {
        var cache = await ReadCacheAsync();

        if (cache != null && !cache.IsExpired() && !forceRefresh)
        {
            // Return cached result, fire-and-forget background refresh
            _ = Task.Run(async () => await RefreshCacheAsync());
            return new VersionCheckResult(
                cache.CurrentVersion,
                cache.LatestVersion,
                cache.UpdateAvailable);
        }

        // Force refresh or cache expired
        return await RefreshCacheAsync();
    }
}

GitHub Actions release workflow:

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    strategy:
      matrix:
        include:
          - rid: osx-arm64
            os: macOS
          - rid: linux-x64
            os: Linux
          - rid: win-x64
            os: Windows

    steps:
      - name: Extract version
        id: version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT

      - name: Build
        run: |
          dotnet publish \
            --runtime ${{ matrix.rid }} \
            --self-contained true \
            /p:PublishSingleFile=true \
            /p:Version=${{ steps.version.outputs.VERSION }}

Containerized Tools

Dockerfile version support:

FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
ARG VERSION=local

WORKDIR /src
COPY ["ProjectName.csproj", "."]
RUN dotnet restore

COPY . .
RUN dotnet publish -c Release -o /app/publish \
    /p:Version=${VERSION}

FROM mcr.microsoft.com/dotnet/runtime:8.0-alpine
ARG VERSION=local
ENV TOOL_VERSION=${VERSION}

WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "ProjectName.dll"]

Build with version:

docker build --build-arg VERSION=1.2.3 -t myimage:1.2.3 .

Version in tool output:

var version = Environment.GetEnvironmentVariable("TOOL_VERSION") ?? "local";
Console.WriteLine($"Tool Version: {version}");

// Or embed in report/output
var report = new Report
{
    ToolVersion = GetVersion(),
    GeneratedAt = DateTime.UtcNow
};

Delivery / Output Patterns

File Output (Always Available)

internal sealed class FileOutput
{
    private readonly string _outputDirectory;
    private readonly string _fileName;
    private readonly ILogger<FileOutput> _log;

    public FileOutput(AppConfig config, ILogger<FileOutput> log)
    {
        _outputDirectory = config.OutputDirectory ?? "/output";
        _fileName = config.OutputFileName ?? "output.json";
        _log = log;
    }

    public async Task WriteAsync<T>(T data)
    {
        Directory.CreateDirectory(_outputDirectory);
        var filePath = Path.Combine(_outputDirectory, _fileName);

        var json = JsonSerializer.Serialize(data, new JsonSerializerOptions
        {
            WriteIndented = true
        });

        await File.WriteAllTextAsync(filePath, json).ConfigureAwait(false);
        _log.LogInformation("Wrote output to {FilePath}", filePath);
    }
}

Configuration:

  • OUTPUT_DIRECTORY - Default: /output
  • OUTPUT_FILENAME - Default: output.json or output.csv

Azure Blob Storage (Optional)

internal sealed class BlobStorageOutput
{
    private readonly BlobServiceClient _blobClient;
    private readonly string _containerName;
    private readonly string _blobName;
    private readonly ILogger<BlobStorageOutput> _log;

    public BlobStorageOutput(AppConfig config, ILogger<BlobStorageOutput> log)
    {
        var connectionString = config.StorageConnectionString;
        _blobClient = new BlobServiceClient(connectionString);
        _containerName = config.StorageContainerName!;
        _blobName = config.StorageBlobName ?? $"output-{DateTime.UtcNow:yyyyMMddHHmmss}.json";
        _log = log;
    }

    public async Task UploadAsync<T>(T data)
    {
        var container = _blobClient.GetBlobContainerClient(_containerName);
        await container.CreateIfNotExistsAsync().ConfigureAwait(false);

        var blob = container.GetBlobClient(_blobName);
        var json = JsonSerializer.Serialize(data);

        using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
        await blob.UploadAsync(stream, overwrite: true).ConfigureAwait(false);

        _log.LogInformation("Uploaded to blob: {BlobName}", _blobName);
    }
}

Configuration:

  • STORAGE_CONNECTION_STRING or STORAGE_ACCOUNT_NAME (with managed identity)
  • STORAGE_CONTAINER_NAME
  • STORAGE_BLOB_NAME (optional, generates timestamp-based name)

Azure Service Bus (Optional)

internal sealed class ServiceBusOutput
{
    private readonly ServiceBusClient _client;
    private readonly string _queueOrTopicName;
    private readonly ILogger<ServiceBusOutput> _log;

    public ServiceBusOutput(AppConfig config, ILogger<ServiceBusOutput> log)
    {
        var connectionString = config.ServiceBusConnectionString;
        _client = new ServiceBusClient(connectionString);
        _queueOrTopicName = config.ServiceBusQueueOrTopicName!;
        _log = log;
    }

    public async Task SendAsync<T>(T data)
    {
        var sender = _client.CreateSender(_queueOrTopicName);
        var json = JsonSerializer.Serialize(data);
        var message = new ServiceBusMessage(json);

        await sender.SendMessageAsync(message).ConfigureAwait(false);
        _log.LogInformation("Sent message to {Destination}", _queueOrTopicName);

        await sender.CloseAsync().ConfigureAwait(false);
    }
}

Configuration:

  • SERVICE_BUS_CONNECTION_STRING or SERVICE_BUS_NAMESPACE (with managed identity)
  • SERVICE_BUS_QUEUE_NAME or SERVICE_BUS_TOPIC_NAME

Webhook / HTTP POST (Optional)

internal sealed class WebhookOutput
{
    private readonly string _webhookUrl;
    private readonly string? _authHeader;
    private readonly HttpClient _httpClient;
    private readonly ILogger<WebhookOutput> _log;

    public WebhookOutput(AppConfig config, HttpClient httpClient, ILogger<WebhookOutput> log)
    {
        _webhookUrl = config.WebhookUrl!;
        _authHeader = config.WebhookAuthHeader;
        _httpClient = httpClient;
        _log = log;
    }

    public async Task PostAsync<T>(T data)
    {
        var json = JsonSerializer.Serialize(data);
        var content = new StringContent(json, Encoding.UTF8, "application/json");

        if (!string.IsNullOrEmpty(_authHeader))
        {
            _httpClient.DefaultRequestHeaders.Authorization =
                AuthenticationHeaderValue.Parse(_authHeader);
        }

        var response = await _httpClient.PostAsync(_webhookUrl, content).ConfigureAwait(false);
        response.EnsureSuccessStatusCode();

        _log.LogInformation("Posted to webhook: {Url}", _webhookUrl);
    }
}

Configuration:

  • WEBHOOK_URL
  • WEBHOOK_AUTH_HEADER (optional, e.g., Bearer token)

Multi-Delivery Orchestration

internal sealed class DeliveryService
{
    private readonly FileOutput _fileOutput;
    private readonly BlobStorageOutput? _blobOutput;
    private readonly ServiceBusOutput? _serviceBusOutput;
    private readonly WebhookOutput? _webhookOutput;
    private readonly ILogger<DeliveryService> _log;

    public async Task DeliverAsync<T>(T data)
    {
        var deliveryTasks = new List<Task>();

        // Always write to file
        deliveryTasks.Add(_fileOutput.WriteAsync(data));

        // Optional deliveries
        if (_blobOutput != null)
            deliveryTasks.Add(_blobOutput.UploadAsync(data));

        if (_serviceBusOutput != null)
            deliveryTasks.Add(_serviceBusOutput.SendAsync(data));

        if (_webhookOutput != null)
            deliveryTasks.Add(_webhookOutput.PostAsync(data));

        // Execute all deliveries concurrently
        try
        {
            await Task.WhenAll(deliveryTasks).ConfigureAwait(false);
            _log.LogInformation("All deliveries completed successfully");
        }
        catch (Exception ex)
        {
            _log.LogError(ex, "One or more deliveries failed: {Error}", ex.Message);
            throw;
        }
    }
}

Documentation Standards

Architecture Decision Records (ADRs)

Purpose: Committed architectural documentation for significant decisions

Location: adr/ directory in project root (committed to git)

Format: Cognitect ADR format (Status, Context, Decision, Consequences)

When to create:

  • Evaluating multiple technology options
  • Significant architectural patterns/decisions
  • Complex deployment tradeoffs
  • Database or framework choices
  • Any decision with long-term implications

When to skip:

  • Straightforward implementations
  • Obvious technology choices
  • Simple tools (< 500 lines)

See ADR Framework section for full details and examples.

README.md

Purpose: User-facing project documentation

Target audience: Competent IT professionals Style: Clear, concise, no pandering, brevity over verbosity

Required sections:

# Project Name

Brief description (1-2 sentences about what this tool/application does)

## Features

- Bullet list of key capabilities
- Each feature on one line
- Focus on user-facing functionality

## Requirements

- Brief list of prerequisites
- No excessive explanation
- Example: "Service principal with Reader role on target subscriptions"

## Configuration

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `VAR_NAME` | Yes | - | Brief description |
| `OPTIONAL_VAR` | No | value | Brief description |

## Usage


\`\`\`bash
# Copy-paste ready examples
docker run --rm \\
  -e VAR1=value \\
  -e VAR2=value \\
  -v $(pwd)/output:/output \\
  image:tag
\`\`\`


\`\`\`bash
# Concise deployment command
az containerapp job create \\
  --name job-name \\
  --resource-group rg \\
  --image image:tag \\
  --env-vars VAR1=value VAR2=value
\`\`\`

## Infrastructure Setup


\`\`\`bash
az ad sp create-for-rbac \\
  --name "sp-name" \\
  --role Reader \\
  --scopes /subscriptions/SUB_ID
\`\`\`


\`\`\`bash
az storage account create \\
  --name storageacct \\
  --resource-group rg
\`\`\`

## Development

\`\`\`bash
# Build
dotnet build

# Test
dotnet test

# Run locally
dotnet run
\`\`\`

## Versioning

Uses conventional commits and semantic versioning.

Commit format: `<type>: lowercase, single sentence`

Types: `chore`, `fix`, `feat`, `docs`

Inline Documentation

XML docs on public APIs:

/// <summary>
///     Brief description of what this class/method does.
/// </summary>
/// <param name="paramName">What this parameter represents.</param>
/// <returns>What is returned.</returns>
/// <exception cref="ExceptionType">When this exception is thrown.</exception>

Code organization:

// Public members - no wrapping

public void PublicMethod() { }

#region Internals

private void HelperMethod() { }

#endregion

Inline comments - only when necessary:

  • Business rule clarifications
  • Non-obvious algorithm explanations
  • Workarounds for external API quirks

Workflow: 9 Phases

Phase 0: Significance Detection & Analysis

Analyze request scope and complexity:

  1. Assess project requirements

    • Count files/classes/services to be created/modified
    • Identify integrations (external APIs, databases, queues)
    • Assess schema/data model requirements
    • Check for deployment/infrastructure changes
  2. Gather requirements

    • Ask clarifying questions if requirements are ambiguous
    • Understand full scope of work
    • Identify technical and business constraints
    • Determine if ADR documentation will be needed

Phase 1: Planning & Architecture

Create architectural foundation:

  1. Assess whether to create adr/ directory

    Create adr/ directory if:

    • Evaluating multiple technology options
    • Significant architectural patterns/decisions
    • Complex deployment tradeoffs
    • Performance/scalability architecture
    • Security architecture requiring justification
    • Database technology choices
    • Any decision with long-term implications

    Skip adr/ directory if:

    • Straightforward implementation following established patterns
    • Obvious technology choice
    • Simple tool/script (< 500 lines)
    • No significant architectural tradeoffs
  2. Make technology stack decision (autonomous)

    • Analyze complexity factors (delivery methods, schema, integrations)
    • Choose C# or Bash based on framework
    • Document decision in adr/0001-technology-stack.md if adr/ directory created
  3. Create todo list

    • Break implementation into phases
    • Track documentation tasks
    • Track implementation progress
  4. Design schema (if applicable)

    • Create strongly-typed records/classes
    • Document in ADR if significant data modeling decisions
  5. Plan integrations

    • Identify authentication approach
    • Plan delivery methods
    • Document in ADRs if complex integration architecture

Phase 2: Project Setup

Establish project structure:

  1. Create directory structure based on project needs

    • Always create: Domain/
    • Create Infrastructure/ if:
      • Using a database (EF Core, CosmosDB)
      • Calling external APIs
      • Using messaging (Service Bus, Event Grid)
      • Using SignalR
    • Create subdirectories as needed:
      • Domain/[Feature]/ for feature-based organization (optional)
      • Domain/Models/ only if mapping from entities to domain models
      • Domain/Services/ for business logic services
      • Infrastructure/Entities/ for database entities
      • Infrastructure/Messages/Incoming/ and /Outgoing/ for integration DTOs
      • Infrastructure/Realtime/ for SignalR hubs
      • Infrastructure/Services/ for repos and infrastructure services
      • Infrastructure/Services/Integration/ for external API clients
      • Infrastructure/Services/Utilities/ for infrastructure utilities
      • Migrations/ at project root if using EF Core
    • For specific project types:
      • Api/ for Azure Functions HTTP triggers
      • Functions/ for Azure Functions non-HTTP triggers
      • Components/ for Blazor UI
      • Outputs/ for delivery mechanisms (console/containerized tools)
    • Create test project structure mirroring Domain/ and Infrastructure/
  2. Initialize .NET project (if C#)

    • Target framework: net8.0 or net10.0
    • Enable nullable reference types
    • Add required NuGet packages:
      • Microsoft.EntityFrameworkCore.SqlServer (if using EF Core)
      • Azure.Identity, Azure.ResourceManager.* (if using Azure SDK)
      • Azure.Messaging.ServiceBus (if using Service Bus)
      • Azure.Storage.Blobs (if using Blob Storage)
    • Create GlobalUsings.cs
    • Create Constants.cs
  3. Initialize Infrastructure layer (if applicable)

    • Create DbContext in Infrastructure/[DbContext].cs
    • Set up connection string loading
    • Configure entity classes in Infrastructure/Entities/
    • Create repository services in Infrastructure/Services/[Entity]Repo.cs
    • Set up EF Core migrations with dotnet ef migrations add Initial
  4. Initialize Domain layer

    • Create feature folders or flat Services/ structure
    • Create domain services in Domain/Services/ or Domain/[Feature]/
    • Create Domain/Models/ only if mapping from entities to rich domain models
    • Domain services depend on Infrastructure repos/services
  5. Create configuration

    • AppConfig class with Keys nested class
    • Environment variable loading
    • Validation in constructor
  6. Create supporting files

    • .gitignore (include .claude/, .vs/, bin/, obj/, *.user, etc.)
    • Initial README.md structure

Phase 3: Core Implementation

Build the application:

  1. Implement models

    • Records for DTOs
    • Classes for domain models
    • JSON serialization attributes
    • Validation attributes
  2. Implement services

    • Business logic with error handling
    • Structured logging throughout
    • Async/await patterns with ConfigureAwait(false)
    • Dependency injection via constructors
  3. Implement delivery/output

    • FileOutput (always)
    • Optional: BlobStorageOutput, ServiceBusOutput, WebhookOutput
    • DeliveryService orchestration
  4. Implement Program.cs

    • Banner/header
    • DI container setup
    • Service registration
    • Execution flow
    • Error handling

Phase 4: Testing

Ensure quality through comprehensive testing:

  1. Use /qa-automation-expert skill for testing implementation

    • Implements InfiniteLoop.Testing.Xunit patterns
    • Creates test infrastructure (Startup.cs, xunit.runner.json, base classes)
    • Generates comprehensive test coverage
    • Ensures all tests follow required patterns
  2. Minimum testing requirements

    • Test project mirrors main project structure
    • All business logic has unit tests
    • Integration tests for external dependencies
    • Scenario tests for workflows (if applicable)
    • All tests must pass
  3. Verify test quality

    • All tests follow InfiniteLoop.Testing.Xunit patterns
    • Proper naming (The{ClassName})
    • Required attributes present
    • Given-When-Then structure
    • Shouldly assertions used

Phase 5: Containerization (if applicable)

Package for deployment:

  1. Create Dockerfile

    • Multi-stage build (build → runtime)
    • ARG VERSION=local for version injection
    • ENV TOOL_VERSION=${VERSION}
    • Optimized layer caching
    • RUN apk update && apk upgrade
  2. Create docker-compose.yml

    • Local testing configuration
    • Volume mounts
    • Environment variables
    • Service definitions
  3. Create makefile

    • build target
    • publish target
    • Version detection (if applicable)
    • Clean targets
  4. Test Docker build

    • Build image locally
    • Run container
    • Validate volume mounts
    • Test all delivery methods

Phase 6: Versioning (if applicable)

Set up version management:

  1. Configure .csproj versioning
<Version Condition="'$(Version)' == ''">0.0.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<InformationalVersion>$(Version)-local</InformationalVersion>
  1. Add version retrieval

    • Assembly metadata retrieval
    • Format as vX.Y.Z
  2. For console apps with auto-update:

    • Implement VersionCheckService (5-minute cache)
    • Implement version command
    • Implement upgrade command
    • Create install scripts (bash, PowerShell)
    • Create GitHub Actions release workflow (multi-platform)
    • SHA256 checksum generation
  3. For containers:

    • Version in Docker tags
    • Version in tool output/reports
    • ENV TOOL_VERSION in Dockerfile

Phase 7: Documentation

Document everything:

  1. Create/update README.md

    • All required sections
    • Copy-paste ready examples
    • Clear, concise instructions
    • Infrastructure setup steps
    • Configuration table
  2. Create/update CHANGELOG.md

    • Initial release entry
    • Follow conventional changelog format
  3. Add inline documentation

    • XML docs on all public APIs
    • Section comments for organization
    • Inline comments only when necessary

Phase 8: Validation & Refactoring

Ensure quality and consistency:

  1. Run all tests

    • Unit tests pass
    • Integration tests pass
    • Validate coverage
  2. Test execution

    • Local (dotnet run or Docker)
    • Validate all features work
    • Test error handling
    • Test all delivery methods
  3. Review code quality

    • Check against style guide
    • Verify nullable annotations
    • Verify ConfigureAwait(false) usage
    • Verify structured logging
  4. Identify refactoring opportunities

    • Modern C# features used?
    • Primary constructors where applicable?
    • Collection expressions instead of new()?
    • Nullable reference types enabled?
  5. Suggest refactoring

    • Proactively suggest modernization
    • Document refactoring tasks in todo list
    • Explain benefits
    • User can accept or decline

Phase 9: Completion

Wrap up the work:

  1. Mark all todos complete

    • Verify every task done
    • Clear completed items
  2. Verify deliverables checklist

    • Code follows style guide ✅
    • Tests implemented and passing ✅
    • README.md comprehensive ✅
    • Documentation synchronized ✅
    • ADRs complete (if applicable) ✅
  3. Provide summary

    • What was built
    • Key decisions made
    • How to use it
    • Next steps (if any)
  4. Note follow-up items

    • Suggested enhancements
    • Known limitations to address
    • Additional testing needed

Refactoring Guidelines

Detection

When editing existing code, automatically analyze:

  1. Nullable reference types enabled?

    • Check .csproj for <Nullable>enable</Nullable>
    • If missing, suggest enabling
  2. Modern C# features used?

    • Primary constructors for simple classes?
    • Collection expressions (= []) vs old syntax (= new())?
    • Init-only properties vs set properties?
    • Records for DTOs?
  3. Logging patterns followed?

    • Structured logging with placeholders?
    • Logger named _log?
    • Never string interpolation in log messages?
  4. Async patterns correct?

    • ConfigureAwait(false) used?
    • Async suffix on method names?
  5. Code organization?

    • One class per file?
    • Section comments for organization?
    • XML docs on public APIs?

Suggestions

Always suggest refactoring when touching code that:

  • Doesn't use nullable reference types
  • Uses old collection initialization (new List() vs [])
  • Doesn't use primary constructors for simple DI classes
  • Uses string interpolation in logging
  • Missing ConfigureAwait(false)
  • Poorly organized (multiple classes per file)

Suggestion format:

I notice this file doesn't use [modern feature].

Current pattern:
[show current code]

Recommended pattern:
[show improved code]

Benefits:
- [benefit 1]
- [benefit 2]

Should I refactor while making these changes? (This is recommended but you can decline)

Document refactoring:

  • Add as separate todo items
  • Explain benefits in comments/ADRs if significant architectural changes

Deliverables Checklist

Before marking work complete, verify:

Code Quality:

  • ✅ Follows C# or Bash style guide
  • ✅ Nullable reference types enabled (C#)
  • ✅ Modern C# features used appropriately
  • ✅ Structured logging throughout
  • ✅ ConfigureAwait(false) on all awaits
  • ✅ Proper error handling

Project Structure:

  • ✅ Matches conventions for project type
  • ✅ One class per file
  • ✅ Organized with Domain/, Services/, Models/
  • ✅ GlobalUsings.cs created
  • ✅ Constants.cs organized with nested classes

Testing:

  • ✅ Tests implemented using /qa-automation-expert skill
  • ✅ All tests passing
  • ✅ InfiniteLoop.Testing.Xunit patterns followed

Documentation:

  • ✅ ADRs documented (if applicable)
  • ✅ README.md comprehensive and clear
  • ✅ All required sections present
  • ✅ Copy-paste ready examples
  • ✅ Infrastructure setup documented
  • ✅ CHANGELOG.md maintained
  • ✅ XML docs on public APIs
  • ✅ Inline comments where necessary

Versioning (if applicable):

  • ✅ .csproj version configuration
  • ✅ Version retrieval implemented
  • ✅ Console apps: version command, upgrade command
  • ✅ Containers: VERSION build arg, TOOL_VERSION env var
  • ✅ Install scripts (if applicable)

Containerization (if applicable):

  • ✅ Dockerfile with multi-stage build
  • ✅ docker-compose.yml for local testing
  • ✅ makefile with build targets
  • ✅ Tested Docker build and execution

CI/CD (if applicable):

  • ✅ GitHub Actions workflow
  • ✅ Required secrets documented in README
  • ✅ Release automation configured

General:

  • ✅ .gitignore appropriate for project type (includes .claude/)
  • ✅ Todo list completed
  • ✅ Refactoring suggestions made
  • ✅ All deliverables synchronized