Metadata-Version: 2.4
Name: deathbed
Version: 3.1.0
Summary: Find the files in your codebase that are dying before they kill you.
Project-URL: Homepage, https://github.com/NikoloziKhachiashvili/deathbed
Project-URL: Repository, https://github.com/NikoloziKhachiashvili/deathbed
Project-URL: Issues, https://github.com/NikoloziKhachiashvili/deathbed/issues
Author: Nikolozi Khachiashvili
License: MIT
Keywords: analysis,cli,code-quality,developer-tools,git,technical-debt
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Python: >=3.9
Requires-Dist: click>=8.0
Requires-Dist: gitpython>=3.1
Requires-Dist: pathspec>=0.11
Requires-Dist: radon>=6.0
Requires-Dist: rich>=13.0
Requires-Dist: vulture>=2.7
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: interactive
Requires-Dist: textual>=0.40; extra == 'interactive'
Description-Content-Type: text/markdown

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

<p align="center">
  <em>every codebase has files that are dying.  find them.</em>
</p>

<p align="center">
  <img alt="Python 3.9+" src="https://img.shields.io/badge/python-3.9%2B-crimson?style=flat-square">
  <img alt="License MIT" src="https://img.shields.io/badge/license-MIT-crimson?style=flat-square">
  <img alt="Rich" src="https://img.shields.io/badge/powered%20by-Rich-crimson?style=flat-square">
  <img alt="PyPI" src="https://img.shields.io/pypi/v/deathbed?style=flat-square&color=crimson">
</p>

---

**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

```bash
pip install deathbed
```

For the interactive TUI (`--interactive`), install the optional `textual` dependency:

```bash
pip install "deathbed[interactive]"
```

Or, to hack on it:

```bash
git clone https://github.com/NikoloziKhachiashvili/deathbed
cd deathbed
pip install -e ".[dev]"
```

---

## Usage

```bash
# 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](demo.gif)

---

## 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

```bash
deathbed --interactive
# or
deathbed -i
```

Requires: `pip install "deathbed[interactive]"` (installs [textual](https://github.com/Textualize/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

```bash
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

```bash
# 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`:

```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

```bash
deathbed --watch
```

Re-scans the repository every 30 seconds, clears the screen, and reprints the full report.  Press **Ctrl+C** to stop.

---

## Diff mode

```bash
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

```bash
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

```bash
# 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

```bash
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

```bash
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

```bash
deathbed --badge
```

Prints a shields.io Markdown badge reflecting the current repo score.  Paste it into your README:

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

---

## CI integration

```bash
# 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

```bash
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 <ref>".  Ideal for per-PR health gates in code review.

---

## Blame mode

```bash
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

```bash
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.

```toml
[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:

```json
{
  "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

```bash
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](https://github.com/NikoloziKhachiashvili/deathbed/issues).

---

## License

MIT License — Copyright (c) 2026 Nikolozi Khachiashvili

---

<p align="center">
  Made with 💀 and <a href="https://github.com/Textualize/rich">Rich</a>
</p>
