All work

debtctl

Govern dependency overrides as documented technical debt — and fail CI when they go stale.

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 review

Scope

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.