OSPulse

How the Package Health Index is scored.

OSPulse scores open-source packages from 0 to 100. Every score comes from one deterministic function, published in full below, run against public registry and vulnerability data. Nothing is scored by opinion, by a model, or by hand.

The same function runs in the free health-check tool and at build time for every page in this index, so a score you see here is the same score you’d get pasting the same package into the tool yourself.

Every package starts at 100 and loses points on five signals.

The result is clamped to a 0-100 range and rounded to a whole number. Four of the five signals can cost points, one is informational only. There’s no weighted average and no hidden multiplier, the deductions below are the entire model.

01

Last release recency

0 to -45

Days since the newest recorded release. 90 days or fewer costs nothing. 91-180 days is "quiet" (-5). 181-365 days is "slowing" (-15). 366-730 days is "dormant" (-30). Over 730 days is "likely abandoned" (-45), the single largest deduction in the model.

02

Release cadence collapse

0 or -15

Only evaluated once a package has at least 4 recorded releases, because a median gap needs a real sample to mean anything. The algorithm takes the median gap between historical releases, then checks the gap since the last one against it. If today’s gap exceeds both 180 days and 3.5x the historical median, that reads as a genuine collapse in velocity rather than a normal lull, and costs a flat 15 points.

03

Maintainer bus factor

0 to -15, npm only

A single listed maintainer costs 15 points. Two costs 5. Three or more costs nothing. PyPI’s registry API doesn’t expose a maintainer count at all, so PyPI packages skip this signal entirely rather than being scored on a guess. That’s a limitation, not a finding, see below.

04

Known vulnerabilities

0 to -45

Every advisory OSV.dev returns for the package costs 8 points, with a further 14 for each one rated critical and 7 for each rated high, capped at a 45-point deduction regardless of how high the raw total runs. Each advisory’s OSV or GHSA identifier is listed on the package’s own page.

05

Package age

informational, 0

Years since first release and total release count are shown for context on every page. Age doesn’t move the score in either direction. A package isn’t penalised for being young, or rewarded for being old.

Worked example: a package with no release in 400 days (-30), a single listed maintainer (-15) and two open advisories including one critical (2 x 8 + 1 x 14 = 30, so -30) lands at 100 - 30 - 15 - 30 = 25, a critical-risk score. Its own page shows each of those rows with the same numbers.

Three public sources, nothing proprietary.

Release history and maintainer counts come straight from the registry each package is published on: the registry.npmjs.org JSON endpoint for npm packages, the pypi.org JSON endpoint for PyPI packages. Security advisories come from OSV.dev, the open-source vulnerability database that aggregates GitHub Security Advisories, PyPA advisories and more under a single API.

All three are queried directly, at build time, with no intermediate cache beyond a per-day file OSPulse keeps to avoid re-hitting a free API on every rebuild. Anyone can run the same three requests for the same package and check the numbers themselves, that’s the point of publishing the method.

Rebuilt every Monday, and on every deploy.

A scheduled rebuild runs every Monday at 06:00 UTC, re-fetching fresh data from all three sources for every package in the index and redeploying the site with updated scores. It runs whether or not any code changed that week, because the point of the schedule is fresh data, not fresh code.

The same rebuild also runs on every push to the live branch, so a code change and a data refresh can land together. Either way, a score reflects data as of its most recent build, at most a week old.

35 packages, not every popular one.

A package earns a page here only when there’s something to say about it: a health score of 35 or below, or 20 or more recorded OSV advisories, plus a short hand-picked list of famous supply-chain incidents (ua-parser-js, node-ipc) that the numbers alone would miss.

The index used to publish 119 pages. Cutting it to today’s 35 removed 84 packages that were healthy, advisory-free, and scored 84 on average, pages that ran to around 580 words of boilerplate restating a clean bill of health. Google declined to index most of them, which is a fair verdict: a page that says "this is fine" in six different ways isn’t worth a click. A healthy package you’re checking on won’t have a page here, and that absence is itself informative, run it through the free health check instead.

The known failure modes.

A scoring model built from a handful of signals will always have blind spots. These are the ones this one has, stated plainly rather than left for you to find.

Low velocity isn’t the same as abandonment

The recency signal reads a long gap since the last release as a warning, and for most packages that’s the right call. But a small, feature-complete library that does exactly what it needs to and hasn’t needed a release in a year isn’t unmaintained, it’s finished. The score can’t currently tell "nothing left to fix" apart from "nobody’s looking". Read the evidence, not just the number, before writing a package off.

npm and PyPI aren’t scored on the same signals

Maintainer bus factor only applies to npm, because PyPI’s registry doesn’t expose who maintains a package. That means a PyPI score can’t be marked down for a bus-factor-of-one the way an equivalent npm package would be, not because the risk isn’t there but because the data isn’t. Don’t read a PyPI package’s clean bus-factor row as healthier than an npm package’s penalised one, it means something different.

A dependency can be healthy in one ecosystem and shaky in a sibling one

Plenty of projects ship the same functionality as both an npm package and a PyPI package, maintained separately, at different paces, by different people. This index scores each package on its own registry history. It doesn’t infer one ecosystem’s health from the other’s.

Advisory counts follow OSV’s coverage, not the package’s true vulnerability history

A score of "0 known advisories" means OSV.dev has nothing published and indexed for that package today, not that the package has never had a security issue and not that it never will. Disclosure lags discovery, and OSV’s coverage varies by ecosystem. Treat a clean advisory row as an absence of evidence, not evidence of absence.

Sparse release history lowers confidence, and the page says so

A package with fewer than two recorded release dates gets marked "Low confidence" on its own page, because the algorithm can’t establish a real cadence from one data point. The score still computes, it just carries that flag rather than pretending the history is richer than it is.

Every score is a snapshot, not a live reading

The number on a package’s page reflects data pulled at the most recent build, at most a week old under the refresh schedule below. A critical advisory disclosed an hour ago won’t appear until the next rebuild picks it up.

Check the numbers yourself.

Every score here is reproducible. Run the same three public requests for a package and you get the same figures, or paste your own dependency manifest into the free tool and get the same deterministic scoring, no signup. The whole index is published as a machine-readable dataset under CC BY 4.0, for anyone building on top of it.