Skip to content
Back to skills

Debt Ledger

ASecurity

Track technical debt as a persisted, ranked ledger — dated/owned TODOs, unjustified suppressions, deprecated-API call sites, major-version-lag deps — each with a blast-radius and a fix-cost, diffed run-over-run so accrual (new debt vs paid-down) is visible instead of rediscovered every audit.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 20, 2026
ai-agentsgobashnodegitapisecurity

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 20, 2026

npx -y skills add adnanmokhtar/refract --skill debt-ledger --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Debt Ledger?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Debt Ledger
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adnanmokhtar-debt-ledger/badge)](https://www.skillsdirectory.com/skills/adnanmokhtar-debt-ledger)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: debt-ledger
description: Track technical debt as a persisted, ranked ledger — dated/owned TODOs, unjustified suppressions, deprecated-API call sites, major-version-lag deps — each with a blast-radius and a fix-cost, diffed run-over-run so accrual (new debt vs paid-down) is visible instead of rediscovered every audit.
kind: skill
pack: code-quality
allowed-tools: [Read, Write, Edit, Grep, Glob, Bash]
---

# Skill: debt-ledger

## Premise

Technical debt that gets rediscovered on every audit is debt nobody is accountable for. This skill treats debt as a **persisted, ranked ledger** — a durable artifact at `ai/debt-ledger.md` — not a transient scan result. Every item carries an **owner**, a **date**, a **blast-radius**, and a **fix-cost**, and every run **diffs against the last ledger** so you can see what accrued (new debt) and what was paid down. The point is not to list debt once; it is to make *accrual over time* visible, so intentional debt gets a due date instead of becoming permanent.

Every item cites `<path:line>`. "Debt" here means **intentional, marked, or knowingly-deferred** shortcuts: an undated `TODO`, a suppression with no justification, a call into a deprecated API, a dependency several major versions behind. If it isn't cite-backed and classifiable into one of the four inventory sources below, it doesn't go in the ledger.

## Boundary — what this owns vs siblings

- `dead-code-finder` / `dead-code-scan` find **unreachable** code (delete candidates). Debt is *reachable, running* code that's knowingly suboptimal — the two never overlap.
- `@dependency-auditor` scans dependencies for **vulnerabilities / license issues** (a security posture). This skill records **version-lag as debt** (how far behind, what it costs to catch up) — a maintenance posture, not a CVE list. When a dep is both vulnerable *and* lagging, the auditor owns the CVE; the ledger owns the upgrade-cost tracking.
- `check-health` surfaces debt **point-in-time** as one of many health signals. This skill is the **longitudinal tracker**: it persists, ranks, and diffs debt run-over-run so accrual is a trend, not a snapshot. check-health can cite the ledger's headline number; it does not own the ledger.
- `architectural-diagnosis` finds structural smells (god modules, layer violations). Those are *unmarked* architectural debt; this skill tracks *marked/intentional* debt. An architectural finding may spawn a ledger item (with a due date) once acknowledged.

## Halt conditions

- Refuse to enter an item with no `<path:line>` cite.
- Refuse to rank an item without both a blast-radius AND a fix-cost — an unranked ledger is just a list.
- Do NOT auto-delete or auto-fix. This skill *records and ranks*; remediation is a separate, owned decision.
- Do NOT flag exhaustiveness suppressions or framework-required suppressions (a documented `ts-ignore` for a known upstream type bug *with* a justification comment + issue link is paid-down risk, not open debt) — a suppression WITH a justification is compliant; only the unjustified ones are debt.
- If the previous `ai/debt-ledger.md` is missing, run in **baseline mode** (no diff; the whole ledger is "new"; say so in the header) rather than failing.

## When to use

- Quarterly (or per-release) debt review — the canonical cadence; the diff is the deliverable.
- After a "we'll fix it later" sprint — capture the deferrals as dated, owned items before they evaporate.
- Before planning — the ranked ledger feeds the top-N into the backlog.
- After a major dependency or language-version bump — record the new deprecated-API surface the bump introduced.

## Inventory sources (the four debt kinds)

### 1. Undated / unowned markers

`TODO` / `FIXME` / `HACK` / `XXX` comments with no owner and no date. A marker without an owner and a date is debt with no due date — it never gets paid.

```bash
rg -n --pcre2 '\b(TODO|FIXME|HACK|XXX)\b(?!.*\((@?\w+),?\s*\d{4})' -g '!**/{node_modules,vendor,dist,build}/**' .
# then classify: has owner+date in `(name, 2025-01)` shape → compliant; bare → debt
```

Age each marker via `git blame` / `git log -L` — a `FIXME` that's 18 months old ranks above one added last week.

### 2. Unjustified suppressions

**Consumed from `suppression-scan` (this pack), not re-detected.** That skill owns suppression *detection* — blanket and rule-less directives, a suppressed rule hiding a live diagnostic, a rule switched off or left at warn — and proves each with an unsuppressed re-run. The ledger only *tracks* what it inventoried, run over run. Read two of its pre-flight outputs (`suppression-scan` § Pre-flight greps) instead of grepping again:

- **grep 0b — the inventory**, `production <n> · test <n>`: the headline count this ledger diffs run over run (accrual). Production and test are separate lines; a suppression in a test is debt with a lower blast radius.
- **grep 2a — the rule ids silenced in production, by count**: one ledger item per rule id (`<rule-id> × <count>`), evidence the `<path:line>` list for that id. Its blast radius is the count and whether the rule is a correctness rule (`suppression-scan` § 2's list); its fix-cost is the per-site read.

Classify each item against `suppression-scan`'s verdict for it: a suppression it marked **justified** (a specific rule id, a reason that names why it is safe, line-scoped — `precedents/code-quality.md` § 60) is compliant and is not debt; one it reported as a **Finding** (a live diagnostic behind the directive) is not debt either — it is a defect, already on `/quality-fix`'s list, and the ledger cites it rather than ranking it; everything else (no reason, a pointer reason such as `-- see above`, a blanket directive) is debt. `@ts-expect-error` is *slightly* better than `@ts-ignore` (it fails when the error goes away) but still needs a reason.

**Without a `suppression-scan` run for this commit**, run that skill's grep 0b and 2a lines verbatim from its § Pre-flight greps (they carry the exclusion arrays and the test-path split) and mark the section `inventory only (no unsuppressed re-run)`; never fall back to a bare `rg 'eslint-disable|…'`, which mixes tests, vendored and generated code into the count.

### 3. Deprecated-API call sites

Call sites into APIs marked deprecated (by `@deprecated` JSDoc/annotation, a deprecation shim, or the framework's known-deprecated surface). Each call site is a future breakage the next major bump will force.

```bash
# find deprecated declarations, then find their call sites
rg -n '@[Dd]eprecated|DeprecationWarning|@deprecated' -g '!**/{node_modules,vendor}/**' .
```

Rank by call-site count (blast-radius) and removal timeline (fix-cost / urgency).

### 4. Major-version-behind dependencies

Dependencies ≥ 1 major version behind current. Track the lag magnitude and the migration cost, not just "outdated".

```bash
# ecosystem-specific; e.g.
npm outdated --json 2>/dev/null   # compare current vs latest major
# pip: pip list --outdated --format=json ; go: go list -u -m all ; etc.
```

Record `<pkg> current@X.y → latest@Z.y (N majors behind)`. A dep 3 majors behind with a hard breaking-change history is high fix-cost; one 1 major behind with a codemod is low.

## Ranking

Each item is scored **blast-radius × fix-cost**, then bucketed:

- **blast-radius** — how much breaks / how many sites are affected if this debt bites (call-site count, module count, is-it-on-a-hot-path, does-it-block-an-upgrade).
- **fix-cost** — effort to pay it down (mechanical codemod = low; manual per-site + behavior risk = high).
- **priority** = high blast-radius × low fix-cost first (cheap wins that de-risk a lot), then high × high (the scary ones — schedule, don't ignore), then low × low last.

Age is a multiplier: older debt ranks up (it has survived scrutiny and compounds).

## The persisted artifact — `ai/debt-ledger.md`

```markdown
# Technical debt ledger — <iso date>

> Run at commit <sha>. Previous ledger: <prev iso date @ prev sha> (or "baseline — no prior ledger").
> Accrual since last run: +<new> new, −<paid> paid down, net <±N>. Total open: <count>.

## Accrual diff (headline)

| | count | blast×cost (sum) |
|---|---|---|
| Paid down since last run | <n> | −<score> |
| New since last run | <n> | +<score> |
| Carried (unchanged) | <n> | <score> |
| **Total open** | **<n>** | **<score>** |

## Open debt (ranked)

### D-001 — deprecated-api [P0 · blast=high · cost=low]
- **Kind**: deprecated-api-call-site
- **Evidence**: <path:line> calls `oldClient.fetch()` (deprecated since v4, removed in v6) — 23 call sites
- **Owner**: unassigned → NEEDS OWNER
- **Since**: first seen 2024-11 (this run: carried, age 8mo)
- **Blast-radius**: 23 sites across 6 modules; blocks the v6 upgrade
- **Fix-cost**: low (codemod `oldClient.fetch(x)` → `client.get(x)`)
- **Due**: before v6 bump

### D-002 — unjustified-suppression [P1 · blast=med · cost=med]
- **Kind**: suppression-no-justification
- **Evidence**: <path:line> `// @ts-ignore` with no reason; hides a real type mismatch
- **Owner**: <name> (from git blame)
- **Since**: 2025-03 (this run: new)
- ...

### D-003 — stale-marker [P2 · blast=low · cost=low]
- **Kind**: undated-todo
- **Evidence**: <path:line> `// FIXME: handle the empty case` — 14 months old, no owner
- ...

### D-004 — version-lag [P1 · blast=high · cost=high]
- **Kind**: dependency-major-lag
- **Evidence**: `<pkg>` current@2.4 → latest@5.1 (3 majors behind)
- **Fix-cost**: high (breaking changes across 3 majors; no single codemod)
- ...

## Paid down since last run

- ~~D-0xx~~ suppression at <path:line> — removed / justified ✓
- ~~D-0yy~~ `<pkg>` upgraded 1.x → 2.x ✓
```

The ledger is INTERNAL (like the other pack artifacts); `check-health` / planning surfaces cite its headline numbers, not the whole file.

## Procedure (step-by-step)

1. **Pin commit** — `git rev-parse HEAD` → artifact header.
2. **Load prior ledger** — read the previous `ai/debt-ledger.md` if present; else baseline mode.
3. **Run the four inventory sources** — collect cite-backed candidates from markers, deprecated-API call sites and dep version-lag; read the suppressions from `suppression-scan`'s grep 0b and 2a (source 2), never a second grep.
4. **Filter compliant items** — drop markers with owner+date, suppressions `suppression-scan` judged justified (or reported as Findings — those are defects on `/quality-fix`'s list, cited not ranked), `@ts-expect-error` that self-heal; keep only real debt.
5. **Age each item** — `git blame` / `git log` for first-seen date; compute age.
6. **Rank** — blast-radius × fix-cost × age → priority bucket per item.
7. **Diff against prior ledger** — match by (kind, path-anchor, identity); classify each as new / carried / paid-down. Compute the accrual headline.
8. **Assign owners** — from `git blame` where the marker/suppression has no explicit owner; flag `NEEDS OWNER`.
9. **Write `ai/debt-ledger.md`** — the structure above. No fixes applied.

## The owner + date discipline (the point of the whole skill)

- Every new marker/suppression SHOULD carry `(owner, YYYY-MM)` and a reason — the skill flags the ones that don't and back-fills owners from `git blame`.
- Debt with an owner and a due date gets paid; anonymous undated debt becomes permanent. The ledger exists to convert the latter into the former.
- The run-over-run **net accrual** number is the health signal: net-positive quarter over quarter means debt is compounding faster than it's paid — surface that trend, don't bury it.

## False positives / gotchas

- Generated code, vendored code, and fixtures carry markers/suppressions that aren't yours — exclude `node_modules` / `vendor` / `dist` / `build` and generated-file globs.
- A justified suppression is NOT debt — don't punish teams for documenting a real upstream bug with an issue link.
- `@deprecated` on your *own* API you're mid-migration on is expected; rank by remaining call sites, and drop to zero when the last caller is migrated (that's a paid-down item, celebrate it in the diff).
- Dep "latest" can itself be pre-release / unstable — compare against latest *stable* major, not bleeding edge.

## Related

- `dead-code-scan` — finds unreachable code (delete); disjoint from reachable-but-suboptimal debt.
- `suppression-scan` — detects suppressions and proves which hide a live diagnostic; this ledger consumes its grep 0b (inventory) and 2a (rule ids by count) for accrual.
- `architectural-diagnosis` — unmarked structural debt; may spawn ledger items once acknowledged.
- `@dependency-auditor` — owns dep vulnerabilities/licenses; this skill owns dep version-lag as tracked debt.
- `check-health` — point-in-time health snapshot; cites this ledger's headline, doesn't own it.

## Verify (postconditions)

Contract: [`templates/snippets/postcondition-check.md`](../../../../snippets/postcondition-check.md). **Preconditions are not verification** — this skill guards its entry and must also check its exit. Every entry under `## Failure modes` that this skill's own write could cause becomes a check here, observed rather than assumed, reported when it passes as well as when it fails, and `NOT RUN` with a reason where no signal is available. A failure mode the skill documented and never checked is a risk it accepted on the caller's behalf without saying so.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.