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.
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.
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.
- Zero dependencies. Pure Python standard library, 3.8+.
- Portable. One self-contained module, no build step — install it or just run it from source.
- 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.
pipx install orphanThen, in any repository:
orphanClone the repo and run it as a module — nothing to build:
git clone https://github.com/Feareis/Orphan
cd Orphan
python3 -m orphanThat's it, no dependencies. English is built in; the other languages come from orphan/locales.json, shipped alongside the code.
orphan # analyze the current repository
orphan ../other-repo # analyze another repository by path
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)| Option | Default | Description |
|---|---|---|
repo (positional) |
. |
Path to the git repository to analyze. |
--lang |
en |
Output language: en, fr, de, es, it. |
--path |
(repo root) | Restrict analysis to a subdirectory of the repo. |
--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 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
}
]
}Switch the display language with --lang. English is built into the code; the other languages live in orphan/locales.json, shipped with the package. If that file is missing or a string is absent — or even mistranslated with a wrong {placeholder} — that line falls back to English instead of failing.
Adding a language (no code required)
Translations are pure data. To add, say, Portuguese:
- Open
orphan/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/cli.py(theLANGUAGEStuple). - Open a pull request. 🎉
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.
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.
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).
MIT — do what you like, no warranty.
