RepoPilotOpen in app →

khellang/Scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection

Healthy

Healthy across all four use cases

Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 4mo ago
  • 21+ active contributors
  • MIT licensed
Show 4 more →
  • CI configured
  • Tests present
  • Slowing — last commit 4mo ago
  • Concentrated ownership — top contributor handles 60% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/khellang/scrutor)](https://repopilot.app/r/khellang/scrutor)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/khellang/scrutor on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: khellang/Scrutor

Generated by RepoPilot · 2026-05-10 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/khellang/Scrutor shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across all four use cases

  • Last commit 4mo ago
  • 21+ active contributors
  • MIT licensed
  • CI configured
  • Tests present
  • ⚠ Slowing — last commit 4mo ago
  • ⚠ Concentrated ownership — top contributor handles 60% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live khellang/Scrutor repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/khellang/Scrutor.

What it runs against: a local clone of khellang/Scrutor — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in khellang/Scrutor | Confirms the artifact applies here, not a fork | | 2 | License is still MIT | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 136 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>khellang/Scrutor</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of khellang/Scrutor. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/khellang/Scrutor.git
#   cd Scrutor
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of khellang/Scrutor and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "khellang/Scrutor(\\.git)?\\b" \\
  && ok "origin remote is khellang/Scrutor" \\
  || miss "origin remote is not khellang/Scrutor (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
  && ok "license is MIT" \\
  || miss "license drift — was MIT at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "src/Scrutor/ServiceCollectionExtensions.cs" \\
  && ok "src/Scrutor/ServiceCollectionExtensions.cs" \\
  || miss "missing critical file: src/Scrutor/ServiceCollectionExtensions.cs"
test -f "src/Scrutor/ServiceCollectionExtensions.Scanning.cs" \\
  && ok "src/Scrutor/ServiceCollectionExtensions.Scanning.cs" \\
  || miss "missing critical file: src/Scrutor/ServiceCollectionExtensions.Scanning.cs"
test -f "src/Scrutor/ServiceCollectionExtensions.Decoration.cs" \\
  && ok "src/Scrutor/ServiceCollectionExtensions.Decoration.cs" \\
  || miss "missing critical file: src/Scrutor/ServiceCollectionExtensions.Decoration.cs"
test -f "src/Scrutor/ITypeSelector.cs" \\
  && ok "src/Scrutor/ITypeSelector.cs" \\
  || miss "missing critical file: src/Scrutor/ITypeSelector.cs"
test -f "src/Scrutor/ImplementationTypeSelector.cs" \\
  && ok "src/Scrutor/ImplementationTypeSelector.cs" \\
  || miss "missing critical file: src/Scrutor/ImplementationTypeSelector.cs"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 136 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~106d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/khellang/Scrutor"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

Scrutor is a .NET library that provides fluent, assembly-scanning and decorator extensions for Microsoft.Extensions.DependencyInjection. It automates bulk registration of services by scanning assemblies for classes matching specified criteria (e.g., implementing specific interfaces) and automatically wiring them into the DI container with chosen lifetimes, plus a decorator pattern implementation for wrapping already-registered services. Single-package structure: src/Scrutor/ contains the core scanning engine (ServiceCollectionExtensions.Scanning.cs, ImplementationTypeSelector.cs, TypeSourceSelector.cs) and decoration engine (ServiceCollectionExtensions.Decoration.cs, DecorationStrategy.cs subclasses), with test coverage in test/Scrutor.Tests/DecorationTests.cs and KeyedServiceTests.cs. Build artifacts and signing (src/Scrutor/signing.snk) indicate production-grade packaging.

👥Who it's for

.NET developers building dependency injection configurations who need to register dozens of services without writing individual registration statements. Primarily used by architects setting up modular .NET applications using Microsoft.Extensions.DependencyInjection (ASP.NET Core, .NET Generic Host) who want to reduce boilerplate and enforce consistent registration patterns.

🌱Maturity & risk

Production-ready and actively maintained. The codebase shows established patterns (fluent interface chains, comprehensive test suite in test/Scrutor.Tests/, CI/CD via .github/workflows/build.yml), with careful attention to backwards compatibility (global.json specifies target framework versions). The library is widely used in the .NET ecosystem as a standard assembly-scanning tool.

Low risk for production use, but single-maintainer dependency (khellang is the primary contributor). The surface area is well-defined and stable (assembly scanning, decoration), with limited external dependencies (only Microsoft.Extensions.DependencyInjection as a peer). No obvious abandoned signals, though contribution velocity varies. Breaking changes are rare given the fluent API's careful versioning.

Active areas of work

The repo shows recent updates including KeyedServiceTests.cs (indicating .NET 8 keyed service support is being added), and the workflow automation is current. No specific milestone data visible, but the presence of keyed service testing suggests active development tracking Microsoft.Extensions.DependencyInjection feature additions.

🚀Get running

git clone https://github.com/khellang/Scrutor.git
cd Scrutor
dotnet restore
dotnet test

Or to use as a NuGet dependency: dotnet add package Scrutor

Daily commands: This is a library, not an executable. To test locally: dotnet test. To build: dotnet build Scrutor.sln. To package: dotnet pack src/Scrutor/Scrutor.csproj.

🗺️Map of the codebase

  • src/Scrutor/ServiceCollectionExtensions.cs — Main entry point with Scan() and Decorate() extension methods that define the fluent API surface for all scanning and decoration operations.
  • src/Scrutor/ServiceCollectionExtensions.Scanning.cs — Core scanning logic implementing assembly reflection, type filtering, and service registration strategies that power assembly-scanning functionality.
  • src/Scrutor/ServiceCollectionExtensions.Decoration.cs — Decoration strategy implementation enabling service wrapping and chaining with open generic and closed type support.
  • src/Scrutor/ITypeSelector.cs — Primary fluent interface abstraction that orchestrates the scanning pipeline from assembly selection through lifetime binding.
  • src/Scrutor/ImplementationTypeSelector.cs — Concrete implementation of type filtering and service mapping logic that translates reflection metadata into DI registrations.
  • src/Scrutor/ReflectionExtensions.cs — Utilities for runtime type inspection, interface matching, and generic type compatibility checking across the codebase.

🛠️How to make changes

Scan an assembly and auto-register matching services

  1. Call Scan() extension on IServiceCollection to start the fluent pipeline (src/Scrutor/ServiceCollectionExtensions.cs)
  2. Chain assembly selection (e.g., FromAssemblyOf<T>()) using ITypeSourceSelector (src/Scrutor/TypeSourceSelector.cs)
  3. Filter implementation types using AddClasses() and conditions like Where(), WithoutAttribute() (src/Scrutor/ImplementationTypeSelector.cs)
  4. Map services using AsImplementedInterfaces(), AsSelf(), or custom mapping via AsMatchingInterface() (src/Scrutor/ServiceTypeSelector.cs)
  5. Bind lifetime via WithLifetime() or specific methods like WithSingletonLifetime() (src/Scrutor/LifetimeSelector.cs)

Decorate a registered service with a wrapper

  1. Call Decorate<TService>() on IServiceCollection to start decoration pipeline (src/Scrutor/ServiceCollectionExtensions.Decoration.cs)
  2. Choose decoration strategy: With<TDecorator>() for closed types or With(typeof(OpenGeneric<>)) for generics (src/Scrutor/DecorationStrategy.cs)
  3. For closed types, decorate implementation uses ClosedTypeDecorationStrategy (src/Scrutor/ClosedTypeDecorationStrategy.cs)
  4. For open generics like ILogger<>, use OpenGenericDecorationStrategy for proper type matching (src/Scrutor/OpenGenericDecorationStrategy.cs)
  5. Verify decoration behavior matches expected chain via ServiceProviderExtensions or test fixtures (test/Scrutor.Tests/DecorationTests.cs)

Register services with attribute-driven metadata

  1. Decorate service class with [ServiceDescriptor] attribute specifying lifetime and interfaces (src/Scrutor/ServiceDescriptorAttribute.cs)
  2. Include assembly with Scan() and configure to use AttributeSelector (src/Scrutor/AttributeSelector.cs)
  3. Attribute metadata overrides default scanning rules for that type during registration (src/Scrutor/ServiceCollectionExtensions.Scanning.cs)

Handle generic service registrations and compatibility

  1. Check generic type constraints using ReflectionExtensions utility methods (src/Scrutor/ReflectionExtensions.cs)
  2. For open generics, verify ImplementedInterfaces match via TypeMap logic (src/Scrutor/TypeMap.cs)
  3. Apply lifetime and registration strategy via RegistrationStrategy and ServiceDescriptorExtensions (src/Scrutor/RegistrationStrategy.cs)

🔧Why these technologies

  • Microsoft.Extensions.DependencyInjection — Standard .NET DI abstraction; Scrutor extends IServiceCollection to integrate seamlessly with built-in framework patterns.
  • System.Reflection & System.Runtime — Required for runtime assembly discovery, type matching, and generic type constraint validation during scanning.
  • Fluent API design (ITypeSelector, ILifetimeSelector, etc.) — Chainable method calls provide readable, compile-time-safe DSL for complex scanning and decoration scenarios without string-based configuration.
  • Strategy pattern (DecorationStrategy subclasses) — Encapsulates different decoration approaches (closed vs. open generic) allowing runtime selection based on service type properties.

⚖️Trade-offs already made

  • Scan at registration time (reflection at startup) vs. lazy-load type discovery

    • Why: Eager scanning finds registration errors early and is more predictable; avoids runtime reflection surprises.
    • Consequence: Slower application startup for large assemblies; not suitable for dynamic plugin architectures that load assemblies post-startup.
  • Fluent API requires method chaining; pipeline state is immutable between calls

    • Why: Prevents accidental mutation of selector state; each method returns a new context for the next step.
    • Consequence: Slightly more object allocations; developers must understand the pipeline structure to debug or extend.
  • Decoration wraps existing registrations by replacing them in IServiceCollection

    • Why: Allows runtime composition without modifying original
    • Consequence: undefined

🪤Traps & gotchas

Assembly scanning uses reflection and must load types from specified assemblies—ensure assemblies are actually in the load path or ScanErrors will occur. Decoration order matters: DecoratedService.cs maintains a chain, and registering decorators in the wrong order creates incorrect wrapping. Generic type matching (e.g., IOpenGeneric<> vs IOpenGeneric<T>) requires exact syntax—trailing commas in type parameters can break matching. The library respects existing registrations; calling Scan() on already-registered types may trigger DuplicateTypeRegistrationException depending on RegistrationStrategy chosen. Keyed services (newer feature in KeyedServiceTests.cs) require .NET 8+.

🏗️Architecture

💡Concepts to learn

  • Fluent Builder / Fluent Interface — The entire Scrutor API (Scan → FromAssemblyOf → AddClasses → As → WithScopedLifetime) is a fluent chain; understanding this pattern is essential to reading and extending the codebase, especially the I*.cs interface chain
  • Assembly Scanning (Reflection-based Type Discovery) — The core feature of Scrutor relies on .NET reflection to enumerate types in loaded assemblies and match them against predicates; ReflectionExtensions.cs is where this happens, and understanding performance implications (reflection is slower than explicit registration) is critical
  • Decorator Pattern (Structural Design Pattern) — Scrutor's Decorate() feature implements the classic decorator pattern for wrapping service implementations; DecorationStrategy.cs and its subclasses show how this is applied to DI-registered services at composition time
  • Generic Type Constraints & Open Generics — Scrutor supports scanning and decorating open generic types like IQueryHandler<TQuery, TResult>; the type matching logic in ImplementationTypeFilter.cs and OpenGenericDecorationStrategy.cs handles variance and constraint checking
  • Service Lifetime Management (Transient, Scoped, Singleton) — Scrutor's WithTransientLifetime(), WithScopedLifetime(), WithSingletonLifetime() methods bind lifetime choices; misunderstanding these leads to common bugs (e.g., singleton containing scoped services violates DI principles)
  • Service Registry / Service Descriptor — Scrutor manipulates IServiceCollection as a registry of ServiceDescriptors; TypeMap.cs and ServiceDescriptorExtensions.cs manage tracking and validation of registered types, crucial for understanding duplicate detection and decoration ordering
  • Keyed Services (.NET 8+) — The newer KeyedServiceTests.cs indicates Scrutor now supports .NET 8's keyed services feature (registering multiple implementations of the same interface with distinct keys); this changes how decoration and scanning must be coordinated
  • AutofacContrib/Autofac.Modularity — Alternative assembly scanning approach using Autofac container; users evaluating DI solutions often compare Scrutor against Autofac's module system
  • jbogard/MediatR — Common companion library in .NET apps; Scrutor is frequently used to scan and register MediatR handlers, command validators, and behaviors automatically
  • dotnet/runtime — Home of Microsoft.Extensions.DependencyInjection; Scrutor is a direct extension of that ecosystem and tracks its keyed services and other DI feature additions
  • NServiceBus/NServiceBus — Enterprise messaging framework that also performs assembly scanning for handlers; architectural parallel for understanding reflection-based registration at scale
  • khellang/Versionize — Same author's semantic versioning tool; indicates the Scrutor maintainer's commitment to production-grade package releases and changelog automation

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add comprehensive tests for KeyedServiceTests.cs edge cases and keyed service decoration

The repo has KeyedServiceTests.cs in the test suite, but given that keyed services are a relatively newer feature in Microsoft.Extensions.DependencyInjection (added in .NET 8), the test coverage likely lacks scenarios for decorating keyed services, scanning with keyed lifetime selectors, and interaction between keyed and non-keyed registrations. This is high-value because keyed services represent a significant feature gap that contributors could thoroughly test.

  • [ ] Review existing KeyedServiceTests.cs to identify gap areas
  • [ ] Add tests for decorating keyed services with ServiceCollectionExtensions.Decoration.cs
  • [ ] Add tests for scanning assemblies and registering keyed services via ImplementationTypeSelector.cs
  • [ ] Test edge cases: mixing keyed/non-keyed, multiple decorators on keyed services, lifetime validation
  • [ ] Ensure tests cover both open generic and closed type keyed decoration strategies

Refactor ServiceCollectionExtensions into separate feature files by responsibility

ServiceCollectionExtensions.cs is a large entry point file that delegates to ServiceCollectionExtensions.Scanning.cs and ServiceCollectionExtensions.Decoration.cs. However, there's no separate extension file for validation/preconditions or service provider extensions. The file ServiceProviderExtensions.cs exists but may be incomplete. This refactoring would improve maintainability and make the entry points clearer for new contributors.

  • [ ] Audit ServiceCollectionExtensions.cs to identify all public extension methods and their purposes
  • [ ] Create ServiceCollectionExtensions.Validation.cs to isolate Preconditions.cs usage and validation extension methods
  • [ ] Move or enhance ServiceProviderExtensions.cs to include helpers for resolving decorated/scanned services
  • [ ] Update documentation strings in each file to clarify the responsibility of each extension class
  • [ ] Verify all existing tests pass and add integration tests in ServiceCollectionExtensions.cs (test file) for cross-file scenarios

Add GitHub Actions workflow for multi-target framework validation and NuGet package metadata

The repo has build.yml in .github/workflows, but there's no explicit workflow validating that the package works correctly across all target frameworks (likely net6.0, net8.0+ based on DI feature support), and no workflow validating NuGet package metadata/symbols before release. This is valuable because it catches framework-specific bugs and packaging issues early, and is concrete to this repo's multi-framework nature.

  • [ ] Review Scrutor.csproj to identify all TargetFrameworks
  • [ ] Create or enhance .github/workflows/build.yml to test against each framework explicitly
  • [ ] Add a new .github/workflows/nuget-validation.yml that runs dotnet pack and validates package contents, symbols availability, and metadata
  • [ ] Ensure workflow validates that ServiceDescriptorAttribute.cs and other attributes work across framework versions
  • [ ] Test that the keyed service APIs only surface on compatible frameworks (conditional compilation)

🌿Good first issues

  • Add comprehensive XML documentation comments to public interfaces in src/Scrutor/I*.cs files (IAssemblySelector.cs, IImplementationTypeSelector.cs, etc.); the fluent API is powerful but underdocumented at the interface level, and IDE intellisense would greatly improve onboarding
  • Expand test/Scrutor.Tests/DecorationTests.cs with a test case validating decorator behavior when the target service is registered multiple times with different keys (relates to the newer keyed service feature in KeyedServiceTests.cs but currently untested for decoration scenarios)
  • Create helper extension methods in src/Scrutor/TypeSelectorExtensions.cs for common filtering patterns like 'AssignableToAny(params Type[])' and 'NotAssignableTo<T>()'; these are implied by the README examples but not exposed, forcing users to write custom predicates

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 7f315da — Pass API key (khellang)
  • 0da76bd — Pass username (khellang)
  • bf1a8cc — Publish to NuGet.org (khellang)
  • d622284 — add tests and fix service key detection issue (amirhesamph81)
  • 4e6e048 — Add keyed service registration support (A-Ebrahimzadeh-dev)
  • 57b8ace — Add support for exposing decorated services (adamjones2)
  • 2cdfc8d — Add support for service key registration (mokarchi)
  • a68a193 — Remove hardcoded version prefix (khellang)
  • ce444a5 — Integrate MinVer for Git-based versioning (claude)
  • c066306 — Include symbol packages (.snupkg) in artifact uploads (claude)

🔒Security observations

Scrutor is a well-structured NuGet package focused on dependency injection and assembly scanning. No critical or high-severity vulnerabilities were identified. The codebase appears to follow security best practices with proper use of interfaces, exception handling, and no apparent injection points. The only concern is the presence of a signing key file in the repository, which is a low-severity risk. No hardcoded secrets, SQL injection points, or infrastructure misconfigurations were detected. The project maintains clean code organization with appropriate separation of concerns and comprehensive test coverage.

  • Low · Signing Key File Present in Repository — src/signing.snk. The file 'src/signing.snk' indicates a strong name signing key is stored in the repository. While this is sometimes necessary for open-source projects, it represents a potential security risk if the private key is ever compromised, as it could be used to create malicious assemblies that appear to come from the original author. Fix: Consider using delayed signing during development and storing the actual signing key in a secure location (e.g., Azure Key Vault, GitHub Secrets) rather than in the repository. Document the key management strategy clearly.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · khellang/Scrutor — RepoPilot