Debt Tracking & Metrics
Measure debt objectively with trend lines so paydown is visible and the policy is data-driven.
Why
Subjective debt assessment ("the codebase feels messy") is unactionable and demoralizing — you can't trend a feeling. Objective metrics — cyclomatic complexity, duplication %, test coverage, outdated-dep count, TODO count — turn debt into numbers you can plot, trend, and set targets against. The act of measuring also changes behavior: when complexity is on a dashboard, engineers refactor to move the number. Without metrics, debt paydown is invisible to leadership, so it gets cut; with metrics, a downward trend is a visible win that sustains the investment. The goal isn't a single number but a small panel of orthogonal metrics that together describe codebase health.
When
Set up the metrics once in CI, then trend them every sprint. Alert if any metric degrades beyond a threshold.
How
1. Pick 4-6 orthogonal metrics: complexity, duplication %, coverage, outdated-dep count, TODO(debt) count, build time 2. Instrument: eslint (complexity/dup), c8/istanbul (coverage), npm outdated (deps), grep (TODOs) 3. Emit to a dashboard: Code Climate, SonarQube, or a simple JSON + Grafana 4. Set thresholds: fail CI if complexity >15 per function, coverage <70%, or outdated-deps >10 5. Trend per sprint: store a snapshot, plot the line, review in sprint review 6. Alert on regression: if a PR raises complexity by >5 or drops coverage by >2%, block it 7. Publish the dashboard to the whole team — visibility is the point
Example
# CI debt-metrics job (GitHub Actions)
- name: Complexity & duplication
run: npx eslint --rule '{"complexity":["warn",15]}' --format json src/ > complexity.json
- name: Coverage
run: npx c8 --reporter=json npm test
- name: Outdated deps
run: npm outdated --json > outdated.json
- name: TODO count
run: grep -rc "TODO(debt)" src/ | wc -l > todo-count.txt
# Threshold gate (fail the PR)
node scripts/check-debt-thresholds.js
# if complexity > 15 avg || coverage < 70 || outdated > 10 -> exit 1
# Trend snapshot (stored per sprint)
# debt-snapshots/sprint-42.json
{ "complexity": 8.2, "duplication": 3.1, "coverage": 78,
"outdated": 6, "todos": 14, "buildMs": 42000 }
# Plot in Grafana; review the trend line in sprint review.Common Problems
- ⚠Tracking one metric (coverage) and gaming it — 100% coverage with assertion-free tests is worse than 60% with real assertions.
- ⚠Metrics in CI but not on a dashboard — engineers can't see the trend, so the number doesn't drive behavior.
- ⚠No regression gate — a PR that raises complexity by 10 ships because CI only checks "does it build," not "does it rot."
Best Practices
- ✓Track 4-6 orthogonal metrics, not one — a single metric is gameable; a panel is honest.
- ✓Put a regression gate in CI that blocks PRs degrading any metric beyond a threshold — prevent rot, don't just measure it.
- ✓Publish the trend dashboard to the whole team and review it in every sprint review — visibility drives paydown.