agentsclimarketplace

Di audit

Skill darylmcd/Roslyn-Backed-MCP/skills/di-audit

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.From its SKILL.md

Install
npx -y skills add darylmcd/Roslyn-Backed-MCP --skill di-audit

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

12.9 KB, as published. Nobody here has run it

DI Registration Audit

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.

Input

$ARGUMENTS is an optional scope filter. It can be:

  • A project name (e.g., MyApp.Api) to restrict the audit to one project's composition root.
  • A service type or interface name (e.g., IUserRepository) to focus the report on a single registration chain.
  • Empty, in which case audit the whole solution.

If a workspace is not already loaded, ask the user for the solution/project path and load it first.

Server discovery

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.

Connectivity precheck

Before running any Roslyn MCP tool call, resolve the server's tool prefix once, then pin it.

The prefix is client-assigned — never hard-code it. Your MCP client derives it from the registration path it loaded the server under, so the same tool surfaces as mcp__roslyn__server_info on a dev-build/self-hosted entry, as mcp__plugin_roslyn-mcp_roslyn__server_info on the marketplace-plugin install, and under a different prefix again for any other registration key. Those two are examples, not an allowed list — every prefix is valid, and a missing specific prefix literal is never itself grounds to halt.

  1. Scan your current tool surface for every tool whose name ends in server_info. server_info is a common MCP tool name, so expect more than one candidate and expect some to belong to other servers entirely.

  2. Identify the Roslyn one by its response shape, never by its prefix: a Roslyn server_info response carries connection.state, catalogVersion, and surface.*. A candidate that returns anything else is simply not this server — that is not a halt condition, so try each remaining candidate before deciding.

  3. Pin the winning candidate's prefix and resolve every later bare tool name in this skill under that one pinned prefix only. Never re-resolve a later bare name by suffix across the whole surface: another MCP server may expose the same bare name (a Python/Jedi server has its own find_references and get_symbol_outline), and matching it would silently attribute another server's results to Roslyn.

  4. Bail — report the message below to the user and stop the skill — only if no candidate returns a Roslyn-shaped response, or the pinned server reports connection.state as anything other than "ready" (initializing, degraded, absent):

    Roslyn MCP is not connected. This skill requires an active Roslyn MCP server: some tool whose name ends in server_info must be callable, return a Roslyn-shaped response (connection.state, catalogVersion, surface.*), and report connection.state: "ready". The prefix does not matter — your client assigns it from the registration path, so it may be mcp__roslyn__…, mcp__plugin_roslyn-mcp_roslyn__…, or anything else. Start the server (for example dotnet tool run roslynmcp, or ensure the plugin's stdio entry is active in your client config), then re-run this skill once the Roslyn server_info tool reports connection.state: "ready" under whichever prefix your tool surface exposes. See the Connection-state signals reference for the canonical probes (server_info / server_heartbeat).

  5. Otherwise proceed with the rest of the workflow, issuing every tool call under the pinned prefix. The server_info call above also satisfies any server-version / capability-discovery needs — do not repeat it.

Workflow

Execute these steps in order. Use the Roslyn MCP tools — do not shell out for analysis.

Step 1: Load Workspace

  1. Call workspace_load with the solution/project path.
  2. Store the returned workspaceId for all subsequent calls.
  3. Call workspace_status to confirm the workspace loaded successfully and note any load-time warnings.

Step 2: Inventory Registrations

  1. Call get_di_registrations with the workspace ID. If the user supplied a project-name filter via $ARGUMENTS, pass it as a project scope.
  2. Capture the full registration list: service type, implementation type, lifetime (Singleton / Scoped / Transient / Hosted), registration call (AddSingleton, AddScoped, AddTransient, AddHostedService, AddX<TInterface, TImplementation>, etc.), and source file:line.
  3. If zero registrations are found, invoke the refusal condition for "no DI container detected" below.

Step 3: Bucket by Lifetime

Group every registration into four buckets:

  • Singleton — AddSingleton, TryAddSingleton, AddSingleton<T>() self-registrations.
  • Scoped — AddScoped, TryAddScoped.
  • Transient — AddTransient, TryAddTransient.
  • Hosted — AddHostedService<T>().

Within each bucket, sub-group by interface-keyed vs. concrete-keyed registration. Note the count per bucket.

Step 4: Cross-check Interfaces vs Implementations

For each interface-keyed registration:

  1. Call find_implementations on the interface — enumerate every concrete type in the workspace that implements it.
  2. Call type_hierarchy on the interface to confirm the inheritance graph (especially for layered abstractions).
  3. Compare the registered implementation(s) against discovered implementations:
    • Interfaces with no registered implementation — interface declared in the solution but never bound in any services.Add* call. Flag these.
    • Implementations with no resolver — concrete types that appear in 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.

Step 5: Detect Duplicate Registrations

Walk the inventory and group by service type:

  • Two or more non-TryAdd* calls for the same service type → duplicate; the last one wins and silently overwrites. Flag both call sites.
  • A TryAdd* followed by a non-try Add* for the same service type → ordering-dependent override. Flag as a smell.
  • Multiple implementations registered for the same interface with different lifetimes → almost always a bug. Flag.

Step 6: Detect Lifetime Mismatches

For every Singleton and Hosted-service registration:

  1. Call symbol_info on the implementation type to get its constructor signature.
  2. For each constructor parameter, look up its service type in the bucketed inventory.
  3. If a Singleton depends on a Scoped service, flag a captive-dependency bug — the Scoped instance will be captured for the process lifetime and will not honor scope boundaries.
  4. If a Singleton depends on a Transient service that itself depends on a Scoped service, follow the chain with callers_callees on the Transient's constructor and flag any Scoped dependency reachable through that chain.
  5. For Hosted services, apply the same rules as Singleton (they share lifetime semantics for captive-dependency purposes).

Step 7: Flag Reflection-based Registrations

  1. Call find_reflection_usages scoped to the composition-root project (Program.cs, Startup.cs, or any *ServiceCollectionExtensions.cs).
  2. Surface any use of Assembly.GetTypes(), Type.GetTypes(), Scrutor.Scan, convention-based scanning, or Activator.CreateInstance that feeds into a registration call.
  3. For each hit, confirm via callers_callees that the reflection result flows into an IServiceCollection.Add* call — discard hits that are not DI-related.
  4. Excessive reflection-based discovery (more than a handful, or a scan that pulls in an entire assembly) is a smell — note the scope of each scan in the report.

Step 8: Constructor Injection Spot-check

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.
  • Concrete classes injected where an interface exists — indicates a missing registration or a leaky abstraction.

Step 9: Summarize and Close

  1. Assemble the Output Format sections below.
  2. Call workspace_close to release resources.

Anti-pattern Checklist

Check every registration against this table; include a row in the report for each hit.

#SmellSignalSeverity
1Concrete registered as self without interfaceAddScoped<FooService>() with no IFooService bindingP2
2Duplicate registration overwrites priorTwo non-try Add* calls for the same service typeP1
3Scoped in Singleton dependency chain (captive dependency)Singleton ctor injects a Scoped (direct or transitive)P0
4Scoped in Hosted-service dependency chainAddHostedService<T> where T ctor injects a ScopedP0
5IServiceProvider injected directly instead of the needed typeService Locator smell in ctorP2
6Interface declared but no implementation registeredInterface has find_implementations hits but zero Add* callsP1
7Implementation registered but never resolvedConcrete type in DI, zero ctor-parameter referencesP2
8Multiple implementations registered for one interface with mixed lifetimesSame service type, different Add* lifetimesP1
9Excessive reflection-based discoveryAssembly.GetTypes() / Scrutor.Scan pulling in a whole assemblyP2
10TryAdd* ordering bugTryAddSingleton followed by non-try AddSingleton for the same typeP2

Output Format

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}

Refusal conditions

Stop the skill and report to the user when any of the following are true:

  1. Workspace load failed. 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.
  2. No DI container detected. 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.

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.