Find the files in your codebase that are dying before they kill you.
Project description
██████╗ ███████╗ █████╗ ████████╗██╗ ██╗██████╗ ███████╗██████╗
██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██║ ██║██╔══██╗██╔════╝██╔══██╗
██║ ██║█████╗ ███████║ ██║ ███████║██████╔╝█████╗ ██║ ██║
██║ ██║██╔══╝ ██╔══██║ ██║ ██╔══██║██╔══██╗██╔══╝ ██║ ██║
██████╔╝███████╗██║ ██║ ██║ ██║ ██║██████╔╝███████╗██████╔╝
╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝╚═════╝
every codebase has files that are dying. find them.
deathbed analyses every tracked source file in a git repository and gives it a health score based on eight real, local metrics — no external API calls, no secrets needed. It then surfaces the files most likely to cause you pain, explains why they are dying, and tells you exactly what to do first.
Why?
Every codebase accumulates rot. Files that nobody owns. Files too complex to understand. Files last touched three years ago by someone who left. Files importing pickle in a web handler. These files never show up in sprint planning, but they quietly cause the most bugs, the slowest onboarding, and the worst incidents.
deathbed makes the invisible visible.
Install
pip install deathbed
Or, to hack on it:
git clone https://github.com/NikoloziKhachiashvili/deathbed
cd deathbed
pip install -e ".[dev]"
Usage
# Analyse the current git repo
deathbed
# Analyse a different repo
deathbed --path /path/to/repo
# Show only the 20 worst files
deathbed --top 20
# Show only WARNING and CRITICAL files (score < 65)
deathbed --min-score 65
# Output JSON for CI pipelines / scripting
deathbed --format json
# Output a Markdown table (for GitHub comments etc.)
deathbed --format markdown
# Live auto-refreshing dashboard (re-runs every 30s)
deathbed --watch
# Compare health scores between HEAD and HEAD~1
deathbed --diff HEAD~1
# Compare HEAD against any ref
deathbed --diff main
# Export a self-contained HTML report
deathbed --export html
# CI mode — exit 1 if any CRITICAL files are found
deathbed --ci
# Combine flags
deathbed --path ~/projects/myapp --top 10 --format json
deathbed --min-score 70 --ci
# PR mode — only files changed since main
deathbed --since main
# Show last author in the table and Most Wanted panel
deathbed --blame
# Team leaderboard by last-commit author
deathbed --leaderboard
Options
| Flag | Default | Description |
|---|---|---|
--path, -p |
. |
Path to the git repository |
--top, -t |
50 |
Show only the N worst files (0 = all) |
--min-score |
— | Only show files with a health score below this value |
--format, -f |
rich |
Output format: rich, json, or markdown |
--watch, -w |
— | Live auto-refreshing dashboard (30s interval) |
--diff REF |
— | Compare health scores between HEAD and REF |
--export html |
— | Export a self-contained HTML report to deathbed-report.html |
--ci |
— | Exit code 1 if any CRITICAL files found (for CI pipelines) |
--since REF |
— | PR mode — restrict to files changed since REF (e.g. main) |
--blame |
— | Show last-author column in table and blame info in Most Wanted |
--leaderboard |
— | Team view: authors ranked by files needing support |
--version, -V |
— | Show version and exit |
What it looks like
When you run deathbed you get:
Metrics explained
Each file receives a composite health score from 0–100 (higher is healthier), built from eight weighted sub-scores:
| # | Metric | Weight | What it measures |
|---|---|---|---|
| 1 | Size | 13% | Lines of code — penalises files > 300 / 600 / 1000 lines |
| 2 | Age | 13% | Days since any commit touched this file — flags abandoned code |
| 3 | Churn | 9% | Total number of commits — instability signal |
| 4 | Complexity | 18% | Radon cyclomatic complexity average — Python only; N/A otherwise |
| 5 | Authors | 12% | Unique git authors — many authors = diffused ownership |
| 6 | Test coverage | 9% | Whether a corresponding test file exists anywhere in the repo |
| 7 | Recent churn | 16% | Commits in the last 90 days — hotspot detection |
| 8 | Dead code | 10% | Unused functions/classes/variables detected by vulture (Python only) |
Health thresholds
| Score | Status | Meaning |
|---|---|---|
| 86–100 | ✅ HEALTHY | All good |
| 66–85 | 🌡 FAIR | Minor issues |
| 41–65 | ⚠️ WARNING | Needs attention soon |
| 0–40 | 💀 CRITICAL | Actively dangerous |
Diagnoses
deathbed automatically picks the most meaningful diagnosis:
| Diagnosis | What it means |
|---|---|
security smell |
File imports dangerous patterns (pickle, eval, exec, os.system, subprocess shell=True) |
clone risk |
File is >40% similar to another file — likely a copy-paste |
dead code cemetery |
High vulture score — lots of unused symbols |
ownership void |
Abandoned for 6+ months and only ever touched by 1 author |
complexity graveyard |
Cyclomatic complexity is extremely high |
legacy ghost |
Not touched in years — likely orphaned |
too many cooks |
Many authors, nobody owns it |
churn monster |
Modified constantly — unstable abstraction |
growing out of control |
Large and getting larger |
nobody's watching this |
Old code with no test coverage |
abandoned and complex |
Old and hard to understand |
healthy |
Nothing to worry about |
Any diagnosis can gain the 🔥 heating up suffix when recent commit activity has spiked 2× over the prior 90-day window.
Trend arrows in the table
The RECENT column now shows trend arrows alongside the 90-day commit count:
| Arrow | Meaning |
|---|---|
| ▲ | Recent churn is 1.5× higher than the prior 90 days — hotspot forming |
| ▼ | Activity has dropped significantly — cooling down |
| ━ | Stable activity or insufficient data |
SECURITY ALERTS panel
If any files contain dangerous import or call patterns, a dedicated red SECURITY ALERTS panel appears below the main report, listing every affected file and the specific patterns detected.
Live watch mode
deathbed --watch
Re-scans the repository every 30 seconds, clears the screen, and reprints the full report. Press Ctrl+C to stop.
Diff mode
deathbed --diff HEAD~1
deathbed --diff main
Compares health scores between the current state (HEAD) and any historical git ref. Shows ▲ improved / ▼ worsened / ━ unchanged with exact deltas.
HTML export
deathbed --export html
Writes a fully self-contained deathbed-report.html to the current directory. The report mirrors the terminal UI with:
- Dark red/green colour scheme
- Sortable table (click any column header)
- Score gauges per file
- Most Wanted breakdown
- Quick Wins list
- Security Alerts (if any)
No external dependencies — one file, works offline.
CI integration
# In .github/workflows/ci.yml or similar:
deathbed --ci --min-score 50
Exits with code 1 if any files are CRITICAL (score ≤ 40), printing a list of offending files to stderr. Exits 0 otherwise.
Trend history (▲▼━ in the TREND column)
deathbed stores a rolling history of up to 10 scans per repo in ~/.deathbed/history.json. On subsequent runs the TREND column appears in the table, showing each file's score delta vs the previous scan:
| Symbol | Meaning |
|---|---|
▲ +N |
Score improved by N points since last scan |
▼ -N |
Score worsened by N points since last scan |
━ 0 |
No change |
The Most Wanted panel also shows a 5-character sparkline (▁▂▃▄▅▆▇█) built from the file's score history.
The SCAN COMPLETE panel shows the repo's overall weighted score (0–100) with a letter grade (A–F) and delta vs the previous scan.
PR mode
deathbed --since main
deathbed --since HEAD~5
Restricts the scan to files that have changed between <REF> and HEAD (using git diff --name-only <REF>...HEAD). The SCAN COMPLETE panel notes "PR mode — N files changed since ". Ideal for per-PR health gates in code review.
Blame mode
deathbed --blame
Adds a LAST AUTHOR column to the table showing who last committed to each file. The Most Wanted panel also shows the last commit author and subject line.
Team leaderboard
deathbed --leaderboard
Runs a full blame-enriched scan and groups results by last-commit author, showing:
| Column | Meaning |
|---|---|
| AUTHOR | Git author name |
| FILES | Number of files owned (last commit) |
| AVG SCORE | Average health score across owned files |
| CRITICAL | Files in CRITICAL state |
| WARNING | Files in WARNING state |
| GRADE | Letter grade A–F |
Sorted by most at-risk first. Framed as who needs support, not a blame ranking.
Ignore file (.deathbedignore)
Create a .deathbedignore file in your repo root using the same gitignore syntax to permanently exclude files from deathbed analysis:
# .deathbedignore
vendor/**
legacy/old_migration.py
generated/**/*.py
The SCAN COMPLETE panel reports how many files were ignored.
JSON output
--format json returns a machine-readable object with all v1.2.0+ fields:
{
"version": "1.3.0",
"repo": "/path/to/repo",
"total": 3,
"files": [
{
"file": "src/legacy/monster.py",
"health_score": 22,
"status": "CRITICAL",
"diagnosis": "security smell",
"lines": 1284,
"days_since_commit": 847,
"commit_count": 134,
"author_count": 9,
"avg_complexity": 18.3,
"has_test_file": false,
"dead_code_count": 12,
"has_security_smell": true,
"security_smells": ["imports pickle", "calls eval()"],
"clone_similarity": 0.0,
"clone_of": "",
"scores": {
"size": 0, "age": 5, "churn": 15,
"complexity": 2, "authors": 20, "test": 20,
"recent_churn": 40, "dead_code": 20
}
}
]
}
Markdown output
deathbed --format markdown
Emits a GitHub-Flavored Markdown table suitable for pasting into PR comments or issue trackers.
Supported file types
.py .js .ts .jsx .tsx .go .rs .rb .java .cpp .c .cs .php .swift .kt
Automatically skipped: node_modules, venv, dist, build, .git, binary files, lock files, and everything matched by .gitignore or .deathbedignore.
Contributing
- Fork the repo
- Create a branch:
git checkout -b feat/my-idea - Make your changes and run tests:
pytest - Run deathbed against itself:
deathbed - Open a pull request
Bug reports and feature ideas welcome via Issues.
License
MIT License — Copyright (c) 2024 Nikolozi Khachiashvili
Made with 💀 and Rich
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deathbed-1.3.0.tar.gz.
File metadata
- Download URL: deathbed-1.3.0.tar.gz
- Upload date:
- Size: 5.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63489166c3a8a2909d3fffe2071dcde5b80e74b2ecccc6f5821887022df6233e
|
|
| MD5 |
ca34c4fe66f6a969d3886f2002c7158b
|
|
| BLAKE2b-256 |
b7f11e098ca34e3513476f7b79f8a92dd6f758c919830ae5c58ae229685bfe5b
|
File details
Details for the file deathbed-1.3.0-py3-none-any.whl.
File metadata
- Download URL: deathbed-1.3.0-py3-none-any.whl
- Upload date:
- Size: 36.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bdc1d20b37dc287be40d9fc213222bf0f794d5dc81ccf48868ad2afcbe5be5c1
|
|
| MD5 |
8b1915342c540d2c41519d2a841e7fae
|
|
| BLAKE2b-256 |
cb9f58d2e299d7786aa08a60e672fda15e3c255cbb84660d6b28efc7564d7d9b
|