.claude/skills/gen-worker-service/SKILL.md
Use when creating a new .NET Worker Service project for background processing, scheduled tasks, or long-running daemons. Generates a full project scaffold: BackgroundService, options class, health check, and DI registration. Also invoke when the user mentions: worker service, new worker project, dotnet worker, background processor project. Domain: Code Generation, Infrastructure. Level: Intermediate.
npx skillsauth add klod68/littlerae gen-worker-serviceInstall 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.
Generate a Worker Service for ${1:ServicePurpose}.
${2:MyApp}${3:periodic | queue-processor | event-listener}${4:Brief description of what the worker does}${2}.Workers.${1}Worker.cs
/// <summary>
/// Background worker that ${4:description}.
/// </summary>
internal sealed class ${1}Worker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IOptions<${1}Options> _options;
private readonly ILogger<${1}Worker> _logger;
private readonly ${1}HealthCheck _healthCheck;
public ${1}Worker(
IServiceScopeFactory scopeFactory,
IOptions<${1}Options> options,
ILogger<${1}Worker> logger,
${1}HealthCheck healthCheck)
{
ArgumentNullException.ThrowIfNull(scopeFactory);
ArgumentNullException.ThrowIfNull(options);
ArgumentNullException.ThrowIfNull(logger);
ArgumentNullException.ThrowIfNull(healthCheck);
_scopeFactory = scopeFactory;
_options = options;
_logger = logger;
_healthCheck = healthCheck;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var config = _options.Value;
config.EnsureValid();
if (!config.Enabled)
{
_logger.LogInformation("{Worker} is disabled — exiting", nameof(${1}Worker));
return;
}
_logger.LogInformation("{Worker} started with interval {Interval}",
nameof(${1}Worker), config.Interval);
using var timer = new PeriodicTimer(config.Interval);
while (await timer.WaitForNextTickAsync(stoppingToken))
{
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
// Resolve scoped services from scope.ServiceProvider
// ... process one iteration
_healthCheck.RecordSuccess();
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "{Worker} failed during iteration", nameof(${1}Worker));
_healthCheck.RecordFailure();
}
}
_logger.LogInformation("{Worker} stopped", nameof(${1}Worker));
}
}
${2}.Configuration.${1}Options.cs
/// <summary>
/// Configuration for <see cref="${1}Worker"/>.
/// </summary>
public sealed class ${1}Options
{
/// <summary>Whether the worker is enabled.</summary>
public bool Enabled { get; set; } = true;
/// <summary>Interval between processing cycles.</summary>
public TimeSpan Interval { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>Maximum items to process per iteration.</summary>
public int BatchSize { get; set; } = 100;
public void EnsureValid()
{
if (Interval <= TimeSpan.Zero)
throw new ArgumentException("Interval must be positive.", nameof(Interval));
if (BatchSize <= 0)
throw new ArgumentException("BatchSize must be positive.", nameof(BatchSize));
}
}
${2}.HealthChecks.${1}HealthCheck.cs
/// <summary>
/// Reports whether <see cref="${1}Worker"/> is alive and making progress.
/// </summary>
internal sealed class ${1}HealthCheck : IHealthCheck
{
private DateTime _lastSuccess = DateTime.UtcNow;
private readonly TimeSpan _threshold = TimeSpan.FromMinutes(15);
public void RecordSuccess() => _lastSuccess = DateTime.UtcNow;
public void RecordFailure() { /* optional: track consecutive failures */ }
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var elapsed = DateTime.UtcNow - _lastSuccess;
var result = elapsed < _threshold
? HealthCheckResult.Healthy($"Last success {elapsed.TotalSeconds:F0}s ago")
: HealthCheckResult.Unhealthy($"No success for {elapsed.TotalMinutes:F0} minutes");
return Task.FromResult(result);
}
}
${2}.Extensions.${1}WorkerExtensions.cs
/// <summary>
/// Registers the <see cref="${1}Worker"/> background service.
/// </summary>
public static class ${1}WorkerExtensions
{
public static IServiceCollection Add${1}Worker(
this IServiceCollection services,
IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configuration);
services.Configure<${1}Options>(configuration.GetSection("${1}"));
services.AddSingleton<${1}HealthCheck>();
services.AddHealthChecks().AddCheck<${1}HealthCheck>("${1:kebab}");
services.AddHostedService<${1}Worker>();
return services;
}
}
{
"${1}": {
"Enabled": true,
"Interval": "00:05:00",
"BatchSize": 100
}
}
internal sealed and extends BackgroundServiceIServiceScopeFactory, IOptions<T>, ILogger<T>, health check — never scoped servicesPeriodicTimer for interval-based work — never Task.Delay in a loopstoppingToken in all loops and async callsEnsureValid()Enabled flag — disabled workers exit immediatelyworker-service rule fileTask.Run — all work is tracked by the hostThread.Sleep or .Resulttools
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.