Summary
Add a NuGet dependency analyzer to Fallout that flags redundant and conflicting package references across a solution — available both as a CLI command (dotnet fallout :analyze packages) and as a build-consumable check with configurable severity.
This is conceptually a "snitch" (spectresystems/snitch) reimagined on top of machinery Fallout already owns — and positioned to do strictly more than snitch did.
Motivation
Large solutions accumulate <PackageReference> entries that are no longer necessary:
- the package is already provided by a referenced project, or
- the package is already pulled in as a transitive dependency of another package you reference, or
- the same package resolves to different versions across projects/paths (NuGet silently unifies to the highest, which hides real drift).
Cleaning these up reduces csproj noise, makes the real dependency surface legible, and catches version drift early. Snitch solved part of this for the .NET ecosystem but is unmaintained and, notably, only ever detected project-reference redundancy despite its README claiming package-transitive detection — it never walked the NuGet package graph.
Fallout already has the pieces to do the full job (see "Why Fallout is well-positioned").
Proposed scope (first iteration)
Three detections:
- Project-reference redundancy — a direct
PackageReference already provided by a referenced project (snitch's actual behavior).
- True package-transitive redundancy — a direct
PackageReference that is already a transitive dependency of another package you reference (the thing snitch only claimed to do).
- Version conflict / divergence — the same package resolved at different versions across projects or transitive paths.
Out of scope for v1 (candidate follow-ups): pre-release flagging, dependency-graph export (dot/json), auto-fix/csproj rewriting.
CLI surface
dotnet fallout :analyze packages [<path>] [--tfm <moniker>] [--severity <level>] [--exclude <id>...]
<path> — a .slnx/.sln/.csproj; defaults to the solution/project in the working directory.
--tfm — restrict to one target framework (multi-targeted projects analyzed per-TFM otherwise).
--severity — none|trace|normal|warning|error — controls the log level findings are emitted at and whether the command returns a non-zero exit code (see below).
--exclude — package IDs to ignore (e.g. analyzers, intentionally-pinned transitives).
:analyze is introduced as a command namespace so future analyzers (:analyze deadcode, etc.) slot in alongside.
Build integration & configurable severity
The same analyzer is consumable from a build so a target can enforce hygiene in CI. Severity must be configurable as a log level, not just warn-vs-error:
- emit findings at a chosen
LogLevel (Trace/Normal/Warning/Error), and
- optionally fail the build (treat
Error as build-failing; Warning as advisory).
This maps onto the existing Fallout.Build LogLevel enum + Host.Warning/Host.Error and the build's success determination.
Why Fallout is well-positioned (reuse, not greenfield)
| Need |
Existing asset |
| Resolved transitive package graph (per TFM) |
Fallout.Common.Tooling.NuGetPackageResolver — already reads project.assets.json and walks transitive deps via NuspecReader.GetDependencyGroups |
Declared direct refs + PrivateAssets/AutoReferenced intent |
Fallout.ProjectModel (MSBuild evaluation) |
| Project→project edges |
Fallout.Persistence.Solution (SolutionProjectModel.Dependencies) |
| Graph + cycle/topo utilities |
Fallout.Core/Planning/Graph (Vertex<T>, Tarjan SCC, TopoSort) |
| Severity/logging |
Fallout.Build LogLevel + Host |
| Shared-lib-consumed-by-CLI-and-build precedent |
Fallout.Migrate + Fallout.Migrate.Analyzers |
Data sources (both)
project.assets.json (post-restore) → authoritative resolved transitive graph + unified versions. Required for detections 2 & 3. Implies the target must have been restored; the command will say so if assets are missing.
- MSBuild evaluation → declared direct refs +
PrivateAssets/AutoReferenced so we don't recommend removing things that can't be removed or don't flow transitively.
Correctness guardrails (learned from snitch's edges)
- Exclude
PrivateAssets-suppressed refs from the "provider" set (they don't flow to consumers).
- Exclude
AutoReferenced / SuppressParent=All (SDK implicit refs) — can't be removed from csproj.
- Analyze per-TFM; only recommend removal if redundant across all target frameworks.
- Central Package Management aware (
Directory.Packages.props, no inline versions) — versions come from the resolved graph.
- Distinguish "safe to remove" from "removable but changes resolved version" (snitch's can- vs might-be-removed) — removing a higher pin silently downgrades.
Proposed layout
src/Fallout.NuGet.Analysis/ — core analyzer (project graph build + three detections + result model). No console/build coupling.
src/Fallout.Cli/Program.Analyze.cs — :analyze packages command surface.
- build target wiring (follow-up) — consume the core lib with a configurable
LogLevel.
tests/Fallout.NuGet.Analysis.Tests/ — fixtures mirroring the snitch fixture style (chained project refs + transitive-providing packages).
Acceptance criteria (v1)
Implementation is being prototyped on a fork first and will be refined before landing here.
Summary
Add a NuGet dependency analyzer to Fallout that flags redundant and conflicting package references across a solution — available both as a CLI command (
dotnet fallout :analyze packages) and as a build-consumable check with configurable severity.This is conceptually a "snitch" (spectresystems/snitch) reimagined on top of machinery Fallout already owns — and positioned to do strictly more than snitch did.
Motivation
Large solutions accumulate
<PackageReference>entries that are no longer necessary:Cleaning these up reduces csproj noise, makes the real dependency surface legible, and catches version drift early. Snitch solved part of this for the .NET ecosystem but is unmaintained and, notably, only ever detected project-reference redundancy despite its README claiming package-transitive detection — it never walked the NuGet package graph.
Fallout already has the pieces to do the full job (see "Why Fallout is well-positioned").
Proposed scope (first iteration)
Three detections:
PackageReferencealready provided by a referenced project (snitch's actual behavior).PackageReferencethat is already a transitive dependency of another package you reference (the thing snitch only claimed to do).Out of scope for v1 (candidate follow-ups): pre-release flagging, dependency-graph export (dot/json), auto-fix/csproj rewriting.
CLI surface
<path>— a.slnx/.sln/.csproj; defaults to the solution/project in the working directory.--tfm— restrict to one target framework (multi-targeted projects analyzed per-TFM otherwise).--severity—none|trace|normal|warning|error— controls the log level findings are emitted at and whether the command returns a non-zero exit code (see below).--exclude— package IDs to ignore (e.g. analyzers, intentionally-pinned transitives).:analyzeis introduced as a command namespace so future analyzers (:analyze deadcode, etc.) slot in alongside.Build integration & configurable severity
The same analyzer is consumable from a build so a target can enforce hygiene in CI. Severity must be configurable as a log level, not just warn-vs-error:
LogLevel(Trace/Normal/Warning/Error), andErroras build-failing;Warningas advisory).This maps onto the existing
Fallout.BuildLogLevelenum +Host.Warning/Host.Errorand the build's success determination.Why Fallout is well-positioned (reuse, not greenfield)
Fallout.Common.Tooling.NuGetPackageResolver— already readsproject.assets.jsonand walks transitive deps viaNuspecReader.GetDependencyGroupsPrivateAssets/AutoReferencedintentFallout.ProjectModel(MSBuild evaluation)Fallout.Persistence.Solution(SolutionProjectModel.Dependencies)Fallout.Core/Planning/Graph(Vertex<T>, Tarjan SCC,TopoSort)Fallout.BuildLogLevel+HostFallout.Migrate+Fallout.Migrate.AnalyzersData sources (both)
project.assets.json(post-restore) → authoritative resolved transitive graph + unified versions. Required for detections 2 & 3. Implies the target must have been restored; the command will say so if assets are missing.PrivateAssets/AutoReferencedso we don't recommend removing things that can't be removed or don't flow transitively.Correctness guardrails (learned from snitch's edges)
PrivateAssets-suppressed refs from the "provider" set (they don't flow to consumers).AutoReferenced/SuppressParent=All(SDK implicit refs) — can't be removed from csproj.Directory.Packages.props, no inline versions) — versions come from the resolved graph.Proposed layout
src/Fallout.NuGet.Analysis/— core analyzer (project graph build + three detections + result model). No console/build coupling.src/Fallout.Cli/Program.Analyze.cs—:analyze packagescommand surface.LogLevel.tests/Fallout.NuGet.Analysis.Tests/— fixtures mirroring the snitch fixture style (chained project refs + transitive-providing packages).Acceptance criteria (v1)
dotnet fallout :analyze packagesruns against a.slnx/.csproj, prints a categorized report, returns a severity-driven exit code.