Skip to main content

Find the files in your codebase that are dying before they kill you.

Project description

                     ██████╗ ███████╗ █████╗ ████████╗██╗  ██╗██████╗ ███████╗██████╗
                     ██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██║  ██║██╔══██╗██╔════╝██╔══██╗
                     ██║  ██║█████╗  ███████║   ██║   ███████║██████╔╝█████╗  ██║  ██║
                     ██║  ██║██╔══╝  ██╔══██║   ██║   ██╔══██║██╔══██╗██╔══╝  ██║  ██║
                     ██████╔╝███████╗██║  ██║   ██║   ██║  ██║██████╔╝███████╗██████╔╝
                     ╚═════╝ ╚══════╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝╚═════╝ ╚══════╝╚═════╝

every codebase has files that are dying. find them.

Python 3.9+ License MIT Rich PyPI


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:

deathbed demo


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 DECAYING counter — 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:

![deathbed score](https://img.shields.io/badge/deathbed-82%20B-4caf50?style=flat-square&logo=data:...)

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

  1. Fork the repo
  2. Create a branch: git checkout -b feat/my-idea
  3. Make your changes and run tests: pytest
  4. Run deathbed against itself: deathbed
  5. 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

deathbed-3.1.0.tar.gz (5.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deathbed-3.1.0-py3-none-any.whl (67.5 kB view details)

Uploaded Python 3

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

Hashes for deathbed-3.1.0.tar.gz
Algorithm Hash digest
SHA256 ac22979c8786f451e079e44a5ac7b0b01eec36d18cb7ba50786f3c77ca5f9657
MD5 3e63bba00fa201ab21f44be719630522
BLAKE2b-256 b5bc89d409e55808985afa0392d805cb194b3218c63d2e3611d3cf523e8e4bbc

See more details on using hashes here.

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

Hashes for deathbed-3.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3575130eb6f8eb3e6f2591320ae8a3d53d0f4bf7953d27252a0b4460c7f8d80f
MD5 d8b415a6969cb74ef229c27a6da5d79c
BLAKE2b-256 8c9b0ae7fdb95ec7c206ae521aa4bb31f255942a6a42d360568f6f84a018d396

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page