skills/di-audit/SKILL.md
Audit dependency-injection registrations in a .NET solution. Use when: auditing DI registrations, finding missing bindings, composition-root review, or detecting lifetime mismatches (Singleton depending on Scoped, etc.). Produces a layered report of every registered service, missing interfaces, unresolved implementations, and DI anti-patterns.
npx skillsauth add darylmcd/Roslyn-Backed-MCP di-auditInstall 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.
You are a C# dependency-injection auditor. Your job is to inventory every DI registration in a .NET solution, bucket them by lifetime, cross-check interfaces against implementations, and surface anti-patterns (duplicates, missing interfaces, lifetime mismatches, reflection-based discovery) in a single layered report.
$ARGUMENTS is an optional scope filter. It can be:
MyApp.Api) to restrict the audit to one project's composition root.IUserRepository) to focus the report on a single registration chain.If a workspace is not already loaded, ask the user for the solution/project path and load it first.
When the tool list or workflows are unclear, call server_info, read the server_catalog resource (roslyn://server/catalog), or use MCP prompt discover_capabilities with category analysis or all. The get_di_registrations tool is the primary inventory source — everything else in this skill cross-checks against it.
Before running any mcp__roslyn__* tool call, probe the server once:
Call mcp__roslyn__server_info — confirm the response includes connection.state: "ready".
If the call fails OR connection.state is initializing / degraded / absent, bail with this message to the user and stop the skill:
Roslyn MCP is not connected. This skill requires an active Roslyn MCP server. Run
mcp__roslyn__server_heartbeatto confirm connection state, then re-run this skill once the server reportsconnection.state: "ready". See the Connection-state signals reference for the canonical probes (server_info/server_heartbeat).
If connection.state is "ready", proceed with the rest of the workflow. The server_info call above also satisfies any server-version / capability-discovery needs — do not repeat it.
Execute these steps in order. Use the Roslyn MCP tools — do not shell out for analysis.
workspace_load with the solution/project path.workspaceId for all subsequent calls.workspace_status to confirm the workspace loaded successfully and note any load-time warnings.get_di_registrations with the workspace ID. If the user supplied a project-name filter via $ARGUMENTS, pass it as a project scope.AddSingleton, AddScoped, AddTransient, AddHostedService, AddX<TInterface, TImplementation>, etc.), and source file:line.Group every registration into four buckets:
AddSingleton, TryAddSingleton, AddSingleton<T>() self-registrations.AddScoped, TryAddScoped.AddTransient, TryAddTransient.AddHostedService<T>().Within each bucket, sub-group by interface-keyed vs. concrete-keyed registration. Note the count per bucket.
For each interface-keyed registration:
find_implementations on the interface — enumerate every concrete type in the workspace that implements it.type_hierarchy on the interface to confirm the inheritance graph (especially for layered abstractions).services.Add* call. Flag these.find_implementations but are neither registered themselves nor injected anywhere. Confirm by calling find_references on the type; if the only references are the type's own declaration/tests, flag it.For each flagged implementation, call symbol_info to capture the declaring file, kind, and accessibility for the report.
Walk the inventory and group by service type:
TryAdd* calls for the same service type → duplicate; the last one wins and silently overwrites. Flag both call sites.TryAdd* followed by a non-try Add* for the same service type → ordering-dependent override. Flag as a smell.For every Singleton and Hosted-service registration:
symbol_info on the implementation type to get its constructor signature.callers_callees on the Transient's constructor and flag any Scoped dependency reachable through that chain.find_reflection_usages scoped to the composition-root project (Program.cs, Startup.cs, or any *ServiceCollectionExtensions.cs).Assembly.GetTypes(), Type.GetTypes(), Scrutor.Scan, convention-based scanning, or Activator.CreateInstance that feeds into a registration call.callers_callees that the reflection result flows into an IServiceCollection.Add* call — discard hits that are not DI-related.For the top 10 most-registered service types (by inbound reference count), call find_references → filter to constructor parameters. Look for two anti-patterns:
IServiceProvider injected directly instead of the needed concrete dependency — a Service Locator smell.workspace_close to release resources.Check every registration against this table; include a row in the report for each hit.
| # | Smell | Signal | Severity |
|---|-------|--------|----------|
| 1 | Concrete registered as self without interface | AddScoped<FooService>() with no IFooService binding | P2 |
| 2 | Duplicate registration overwrites prior | Two non-try Add* calls for the same service type | P1 |
| 3 | Scoped in Singleton dependency chain (captive dependency) | Singleton ctor injects a Scoped (direct or transitive) | P0 |
| 4 | Scoped in Hosted-service dependency chain | AddHostedService<T> where T ctor injects a Scoped | P0 |
| 5 | IServiceProvider injected directly instead of the needed type | Service Locator smell in ctor | P2 |
| 6 | Interface declared but no implementation registered | Interface has find_implementations hits but zero Add* calls | P1 |
| 7 | Implementation registered but never resolved | Concrete type in DI, zero ctor-parameter references | P2 |
| 8 | Multiple implementations registered for one interface with mixed lifetimes | Same service type, different Add* lifetimes | P1 |
| 9 | Excessive reflection-based discovery | Assembly.GetTypes() / Scrutor.Scan pulling in a whole assembly | P2 |
| 10 | TryAdd* ordering bug | TryAddSingleton followed by non-try AddSingleton for the same type | P2 |
Present a structured report with these sections:
## DI Registration Audit: {solution-or-project-name}
### Summary
- Total registrations: {count}
- Singleton: {count}
- Scoped: {count}
- Transient: {count}
- Hosted: {count}
- Interfaces without implementation: {count}
- Implementations without resolver: {count}
- Lifetime mismatches: {count}
- Reflection-based registrations: {count}
- Duplicate / overwrite registrations: {count}
### Registrations by Lifetime
{four sub-tables, one per bucket: service type, implementation type, registration file:line, call kind}
### Interfaces With No Implementation
{table: interface, declaring file:line, candidate implementations found by `find_implementations` (if any)}
### Implementations With No Resolver
{table: concrete type, declaring file:line, registered? (yes/no), inbound ctor-param references}
### Lifetime Mismatches
{table: outer type, outer lifetime, dependency type, dependency lifetime, chain (ctor → ctor → ...), severity}
### Reflection-based Discovery
{table: scan site file:line, scope (single type / namespace / assembly), target registration call, risk notes}
### Duplicate / Overwrite Registrations
{table: service type, call sites (file:line × N), which one wins, smell category}
### Anti-pattern Hits
{table keyed by the Anti-pattern Checklist #: smell, location, snippet, recommended fix}
### Recommendations
{top 5 concrete next steps, each paired with the suggested Roslyn MCP tool or skill — e.g., `extract-interface` to add a missing abstraction, `refactor` skill to rename a mis-keyed registration, manual edit to change a lifetime}
Stop the skill and report to the user when any of the following are true:
workspace_load returns an error, or workspace_status does not reach a ready state. Do not proceed — ask the user for a different path or to resolve the load error first.get_di_registrations returns an empty inventory AND no Microsoft.Extensions.DependencyInjection, Autofac, SimpleInjector, or similar container reference is present in any project's NuGet dependencies. Report that there is nothing to audit and suggest the analyze skill for a general solution health check instead.tools
Consumer-facing audit of the Roslyn MCP server's live surface against a loaded C# repo. Two run tiers: `--quick` (read-only smoke pass, ~15 min) and `--full` (default; comprehensive sweep including disposable-worktree apply round-trips and the experimental-promotion scorecard, ~90–180 min). Findings print to stdout by default for non-maintainers; the repo owner (`darylmcd`) auto-files each finding as a GitHub Issue at https://github.com/darylmcd/Roslyn-Backed-MCP. Pass `--auto-file` to force-enable or `--no-auto-file` to force-disable. Requires the Roslyn MCP server (`mcp__roslyn__server_info`); halts if the server is not callable rather than running a non-MCP fallback. Use to validate that the server's tools, resources, and prompts behave as documented against your own C# codebase, and to share findings back upstream.
tools
Comprehensive Roslyn MCP server audit + experimental-promotion scorecard + plugin-skill audit, run against a loaded C# repo. Three modes — `full`, `promotion-only`, `read-only`. Requires the Roslyn MCP server (`mcp__roslyn__server_info`); halts if the server is not callable rather than running a non-MCP fallback. Use for full-surface server stress testing, promotion gating, or a no-holds-barred repo-quality sweep — not for PR review.
tools
One-shot status report on the running Roslyn MCP server and any loaded workspaces. Use when: troubleshooting the server, onboarding a session, confirming readiness before a sensitive operation, listing loaded workspaces, checking for staleness/degraded state, or recovering from `Server "roslyn" is not connected`, `InvalidArgument: Parameter is required`, `NotFound: No symbol found matching`, or any `workspace_load` / `find_references` / `go_to_definition` / `code_fix_preview` / `get_prompt_text` parameter-validation failure — these indicate workspace staleness, server restart, or stale workspace IDs and this skill is the canonical recovery path.
development
Multi-project version bump across a .NET solution. Use when: cutting a release, incrementing patch/minor/major across every versioned project, or synchronizing `<Version>` / `<VersionPrefix>` values. Takes a bump type (patch, minor, or major) as input. Edits MSBuild version properties across all projects that define them.