debtctl
Govern dependency overrides as documented technical debt — and fail CI when they go stale.
- TypeScript
- CLI
- Node.js
The problem
Overrides — npm overrides, pnpm.overrides, yarn resolutions — are routinely added to patch a CVE, force peer compatibility, or pin a transitive dependency. They are almost always meant to be temporary. They are almost never reviewed again.
debtctl treats them as what they actually are: technical debt. A sidecar file (.debtctl.json) records why each override exists, who owns it, and when it should be revisited — then surfaces stale overrides in CI before they rot for another two years.
How it works
It enforces three pieces of metadata next to every override and fails CI when one drifts past its expected lifetime. No metadata, no merge. No revisit trigger, no merge. If a reviewer changes an override's range but forgets to update the rationale, CI catches it. A revisit can be anchored three ways:
- version-anchor (recommended for overrides) — fires when the declared range for the package no longer matches what was recorded, so a fix landing upstream automatically prompts a revisit; no human-set deadline required.
- date — fires on or after an explicit expiry, for when there is no natural dependency or patch to anchor against.
- patch-hash (recommended for patches) — fires when a patch file's content hash drifts, catching silent edits to patch-package, pnpm, or yarn-berry patches.
Not a replacement for Renovate / Dependabot
Renovate and Dependabot bump dependencies — they open PRs when a new version is available. They say nothing about why an override exists, who owns it, or when to revisit it. debtctl is the layer next to them: they open the bump PR; debtctl fails CI when the override pinning it has no rationale, no owner, or has drifted past its trigger.
$ debtctl check
Missing metadata (1):
- some-package
Incomplete (1):
- other-package: TODO fields present
Due for review (1):
- third-package: Expired on 2025-09-01
✖ 3 problems: 1 missing, 1 incomplete, 1 due for reviewScope
It does not patch your package.json, run installs, or talk to a registry — it only reads, classifies, and reports, across npm, pnpm, and yarn (classic and berry). Four small runtime dependencies, exit codes designed for CI, and a versioned sidecar schema that auto-migrates older files on read.