Skip to content

Reading the results

How to read the numbers — and, just as important, what to ignore.

The hotspot score

score = rank(change frequency) × rank(complexity)

A value from 0 to 1. Only files that are high on both rise to the top.

SituationScoreWhat it means
Changed often × complexHighHighest return on effort. Start here.
Complex but nobody touches it0Fixing it returns nothing. Leave it alone.
Changed often but simpleLowNot causing you problems.

A file with no changes is always exactly 0

That is deliberate: it stops the tool from recommending code that is complex but stable. Cleaning up something that works and that nobody touches buys you nothing.

It is a rank, not an absolute

The score is a relative position within this repository. "Score 0.9" does not mean "dangerous"; it means "in the top 10% of this repo". It is not comparable across repositories.

Test files in the top ranks are normal

On real data, test files show up high. That is not a bug. Maintaining tests is real cost, and a test that breaks and gets fixed constantly is a genuine problem.

If you only want production code, filter the results.

Coupling degree

degree(A,B) = 2·|A∩B| / (|A|+|B|)

A low degree with a high co-change count still matters. For two very active packages the denominator — their total number of changes — is large, which pushes the degree down.

PairDegreeCo-changesReading
A ↔ B26%90Below the 30% default, but 90 shared commits is not something to ignore

If nothing appears at the default threshold, lower it.

bash
dowse coupling --hidden --min-degree 15

Hidden coupling has two faces

kind: "hidden" only means "these change together and there is no edge between them in the dependency graph". Whether that is a problem depends entirely on what is behind it. Two real examples.

A real problem: duplicated code

@acme/admin-web ↔ @acme/doctor-web   33%, 91 times
  src/lib/server-api.ts ↔ src/lib/server-api.ts   13
  src/app/(authed)/page.tsx ↔ same path            15

Identically named files on both sides, with the same edit copied by hand. A candidate for extraction into a shared package.

Not a problem: a deliberate protocol

@acme/api-contract ↔ @acme/audit-event-names   25%, 49 times
  openapi.json ↔ audit-event-names/src/index.ts   34

This was the convention "when you add an API, add its audit event name" — reasonable by design. Nothing to fix.

Always look at what is behind it

Do not conclude "they are coupled, so fix it" without running dowse why <a> <b>. Numerically, those two examples are the same "hidden coupling".

Code Health and the LoC baseline

Every health score is printed next to the score you would get from lines of code alone.

FileHealthLoC baselineReading
environment-guard.ts4.910.0Short but dense. Counting lines would miss it.
big-but-simple.ts8.08.0No gap — the health score is just saying "this file is big"

When the gap is small, that health score is file length in disguise. Do not read more into it than "this file is large".

Every threshold is a hypothesis

ThresholdDefaultSource
Cyclomatic warning10McCabe / ESLint
Cognitive warning15SonarSource
Minimum coupling degree30%Code Maat
Minor contributor< 5%Bird et al.

Each has a basis in the literature, but none of them is guaranteed to fit your repository. All are configurable. Measure where you are, then calibrate. See Why these metrics for where each number comes from.

Bus factor is about risk, not people

Bus factor 1 does not mean "this person is a bottleneck". It means the knowledge of this package sits with one person, and if they leave, it becomes unmaintainable.

Do not use it to evaluate individuals

Ranking developers by these numbers is explicitly discouraged. This is an indicator for a team to understand structural risk. Tornhill gives the same warning.

The calculation also depends on .mailmap merging identities and on bot commits being excluded. If one person commits under several addresses, dowse will overestimate the bus factor — the package will look safer than it is.

What has been excluded

Every run reports what it removed.

excluded: 6 merge, 0 bot, 1 format-only, 0 ignore-revs (51 large changesets flagged)
KindWhy
Merge commitsThey carry no change of their own
Bot commitsdependabot and friends distort ownership
Format-onlyWhitespace-only edits inflate churn
ignore-revsHonours .git-blame-ignore-revs
Large changesets (> 50 files)Tangled commits fabricate coupling — excluded from coupling only, kept in churn

Generated code (generated/, *.gen.ts) and lockfiles are out of scope by default.

Next

Released under the MIT License