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 nine real, local metrics — no external API calls, no secrets needed. It surfaces the files most likely to cause you pain, explains why they are dying, and tells you exactly what to do first.
v3.0.0 adds decay prediction, an interactive TUI, a terminal heat map, a git regression guard, and multi-language complexity analysis (JS/TS/Go/Rust).
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
For the interactive TUI (--interactive), install the optional textual dependency:
pip install "deathbed[interactive]"
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
# Scan an entire org directory of git repos
deathbed --org /path/to/projects
# Drill into one repo from an org scan
deathbed --org /path/to/projects --repo myapp
# Generate a prioritised refactor plan (Sprint 1/2/3 roadmap)
deathbed --plan
# Write a GitHub Actions workflow to .github/workflows/deathbed.yml
deathbed --init-ci
# Print a shields.io health badge for your README
deathbed --badge
# Interactive TUI with keyboard navigation (requires: pip install deathbed[interactive])
deathbed --interactive
# Terminal heat map — file sizes proportional to lines, coloured by health score
deathbed --heatmap
# Install a post-commit git hook to catch regressions automatically
deathbed --install-hook
# Remove the deathbed post-commit hook
deathbed --uninstall-hook
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 |
--org PATH |
— | Scan a directory of git repos and show an org-wide health table |
--repo NAME |
— | With --org: drill into a single repo by name |
--plan |
— | Generate a prioritised Sprint 1/2/3 refactor roadmap |
--init-ci |
— | Write a GitHub Actions workflow to .github/workflows/deathbed.yml |
--badge |
— | Print a shields.io Markdown health badge for your README |
--interactive, -i |
— | Launch interactive TUI (requires deathbed[interactive]) |
--heatmap |
— | Render a terminal treemap coloured by health score |
--install-hook |
— | Install a post-commit regression guard hook in .git/hooks/ |
--uninstall-hook |
— | Remove the deathbed post-commit hook |
--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 nine weighted sub-scores:
| # | Metric | Weight | What it measures |
|---|---|---|---|
| 1 | Size | 11.7% | Lines of code — penalises files > 300 / 600 / 1000 lines |
| 2 | Age | 11.7% | Days since any commit touched this file — flags abandoned code |
| 3 | Churn | 8.1% | Total number of commits — instability signal |
| 4 | Complexity | 16.2% | Cyclomatic complexity average — Python (radon), JS/TS (heuristic), Go (gocyclo), Rust (match-arm count) |
| 5 | Authors | 10.8% | Unique git authors — many authors = diffused ownership |
| 6 | Test coverage | 8.1% | Whether a corresponding test file exists and has real assertions |
| 7 | Recent churn | 14.4% | Commits in the last 90 days — hotspot detection |
| 8 | Dead code | 9.0% | Unused symbols (vulture for Python; annotations/markers for JS/TS/Go/Rust) |
| 9 | Coupling | 10.0% | How many other files import this file — high coupling = fragile hub |
Language support
| Language | Complexity | Dead code |
|---|---|---|
Python (.py) |
radon cyclomatic complexity | vulture unused symbols |
JavaScript / TypeScript (.js .ts .jsx .tsx) |
function/class/arrow heuristic | TODO/FIXME/DEAD comment density |
Go (.go) |
gocyclo subprocess (graceful fallback) | #[allow(dead_code)] equivalent markers |
Rust (.rs) |
match-arm + function count heuristic | #[allow(dead_code)] annotation count |
| All others | N/A (skipped, not penalised) | N/A |
Go complexity requires gocyclo to be installed (go install github.com/fzipp/gocyclo/cmd/gocyclo@latest). If it is not present deathbed silently skips complexity for .go files — no crash, no error.
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) |
god file |
High coupling (5+ importers) AND already complex AND large — the worst kind of hub |
clone risk |
File is >40% similar to another file — likely a copy-paste |
test theatre |
Has a test file but the test contains zero assertions — false safety net |
dead code cemetery |
High vulture score — lots of unused symbols |
haunted |
5+ authors AND high complexity AND still churning — too many cooks, no one understands it |
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 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.
Coupling
The COUP column in the main table shows how many other files import each file. Files with 5+ importers are flagged as coupling hotspots and counted in the SCAN COMPLETE bar.
A dedicated MOST COUPLED panel lists the top 3 most-imported files and shows which files depend on them. When a file is both heavily coupled and complex and large, it receives the god file diagnosis.
Decay prediction
After three or more scans, deathbed uses linear regression on ~/.deathbed/history.json to forecast when each file's score will cross a threshold:
- An ETA column appears in the table showing days until the file crosses WARNING (65) or CRITICAL (40)
- A DECAY FORECAST panel lists every declining file with its slope (points/week) and estimated crossing date
- The SCAN COMPLETE bar shows a
DECAYINGcounter — files projected to cross a threshold within 30 days - The Most Wanted panel shows slope and ETA for the single worst file
Thresholds and the forecast horizon are configurable in .deathbed.toml.
Interactive TUI
deathbed --interactive
# or
deathbed -i
Requires: pip install "deathbed[interactive]" (installs textual). If textual is not installed, deathbed falls back to the standard Rich output automatically.
The TUI provides:
| Key | Action |
|---|---|
↑ / ↓ |
Navigate the file table |
Enter |
Open detail view for the selected file |
p |
Open the Sprint refactor plan screen |
/ |
Filter files by name |
s |
Cycle sort column (score / file / diagnosis) |
q / Esc |
Go back / quit |
The detail screen shows the full metric breakdown, sparkline history, decay forecast, and the specific refactoring action recommended for that file.
Terminal heat map
deathbed --heatmap
Renders a treemap directly in the terminal. Each file is represented as a block whose area is proportional to its line count and whose colour reflects its health score:
| Colour | Score range |
|---|---|
| 🟥 Red (█) | CRITICAL (0–40) |
| 🟧 Orange (▓) | WARNING (41–65) |
| 🟨 Yellow (▒) | FAIR (66–85) |
| 🟩 Green (░) | HEALTHY (86–100) |
Requires a terminal at least 80 columns wide. Falls back to a plain sorted list if the terminal is too narrow.
Regression guard
# Install the hook (one-time, per repo)
deathbed --install-hook
# Remove it
deathbed --uninstall-hook
Installs a post-commit git hook that re-runs deathbed after every commit and checks for score regressions vs the previous scan:
- Warn (default: drop ≥ 10 points) — prints a ⚠️ warning but lets the commit through
- Block (default: drop ≥ 20 points) — prints a 🚨 alert and exits with code 1, preventing the push
Thresholds are configurable in .deathbed.toml:
[guard]
warn_drop = 10 # points drop that triggers a warning
block_drop = 20 # points drop that blocks the commit
The hook is idempotent — safe to run --install-hook multiple times. If a post-commit hook already exists and was not created by deathbed, the install will fail with a clear error rather than silently overwriting your hook.
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.
Multi-repo org scanning
# Scan every git repo in a directory
deathbed --org /path/to/projects
# Drill into one specific repo
deathbed --org /path/to/projects --repo myapp
Scans every immediate subdirectory that is a git repository and displays an org-wide health table sorted worst-first:
| Column | Meaning |
|---|---|
| GRADE | Letter grade A–F for the repo |
| REPO | Repository directory name |
| SCORE | Overall repo health score (0–100) |
| CRITICAL | Number of CRITICAL files |
| WARNING | Number of WARNING files |
| FILES | Total files scanned |
| WORST FILE | The single most unhealthy file |
| WORST SCORE | Its health score |
A combined ORG SCORE is shown at the bottom — the average across all successfully scanned repos.
Refactor planner
deathbed --plan
# Export the plan as Markdown
deathbed --plan --format markdown
Generates a prioritised Sprint 1 / 2 / 3 roadmap from the current scan results. Each item includes:
- The file and its diagnosis
- A concrete, per-diagnosis action (e.g. "Split into smaller modules", "Replace pickle with json", "Add assertions to test")
- Effort estimate: Small, Medium, or Large
- Current health score
Sprint 1 = CRITICAL files (do this week). Sprint 2 = WARNING files (do this month). Sprint 3 = FAIR files (do this quarter).
GitHub Actions integration
Auto-setup
deathbed --init-ci
Writes a ready-to-use workflow to .github/workflows/deathbed.yml that runs deathbed --ci on every push and pull request. Commit and push the file to activate it.
Health badge
deathbed --badge
Prints a shields.io Markdown badge reflecting the current repo score. Paste it into your README:

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. 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:
| 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.
Configuration (.deathbed.toml)
Create a .deathbed.toml file in your repo root (or ~/.deathbed/config.toml for global defaults) to override any setting. Repo config takes precedence over global config.
[thresholds]
warning = 65 # score below which a file is WARNING
critical = 40 # score below which a file is CRITICAL
[guard]
warn_drop = 10 # post-commit: warn when score drops this many points
block_drop = 20 # post-commit: block commit when score drops this many points
[org]
exclude = ["archived-repos", "vendor"] # repo names to skip in --org scans
[decay]
min_scans = 3 # minimum historical scans before decay prediction kicks in
horizon_days = 30 # only show ETA if threshold will be crossed within this many days
All sections and keys are optional — omit any you don't need. deathbed falls back to built-in defaults if TOML libraries (tomllib / tomli) are not available.
Ignore file (.deathbedignore)
Create a .deathbedignore file in your repo root using the same gitignore syntax to permanently exclude files from 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:
{
"version": "3.0.0",
"repo": "/path/to/repo",
"total": 3,
"files": [
{
"file": "src/legacy/monster.py",
"health_score": 22,
"status": "CRITICAL",
"diagnosis": "god file",
"lines": 1284,
"days_since_commit": 847,
"commit_count": 134,
"author_count": 9,
"avg_complexity": 18.3,
"has_test_file": false,
"test_has_assertions": false,
"dead_code_count": 12,
"has_security_smell": true,
"security_smells": ["imports pickle", "calls eval()"],
"clone_similarity": 0.0,
"clone_of": "",
"coupling_count": 7,
"importers": ["src/api/handler.py", "src/utils/loader.py"],
"scores": {
"size": 0, "age": 5, "churn": 15,
"complexity": 2, "authors": 20, "test": 10,
"recent_churn": 40, "dead_code": 20, "coupling": 40
}
}
]
}
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) 2026 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-3.1.0.tar.gz.
File metadata
- Download URL: deathbed-3.1.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 |
ac22979c8786f451e079e44a5ac7b0b01eec36d18cb7ba50786f3c77ca5f9657
|
|
| MD5 |
3e63bba00fa201ab21f44be719630522
|
|
| BLAKE2b-256 |
b5bc89d409e55808985afa0392d805cb194b3218c63d2e3611d3cf523e8e4bbc
|
File details
Details for the file deathbed-3.1.0-py3-none-any.whl.
File metadata
- Download URL: deathbed-3.1.0-py3-none-any.whl
- Upload date:
- Size: 67.5 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 |
3575130eb6f8eb3e6f2591320ae8a3d53d0f4bf7953d27252a0b4460c7f8d80f
|
|
| MD5 |
d8b415a6969cb74ef229c27a6da5d79c
|
|
| BLAKE2b-256 |
8c9b0ae7fdb95ec7c206ae521aa4bb31f255942a6a42d360568f6f84a018d396
|