Try it live: https://haunted-lockfile.designmaster.workers.dev (click "try the demo lockfile")

Inspiration

Every JavaScript project carries hundreds of packages nobody looks at after npm install. Some were deprecated years ago, some stopped getting releases, and a few still carry a known advisory. npm audit prints a wall of text that people learn to ignore. We wanted the dead ones to be impossible to miss, so the lockfile becomes a graveyard.

What it does

Paste or drop a package-lock.json and every dependency lands in one of four plots, from live data (deps.dev, the OSV vulnerability database and the npm registry):

  • Zombie: stopped moving but still carries a known advisory (for example [email protected], deprecated, 6.7 years without a release, GHSA-p8p7-x288-28g6 SSRF).
  • Ghost: deprecated or silent for years.
  • Tombstone: finished, no release in years, no advisory.
  • Alive: maintained.

Tombstones grow with the years since the last release, the fog thickens with the share of risky packages, and zombie hands rise from the graves that still have advisories. Click any grave for the locked version, last release, deprecation notice, advisories and the next step. A table view lists the same verdicts, and Export Report gives a file to attach to a pull request. Nothing about your project is stored.

How we built it

Spec-first in Kiro (kiro-cli chat, agent mode):

  • Steering (.kiro/steering/): product, tech (Cloudflare Worker, TypeScript, no framework) and structure, before any code.
  • Spec (.kiro/specs/haunted-lockfile/): 32 acceptance criteria in EARS notation, a design document with the classify() decision tree and Worker batching, and an ordered task list Kiro worked through.
  • Hooks (.kiro/hooks/): npm test and the type check run on every save of a source or test file.

The first version was written by Kiro from that spec. After Kiro's quota ran out we kept going by hand against the same spec and tests: grave rows on their own ground line, the demo lockfile with all four plots, the zombie hand in the right row.

Tests: property-based tests with fast-check (the classifier always returns exactly one plot, a package with no advisory is never a zombie, no malformed date crashes it) and fixtures saved from the real deps.dev, OSV and npm APIs. 67 tests, all green.

Challenges we ran into

SVG percentages are relative to the whole viewBox, so a translateY(40%) zombie hand dropped into the row below once the graveyard had two rows. Large lockfiles (150+ packages) also stacked every grave on one line until the layout gave each row its own ground.

Accomplishments that we're proud of

It runs on a real lockfile in seconds, it is honest about its sources (each panel says which of deps.dev, OSV and npm answered), and it fits the spooky theme without hiding the actual answer: what to replace next.

What we learned

Writing EARS requirements first made the classifier boring in the best way: every edge case had a criterion and a test before it had code.

What's next

yarn.lock and pnpm-lock support, a GitHub Action that comments the graveyard on pull requests, and suggested replacements for each zombie.

Built With

  • cloudflare-workers
  • deps.dev
  • fast-check
  • kiro
  • npm
  • osv
  • svg
  • typescript
  • vitest
Share this project:

Updates

Submission history