orphan
The bus factor, made concrete.
Not an abstract score — the named list of files whose knowledge sits with a single person, sorted by how much is actually at risk.
What it does
Every mature codebase has files only one person has ever really touched. Some are harmless. Some are a terraform module or an nginx.conf holding up production, understood by exactly one engineer — who may have left months ago.
orphan reads your git history and surfaces those files by name, with the owner, the age of the last commit, and a risk category. It reads history only, never file contents, so it works on any repository regardless of language — application code and infrastructure alike.
The three categories
Age alone is misleading: a LICENSE written once and never touched looks exactly like a dangerous orphan, but it's the healthiest file in the repo. orphan separates stable from risky using two axes — how much a file has evolved (commit count, a proxy for accumulated knowledge) and whether its sole owner is still active.
| Category | Meaning | Signal |
|---|---|---|
| ◆ silo | Many commits, one owner, still active | Live, concentrated knowledge. One brain holds a growing area. |
| ✖ orphan | Many commits, one owner, now inactive | The knowledge has likely already walked out the door. |
| · stable | Few commits (a base or config file) | Low risk. Shown for completeness, not as an alarm. |
The stable bucket exists on purpose: it's where the classic false positives land (base files an author wrote once before moving on), so they stop drowning out the real risks.
Why you'll like it
- Zero dependencies. Pure Python standard library, 3.8+.
- Portable. A single script; drop
orphan.pyanywhere and run it. - Language-agnostic. Works on Terraform, Ansible, shell, YAML — anything git tracks, not just source code.
- Localized output. English, French, German, Spanish, Italian (see Languages).
- Scriptable.
--jsonfor pipelines and dashboards.
Installation
From PyPI
pip install orphan
Then, in any repository:
orphan
From source (no install)
The tool is a single file. Grab orphan.py (and locales.json if you want non-English output), then:
python3 orphan.py
That's it — nothing to build, nothing to install. orphan.py alone runs in English; add locales.json beside it to unlock the other languages.
Usage
orphan # analyze the current repository
orphan --lang fr # localized output (en, fr, de, es, it)
orphan --dense-commits 8 # raise the silo/orphan vs stable threshold
orphan --active-months 6 # "inactive owner" cutoff (default: 12 months)
orphan --threshold 2 # also consider files with up to 2 authors
orphan --top 40 # cap the displayed rows (0 = all)
orphan --path infra/ # restrict to a subdirectory
orphan --json # machine-readable output (never localized)
Options
| Option | Default | Description |
|---|---|---|
--lang |
en |
Output language: en, fr, de, es, it. |
--path |
(repo root) | Restrict analysis to a subdirectory. |
--threshold |
1 |
Max distinct authors for a file to be considered single-owned. |
--dense-commits |
5 |
Commits from which a file counts as knowledge-dense (silo/orphan vs stable). |
--active-months |
12 |
An author with no commit for N months counts as inactive. |
--top |
25 |
Show only the N riskiest files (0 for all). |
--json |
(off) | Emit raw JSON instead of the terminal report. |
--no-color |
(off) | Disable ANSI colors. |
JSON output
--json emits stable, language-neutral data — ideal for CI gates or a dashboard:
{
"summary": { "total": 12, "silo": 3, "orphan": 4, "stable": 5, "tracked_files": 210, "dense_commits": 5 },
"files": [
{
"path": "infra/pipeline.tf",
"kind": "infra",
"category": "orphan",
"owners": ["Bob"],
"owner_count": 1,
"commits": 8,
"last_commit": "2024-05-01T10:00:00+00:00",
"age_days": 800,
"departed": true
}
]
}
Languages
Switch the display language with --lang. English is built into the script; the other languages live in locales.json next to it. If that file is missing or a string is absent, the output falls back to English.
Adding a language (no code required)
Translations are pure data. To add, say, Portuguese:
- Open
locales.json. - Copy the
"en"block — it's the reference and lists every key. - Rename it
"pt"and translate the values. Keep the{placeholders}intact. - For the age units, note the
_one/_otherpairs — they exist so plurals read correctly (e.g.1 mêsvs6 meses). - Add
"pt"to the--langchoices inorphan.py(theLANGUAGEStuple). - Open a pull request. 🎉
How it works
A single git log pass over the whole repository collects, per file, the set of authors and the date of the last commit, plus each author's most recent activity anywhere in the repo. %aN applies your .mailmap, so one person committing under several emails counts once. Files are then bucketed into the three categories and sorted so the densest live silos rise to the top.
One git invocation for the whole repo — fast even on large histories.
Limitations
Honest by design:
- Commit count is a proxy for knowledge, not size. A large file written in a single commit reads as
stableeven if it holds a lot. - Renames aren't followed in a repo-wide log, so a freshly renamed file may look single-owner even if it had history under its old name.
- Ownership is per commit, not per line. "Who has ever touched this file" is the right orphan signal and far faster than running
git blameper file. - "Inactive" is inferred from git activity, not HR data. Someone working on another repo for a year will read as inactive. Tune
--active-monthsto your team's rhythm.
Contributing
Issues and pull requests welcome — a new translation, a smarter category heuristic, an extra infra file type. Adding a language needs no Python (see above).
License
MIT — do what you like, no warranty.
Metadata
Release files for orphan 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| orphan-0.2.0.tar.gz | 15.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| orphan-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.3 kB
Release files / orphan-0.2.0.tar.gz
| Download URL | orphan-0.2.0.tar.gz |
|---|---|
| Size | 15.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0ffba7fb82f95b3b7c21e02e3330f445914f963399638c62ffed7d9520cad2b1
|
|
BLAKE2b-256 checksum How to use checksums |
ae3c98717216ec5e545babe71725ef909af94d5e05cc22ac0ebce6c88e460bd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|
Release files / orphan-0.2.0-py3-none-any.whl
| Download URL | orphan-0.2.0-py3-none-any.whl |
|---|---|
| Size | 14.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f5c37d70a663d72de720260526702107f7c225b88f92a96f516c31b9cf3daef0
|
|
BLAKE2b-256 checksum How to use checksums |
d1f3a7ec4e7deb274d2807e1f6cc37f2d747fa7eb78df140dbbdd82520f520ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.5
|