How dead-deps decides a package is unmaintained
Every verdict is a state with sources attached, not a score with a threshold. This page describes what gets read, how it is weighed, what the tool refuses to claim, and the guard that keeps finished packages out of the report.
What gets read
Nothing is scraped and nothing is guessed. For each dependency the tool asks public indexes for a fixed set of facts: the registry’s own status and any deprecation message, the repository URL and whether it is archived, first and latest release dates, total version count, the median gap between releases across the package’s life, dependent counts, downloads, the development distribution score, committer count, issue and pull-request activity over the trailing year, the list of maintainers upstream still considers active, and any open advisories.
Responses are cached on disk for a day by default, so a repeated scan costs nothing upstream. Sources that fail are recorded as failures rather than treated as zeroes — a network error must never read as "no activity".
Every claim carries its source
Each signal that argues for or against a verdict becomes a piece of evidence: a one-line human-readable label, a URL where you can check it, when the datum was observed upstream, and a signed weight. Negative weights argue that the package is alive. An unsourced verdict is treated as a bug in the tool, not as a judgement call.
| Kind | What it means |
|---|---|
registry-deprecation | An explicit deprecation notice on the npm registry. |
repo-archived | The source repository is archived and read-only. |
repo-missing | The repository the package points at no longer resolves. |
release-cadence | The gap since the last release, measured against the package’s own median gap, not a fixed threshold. |
commit-activity | When the repository was last pushed to. |
issue-responsiveness | Issues and pull requests opened, closed and merged over the trailing year. |
bus-factor | How concentrated authorship is; a development distribution score near zero means one person. |
maintainer-activity | Whether upstream still considers any maintainer active. |
security-advisory | Open advisories against the package. |
dependent-flight | Dependents moving away faster than they arrive. |
curated-dataset | A hand-verified row in the succession dataset. |
stability-heuristic | Signals that argue the package is finished rather than abandoned. These carry negative weight. |
The verdict is a state, never a boolean
"Is this package dead" is the wrong question, because the interesting cases are the ones where the honest answer is "it is finished", "it is slowing down", or "we do not know". The tool sorts findings and sets its exit code by severity, so a CI gate can be pointed at exactly the states you care about with --min-state.
| State | Severity | Meaning |
|---|---|---|
active | 0 | Releases, commits or issue responses within the recent window. Nothing to do. |
stable-complete | 1 | Quiet, but finished rather than abandoned: small surface, no open bug pressure, still depended on heavily and still working. Never reported as a problem. |
unknown | 2 | Not enough coverage to judge — an unindexed package, a private registry, or a source that failed. Reported as ignorance, not as a verdict. |
low-activity | 3 | Slower than its own history would predict, with some sign of life. Worth watching, not worth a migration. |
unmaintained | 4 | The release cadence has stopped relative to its own baseline, issues go unanswered, and no maintainer is visibly active. |
deprecated | 5 | The registry or the maintainers say so explicitly. This is a statement of fact from upstream, not an inference. |
abandoned | 6 | Unmaintained, plus a hard signal: an archived repository, a missing repository, or a maintainer statement that the project is over. |
hijack-risk | 7 | Abandoned while still widely depended on, with a small bus factor or an unclaimed publish surface. The supply-chain case, ranked worst on purpose. |
Why "quiet" is not "dead"
The naive detector reads the date of the last release and calls anything old dead. It scores well on obviously dead projects and is still worthless, because a large share of the npm graph is tiny packages that were finished years ago. once, wrappy, inherits, util-deprecate, imurmurhash: they do one small thing, they do it correctly, there is nothing left to add, and so nobody commits to them. They are downloaded hundreds of millions of times a week.
On last-release date alone those packages are indistinguishable from abandonware. Flagging them is not a harmless false positive — it is the failure mode that makes a maintenance tool useless, because a report full of things you must not act on trains you to ignore the report. Recommending that someone migrate off inherits has not found a problem; it has become one.
The stable-complete guard
So "finished" is a first-class verdict with its own guard, and the guard runs before anything is reported. A package is treated as stable-complete rather than unmaintained when the picture is coherent: a long release history that settled down rather than stopped mid-flight; a small, stable API surface; no deprecation notice and no archived repository; no open advisories; no pile-up of unanswered issues relative to the package’s size; and continued heavy use by dependents that have had every opportunity to leave and have not.
The guard is deliberately asymmetric. Any hard signal — a registry deprecation, an archived repository, an open advisory, a maintainer saying the project is over — overrides it immediately, because those are statements of fact rather than inferences from silence. Silence alone can only ever produce stable-complete or low-activity; it can never on its own produce abandoned.
Confidence, and admitting ignorance
Upstream indexes lag. A verdict built on two-year-old issue data has to say so rather than presenting it as current, so every assessment carries a confidence of high, medium or low derived from how much of the signal set was actually available and how fresh it was. When coverage is too thin to judge, the state is unknown and the package is not reported as a problem. "We could not find out" and "it is fine" are different answers and the tool keeps them apart.
Where successors come from
What replaced a package cannot be inferred from metadata, so it is not. The successor half of every report comes from a hand-curated dataset in which each row is checked by a human against primary sources: an npm deprecation notice, a maintainer statement, an archived repository, a README, a release note, or an official migration guide. Blog posts are supporting evidence, never the only evidence.
A row is only added when the succession is consensus rather than opinion. Where reasonable engineers still disagree, the row is marked low confidence and the candidates are listed as alternatives instead of one being crowned. Rows about small, finished, still-working packages are refused outright — the dataset is a map of successions, not a list of packages someone finds unfashionable.
Each row also records how the succession happened, because "a maintained fork exists" and "the platform absorbed it" call for completely different work. All 81 rows are published as individual pages with their evidence.
Calibration
Because the false-positive claim above is the whole value of the tool, it is measured rather than asserted. A hand-labelled corpus of real packages — deliberately adversarial, and weighted toward finished-but-quiet ones — is scored against the detector, and the false-positive rate over the stable-complete bucket is reported separately from overall accuracy. A detector that flags finished packages fails calibration no matter how well it does elsewhere.
| Metric | Value | Basis |
|---|---|---|
| False positives on finished packages | 0.0% | 0 / 13 stable-complete |
| False alarms on anything healthy | 0.0% | 0 / 30 healthy packages |
| Missed dead packages | 4.2% | 1 / 24 genuinely dead |
| Strict accuracy (exact state) | 83.3% | 45 / 54 |
| Lenient accuracy (near misses forgiven) | 87.0% | 47 / 54 |
Corpus: 54 hand-labelled packages, measured on 2026-08-04. The detector is scored on upstream signals alone here — the curated succession dataset is deliberately withheld from it, so these numbers are not inflated by answers it was handed.
What this cannot tell you
- Whether you should migrate. An unmaintained dependency that is vendored, pinned and working may be a perfectly rational thing to keep. The tool reports state; the decision is yours.
- Anything about your own code. Only lockfile entries and public metadata are read. Nothing is uploaded.
- Private packages. Anything the public indexes do not carry comes back
unknown. - Whether a successor is good. The dataset records where the ecosystem went, which is not always where the best-engineered option was.
See it applied
- @angular/httpAbsorbed elsewhere: @angular/common · unmaintained since May 2019
- eslint-loaderSuccessor named by the maintainers: eslint-webpack-plugin · unmaintained since April 2020
- koa-routerRenamed package: @koa/router
- react-addons-test-utilsAbsorbed elsewhere: react-dom · unmaintained since April 2017