IronMarten.Bearing 1.0.0

Prefix Reserved
dotnet tool install --global IronMarten.Bearing --version 1.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local IronMarten.Bearing --version 1.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=IronMarten.Bearing&version=1.0.0
                    
nuke :add-package IronMarten.Bearing --version 1.0.0
                    

Bearing

Get your bearings in a .NET codebase.

Bearing reads a solution and gives you two things: a map of the system, and a short list of the components that are unusual for what they are.

dotnet tool install -g IronMarten.Bearing
bearing ./MySolution.sln

Zero configuration. No account. No network call.


Status: 1.0

Bearing analyses. The engine is validated against nopCommerce, Jellyfin and Umbraco, and every threshold it cites was set by measurement on those rather than chosen.

The output is a contract. From 1.0, a breaking change to the command line, the acknowledgment file, the JSON or the CSV is a new major version. The JSON and CSV are stable below says exactly what that covers and what it does not.


What it answers

"Do I understand my system?"

Not "is my code healthy?" — that is a different question, it is well served by other tools, and its audience is architecture specialists. The first question gets asked constantly by people who are not specialists, usually right before they change something.

What you get

A terminal report, which opens with one claim per kind of risk the run found and then describes the structure. Everything else is a flag, and nothing is written unless you ask for it:

--html <file>          one shareable page, self-contained, no network
--diagram <file.svg>   the project map, sized for pasting into chat
--mosaic <file.svg>    every type as one cell
--plot <file.svg>      projects by reach and density
--json <file>          the whole model and every finding, versioned
--csv <dir>            types.csv, members.csv, edges.csv
--full                 enumerate every finding rather than one per kind

bearing --help lists these and every threshold the report cites, each with its default. All of them can be moved.

How it reports

Findings are sentences, not scores. Every measurement exists to support a claim you can act on. If a number does not end in something that changes what someone does, it is not shown.

Nothing absolute is ever the headline. A finding is relative to the peer cohort a component should resemble — discovered structurally from shared interfaces, base types, name suffixes, architectural role, or namespace. "Top 2% of your 56 normalizers" is a claim you can check. "Risk score 103,680" is a claim you can only argue with.

There is no composite score. Not on a dashboard, not in a tooltip, not as a CSV column. This is a deliberate and permanent constraint.

Silence is never a clean bill of health. Every report states what it stayed quiet about — components with no peer group, excluded generated code, projects that failed to load. A tool that quietly says nothing about the riskiest thing in your codebase is worse than no tool.

A finding you have decided about stays decided. Put its key in .bearing/acknowledged beside your solution, commit the file, and the claim stops being made — so a second run tells you what is new rather than repeating what you already looked at. Keys come from --json, where every finding carries the one it is identified by; a note after a tab records why, which is the half that is still worth reading a year later. The report always says how many findings the file kept out of it, because a tool that can be told to go quiet has to say when it has.

One file per solution, beside the solution — not one per repository. A repository with two solutions gets two, and that is the point: what is fine in one is a claim about that analysis, and a single shared file would have to tell them apart by name. It is a line-per-finding text file precisely so that a pull request reviewing it shows what was dismissed and why, in your words, rather than a re-indented array.

Acknowledging is not deleting. The claim stays in --json with its evidence, marked acknowledged, and an entry that stops matching anything — which is what a rename does — is reported rather than dropped.

The JSON and CSV are stable from 1.0. A change that would make a consumer change to keep reading — a field or column removed, renamed or given a new meaning, a flag removed, or a line the acknowledgment file used to accept being refused — is 2.0. Fields, columns and flags can be added in any release, so read the CSV by header rather than by position. --json carries a schemaVersion, independent of the tool's version; an addition moves its minor, and throughout Bearing 1.x its major stays where it is.

What the promise does not cover. Which findings a run reports: a release can refine a detector, and that is most of what releases are for. A finding's key, which can change when its detector changes what it measures — an acknowledgment whose key stops matching is reported as unmatched, never silently dropped. And the terminal report and the HTML page, which are written for people and change freely.

Design constraints

These are not preferences. Each one was learned by building the opposite and watching it produce confident, plausible, wrong output.

  • Every normalized measure carries an absolute floor beside it. Ratios and percentiles discard magnitude, and magnitude is frequently the point.
  • Anomaly, not roll-call. A flag that fires on every member of a category conveys nothing; four controllers reaching into data access is one fact about your layering, not four findings.
  • Never two findings that contradict each other about one component.
  • Never imply safety at a boundary. Bearing cannot see external consumers, so it marks the boundary as unseeable rather than guessing.
  • Blank, never fake. A statistic with no meaningful basis is emitted empty.
  • Name the specifics. "Spans 3 architectural kinds" is arguable; "why is authentication calling TenantStore?" is not.

Non-goals

  • No runtime or traffic observation, ever. Not telemetry, not API keys, not sampling.
  • No composite score, letter grade, or "architecture score 92".
  • No AI-generated explanations. Findings are deterministic and auditable.

Requirements

The .NET 10 SDK. Bearing runs on .NET 10 and loads the solution with the newest SDK the machine has, so a solution that targets net8.0 or net9.0 is analysed as it is — it does not need to move.

Restore the target solution first. A project that loads with unresolved references is missing edges, so fan-in and everything derived from it reads low. Bearing does not fail on this and does not hide it: every report names the projects affected and how many type names did not resolve, and says that the numbers below are a lower bound. It states the reassuring case too — "every project resolved every reference it names" — because the absence of a warning is not the same as being told.

The cause is usually an unrestored solution and is not always: a fully restored project can still name a type nothing declares, and the missing edge is identical. Bearing reports the consequence, which it can see, and stays quiet about the cause, which it cannot.

Licence

Apache-2.0 © Iron Marten

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.0.0 89 9/28/2026
0.1.0 114 8/27/2026