richlander

dotnet-inspect-compatibility

Decide whether a change or upgrade is safe — API-surface diffs, behavioral diffs (allocations, exceptions), feature switches, and version resolution.

richlander 147 10 Updated 1d ago
GitHub

Install

npx skillscat add richlander/dotnet-inspect/dotnet-inspect-compatibility

Install via the SkillsCat registry.

SKILL.md

dotnet-inspect: compatibility and change analysis

Use this skill to decide whether a change is safe to adopt: what changed between
two versions and what surface a library exposes. The scenario crosses commands —
diff for change, library/package for the surface a single version exposes.

dnx dotnet-inspect -y -- <command>

Did the API surface change?

diff compares a version range from a package, a platform (in-box) library, or
two local builds. Pick the lens for the question you are answering:

dnx dotnet-inspect -y -- diff --package System.Text.Json@9.0.0..10.0.0 --breaking
dnx dotnet-inspect -y -- diff --platform System.Runtime@9.0.0..10.0.0 --additive
dnx dotnet-inspect -y -- diff --library old/Foo.dll..new/Foo.dll --changed

--breaking for migration work, --additive for release notes, --changed
for in-place member changes, --name-only for a quick list. Narrow with
-t TypeName; widen with --all.

Did runtime behavior change? (allocations, exceptions)

-S "Analysis Diff" compares body-level signal deltas between the two
versions, not just the API shape. Rows are Member | Signal | Old | New | Delta, where Signal covers allocations, copies, reflection, throws,
catches, finallys, unsafe, constructed-exceptions, and optimization
shapes. This is how you catch an allocation regression or a change in exception
coverage across versions. (For what these signals mean on a single version, see
the performance and correctness skills.)

dnx dotnet-inspect -y -- diff --package Foo@1.0.0..2.0.0 -S "Analysis Diff"
dnx dotnet-inspect -y -- diff --library old/Foo.dll..new/Foo.dll -S "Analysis Diff" --changed

Use Analysis Diff for aggregate regression triage. To confirm whether one
allocation occurrence was introduced at a caller-selected boundary, resolve one
method and request the native Analysis Finding pairs:

dnx dotnet-inspect -y -- diff --package Foo@1.4.0..1.5.0 \
  -t Foo.Parser -m Parse \
  --finding analysis.allocation

PairFinding.Added with Old=absent and New=present confirms allocation
onset. Present, Removed, and Changed remain distinct; do not infer onset
from an aggregate allocation-count delta.

For a direct-call boundary in one caller method, select the call-site producer:

dnx dotnet-inspect -y -- diff --package Foo@1.4.0..1.5.0 \
  -t Foo.Parser -m Parse \
  --finding analysis.call-site

Rows identify the callees. PairFinding.Added confirms a new direct-call
occurrence; Changed reports retained-call facet changes such as moving into a
loop.

For a definite unsafe-operation boundary in one method, select the unsafety
producer:

dnx dotnet-inspect -y -- diff --package Foo@1.4.0..1.5.0 \
  -t Foo.Parser -m Parse \
  --finding analysis.unsafety

Rows identify unsafe operation kinds and details. PairFinding.Added confirms
introduction; Present and Removed distinguish persistence from
disappearance without treating endpoint-local IL offsets as identity.

Did the implementation change? (decompiled C# + IL + PDB Source)

-S "Implementation Diff" selects Research-composed body evidence instead of
the default API compatibility view. Rows identify the member, producer (C#,
IL, or PDB Source), change kind, and producer-owned evidence. C# is
decompiled text; PDB Source is Portable-PDB-selected, checksum-verified text
acquired locally or through SourceLink. The lanes are peers: PDB-source absence
or failure stays visible and never replaces the C# lane. Narrow with -t and
-m; use --table, --tsv, or --jsonl for columnar output.

dnx dotnet-inspect -y -- diff --library old/Foo.dll..new/Foo.dll \
  -S "Implementation Diff" --pdb-source --repo /path/to/Foo \
  -t MyType -m HotPath

--repo requires a fully qualified clone path. For
raw.githubusercontent.com SourceLink URLs, it reads the committed blob at the
SourceLink commit and verifies it against the PDB checksum before fetching the
source body remotely. Package or PDB acquisition may still use the network;
other SourceLink hosts do not use the local-repository path. Treat these rows
as implementation evidence, not semantic-equivalence proof.

For one explicitly selected method and one library per endpoint,
--pdb-source compares authored source even when C# and IL are unchanged.
For example, changing 1 + 2 to 3 can change Source without changing either
compiled lane. The selected Source lane also reports unchanged or unavailable
evidence, retaining acquisition failures rather than treating missing text as
a deletion. Broader selections still enrich the locally changed members.

What can be configured? (feature switches)

-S Switches on library or package --library reports the behavior and
trim/AOT knobs: [FeatureSwitchDefinition]s, runtime host configuration
options, and AppContext switches.

dnx dotnet-inspect -y -- library System.Text.Json -S Switches

Which versions to compare

Version resolution is source-scoped. Online Foo --version and
Foo --latest-version resolve the newest listed version from fresh, complete
discovery across all eligible configured sources, without legacy candidate
cache reuse. Use
Foo --versions [N] (add --preview) to list published versions. Unlisted
versions are hidden unless
--include-unlisted is explicit. --versions-with-feed retains each
version/feed pair when source identity matters. Source declaration order is not
precedence; load the private-feeds skill for source and credential workflows.
Pin with @: Foo@9.0.0, Foo@latest.

For caller-driven onset or bisect work, resolve an inclusive addressable vector,
then probe only the cells you choose:

dnx dotnet-inspect -y -- package Foo@1.0.0..2.0.0 --versions
dnx dotnet-inspect -y -- type TargetType --package Foo@1.0.0..2.0.0 --at '#5'
dnx dotnet-inspect -y -- member TargetType TargetMember --package Foo@1.0.0..2.0.0 --at 1.6.0
dnx dotnet-inspect -y -- timeline --package Foo@1.0.0..2.0.0 \
  --type TargetType --members --at first --at last
dnx dotnet-inspect -y -- timeline --package Foo@1.0.0..2.0.0 \
  --type TargetType --member TargetMember \
  --finding analysis.unsafety --at first --at last

--at accepts an exact version, one-based #N, first, or last. Vector
resolution does not download every package; only the selected probe is
acquired. The agent owns the search policy and bound. For recurrence-safe
current onset, walk backward from the bad version until the first successful
absence; use binary search only for a predicate known to be monotonic.

timeline renders Evaluations and Transitions over the same vector. Omit
--at for a zero-payload address view and midpoint recommendation, repeat
--at for sparse probes, or pass --at all for explicit dense traversal.
Choose the type-focused census with --type-presence, --members, or
--attributes (aliases for api.type, api.member, and api.attribute).
Add --member to api.member for one exact member identity track. The same
member selector scopes analysis.allocation, analysis.call-site, and
analysis.unsafety timelines to one method body.
Gap-spanning transitions are evidence across the selected probes, not claims
about the exact introduction or removal version.

The range and point probes identify a candidate boundary. Confirm the adjacent
pair with Metadata's real Finding comparison rather than inferring introduction
from probe text:

dnx dotnet-inspect -y -- diff \
  --package System.Text.Json@8.0.6..9.0.0 \
  -t System.Text.Json.Schema.JsonSchemaExporter \
  -S "Finding Transitions"

An introduction boundary is a row with PairFinding.Added, Old=absent, and
New=present. PairFinding.Present means the target exists at both endpoints;
for a type target, no row means it exists at neither. Use -m Type.Member:1 for
an API member boundary. Use --finding analysis.allocation,
--finding analysis.call-site, or --finding analysis.unsafety with exactly
one method target for the corresponding Analysis boundary.
Use --finding csharp.line or --finding il.op with exactly one method target
to inspect native implementation-census transitions. Those lenses preserve
complete, absent, and failed outcomes, including added or removed methods.